source: Dev/branches/rest-dojo-ui/client/dojo/behavior.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 7.6 KB
Line 
1define(["./_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
8dojo.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
245ready(dojo.behavior, "apply"); // FIXME: should this use a priority? before/after parser priority?
246
247return dojo.behavior;
248});
Note: See TracBrowser for help on using the repository browser.