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