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 });