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