KeyMap.html 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  5. <title>The source code</title>
  6. <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
  7. <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
  8. <style type="text/css">
  9. .highlight { display: block; background-color: #ddd; }
  10. </style>
  11. <script type="text/javascript">
  12. function highlight() {
  13. document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
  14. }
  15. </script>
  16. </head>
  17. <body onload="prettyPrint(); highlight();">
  18. <pre class="prettyprint lang-js"><span id='Ext-util-KeyMap'>/**
  19. </span> * Handles mapping key events to handling functions for an element or a Component. One KeyMap can be used for multiple
  20. * actions.
  21. *
  22. * A KeyMap must be configured with a {@link #target} as an event source which may be an Element or a Component.
  23. *
  24. * If the target is an element, then the `keydown` event will trigger the invocation of {@link #binding}s.
  25. *
  26. * It is possible to configure the KeyMap with a custom {@link #eventName} to listen for. This may be useful when the
  27. * {@link #target} is a Component.
  28. *
  29. * The KeyMap's event handling requires that the first parameter passed is a key event. So if the Component's event
  30. * signature is different, specify a {@link #processEvent} configuration which accepts the event's parameters and
  31. * returns a key event.
  32. *
  33. * Functions specified in {@link #binding}s are called with this signature : `(String key, Ext.EventObject e)` (if the
  34. * match is a multi-key combination the callback will still be called only once). A KeyMap can also handle a string
  35. * representation of keys. By default KeyMap starts enabled.
  36. *
  37. * Usage:
  38. *
  39. * // map one key by key code
  40. * var map = new Ext.util.KeyMap({
  41. * target: &quot;my-element&quot;,
  42. * key: 13, // or Ext.EventObject.ENTER
  43. * fn: myHandler,
  44. * scope: myObject
  45. * });
  46. *
  47. * // map multiple keys to one action by string
  48. * var map = new Ext.util.KeyMap({
  49. * target: &quot;my-element&quot;,
  50. * key: &quot;a\r\n\t&quot;,
  51. * fn: myHandler,
  52. * scope: myObject
  53. * });
  54. *
  55. * // map multiple keys to multiple actions by strings and array of codes
  56. * var map = new Ext.util.KeyMap({
  57. * target: &quot;my-element&quot;,
  58. * binding: [{
  59. * key: [10,13],
  60. * fn: function(){ alert(&quot;Return was pressed&quot;); }
  61. * }, {
  62. * key: &quot;abc&quot;,
  63. * fn: function(){ alert('a, b or c was pressed'); }
  64. * }, {
  65. * key: &quot;\t&quot;,
  66. * ctrl:true,
  67. * shift:true,
  68. * fn: function(){ alert('Control + shift + tab was pressed.'); }
  69. * }]
  70. * });
  71. *
  72. * Since 4.1.0, KeyMaps can bind to Components and process key-based events fired by Components.
  73. *
  74. * To bind to a Component, use the single parameter form of constructor and include the Component event name
  75. * to listen for, and a `processEvent` implementation which returns the key event for further processing by
  76. * the KeyMap:
  77. *
  78. * var map = new Ext.util.KeyMap({
  79. * target: myGridView,
  80. * eventName: 'itemkeydown',
  81. * processEvent: function(view, record, node, index, event) {
  82. *
  83. * // Load the event with the extra information needed by the mappings
  84. * event.view = view;
  85. * event.store = view.getStore();
  86. * event.record = record;
  87. * event.index = index;
  88. * return event;
  89. * },
  90. * binding: {
  91. * key: Ext.EventObject.DELETE,
  92. * fn: function(keyCode, e) {
  93. * e.store.remove(e.record);
  94. *
  95. * // Attempt to select the record that's now in its place
  96. * e.view.getSelectionModel().select(e.index);
  97. * e.view.el.focus();
  98. * }
  99. * }
  100. * });
  101. */
  102. Ext.define('Ext.util.KeyMap', {
  103. alternateClassName: 'Ext.KeyMap',
  104. <span id='Ext-util-KeyMap-cfg-target'> /**
  105. </span> * @cfg {Ext.Component/Ext.Element/HTMLElement/String} target
  106. * The object on which to listen for the event specified by the {@link #eventName} config option.
  107. */
  108. <span id='Ext-util-KeyMap-cfg-binding'> /**
  109. </span> * @cfg {Object/Object[][]} binding
  110. * Either a single object describing a handling function for s specified key (or set of keys), or
  111. * an array of such objects.
  112. * @cfg {String/String[]} binding.key A single keycode or an array of keycodes to handle
  113. * @cfg {Boolean} binding.shift True to handle key only when shift is pressed, False to handle the
  114. * key only when shift is not pressed (defaults to undefined)
  115. * @cfg {Boolean} binding.ctrl True to handle key only when ctrl is pressed, False to handle the
  116. * key only when ctrl is not pressed (defaults to undefined)
  117. * @cfg {Boolean} binding.alt True to handle key only when alt is pressed, False to handle the key
  118. * only when alt is not pressed (defaults to undefined)
  119. * @cfg {Function} binding.handler The function to call when KeyMap finds the expected key combination
  120. * @cfg {Function} binding.fn Alias of handler (for backwards-compatibility)
  121. * @cfg {Object} binding.scope The scope of the callback function
  122. * @cfg {String} binding.defaultEventAction A default action to apply to the event. Possible values
  123. * are: stopEvent, stopPropagation, preventDefault. If no value is set no action is performed.
  124. */
  125. <span id='Ext-util-KeyMap-cfg-processEventScope'> /**
  126. </span> * @cfg {Object} [processEventScope=this]
  127. * The scope (`this` context) in which the {@link #processEvent} method is executed.
  128. */
  129. <span id='Ext-util-KeyMap-cfg-ignoreInputFields'> /**
  130. </span> * @cfg {Boolean} [ignoreInputFields=false]
  131. * Configure this as `true` if there are any input fields within the {@link #target}, and this KeyNav
  132. * should not process events from input fields, (`&amp;lt;input&gt;, &amp;lt;textarea&gt; and elements with `contentEditable=&quot;true&quot;`)
  133. */
  134. <span id='Ext-util-KeyMap-cfg-eventName'> /**
  135. </span> * @cfg {String} eventName
  136. * The event to listen for to pick up key events.
  137. */
  138. eventName: 'keydown',
  139. constructor: function(config) {
  140. var me = this;
  141. // Handle legacy arg list in which the first argument is the target.
  142. // TODO: Deprecate in V5
  143. if ((arguments.length !== 1) || (typeof config === 'string') || config.dom || config.tagName || config === document || config.isComponent) {
  144. me.legacyConstructor.apply(me, arguments);
  145. return;
  146. }
  147. Ext.apply(me, config);
  148. me.bindings = [];
  149. if (!me.target.isComponent) {
  150. me.target = Ext.get(me.target);
  151. }
  152. if (me.binding) {
  153. me.addBinding(me.binding);
  154. } else if (config.key) {
  155. me.addBinding(config);
  156. }
  157. me.enable();
  158. },
  159. <span id='Ext-util-KeyMap-method-legacyConstructor'> /**
  160. </span> * @private
  161. * Old constructor signature
  162. * @param {String/HTMLElement/Ext.Element/Ext.Component} el The element or its ID, or Component to bind to
  163. * @param {Object} binding The binding (see {@link #addBinding})
  164. * @param {String} [eventName=&quot;keydown&quot;] The event to bind to
  165. */
  166. legacyConstructor: function(el, binding, eventName){
  167. var me = this;
  168. Ext.apply(me, {
  169. target: Ext.get(el),
  170. eventName: eventName || me.eventName,
  171. bindings: []
  172. });
  173. if (binding) {
  174. me.addBinding(binding);
  175. }
  176. me.enable();
  177. },
  178. <span id='Ext-util-KeyMap-method-addBinding'> /**
  179. </span> * Add a new binding to this KeyMap.
  180. *
  181. * Usage:
  182. *
  183. * // Create a KeyMap
  184. * var map = new Ext.util.KeyMap(document, {
  185. * key: Ext.EventObject.ENTER,
  186. * fn: handleKey,
  187. * scope: this
  188. * });
  189. *
  190. * //Add a new binding to the existing KeyMap later
  191. * map.addBinding({
  192. * key: 'abc',
  193. * shift: true,
  194. * fn: handleKey,
  195. * scope: this
  196. * });
  197. *
  198. * @param {Object/Object[]} binding A single KeyMap config or an array of configs.
  199. * The following config object properties are supported:
  200. * @param {String/Array} binding.key A single keycode or an array of keycodes to handle.
  201. * @param {Boolean} binding.shift True to handle key only when shift is pressed,
  202. * False to handle the keyonly when shift is not pressed (defaults to undefined).
  203. * @param {Boolean} binding.ctrl True to handle key only when ctrl is pressed,
  204. * False to handle the key only when ctrl is not pressed (defaults to undefined).
  205. * @param {Boolean} binding.alt True to handle key only when alt is pressed,
  206. * False to handle the key only when alt is not pressed (defaults to undefined).
  207. * @param {Function} binding.handler The function to call when KeyMap finds the
  208. * expected key combination.
  209. * @param {Function} binding.fn Alias of handler (for backwards-compatibility).
  210. * @param {Object} binding.scope The scope of the callback function.
  211. * @param {String} binding.defaultEventAction A default action to apply to the event.
  212. * Possible values are: stopEvent, stopPropagation, preventDefault. If no value is
  213. * set no action is performed..
  214. */
  215. addBinding : function(binding){
  216. var keyCode = binding.key,
  217. processed = false,
  218. key,
  219. keys,
  220. keyString,
  221. i,
  222. len;
  223. if (Ext.isArray(binding)) {
  224. for (i = 0, len = binding.length; i &lt; len; i++) {
  225. this.addBinding(binding[i]);
  226. }
  227. return;
  228. }
  229. if (Ext.isString(keyCode)) {
  230. keys = [];
  231. keyString = keyCode.toUpperCase();
  232. for (i = 0, len = keyString.length; i &lt; len; ++i){
  233. keys.push(keyString.charCodeAt(i));
  234. }
  235. keyCode = keys;
  236. processed = true;
  237. }
  238. if (!Ext.isArray(keyCode)) {
  239. keyCode = [keyCode];
  240. }
  241. if (!processed) {
  242. for (i = 0, len = keyCode.length; i &lt; len; ++i) {
  243. key = keyCode[i];
  244. if (Ext.isString(key)) {
  245. keyCode[i] = key.toUpperCase().charCodeAt(0);
  246. }
  247. }
  248. }
  249. this.bindings.push(Ext.apply({
  250. keyCode: keyCode
  251. }, binding));
  252. },
  253. <span id='Ext-util-KeyMap-method-handleTargetEvent'> /**
  254. </span> * Process the {@link #eventName event} from the {@link #target}.
  255. * @private
  256. * @param {Ext.EventObject} event
  257. */
  258. handleTargetEvent: (function() {
  259. var tagRe = /input|textarea/i;
  260. return function(event) {
  261. var me = this,
  262. bindings, i, len,
  263. target, contentEditable;
  264. if (this.enabled) { //just in case
  265. bindings = this.bindings;
  266. i = 0;
  267. len = bindings.length;
  268. // Process the event
  269. event = me.processEvent.apply(me||me.processEventScope, arguments);
  270. // Ignore events from input fields if configured to do so
  271. if (me.ignoreInputFields) {
  272. target = event.target;
  273. contentEditable = target.contentEditable;
  274. // contentEditable will default to inherit if not specified, only check if the
  275. // attribute has been set or explicitly set to true
  276. // http://html5doctor.com/the-contenteditable-attribute/
  277. if (tagRe.test(target.tagName) || (contentEditable === '' || contentEditable === 'true')) {
  278. return;
  279. }
  280. }
  281. // If the processor does not return a keyEvent, we can't process it.
  282. // Allow them to return false to cancel processing of the event
  283. if (!event.getKey) {
  284. return event;
  285. }
  286. for(; i &lt; len; ++i){
  287. this.processBinding(bindings[i], event);
  288. }
  289. }
  290. }
  291. }()),
  292. <span id='Ext-util-KeyMap-cfg-processEvent'> /**
  293. </span> * @cfg {Function} processEvent
  294. * An optional event processor function which accepts the argument list provided by the
  295. * {@link #eventName configured event} of the {@link #target}, and returns a keyEvent for processing by the KeyMap.
  296. *
  297. * This may be useful when the {@link #target} is a Component with s complex event signature, where the event is not
  298. * the first parameter. Extra information from the event arguments may be injected into the event for use by the handler
  299. * functions before returning it.
  300. */
  301. processEvent: function(event){
  302. return event;
  303. },
  304. <span id='Ext-util-KeyMap-method-processBinding'> /**
  305. </span> * Process a particular binding and fire the handler if necessary.
  306. * @private
  307. * @param {Object} binding The binding information
  308. * @param {Ext.EventObject} event
  309. */
  310. processBinding: function(binding, event){
  311. if (this.checkModifiers(binding, event)) {
  312. var key = event.getKey(),
  313. handler = binding.fn || binding.handler,
  314. scope = binding.scope || this,
  315. keyCode = binding.keyCode,
  316. defaultEventAction = binding.defaultEventAction,
  317. i,
  318. len,
  319. keydownEvent = new Ext.EventObjectImpl(event);
  320. for (i = 0, len = keyCode.length; i &lt; len; ++i) {
  321. if (key === keyCode[i]) {
  322. if (handler.call(scope, key, event) !== true &amp;&amp; defaultEventAction) {
  323. keydownEvent[defaultEventAction]();
  324. }
  325. break;
  326. }
  327. }
  328. }
  329. },
  330. <span id='Ext-util-KeyMap-method-checkModifiers'> /**
  331. </span> * Check if the modifiers on the event match those on the binding
  332. * @private
  333. * @param {Object} binding
  334. * @param {Ext.EventObject} event
  335. * @return {Boolean} True if the event matches the binding
  336. */
  337. checkModifiers: function(binding, e) {
  338. var keys = ['shift', 'ctrl', 'alt'],
  339. i = 0,
  340. len = keys.length,
  341. val, key;
  342. for (; i &lt; len; ++i){
  343. key = keys[i];
  344. val = binding[key];
  345. if (!(val === undefined || (val === e[key + 'Key']))) {
  346. return false;
  347. }
  348. }
  349. return true;
  350. },
  351. <span id='Ext-util-KeyMap-method-on'> /**
  352. </span> * Shorthand for adding a single key listener.
  353. *
  354. * @param {Number/Number[]/Object} key Either the numeric key code, array of key codes or an object with the
  355. * following options: `{key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}`
  356. * @param {Function} fn The function to call
  357. * @param {Object} [scope] The scope (`this` reference) in which the function is executed.
  358. * Defaults to the browser window.
  359. */
  360. on: function(key, fn, scope) {
  361. var keyCode, shift, ctrl, alt;
  362. if (Ext.isObject(key) &amp;&amp; !Ext.isArray(key)) {
  363. keyCode = key.key;
  364. shift = key.shift;
  365. ctrl = key.ctrl;
  366. alt = key.alt;
  367. } else {
  368. keyCode = key;
  369. }
  370. this.addBinding({
  371. key: keyCode,
  372. shift: shift,
  373. ctrl: ctrl,
  374. alt: alt,
  375. fn: fn,
  376. scope: scope
  377. });
  378. },
  379. <span id='Ext-util-KeyMap-method-isEnabled'> /**
  380. </span> * Returns true if this KeyMap is enabled
  381. * @return {Boolean}
  382. */
  383. isEnabled : function() {
  384. return this.enabled;
  385. },
  386. <span id='Ext-util-KeyMap-method-enable'> /**
  387. </span> * Enables this KeyMap
  388. */
  389. enable: function() {
  390. var me = this;
  391. if (!me.enabled) {
  392. me.target.on(me.eventName, me.handleTargetEvent, me);
  393. me.enabled = true;
  394. }
  395. },
  396. <span id='Ext-util-KeyMap-method-disable'> /**
  397. </span> * Disable this KeyMap
  398. */
  399. disable: function() {
  400. var me = this;
  401. if (me.enabled) {
  402. me.target.removeListener(me.eventName, me.handleTargetEvent, me);
  403. me.enabled = false;
  404. }
  405. },
  406. <span id='Ext-util-KeyMap-method-setDisabled'> /**
  407. </span> * Convenience function for setting disabled/enabled by boolean.
  408. * @param {Boolean} disabled
  409. */
  410. setDisabled : function(disabled) {
  411. if (disabled) {
  412. this.disable();
  413. } else {
  414. this.enable();
  415. }
  416. },
  417. <span id='Ext-util-KeyMap-method-destroy'> /**
  418. </span> * Destroys the KeyMap instance and removes all handlers.
  419. * @param {Boolean} removeTarget True to also remove the {@link #target}
  420. */
  421. destroy: function(removeTarget) {
  422. var me = this,
  423. target = me.target;
  424. me.bindings = [];
  425. me.disable();
  426. if (removeTarget === true) {
  427. if (target.isComponent) {
  428. target.destroy();
  429. } else {
  430. target.remove();
  431. }
  432. }
  433. delete me.target;
  434. }
  435. });</pre>
  436. </body>
  437. </html>