Skip to content

Instantly share code, notes, and snippets.

@thingsinjars
Forked from 140bytes/LICENSE.txt
Created December 12, 2011 09:39
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save thingsinjars/1466219 to your computer and use it in GitHub Desktop.
Save thingsinjars/1466219 to your computer and use it in GitHub Desktop.
Chainable DOM Manipulation
function(
constructor, //The group to make chainable -
// e.g. Window.Element, Window.Node, Window.DOMTokenList etc...
prototype, //Placeholder
method //Placeholder
) {
prototype = constructor.prototype; //Shorthand to work with the prototype of the constructor
for (method in prototype) // For every method on this object
if (prototype[method].call) // ...if it's a function
prototype[method] = (function(method) { // Make every function
return function() { // into another function
return method.apply(this, arguments) // which does its job then either returns itself if that was the default behaviour
|| // or
this; // returns itself here
}
})(prototype[method]) // pass in the method we want to be working with
}
function(a,b,c){b=a.prototype;for(c in b)if(b[c].call)b[c]=(function(d){return function(){return d.apply(this,arguments)||this}})(b[c])}
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2011 YOUR_NAME_HERE <YOUR_URL_HERE>
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": "chainableDomManipulation",
"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 chainer = function(a,b,c){b=a.prototype;for(c in b)if(b[c].call)b[c]=(function(d){return function(){return d.apply(this,arguments)||this}})(b[c])}
// 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}}
chainer(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
Copy link
Author

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
Copy link
Author

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
Copy link

nikola commented Dec 12, 2011

You can eliminate a bunch of curly brackets.

@thingsinjars
Copy link
Author

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

@subzey
Copy link

subzey 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
Copy link
Author

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
Copy link

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
Copy link
Author

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
Copy link

subzey commented Dec 19, 2011

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

@thingsinjars
Copy link
Author

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
Copy link

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

@LeaVerou
Copy link

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
Copy link

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

@subzey
Copy link

subzey 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
Copy link
Author

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
Copy link

nikola commented Jan 1, 2012

But this works in IE7+

/^f/.test(typeof b)

Well, saves 1 byte.

@williammalo
Copy link

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
Copy link

tjbenton 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
Copy link

@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