Posted in Dojo Cookies
Jammastergoat - dojo.hitch
This cookie has been a long time coming. I don’t think any one component of Dojo base has more uses, or is used more often. Unfortunately, because of the power contained within this single simple function, it’s terribly difficult to explain, though I am going to take a very noble stab at giving all of you the power contained within the most magic of magic functions: dojo.hitch.
The summary of dojo.hitch says it all, though leaves much unexplained:
Returns a function that will only ever execute in the a given scope. This allows for easy use of object member functions in callbacks and other places in which the “this” keyword may otherwise not reference the expected scope. Any number of default positional arguments may be passed as parameters beyond “method”. Each of these values will be used to “placehold” (similar to curry) for the hitched function.
Lets focus on the first part: “Returns a function that will only ever execute in a given scope”. Scope being the first argument passed, method being the second, and though not clearly stated above: any number of positional arguments to pass to the new function (which is suuuuuper cool, and super helpful).
so dojo.hitch() returns a function. The basic training needed to visual this is:
var foo = dojo.hitch(scope,method); foo();
Again, it’s a difficult concept to explain without a real use case, so lets invent a simple one:
var foo = { attr:"an attribute", debug: function(){ console.log(this.attr); } }; someNode.onclick = dojo.hitch(foo,"debug");
So when we click on the node referenced as ’someNode”, the debug() method of the foo object will be executed in the scope of foo. ‘this’ means something useful. Here, this.attr is foo.attr, as we’re in the scope of ‘foo’ dictated by hitch. neat.
Stepping back, here is an entirely redundant use of dojo.hitch():
dojo.hitch(dojo,"style","someNode","display","none")(); // is the same as: dojo.style("someNode","display","none"); // and var foo = dojo.hitch(dojo,"style","someNode","display","none"); setTimeout(foo,2000); // except foo gets executed after 2 seconds. a shorthand: var randomOptional = "block"; setTimeout(dojo.hitch(dojo,"style","someNode","display",randomOptional),1000); // this time, one second. but pass a random var from this scope 'randomOptional'
An even better, though lengthier example:
var collection = { otherFunc:function(e){ console.log("me first"); }, myFunc:function(e){ this.otherFunc(e); console.log("I did something",this,e); } }; var node = dojo.byId("foo"); dojo.connect(node,"onclick",dojo.hitch(collection,"myFunc");
I say hitch() is magic because it happens in a lot of cool places you’d want it to and you probably just never noticed. The above dojo.connect call can simply be written as:
dojo.connect(node,"onclick",collection,"myFunc");
This works wonderfully, and exactly the same as the above hitch()’d version, though only does as dojo.connect does and pass the event object to ‘myFunc’. This is where the “optional positioned arguments” come into play. dojo.hitch() gives us the power to pass arguments from a different scope into the the function on the fly:
// a global variable var outside = null, outsideWithout = null; (function(){ // hidden within this closure var thinger = { attr:"somestring", debug: function(){ console.log('attr',this.attr); }, setAttr:function(str,opt){ // inspect this.attr before and after this.debug(); this.attr = str + (opt.why || ""); this.debug(); } }; var randomString = "because"; // create a hitch() within this scope linking outside outside = dojo.hitch(thinger,"setAttr",randomString); outsideWithout = dojo.hitch(thinger,"setAttr"); })(); // it worked, it's a function, call it to set a new attr value if(dojo.isFunction(outside)){ outside("a new attr"); outsideWithout("lacks the phase 'because' in the text"); }
Above, we created a variable ‘thinger’ which is otherwise entirely private as far as JavaScript will allow. The only available link to thinger, is the ‘outside’ function, hitch()’d exclusively to the setAttr method. It executes only ever in that scope, and has access to those values. Nothing else does.
This is great, and slightly confusing I’m sure. I’ve still yet to produce a _real_ usecase. Onward, lets make a simple declared class based on Nikolai’s custom connect example:
dojo.provide("my.Thinger"); dojo.declare("my.Thinger",null,{ constructor:function(args){ console.log(args); }, show:function(){ }, hide:function(){ } });
It doesn’t do anything, but is generally how all dijit’s and declared classes work. (I’m inching towards use case, bear with me).
var one = new my.Thinger("firstone"); var two = new my.Thinger("secondone"); var obj = { hideFirst: function(){ one.hide(); } } dojo.connect(two,"show",obj,"hideFirst");
Here, hitch() is quietly used by dojo.connect to run obj.hideFirst() anytime the instance two.show() is called.
The most common use case for hitch (as far as my experience has taken me) is passing ‘this’ as the first argument (the scope), to call a function that otherwise would not be called in any particular scope.
var randomObj = { errorFound:function(data,ioargs){ // my generic error handler } }; dojo.xhrGet({ url:"script.php", load:function(data,ioArgs){ // loaded okay, do something with 'data' }, // there was an error, pass off to my generic error handler error: dojo.hitch(randomObj,"errorFound") });
Its important to note that hitch() returns a function rather than executing it. dojo.addOnLoad(function(){ }) is not the same as dojo.addOnLoad(someFunction()) … (someFunction() in this example executing immediatly, and the anonymous actual function just being a reference (a scoped area, at that) which is called by addOnLoad. Without looking, I would bet addOnLoad uses hitch() somewhere along the lines
of it’s magic.
A semi-real-world use case: When a fadeOut is done, simply tell some other methd to fire, say disable a dijit.form.Button with an id=”myButton”:
var button = dijit.byId("myButton"); dojo.fadeOut({ node:"someDiv", onEnd: dojo.hitch(button,"setAttribute","disabled","true") });
or with other things that don’t fire synchronously, like most Ajax calls:
var thinger = function(node,url){ var node = null; var load = function(someurl){ dojo.xhrGet({ url: someurl, // pass 'data,ioArgs' to setContent via magic, er, hitch() load:dojo.hitch(this,"setContent"), error:function(e){ console.warn('oh noes',e); } }); }; var setContent = function(data){ node.innerHTML = data || ""; }; node = dojo.byId(node); load(url); } // set div id="someNode" to the result of foobar.html var yep = new thinger("someNode","foobar.html");
Which is about as far as I can take hitch() in a cookie. Note, my thinger class to set the result of a node byId() to some url is not near as elegant a solution as pottedmeat’s universal fix, but does help to illustrate the magic of dojo.hitch().
One last item where hitch() is magically used to set the scope of something: forEach. The optional third parameter is used to scope the function in the second paramter somwhere. Again, ‘this’ is a very common usage:
dojo.declare("thinger",null,{ constructor:function(){ this.letters = []; dojo.forEach(["a","b","c","d"],function(item){ // 'this' in this context means our local scope this.addLetter(item); },this); }, addLetter:function(letter){ this.letters.push(letter); } }); var foo = new thinger(); console.log(foo.letters);
Go hitch(). I recall at TAE, during a prototype talk someone referring to dojo.hitch() as “romantic” (and it kind of is in a sense) though was mistakenly comparing it to prototype’s .bind(), which is the same as the automatic relationship between dojo.connect() and dojo.hitch(), though hitch() is endlessly magic, and needs no romance to be useful.
Buen provecho.
Tags: dojo.hitch