1 /*
  2  * © 2009 ROBO Design
  3  * http://www.robodesign.ro
  4  *
  5  * $Date: 2009-04-22 15:40:44 +0300 $
  6  */
  7 
  8 /**
  9  * @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a>
 10  * @fileOverview Minimal JavaScript library which provides functionality for 
 11  * cross-browser compatibility support.
 12  */
 13 
 14 /**
 15  * @namespace Holds methods and properties necessary throughout the entire 
 16  * application.
 17  */
 18 var lib = {};
 19 
 20 /**
 21  * @namespace Holds browser information.
 22  */
 23 lib.browser = {};
 24 
 25 (function () {
 26 var ua = '';
 27 
 28 if (window.navigator && window.navigator.userAgent) {
 29   ua = window.navigator.userAgent.toLowerCase();
 30 }
 31 
 32 /**
 33  * @type Boolean
 34  */
 35 lib.browser.opera = window.opera ? true : /\bopera\b/.test(ua);
 36 
 37 /**
 38  * Webkit is the render engine used primarily by Safari. It's also used by 
 39  * Google Chrome and GNOME Epiphany.
 40  *
 41  * @type Boolean
 42  */
 43 lib.browser.webkit = /\b(applewebkit|webkit)\b/.test(ua);
 44 
 45 /**
 46  * Firefox uses the Gecko render engine.
 47  *
 48  * @type Boolean
 49  */
 50 // In some variations of the User Agent strings provided by Opera, Firefox is 
 51 // mentioned.
 52 lib.browser.firefox = /\bfirefox\b/.test(ua) && !lib.browser.opera;
 53 
 54 /**
 55  * Gecko is the render engine used by Firefox and related products.
 56  *
 57  * @type Boolean
 58  */
 59 // Typically, the user agent string of WebKit also mentions Gecko. Additionally, 
 60 // Opera mentions Gecko for tricking some sites.
 61 lib.browser.gecko = /\bgecko\b/.test(ua) && !lib.browser.opera && !lib.browser.webkit;
 62 
 63 /**
 64  * Microsoft Internet Explorer. The future of computing.
 65  *
 66  * @type Boolean
 67  */
 68 // Again, Opera allows users to easily fake the UA.
 69 lib.browser.msie = /\bmsie\b/.test(ua) && !lib.browser.opera;
 70 
 71 /**
 72  * Presto is the render engine used by Opera.
 73  *
 74  * @type Boolean
 75  */
 76 // Older versions of Opera did not mention Presto in the UA string.
 77 lib.browser.presto = /\bpresto\b/.test(ua) || lib.browser.opera;
 78 
 79 
 80 /**
 81  * Browser operating system
 82  *
 83  * @type String
 84  */
 85 lib.browser.os = (ua.match(/\b(windows|linux)\b/) || [])[1];
 86 
 87 delete ua;
 88 })();
 89 
 90 
 91 /**
 92  * @namespace Holds methods and properties necessary for DOM manipulation.
 93  */
 94 lib.dom = {};
 95 
 96 /**
 97  * @namespace Holds the list of virtual key identifiers and a few characters, 
 98  * each being associated to a key code commonly used by Web browsers.
 99  *
100  * @private
101  */
102 lib.dom.keyNames = {
103   Help:          6,
104   Backspace:     8,
105   Tab:           9,
106   Clear:         12,
107   Enter:         13,
108   Shift:         16,
109   Control:       17,
110   Alt:           18,
111   Pause:         19,
112   CapsLock:      20,
113   Cancel:        24,
114   'Escape':      27,
115   Space:         32,
116   PageUp:        33,
117   PageDown:      34,
118   End:           35,
119   Home:          36,
120   Left:          37,
121   Up:            38,
122   Right:         39,
123   Down:          40,
124   PrintScreen:   44,
125   Insert:        45,
126   'Delete':      46,
127   Win:           91,
128   ContextMenu:   93,
129   '*':           106,
130   '+':           107,
131   F1:            112,
132   F2:            113,
133   F3:            114,
134   F4:            115,
135   F5:            116,
136   F6:            117,
137   F7:            118,
138   F8:            119,
139   F9:            120,
140   F10:           121,
141   F11:           122,
142   F12:           123,
143   NumLock:       144,
144   ';':           186,
145   '=':           187,
146   ',':           188,
147   '-':           189,
148   '.':           190,
149   '/':           191,
150   '`':           192,
151   '[':           219,
152   '\\':          220,
153   ']':           221,
154   "'":           222
155 };
156 
157 /**
158  * @namespace Holds the list of codes, each being associated to a virtual key 
159  * identifier.
160  *
161  * @private
162  */
163 lib.dom.keyCodes = {
164   /*
165    * For almost each key code, these comments give the key name, the 
166    * keyIdentifier from the DOM 3 Events spec and the Unicode character 
167    * information (if you would use the decimal code for direct conversion to 
168    * a character, e.g. String.fromCharCode()). Obviously, the Unicode character 
169    * information is not to be used, since these are only virtual key codes (not 
170    * really char codes) associated to key names.
171    *
172    * Each key name in here tries to follow the same style as the defined 
173    * keyIdentifiers from the DOM 3 Events. Thus for the Page Down button, 
174    * 'PageDown' is used (not other variations like 'pag-up'), and so on.
175    *
176    * Multiple key codes might be associated to the same key - it's not an error.
177    *
178    * Note that this list is not an exhaustive key codes list. This means that 
179    * for key A or for key 0, the script will do String.fromCharCode(keyCode), to 
180    * determine the key. For the case of alpha-numeric keys, this works fine.
181    */
182 
183   /*
184    * Key: Enter
185    * Unicode: U+0003 [End of text]
186    *
187    * Note 1: This keyCode is only used in Safari 2 (older Webkit) for the Enter 
188    * key.
189    *
190    * Note 2: In Gecko this keyCode is used for the Cancel key (see 
191    * DOM_VK_CANCEL).
192    */
193   3: 'Enter',
194 
195   /*
196    * Key: Help
197    * Unicode: U+0006 [Acknowledge]
198    *
199    * Note: Taken from Gecko (DOM_VK_HELP).
200    */
201   6: 'Help',
202 
203   /*
204    * Key: Backspace
205    * Unicode: U+0008 [Backspace]
206    * keyIdentifier: U+0008
207    */
208   8: 'Backspace',
209 
210   /*
211    * Key: Tab
212    * Unicode: U+0009 [Horizontal tab]
213    * keyIdentifier: U+0009
214    */
215   9: 'Tab',
216 
217   /*
218    * Key: Enter
219    * Unicode: U+0010 [Line feed (LF) / New line (NL) / End of line (EOL)]
220    *
221    * Note: Taken from the Unicode characters list. If it ends up as a keyCode in 
222    * some event, it's simply considered as being the Enter key.
223    */
224   10: 'Enter',
225 
226   /*
227    * Key: NumPad_Center
228    * Unicode: U+000C [Form feed]
229    * keyIdentifier: Clear
230    *
231    * Note 1: This keyCode is used when NumLock is off, and the user pressed the 
232    * 5 key on the numeric pad.
233    *
234    * Note 2: Safari 2 (older Webkit) assigns this keyCode to the NumLock key 
235    * itself.
236    */
237   12: 'Clear',
238 
239   /*
240    * Key: Enter
241    * Unicode: U+000D [Carriage return (CR)]
242    * keyIdentifier: Enter
243    *
244    * Note 1: This is the keyCode used by most of the Web browsers when the Enter 
245    * key is pressed.
246    *
247    * Note 2: Gecko associates the DOM_VK_RETURN to this keyCode.
248    */
249   13: 'Enter',
250 
251   /*
252    * Key: Enter
253    * Unicode: U+000E [Shift out]
254    *
255    * Note: Taken from Gecko (DOM_VK_ENTER).
256    */
257   14: 'Enter',
258 
259   /*
260    * Key: Shift
261    * Unicode: U+0010 [Data link escape]
262    * keyIdentifier: Shift
263    *
264    * Note: In older Safari (Webkit) versions Shift+Tab is assigned a different 
265    * keyCode: keyCode 25.
266    */
267   16: 'Shift',
268 
269   /*
270    * Key: Control
271    * Unicode: U+0011 [Device control one]
272    * keyIdentifier: Control
273    */
274   17: 'Control',
275 
276   /*
277    * Key: Alt
278    * Unicode: U+0012 [Device control two]
279    * keyIdentifier: Alt
280    */
281   18: 'Alt',
282 
283   /*
284    * Key: Pause
285    * Unicode: U+0013 [Device control three]
286    * keyIdentifier: Pause
287    */
288   19: 'Pause',
289 
290   /*
291    * Key: CapsLock
292    * Unicode: U+0014 [Device control four]
293    * keyIdentifier: CapsLock
294    */
295   20: 'CapsLock',
296 
297   /*
298    * Key: Cancel
299    * Unicode: U+0018 [Cancel]
300    * keyIdentifier: U+0018
301    */
302   24: 'Cancel',
303 
304   /*
305    * Key: Escape
306    * Unicode: U+001B [Escape]
307    * keyIdentifier: U+001B
308    */
309   27: 'Escape',
310 
311   /*
312    * Key: Space
313    * Unicode: U+0020 [Space]
314    * keyIdentifier: U+0020
315    */
316   32: 'Space',
317 
318   /*
319    * Key: PageUp or NumPad_North_East
320    * Unicode: U+0021 ! [Exclamation mark]
321    * keyIdentifier: PageUp
322    */
323   33: 'PageUp',
324 
325   /*
326    * Key: PageDown or NumPad_South_East
327    * Unicode: U+0022 " [Quotation mark]
328    * keyIdentifier: PageDown
329    */
330   34: 'PageDown',
331 
332   /*
333    * Key: End or NumPad_South_West
334    * Unicode: U+0023 # [Number sign]
335    * keyIdentifier: PageDown
336    */
337   35: 'End',
338 
339   /*
340    * Key: Home or NumPad_North_West
341    * Unicode: U+0024 $ [Dollar sign]
342    * keyIdentifier: Home
343    */
344   36: 'Home',
345 
346   /*
347    * Key: Left or NumPad_West
348    * Unicode: U+0025 % [Percent sign]
349    * keyIdentifier: Left
350    */
351   37: 'Left',
352 
353   /*
354    * Key: Up or NumPad_North
355    * Unicode: U+0026 & [Ampersand]
356    * keyIdentifier: Up
357    */
358   38: 'Up',
359 
360   /*
361    * Key: Right or NumPad_East
362    * Unicode: U+0027 ' [Apostrophe]
363    * keyIdentifier: Right
364    */
365   39: 'Right',
366 
367   /*
368    * Key: Down or NumPad_South
369    * Unicode: U+0028 ( [Left parenthesis]
370    * keyIdentifier: Down
371    */
372   40: 'Down',
373 
374   /*
375    * Key: PrintScreen
376    * Unicode: U+002C , [Comma]
377    * keyIdentifier: PrintScreen
378    */
379   //44: 'PrintScreen',
380 
381   /*
382    * Key: Insert or NumPad_Insert
383    * Unicode: U+002D - [Hyphen-Minus]
384    * keyIdentifier: Insert
385    */
386   45: 'Insert',
387 
388   /*
389    * Key: Delete or NumPad_Delete
390    * Unicode: U+002E . [Full stop / period]
391    * keyIdentifier: U+007F
392    */
393   46: 'Delete',
394 
395   /*
396    * Key: WinLeft
397    * Unicode: U+005B [ [Left square bracket]
398    * keyIdentifier: Win
399    *
400    * Disabled: rarely needed.
401    */
402   //91: 'Win',
403 
404   /*
405    * Key: WinRight
406    * Unicode: U+005C \ [Reverse solidus / Backslash]
407    * keyIdentifier: Win
408    */
409   //92: 'Win',
410 
411   /*
412    * Key: Menu/ContextMenu
413    * Unicode: U+005D ] [Right square bracket]
414    * keyIdentifier: ...
415    *
416    * Disabled: Is it Meta? Is it Menu, ContextMenu, what? Too much mess.
417    */
418   //93: 'ContextMenu',
419 
420   /*
421    * Key: NumPad_0
422    * Unicode: U+0060 ` [Grave accent]
423    * keyIdentifier: 0
424    */
425   96: '0',
426 
427   /*
428    * Key: NumPad_1
429    * Unicode: U+0061 a [Latin small letter a]
430    * keyIdentifier: 1
431    */
432   97: '1',
433 
434   /*
435    * Key: NumPad_2
436    * Unicode: U+0062 b [Latin small letter b]
437    * keyIdentifier: 2
438    */
439   98: '2',
440 
441   /*
442    * Key: NumPad_3
443    * Unicode: U+0063 c [Latin small letter c]
444    * keyIdentifier: 3
445    */
446   99: '3',
447 
448   /*
449    * Key: NumPad_4
450    * Unicode: U+0064 d [Latin small letter d]
451    * keyIdentifier: 4
452    */
453   100: '4',
454 
455   /*
456    * Key: NumPad_5
457    * Unicode: U+0065 e [Latin small letter e]
458    * keyIdentifier: 5
459    */
460   101: '5',
461 
462   /*
463    * Key: NumPad_6
464    * Unicode: U+0066 f [Latin small letter f]
465    * keyIdentifier: 6
466    */
467   102: '6',
468 
469   /*
470    * Key: NumPad_7
471    * Unicode: U+0067 g [Latin small letter g]
472    * keyIdentifier: 7
473    */
474   103: '7',
475 
476   /*
477    * Key: NumPad_8
478    * Unicode: U+0068 h [Latin small letter h]
479    * keyIdentifier: 8
480    */
481   104: '8',
482 
483   /*
484    * Key: NumPad_9
485    * Unicode: U+0069 i [Latin small letter i]
486    * keyIdentifier: 9
487    */
488   105: '9',
489 
490   /*
491    * Key: NumPad_Multiply
492    * Unicode: U+0070 j [Latin small letter j]
493    * keyIdentifier: U+002A * [Asterisk / Star]
494    */
495   106: '*',
496 
497   /*
498    * Key: NumPad_Plus
499    * Unicode: U+0071 k [Latin small letter k]
500    * keyIdentifier: U+002B + [Plus]
501    */
502   107: '+',
503 
504   /*
505    * Key: NumPad_Minus
506    * Unicode: U+0073 m [Latin small letter m]
507    * keyIdentifier: U+002D + [Hyphen / Minus]
508    */
509   109: '-',
510 
511   /*
512    * Key: NumPad_Period
513    * Unicode: U+0074 n [Latin small letter n]
514    * keyIdentifier: U+002E . [Period]
515    */
516   110: '.',
517 
518   /*
519    * Key: NumPad_Division
520    * Unicode: U+0075 o [Latin small letter o]
521    * keyIdentifier: U+002F / [Solidus / Slash]
522    */
523   111: '/',
524 
525   112: 'F1',                // p
526   113: 'F2',                // q
527   114: 'F3',                // r
528   115: 'F4',                // s
529   116: 'F5',                // t
530   117: 'F6',                // u
531   118: 'F7',                // v
532   119: 'F8',                // w
533   120: 'F9',                // x
534   121: 'F10',               // y
535   122: 'F11',               // z
536   123: 'F12',               // {
537 
538   /*
539    * Key: Delete
540    * Unicode: U+007F [Delete]
541    * keyIdentifier: U+007F
542    */
543   127: 'Delete',
544 
545   /*
546    * Key: NumLock
547    * Unicode: U+0090 [Device control string]
548    * keyIdentifier: NumLock
549    */
550   144: 'NumLock',
551 
552   186: ';',                 // º (Masculine ordinal indicator)
553   187: '=',                 // »
554   188: ',',                 // ¼
555   189: '-',                 // ½
556   190: '.',                 // ¾
557   191: '/',                 // ¿
558   192: '`',                 // À
559   219: '[',                 // Û
560   220: '\\',                // Ü
561   221: ']',                 // Ý
562   222: "'"                  // Þ (Latin capital letter thorn)
563 
564   //224: 'Win',               // à
565   //229: 'WinIME',            // å or WinIME or something else in Webkit
566   //255: 'NumLock',           // ÿ, Gecko and Chrome, Windows XP in VirtualBox
567   //376: 'NumLock'            // Ÿ, Opera, Windows XP in VirtualBox
568 };
569 
570 if (lib.browser.gecko) {
571   lib.dom.keyCodes[3] = 'Cancel'; // DOM_VK_CANCEL
572 }
573 
574 /**
575  * @namespace Holds a list of common wrong key codes in Web browsers.
576  *
577  * @private
578  */
579 lib.dom.keyCodes_fixes = {
580   42:   lib.dom.keyNames['*'],          // char * to key *
581   47:   lib.dom.keyNames['/'],          // char / to key /
582   59:   lib.dom.keyNames[';'],          // char ; to key ;
583   61:   lib.dom.keyNames['='],          // char = to key =
584   96:   48,                             // NumPad_0 to char 0
585   97:   49,                             // NumPad_1 to char 1
586   98:   50,                             // NumPad_2 to char 2
587   99:   51,                             // NumPad_3 to char 3
588   100:  52,                             // NumPad_4 to char 4
589   101:  53,                             // NumPad_5 to char 5
590   102:  54,                             // NumPad_6 to char 6
591   103:  55,                             // NumPad_7 to char 7
592   104:  56,                             // NumPad_8 to char 8
593   105:  57,                             // NumPad_9 to char 9
594   //106:  56,                           // NumPad_Multiply to char 8
595   //107:  187,                          // NumPad_Plus to key =
596   109:  lib.dom.keyNames['-'],          // NumPad_Minus to key -
597   110:  lib.dom.keyNames['.'],          // NumPad_Period to key .
598   111:  lib.dom.keyNames['/']           // NumPad_Division to key /
599 };
600 
601 /**
602  * @namespace Holds the list of broken key codes generated by older Webkit 
603  * (Safari 2).
604  *
605  * @private
606  */
607 lib.dom.keyCodes_Safari2 = {
608   63232: lib.dom.keyNames.Up,               // 38
609   63233: lib.dom.keyNames.Down,             // 40
610   63234: lib.dom.keyNames.Left,             // 37
611   63235: lib.dom.keyNames.Right,            // 39
612   63236: lib.dom.keyNames.F1,               // 112
613   63237: lib.dom.keyNames.F2,               // 113
614   63238: lib.dom.keyNames.F3,               // 114
615   63239: lib.dom.keyNames.F4,               // 115
616   63240: lib.dom.keyNames.F5,               // 116
617   63241: lib.dom.keyNames.F6,               // 117
618   63242: lib.dom.keyNames.F7,               // 118
619   63243: lib.dom.keyNames.F8,               // 119
620   63244: lib.dom.keyNames.F9,               // 120
621   63245: lib.dom.keyNames.F10,              // 121
622   63246: lib.dom.keyNames.F11,              // 122
623   63247: lib.dom.keyNames.F12,              // 123
624   63248: lib.dom.keyNames.PrintScreen,      // 44
625   63272: lib.dom.keyNames['Delete'],        // 46
626   63273: lib.dom.keyNames.Home,             // 36
627   63275: lib.dom.keyNames.End,              // 35
628   63276: lib.dom.keyNames.PageUp,           // 33
629   63277: lib.dom.keyNames.PageDown,         // 34
630   63289: lib.dom.keyNames.NumLock,          // 144
631   63302: lib.dom.keyNames.Insert            // 45
632 };
633 
634 
635 /**
636  * A complete keyboard events cross-browser compatibility layer.
637  *
638  * <p>Unfortunately, due to the important differences across Web browsers, 
639  * simply using the available properties in a single keyboard event is not 
640  * enough to accurately determine the key the user pressed. Thus, one needs to 
641  * have event handlers for all keyboard-related events <code>keydown</code>, 
642  * <code>keypress</code> and <code>keyup</code>.
643  *
644  * <p>This class provides a complete keyboard event compatibility layer. For any 
645  * new instance you provide the DOM element you want to listen events for, and 
646  * the event handlers for any of the three events <code>keydown</code> 
647  * / <code>keypress</code> / <code>keyup</code>.
648  *
649  * <p>Your event handlers will receive the original DOM Event object, with 
650  * several new properties defined:
651  *
652  * <ul>
653  *   <li><var>event.keyCode_</var> holds the correct code for event key.
654  *
655  *   <li><var>event.key_</var> holds the key the user pressed. It can be either 
656  *   a key name like "PageDown", "Delete", "Enter", or it is a character like 
657  *   "A", "1", or "[".
658  *
659  *   <li><var>event.charCode_</var> holds the Unicode character decimal code.
660  *
661  *   <li><var>event.char_</var> holds the character generated by the event.
662  *
663  *   <li><var>event.repeat_</var> is a boolean property telling if the 
664  *   <code>keypress</code> event is repeated - the user is holding down the key 
665  *   for a long-enough period of time to generate multiple events.
666  * </ul>
667  *
668  * <p>The character-related properties, <var>charCode_</var> and 
669  * <var>char_</var> are only available in the <code>keypress</code> and 
670  * <code>keyup</code> event objects.
671  *
672  * <p>This class will ensure that the <code>keypress</code> event is always 
673  * fired in Webkit and MSIE for all keys, except modifiers. For modifier keys 
674  * like <kbd>Shift</kbd>, <kbd>Control</kbd>, and <kbd>Alt</kbd>, the 
675  * <code>keypress</code> event will not be fired, even if the Web browser does 
676  * it.
677  *
678  * <p>Some user agents like Webkit repeat the <code>keydown</code> event while 
679  * the user holds down a key. This class will ensure that only the 
680  * <code>keypress</code> event is repeated.
681  *
682  * <p>If you want to prevent the default action for an event, you should prevent 
683  * it on <code>keypress</code>. This class will prevent the default action for 
684  * <code>keydown</code> if need (in MSIE).
685  *
686  * @example
687  * <code>var <var>klogger</var> = function (<var>ev</var>) {
688  *   console.log(<var>ev</var>.type +
689  *     ' keyCode_ ' + <var>ev</var>.keyCode_ +
690  *     ' key_ ' + <var>ev</var>.key_ +
691  *     ' charCode_ ' + <var>ev</var>.charCode_ +
692  *     ' char_ ' + <var>ev</var>.char_ +
693  *     ' repeat_ ' + <var>ev</var>.repeat_);
694  * };
695  *
696  * var <var>kbListener</var> = new lib.dom.KeyboardEventListener(window,
697  *               {keydown: <var>klogger</var>,
698  *                keypress: <var>klogger</var>,
699  *                keyup: <var>klogger</var>});</code>
700  *
701  * // later when you're done...
702  * <code><var>kbListener</var>.detach();</code>
703  *
704  * @class A complete keyboard events cross-browser compatibility layer.
705  *
706  * @param {Element} elem_ The DOM Element you want to listen events for.
707  *
708  * @param {Object} handlers_ The object holding the list of event handlers 
709  * associated to the name of each keyboard event you want to listen. To listen 
710  * for all the three keyboard events use <code>{keydown: <var>fn1</var>, 
711  * keypress: <var>fn2</var>, keyup: <var>fn3</var>}</code>.
712  *
713  * @throws {TypeError} If the <var>handlers_</var> object does not contain any 
714  * event handler.
715  */
716 lib.dom.KeyboardEventListener = function (elem_, handlers_) {
717   /*
718     Technical details:
719 
720     For the keyup and keydown events the keyCode provided is that of the virtual 
721     key irrespective of other modifiers (e.g. Shift). Generally, during the 
722     keypress event, the keyCode holds the Unicode value of the character 
723     resulted from the key press, say an alphabetic upper/lower-case char, 
724     depending on the actual intent of the user and depending on the currently 
725     active keyboard layout.
726 
727     Examples:
728     * Pressing p you get keyCode 80 in keyup/keydown, and keyCode 112 in 
729     keypress.  String.fromCharCode(80) = 'P' and String.fromCharCode(112) = 'p'.
730     * Pressing P you get keyCode 80 in all events.
731     * Pressing F1 you get keyCode 112 in keyup, keydown and keypress.
732     * Pressing 9 you get keyCode 57 in all events.
733     * Pressing Shift+9 you get keyCode 57 in keyup/keydown, and keyCode 40 in 
734     keypress. String.fromCharCode(57) = '9' and String.fromCharCode(40) = '('.
735 
736     * Using the Greek layout when you press v on an US keyboard you get the 
737     output character ω. The keyup/keydown events hold keyCode 86 which is V.  
738     This does make sense, since it's the virtual key code we are dealing with 
739     - not the character code, not the result of pressing the key. The keypress 
740     event will hold keyCode 969 (ω).
741 
742     * Pressing NumPad_Minus you get keyCode 109 in keyup/keydown and keyCode 45 
743     in keypress. Again, this happens because in keyup/keydown you don't get the 
744     character code, you get the key code, as indicated above. For
745     your information: String.fromCharCode(109) = 'm' and String.fromCharCode(45) 
746     = '-'.
747 
748     Therefore, we need to map all the codes of several keys, like F1-F12, 
749     Escape, Enter, Tab, etc. This map is held by lib.dom.keyCodes. It associates, 
750     for example, code 112 to F1, or 13 to Enter. This map is used to detect 
751     virtual keys in all events.
752 
753     (This is only the general story, details about browser-specific differences 
754     follow below.)
755 
756     If the code given by the browser doesn't appear in keyCode maps, it's used 
757     as is.  The key_ returned is that of String.fromCharCode(keyCode).
758 
759     In all browsers we consider all events having keyCode <= 32, as being events  
760     generated by a virtual key (not a character). As such, the keyCode value is 
761     always searched in lib.dom.keyCodes.
762 
763     As you might notice from the above description, in the keypress event we 
764     cannot tell the difference from say F1 and p, because both give the code 
765     112. In Gecko and Webkit we can tell the difference because these UAs also 
766     set the charCode event property when the key generates a character. If F1 is 
767     pressed, or some other virtual key, charCode is never set.
768 
769     In Opera the charCode property is never set. However, the 'which' event 
770     property is not set for several virtual keys. This means we can tell the 
771     difference between a character and a virtual key. However, there's a catch: 
772     not *all* virtual keys have the 'which' property unset. Known exceptions: 
773     Backspace (8), Tab (9), Enter (13), Shift (16), Control (17), Alt (18), 
774     Pause (19), Escape (27), End (35), Home (36), Insert (45), Delete (46) and 
775     NumLock (144). Given we already consider any keyCode <= 32 being one of some 
776     virtual key, fewer exceptions remain. We only have the End, Home, Insert, 
777     Delete and the NumLock keys which cannot be 100% properly detected in the 
778     keypress event, in Opera. To properly detect End/Home we can check if the 
779     Shift modifier is active or not. If the user wants # instead of End, then 
780     Shift must be active. The same goes for $ and Home. Thus we now only have 
781     the '-' (Insert) and the '.' (Delete) characters incorrectly detected as 
782     being Insert/Delete.
783     
784     The above brings us to one of the main visible difference, when comparing 
785     the lib.dom.KeyboardEventListener class and the simple 
786     lib.dom.KeyboardEvent.getKey() function. In getKey(), for the keypress event 
787     we cannot accurately determine the exact key, because it requires checking
788     the keyCode used for the keydown event. The KeyboardEventListener
789     class monitors all the keyboard events, ensuring a more accurate key 
790     detection.
791 
792     Different keyboard layouts and international characters are generally 
793     supported. Tested and known to work with the Cyrillic alphabet (Greek 
794     keyboard layout) and with the US Dvorak keyboard layouts.
795 
796     Opera does not fire the keyup event for international characters when 
797     running on Linux. For example, this happens with the Greek keyboard layout, 
798     when trying Cyrillic characters.
799 
800     Gecko gives no keyCode/charCode/which for international characters when 
801     running on Linux, in the keyup/keydown events. Thus, all such keys remain 
802     unidentified for these two events. For the keypress event there are no 
803     issues with such characters.
804 
805     Webkit and Konqueror 4 also implement the keyIdentifier property from the 
806     DOM 3 Events specification. In theory, this should be great, but it's not 
807     without problems.  Sometimes keyCode/charCode/which are all 0, but 
808     keyIdentifier is properly set. For several virtual keys the keyIdentifier 
809     value is simply 'U+0000'. Thus, the keyIdentifier is used only if the value 
810     is not 'Unidentified' / 'U+0000', and only when keyCode/charCode/which are 
811     not available.
812 
813     Konqueror 4 does not use the 'U+XXXX' notation for Unicode characters. It 
814     simply gives the character, directly.
815 
816     Additionally, Konqueror seems to have some problems with several keyCodes in 
817     keydown/keyup. For example, the key '[' gives keyCode 91 instead of 219.  
818     Thus, it effectively gives the Unicode for the character, not the key code.  
819     This issue is visible with other key as well.
820 
821     NumPad_Clear is unidentified on Linux in all browsers, but it works on 
822     Windows.
823 
824     In MSIE the keypress event is only fired for characters and for Escape, 
825     Space and Enter. Similarly, Webkit only fires the keypress event for 
826     characters. However, Webkit does not fire keypress for Escape.
827 
828     International characters and different keyboard layouts seem to work fine in 
829     MSIE as well.
830 
831     As of MSIE 4.0, the keypress event fires for the following keys:
832       * Letters: A - Z (uppercase and lowercase)
833       * Numerals: 0 - 9
834       * Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~
835       * System: Escape (27), Space (32), Enter (13)
836 
837     Documentation about the keypress event:
838     http://msdn.microsoft.com/en-us/library/ms536939(VS.85).aspx
839 
840     As of MSIE 4.0, the keydown event fires for the following keys:
841       * Editing: Delete (46), Insert (45)
842       * Function: F1 - F12
843       * Letters: A - Z (uppercase and lowercase)
844       * Navigation: Home, End, Left, Right, Up, Down
845       * Numerals: 0 - 9
846       * Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~
847       * System: Escape (27), Space (32), Shift (16), Tab (9)
848 
849     As of MSIE 5, the event also fires for the following keys:
850       * Editing: Backspace (8)
851       * Navigation: PageUp (33), PageDown (34)
852       * System: Shift+Tab (9)
853 
854     Documentation about the keydown event:
855     http://msdn.microsoft.com/en-us/library/ms536938(VS.85).aspx
856 
857     As of MSIE 4.0, the keyup event fires for the following keys:
858       * Editing: Delete, Insert
859       * Function: F1 - F12
860       * Letters: A - Z (uppercase and lowercase)
861       * Navigation: Home (36), End (35), Left (37), Right (39), Up (38), Down (40)
862       * Numerals: 0 - 9
863       * Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~
864       * System: Escape (27), Space (32), Shift (16), Tab (9)
865 
866     As of MSIE 5, the event also fires for the following keys:
867       * Editing: Backspace (8)
868       * Navigation: PageUp (33), PageDown (34)
869       * System: Shift+Tab (9)
870 
871     Documentation about the keyup event:
872     http://msdn.microsoft.com/en-us/library/ms536940(VS.85).aspx
873 
874     For further gory details and a different implementation see:
875     http://code.google.com/p/doctype/source/browse/trunk/goog/events/keycodes.js
876     http://code.google.com/p/doctype/source/browse/trunk/goog/events/keyhandler.js
877 
878     Opera keydown/keyup:
879       These events fire for all keys, including for modifiers.
880       keyCode is always set.
881       charCode is never set.
882       which is always set.
883       keyIdentifier is always undefined.
884 
885     Opera keypress:
886       This event fires for all keys, except for modifiers themselves.
887       keyCode is always set.
888       charCode is never set.
889       which is set for all characters. which = 0 for several virtual keys.
890       which is known to be set for: Backspace (8), Tab (9), Enter (13), Shift 
891       (16), Control (17), Alt (18), Pause (19), Escape (27), End (35), Home 
892       (36), Insert (45), Delete (46), NumLock (144).
893       which is known to be unset for: F1 - F12, PageUp (33), PageDown (34), Left 
894       (37), Up (38), Right (39), Down (40).
895       keyIdentifier is always undefined.
896 
897     MSIE keyup/keypress/keydown:
898       Event firing conditions are described above.
899       keyCode is always set.
900       charCode is never set.
901       which is never set.
902       keyIdentifier is always undefined.
903 
904     Webkit keydown/keyup:
905       These events fires for all keys, including for modifiers.
906       keyCode is always set.
907       charCode is never set.
908       which is always set.
909       keyIdentifier is always set.
910 
911     Webkit keypress:
912       This event fires for characters keys, similarly to MSIE (see above info).
913       keyCode is always set.
914       charCode is always set for all characters.
915       which is always set.
916       keyIdentifier is null.
917 
918     Gecko keydown/keyup:
919       These events fire for all keys, including for modifiers.
920       keyCode is always set.
921       charCode is never set.
922       which is always set.
923       keyIdentifier is always undefined.
924 
925     Gecko keypress:
926       This event fires for all keys, except for modifiers themselves.
927       keyCode is only set for virtual keys, not for characters.
928       charCode is always set for all characters.
929       which is always set for all characters and for the Enter virtual key.
930       keyIdentifier is always undefined.
931 
932     Another important difference between the KeyboardEventListener class and the 
933     getKey() function is that the class tries to ensure that the keypress event 
934     is fired for the handler, even if the Web browser does not do it natively.  
935     Also, the class tries to provide a consistent approach to keyboard event 
936     repetition when the user holds down a key for longer periods of time, by 
937     repeating only the keypress event.
938 
939     On Linux, Opera, Firefox and Konqueror do not repeat the keydown event, only 
940     keypress. On Windows, Opera, Firefox and MSIE do repeat the keydown and 
941     keypress events while the user holds down the key. Webkit  repeats the 
942     keydown and the keypress (when it fires) events on both systems.
943 
944     The default action can be prevented for during keydown in MSIE, and during 
945     keypress for the other browsers. In Webkit when keypress doesn't fire, 
946     keydown needs to be prevented.
947 
948     The KeyboardEventListener class tries to bring consistency. The keydown 
949     event never repeats, only the keypress event repeats and it always fires for 
950     all keys. The keypress event never fires for modifiers. Events should always 
951     be prevented during keypress - the class deals with preventing the event 
952     during keydown or keypress as needed in Webkit and MSIE.
953 
954     If no code/keyIdentifier is given by the browser, the getKey() function 
955     returns null. In the case of the KeyboardEventListener class, keyCode_ 
956     / key_ / charCode_ / char_ will be null or undefined.
957    */
958 
959   /**
960    * During a keyboard event flow, this holds the current key code, starting 
961    * from the <code>keydown</code> event.
962    *
963    * @private
964    * @type Number
965    */
966   var keyCode_ = null;
967 
968   /**
969    * During a keyboard event flow, this holds the current key, starting from the 
970    * <code>keydown</code> event.
971    *
972    * @private
973    * @type String
974    */
975   var key_ = null;
976 
977   /**
978    * During a keyboard event flow, this holds the current character code, 
979    * starting from the <code>keypress</code> event.
980    *
981    * @private
982    * @type Number
983    */
984   var charCode_ = null;
985 
986   /**
987    * During a keyboard event flow, this holds the current character, starting 
988    * from the <code>keypress</code> event.
989    *
990    * @private
991    * @type String
992    */
993   var char_ = null;
994 
995   /**
996    * True if the current keyboard event is repeating. This happens when the user 
997    * holds down a key for longer periods of time.
998    *
999    * @private
1000    * @type Boolean
1001    */
1002   var repeat_ = false;
1003 
1004 
1005   if (!handlers_) {
1006     throw new TypeError('The first argument must be of type an object.');
1007   }
1008 
1009   if (!handlers_.keydown && !handlers_.keypress && !handlers_.keyup) {
1010     throw new TypeError('The provided handlers object has no keyboard event' +
1011         'handler.');
1012   }
1013 
1014   if (handlers_.keydown && typeof handlers_.keydown != 'function') {
1015     throw new TypeError('The keydown event handler is not a function!');
1016   }
1017   if (handlers_.keypress && typeof handlers_.keypress != 'function') {
1018     throw new TypeError('The keypress event handler is not a function!');
1019   }
1020   if (handlers_.keyup && typeof handlers_.keyup != 'function') {
1021     throw new TypeError('The keyup event handler is not a function!');
1022   }
1023 
1024   /**
1025    * Attach the keyboard event listeners to the current DOM element.
1026    */
1027   this.attach = function () {
1028     keyCode_ = null;
1029     key_ = null;
1030     charCode_ = null;
1031     char_ = null;
1032     repeat_ = false;
1033 
1034     // FIXME: I have some ideas for a solution to the problem of having multiple 
1035     // event handlers like these attached to the same element. Somehow, only one 
1036     // should do all the needed work.
1037 
1038     elem_.addEventListener('keydown',  keydown,  false);
1039     elem_.addEventListener('keypress', keypress, false);
1040     elem_.addEventListener('keyup',    keyup,    false);
1041   };
1042 
1043   /**
1044    * Detach the keyboard event listeners from the current DOM element.
1045    */
1046   this.detach = function () {
1047     elem_.removeEventListener('keydown',  keydown,  false);
1048     elem_.removeEventListener('keypress', keypress, false);
1049     elem_.removeEventListener('keyup',    keyup,    false);
1050 
1051     keyCode_ = null;
1052     key_ = null;
1053     charCode_ = null;
1054     char_ = null;
1055     repeat_ = false;
1056   };
1057 
1058   /**
1059    * Dispatch an event.
1060    *
1061    * <p>This function simply invokes the handler for the event of the given 
1062    * <var>type</var>. The handler receives the <var>ev</var> event.
1063    *
1064    * @private
1065    * @param {String} type The event type to dispatch.
1066    * @param {Event} ev The DOM Event object to dispatch to the handler.
1067    */
1068   function dispatch (type, ev) {
1069     if (!handlers_[type]) {
1070       return;
1071     }
1072 
1073     var handler = handlers_[type];
1074 
1075     if (type == ev.type) {
1076       handler.call(elem_, ev);
1077 
1078     } else {
1079       // This happens when the keydown event tries to dispatch a keypress event.
1080 
1081       // FIXME: I could use createEvent() ... food for thought for later
1082       var ev_new = {};
1083       lib.extend(ev_new, ev);
1084       ev_new.type = type;
1085 
1086       // Make sure preventDefault() is not borked...
1087       ev_new.preventDefault = function () {
1088         ev.preventDefault();
1089       };
1090 
1091       handler.call(elem_, ev_new);
1092     }
1093   };
1094 
1095   /**
1096    * The <code>keydown</code> event handler. This function determines the key 
1097    * pressed by the user, and checks if the <code>keypress</code> event will 
1098    * fire in the current Web browser, or not. If it does not, a synthetic 
1099    * <code>keypress</code> event will be fired.
1100    *
1101    * @private
1102    * @param {Event} ev The DOM Event object.
1103    */
1104   function keydown (ev) {
1105     var prevKey = key_;
1106 
1107     charCode_ = null;
1108     char_ = null;
1109 
1110     findKeyCode(ev);
1111 
1112     ev.keyCode_ = keyCode_;
1113     ev.key_ = key_;
1114     ev.repeat_ = key_ && prevKey == key_ ? true : false;
1115 
1116     repeat_ = ev.repeat_;
1117 
1118     // When the user holds down a key for a longer period of time, the keypress 
1119     // event is generally repeated. However, in Webkit keydown is repeated (and 
1120     // keypress if it fires keypress for the key). As such, we do not dispatch 
1121     // the keydown event when a key event starts to be repeated.
1122     if (!repeat_) {
1123       dispatch('keydown', ev);
1124     }
1125 
1126     // MSIE and Webkit only fire the keypress event for characters 
1127     // (alpha-numeric and symbols).
1128     if (!isModifierKey(key_) && !firesKeyPress(ev)) {
1129       ev.type_ = 'keydown';
1130       keypress(ev);
1131     }
1132   };
1133 
1134   /**
1135    * The <code>keypress</code> event handler. This function determines the 
1136    * character generated by the keyboard event.
1137    *
1138    * @private
1139    * @param {Event} ev The DOM Event object.
1140    */
1141   function keypress (ev) {
1142     // We reuse the keyCode_/key_ from the keydown event, because ev.keyCode 
1143     // generally holds the character code during the keypress event.
1144     // However, if keyCode_ is not available, try to determine the key for this 
1145     // event as well.
1146     if (!keyCode_) {
1147       findKeyCode(ev);
1148       repeat_ = false;
1149     }
1150 
1151     ev.keyCode_ = keyCode_;
1152     ev.key_ = key_;
1153 
1154     findCharCode(ev);
1155 
1156     ev.charCode_ = charCode_;
1157     ev.char_ = char_;
1158 
1159     // Any subsequent keypress event is considered a repeated keypress (the user 
1160     // is holding down the key).
1161     ev.repeat_ = repeat_;
1162     if (!repeat_) {
1163       repeat_ = true;
1164     }
1165 
1166     if (!isModifierKey(key_)) {
1167       dispatch('keypress', ev);
1168     }
1169   };
1170 
1171   /**
1172    * The <code>keyup</code> event handler.
1173    *
1174    * @private
1175    * @param {Event} ev The DOM Event object.
1176    */
1177   function keyup (ev) {
1178     /*
1179      * Try to determine the keyCode_ for keyup again, even if we might already 
1180      * have it from keydown. This is needed because the user might press some 
1181      * key which only generates the keydown and keypress events, after which 
1182      * a sudden keyup event is fired for a completely different key.
1183      *
1184      * Example: in Opera press F2 then Escape. It will first generate two 
1185      * events, keydown and keypress, for the F2 key. When you press Escape to 
1186      * close the dialog box, the script receives keyup for Escape.
1187      */
1188     findKeyCode(ev);
1189 
1190     ev.keyCode_ = keyCode_;
1191     ev.key_ = key_;
1192 
1193     // Provide the character info from the keypress event in keyup as well.
1194     ev.charCode_ = charCode_;
1195     ev.char_ = char_;
1196 
1197     dispatch('keyup', ev);
1198 
1199     keyCode_ = null;
1200     key_ = null;
1201     charCode_ = null;
1202     char_ = null;
1203     repeat_ = false;
1204   };
1205 
1206   /**
1207    * Tells if the <var>key</var> is a modifier or not.
1208    *
1209    * @private
1210    * @param {String} key The key name.
1211    * @returns {Boolean} True if the <var>key</var> is a modifier, or false if 
1212    * not.
1213    */
1214   function isModifierKey (key) {
1215     switch (key) {
1216       case 'Shift':
1217       case 'Control':
1218       case 'Alt':
1219       case 'Meta':
1220       case 'Win':
1221         return true;
1222       default:
1223         return false;
1224     }
1225   };
1226 
1227   /**
1228    * Tells if the current Web browser will fire the <code>keypress</code> event 
1229    * for the current <code>keydown</code> event object.
1230    *
1231    * @private
1232    * @param {Event} ev The DOM Event object.
1233    * @returns {Boolean} True if the Web browser will fire 
1234    * a <code>keypress</code> event, or false if not.
1235    */
1236   function firesKeyPress (ev) {
1237     if (!lib.browser.msie && !lib.browser.webkit) {
1238       return true;
1239     }
1240 
1241     // Check if the key is a character key, or not.
1242     // If it's not a character, then keypress will not fire.
1243     // Known exceptions: keypress fires for Space, Enter and Escape in MSIE.
1244     if (key_ && key_ != 'Space' && key_ != 'Enter' && key_ != 'Escape' && 
1245         key_.length != 1) {
1246       return false;
1247     }
1248 
1249     // Webkit doesn't fire keypress for Escape as well ...
1250     if (lib.browser.webkit && key_ == 'Escape') {
1251       return false;
1252     }
1253 
1254     // MSIE does not fire keypress if you hold Control / Alt down, while Shift 
1255     // is off. Albeit, based on testing I am not completely sure if Shift needs 
1256     // to be down or not. Sometimes MSIE won't fire keypress even if I hold 
1257     // Shift down, and sometimes it does. Eh.
1258     if (lib.browser.msie && !ev.shiftKey && (ev.ctrlKey || ev.altKey)) {
1259       return false;
1260     }
1261 
1262     return true;
1263   };
1264 
1265   /**
1266    * Determine the key and the key code for the current DOM Event object. This 
1267    * function updates the <var>keyCode_</var> and the <var>key_</var> variables 
1268    * to hold the result.
1269    *
1270    * @private
1271    * @param {Event} ev The DOM Event object.
1272    */
1273   function findKeyCode (ev) {
1274     /*
1275      * If the event has no keyCode/which/keyIdentifier values, then simply do 
1276      * not overwrite any existing keyCode_/key_.
1277      */
1278     if (ev.type == 'keyup' && !ev.keyCode && !ev.which && (!ev.keyIdentifier || 
1279           ev.keyIdentifier == 'Unidentified' || ev.keyIdentifier == 'U+0000')) {
1280       return;
1281     }
1282 
1283     keyCode_ = null;
1284     key_ = null;
1285 
1286     // Try to use keyCode/which.
1287     if (ev.keyCode || ev.which) {
1288       keyCode_ = ev.keyCode || ev.which;
1289 
1290       // Fix Webkit quirks
1291       if (lib.browser.webkit) {
1292         // Old Webkit gives keyCode 25 when Shift+Tab is used.
1293         if (keyCode_ == 25 && this.shiftKey) {
1294           keyCode_ = lib.dom.keyNames.Tab;
1295         } else if (keyCode_ >= 63232 && keyCode_ in lib.dom.keyCodes_Safari2) {
1296           // Old Webkit gives wrong values for several keys.
1297           keyCode_ = lib.dom.keyCodes_Safari2[keyCode_];
1298         }
1299       }
1300 
1301       // Fix keyCode quirks in all browsers.
1302       if (keyCode_ in lib.dom.keyCodes_fixes) {
1303         keyCode_ = lib.dom.keyCodes_fixes[keyCode_];
1304       }
1305 
1306       key_ = lib.dom.keyCodes[keyCode_] || String.fromCharCode(keyCode_);
1307 
1308       return;
1309     }
1310 
1311     // Try to use ev.keyIdentifier. This is only available in Webkit and 
1312     // Konqueror 4, each having some quirks. Sometimes the property is needed, 
1313     // because keyCode/which are not always available.
1314 
1315     var key = null,
1316         keyCode = null,
1317         id = ev.keyIdentifier;
1318 
1319     if (!id || id == 'Unidentified' || id == 'U+0000') {
1320       return;
1321     }
1322 
1323     if (id.substr(0, 2) == 'U+') {
1324       // Webkit gives character codes using the 'U+XXXX' notation, as per spec.
1325       keyCode = parseInt(id.substr(2), 16);
1326 
1327     } else if (id.length == 1) {
1328       // Konqueror 4 implements keyIdentifier, and they provide the Unicode 
1329       // character directly, instead of using the 'U+XXXX' notation.
1330       keyCode = id.charCodeAt(0);
1331       key = id;
1332 
1333     } else {
1334       /*
1335        * Common keyIdentifiers like 'PageDown' are used as they are.
1336        * We determine the common keyCode used by Web browsers, from the 
1337        * lib.dom.keyNames object.
1338        */
1339       keyCode_ = lib.dom.keyNames[id] || null;
1340       key_ = id;
1341 
1342       return;
1343     }
1344 
1345     // Some keyIdentifiers like 'U+007F' (127: Delete) need to become key names.
1346     if (keyCode in lib.dom.keyCodes && (keyCode <= 32 || keyCode == 127 || keyCode 
1347           == 144)) {
1348       key_ = lib.dom.keyCodes[keyCode];
1349     } else {
1350       if (!key) {
1351         key = String.fromCharCode(keyCode);
1352       }
1353 
1354       // Konqueror gives lower-case chars
1355       key_ = key.toUpperCase();
1356       if (key != key_) {
1357         keyCode = key_.charCodeAt(0);
1358       }
1359     }
1360 
1361     // Correct the keyCode, make sure it's a common keyCode, not the Unicode 
1362     // decimal representation of the character.
1363     if (key_ == 'Delete' || key_.length == 1 && key_ in lib.dom.keyNames) {
1364       keyCode = lib.dom.keyNames[key_];
1365     }
1366 
1367     keyCode_ = keyCode;
1368   };
1369 
1370   /**
1371    * Determine the character and the character code for the current DOM Event 
1372    * object. This function updates the <var>charCode_</var> and the 
1373    * <var>char_</var> variables to hold the result.
1374    *
1375    * @private
1376    * @param {Event} ev The DOM Event object.
1377    */
1378   function findCharCode (ev) {
1379     charCode_ = null;
1380     char_ = null;
1381 
1382     // Webkit and Gecko implement ev.charCode.
1383     if (ev.charCode) {
1384       charCode_ = ev.charCode;
1385       char_ = String.fromCharCode(ev.charCode);
1386 
1387       return;
1388     }
1389 
1390     // Try the keyCode mess.
1391     if (ev.keyCode || ev.which) {
1392       var keyCode = ev.keyCode || ev.which;
1393 
1394       var force = false;
1395 
1396       // We accept some keyCodes.
1397       switch (keyCode) {
1398         case lib.dom.keyNames.Tab:
1399         case lib.dom.keyNames.Enter:
1400         case lib.dom.keyNames.Space:
1401           force = true;
1402       }
1403 
1404       // Do not consider the keyCode a character code, if during the keydown 
1405       // event it was determined the key does not generate a character, unless 
1406       // it's Tab, Enter or Space.
1407       if (!force && key_ && key_.length != 1) {
1408         return;
1409       }
1410 
1411       // If the keypress event at hand is synthetically dispatched by keydown, 
1412       // then special treatment is needed. This happens only in Webkit and MSIE.
1413       if (ev.type_ == 'keydown') {
1414         var key = lib.dom.keyCodes[keyCode];
1415         // Check if the keyCode points to a single character.
1416         // If it does, use it.
1417         if (key && key.length == 1) {
1418           charCode_ = key.charCodeAt(0); // keyCodes != charCodes
1419           char_ = key;
1420         }
1421       } else if (keyCode >= 32 || force) {
1422         // For normal keypress events, we are done.
1423         charCode_ = keyCode;
1424         char_ = String.fromCharCode(keyCode);
1425       }
1426 
1427       if (charCode_) {
1428         return;
1429       }
1430     }
1431 
1432     /*
1433      * Webkit and Konqueror do not provide a keyIdentifier in the keypress 
1434      * event, as per spec. However, in the unlikely case when the keyCode is 
1435      * missing, and the keyIdentifier is available, we use it.
1436      *
1437      * This property might be used when a synthetic keypress event is generated 
1438      * by the keydown event, and keyCode/charCode/which are all not available.
1439      */
1440 
1441     var c = null,
1442         charCode = null,
1443         id = ev.keyIdentifier;
1444 
1445     if (id && id != 'Unidentified' && id != 'U+0000' &&
1446         (id.substr(0, 2) == 'U+' || id.length == 1)) {
1447 
1448       // Characters in Konqueror...
1449       if (id.length == 1) {
1450         charCode = id.charCodeAt(0);
1451         c = id;
1452 
1453       } else {
1454         // Webkit uses the 'U+XXXX' notation as per spec.
1455         charCode = parseInt(id.substr(2), 16);
1456       }
1457 
1458       if (charCode == lib.dom.keyNames.Tab ||
1459           charCode == lib.dom.keyNames.Enter ||
1460           charCode >= 32 && charCode != 127 &&
1461           charCode != lib.dom.keyNames.NumLock) {
1462 
1463         charCode_ = charCode;
1464         char_ = c || String.fromCharCode(charCode);
1465 
1466         return;
1467       }
1468     }
1469 
1470     // Try to use the key determined from the previous keydown event, if it 
1471     // holds a character.
1472     if (key_ && key_.length == 1) {
1473       charCode_ = key_.charCodeAt(0);
1474       char_ = key_;
1475     }
1476   };
1477 
1478   this.attach();
1479 };
1480 
1481 // lib.dom.KeyboardEvent.getKey() is not included here. You can get it from the 
1482 // libmacrame project: http://code.google.com/p/libmacrame.
1483 
1484 // vim:set spell spl=en fo=wan1croql tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix:
1485