1 /**
  2  * Event
  3  *
  4  * Event api wrapper
  5  * @todo Add method for triggering events
  6  */
  7 (function (){
  8 
  9 	"use strict";
 10 
 11 	// Property name for expandos on DOM objects
 12 	var kis_expando = "KIS_0_6_0";
 13 
 14 	var _attach, _remove, _add_remove, e, _attach_delegate;
 15 
 16 	// Define the proper _attach and _remove functions
 17 	// based on browser support
 18 	if(typeof document.addEventListener !== "undefined")
 19 	{
 20 		/**
 21 		 * @private
 22 		 */
 23 		_attach = function (sel, event, callback)
 24 		{
 25 			if(typeof sel.addEventListener !== "undefined")
 26 			{
 27 				// Duplicated events are dropped, per the specification
 28 				sel.addEventListener(event, callback, false);
 29 			}
 30 		};
 31 		/**
 32 		 * @private
 33 		 */
 34 		_remove = function (sel, event, callback)
 35 		{
 36 			if(typeof sel.removeEventListener !== "undefined")
 37 			{
 38 				sel.removeEventListener(event, callback, false);
 39 			}
 40 		};
 41 	}
 42 	// typeof function doesn't work in IE where attachEvent is available: brute force it
 43 	else if(typeof document.attachEvent !== "undefined")
 44 	{
 45 		/**
 46 		 * @private
 47 		 */
 48 		_attach = function (sel, event, callback)
 49 		{
 50 			function _listener () {
 51 				// Internet Explorer fails to correctly set the 'this' object
 52 				// for event listeners, so we need to set it ourselves.
 53 				callback.apply(arguments[0]);
 54 			}
 55 
 56 			if (typeof sel.attachEvent !== "undefined")
 57 			{
 58 				_remove(event, callback); // Make sure we don't have duplicate listeners
 59 
 60 				sel.attachEvent("on" + event, _listener);
 61 				// Store our listener so we can remove it later
 62 				var expando = sel[kis_expando] = sel[kis_expando] || {};
 63 				expando.listeners = expando.listeners || {};
 64 				expando.listeners[event] = expando.listeners[event] || [];
 65 				expando.listeners[event].push({
 66 					callback: callback,
 67 					_listener: _listener
 68 				});
 69 			}
 70 		};
 71 		/**
 72 		 * @private
 73 		 */
 74 		_remove = function (sel, event, callback)
 75 		{
 76 			if(typeof sel.detachEvent !== "undefined")
 77 			{
 78 				var expando = sel[kis_expando];
 79 				if (expando && expando.listeners
 80 						&& expando.listeners[event])
 81 				{
 82 					var listeners = expando.listeners[event];
 83 					var len = listeners.length;
 84 					for (var i=0; i<len; i++)
 85 					{
 86 						if (listeners[i].callback === callback)
 87 						{
 88 							sel.detachEvent("on" + event, listeners[i]._listener);
 89 							listeners.splice(i, 1);
 90 							if(listeners.length === 0)
 91 							{
 92 								delete expando.listeners[event];
 93 							}
 94 							return;
 95 						}
 96 					}
 97 				}
 98 			}
 99 		};
100 	}
101 
102 	_add_remove = function (sel, event, callback, add)
103 	{
104 		var i, len;
105 
106 		if(typeof sel === "undefined")
107 		{
108 			return null;
109 		}
110 
111 		// Multiple events? Run recursively!
112 		if ( ! event.match(/^([\w\-]+)$/))
113 		{
114 			event = event.split(" ");
115 
116 			len = event.length;
117 
118 			for (i = 0; i < len; i++)
119 			{
120 				_add_remove(sel, event[i], callback, add);
121 			}
122 
123 			return;
124 		}
125 
126 
127 		if(add === true)
128 		{
129 			_attach(sel, event, callback);
130 		}
131 		else
132 		{
133 			_remove(sel, event, callback);
134 		}
135 	};
136 
137 	_attach_delegate = function(sel, target, event, callback)
138 	{
139 		// attach the listener to the parent object
140 		_add_remove(sel, event, function(e){
141 
142 			var elem, t, tar;
143 
144 			// IE 8 doesn't have event bound to element
145 			e = e || window.event;
146 
147 			// Get the live version of the target selector
148 			t = $_.$(target, sel);
149 
150 			// Check each element to see if it matches the target
151 			for(elem in t)
152 			{
153 				// IE 8 doesn't have target in the event object
154 				tar = e.target || e.srcElement;
155 
156 				// Fire target callback when event bubbles from target
157 				if(tar == t[elem])
158 				{
159 					// Trigger the event callback
160 					callback.call(t[elem], e);
161 
162 					// Stop event propegation
163 					e.stopPropagation();
164 				}
165 			}
166 
167 		}, true);
168 	};
169 
170 	// --------------------------------------------------------------------------
171 
172 	/**
173 	 * Event Listener module
174 	 *
175 	 * @namespace
176 	 * @name event
177 	 * @memberOf $_
178 	 */
179 	e = {
180 		/**
181 		 * Adds an event that returns a callback when triggered on the selected
182 		 * event and selector
183 		 *
184 		 * @memberOf $_.event
185 		 * @name add
186 		 * @function
187 		 * @example Eg. $_("#selector").event.add("click", do_something());
188 		 * @param string event
189 		 * @param function callback
190 		 */
191 		add: function (event, callback)
192 		{
193 			$_.each(function(e){
194 				_add_remove(e, event, callback, true);
195 			});
196 		},
197 		/**
198 		 * Removes an event bound the the specified selector, event type, and callback
199 		 *
200 		 * @memberOf $_.event
201 		 * @name remove
202 		 * @function
203 		 * @example Eg. $_("#selector").event.remove("click", do_something());
204 		 * @param string event
205 		 * @param string callback
206 		 */
207 		remove: function (event, callback)
208 		{
209 			$_.each(function(e){
210 				_add_remove(e, event, callback, false);
211 			});
212 		},
213 		/**
214 		 * Binds a persistent event to the document
215 		 *
216 		 * @memberOf $_.event
217 		 * @name live
218 		 * @function
219 		 * @example Eg. $_.event.live(".button", "click", do_something());
220 		 * @param string target
221 		 * @param string event
222 		 * @param function callback
223 		 */
224 		live: function (target, event, callback)
225 		{
226 			_attach_delegate(document.documentElement, target, event, callback);
227 		},
228 		/**
229 		 * Binds an event to a parent object
230 		 *
231 		 * @memberOf $_.event
232 		 * @name delegate
233 		 * @function
234 		 * @example Eg. $_("#parent").delegate(".button", "click", do_something());
235 		 * @param string target
236 		 * @param string event_type
237 		 * @param function callback
238 		 */
239 		delegate: function (target, event, callback)
240 		{
241 			$_.each(function(e){
242 				_attach_delegate(e, target, event, callback);
243 			});
244 		}
245 	};
246 
247 	$_.ext('event', e);
248 
249 }());