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