Changing APIs, or: why JavaScript rocks.
Dojo has an exceptionally fast and performant "custom event" system that allows a developer to communicate ambiguously between components with little overhead, and [much like Dojo itself] in a "use as needed" manner: dojo.publish and dojo.subscribe, both provided in base Dojo (dojo.js)
The API's are basic: dojo.subscribe some function to a topic, and dojo.publish some data on that topic. Bonus points for being able to unsubscribe at will:
var h = dojo.subscribe("/dojo/rocks", function(data){ console.log(data); // "A message"; }); dojo.publish("/dojo/rocks", ["A message!"]); dojo.unsubscribe(h);
The most immediate issue seen is having to pass an array to the publish() call. Dion pointed this out to me shortly after I reviewed the newly-ported-to-Dojo Bespin code base. The "array as the first parameter" bit is defined by the API docs and expected, though in many cases entirely unnecessary. The reason makes absolute sense: dojo.publish converts the passed array into ordered arguments:
dojo.subscribe("/dojo/kicks", function(a, b, c, d){ console.log(a,b,c,d); // one, 2, three, 4 }); dojo.publish("/dojo/kicks", ["one", 2, "three", 4]);
Without getting into the details, the reason is the subscribed function is called with the array passed to .apply:
// define dojo.publish: dojo.publish = function(topic, args){ var f = dojo._topics[topic]; if(f){ f.apply(f, args||[]); } }
This triggers an empty function bound to the the topic name, and the magic of dojo.connect calls any attached functions, passing the same arguments to each. Because .apply() only accepts an array an empty array is passed in the event the passed args parameter is 'falsey'.
... which makes perfect sense until one of two things happen (which are arguably the most common):
// I want to pass one, single, parameter: dojo.publish("topic:foo", "bar"); // I want to pass a list of parameters in order: dojo.publish("topic:foo", "a", "b", "c");
The second example makes less sense until you understand that dojo.connect executes all attached functions with the same parameters as the called function:
var obj = { bar: function(a,b,c){ // ... anything. } } // this won't work out of the box: dojo.connect(obj, "bar", dojo.partial(dojo.publish, "foo")); dojo.subscribe("foo", function(a,b,c,d){ console.log(a,b,c,d); // one, two, three, undefined }); obj.bar("one", "two", "three");
When obj.bar() is called, the dojo.partial call executes "dojo.publish('foo', ...)", where '...' is equal to any arguments passed to the original obj.bar() call. In the above case, "one", "two", "three" is passed to the bound connected function. The connected function is a curried version of dojo.publish, pushing to the topic "foo".
I wanted to allow for either publish() syntax as mentioned, and not break backwards compatibility. I attempted to do this with:
// _this_ is why JavaScript is cool: var old = dojo.publish; dojo.publish = function(topic, args){ return old(topic, dojo.isArray(args) ? args : [args]); }
But this has the unfortunate (non-back-compat) side effects of a) potentially passing a literal undefined to the subscribed functions 2) is an additional call to some [possibly] expensive functions, and c) generally breaks down for anyone expecting publish() to work as they've been explained previously, and 4) doesn't cover the case of an ambiguous length of arguments.
A quick solution to a) would be:
return old(topic, dojo.isArray(args) ? args : [ args || {} ]);
... but you must still check the passed argument before dot-accessing any of it's members.
My solution was a new API, "pub", which simply wraps "dojo.publish" with some fun JavaScript foo:
dojo.pub = function(){ // summary: wrap `dojo.publish` to allow any number of arguments var a = dojo._toArray(arguments); return dojo.publish(a.shift(), a); }
Thought I won't take credit for it, I may have just just invented the most compelling use of an inline .shift() call, ever. Briefly: dojo._toArray, while private, will convert any enumerable object to a real array. Our function "pub" takes any number of arguments, converts them to an array (a), then returns the value from calling dojo.publish() ... what we pass to publish() is the fun:
a.shift() will return the first element of the array (a). This return value is passed to the first argument of the dojo.publish call. The shift() call also modifies the array (a) to be one element shorter, so our next reference to (a) will the reduced list. We're passing this reduced array to the second.
Following this pattern in your own functions is easy, even without Dojo. dojo._toArray does more, but in these examples all we've done is convert 'arguments' to an array. We can likely do this faster (and without Dojo):
var aps = Array.prototype.slice; // simple version of `dojo._toArray` var toArray(obj){ return aps.call(obj, 0); }
`dojo.pub` is implemented in plugd, which will have a new version to coincide with Dojo 1.3.0 to be released shortly. Hopefully the dojo.pub API will take over the existing dojo.publish API in Dojo 2.0, where we get to fix these kinds of things.
update Mar 7 2009: I mistakenly transposed the function as .slice(). It should have been .shift() in this blog. Thanks Wolfram for catching that! PlugD was using .shift() and the unit tests pass.
HAROLD:
Buy:Actos.100% Pure Okinawan Coral Calcium.Prednisolone.Zyban.Human Growth Hormone.Petcam (Metacam) Oral Suspension.Retin-A.Arimidex.Nexium.Prevacid.Lumigan.Zovirax.Mega Hoodia.Valtrex.Synthroid.Accutane….
3 July 2010, 9:21 pmKENT:
Buy:Super Active ED Pack.Viagra Professional.Cialis Professional.Propecia.Levitra.Viagra Super Force.Cialis.Cialis Soft Tabs.Viagra Super Active+.Soma.Viagra Soft Tabs.Viagra.Tramadol.VPXL.Zithromax.Maxaman.Cialis Super Active+….
21 July 2010, 10:50 am