/*
	Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved.
	Available via Academic Free License >= 2.1 OR the modified BSD license.
	see: http://dojotoolkit.org/license for details
*/


if(!dojo._hasResource["dojo.behavior"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.behavior"] = true;
dojo.provide("dojo.behavior");

dojo.behavior = new function(){
	// summary: 
	//		Utility for unobtrusive/progressive event binding, DOM traversal,
	//		and manipulation.
	//
	// description:
	//		
	//		A very simple, lightweight mechanism for applying code to 
	//		existing documents, based around `dojo.query` (CSS3 selectors) for node selection, 
	//		and a simple two-command API: `dojo.behavior.add()` and `dojo.behavior.apply()`;
	//	
	//		Behaviors apply to a given page, and are registered following the syntax 
	//		options described by `dojo.behavior.add` to match nodes to actions, or "behaviors".
	//		
	//		Added behaviors are applied to the current DOM when .apply() is called,
	//		matching only new nodes found since .apply() was last called. 
	//		
	function arrIn(obj, name){
		if(!obj[name]){ obj[name] = []; }
		return obj[name];
	}

	var _inc = 0;

	function forIn(obj, scope, func){
		var tmpObj = {};
		for(var x in obj){
			if(typeof tmpObj[x] == "undefined"){
				if(!func){
					scope(obj[x], x);
				}else{
					func.call(scope, obj[x], x);
				}
			}
		}
	}

	// FIXME: need a better test so we don't exclude nightly Safari's!
	this._behaviors = {};
	this.add = function(/* Object */behaviorObj){
		//	summary:
		//		Add the specified behavior to the list of behaviors, ignoring existing
		//		matches. 
		//
		//	description:
		//		Add the specified behavior to the list of behaviors which will
		//		be applied the next time apply() is called. Calls to add() for
		//		an already existing behavior do not replace the previous rules,
		//		but are instead additive. New nodes which match the rule will
		//		have all add()-ed behaviors applied to them when matched.
		//		
		//		The "found" method is a generalized handler that's called as soon
		//		as the node matches the selector. Rules for values that follow also
		//		apply to the "found" key.
		//		
		//		The "on*" handlers are attached with `dojo.connect()`, using the 
		//		matching node
		//		
		//		If the value corresponding to the ID key is a function and not a
		//		list, it's treated as though it was the value of "found".
		//
		// 		dojo.behavior.add() can be called any number of times before 
		//		the DOM is ready. `dojo.behavior.apply()` is called automatically
		//		by `dojo.addOnLoad`, though can be called to re-apply previously added
		//		behaviors anytime the DOM changes.
		//
		//		There are a variety of formats permitted in the behaviorObject
		//	
		//	example:
		//		Simple list of properties. "found" is special. "Found" is assumed if 
		//		no property object for a given selector, and property is a function.
		//
		//	|	dojo.behavior.add({
		//	|		"#id": {
		//	|			"found": function(element){
		//	|				// node match found
		//	|			},
		//	|			"onclick": function(evt){
		//	|				// register onclick handler for found node
		//	|			}
		//	|		},
		// 	|		"#otherid": function(element){
		//	|			// assumes "found" with this syntax
		//	|		}
		//	|	});
		//
		//	example: 
		//		 If property is a string, a dojo.publish will be issued on the channel:
		//
		//	|	dojo.behavior.add({
		//	|		// dojo.publish() whenever class="noclick" found on anchors
		//	|		"a.noclick": "/got/newAnchor",
		//	|		"div.wrapper": {
		//	|			"onclick": "/node/wasClicked"
		//	|		}
		//	|	});
		//	|	dojo.subscribe("/got/newAnchor", function(node){
		//	|		// handle node finding when dojo.behavior.apply() is called, 
		//	|		// provided a newly matched node is found.
		//	|	});
		//
		//	example:
		//		Scoping can be accomplished by passing an object as a property to 
		//		a connection handle (on*):
		//	
		//	|	dojo.behavior.add({ 
		//	|		 	"#id": {
		//	|				// like calling dojo.hitch(foo,"bar"). execute foo.bar() in scope of foo
		//	|				"onmouseenter": { targetObj: foo, targetFunc: "bar" },
		//	|				"onmouseleave": { targetObj: foo, targetFunc: "baz" }
		//	|			}
		//	|	});
		//
		//	example: 
		//		Bahaviors match on CSS3 Selectors, powered by dojo.query. Example selectors:
		//
		//	|	dojo.behavior.add({
		//	|		// match all direct descendants
		//	|		"#id4 > *": function(element){
		//	|			// ...
		//	|		},
		//	|		
		//	|		// match the first child node that's an element
		//	|		"#id4 > :first-child": { ... },
		//	|		
		//	|		// match the last child node that's an element
		//	|		"#id4 > :last-child":  { ... },
		//	|		
		//	|		// all elements of type tagname
		//	|		"tagname": {
		//	|			// ...
		//	|		},
		//	|		
		//	|		"tagname1 tagname2 tagname3": {
		//	|			// ...
		//	|		},
		//	|		
		//	|		".classname": {
		//	|			// ...
		//	|		},
		//	|		
		//	|		"tagname.classname": {
		//	|			// ...
		//	|		}
		//	|	});
		//   

		var tmpObj = {};
		forIn(behaviorObj, this, function(behavior, name){
			var tBehavior = arrIn(this._behaviors, name);
			if(typeof tBehavior["id"] != "number"){
				tBehavior.id = _inc++;
			}
			var cversion = [];
			tBehavior.push(cversion);
			if((dojo.isString(behavior))||(dojo.isFunction(behavior))){
				behavior = { found: behavior };
			}
			forIn(behavior, function(rule, ruleName){
				arrIn(cversion, ruleName).push(rule);
			});
		});
	}

	var _applyToNode = function(node, action, ruleSetName){
		if(dojo.isString(action)){
			if(ruleSetName == "found"){
				dojo.publish(action, [ node ]);
			}else{
				dojo.connect(node, ruleSetName, function(){
					dojo.publish(action, arguments);
				});
			}
		}else if(dojo.isFunction(action)){
			if(ruleSetName == "found"){
				action(node);
			}else{
				dojo.connect(node, ruleSetName, action);
			}
		}
	}

	this.apply = function(){
		// summary:
		//		Applies all currently registered behaviors to the document.
		// 
		// description:
		//		Applies all currently registered behaviors to the document,
		//		taking care to ensure that only incremental updates are made
		//		since the last time add() or apply() were called. 
		//	
		//		If new matching nodes have been added, all rules in a behavior will be
		//		applied to that node. For previously matched nodes, only
		//		behaviors which have been added since the last call to apply()
		//		will be added to the nodes.
		//
		//		apply() is called once automatically by `dojo.addOnLoad`, so 
		//		registering behaviors with `dojo.behavior.add` before the DOM is
		//		ready is acceptable, provided the dojo.behavior module is ready.
		//		
		//		Calling appy() manually after manipulating the DOM is required 
		//		to rescan the DOM and apply newly .add()ed behaviors, or to match
		//		nodes that match existing behaviors when those nodes are added to 
		//		the DOM.
		//		
		forIn(this._behaviors, function(tBehavior, id){
			dojo.query(id).forEach( 
				function(elem){
					var runFrom = 0;
					var bid = "_dj_behavior_"+tBehavior.id;
					if(typeof elem[bid] == "number"){
						runFrom = elem[bid];
						if(runFrom == (tBehavior.length)){
							return;
						}
					}
					// run through the versions, applying newer rules at each step

					for(var x=runFrom, tver; tver = tBehavior[x]; x++){
						forIn(tver, function(ruleSet, ruleSetName){
							if(dojo.isArray(ruleSet)){
								dojo.forEach(ruleSet, function(action){
									_applyToNode(elem, action, ruleSetName);
								});
							}
						});
					}

					// ensure that re-application only adds new rules to the node
					elem[bid] = tBehavior.length;
				}
			);
		});
	}
}

dojo.addOnLoad(dojo.behavior, "apply");

}