1
dojo.provide("dojo.behavior");
3
dojo.behavior = new function(){
5
// Utility for unobtrusive/progressive event binding, DOM traversal,
10
// A very simple, lightweight mechanism for applying code to
11
// existing documents, based around `dojo.query` (CSS3 selectors) for node selection,
12
// and a simple two-command API: `dojo.behavior.add()` and `dojo.behavior.apply()`;
14
// Behaviors apply to a given page, and are registered following the syntax
15
// options described by `dojo.behavior.add` to match nodes to actions, or "behaviors".
17
// Added behaviors are applied to the current DOM when .apply() is called,
18
// matching only new nodes found since .apply() was last called.
20
function arrIn(obj, name){
21
if(!obj[name]){ obj[name] = []; }
27
function forIn(obj, scope, func){
30
if(typeof tmpObj[x] == "undefined"){
34
func.call(scope, obj[x], x);
40
// FIXME: need a better test so we don't exclude nightly Safari's!
42
this.add = function(/* Object */behaviorObj){
44
// Add the specified behavior to the list of behaviors, ignoring existing
48
// Add the specified behavior to the list of behaviors which will
49
// be applied the next time apply() is called. Calls to add() for
50
// an already existing behavior do not replace the previous rules,
51
// but are instead additive. New nodes which match the rule will
52
// have all add()-ed behaviors applied to them when matched.
54
// The "found" method is a generalized handler that's called as soon
55
// as the node matches the selector. Rules for values that follow also
56
// apply to the "found" key.
58
// The "on*" handlers are attached with `dojo.connect()`, using the
61
// If the value corresponding to the ID key is a function and not a
62
// list, it's treated as though it was the value of "found".
64
// dojo.behavior.add() can be called any number of times before
65
// the DOM is ready. `dojo.behavior.apply()` is called automatically
66
// by `dojo.addOnLoad`, though can be called to re-apply previously added
67
// behaviors anytime the DOM changes.
69
// There are a variety of formats permitted in the behaviorObject
72
// Simple list of properties. "found" is special. "Found" is assumed if
73
// no property object for a given selector, and property is a function.
75
// | dojo.behavior.add({
77
// | "found": function(element){
78
// | // node match found
80
// | "onclick": function(evt){
81
// | // register onclick handler for found node
84
// | "#otherid": function(element){
85
// | // assumes "found" with this syntax
90
// If property is a string, a dojo.publish will be issued on the channel:
92
// | dojo.behavior.add({
93
// | // dojo.publish() whenever class="noclick" found on anchors
94
// | "a.noclick": "/got/newAnchor",
96
// | "onclick": "/node/wasClicked"
99
// | dojo.subscribe("/got/newAnchor", function(node){
100
// | // handle node finding when dojo.behavior.apply() is called,
101
// | // provided a newly matched node is found.
105
// Scoping can be accomplished by passing an object as a property to
106
// a connection handle (on*):
108
// | dojo.behavior.add({
110
// | // like calling dojo.hitch(foo,"bar"). execute foo.bar() in scope of foo
111
// | "onmouseenter": { targetObj: foo, targetFunc: "bar" },
112
// | "onmouseleave": { targetObj: foo, targetFunc: "baz" }
117
// Bahaviors match on CSS3 Selectors, powered by dojo.query. Example selectors:
119
// | dojo.behavior.add({
120
// | // match all direct descendants
121
// | "#id4 > *": function(element){
125
// | // match the first child node that's an element
126
// | "#id4 > :first-child": { ... },
128
// | // match the last child node that's an element
129
// | "#id4 > :last-child": { ... },
131
// | // all elements of type tagname
136
// | "tagname1 tagname2 tagname3": {
144
// | "tagname.classname": {
151
forIn(behaviorObj, this, function(behavior, name){
152
var tBehavior = arrIn(this._behaviors, name);
153
if(typeof tBehavior["id"] != "number"){
154
tBehavior.id = _inc++;
157
tBehavior.push(cversion);
158
if((dojo.isString(behavior))||(dojo.isFunction(behavior))){
159
behavior = { found: behavior };
161
forIn(behavior, function(rule, ruleName){
162
arrIn(cversion, ruleName).push(rule);
167
var _applyToNode = function(node, action, ruleSetName){
168
if(dojo.isString(action)){
169
if(ruleSetName == "found"){
170
dojo.publish(action, [ node ]);
172
dojo.connect(node, ruleSetName, function(){
173
dojo.publish(action, arguments);
176
}else if(dojo.isFunction(action)){
177
if(ruleSetName == "found"){
180
dojo.connect(node, ruleSetName, action);
185
this.apply = function(){
187
// Applies all currently registered behaviors to the document.
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.
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.
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.
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
208
forIn(this._behaviors, function(tBehavior, id){
209
dojo.query(id).forEach(
212
var bid = "_dj_behavior_"+tBehavior.id;
213
if(typeof elem[bid] == "number"){
215
if(runFrom == (tBehavior.length)){
219
// run through the versions, applying newer rules at each step
221
for(var x=runFrom, tver; tver = tBehavior[x]; x++){
222
forIn(tver, function(ruleSet, ruleSetName){
223
if(dojo.isArray(ruleSet)){
224
dojo.forEach(ruleSet, function(action){
225
_applyToNode(elem, action, ruleSetName);
231
// ensure that re-application only adds new rules to the node
232
elem[bid] = tBehavior.length;
239
dojo.addOnLoad(dojo.behavior, "apply");