1 /** @private */
  2 function returnFalse() {
  3 	return false;
  4 }
  5 /** @private */
  6 function returnTrue() {
  7 	return true;
  8 }
  9 /** @private used to clear all events on a provided element */
 10 function clearEvents( elem, type, events, handlers ){
 11 	// check whether it is a W3C browser or not
 12 	if ( elem.removeEventListener ) {
 13 		// remove event listener and unregister element event
 14 		elem.removeEventListener( type, handlers[ type ], false );
 15 	} else if ( elem.detachEvent ) {
 16 
 17 		elem.detachEvent( "on" + type, handlers[ type ] );
 18 	}
 19 	
 20 	if( events && events[type] ){ delete events[ type ]; }
 21 	if( handlers && handlers[type] ){ delete handlers[ type ]; }
 22 }
 23 /**
 24  * @constructor
 25  * @description the event constructor to provide unified event object support
 26  * @param {String|Event} the name or event to coerce into a Simples.Event to bridge the differences between implementations
 27  */
 28 Simples.Event = function( event ){
 29 	// Allow instantiation without the 'new' keyword
 30 	if ( !this.isDefaultPrevented ) {
 31 		return new Simples.Event( event );
 32 	}
 33 
 34 	// Event object
 35 	if ( event && event.type ) {
 36 		this.originalEvent = event;
 37 		this.type = event.type;
 38 	// Event type
 39 	} else {
 40 		this.type = event;
 41 	}
 42     
 43 	// timeStamp is buggy for some events on Firefox(#3843)
 44 	// So we won't rely on the native value
 45 	this.timeStamp = new Date().getTime();
 46 
 47 	// set the event to be fixed
 48 	this[ accessID ] = true;
 49 	// return self
 50 	return this;   
 51 };
 52 /**
 53  * Simples.Event: the event constructor to provide unified event object support
 54  */
 55 Simples.Event.prototype = {
 56 	/** 
 57 	 * @description used to prevent the browser from performing its default action
 58 	 */
 59 	preventDefault: function() {
 60 		this.isDefaultPrevented = returnTrue;
 61 
 62 		var e = this.originalEvent;
 63 		if ( !e ) {
 64 			return;
 65 		}
 66 		
 67 		// if preventDefault exists run it on the original event
 68 		if ( e.preventDefault ) {
 69 			e.preventDefault();
 70 		}
 71 		// otherwise set the returnValue property of the original event to false (IE)
 72 		e.returnValue = false;
 73 	},
 74 	/** 
 75 	 * @description used to stop the event from continuing its bubbling
 76 	 */	
 77 	stopPropagation: function() {
 78 		this.isPropagationStopped = returnTrue;
 79 
 80 		var e = this.originalEvent;
 81 		if ( !e ) {
 82 			return;
 83 		}
 84 		// if stopPropagation exists run it on the original event
 85 		if ( e.stopPropagation ) {
 86 			e.stopPropagation();
 87 		}
 88 		// otherwise set the cancelBubble property of the original event to true (IE)
 89 		e.cancelBubble = true;
 90 	},
 91 	/** 
 92 	 * @description used to stop the event bubbling up and any other event callbacks from being triggered on the current element
 93 	 */	
 94 	stopImmediatePropagation: function() {
 95 	    this.isImmediatePropagationStopped = returnTrue;
 96 	    this.stopPropagation();
 97 	},
 98 	/**
 99 	 * @function
100 	 * @description used to determine wherther the event has had preventDefault called
101 	 */	
102 	isDefaultPrevented: returnFalse,
103 	/** 
104 	 * @function
105 	 * @description used to determine wherther the event has had stopPropagation called
106 	 */	
107 	isPropagationStopped: returnFalse,
108 	/** 
109 	 * @function
110 	 * @description used to determine wherther the event has had stopImmediatePropagation called
111 	 */	
112 	isImmediatePropagationStopped: returnFalse
113 };
114 	
115 Simples.merge( /** @lends Simples */ {
116 	/**
117 	 * @description to add the event to the provided element
118 	 * @param {Element} elem the element to attach the event to	
119 	 * @param {String} type the type of event to bind i.e. click, custom, etc
120 	 * @param {Function} callback the callback to bind, false can be specified to have a return false callback
121 	 */
122 	attach : function( elem, type, callback ){
123 		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
124 			return;
125 		} else if( typeof type === "string" && type.indexOf(" ") > -1 ){
126 			type = type.split(" ");
127 			for(var m=0,n=type.length;m<n;m++){
128 				Simples.attach( elem, type[m], callback );
129 			}
130 			return;
131 		}
132 		
133 		if ( callback === false ) {
134 			callback = returnFalse;
135 		}
136 		// For whatever reason, IE has trouble passing the window object
137 		// around, causing it to be cloned in the process
138 		if ( elem.setInterval && ( elem !== WIN && !elem.frameElement ) ) {
139 			elem = WIN;
140 		}
141         
142 		if( Simples.isConstructor( callback, "Function" ) && canDoData( elem ) ){ 
143 			
144 			var data = Simples.data( elem ),
145 				events = data.events ? data.events : data.events = {},
146 				handlers = data.handlers ? data.handlers : data.handlers = {};
147 			
148 			var guid = !callback.guid ? callback.guid = Simples.guid++ : callback.guid, 
149 				handler = handlers[ type ];
150 				
151 			if( !handler ){
152 				handler = handlers[ type ] = function( evt ){
153 					return Simples !== UNDEF ? Simples._eventHandler.apply( handler.elem, arguments ) : UNDEF;
154 				};
155 				handler.elem = elem;
156 				// Attach to the element
157 				if ( elem.addEventListener ) {
158 
159 			        elem.addEventListener(type, handler, false);
160 			    } else if ( elem.attachEvent ) {
161 
162 			        elem.attachEvent("on" + type, handler);
163 			    }
164 			}
165 			
166 			events[ type ] = events[ type ] || [];
167 			events[ type ].push( { callback : callback, guid : guid } );
168 			
169 		}
170 	},
171 	/**
172 	 * @description to remove the event from the provided element
173 	 * @param {Element} elem the element to detach the event from
174 	 * @param {String} type the type of event to unbind i.e. click, custom, etc, if no type is specifed then all events are unbound
175 	 * @param {Function} callback the callback to unbind, if not specified will unbind all the callbacks to this event	
176 	 */
177 	detach : function( elem, type, callback ){
178 		
179 		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
180 			return;
181 		} else if( typeof type === "string" && type.indexOf(" ") > -1 ){
182 			type = type.split(" ");
183 			for(var m=0,n=type.length;m<n;m++){
184 				Simples.detach( elem, type[m], callback );
185 			}
186 			return;
187 		}
188 
189 		if( type && type.type ){
190 			callback = type.handler;
191 			type = type.type;
192 		} else if ( callback === false ) {
193 			callback = returnFalse;
194 		}
195 		   
196 		var elemData = Simples.data( elem ),
197 			events = elemData.events,
198 			handlers = elemData.handlers;
199 		
200 		if( type === UNDEF ){
201 			for( var eventType in events ){
202 				clearEvents( elem, eventType, events, handlers );
203 			}
204 		} else {
205 			var event = events[ type ];
206 
207 			for(var i=0;i<event.length;i++){
208 				if( callback === UNDEF || callback.guid === event[i].guid ){
209 					event.splice( i--, 1 );
210 				}
211 			}
212 
213 			if( event.length === 0 ){
214 				clearEvents( elem, type, events, handlers );
215 			}
216 		}
217 	},
218 	/**
219 	 * @description to trigger an event on a supplied element
220 	 * @param {Element} elem the element to trigger the event on
221 	 * @param {String} type the type of event to trigger i.e. click, custom, etc
222 	 * @param {Any} data the data to attach to the event	
223 	 */
224 	trigger : function( elem, type, data ){
225 		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
226 			return;
227 		}
228 		if ( canDoData( elem ) ) {
229 			// Use browser event generators
230 			var e;
231 			if( elem.dispatchEvent ){
232 				// Build Event
233 				e = DOC.createEvent("HTMLEvents");
234 				e.initEvent(type, true, true); 
235 				if( data ){ e.data = JSON.stringify(data); }
236 				// Dispatch the event to the ELEMENT
237 				elem.dispatchEvent(e);
238 			} else if( elem.fireEvent ) {
239 				e = DOC.createEventObject();
240 				if( data ){ e.data = JSON.stringify(data); }
241 				e.target = elem;
242 				e.eventType = "on"+type;
243 				elem.fireEvent( "on"+type, e );
244 			} 
245 		}		                                         
246 	},
247 	/** @private properties as part of the fix process */
248 	_eventProperties : "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
249 	/** @private to fix the native Event */
250 	_eventFix : function( event ){
251 		 if( event[ accessID ] ){
252 			return event;
253 		}
254 	    // store a copy of the original event object
255 	    // and "clone" to set read-only properties 
256 		var originalEvent = event;
257 		
258 		event = Simples.Event( originalEvent );
259 
260 	    for (var i=Simples._eventProperties.length, prop; i;) {
261 	        prop = Simples._eventProperties[--i];
262 	        event[ prop ] = originalEvent[ prop ];
263 	    }
264 
265 		// Fix target property, if necessary
266 		if ( !event.target ) {
267 			event.target = event.srcElement || DOC; // Fixes #1925 where srcElement might not be defined either
268 		}
269 
270 		// check if target is a textnode (safari)
271 		if ( event.target.nodeType === 3 ) {
272 			event.target = event.target.parentNode;
273 		}
274 
275 		// Add relatedTarget, if necessary
276 		if ( !event.relatedTarget && event.fromElement ) {
277 			event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
278 		}
279 
280 		// Calculate pageX/Y if missing and clientX/Y available
281 		if ( event.pageX == null && event.clientX != null ) {
282 			var doc = DOC.documentElement, body = DOC.body;
283 			event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
284 			event.pageY = event.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) - (doc && doc.clientTop  || body && body.clientTop  || 0);
285 		}
286 
287 		// Add which for key events
288 		if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) {
289 			event.which = event.charCode || event.keyCode;
290 		}
291 
292 		// Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
293 		if ( !event.metaKey && event.ctrlKey ) {
294 			event.metaKey = event.ctrlKey;
295 		}
296 
297 		// Add which for click: 1 === left; 2 === middle; 3 === right
298 		// Note: button is not normalized, so don't use it
299 		if ( !event.which && event.button !== UNDEF ) {
300 			event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
301 		}
302 
303 		if( event.data ){
304             event.data = JSON.parse(event.data);
305 		}
306 
307 	    return event;
308 	},
309 	/** @private to create a unique identifier */
310 	guid : 1e6,
311 	/** @private event handler this is bound to the elem event */
312 	_eventHandler : function( event ){ 
313 		var events, callbacks;
314 		var args = slice.call( arguments, 0 );
315 		event = args[0] = Simples._eventFix( event || WIN.event );
316         event.currentTarget = this;
317 
318 		events = Simples.data( this, "events" );
319 		callbacks = (events || {})[ event.type ];
320          
321 		if( events && callbacks ){
322 			callbacks = slice.call(callbacks, 0);
323 			
324 			for( var i=0,l=callbacks.length;i<l;i++){ 
325 				var callback = callbacks[i];
326 				event.handler = callback.callback;
327 				
328 				var ret = event.handler.apply( this, args );
329 				if( ret !== UNDEF ){
330 					event.result = ret;
331 					if ( ret === false ) { 
332 						event.preventDefault();
333 						event.stopPropagation();
334 					}
335 				}
336 				
337 				if ( event.isImmediatePropagationStopped() ) {
338 					break;
339 				}
340 			}
341 		}
342 		return event.result;
343 	}
344 });
345 
346 Simples.extend( /** @lends Simples.fn */ {
347 	/**
348 	 * @description to add the event from the elements on the Simples object
349 	 * @param {String} type the type of event to bind i.e. click, custom, etc
350 	 * @param {Function} callback the callback to bind, false can be specified to have a return false callback
351 	 */
352 	bind : function( type, callback ){
353 		if( typeof type === "string" && ( callback === false || Simples.isConstructor( callback, "Function" ) ) ){
354 			// Loop over elements    
355 			var i=0,l=this.length;
356 			while(i<l){
357 				// Register each original event and the handled event to allow better detachment
358 				Simples.attach( this[i++], type, callback );
359 			}
360 		}
361 		return this;	
362 	},
363 	/**
364 	 * @description to remove the event from the elements on the Simples object
365 	 * @param {String} type the type of event to unbind i.e. click, custom, etc, if no type is specifed then all events are unbound
366 	 * @param {Function} callback the callback to unbind, if not specified will unbind all the callbacks to this event
367 	 */
368 	unbind : function( type, callback ){
369 		// Loop over elements    
370 		var i=0,l=this.length;
371 		while(i<l){
372 			// Register each original event and the handled event to allow better detachment    
373 			Simples.detach( this[i++], type, callback );
374 		}
375 		return this;
376 	},
377 	/**
378 	 * @description to trigger an event on the elements on the Simples object
379 	 * @param {String} type the type of event to trigger i.e. click, custom, etc
380 	 * @param {Any} data the data to attach to the event
381 	 */
382 	trigger : function( type, data ){
383 		if( typeof type === "string"){ 
384 			// Loop over elements
385 			var i=0,l=this.length;
386 			while(i<l){
387 				// Register each original event and the handled event to allow better detachment    
388 				Simples.trigger( this[i++], type, data );
389 			}
390 		}
391 		return this;
392 	}
393 });