Free cookie consent management tool by TermsFeed Policy Generator

source: branches/WebApplication/MVC2/HeuristicLabWeb.PluginHost/HLWebPluginHost/Content/jQuery/jQueryPlugins/DataTables-1.7.6/extras/KeyTable/js/KeyTable.js @ 6286

Last change on this file since 6286 was 6286, checked in by dkahn, 13 years ago

#1198 Added jQuery plus plugins

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