| /** |
| * Copyright 2008 Steve McKay. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** |
| * Kibbles.Keys is a Javascript library providing simple cross browser |
| * keyboard event support. |
| */ |
| (function(){ |
| |
| var _listening = false; |
| |
| // code to handler list map. |
| // Wildcard listeners use magic code wildcards "before" and "after". |
| var _listeners = { |
| before: [], |
| after: [] |
| }; |
| |
| /* |
| * Map of key names to char code. This map is consulted before |
| * charCodeAt(0) is used to determine the character code. |
| * |
| * This map also serves as a definitive list of supported "special" keys. |
| * See _codeForEvent for details. |
| */ |
| var _CODE_MAP = { |
| ESC: 27, |
| ENTER: 13 |
| }; |
| |
| /** |
| * Register a keypress listener. |
| */ |
| function _listen() { |
| if (_listening) return; |
| |
| var d = document; |
| if (d.addEventListener) { |
| d.addEventListener('keypress', _handleKeyboardEvent, false); |
| d.addEventListener('keydown', _handleKeyDownEvent, false); |
| } else if (d.attachEvent) { |
| d.documentElement.attachEvent('onkeypress', _handleKeyboardEvent); |
| d.documentElement.attachEvent('onkeydown', _handleKeyDownEvent); |
| } |
| _listening = true; |
| } |
| |
| /** |
| * Register a keypress listener for the supplied skip code. |
| */ |
| function _addKeyPressListener(spec, handler) { |
| var code = spec.toLowerCase(); |
| if (code == "before" || code == "after") { |
| _listeners[code].push(handler); |
| return; |
| } |
| |
| // try to find the character or key code. |
| code = _CODE_MAP[spec.toUpperCase()]; |
| if (!code) { |
| code = spec.charCodeAt(0); |
| } |
| if (!_listeners[code]) { |
| _listeners[code] = []; |
| } |
| _listeners[code].push(handler); |
| } |
| |
| /** |
| * Our handler for keypress events. |
| */ |
| function _handleKeyboardEvent(e) { |
| |
| // If event is null, this is probably IE. |
| if (!e) e = window.event; |
| |
| var source = _getSourceElement(e); |
| if (_isInputElement(source)) { |
| return; |
| } |
| |
| if (_hasFlakeyModifier(e)) return; |
| |
| var code = _codeForEvent(e); |
| |
| if (code == undefined) return; |
| |
| var payload = { |
| code: code |
| }; |
| |
| for (var i = 0; i < _listeners.before.length; i++) { |
| _listeners.before[i](payload); |
| } |
| |
| var listeners = _listeners[code]; |
| if (listeners) { |
| for (var i = 0; i < listeners.length; i++) { |
| listeners[i]({ |
| code: code |
| }); |
| } |
| } |
| |
| for (var i = 0; i < _listeners.after.length; i++) { |
| _listeners.after[i](payload); |
| } |
| } |
| |
| function _handleKeyDownEvent(e) { |
| if (!e) e = window.event; |
| var code = _codeForEvent(e); |
| if (code == _CODE_MAP['ESC'] || code == _CODE_MAP['ENTER']) { |
| _handleKeyboardEvent(e); |
| } |
| } |
| |
| /** |
| * Returns the keycode associated with the event. |
| */ |
| function _codeForEvent(e) { |
| return e.keyCode ? e.keyCode : e.which; |
| } |
| |
| /** |
| * Returns true if the supplied event has an associated modifier key |
| * that we have had trouble with in certain browsers. |
| */ |
| function _hasFlakeyModifier(e) { |
| return e.altKey || e.ctrlKey || e.metaKey; |
| } |
| |
| /** |
| * Returns the source element for the supplied event. |
| */ |
| function _getSourceElement(e) { |
| var element = e.target; |
| if (!element) { |
| element = e.srcElement; |
| } |
| |
| if (element.shadowRoot) { |
| // Find the element within the shadowDOM. |
| const path = e.path || e.composedPath(); |
| element = path[0]; |
| } |
| |
| // If the source element is a text node, the parent is the object |
| // we're interested in. |
| if (element.nodeType == 3) { |
| element = element.parentNode; |
| } |
| |
| return element; |
| } |
| |
| /** |
| * Returns true if the element is a known form input element. |
| */ |
| function _isInputElement(element) { |
| return element.tagName == 'INPUT' || element.tagName == 'TEXTAREA'; |
| } |
| |
| /* |
| * A nice little namespace to call our own. |
| * |
| * Formalizing Kibbles.Keys as a traditional javascript class caused headaches |
| * with respect to capturing the context (what is "this" at any point in time). |
| * So we use a simple script exported via the "kibbles.keys" namespace. |
| */ |
| if (!window.kibbles) |
| window.kibbles = {} |
| |
| window.kibbles.keys = { |
| listen: _listen, |
| addKeyPressListener: _addKeyPressListener |
| }; |
| |
| })(); |