/* Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ if(!dojo._hasResource["dojo.behavior"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dojo.behavior"] = true; dojo.provide("dojo.behavior"); dojo.behavior = new function(){ // summary: // Utility for unobtrusive/progressive event binding, DOM traversal, // and manipulation. // // description: // // A very simple, lightweight mechanism for applying code to // existing documents, based around `dojo.query` (CSS3 selectors) for node selection, // and a simple two-command API: `dojo.behavior.add()` and `dojo.behavior.apply()`; // // Behaviors apply to a given page, and are registered following the syntax // options described by `dojo.behavior.add` to match nodes to actions, or "behaviors". // // Added behaviors are applied to the current DOM when .apply() is called, // matching only new nodes found since .apply() was last called. // function arrIn(obj, name){ if(!obj[name]){ obj[name] = []; } return obj[name]; } var _inc = 0; 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(/* Object */behaviorObj){ // summary: // Add the specified behavior to the list of behaviors, ignoring existing // matches. // // description: // Add the specified behavior to the list of behaviors which will // be applied the next time apply() is called. Calls to add() for // an already existing behavior do not replace the previous rules, // but are instead additive. New nodes which match the rule will // have all add()-ed behaviors applied to them when matched. // // 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.connect()`, using the // matching node // // 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". // // dojo.behavior.add() can be called any number of times before // the DOM is ready. `dojo.behavior.apply()` is called automatically // by `dojo.addOnLoad`, though can be called to re-apply previously added // behaviors anytime the DOM changes. // // There are a variety of formats permitted in the behaviorObject // // example: // Simple list of properties. "found" is special. "Found" is assumed if // no property object for a given selector, and property is a function. // // | dojo.behavior.add({ // | "#id": { // | "found": function(element){ // | // node match found // | }, // | "onclick": function(evt){ // | // register onclick handler for found node // | } // | }, // | "#otherid": function(element){ // | // assumes "found" with this syntax // | } // | }); // // example: // If property is a string, a dojo.publish will be issued on the channel: // // | dojo.behavior.add({ // | // dojo.publish() whenever class="noclick" found on anchors // | "a.noclick": "/got/newAnchor", // | "div.wrapper": { // | "onclick": "/node/wasClicked" // | } // | }); // | dojo.subscribe("/got/newAnchor", function(node){ // | // handle node finding when dojo.behavior.apply() is called, // | // provided a newly matched node is found. // | }); // // example: // Scoping can be accomplished by passing an object as a property to // a connection handle (on*): // // | dojo.behavior.add({ // | "#id": { // | // like calling dojo.hitch(foo,"bar"). execute foo.bar() in scope of foo // | "onmouseenter": { targetObj: foo, targetFunc: "bar" }, // | "onmouseleave": { targetObj: foo, targetFunc: "baz" } // | } // | }); // // example: // Bahaviors match on CSS3 Selectors, powered by dojo.query. Example selectors: // // | dojo.behavior.add({ // | // match all direct descendants // | "#id4 > *": function(element){ // | // ... // | }, // | // | // match the first child node that's an element // | "#id4 > :first-child": { ... }, // | // | // match the last child node that's an element // | "#id4 > :last-child": { ... }, // | // | // all elements of type tagname // | "tagname": { // | // ... // | }, // | // | "tagname1 tagname2 tagname3": { // | // ... // | }, // | // | ".classname": { // | // ... // | }, // | // | "tagname.classname": { // | // ... // | } // | }); // var tmpObj = {}; forIn(behaviorObj, this, function(behavior, name){ var tBehavior = arrIn(this._behaviors, name); if(typeof tBehavior["id"] != "number"){ tBehavior.id = _inc++; } var cversion = []; tBehavior.push(cversion); if((dojo.isString(behavior))||(dojo.isFunction(behavior))){ behavior = { found: behavior }; } forIn(behavior, function(rule, ruleName){ arrIn(cversion, ruleName).push(rule); }); }); } var _applyToNode = function(node, action, ruleSetName){ if(dojo.isString(action)){ if(ruleSetName == "found"){ dojo.publish(action, [ node ]); }else{ dojo.connect(node, ruleSetName, function(){ dojo.publish(action, arguments); }); } }else if(dojo.isFunction(action)){ if(ruleSetName == "found"){ action(node); }else{ dojo.connect(node, ruleSetName, action); } } } this.apply = function(){ // summary: // Applies all currently registered behaviors to the document. // // description: // Applies all currently registered behaviors to the document, // taking care to ensure that only incremental updates are made // since the last time add() or apply() were called. // // If new matching nodes have been added, all rules in a behavior will be // applied to that node. For previously matched nodes, only // behaviors which have been added since the last call to apply() // will be added to the nodes. // // apply() is called once automatically by `dojo.addOnLoad`, so // registering behaviors with `dojo.behavior.add` before the DOM is // ready is acceptable, provided the dojo.behavior module is ready. // // Calling appy() manually after manipulating the DOM is required // to rescan the DOM and apply newly .add()ed behaviors, or to match // nodes that match existing behaviors when those nodes are added to // the DOM. // forIn(this._behaviors, function(tBehavior, id){ dojo.query(id).forEach( function(elem){ var runFrom = 0; var bid = "_dj_behavior_"+tBehavior.id; if(typeof elem[bid] == "number"){ runFrom = elem[bid]; if(runFrom == (tBehavior.length)){ return; } } // run through the versions, applying newer rules at each step for(var x=runFrom, tver; tver = tBehavior[x]; x++){ forIn(tver, function(ruleSet, ruleSetName){ if(dojo.isArray(ruleSet)){ dojo.forEach(ruleSet, function(action){ _applyToNode(elem, action, ruleSetName); }); } }); } // ensure that re-application only adds new rules to the node elem[bid] = tBehavior.length; } ); }); } } dojo.addOnLoad(dojo.behavior, "apply"); }