behavior.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dojo.behavior"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojo.behavior"] = true;
  8. dojo.provide("dojo.behavior");
  9. dojo.behavior = new function(){
  10. // summary:
  11. // Utility for unobtrusive/progressive event binding, DOM traversal,
  12. // and manipulation.
  13. //
  14. // description:
  15. //
  16. // A very simple, lightweight mechanism for applying code to
  17. // existing documents, based around `dojo.query` (CSS3 selectors) for node selection,
  18. // and a simple two-command API: `dojo.behavior.add()` and `dojo.behavior.apply()`;
  19. //
  20. // Behaviors apply to a given page, and are registered following the syntax
  21. // options described by `dojo.behavior.add` to match nodes to actions, or "behaviors".
  22. //
  23. // Added behaviors are applied to the current DOM when .apply() is called,
  24. // matching only new nodes found since .apply() was last called.
  25. //
  26. function arrIn(obj, name){
  27. if(!obj[name]){ obj[name] = []; }
  28. return obj[name];
  29. }
  30. var _inc = 0;
  31. function forIn(obj, scope, func){
  32. var tmpObj = {};
  33. for(var x in obj){
  34. if(typeof tmpObj[x] == "undefined"){
  35. if(!func){
  36. scope(obj[x], x);
  37. }else{
  38. func.call(scope, obj[x], x);
  39. }
  40. }
  41. }
  42. }
  43. // FIXME: need a better test so we don't exclude nightly Safari's!
  44. this._behaviors = {};
  45. this.add = function(/* Object */behaviorObj){
  46. // summary:
  47. // Add the specified behavior to the list of behaviors, ignoring existing
  48. // matches.
  49. //
  50. // description:
  51. // Add the specified behavior to the list of behaviors which will
  52. // be applied the next time apply() is called. Calls to add() for
  53. // an already existing behavior do not replace the previous rules,
  54. // but are instead additive. New nodes which match the rule will
  55. // have all add()-ed behaviors applied to them when matched.
  56. //
  57. // The "found" method is a generalized handler that's called as soon
  58. // as the node matches the selector. Rules for values that follow also
  59. // apply to the "found" key.
  60. //
  61. // The "on*" handlers are attached with `dojo.connect()`, using the
  62. // matching node
  63. //
  64. // If the value corresponding to the ID key is a function and not a
  65. // list, it's treated as though it was the value of "found".
  66. //
  67. // dojo.behavior.add() can be called any number of times before
  68. // the DOM is ready. `dojo.behavior.apply()` is called automatically
  69. // by `dojo.addOnLoad`, though can be called to re-apply previously added
  70. // behaviors anytime the DOM changes.
  71. //
  72. // There are a variety of formats permitted in the behaviorObject
  73. //
  74. // example:
  75. // Simple list of properties. "found" is special. "Found" is assumed if
  76. // no property object for a given selector, and property is a function.
  77. //
  78. // | dojo.behavior.add({
  79. // | "#id": {
  80. // | "found": function(element){
  81. // | // node match found
  82. // | },
  83. // | "onclick": function(evt){
  84. // | // register onclick handler for found node
  85. // | }
  86. // | },
  87. // | "#otherid": function(element){
  88. // | // assumes "found" with this syntax
  89. // | }
  90. // | });
  91. //
  92. // example:
  93. // If property is a string, a dojo.publish will be issued on the channel:
  94. //
  95. // | dojo.behavior.add({
  96. // | // dojo.publish() whenever class="noclick" found on anchors
  97. // | "a.noclick": "/got/newAnchor",
  98. // | "div.wrapper": {
  99. // | "onclick": "/node/wasClicked"
  100. // | }
  101. // | });
  102. // | dojo.subscribe("/got/newAnchor", function(node){
  103. // | // handle node finding when dojo.behavior.apply() is called,
  104. // | // provided a newly matched node is found.
  105. // | });
  106. //
  107. // example:
  108. // Scoping can be accomplished by passing an object as a property to
  109. // a connection handle (on*):
  110. //
  111. // | dojo.behavior.add({
  112. // | "#id": {
  113. // | // like calling dojo.hitch(foo,"bar"). execute foo.bar() in scope of foo
  114. // | "onmouseenter": { targetObj: foo, targetFunc: "bar" },
  115. // | "onmouseleave": { targetObj: foo, targetFunc: "baz" }
  116. // | }
  117. // | });
  118. //
  119. // example:
  120. // Bahaviors match on CSS3 Selectors, powered by dojo.query. Example selectors:
  121. //
  122. // | dojo.behavior.add({
  123. // | // match all direct descendants
  124. // | "#id4 > *": function(element){
  125. // | // ...
  126. // | },
  127. // |
  128. // | // match the first child node that's an element
  129. // | "#id4 > :first-child": { ... },
  130. // |
  131. // | // match the last child node that's an element
  132. // | "#id4 > :last-child": { ... },
  133. // |
  134. // | // all elements of type tagname
  135. // | "tagname": {
  136. // | // ...
  137. // | },
  138. // |
  139. // | "tagname1 tagname2 tagname3": {
  140. // | // ...
  141. // | },
  142. // |
  143. // | ".classname": {
  144. // | // ...
  145. // | },
  146. // |
  147. // | "tagname.classname": {
  148. // | // ...
  149. // | }
  150. // | });
  151. //
  152. var tmpObj = {};
  153. forIn(behaviorObj, this, function(behavior, name){
  154. var tBehavior = arrIn(this._behaviors, name);
  155. if(typeof tBehavior["id"] != "number"){
  156. tBehavior.id = _inc++;
  157. }
  158. var cversion = [];
  159. tBehavior.push(cversion);
  160. if((dojo.isString(behavior))||(dojo.isFunction(behavior))){
  161. behavior = { found: behavior };
  162. }
  163. forIn(behavior, function(rule, ruleName){
  164. arrIn(cversion, ruleName).push(rule);
  165. });
  166. });
  167. };
  168. var _applyToNode = function(node, action, ruleSetName){
  169. if(dojo.isString(action)){
  170. if(ruleSetName == "found"){
  171. dojo.publish(action, [ node ]);
  172. }else{
  173. dojo.connect(node, ruleSetName, function(){
  174. dojo.publish(action, arguments);
  175. });
  176. }
  177. }else if(dojo.isFunction(action)){
  178. if(ruleSetName == "found"){
  179. action(node);
  180. }else{
  181. dojo.connect(node, ruleSetName, action);
  182. }
  183. }
  184. };
  185. this.apply = function(){
  186. // summary:
  187. // Applies all currently registered behaviors to the document.
  188. //
  189. // description:
  190. // Applies all currently registered behaviors to the document,
  191. // taking care to ensure that only incremental updates are made
  192. // since the last time add() or apply() were called.
  193. //
  194. // If new matching nodes have been added, all rules in a behavior will be
  195. // applied to that node. For previously matched nodes, only
  196. // behaviors which have been added since the last call to apply()
  197. // will be added to the nodes.
  198. //
  199. // apply() is called once automatically by `dojo.addOnLoad`, so
  200. // registering behaviors with `dojo.behavior.add` before the DOM is
  201. // ready is acceptable, provided the dojo.behavior module is ready.
  202. //
  203. // Calling appy() manually after manipulating the DOM is required
  204. // to rescan the DOM and apply newly .add()ed behaviors, or to match
  205. // nodes that match existing behaviors when those nodes are added to
  206. // the DOM.
  207. //
  208. forIn(this._behaviors, function(tBehavior, id){
  209. dojo.query(id).forEach(
  210. function(elem){
  211. var runFrom = 0;
  212. var bid = "_dj_behavior_"+tBehavior.id;
  213. if(typeof elem[bid] == "number"){
  214. runFrom = elem[bid];
  215. if(runFrom == (tBehavior.length)){
  216. return;
  217. }
  218. }
  219. // run through the versions, applying newer rules at each step
  220. for(var x=runFrom, tver; tver = tBehavior[x]; x++){
  221. forIn(tver, function(ruleSet, ruleSetName){
  222. if(dojo.isArray(ruleSet)){
  223. dojo.forEach(ruleSet, function(action){
  224. _applyToNode(elem, action, ruleSetName);
  225. });
  226. }
  227. });
  228. }
  229. // ensure that re-application only adds new rules to the node
  230. elem[bid] = tBehavior.length;
  231. }
  232. );
  233. });
  234. };
  235. };
  236. dojo.addOnLoad(dojo.behavior, "apply");
  237. }