Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Chainable DOM Manipulation

The two functions mentioned here, chainify (https://gist.github.com/gists/1466219) and propertize (https://gist.github.com/1466253) work fine on their own but best when used together.

Chainify

When provided with a constructor such as Element, Node, CSSStyleDeclaration, etc, the function will run through every property. If the property is a function, it will be wrapped in another function which will ensure that the return value is the original object.

This means that you can use the syntax

    document.createElement('a')
        .setAttribute('data-monkeys', '∞')
        .addEventListener('click', function(){ alert('Urk! Alert!')}, false)

Propertize

Similar to the chainify function, this takes a constructor as its argument. It adds the method .prop to everything which will set the properties provided either as an object or as a propertyName and propertyValue then return the original object to ensure the outcome is still chainable.

    myElement
        .prop({
            'href': 'http://140byt.es/',
            'title': 'tiny awsm'
        })
        .prop('innerHTML', '140byt.es');

When the two are combined, you have a completely chainable DOM manipulation tool.

Chainvas

The idea for the .prop function came from Lea Verou's Chainvas. This should be mostly compatible with the syntax (although possibly less robust). You can do the same chainify/propertize on any of the standard constructors - Element, Node, CSSStyleDeclaration, DOMTokenList, CanvasRenderingContext2D to make any API chainable.

function(
a, // The constructor of the group to make chainable
// e.g. Window.Element, Window.Node, Window.DOMTokenList etc...
b // Placeholder
c // Placeholder
) {
for(a in // Reusing this variable as we don't need it after the next line
// It's now used for the methods in the prototype of the constructor
b=a.prototype) // Shorthand to work with the prototype of the constructor
with({d:b[a]}) // Assign the method in here, creating a closure without needing another function wrapper
typeof d=='function' // if this is a function
? // Then
b[a]=function(){ // make every method a function which
return
(
c=d.apply(this,arguments)// does its job then assigns its return value
)===a._ // if the return value was undefined
? // Then
this // return 'this'
: // Else
c // return the intended return value
}
: // Else (this isn't a function)
0 // Do nothing
}
function(a,b,c){for(a in b=a.prototype)with({d:b[a]})typeof d=='function'?b[a]=function(){return(c=d.apply(this,arguments))===a._?this:c}:0}
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2011 Simon Madine <http://thingsinjars.com>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
{
"name": "chainify",
"description": "Extremely minimal chainable DOM manipulation based on Chainvas",
"keywords": [
"html",
"DOM",
"chaining"
]
}
<!DOCTYPE html>
<meta charset="utf-8">
<title>Chain anything</title>
<div>Expected value: <b><a href="http://140byt.es/" title="tiny awsm" data-monkeys="">140byt.es</a></b></div>
<div>Actual value: <b id="ret"></b></div>
<script>
// This is paired with the propertize gist to cover chainability and setting properties
//This is a very, very minimal version of the functionality of Chainvas (http://lea.verou.me/chainvas/) to make anything chainable
var chainify = function(a,b,c){for(a in b=a.prototype)with({d:b[a]})typeof d=='function'?b[a]=function(){return(c=d.apply(this,arguments))===a._?this:c}:0}
// see https://gist.github.com/gists/1466253
var propertize = function(a,b,c){a.prototype.prop=function(){b=arguments;if(1 in b)this[b[0]]=b[1];else{b=b[0];for(c in b)this[c]=b[c]}return this}}
chainify(window.Element);
propertize(window.Element);
document.getElementById('ret').appendChild(
document.createElement('a')
.prop({
'href': 'http://140byt.es/',
'title': 'tiny awsm'
})
.prop('innerHTML', '140byt.es')
.setAttribute('data-monkeys', '')
.addEventListener('click', function(){ alert('Urk! Alert!')}, false)
);
</script>
@thingsinjars

This comment has been minimized.

Copy link
Owner Author

commented Dec 12, 2011

This is currently at 146 bytes so there's a little room for improvement.

The test file references the chainable property setter (https://gist.github.com/1466253) as I was working on them both at the same time although I could probably separate them into smaller test cases.

It started off as a minimal implementation of Lea Verou's Chainvas

@thingsinjars

This comment has been minimized.

Copy link
Owner Author

commented Dec 12, 2011

I took out the extra check

if (prototype[method] && // If it exists and...

as we only get to the check when we are iterating through the object so we know it already exists. It's now at 140bytes exactly.

@nikola

This comment has been minimized.

Copy link

commented Dec 12, 2011

You can eliminate a bunch of curly brackets.

@thingsinjars

This comment has been minimized.

Copy link
Owner Author

commented Dec 12, 2011

You're right. I've just taken out four of them. Thanks

@subzey

This comment has been minimized.

Copy link

commented Dec 14, 2011

Nice idea and great job!

A little code minimizing hint: extra scope for closure may also be created using with.This and some Byte-saving techniques makes the code shorter:

// 114 bytes
function(a,b){for(a in b=a.prototype)with({d:b[a]})d.call?b[a]=function(){return d.apply(this,arguments)||this}:0}

@thingsinjars

This comment has been minimized.

Copy link
Owner Author

commented Dec 14, 2011

Dang, that's a clever shuffle. I'd never have considered using a with there.

I've updated the code and (I think) annotated it right.

@LeaVerou

This comment has been minimized.

Copy link

commented Dec 17, 2011

Hawt stuff!
The problem is that with using the || operator, you return this even when the result was going to be null or 0 or the empty string, all three being quite common.

@thingsinjars

This comment has been minimized.

Copy link
Owner Author

commented Dec 17, 2011

Ah, well spotted, thanks.

Thanks to @subzey's clever shuffling, there's enough space to put in a strict undefined check and return correctly on null or 0. There might still be a clever way to remove the c variable introduced here but I shan't attempt to figure it out tonight, I don't think.

My initial hope of combining chain and prop together looks to be a bit unobtainable. Still, they both function fine on their own.

@subzey

This comment has been minimized.

Copy link

commented Dec 19, 2011

I suppose, []._ may be replaced by a._. a is "inherited" from outer scope and is surely a string

@thingsinjars

This comment has been minimized.

Copy link
Owner Author

commented Dec 20, 2011

Done. I must get round to putting together some performance tests at some point.

*edit: http://jsperf.com/trialchainifytest

Not sure I've got the setup quite right but, anyway, it's not too bad for performance.

@gbakernet

This comment has been minimized.

Copy link

commented Dec 22, 2011

Love it... using the call property to detect a function is brilliantly simply yet rarely seen. Does it work cross browser?

@LeaVerou

This comment has been minimized.

Copy link

commented Dec 22, 2011

That’s duck typing, and not usually considered a good practice (although in this case it saves characters). Don't do it when # of bytes is not such a huge issue.

@gbakernet

This comment has been minimized.

Copy link

commented Dec 22, 2011

True, a simple {call:true} would make it unstuck.

@subzey

This comment has been minimized.

Copy link

commented Dec 23, 2011

Uh-oh, if any prototype property will be null or undefined, function throws TypeError.
I suppose, typeof should be used, it's just 140 bytes:

function(a,b,c){for(a in b=a.prototype)with({d:b[a]})typeof d=='function'?b[a]=function(){return(c=d.apply(this,arguments))===a._?this:c}:0}

@thingsinjars

This comment has been minimized.

Copy link
Owner Author

commented Jan 1, 2012

I started messing around testing whether something is a function by using (typeof d)[0]=='f' before deciding it just felt wrong.

Besides, I don't think it works in IE7.

@nikola

This comment has been minimized.

Copy link

commented Jan 1, 2012

But this works in IE7+

/^f/.test(typeof b)

Well, saves 1 byte.

@williammalo

This comment has been minimized.

Copy link

commented Mar 24, 2012

Since with() is deprecated and unreliable, I remade your function to not use with().

chainify = function(a,b){for(b in a)(function(c){a[b]=function(){c.apply(this,arguments);return this}})(a[b])}

chainify(window.Element.prototype)

Feel free to use it :)

@tjbenton

This comment has been minimized.

Copy link

commented Mar 6, 2014

Note: These updates include calling the function on window.Element or window.Element.prototype

Seperate functions --------------------

Original prop - 174 bytes
var propertize=function(a,b,c){a.prototype.prop=function(){if(b=arguments,1 in b)this[b[0]]=b[1];else{b=b[0];for(c in b)this[c]=b[c]}return this}};propertize(window.Element);

Updated prop - 141 bytes
!function(a,b,c){a.prototype.prop=function(){b=arguments,1 in b?this[b[0]]=b[1]:b=b[0];for(c in b)this[c]=b[c];return this}}(window.Element);

Original chanify - 166 bytes
var chainify=function(a,b,c){for(a in b=a.prototype)with({d:b[a]})d.call?b[a]=function(){return(c=d.apply(this,arguments))===[]._?this:c}:0};chainify(window.Element);

Updated chanify - 129 bytes
!function(a,b){for(b in a=a.prototype)(function(c){a[b]=function(){return c.apply(this,arguments),this}})(a[b])}(window.Element);

Combined functions --------------------

Original prop and chanify - 336 bytes
var propertize=function(a,b,c){a.prototype.prop=function(){if(b=arguments,1 in b)this[b[0]]=b[1];else{b=b[0];for(c in b)this[c]=b[c]}return this}},chainify=function(a,b,c){for(a in b=a.prototype)with({d:b[a]})d.call?b[a]=function(){return(c=d.apply(this,arguments))===[]._?this:c}:0};propertize(window.Element),chainify(window.Element);

Updated prop and chanify - 228 bytes
!function(a,b,c){a.prop=function(){b=arguments,1 in b?this[b[0]]=b[1]:b=b[0];for(c in b)this[c]=b[c];return this};for(b in a=a)(function(c){a[b]=function(){return c.apply(this,arguments),this}})(a[b])}(window.Element.prototype);

@tunnckoCore

This comment has been minimized.

Copy link

commented Feb 26, 2015

@tjbenton awesome! use codeblocks...

@thingsinjars, need update of the gist because it is 2015 now :)

Based on above
propertize - 123 bytes

function(a,b,c){a.prototype.prop=function(){b=arguments,1 in b?this[b[0]]=b[1]:b=b[0];for(c in b)this[c]=b[c];return this}}

chainify - 111 bytes

function(a,b){for(b in a=a.prototype)(function(c){a[b]=function(){return c.apply(this,arguments),this}})(a[b])}

Combined propertize + chainify - 200 bytes - test pass!

function(a,b,c){a.prop=function(){b=arguments,1 in b?this[b[0]]=b[1]:b=b[0];for(c in b)this[c]=b[c];return this};for(b in a=a)(function(c){a[b]=function(){return c.apply(this,arguments),this}})(a[b])}

test pass

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.