Skip to content

Instantly share code, notes, and snippets.

@dtipson
Last active August 29, 2015 14:14
Show Gist options
  • Save dtipson/c1d01bac35d8baca25e3 to your computer and use it in GitHub Desktop.
Save dtipson/c1d01bac35d8baca25e3 to your computer and use it in GitHub Desktop.
micro-plugin that allows you to one-line callbacks and chain compose jQuery operations
(function($){
/*define the function */
function use(method /*args*/){
return use.fn.init.apply({},[Array.prototype.slice.call(arguments)]);
}
/*define the underlying prototype*/
use.fn = use.prototype = {
init: function(initStack,oldstack){
var user = function(){
var context = user.ctx,
stackln = user.stack.length,
args = arguments;
//we need a way to specify that we can take an arg
if($.isFunction(user.ctx)){
context = user.ctx();
}
$.each(user.stack,function(i,op){
context = context[op[0]].apply(//using the method
context,//with our context
op[1].concat(//apply the arguments
Array.prototype.slice.call(
args,
0,
op[2]||0//plus any additional arguments we said this could take, if any
)
)
);
});
return context;
};
function setContext(ctx){
return use.fn.init.call(ctx||$(this),undefined,user.stack);
}
function returnUse(){
return user.ctx && user.ctx[user.stack[0][0]]||$.isFunction(user.ctx)?
user:
{
on: setContext,
the: setContext,
$: function(){ return setContext.apply(this); },//take our context from the context, guarding against additional arguments
thenUse: user.thenUse,
take: user.take,
stack: user.stack,
ctx: user.ctx
};
}
$.extend(user,{
stack : oldstack||[[initStack[0],initStack.slice(1)]],
ctx : this,
thenUse: function(method){
return use.fn.init.call(user.ctx,undefined,
user.stack.concat([[method,Array.prototype.slice.call(arguments, 1)||[]]])
);
},
take: function(num){
this.stack.reverse()[0].push(num);//explain how many arguments the most recently defined method should take
return returnUse();
}
});
return returnUse();
}
};
/*define as a jQuery plugin bound to */
$.fn.use = function(method /*args*/){
if(method && method.stack && method.stack[0]){
return use.fn.init.apply(this,[undefined,method.stack]);//steal the stack from some other method & apply it to a new context!
}else{
return use.fn.init.apply(this,[Array.prototype.slice.call(arguments)]);
}
};
window.use = use;
}(jQuery));
/*
var toggleHi = $('body').use('toggleClass','hi'),// returns a callback that WILL run .toggleClass('hi') on $('body') using the jQuery plugin
andToggle = toggleHi.thenUse('toggle'),// adds to toggleHi: now the callback WILL add the class and toggle $('body')
dim = use('css',{opacity:0.3}),// create a set of methods/arguments that aren't even bound TO anything yet...
dimtheHeader = dim.the($('header')),// but that you can later apply to something TO create a callback that WILL run on that item
dimthenFadeThen = dimtheHeader.thenUse('delay',5000).thenUse('fadeOut',3000,dim.the($('body'))),//or chain a bunch of things logically, including using a use as a callback inside the callback definition
thatThing = use('find','.thatThing').on($('body'),//.thatThing might not exist yet, here's a function that WILL find and return it, defined a head of time
dimThing = dim.the(thatThing);//which can then be used to make a function that WILL first find it and then dim it
Why would you want to do this?
Because then you can turn this:
$.ajax().done( function(){
$oldLists.remove();
//some other stuff that may or may not be related, who knows, we just dump all sorts of garbage in here
})
into:
$.ajax().done( $oldLists.use('remove'), function(){
//you can still do your other stuff like this I guess
});
or even:
var remove = use('remove');
$.ajax().done( remove.the($oldLists), iCleanedupThatStuffTooHa );
It reads better and it sometimes even minifies better. It's sort of convenient and if you go crazy with it, you can compose out entire sets of reusable behavior pretty intelligibly, all without actually running it or even specifying what it runs on. Once you define a set of behavior you can also re-apply it to a different element regardless of whether it was bound to an element, i.e. $('body').use(dim); or $('header').use(andToggle); It just transfers the stack of methods over and returns it as a function that will run those methods on the new element.
var $body = $('body'),
transientThing = use('find','.thatThing').on($body),//not run yet, just defined as something we can do
appendToBody = use('appendTo',$body);//append what to body? Well, we haven't said yet
$.ajax('/thing/api')
.then(someStuff)
.then(appendtoBody.the(transientThing)); //ok, when that async call is done THEN find that darn thing and append it to the body
Finally, you can optionally allow methods to accept arguments when called, like this
var $messageContainer = $('.message-container'),
addTextTo = use('addClass','done').thenUse('text').take(1);
$.ajax('textapi')
.then(returnString)
.done( addTextTo.the($messageContainer) );
The .take method there basically says that the most recent method in our stack of methods should accept 1 additional argument when it's run, which in this case would be the string of text that we got from an ajax request. The result is that we've created a function that accepts one argument, a string of text, and then applies it to an element or our choice (in addition to adding a class). $messageContainer.use(addTextTo) would have also done the same thing.
Sometimes callbacks have a context that's important. If so, the .$ method, used at the very end of a chain, signifies that the function should be run such that it takes whatever the context is applied to it.
A not particularly use, but instructive, example:
var funcs = $('li').map(use('toggle').take(1).$); // returns an array of functions, each bound to one of the jQuery elements in a collection, each one taking one argument. Running funcs[0](false) would hide the first li, for instance, while funcs[0](true) would reveal it again.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment