Mercurial > repos > saskia-hiltemann > ireport
comparison DataTables-1.9.4/extras/KeyTable/js/KeyTable.js @ 0:ac5f9272033b draft
first upload
author | saskia-hiltemann |
---|---|
date | Tue, 01 Jul 2014 11:42:23 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:ac5f9272033b |
---|---|
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 } |