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