Add mode comments to the classes
[proteocache.git] / webapp / resources / datatables-1.9.4 / extras / KeyTable / js / KeyTable.js
1 /*
2  * File:        KeyTable.js
3  * Version:     1.1.7
4  * CVS:         $Idj$
5  * Description: Keyboard navigation for HTML tables
6  * Author:      Allan Jardine (www.sprymedia.co.uk)
7  * Created:     Fri Mar 13 21:24:02 GMT 2009
8  * Modified:    $Date$ by $Author$
9  * Language:    Javascript
10  * License:     GPL v2 or BSD 3 point style
11  * Project:     Just a little bit of fun :-)
12  * Contact:     www.sprymedia.co.uk/contact
13  * 
14  * Copyright 2009-2011 Allan Jardine, all rights reserved.
15  *
16  * This source file is free software, under either the GPL v2 license or a
17  * BSD style license, available at:
18  *   http://datatables.net/license_gpl2
19  *   http://datatables.net/license_bsd
20  */
21
22
23 function KeyTable ( oInit )
24 {
25         /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
26          * API parameters
27          * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
28         
29         /*
30          * Variable: block
31          * Purpose:  Flag whether or not KeyTable events should be processed
32          * Scope:    KeyTable - public
33          */
34         this.block = false;
35         
36         /*
37          * Variable: event
38          * Purpose:  Container for all event application methods
39          * Scope:    KeyTable - public
40          * Notes:    This object contains all the public methods for adding and removing events - these
41          *           are dynamically added later on
42          */
43         this.event = {
44                 "remove": {}
45         };
46         
47         
48         /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
49          * API methods
50          * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
51         
52         /*
53          * Function: fnGetCurrentPosition
54          * Purpose:  Get the currently focused cell's position
55          * Returns:  array int: [ x, y ]
56          * Inputs:   void
57          */
58         this.fnGetCurrentPosition = function ()
59         {
60                 return [ _iOldX, _iOldY ];
61         };
62         
63         
64         /*
65          * Function: fnGetCurrentData
66          * Purpose:  Get the currently focused cell's data (innerHTML)
67          * Returns:  string: - data requested
68          * Inputs:   void
69          */
70         this.fnGetCurrentData = function ()
71         {
72                 return _nOldFocus.innerHTML;
73         };
74         
75         
76         /*
77          * Function: fnGetCurrentTD
78          * Purpose:  Get the currently focused cell
79          * Returns:  node: - focused element
80          * Inputs:   void
81          */
82         this.fnGetCurrentTD = function ()
83         {
84                 return _nOldFocus;
85         };
86         
87         
88         /*
89          * Function: fnSetPosition
90          * Purpose:  Set the position of the focused cell
91          * Returns:  -
92          * Inputs:   int:x - x coordinate
93          *           int:y - y coordinate
94          * Notes:    Thanks to Rohan Daxini for the basis of this function
95          */
96         this.fnSetPosition = function( x, y )
97         {
98                 if ( typeof x == 'object' && x.nodeName )
99                 {
100                         _fnSetFocus( x );
101                 }
102                 else
103                 {
104                         _fnSetFocus( _fnCellFromCoords(x, y) );
105                 }
106         };
107         
108         
109         /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
110          * Private parameters
111          * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
112         
113         /*
114          * Variable: _nBody
115          * Purpose:  Body node of the table - cached for renference
116          * Scope:    KeyTable - private
117          */
118         var _nBody = null;
119         
120         /*
121          * Variable: 
122          * Purpose:  
123          * Scope:    KeyTable - private
124          */
125         var _nOldFocus = null;
126         
127         /*
128          * Variable: _iOldX and _iOldY
129          * Purpose:  X and Y coords of the old elemet that was focused on
130          * Scope:    KeyTable - private
131          */
132         var _iOldX = null;
133         var _iOldY = null;
134         
135         /*
136          * Variable: _that
137          * Purpose:  Scope saving for 'this' after a jQuery event
138          * Scope:    KeyTable - private
139          */
140         var _that = null;
141         
142         /*
143          * Variable: sFocusClass
144          * Purpose:  Class that should be used for focusing on a cell
145          * Scope:    KeyTable - private
146          */
147         var _sFocusClass = "focus";
148         
149         /*
150          * Variable: _bKeyCapture
151          * Purpose:  Flag for should KeyTable capture key events or not
152          * Scope:    KeyTable - private
153          */
154         var _bKeyCapture = false;
155         
156         /*
157          * Variable: _oaoEvents
158          * Purpose:  Event cache object, one array for each supported event for speed of searching
159          * Scope:    KeyTable - private
160          */
161         var _oaoEvents = {
162                 "action": [],
163                 "esc": [],
164                 "focus": [],
165                 "blur": []
166         };
167         
168         /*
169          * Variable: _oDatatable
170          * Purpose:  DataTables object for if we are actually using a DataTables table
171          * Scope:    KeyTable - private
172          */
173         var _oDatatable = null;
174         
175         var _bForm;
176         var _nInput;
177         var _bInputFocused = false;
178         
179         
180         /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
181          * Private methods
182          * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
183         
184         /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
185          * Key table events
186          */
187         
188         /*
189          * Function: _fnEventAddTemplate
190          * Purpose:  Create a function (with closure for sKey) event addition API
191          * Returns:  function: - template function
192          * Inputs:   string:sKey - type of event to detect
193          */
194         function _fnEventAddTemplate( sKey )
195         {
196                 /*
197                  * Function: -
198                  * Purpose:  API function for adding event to cache
199                  * Returns:  -
200                  * Inputs:   1. node:x - target node to add event for
201                  *           2. function:y - callback function to apply
202                  *         or
203                  *           1. int:x - x coord. of target cell (can be null for live events)
204                  *           2. int:y - y coord. of target cell (can be null for live events)
205                  *           3. function:z - callback function to apply
206                  * Notes:    This function is (interally) overloaded (in as much as javascript allows for
207                  *           that) - the target cell can be given by either node or coords.
208                  */
209                 return function ( x, y, z ) {
210                         if ( (x===null || typeof x == "number") && 
211                              (y===null || typeof y == "number") && 
212                              typeof z == "function" )
213                         {
214                                 _fnEventAdd( sKey, x, y, z );
215                         }
216                         else if ( typeof x == "object" && typeof y == "function" )
217                         {
218                                 var aCoords = _fnCoordsFromCell( x );
219                                 _fnEventAdd( sKey, aCoords[0], aCoords[1], y );
220                         }
221                         else
222                         {
223                                 alert( "Unhandable event type was added: x" +x+ "  y:" +y+ "  z:" +z );
224                         }
225                 };
226         }
227         
228         
229         /*
230          * Function: _fnEventRemoveTemplate
231          * Purpose:  Create a function (with closure for sKey) event removal API
232          * Returns:  function: - template function
233          * Inputs:   string:sKey - type of event to detect
234          */
235         function _fnEventRemoveTemplate( sKey )
236         {
237                 /*
238                  * Function: -
239                  * Purpose:  API function for removing event from cache
240                  * Returns:  int: - number of events removed
241                  * Inputs:   1. node:x - target node to remove event from
242                  *           2. function:y - callback function to apply
243                  *         or
244                  *           1. int:x - x coord. of target cell (can be null for live events)
245                  *           2. int:y - y coord. of target cell (can be null for live events)
246                  *           3. function:z - callback function to remove - optional
247                  * Notes:    This function is (interally) overloaded (in as much as javascript allows for
248                  *           that) - the target cell can be given by either node or coords and the function
249                  *           to remove is optional
250                  */
251                 return function ( x, y, z ) {
252                         if ( (x===null || typeof arguments[0] == "number") && 
253                              (y===null || typeof arguments[1] == "number" ) )
254                         {
255                                 if ( typeof arguments[2] == "function" )
256                                 {
257                                         _fnEventRemove( sKey, x, y, z );
258                                 }
259                                 else
260                                 {
261                                         _fnEventRemove( sKey, x, y );
262                                 }
263                         }
264                         else if ( typeof arguments[0] == "object" )
265                         {
266                                 var aCoords = _fnCoordsFromCell( x );
267                                 if ( typeof arguments[1] == "function" )
268                                 {
269                                         _fnEventRemove( sKey, aCoords[0], aCoords[1], y );
270                                 }
271                                 else
272                                 {
273                                         _fnEventRemove( sKey, aCoords[0], aCoords[1] );
274                                 }
275                         }
276                         else
277                         {
278                                 alert( "Unhandable event type was removed: x" +x+ "  y:" +y+ "  z:" +z );
279                         }
280                 };
281         }
282         
283         /* Use the template functions to add the event API functions */
284         for ( var sKey in _oaoEvents )
285         {
286                 if ( sKey )
287                 {
288                         this.event[sKey] = _fnEventAddTemplate( sKey );
289                         this.event.remove[sKey] = _fnEventRemoveTemplate( sKey );
290                 }
291         }
292         
293         
294         /*
295          * Function: _fnEventAdd
296          * Purpose:  Add an event to the internal cache
297          * Returns:  -
298          * Inputs:   string:sType - type of event to add, given by the available elements in _oaoEvents
299          *           int:x - x-coords to add event to - can be null for "blanket" event
300          *           int:y - y-coords to add event to - can be null for "blanket" event
301          *           function:fn - callback function for when triggered
302          */
303         function _fnEventAdd( sType, x, y, fn )
304         {
305                 _oaoEvents[sType].push( {
306                         "x": x,
307                         "y": y,
308                         "fn": fn
309                 } );
310         }
311         
312         
313         /*
314          * Function: _fnEventRemove
315          * Purpose:  Remove an event from the event cache
316          * Returns:  int: - number of matching events removed
317          * Inputs:   string:sType - type of event to look for
318          *           node:nTarget - target table cell
319          *           function:fn - optional - remove this function. If not given all handlers of this
320          *             type will be removed
321          */
322         function _fnEventRemove( sType, x, y, fn )
323         {
324                 var iCorrector = 0;
325                 
326                 for ( var i=0, iLen=_oaoEvents[sType].length ; i<iLen-iCorrector ; i++ )
327                 {
328                         if ( typeof fn != 'undefined' )
329                         {
330                                 if ( _oaoEvents[sType][i-iCorrector].x == x &&
331                                      _oaoEvents[sType][i-iCorrector].y == y &&
332                                            _oaoEvents[sType][i-iCorrector].fn == fn )
333                                 {
334                                         _oaoEvents[sType].splice( i-iCorrector, 1 );
335                                         iCorrector++;
336                                 }
337                         }
338                         else
339                         {
340                                 if ( _oaoEvents[sType][i-iCorrector].x == x &&
341                                      _oaoEvents[sType][i-iCorrector].y == y )
342                                 {
343                                         _oaoEvents[sType].splice( i, 1 );
344                                         return 1;
345                                 }
346                         }
347                 }
348                 return iCorrector;
349         }
350         
351         
352         /*
353          * Function: _fnEventFire
354          * Purpose:  Look thought the events cache and fire off the event of interest
355          * Returns:  int:iFired - number of events fired
356          * Inputs:   string:sType - type of event to look for
357          *           int:x - x coord of cell
358          *           int:y - y coord of  ell
359          * Notes:    It might be more efficient to return after the first event has been tirggered,
360          *           but that would mean that only one function of a particular type can be
361          *           subscribed to a particular node.
362          */
363         function _fnEventFire ( sType, x, y )
364         {
365                 var iFired = 0;
366                 var aEvents = _oaoEvents[sType];
367                 for ( var i=0 ; i<aEvents.length ; i++ )
368                 {
369                         if ( (aEvents[i].x == x     && aEvents[i].y == y    ) ||
370                              (aEvents[i].x === null && aEvents[i].y == y    ) ||
371                              (aEvents[i].x == x     && aEvents[i].y === null ) ||
372                              (aEvents[i].x === null && aEvents[i].y === null )
373                         )
374                         {
375                                 aEvents[i].fn( _fnCellFromCoords(x,y), x, y );
376                                 iFired++;
377                         }
378                 }
379                 return iFired;
380         }
381         
382         
383         
384         /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
385          * Focus functions
386          */
387         
388         /*
389          * Function: _fnSetFocus
390          * Purpose:  Set focus on a node, and remove from an old node if needed
391          * Returns:  -
392          * Inputs:   node:nTarget - node we want to focus on
393          *           bool:bAutoScroll - optional - should we scroll the view port to the display
394          */
395         function _fnSetFocus( nTarget, bAutoScroll )
396         {
397                 /* If node already has focus, just ignore this call */
398                 if ( _nOldFocus == nTarget )
399                 {
400                         return;
401                 }
402                 
403                 if ( typeof bAutoScroll == 'undefined' )
404                 {
405                         bAutoScroll = true;
406                 }
407                 
408                 /* Remove old focus (with blur event if needed) */
409                 if ( _nOldFocus !== null )
410                 {
411                         _fnRemoveFocus( _nOldFocus );
412                 }
413                 
414                 /* Add the new class to highlight the focused cell */
415                 jQuery(nTarget).addClass( _sFocusClass );
416                 jQuery(nTarget).parent().addClass( _sFocusClass );
417                 
418                 /* If it's a DataTable then we need to jump the paging to the relevant page */
419                 var oSettings;
420                 if ( _oDatatable )
421                 {
422                         oSettings = _oDatatable.fnSettings();
423                         var iRow = _fnFindDtCell( nTarget )[1];
424                         var bKeyCaptureCache = _bKeyCapture;
425                         
426                         /* Page forwards */
427                         while ( iRow >= oSettings.fnDisplayEnd() )
428                         {
429                                 if ( oSettings._iDisplayLength >= 0 )
430                                 {
431                                         /* Make sure we are not over running the display array */
432                                         if ( oSettings._iDisplayStart + oSettings._iDisplayLength < oSettings.fnRecordsDisplay() )
433                                         {
434                                                 oSettings._iDisplayStart += oSettings._iDisplayLength;
435                                         }
436                                 }
437                                 else
438                                 {
439                                         oSettings._iDisplayStart = 0;
440                                 }
441                                 _oDatatable.oApi._fnCalculateEnd( oSettings );
442                         }
443                         
444                         /* Page backwards */
445                         while ( iRow < oSettings._iDisplayStart )
446                         {
447                                 oSettings._iDisplayStart = oSettings._iDisplayLength>=0 ?
448                                         oSettings._iDisplayStart - oSettings._iDisplayLength :
449                                         0;
450                                         
451                                 if ( oSettings._iDisplayStart < 0 )
452                                 {
453                                   oSettings._iDisplayStart = 0;
454                                 }
455                                 _oDatatable.oApi._fnCalculateEnd( oSettings );
456                         }
457                         
458                         /* Re-draw the table */
459                         _oDatatable.oApi._fnDraw( oSettings );
460                         
461                         /* Restore the key capture */
462                         _bKeyCapture = bKeyCaptureCache;
463                 }
464                 
465                 /* Cache the information that we are interested in */
466                 var aNewPos = _fnCoordsFromCell( nTarget );
467                 _nOldFocus = nTarget;
468                 _iOldX = aNewPos[0];
469                 _iOldY = aNewPos[1];
470                 
471                 var iViewportHeight, iViewportWidth, iScrollTop, iScrollLeft, iHeight, iWidth, aiPos;
472                 if ( bAutoScroll )
473                 {
474                         /* Scroll the viewport such that the new cell is fully visible in the rendered window */
475                         iViewportHeight = document.documentElement.clientHeight;
476                         iViewportWidth = document.documentElement.clientWidth;
477                         iScrollTop = document.body.scrollTop || document.documentElement.scrollTop;
478                         iScrollLeft = document.body.scrollLeft || document.documentElement.scrollLeft;
479                         iHeight = nTarget.offsetHeight;
480                         iWidth = nTarget.offsetWidth;
481                         aiPos = _fnGetPos( nTarget );
482                         
483                         /* Take account of scrolling in DataTables 1.7 - remove scrolling since that would add to
484                          * the positioning calculation
485                          */
486                         if ( _oDatatable && typeof oSettings.oScroll != 'undefined' &&
487                           (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "") )
488                         {
489                                 aiPos[1] -= $(oSettings.nTable.parentNode).scrollTop();
490                                 aiPos[0] -= $(oSettings.nTable.parentNode).scrollLeft();
491                         }
492                         
493                         /* Correct viewport positioning for vertical scrolling */
494                         if ( aiPos[1]+iHeight > iScrollTop+iViewportHeight )
495                         {
496                                 /* Displayed element if off the bottom of the viewport */
497                                 _fnSetScrollTop( aiPos[1]+iHeight - iViewportHeight );
498                         }
499                         else if ( aiPos[1] < iScrollTop )
500                         {
501                                 /* Displayed element if off the top of the viewport */
502                                 _fnSetScrollTop( aiPos[1] );
503                         }
504                         
505                         /* Correct viewport positioning for horizontal scrolling */
506                         if ( aiPos[0]+iWidth > iScrollLeft+iViewportWidth )
507                         {
508                                 /* Displayed element is off the bottom of the viewport */
509                                 _fnSetScrollLeft( aiPos[0]+iWidth - iViewportWidth );
510                         }
511                         else if ( aiPos[0] < iScrollLeft )
512                         {
513                                 /* Displayed element if off the Left of the viewport */
514                                 _fnSetScrollLeft( aiPos[0] );
515                         }
516                 }
517                 
518                 /* Take account of scrolling in DataTables 1.7 */
519                 if ( _oDatatable && typeof oSettings.oScroll != 'undefined' &&
520                   (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "") )
521                 {
522                         var dtScrollBody = oSettings.nTable.parentNode;
523                         iViewportHeight = dtScrollBody.clientHeight;
524                         iViewportWidth = dtScrollBody.clientWidth;
525                         iScrollTop = dtScrollBody.scrollTop;
526                         iScrollLeft = dtScrollBody.scrollLeft;
527                         iHeight = nTarget.offsetHeight;
528                         iWidth = nTarget.offsetWidth;
529                         
530                         /* Correct for vertical scrolling */
531                         if ( nTarget.offsetTop + iHeight > iViewportHeight+iScrollTop )
532                         {
533                                 dtScrollBody.scrollTop = (nTarget.offsetTop + iHeight) - iViewportHeight;
534                         }
535                         else if ( nTarget.offsetTop < iScrollTop )
536                         {
537                                 dtScrollBody.scrollTop = nTarget.offsetTop;
538                         }
539                         
540                         /* Correct for horizontal scrolling */
541                         if ( nTarget.offsetLeft + iWidth > iViewportWidth+iScrollLeft )
542                         {
543                                 dtScrollBody.scrollLeft = (nTarget.offsetLeft + iWidth) - iViewportWidth;
544                         }
545                         else if ( nTarget.offsetLeft < iScrollLeft )
546                         {
547                                 dtScrollBody.scrollLeft = nTarget.offsetLeft;
548                         }
549                 }
550
551                 /* Focused - so we want to capture the keys */
552                 _fnCaptureKeys();
553                 
554                 /* Fire of the focus event if there is one */
555                 _fnEventFire( "focus", _iOldX, _iOldY );
556         }
557         
558         
559         /*
560          * Function: _fnBlur
561          * Purpose:  Blur focus from the whole table
562          * Returns:  -
563          * Inputs:   -
564          */
565         function _fnBlur()
566         {
567                 _fnRemoveFocus( _nOldFocus );
568                 _iOldX = null;
569                 _iOldY = null;
570                 _nOldFocus = null;
571                 _fnReleaseKeys();
572         }
573         
574         
575         /*
576          * Function: _fnRemoveFocus
577          * Purpose:  Remove focus from a cell and fire any blur events which are attached
578          * Returns:  -
579          * Inputs:   node:nTarget - cell of interest
580          */
581         function _fnRemoveFocus( nTarget )
582         {
583                 jQuery(nTarget).removeClass( _sFocusClass );
584                 jQuery(nTarget).parent().removeClass( _sFocusClass );
585                 _fnEventFire( "blur", _iOldX, _iOldY );
586         }
587         
588         
589         /*
590          * Function: _fnClick
591          * Purpose:  Focus on the element that has been clicked on by the user
592          * Returns:  -
593          * Inputs:   event:e - click event
594          */
595         function _fnClick ( e )
596         {
597                 var nTarget = this;
598                 while ( nTarget.nodeName != "TD" )
599                 {
600                         nTarget = nTarget.parentNode;
601                 }
602                 
603                 _fnSetFocus( nTarget );
604                 _fnCaptureKeys();
605         }
606         
607         
608         
609         /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
610          * Key events
611          */
612         
613         /*
614          * Function: _fnKey
615          * Purpose:  Deal with a key events, be it moving the focus or return etc.
616          * Returns:  bool: - allow browser default action
617          * Inputs:   event:e - key event
618          */
619         function _fnKey ( e )
620         {
621                 /* If user or system has blocked KeyTable from doing anything, just ignore this event */
622                 if ( _that.block || !_bKeyCapture )
623                 {
624                         return true;
625                 }
626                 
627                 /* If a modifier key is pressed (exapct shift), ignore the event */
628                 if ( e.metaKey || e.altKey || e.ctrlKey )
629                 {
630                     return true;
631                 }
632                 var
633                         x, y,
634                         iTableWidth = _nBody.getElementsByTagName('tr')[0].getElementsByTagName('td').length, 
635                         iTableHeight;
636                 
637                 /* Get table height and width - done here so as to be dynamic (if table is updated) */
638                 if ( _oDatatable )
639                 {
640                         /* 
641                          * Locate the current node in the DataTable overriding the old positions - the reason for
642                          * is is that there might have been some DataTables interaction between the last focus and
643                          * now
644                          */
645                         var oSettings = _oDatatable.fnSettings();
646                         iTableHeight = oSettings.aiDisplay.length;
647                         
648                         var aDtPos = _fnFindDtCell( _nOldFocus );
649                         if ( aDtPos === null )
650                         {
651                                 /* If the table has been updated such that the focused cell can't be seen - do nothing */
652                                 return;
653                         }
654                         _iOldX = aDtPos[ 0 ];
655                         _iOldY = aDtPos[ 1 ];
656                 }
657                 else
658                 {
659                         iTableHeight = _nBody.getElementsByTagName('tr').length;
660                 }
661                 
662                 /* Capture shift+tab to match the left arrow key */
663                 var iKey = (e.keyCode == 9 && e.shiftKey) ? -1 : e.keyCode;
664                 
665                 switch( iKey )
666                 {
667                         case 13: /* return */
668                                 e.preventDefault();
669                                 e.stopPropagation();
670                                 _fnEventFire( "action", _iOldX, _iOldY );
671                                 return true;
672                                 
673                         case 27: /* esc */
674                                 if ( !_fnEventFire( "esc", _iOldX, _iOldY ) )
675                                 {
676                                         /* Only lose focus if there isn't an escape handler on the cell */
677                                         _fnBlur();
678                                         return;
679                                 }
680                                 x = _iOldX;
681                                 y = _iOldY;
682                                 break;
683                         
684                         case -1:
685                         case 37: /* left arrow */
686                                 if ( _iOldX > 0 ) {
687                                         x = _iOldX - 1;
688                                         y = _iOldY;
689                                 } else if ( _iOldY > 0 ) {
690                                         x = iTableWidth-1;
691                                         y = _iOldY - 1;
692                                 } else {
693                                         /* at start of table */
694                                         if ( iKey == -1 && _bForm )
695                                         {
696                                                 /* If we are in a form, return focus to the 'input' element such that tabbing will
697                                                  * follow correctly in the browser
698                                                  */
699                                                 _bInputFocused = true;
700                                                 _nInput.focus();
701                                                 
702                                                 /* This timeout is a little nasty - but IE appears to have some asyhnc behaviour for 
703                                                  * focus
704                                                  */
705                                                 setTimeout( function(){ _bInputFocused = false; }, 0 );
706                                                 _bKeyCapture = false;
707                                                 _fnBlur();
708                                                 return true; 
709                                         }
710                                         else
711                                         {
712                                                 return false;
713                                         }
714                                 }
715                                 break;
716                         
717                         case 38: /* up arrow */
718                                 if ( _iOldY > 0 ) {
719                                         x = _iOldX;
720                                         y = _iOldY - 1;
721                                 } else {
722                                         return false;
723                                 }
724                                 break;
725                         
726                         case 9: /* tab */
727                         case 39: /* right arrow */
728                                 if ( _iOldX < iTableWidth-1 ) {
729                                         x = _iOldX + 1;
730                                         y = _iOldY;
731                                 } else if ( _iOldY < iTableHeight-1 ) {
732                                         x = 0;
733                                         y = _iOldY + 1;
734                                 } else {
735                                         /* at end of table */
736                                         if ( iKey == 9 && _bForm )
737                                         {
738                                                 /* If we are in a form, return focus to the 'input' element such that tabbing will
739                                                  * follow correctly in the browser
740                                                  */
741                                                 _bInputFocused = true;
742                                                 _nInput.focus();
743                                                 
744                                                 /* This timeout is a little nasty - but IE appears to have some asyhnc behaviour for 
745                                                  * focus
746                                                  */
747                                                 setTimeout( function(){ _bInputFocused = false; }, 0 );
748                                                 _bKeyCapture = false;
749                                                 _fnBlur();
750                                                 return true; 
751                                         }
752                                         else
753                                         {
754                                                 return false;
755                                         }
756                                 }
757                                 break;
758                         
759                         case 40: /* down arrow */
760                                 if ( _iOldY < iTableHeight-1 ) {
761                                         x = _iOldX;
762                                         y = _iOldY + 1;
763                                 } else {
764                                         return false;
765                                 }
766                                 break;
767                         
768                         default: /* Nothing we are interested in */
769                                 return true;
770                 }
771                 
772                 _fnSetFocus( _fnCellFromCoords(x, y) );
773                 return false;
774         }
775         
776         
777         /*
778          * Function: _fnCaptureKeys
779          * Purpose:  Start capturing key events for this table
780          * Returns:  -
781          * Inputs:   -
782          */
783         function _fnCaptureKeys( )
784         {
785                 if ( !_bKeyCapture )
786                 {
787                         _bKeyCapture = true;
788                 }
789         }
790         
791         
792         /*
793          * Function: _fnReleaseKeys
794          * Purpose:  Stop capturing key events for this table
795          * Returns:  -
796          * Inputs:   -
797          */
798         function _fnReleaseKeys( )
799         {
800                 _bKeyCapture = false;
801         }
802         
803         
804         
805         /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
806          * Support functions
807          */
808         
809         /*
810          * Function: _fnCellFromCoords
811          * Purpose:  Calulate the target TD cell from x and y coordinates
812          * Returns:  node: - TD target
813          * Inputs:   int:x - x coordinate
814          *           int:y - y coordinate
815          */
816         function _fnCellFromCoords( x, y )
817         {
818                 if ( _oDatatable )
819                 {
820                         var oSettings = _oDatatable.fnSettings();
821                         if ( typeof oSettings.aoData[ oSettings.aiDisplay[ y ] ] != 'undefined' )
822                         {
823                                 return oSettings.aoData[ oSettings.aiDisplay[ y ] ].nTr.getElementsByTagName('td')[x];
824                         }
825                         else
826                         {
827                                 return null;
828                         }
829                 }
830                 else
831                 {
832                         return jQuery('tr:eq('+y+')>td:eq('+x+')', _nBody )[0];
833                 }
834         }
835         
836         
837         /*
838          * Function: _fnCoordsFromCell
839          * Purpose:  Calculate the x and y position in a table from a TD cell
840          * Returns:  array[2] int: [x, y]
841          * Inputs:   node:n - TD cell of interest
842          * Notes:    Not actually interested in this for DataTables since it might go out of date
843          */
844         function _fnCoordsFromCell( n )
845         {
846                 if ( _oDatatable )
847                 {
848                         var oSettings = _oDatatable.fnSettings();
849                         return [
850                                 jQuery('td', n.parentNode).index(n),
851                                 jQuery('tr', n.parentNode.parentNode).index(n.parentNode) + oSettings._iDisplayStart
852                         ];
853                 }
854                 else
855                 {
856                         return [
857                                 jQuery('td', n.parentNode).index(n),
858                                 jQuery('tr', n.parentNode.parentNode).index(n.parentNode)
859                         ];
860                 }
861         }
862         
863         
864         /*
865          * Function: _fnSetScrollTop
866          * Purpose:  Set the vertical scrolling position
867          * Returns:  -
868          * Inputs:   int:iPos - scrolltop
869          * Notes:    This is so nasty, but without browser detection you can't tell which you should set
870          *           So on browsers that support both, the scroll top will be set twice. I can live with
871          *           that :-)
872          */
873         function _fnSetScrollTop( iPos )
874         {
875                 document.documentElement.scrollTop = iPos;
876                 document.body.scrollTop = iPos;
877         }
878         
879         
880         /*
881          * Function: _fnSetScrollLeft
882          * Purpose:  Set the horizontal scrolling position
883          * Returns:  -
884          * Inputs:   int:iPos - scrollleft
885          */
886         function _fnSetScrollLeft( iPos )
887         {
888                 document.documentElement.scrollLeft = iPos;
889                 document.body.scrollLeft = iPos;
890         }
891         
892         
893         /*
894          * Function: _fnGetPos
895          * Purpose:  Get the position of an object on the rendered page
896          * Returns:  array[2] int: [left, right]
897          * Inputs:   node:obj - element of interest
898          */
899         function _fnGetPos ( obj )
900         {
901                 var iLeft = 0;
902                 var iTop = 0;
903                 
904                 if (obj.offsetParent) 
905                 {
906                         iLeft = obj.offsetLeft;
907                         iTop = obj.offsetTop;
908                         obj = obj.offsetParent;
909                         while (obj) 
910                         {
911                                 iLeft += obj.offsetLeft;
912                                 iTop += obj.offsetTop;
913                                 obj = obj.offsetParent;
914                         }
915                 }
916                 return [iLeft,iTop];
917         }
918         
919         
920         /*
921          * Function: _fnFindDtCell
922          * Purpose:  Get the coords. of a cell from the DataTables internal information
923          * Returns:  array[2] int: [x, y] coords. or null if not found
924          * Inputs:   node:nTarget - the node of interest
925          */
926         function _fnFindDtCell( nTarget )
927         {
928                 var oSettings = _oDatatable.fnSettings();
929                 for ( var i=0, iLen=oSettings.aiDisplay.length ; i<iLen ; i++ )
930                 {
931                         var nTr = oSettings.aoData[ oSettings.aiDisplay[i] ].nTr;
932                         var nTds = nTr.getElementsByTagName('td');
933                         for ( var j=0, jLen=nTds.length ; j<jLen ; j++ )
934                         {
935                                 if ( nTds[j] == nTarget )
936                                 {
937                                         return [ j, i ];
938                                 }
939                         }
940                 }
941                 return null;
942         }
943         
944         
945         
946         /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
947          * Initialisation
948          */
949         
950         /*
951          * Function: _fnInit
952          * Purpose:  Initialise the KeyTable
953          * Returns:  -
954          * Inputs:   object:oInit - optional - Initalisation object with the following parameters:
955          *   array[2] int:focus - x and y coordinates of the initial target
956          *     or
957          *     node:focus - the node to set initial focus on
958          *   node:table - the table to use, if not given, first table with class 'KeyTable' will be used
959          *   string:focusClass - focusing class to give to table elements
960          *           object:that - focus
961          *   bool:initScroll - scroll the view port on load, default true
962          *   int:tabIndex - the tab index to give the hidden input element
963          */
964         function _fnInit( oInit, that )
965         {
966                 /* Save scope */
967                 _that = that;
968                 
969                 /* Capture undefined initialisation and apply the defaults */
970                 if ( typeof oInit == 'undefined' ) {
971                         oInit = {};
972                 }
973                 
974                 if ( typeof oInit.focus == 'undefined' ) {
975                         oInit.focus = [0,0];
976                 }
977                 
978                 if ( typeof oInit.table == 'undefined' ) {
979                         oInit.table = jQuery('table.KeyTable')[0];
980                 } else {
981                         $(oInit.table).addClass('KeyTable');
982                 }
983                 
984                 if ( typeof oInit.focusClass != 'undefined' ) {
985                         _sFocusClass = oInit.focusClass;
986                 }
987                 
988                 if ( typeof oInit.datatable != 'undefined' ) {
989                         _oDatatable = oInit.datatable;
990                 }
991                 
992                 if ( typeof oInit.initScroll == 'undefined' ) {
993                         oInit.initScroll = true;
994                 }
995                 
996                 if ( typeof oInit.form == 'undefined' ) {
997                         oInit.form = false;
998                 }
999                 _bForm = oInit.form;
1000                 
1001                 /* Cache the tbody node of interest */
1002                 _nBody = oInit.table.getElementsByTagName('tbody')[0];
1003                 
1004                 /* If the table is inside a form, then we need a hidden input box which can be used by the
1005                  * browser to catch the browser tabbing for our table
1006                  */
1007                 if ( _bForm )
1008                 {
1009                         var nDiv = document.createElement('div');
1010                         _nInput = document.createElement('input');
1011                         nDiv.style.height = "1px"; /* Opera requires a little something */
1012                         nDiv.style.width = "0px";
1013                         nDiv.style.overflow = "hidden";
1014                         if ( typeof oInit.tabIndex != 'undefined' )
1015                         {
1016                                 _nInput.tabIndex = oInit.tabIndex;
1017                         }
1018                         nDiv.appendChild(_nInput);
1019                         oInit.table.parentNode.insertBefore( nDiv, oInit.table.nextSibling );
1020                         
1021                         jQuery(_nInput).focus( function () {
1022                                 /* See if we want to 'tab into' the table or out */
1023                                 if ( !_bInputFocused )
1024                                 {
1025                                         _bKeyCapture = true;
1026                                         _bInputFocused = false;
1027                                         if ( typeof oInit.focus.nodeName != "undefined" )
1028                                         {
1029                                                 _fnSetFocus( oInit.focus, oInit.initScroll );
1030                                         }
1031                                         else
1032                                         {
1033                                                 _fnSetFocus( _fnCellFromCoords( oInit.focus[0], oInit.focus[1]), oInit.initScroll );
1034                                         }
1035                                         
1036                                         /* Need to interup the thread for this to work */
1037                                         setTimeout( function() { _nInput.blur(); }, 0 );
1038                                 }
1039                         } );
1040                         _bKeyCapture = false;
1041                 }
1042                 else
1043                 {
1044                         /* Set the initial focus on the table */
1045                         if ( typeof oInit.focus.nodeName != "undefined" )
1046                         {
1047                                 _fnSetFocus( oInit.focus, oInit.initScroll );
1048                         }
1049                         else
1050                         {
1051                                 _fnSetFocus( _fnCellFromCoords( oInit.focus[0], oInit.focus[1]), oInit.initScroll );
1052                         }
1053                         _fnCaptureKeys();
1054                 }
1055                 
1056                 /*
1057                  * Add event listeners
1058                  * Well - I hate myself for doing this, but it would appear that key events in browsers are
1059                  * a complete mess, particulay when you consider arrow keys, which of course are one of the
1060                  * main areas of interest here. So basically for arrow keys, there is no keypress event in
1061                  * Safari and IE, while there is in Firefox and Opera. But Firefox and Opera don't repeat the
1062                  * keydown event for an arrow key. OUCH. See the following two articles for more:
1063                  *   http://www.quirksmode.org/dom/events/keys.html
1064                  *   https://lists.webkit.org/pipermail/webkit-dev/2007-December/002992.html
1065                  *   http://unixpapa.com/js/key.html
1066                  * PPK considers the IE / Safari method correct (good enough for me!) so we (urgh) detect
1067                  * Mozilla and Opera and apply keypress for them, while everything else gets keydown. If
1068                  * Mozilla or Opera change their implemention in future, this will need to be updated... 
1069                  * although at the time of writing (14th March 2009) Minefield still uses the 3.0 behaviour.
1070                  */
1071                 if ( jQuery.browser.mozilla || jQuery.browser.opera )
1072                 {
1073                         jQuery(document).bind( "keypress", _fnKey );
1074                 }
1075                 else
1076                 {
1077                         jQuery(document).bind( "keydown", _fnKey );
1078                 }
1079                 
1080                 if ( _oDatatable )
1081                 {
1082                         jQuery('tbody td', _oDatatable.fnSettings().nTable).live( 'click', _fnClick );
1083                 }
1084                 else
1085                 {
1086                         jQuery('td', _nBody).live( 'click', _fnClick );
1087                 }
1088                 
1089                 /* Loose table focus when click outside the table */
1090                 jQuery(document).click( function(e) {
1091                         var nTarget = e.target;
1092                         var bTableClick = false;
1093                         while ( nTarget )
1094                         {
1095                                 if ( nTarget == oInit.table )
1096                                 {
1097                                         bTableClick = true;
1098                                         break;
1099                                 }
1100                                 nTarget = nTarget.parentNode;
1101                         }
1102                         if ( !bTableClick )
1103                         {
1104                                 _fnBlur();
1105                         }
1106                 } );
1107         }
1108         
1109         /* Initialise our new object */
1110         _fnInit( oInit, this );
1111 }