Mercurial > repos > saskia-hiltemann > ireport_devel
comparison DataTables-1.9.4/extras/KeyTable/js/KeyTable.js @ 4:363cd315d0e9 draft
Uploaded
| author | saskia-hiltemann |
|---|---|
| date | Mon, 16 Nov 2015 09:46:51 -0500 |
| parents | 3c160414da2e |
| children |
comparison
equal
deleted
inserted
replaced
| 3:945e72d0e723 | 4:363cd315d0e9 |
|---|---|
| 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 } |
