1 /** 2 * @namespace Cross-browser registration of event handlers, automatically normalizes the event object to provide web 3 * standard features such as preventDefault() and stopPropagation(). 4 */ 5 Jelo.Event = function() { 6 7 /** @private convenience */ 8 var D = document; 9 10 /** @private */ 11 var isDomReady = false; 12 13 /** @private */ 14 var domFunctions = []; 15 16 /** @private */ 17 var handlers = []; 18 19 /** @private */ 20 var fireDomReady = function() { 21 isDomReady = true; 22 for (var i = 0; i < domFunctions.length; i++) { 23 var fn = domFunctions[i]; 24 try { 25 fn(); 26 } catch (e) { 27 // TODO: log these internally so they can be shown if the developer desires 28 } 29 } 30 domFunctions = []; 31 }; 32 33 /** @private */ 34 var init = function() { 35 if (D.addEventListener) { 36 D.addEventListener("DOMContentLoaded", fireDomReady, false); 37 } else if (Jelo.Env.isIE) { 38 D.write("<script id='ieDomReady' defer " + "src=//:><\/script>"); 39 var ieReady = D.getElementById("ieDomReady"); 40 ieReady.onreadystatechange = function() { 41 if (ieReady.readyState === "complete") { 42 fireDomReady(); 43 } 44 }; 45 } else { 46 var oldOnload = (typeof window.onload == "function") 47 ? window.onload 48 : function() {}; 49 window.onload = function() { 50 oldOnload(); 51 fireDomReady(); 52 }; 53 } 54 if (Jelo.Env.isWebkit) { 55 var timerDomReady = setInterval(function() { 56 if (/loaded|complete/i.test(D.readyState)) { 57 fireDomReady.call(this); 58 clearInterval(timerDomReady); 59 } 60 }, 10); 61 } 62 }(); 63 64 function find(el, ev, fn) { 65 var handlers = el._handlers; 66 if (!handlers) { 67 return -1; 68 } 69 var d = el.document || el; 70 var w = d.parentWindow; 71 for (var i = handlers.length - 1; i >= 0; i--) { 72 var a = w._allHandlers[handlers[i]]; 73 if (a.eventType == ev && a.handler == fn) { 74 return i; 75 } 76 } 77 return -1; 78 } 79 80 function removeAllHandlers() { 81 var w = this; 82 var wa = w._allHandlers; 83 for (var id in wa) { 84 if (wa.hasOwnProperty(id)) { 85 var h = wa[id]; 86 h.element.detachEvent('on' + h.eventType, h.wrappedHandler); 87 delete wa[id]; 88 } 89 } 90 } 91 92 /** @scope Jelo.Event */ 93 return { 94 /** 95 * Fires after all elements in the document is available. In most cases, this function may be executed before 96 * window.onload and before images fully load, which can result in a faster page response time. 97 * 98 * @see Jelo#onReady 99 */ 100 onReady : function(fn) { 101 if (typeof fn === "function") { 102 if (isDomReady) { 103 fn(); 104 } else { 105 domFunctions.push(fn); 106 } 107 } 108 }, 109 /** 110 * Start listening for an event. Multiple listeners can be registered to a single element. Can be accessed via 111 * {@link Jelo.on}. 112 * 113 * @param {HTMLElement} element HTML element to which Jelo should listen. 114 * @param {String} eventName The type of event for which Jelo should listen, such as "click" or "mouseup". 115 * Should be all lowercase, and WITHOUT the IE prefix "on". 116 * @param {Function} handler Method to be invoked when the event occurs. The execution scope ("this") will be 117 * the actual element that caught the event which, due to the DOM hierarchy, may be a child of the 118 * element registered via this function. The function is passed the following arguments: 119 * <ul> 120 * <li>target: The element that caught the event.</li> 121 * <li>event: The event object, normalized to conform to W3C standards.</li> 122 * </ul> 123 */ 124 add : function(el, ev, fn) { 125 if (!el || !ev || !fn) { 126 return; 127 } 128 if (Jelo.Valid.isArray(el)) { 129 Jelo.each(el, function() { 130 Jelo.Event.add(this, ev, fn); 131 }); 132 return; 133 } 134 var handler = function(e) { 135 e = e || window.event; 136 var event = {}; 137 var properties = ['type', 'shiftKey', 'ctrlKey', 'altKey', 'keyCode', 'charCode', 'button', 'which', 138 'clientX', 'clientY', 'mouseX', 'mouseY', 'metaKey', 'pageX', 'pageY', 'screenX', 'screenY', 139 'relatedTarget']; 140 var len = properties.length; 141 for (var i = 0; i < len; i++) { 142 event[properties[i]] = e[properties[i]]; 143 } 144 target = e.target 145 ? e.target 146 : e.srcElement; 147 if (target.nodeType === 3) { 148 target = target.parentNode; 149 } 150 event.preventDefault = function() { 151 if (e.preventDefault) { 152 e.preventDefault(); 153 } else { 154 e.returnValue = false; 155 } 156 }; 157 event.stopPropagation = function() { 158 if (e.stopPropagation) { 159 e.stopPropagation(); 160 } else { 161 e.cancelBubble = true; 162 } 163 }; 164 fn.call(target, target, event); 165 }; 166 if (el.addEventListener) { 167 el.addEventListener(ev, handler, false); 168 } else if (el.attachEvent) { 169 el.attachEvent("on" + ev, handler); 170 } else { 171 throw ("Jelo.Event.add: Could not observe " + ev + " on " + el); 172 } 173 var newEvent = { 174 target : el, 175 event : ev, 176 handler : handler, 177 fn : fn 178 }; 179 handlers.push(newEvent); 180 }, 181 /** 182 * Stop listening for an event. Safe to call even if no such listener has been registered. Can be accessed via 183 * {@link Jelo.un}. 184 * 185 * @param {HTMLElement} element HTML element to which Jelo should listen. 186 * @param {String} eventName The type of event for which Jelo should listen, such as "click" or "mouseup". 187 * Should be all lowercase, and WITHOUT the IE prefix "on". 188 * @param {Function} handler Method to be invoked when the event occurs. Anonymous handlers cannot be 189 * unregistered at this time. 190 */ 191 remove : function(el, ev, fn) { 192 if (Jelo.Valid.isArray(el)) { 193 Jelo.each(el, function() { 194 Jelo.Event.remove(this, ev, fn); 195 }); 196 return; 197 } 198 if (!!el && (typeof ev == 'string') && (typeof fn == 'function')) { 199 for (var i = 0; i < handlers.length; i++) { 200 var jeh = handlers[i]; 201 var ml = el === jeh.target; 202 var mv = ev === jeh.event; 203 var mf = fn === jeh.fn; 204 if (ml && mv && mf) { 205 if (el.removeEventListener) { 206 el.removeEventListener(ev, jeh.handler, false); 207 } else if (el.detachEvent) { 208 el.detachEvent("on" + ev, jeh.handler); 209 } else { 210 throw ("Jelo.Event.remove: Could not remove " + ev + " from " + el); 211 } 212 handlers.splice(i, 1); 213 break; 214 } 215 } 216 } else { 217 throw ("Syntax Error. Jelo.Event.remove(DOMElement, Event:String, Function"); 218 } 219 }, 220 221 /** 222 * @deprecated Bootstrap the next version of Jelo.Event.add and Jelo.Event.remove. The only difference you 223 * should notice as a developer is that the arguments passed to the OLD event handler were (target, 224 * event). The NEW arguments are (event) to fit in line with most implementations out there. See 225 * also: {@link Jelo.Event.isFixed}. 226 */ 227 normalize : function() { 228 Jelo.Event.add = (D.addEventListener) 229 ? function(el, ev, fn) { 230 if (Jelo.Valid.isArray(el)) { 231 Jelo.each(el, function() { 232 Jelo.Event.add(this, ev, fn); 233 }); 234 return; 235 } 236 el.addEventListener(ev, fn, false); 237 } 238 : function(el, ev, fn) { 239 if (Jelo.Valid.isArray(el)) { 240 Jelo.each(el, function() { 241 Jelo.Event.add(this, ev, fn); 242 }); 243 return; 244 } 245 if (find(el, ev, fn) != -1) { 246 return; 247 } 248 var wh = function(e) { 249 e = e || window.event; 250 var event = { 251 _event : e, 252 type : e.type, 253 target : e.srcElement, 254 currentTarget : el, 255 relatedTarget : e.fromElement 256 ? e.fromElement 257 : e.toElement, 258 eventPhase : (e.srcElement == el) 259 ? 2 260 : 3, 261 clientX : e.clientX, 262 clientY : e.clientY, 263 screenX : e.screenX, 264 screenY : e.screenY, 265 altKey : e.altKey, 266 ctrlKey : e.ctrlKey, 267 shiftKey : e.shiftKey, 268 charCode : e.charCode || e.keyCode, 269 keyCode : e.keyCode || e.charCode, 270 button : e.button 271 ? { 272 1 : 0, 273 4 : 1, 274 2 : 2 275 }[e.button] 276 : (e.which 277 ? e.which - 1 278 : -1), 279 which : e.which || e.button, 280 stopPropagation : function() { 281 this._event.cancelBubble = true; 282 }, 283 preventDefault : function() { 284 this._event.returnValue = false; 285 } 286 }; 287 fn.call(el, event); 288 }; 289 el.attachEvent('on' + ev, wh); 290 var h = { 291 element : el, 292 eventType : ev, 293 handler : fn, 294 wrappedHandler : wh 295 }; 296 var d = el.document || el; 297 var w = d.parentWindow; 298 var id = 'h' + Jelo.uID(); 299 if (!w._allHandlers) { 300 w._allHandlers = {}; 301 } 302 w._allHandlers[id] = h; 303 if (!el._handlers) { 304 el._handlers = []; 305 } 306 el._handlers.push(id); 307 if (!w._onunloadRegistered) { 308 w.attachEvent('onunload', removeAllHandlers); 309 w._onunloadRegistered = true; 310 } 311 }; 312 Jelo.Event.remove = (D.removeEventListener) 313 ? function(el, ev, fn) { 314 if (Jelo.Valid.isArray(el)) { 315 Jelo.each(el, function() { 316 Jelo.Event.remove(this, ev, fn); 317 }); 318 return; 319 } 320 el.removeEventListener(ev, fn, false); 321 } 322 : function(el, ev, fn) { 323 if (Jelo.Valid.isArray(el)) { 324 Jelo.each(el, function() { 325 Jelo.Event.remove(this, ev, fn); 326 }); 327 return; 328 } 329 var i = find(el, ev, fn); 330 if (i == -1) { 331 return; 332 } 333 var d = el.document || el; 334 var w = d.parentWindow; 335 var hid = el._handlers[i]; 336 var h = w._allHandlers[hid]; 337 el.detachEvent('on' + ev, h.wrappedHandler); 338 el._handlers.splice(i, 1); 339 delete w._allHandlers[hid]; 340 }; 341 Jelo.on = Jelo.Event.add; 342 Jelo.un = Jelo.Event.remove; 343 Jelo.Event.isFixed = function() { 344 return true; 345 } 346 }, 347 /** 348 * @deprecated True when {@link Jelo.Event.normalize} has been called. 349 */ 350 isFixed : function() { 351 return false; 352 } 353 354 }; 355 }(); 356 357 Jelo.Event.fix = Jelo.Event.normalize; 358 359 /** 360 * Alias for {@link Jelo.Event.add}. 361 * 362 * @function 363 * @memberOf Jelo 364 */ 365 Jelo.on = Jelo.Event.add; 366 367 /** 368 * Alias for {@link Jelo.Event.remove}. 369 * 370 * @function 371 * @memberOf Jelo 372 */ 373 Jelo.un = Jelo.Event.remove; 374 375 /** 376 * Alias for {@link Jelo.Event.remove}. 377 * 378 * @function 379 * @memberOf Jelo.Event 380 */ 381 Jelo.Event.rem = Jelo.Event.remove; 382 383 /** 384 * Alias for {@link Jelo.Event.onReady}. 385 * 386 * @function 387 * @memberOf Jelo 388 */ 389 Jelo.onReady = Jelo.Event.onReady; 390