• Stars
    star
    262
  • Rank 156,136 (Top 4 %)
  • Language
    HTML
  • Created about 13 years ago
  • Updated almost 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Make APIs suck less

chainvas

Chaining for every API

chain•vas |chānvəs| n. A tiny, modular library that can add chaining to any API that isn't naturally chainable, like the Canvas API, the DOM and more.

How does it work?

chainvas goes through all the methods in a prototype and wraps a function around them that returns the returned value or, if that is undefined, the function's context (this). Since the function context is our original object, we can keep calling methods without having to write the variable name over and over again, like this:

obj.foo().bar().baz();

instead of this:

obj.foo();
obj.bar();
obj.baz();

This practice is usually called chaining and was popularized by jQuery back in 2006. If you find the first code example harder to read than the second, keep in mind that you can format it like this:

obj
  .foo()
  .bar()
  .baz();

which not only saves you the extra bytes and typing, but is also much easier to read than both of the previous examples.

chainvas also optionally adds a few helper methods (currently only one at the Core level, .prop()) to make chaining easier. For details about all this, read the Documentation below.

chainvas' Core does not modify any existing objects. There are some official modules that are optionally supplied with it which do so, for a number of different APIs. However, if you feel uneasy about modifying existing prototypes, you can just download the extra lightweight Core and use it in a custom way.

Documentation

Chainvas Core

Chainvas.chainable(method)[Chainvas.chainable]

This function accepts a function as a parameter and returns a function that's the same, with the only difference that it returns this in cases when it was going to return undefined, essentially making it chainable.

Chainvas.chainablizeOne(prototype, method)[Chainvas.chainablizeOne]

This function accepts a prototype object and a method name (string) and makes that method chainable, if and only if it's already present on the prototype (in other words, you can't use it to add extra methods to it). This function is useful for more conservative developers that don't want to alter the APIs too much, but only use that convenience for a couple methods they use most often.

Chainvas.chainablize(constructor [, restricted])[Chainvas.chainablize]

This function is similar to Chainvas.chainablizeOne(), but it's intended for more methods instead of just one or two. If the second argument is not used, it will loop through all the prototype's methods and make them all chainable. If you want to restrict that behavior to only a subset of methods, you can pass an array with method names (strings) as its second parameter.

Please note that the first parameter is the constructor, not the prototype. In other words, if you wanted to chainablize Array, you would use Chainvas.chainablize(Array) and not Chainvas.chainablize(Array.prototype). However, since Array is naturally quite chainable, it makes sense to only chainablize one or two methods, for example forEach. In this case, you would use Chainvas.chainablizeOne(Array.prototype, 'forEach')

Chainvas.helpers(constructor [, extras])[Chainvas.helpers]

This function adds both the [Chainvas core methods][chainvas-core-methods] along with any extra helpers you might want to add. Note that your helpers will have to return this themselves, as they won't be wrapped. It's just a shortcut for Foo.prototype.myExtra = function() {...}. For an example of using Chainvas.helpers with extras, check the source of the Canvas module.

Chainvas.global(constructorNames [, extras [, restricted]])[Chainvas.global]

This is a convenience function for chainablizing and adding helpers to one or more global constructors. You pass in the names of the constructors as either a string or an array of strings. For example 'Node', 'Element' or ['Node', 'Element'] for both.

The extras parameter works the same as it does for [Chainvas.helpers][Chainvas.helpers] and the restricted parameter is the same as in [Chainvas.chainablize][Chainvas.chainablize].

Of course, all the Chainvas methods are chainable themselves as well, so you can do stuff like:

Chainvas.chainablize(...).helpers(...);

Chainvas core methods[chainvas-core-methods]

The following methods will be added to every prototype when Chainvas.helpers is called on its constructor:

obj.prop(values)
obj.prop(property, value)

Helpful for setting properties (like lineWidth, strokeStyle etc in canvas contexts) in a more readable way and without breaking the code chain.

You might be wondering why there's no prop(property) for getting values. However, that would be redundant, since in our case we can just do object.foo instead of object.prop('foo').

You can also use this method to set multiple properties on every object, by using Chainvas.methods.prop.call(obj, properties) or Chainvas.extend(obj, properties) which is a shortcut to that.

And that's all for now. You can suggest methods to add, but I'm very conservative about adding new methods to every chainablized prototype, so it has to be super-useful and with low chances or collisions, like prop. If it's only useful for a certain case, add it to that module instead, through the extras parameter in Chainvas.helpers.

DOM module[DOM-module]

The Chainvas DOM module makes the following chainable:

  • DOM methods on elements, allowing you to do amazing things like:

      document.body.appendChild(
      	document.createElement('a')
      		.prop({
      			'href': 'http://leaverou.me',
      			'innerHTML': 'Lea Verou',
      			'title': 'Visit this awesome blog!',
      			'onclick': function(evt){ alert('gotcha!'); }
      		})
      		.setAttribute('data-unicorns', '42')
      		.addEventListener('mouseover', function(){ alert('don’t do this')}, false)
      );
    
  • Style objects, allowing you to do stuff like:

      var css = document.body.style.prop({
      	color: '#245',
      	fontFamily: 'Georgia, serif',
      	textDecoration: 'underline'
      }).removeProperty('text-decoration')
      .cssText;
    
  • classList methods, allowing you to do things like:

      element.classList
      	.add('foo')
      	.add('bar')
      	.toggle('baz');
    

when classList is supported.

It does not add any extra helpers (besides the [Chainvas code methods][chainvas-core-methods] of course).

Canvas module[Canvas-module]

The Chainvas Canvas module alters the native methods on the canvas context, so that they are chainable, allowing you to do things like this:

ctx.beginPath()
	.prop({
		lineWidth: 2,
		strokeStyle: '#333'
	}).moveTo(0,0)
	.bezierCurveTo(50,50, 80,80, 100,100)
	.stroke().closePath();

It also adds the following helper methods:

circle(x, y, radius)

Draws a circle. You still have to call stroke() or fill() to actually display it.

roundRect(x, y, width, height, radius)

Draws a rounded rectangle. You still have to call stroke() or fill() to actually display it.

Browser support

Chainvas core has been tested and found to work in:

  • Chrome
  • Firefox 3.6+
  • Opera 10+
  • Safari 4+
  • IE6+ (yes, IE6!)

Of course, the individual Chainvas modules have different browser support than the core. For example, the [Chainvas DOM module][DOM-module] won't work in IE7 or below, since it doesn't expose the DOM interfaces and the [Chainvas Canvas module][Canvas-module] won't work in any browser that doesn't support canvas. Here's the browser support information for the built-in modules:

DOM module

  • Chrome
  • Firefox 3.6+*
  • Opera 10+
  • Safari 4+
  • IE8+

* Except for addEventListener prior to Firefox 4, due to this bug

Canvas module

Every browser that supports canvas.

Just because an older version isn't listed above, doesn't mean Chainvas doesn't work in it. It just means I haven't tested in it. You can run the unit tests in your browsers and operating systems and let me know.

Making your own modules[making-your-own-modules]

Making new modules is super easy. Modules are essentially pieces of code added in chainvas.js through comments like this:

/**
 * Chainvas module: Your module id
 */

Then, the build script splits chainvas.js where these comments are. Module ids can contain letters, numbers, spaces, hyphens or underscores (_). Please note that these comments have to also be present in the minified chainvas.min.js file, even though they're not included in the built minified file.

So, you can fork the Chainvas repo on github, add your module and send a pull request.

FAQ

Isn't modifying host objects bad?

In the general case, yes it is. However, most arguments against it, don't really apply to what Chainvas does. Besides, Chainvas core doesn't modify any existing objects, so it's up to you which extra modules to use, if any. Most counter-arguments below apply to the Chainvas modules that modify host objects (like the [DOM module][DOM-module]), not the Chainvas core itself.

Kangax has written an extensive article against the practice of modifying host objects which is commonly mentioned in relevant discussions. This article was written years ago and while it was great advice at that time, some of its points don't really apply nowadays. And the ones that do, don't apply to Chainvas. Lets tackle his arguments one by one:

  • Lack of specification: Yes, there is no official requirement that browsers expose these interfaces. However, the fact is that every browser currently in use does so, except old IEs (< 8). innerHTML and a bunch of other commonly used stuff wasn't required to be there by any spec either up to a while ago, but everyone still used it.
  • Host objects have no rules: This is similar to the first one. Yes, they are not required to do anything by the specifications, but fact is, they behave quite consistently in practice. Most of the issues that kangax mentions are found in old IEs, which now have a tiny market share and Chainvas doesn't even support, and they're not about the prototypes, but instances themselves. Chainvas doesn't attempt to do manual extension on any instances, only on prototypes.
  • Chance of collisions: This is IMHO the most valid argument, and the reason you should be very conservative when using [Chainvas.helpers][Chainvas.helpers]. However, in chainablizing existing methods, this point is moot, since they already exist there.
  • Performance overhead: Again, a very good point in general, but Chainvas does not manually extend any instances. There is a very slight performance overhead introduced by wrapping the native methods, which you can reduce even further by using the restricted parameter in [Chainvas.chainablize][Chainvas.chainablize] to limit which methods become chainable. However, this has the disadvantage of bloating your code with extra bytes and making it less future-proof, which is why it's not used by default in the official Chainvas modules.
  • IE DOM is a mess: Again, this is only about manual extension, which Chainvas does not do.
  • Browser bugs: The mentioned bugs are only relevant for IE8 now, browsers like Safari 3 have faded into obscurity. And those IE8 bugs do not affect what the Chainvas DOM module does at all.

Another argument that people commonly use is that "modifying host objects makes their behavior unpredictable and breaks existing scripts". This is very true for some cases: You should never, for example, modify a native method to do what you think it should, instead of what it actually does. However, the only thing that Chainvas changes in native methods is that they return this instead of undefined. Can you name any real use case where a script breaks if a native method doesn't return undefined? Neither can I. Because we usually don't even store the return value of these methods, let alone do something useful with it.

What's up with all the canvas references?

Chainvas originally started as a library to make the Canvas API chainable. I realized afterwards that its potential is much more than that, so I made it more generic and split all the Canvas-related functionality to just an extra module.

I love Chainvas! How can I help?

  • You can help make it better, by [writing new modules][making-your-own-modules]
  • You can run the unit tests in your environment and let me know about the results
  • You can show your Chainvas love by linking to this page and/or using the following graphic (SVG): Made with care and chainvas

More Repositories

1

awesomplete

Ultra lightweight, usable, beautiful autocomplete with zero dependencies.
JavaScript
6,964
star
2

prefixfree

Break free from CSS prefix hell!
JavaScript
3,841
star
3

animatable

One property, two values, endless possiblities
HTML
2,580
star
4

bliss

Blissful JavaScript
JavaScript
2,390
star
5

inspire.js

Lean, hackable, extensible slide deck framework. Previously known as CSSS.
JavaScript
1,717
star
6

color.js

Color conversion & manipulation library by the editors of the CSS Color specifications
JavaScript
1,581
star
7

css3patterns

The popular CSS3 patterns gallery, now on Github :)
HTML
1,415
star
8

stretchy

Form element autosizing, the way it should be
JavaScript
1,275
star
9

dabblet

An interactive CSS playground
JavaScript
817
star
10

dpi

dpi love - Easily find the DPI/PPI of any screen
JavaScript
748
star
11

multirange

A tiny polyfill for HTML5 multi-handle sliders
CSS
605
star
12

conic-gradient

Polyfill for conic-gradient() and repeating-conic-gradient()
HTML
486
star
13

rety

Record typing on one or more editors and replay it at will, to simulate live coding
JavaScript
383
star
14

parsel

A tiny, permissive CSS selector parser
TypeScript
361
star
15

md-block

A custom element for rendering stylable (light DOM) Markdown
JavaScript
283
star
16

regexplained

JavaScript
273
star
17

css3test

How does your browser score for its CSS3 support?
JavaScript
214
star
18

css.land

Hands on CSS demos
HTML
213
star
19

nudeui

Lea's kitchen sink of form components. WIP. Try at your own risk or come back later.
JavaScript
204
star
20

HTML5-Progress-polyfill

Polyfill for the HTML5 <progress> element
JavaScript
178
star
21

rgba.php

Script for automatic generation of one pixel alpha-transparent images for non-RGBA browsers to easily emulate RGBA colors in backgrounds
PHP
176
star
22

cubic-bezier

Playground for CSS bezier-based timing functions
JavaScript
169
star
23

markapp

Building apps by authoring HTML
CSS
134
star
24

duoload

Simple, client-side comparison of website loading behavior
CSS
130
star
25

play.csssecrets.io

CSS Secrets Book live demos
CSS
119
star
26

talks

All my talks (move still in progress)
HTML
92
star
27

incrementable

Increment length values in textfields
JavaScript
83
star
28

corner-shape

Play with corner-shape before it’s implemented!
JavaScript
75
star
29

StronglyTyped

A library for strongly typed properties & global variables in JavaScript
JavaScript
70
star
30

css-colors

Share & convert CSS colors
JavaScript
62
star
31

bubbly

[Unfinished] CSS speech bubbles made easy!
JavaScript
50
star
32

html-syntax-guidelines

48
star
33

whathecolor

CSS
47
star
34

css-almanac

Repo for planning & voting on which stats to study
SCSS
34
star
35

forkgasm

Lea & Chris’ culinary adventures
HTML
33
star
36

talks-list

Automatic talks list, generated from JSON. Unmaintained, use Mavo instead.
JavaScript
29
star
37

chroma-zone

Slides for my talk “The Chroma Zone: Engineering Color on the Web”
CSS
21
star
38

htest

Declarative, boilerplate-free unit testing
JavaScript
19
star
39

lea.verou.me

Towards a serverless lea.verou.me! WIP, either help out or move along.
JavaScript
17
star
40

mavoice

Prioritize features for open source projects
CSS
15
star
41

issue-closing

View issue closing stats for any repo!
HTML
14
star
42

missing-slice

The Missing Slice talk slides
HTML
12
star
43

hci.mit.edu

WIP. Preview:
HTML
12
star
44

tweeplus

Longer tweets, no strings attached.
JavaScript
11
star
45

brep

Write batch find & replace scripts that transform files with a simple human-readable syntax
JavaScript
10
star
46

leaverou.github.io

Just a data repo, I don't intend to put a website here for now
10
star
47

mygraine

A migraine tracker, built with Mavo. Work in progress, come back later.
HTML
9
star
48

bytesizematters

JavaScript
8
star
49

refresh-it

Refresh page resources without a page reload. An alternative to the heavyweight processes that take over your local server.
JavaScript
8
star
50

uist2017

Website for ACM UIST 2017
HTML
6
star
51

homesearch

A sample Mavo app for people looking for housing to store info about and compare the homes they are trying to decide between
HTML
6
star
52

feedback

Easily save & share lists of tweets and own your data. Made with Mavo.
HTML
5
star
53

eleventy-plugin-citations

JavaScript
5
star
54

my-lifesheets

HTML
4
star
55

expenses

App to generate summary of expenses, made with Mavo
CSS
4
star
56

rework-utils

Utilities to explore ASTs generated by the Rework CSS parser. Originally written for the Web Almanac.
JavaScript
3
star
57

mv-data

3
star
58

testlib

Just a test, move along
JavaScript
2
star
59

blissful-hooks

Deep extensibility for all!
JavaScript
2
star
60

labelizer

Manage repo labels (WIP)
JavaScript
1
star
61

contacts

1
star
62

zoe-eats

Baby food log, made with Mavo
1
star
63

zoelearns.com

JavaScript
1
star
64

website

1
star
65

pathed

Get/set/subset deep objects via readable, extensible paths. WIP.
JavaScript
1
star