/* Copyright (c) 2004-2006, The Dojo Foundation All Rights Reserved. Licensed under the Academic Free License version 2.1 or above OR the modified BSD license. For more information on Dojo licensing, see: http://dojotoolkit.org/community/licensing.shtml */ dojo.provide("dojo.behavior"); dojo.require("dojo.event.*"); dojo.require("dojo.experimental"); dojo.experimental("dojo.behavior"); dojo.behavior = new function(){ function arrIn(obj, name){ if(!obj[name]){ obj[name] = []; } return obj[name]; } function forIn(obj, scope, func){ var tmpObj = {}; for(var x in obj){ if(typeof tmpObj[x] == "undefined"){ if(!func){ scope(obj[x], x); }else{ func.call(scope, obj[x], x); } } } } // FIXME: need a better test so we don't exclude nightly Safari's! this.behaviors = {}; this.add = function(behaviorObj){ /* behavior objects are specified in the following format: * * { * "#id": { * "found": function(element){ * // ... * }, * * "onblah": {targetObj: foo, targetFunc: "bar"}, * * "onblarg": "/foo/bar/baz/blarg", * * "onevent": function(evt){ * }, * * "onotherevent: function(evt){ * // ... * } * }, * * "#id2": { * // ... * }, * * "#id3": function(element){ * // ... * }, * * // publish the match on a topic * "#id4": "/found/topic/name", * * // match all direct descendants * "#id4 > *": function(element){ * // ... * }, * * // match the first child node that's an element * "#id4 > @firstElement": { ... }, * * // match the last child node that's an element * "#id4 > @lastElement": { ... }, * * // all elements of type tagname * "tagname": { * // ... * }, * * // maps to roughly: * // dojo.lang.forEach(body.getElementsByTagName("tagname1"), function(node){ * // dojo.lang.forEach(node.getElementsByTagName("tagname2"), function(node2){ * // dojo.lang.forEach(node2.getElementsByTagName("tagname3", function(node3){ * // // apply rules * // }); * // }); * // }); * "tagname1 tagname2 tagname3": { * // ... * }, * * ".classname": { * // ... * }, * * "tagname.classname": { * // ... * }, * } * * The "found" method is a generalized handler that's called as soon * as the node matches the selector. Rules for values that follow also * apply to the "found" key. * * The "on*" handlers are attached with dojo.event.connect(). If the * value is not a function but is rather an object, it's assumed to be * the "other half" of a dojo.event.kwConnect() argument object. It * may contain any/all properties of such a connection modifier save * for the sourceObj and sourceFunc properties which are filled in by * the system automatically. If a string is instead encountered, the * node publishes the specified event on the topic contained in the * string value. * * If the value corresponding to the ID key is a function and not a * list, it's treated as though it was the value of "found". * */ var tmpObj = {}; forIn(behaviorObj, this, function(behavior, name){ var tBehavior = arrIn(this.behaviors, name); if((dojo.lang.isString(behavior))||(dojo.lang.isFunction(behavior))){ behavior = { found: behavior }; } forIn(behavior, function(rule, ruleName){ arrIn(tBehavior, ruleName).push(rule); }); }); } this.apply = function(){ dojo.profile.start("dojo.behavior.apply"); var r = dojo.render.html; // note, we apply one way for fast queries and one way for slow // iteration. So be it. var safariGoodEnough = (!r.safari); if(r.safari){ // Anything over release #420 should work the fast way var uas = r.UA.split("AppleWebKit/")[1]; if(parseInt(uas.match(/[0-9.]{3,}/)) >= 420){ safariGoodEnough = true; } } if((dj_undef("behaviorFastParse", djConfig) ? (safariGoodEnough) : djConfig["behaviorFastParse"])){ this.applyFast(); }else{ this.applySlow(); } dojo.profile.end("dojo.behavior.apply"); } this.matchCache = {}; this.elementsById = function(id, handleRemoved){ var removed = []; var added = []; arrIn(this.matchCache, id); if(handleRemoved){ var nodes = this.matchCache[id]; for(var x=0; x