1 /** 2 * @fileOverview Event handling functionality 3 * @author <a href="http://www.bobs-bits.com">Stephen Reay</a> 4 */ 5 6 /** 7 * @namespace BTM Event handling functions 8 */ 9 BTM.Event = { 10 /** 11 * Flag to identify if the DOM has loaded. 12 * @type Boolean 13 */ 14 DOMLoaded : false, 15 16 /** 17 * Reference to all event handlers registered through {@link BTM.Event.observe} 18 * @type {Object[]} 19 */ 20 eventHandlers : [], 21 22 /** 23 * Hash of Event Key Codes, use with {@link BTM.Event.getKeyCode} 24 * @type {Object} 25 */ 26 keyCodes : { 27 3 : 'KEY_CANCEL', 28 6 : 'KEY_HELP', 29 8 : 'KEY_BACK_SPACE', 30 9 : 'KEY_TAB', 31 12 : 'KEY_CLEAR', 32 13 : 'KEY_RETURN', 33 14 : 'KEY_ENTER', 34 16 : 'KEY_SHIFT', 35 17 : 'KEY_CONTROL', 36 18 : 'KEY_ALT', 37 19 : 'KEY_PAUSE', 38 20 : 'KEY_CAPS_LOCK', 39 27 : 'KEY_ESCAPE', 40 32 : 'KEY_SPACE', 41 33 : 'KEY_PAGE_UP', 42 34 : 'KEY_PAGE_DOWN', 43 35 : 'KEY_END', 44 36 : 'KEY_HOME', 45 37 : 'KEY_LEFT', 46 38 : 'KEY_UP', 47 39 : 'KEY_RIGHT', 48 40 : 'KEY_DOWN', 49 44 : 'KEY_PRINTSCREEN', 50 45 : 'KEY_INSERT', 51 46 : 'KEY_DELETE', 52 48 : 'KEY_0', 53 49 : 'KEY_1', 54 50 : 'KEY_2', 55 51 : 'KEY_3', 56 52 : 'KEY_4', 57 53 : 'KEY_5', 58 54 : 'KEY_6', 59 55 : 'KEY_7', 60 56 : 'KEY_8', 61 57 : 'KEY_9', 62 59 : 'KEY_SEMICOLON', 63 61 : 'KEY_EQUALS', 64 65 : 'KEY_A', 65 66 : 'KEY_B', 66 67 : 'KEY_C', 67 68 : 'KEY_D', 68 69 : 'KEY_E', 69 70 : 'KEY_F', 70 71 : 'KEY_G', 71 72 : 'KEY_H', 72 73 : 'KEY_I', 73 74 : 'KEY_J', 74 75 : 'KEY_K', 75 76 : 'KEY_L', 76 77 : 'KEY_M', 77 78 : 'KEY_N', 78 79 : 'KEY_O', 79 80 : 'KEY_P', 80 81 : 'KEY_Q', 81 82 : 'KEY_R', 82 83 : 'KEY_S', 83 84 : 'KEY_T', 84 85 : 'KEY_U', 85 86 : 'KEY_V', 86 87 : 'KEY_W', 87 88 : 'KEY_X', 88 89 : 'KEY_Y', 89 90 : 'KEY_Z', 90 93 : 'KEY_CONTEXT_MENU', 91 96 : 'KEY_NUMPAD0', 92 97 : 'KEY_NUMPAD1', 93 98 : 'KEY_NUMPAD2', 94 99 : 'KEY_NUMPAD3', 95 100 : 'KEY_NUMPAD4', 96 101 : 'KEY_NUMPAD5', 97 102 : 'KEY_NUMPAD6', 98 103 : 'KEY_NUMPAD7', 99 104 : 'KEY_NUMPAD8', 100 105 : 'KEY_NUMPAD9', 101 106 : 'KEY_MULTIPLY', 102 107 : 'KEY_ADD', 103 108 : 'KEY_SEPARATOR', 104 109 : 'KEY_SUBTRACT', 105 110 : 'KEY_DECIMAL', 106 111 : 'KEY_DIVIDE', 107 112 : 'KEY_F1', 108 113 : 'KEY_F2', 109 114 : 'KEY_F3', 110 115 : 'KEY_F4', 111 116 : 'KEY_F5', 112 117 : 'KEY_F6', 113 118 : 'KEY_F7', 114 119 : 'KEY_F8', 115 120 : 'KEY_F9', 116 121 : 'KEY_F10', 117 122 : 'KEY_F11', 118 123 : 'KEY_F12', 119 124 : 'KEY_F13', 120 125 : 'KEY_F14', 121 126 : 'KEY_F15', 122 127 : 'KEY_F16', 123 128 : 'KEY_F17', 124 129 : 'KEY_F18', 125 130 : 'KEY_F19', 126 131 : 'KEY_F20', 127 132 : 'KEY_F21', 128 133 : 'KEY_F22', 129 134 : 'KEY_F23', 130 135 : 'KEY_F24', 131 144 : 'KEY_NUM_LOCK', 132 145 : 'KEY_SCROLL_LOCK', 133 188 : 'KEY_COMMA', 134 190 : 'KEY_PERIOD', 135 191 : 'KEY_SLASH', 136 192 : 'KEY_BACK_QUOTE', 137 219 : 'KEY_OPEN_BRACKET', 138 220 : 'KEY_BACK_SLASH', 139 221 : 'KEY_CLOSE_BRACKET', 140 222 : 'KEY_QUOTE', 141 224 : 'KEY_META' 142 } 143 }; 144 145 /** 146 * Add an event listener to an element 147 * @param {HTMLElement|String} element element ID or element reference to add event listener to. This can be any element accessibly via JavaScript, including document, window, etc. 148 * @param {String} eventType the event to listen to, e.g.: 'click', 'mouseover', etc. Also supports the custom event 'DOMContentLoaded' on the document object. 149 * @param {Function} callback the Function to be called when the event fires 150 * @param {Number} [priority=5] the priority of the event listener, lower numbers run first 151 * @returns {Number} the index in the Array that holds this callback. Used with {@link BTM.Event.stopObserving} 152 * @example 153 * var t = new MyClass(); 154 * BTM.observe('mydiv', 'click', t.myMethod.bindAsEventListener(t, 'hello', 'world')); 155 * BTM.observe(document,'DOMContentLoaded', someRandomFunc, 8); // runs someRandomFunc() once the DOM has loaded, as a lower priority. 156 * @see {BTM.Event.stopObserving} 157 * @see {BTM.Event.fire} 158 * @see {Function#bind} 159 * @see {Function#bindAsEventListener} 160 */ 161 BTM.Event.observe = function observe(element, eventType, callback, priority) { 162 element = Object.isString(element) ? BTM.$(element) : element; 163 164 if (!Object.isFunction(callback)) { 165 throw new TypeError(); 166 } 167 168 priority = priority || 5; 169 170 var callbackName = callback.name || 'anonymous Function'; 171 172 173 if (Object.isUndefined(element.observerID)) { 174 element.observerID = BTM.Event.eventHandlers.push({'element':element}) - 1; 175 } 176 177 BTM.log("Adding '" + eventType + "' handler '" + callbackName + "'", element); 178 179 if (!BTM.Event.eventHandlers[element.observerID][eventType]) { 180 BTM.Event.eventHandlers[element.observerID][eventType] = []; 181 182 var loaderCallback = BTM.Event.runEventFuncs.bindAsEventListener(window, element, eventType); 183 184 //Add native event listener for our loader 185 if (element.addEventListener) { 186 element.addEventListener(eventType, loaderCallback, false); 187 } 188 else if (element === window && document.addEventListener) { 189 document.addEventListener(eventType, loaderCallback, false); 190 } 191 else if (element.attachEvent) { 192 element.attachEvent('on'+eventType, loaderCallback); 193 } 194 195 } 196 197 if (!BTM.Event.eventHandlers[element.observerID][eventType][priority]) { 198 BTM.Event.eventHandlers[element.observerID][eventType][priority] = []; 199 } 200 201 return BTM.Event.eventHandlers[element.observerID][eventType][priority].push(callback) -1; 202 }; 203 204 /** 205 * Convenience shortcut to {@link BTM.Event.oserve} 206 */ 207 BTM.observe = BTM.Event.observe; 208 209 /** 210 * Remove an event listener from an element 211 * @param {HTMLElement|String} element element ID or element reference to add event listener to. This can be any element accessible via JavaScript, including document, window, etc. 212 * @param {String} eventType the event to listen to, e.g.: 'click', 'mouseover', etc. Also supports the event 'DOMContentLoaded' on the document object. 213 * @param {Number} callbackIndex the index in the Array that holds the callback to be removed. Generated by {@link BTM.Event.observe} 214 * @param {Number} [priority=5] the priority of the event listener, lower numbers run first 215 * @returns {Function} the callback that was removed 216 * @see {BTM.Event.observe} 217 */ 218 BTM.Event.stopObserving = function stopObserving(element, eventType, callbackIndex, priority) { 219 element = Object.isString(element) ? BTM.$(element) : element; 220 221 priority = Object.isUndefined(priority) ? 5 : priority; 222 223 if (element.observerID && 224 BTM.Event.eventHandlers[element.observerID] && 225 BTM.Event.eventHandlers[element.observerID][eventType] && 226 BTM.Event.eventHandlers[element.observerID][eventType][priority] && 227 BTM.Event.eventHandlers[element.observerID][eventType][priority][callbackIndex]) { 228 var callback = BTM.Event.eventHandlers[element.observerID][eventType][priority][callbackIndex]; 229 var callbackName = callback.name || 'anonymous Function'; 230 231 BTM.log("Removing '" + eventType + "' handler '" + callbackName + "'", element); 232 233 delete BTM.Event.eventHandlers[element.observerID][eventType][priority][callbackIndex]; 234 235 return callback; 236 } 237 }; 238 239 /** 240 * Convenience shortcut to {@link BTM.Event.stopObserving} 241 */ 242 BTM.stopObserving = BTM.Event.stopObserving; 243 244 /** 245 * Create a fake Event object 246 * @param {HTMLElement|String} element element ID or element reference to generate Event object for. This can be any element accessible via JavaScript, including document, window, etc 247 * @param {String} eventType the event to fire, e.g.: 'click', 'mouseover', 'MyApp:CustomEvent', etc. Also supports the event 'DOMContentLoaded' on the document object 248 * @param {Object} [extras] key:value pairs of extra data to be included in the custom Event object generated when the Event fires 249 * @returns {Object} the generated Event object 250 */ 251 BTM.Event.fakeEvent = function fakeEvent(element, type, extras) { 252 var eventObject = Object.update({ 253 'createdBy' : 'BTM Event Handling', 254 'target' : element, 255 'type' : type 256 }, extras || {}); 257 258 return eventObject; 259 }; 260 261 262 /** 263 * Run the event listeners assigned to a specific element/event combination. 264 * @param {Event} event the Event object 265 * @param {HTMLElement} element the DOM Element the Event was fired on 266 * @param {String} eventType the Event type, e.g.: 'click', 'hover', etc 267 * @see BTM.Event.observe 268 * @see BTM.Event.fire 269 */ 270 BTM.Event.runEventFuncs = function runEventFuncs(event, element, eventType) { 271 var data = BTM.Event.eventHandlers[element.observerID][eventType]; 272 event = event || false; 273 274 BTM.log("Running '" + eventType + "' event listeners", element); 275 data.forEach(function (callbacks, level) { 276 BTM.log("Running " + callbacks.length + " callbacks at level " + level); 277 callbacks.forEach(function (callback) { 278 callback(event); 279 }); 280 }); 281 }; 282 283 /** 284 * Fire an event on an element. This will apply to native and custom events registered via BTM.Event.observe 285 * @param {HTMLElement|String} element element ID or element reference to fire Event on. This can be any element accessible via JavaScript, including document, window, etc 286 * @param {String} eventType the event to fire, e.g.: 'click', 'mouseover', 'MyApp:CustomEvent', etc. Also supports the event 'DOMContentLoaded' on the document object 287 * @param {Object} [extras] key:value pairs of extra data to be included in the custom Event object generated when the Event fires 288 */ 289 BTM.Event.fire = function fire(element, eventType, extras) { 290 element = BTM.$(element); 291 BTM.log("Firing '" + eventType + "' event", element); 292 if (!Object.isUndefined(element.observerID)) { 293 var evt = BTM.Event.fakeEvent(element, eventType, extras); 294 BTM.Event.runEventFuncs(evt, element, eventType); 295 } 296 }; 297 298 299 /** 300 * Cancel an event. This stops the event from "bubbling" up the DOM tree, and prevents the default action. 301 * @param {Event} event the event to be cancelled 302 */ 303 BTM.Event.cancelEvent = function cancelEvent(event) { 304 if(event === false) { 305 return false; 306 } 307 else if (Object.isUndefined(event)) { 308 event = window.event; 309 } 310 311 BTM.log("Attempting to cancel event", event); 312 313 event.cancelBubble = true; 314 event.returnValue = false; 315 316 if (event.stopPropagation) { 317 event.stopPropagation(); 318 } 319 320 if (event.preventDefault) { 321 event.preventDefault(); 322 } 323 }; 324 325 /** 326 * Determine the original target element an Event fired on 327 * @param {Event} event the event to determine the original target of 328 * @returns {HTMLElement} the element the Event originally fired on 329 */ 330 BTM.Event.getTarget = function getTarget(event) { 331 var target; 332 333 BTM.log("Finding target Element for event", event); 334 335 if (event.target) { 336 target = event.target; 337 } 338 else if (event.srcElement) { 339 target = event.srcElement; 340 } 341 342 if (target.nodeType === 3) { // defeat Safari bug 343 target = target.parentNode; 344 } 345 346 return target; 347 }; 348 349 /** 350 * Determine the Key that fired an event 351 * @param {Event} event the event to determine the original Key of 352 * @returns {String} the Key that was pressed 353 */ 354 BTM.Event.getKeyCode = function getKeyCode(event) { 355 var code; 356 if (event.keyCode) { 357 code = event.keyCode; 358 } 359 else if (event.which) { 360 code = event.which; 361 } 362 363 return code; 364 }; 365 366 (function mimicDOMContentLoaded() { 367 function runLoadEvents(event) { 368 if (BTM.Event.DOMLoaded) { 369 return false; 370 } 371 372 if (typeof ieOnLoadScript !== 'undefined') { 373 ieOnLoadScript.parentNode.removeChild(ieOnLoadScript); 374 } 375 376 BTM.Event.DOMLoaded = true; 377 BTM.Event.runEventFuncs(false, document, 'DOMContentLoaded'); 378 } 379 380 // Add DOMContentLoaded Event listener entry 381 BTM.Event.eventHandlers[0] = { 382 element : document, 383 DOMContentLoaded: [] 384 }; 385 document.observerID = 0; 386 387 //Simulated DOMContentLoaded event for unsupporting browsers 388 // Dean Edwards/Matthias Miller/John Resig 389 var timer = false; 390 391 /*@cc_on @*/ 392 /*@if (@_win32) 393 document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>"); 394 var ieOnLoadScript = document.getElementById("__ie_onload"); 395 ieOnLoadScript.onreadystatechange = function() { 396 if (ieOnLoadScript.readyState === "complete") { 397 runLoadEvents(); // call the onload handler 398 } 399 }; 400 @else @*/ 401 /* for Mozilla/Opera9 */ 402 if (document.addEventListener) { 403 document.addEventListener("DOMContentLoaded", runLoadEvents, false); 404 } 405 /* for WebKit */ 406 if (/WebKit/i.test(navigator.userAgent)) { 407 timer = setInterval(function() { 408 if (/loaded|complete/.test(document.readyState)) { 409 clearInterval(timer); 410 runLoadEvents(); // call the onload handler 411 } 412 }, 10); 413 } 414 /* for other browsers */ 415 window.onload = runLoadEvents; 416 /*@end @*/ 417 418 419 })(); 420