| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 goog.provide('cvox.AxsNav'); |
| 6 |
| 7 goog.require('cvox.CustomWalker'); |
| 8 |
| 9 /** |
| 10 * @fileoverview AxsNav - JavaScript library for enhancing the navigation |
| 11 * of content on web pages. |
| 12 */ |
| 13 |
| 14 /** |
| 15 * Class for managing navigation of website content |
| 16 * |
| 17 * @param {Object} |
| 18 * axsJAXObj An instance of an AxsJAX object. |
| 19 * @constructor |
| 20 */ |
| 21 cvox.AxsNav = function(axsJAXObj) { |
| 22 this.nextListKeys = ''; |
| 23 this.prevListKeys = ''; |
| 24 this.navArray = new Array(); |
| 25 this.navListIdx = 0; |
| 26 this.navItemIdxs = new Array(); |
| 27 this.lastItem = null; |
| 28 this.targetsArray = new Array(); |
| 29 this.topCharCodeMap = new Object(); |
| 30 this.topKeyCodeMap = new Object(); |
| 31 this.charCodeMaps = new Array(); |
| 32 this.keyCodeMaps = new Array(); |
| 33 this.axs_ = axsJAXObj; |
| 34 this.lens_ = null; |
| 35 this.pk_ = null; |
| 36 this.snd_ = null; |
| 37 this.LIST_SND = 'list'; |
| 38 this.ITEM_SND = 'item'; |
| 39 this.WRAP_SND = 'wrap'; |
| 40 this.keyHandler = null; |
| 41 this.managedCompletionListName = null; |
| 42 this.titleToEarconMap = new Object(); |
| 43 this.titleToEarconMap[cvox.AxsNav.str.CATEGORY_EARCONS_LIST] = this.LIST_SND; |
| 44 this.titleToEarconMap[cvox.AxsNav.str.CATEGORY_EARCONS_ITEM] = this.ITEM_SND; |
| 45 this.titleToEarconMap[cvox.AxsNav.str.CATEGORY_EARCONS_WRAP] = this.WRAP_SND; |
| 46 }; |
| 47 |
| 48 /** |
| 49 * Object that contains all string literals used by cvox.AxsNav. |
| 50 * |
| 51 * @type {Object} |
| 52 */ |
| 53 cvox.AxsNav.str = { |
| 54 NEXT_LIST: 'Next list', |
| 55 PREV_LIST: 'Previous list', |
| 56 FORWARD_ITEM: 'Forward item', |
| 57 BACKWARDS_ITEM: 'Backwards item', |
| 58 CYCLE_NEXT_ITEM: 'Cycle next item', |
| 59 CYCLE_PREV_ITEM: 'Cycle previous item', |
| 60 SELECT_ACTION: 'Select available action', |
| 61 NO_AVAILABLE_ACTION: 'No available actions', |
| 62 PGUP: 'Page up', |
| 63 PGDOWN: 'Page down', |
| 64 ENTER: 'Enter', |
| 65 ESC: 'Escape', |
| 66 DEL: 'Delete', |
| 67 UP: 'Up', |
| 68 DOWN: 'Down', |
| 69 LEFT: 'Left', |
| 70 RIGHT: 'Right', |
| 71 OR: 'or', |
| 72 CATEGORY_NAVIGATION_ACTIONS: 'Navigation actions', |
| 73 CATEGORY_APPLICATION_ACTIONS: 'Application actions', |
| 74 CATEGORY_EARCONS: 'Earcons', |
| 75 CATEGORY_EARCONS_LIST: 'List selected', |
| 76 CATEGORY_EARCONS_ITEM: 'Item selected', |
| 77 CATEGORY_EARCONS_WRAP: 'List wrap', |
| 78 CATEGORY_HELP_INSTRUCTIONS: 'Help for Javascript enhanced Web content. ' + |
| 79 'Use arrows to explore.', |
| 80 CATEGORY_HELP_INSTRUCTIONS_EXPLORE_HELP: 'Use the left and right ' + |
| 81 'arrows to explore the help categories and the down and up arrows ' + |
| 82 'to explore the current category.', |
| 83 CATEGORY_HELP_INSTRUCTIONS_STRUCTURE: 'The page content is divided ' + |
| 84 'into lists with similar items. A user can switch the current list ' + |
| 85 'and traverse it.', |
| 86 CATEGORY_HELP_INSTRUCTIONS_SHORTCUTS: 'There are global shotcuts that ' + |
| 87 'can be triggered from any list and local shortcuts that can be ' + |
| 88 'triggered only from the current list.', |
| 89 CATEGORY_HELP_INSTRUCTIONS_SELECTOR: 'Press dot anytime to show an ' + |
| 90 'available actions selector.' |
| 91 }; |
| 92 |
| 93 /** |
| 94 * Set the PowerKey object used for displaying the valid actions in a given |
| 95 * context. The PowerKey auto completion input element is invoked via a |
| 96 * shortcutKey. |
| 97 * |
| 98 * @param {Object} |
| 99 * powerKeyObj A PowerKey object. |
| 100 * @param {string} |
| 101 * shortcutKey A key for invoking PowerKey. |
| 102 */ |
| 103 cvox.AxsNav.prototype.setPowerKey = function(powerKeyObj, shortcutKey) { |
| 104 if (shortcutKey) { |
| 105 this.pk_ = powerKeyObj; |
| 106 // Initialize the PowerKey object if it has not been initialized yet |
| 107 if (this.pk_.cmpTextElement === null) { |
| 108 this.defaultInitPowerKeyObj(); |
| 109 } |
| 110 var self = this; |
| 111 var keyArray = new Array(shortcutKey); |
| 112 this.assignKeysToMethod(keyArray, this.topCharCodeMap, |
| 113 this.topKeyCodeMap, function() { |
| 114 self.showAvailableActionsSelector(); |
| 115 }); |
| 116 } |
| 117 }; |
| 118 |
| 119 /** |
| 120 * Returns whether the specified navigation list is valid. If the navigation |
| 121 * list is dynamic and appears to not be valid, this function will try to reload |
| 122 * it and check whether or not it becomes valid. |
| 123 * |
| 124 * @param {Object} |
| 125 * navList The specified list object to check. |
| 126 * @return {boolean} Whether the specified list object is valid. |
| 127 */ |
| 128 cvox.AxsNav.prototype.validateList = function(navList) { |
| 129 var valid = true; |
| 130 // Reload dynamic lists |
| 131 if ((navList.type == 'dynamic') && (navList.items.length === 0)) { |
| 132 // Clear the lens to avoid its contents interfering with the xpath |
| 133 if (this.lens_ !== null) { |
| 134 this.lens_.view(null); |
| 135 } |
| 136 navList.items = this.makeItemsArray(navList.origListObj); |
| 137 navList.targets = this.makeTargetsArray(navList.origListObj); |
| 138 // Reset the nav index of the list being validated. |
| 139 // Most of the time, the list being validated is the same |
| 140 // as the current list. |
| 141 if (this.navArray[this.navListIdx] === navList) { |
| 142 this.navItemIdxs[this.navListIdx] = -1; |
| 143 } else { |
| 144 for (var i = 0, tempList; tempList = this.navArray[i]; i++) { |
| 145 if (tempList === navList) { |
| 146 this.navItemIdxs[i] = -1; |
| 147 break; |
| 148 } |
| 149 } |
| 150 } |
| 151 } |
| 152 if ((navList.items.length === 0) && (navList.entryTarget === null)) { |
| 153 valid = false; |
| 154 } |
| 155 return valid; |
| 156 }; |
| 157 |
| 158 /** |
| 159 * Performs the specified action with a list is switched into. |
| 160 */ |
| 161 cvox.AxsNav.prototype.doListEntryActions = function() { |
| 162 var currentList = this.currentList(); |
| 163 var target = currentList.entryTarget; |
| 164 var func = null; |
| 165 if (target !== null) { |
| 166 this.actOnTarget(target); |
| 167 func = this.getCallbackFunction(target.action); |
| 168 } |
| 169 if (func === null) { |
| 170 this.announceCurrentList(); |
| 171 if (this.snd_ !== null) { |
| 172 this.snd_.play(this.LIST_SND); |
| 173 } |
| 174 } |
| 175 }; |
| 176 |
| 177 /** |
| 178 * Goes to the next navigation list and returns it |
| 179 * |
| 180 * @return {Object?} The next navigation list. |
| 181 */ |
| 182 cvox.AxsNav.prototype.nextList = function() { |
| 183 if (this.navArray.length < 1) { |
| 184 return null; |
| 185 } |
| 186 // Find the next list with items |
| 187 for (var i = 0, list; list = this.navArray[i]; i++) { |
| 188 this.navListIdx++; |
| 189 if (this.navListIdx >= this.navArray.length) { |
| 190 this.navListIdx = 0; |
| 191 } |
| 192 if (this.validateList(this.navArray[this.navListIdx])) { |
| 193 break; |
| 194 } |
| 195 } |
| 196 return this.currentList(); |
| 197 }; |
| 198 |
| 199 /** |
| 200 * Goes to the previous navigation list and returns it |
| 201 * |
| 202 * @return {Object?} The previous navigation list. |
| 203 */ |
| 204 cvox.AxsNav.prototype.prevList = function() { |
| 205 if (this.navArray.length < 1) { |
| 206 return null; |
| 207 } |
| 208 // Find the next list with item |
| 209 for (var i = 0, list; list = this.navArray[i]; i++) { |
| 210 this.navListIdx--; |
| 211 if (this.navListIdx < 0) { |
| 212 this.navListIdx = this.navArray.length - 1; |
| 213 } |
| 214 if (this.validateList(this.navArray[this.navListIdx])) { |
| 215 break; |
| 216 } |
| 217 } |
| 218 return this.currentList(); |
| 219 }; |
| 220 |
| 221 /** |
| 222 * Returns the current navigation list. |
| 223 * |
| 224 * @return {Object} The current navigation list. |
| 225 */ |
| 226 cvox.AxsNav.prototype.currentList = function() { |
| 227 return this.navArray[this.navListIdx]; |
| 228 }; |
| 229 |
| 230 /** |
| 231 * Speaks the title of the current list |
| 232 */ |
| 233 cvox.AxsNav.prototype.announceCurrentList = function() { |
| 234 this.axs_.speakTextViaNode(this.currentList().title); |
| 235 }; |
| 236 |
| 237 /** |
| 238 * Goes to the next item and returns it; if there is no next item, this will |
| 239 * wrap to the first item in the list. |
| 240 * |
| 241 * @return {Object?} The next item. Use item.elem to get at the DOM node. |
| 242 */ |
| 243 cvox.AxsNav.prototype.nextItem = function() { |
| 244 if (this.navArray.length < 1) { |
| 245 return null; |
| 246 } |
| 247 var currentList = this.navArray[this.navListIdx]; |
| 248 var items = currentList.items; |
| 249 if (items.length < 1) { |
| 250 return null; |
| 251 } |
| 252 if (this.lastItem) { |
| 253 var currentListId = this.getListId(currentList); |
| 254 var syncedIndex = this.lastItem.elem.AxsNavInfo[currentListId]; |
| 255 if (typeof (syncedIndex) != 'undefined') { |
| 256 this.navItemIdxs[this.navListIdx] = syncedIndex; |
| 257 } |
| 258 } |
| 259 this.navItemIdxs[this.navListIdx]++; |
| 260 var looped = false; |
| 261 if (this.navItemIdxs[this.navListIdx] >= items.length) { |
| 262 this.navItemIdxs[this.navListIdx] = 0; |
| 263 looped = true; |
| 264 } |
| 265 var itemIndex = this.navItemIdxs[this.navListIdx]; |
| 266 // Perform a validity check to determine if the xpaths should be |
| 267 // re-evaluated |
| 268 if (items[itemIndex].elem.parentNode === null) { |
| 269 // Clear the lens to avoid its contents interfering with the xpath |
| 270 if (this.lens_ !== null) { |
| 271 this.lens_.view(null); |
| 272 } |
| 273 currentList.items = this.makeItemsArray(currentList.origListObj); |
| 274 this.navItemIdxs[this.navListIdx] = 0; |
| 275 itemIndex = this.navItemIdxs[this.navListIdx]; |
| 276 } |
| 277 this.lastItem = items[itemIndex]; |
| 278 if (this.snd_ !== null) { |
| 279 if (looped) { |
| 280 this.snd_.play(this.WRAP_SND); |
| 281 } else { |
| 282 this.snd_.play(this.ITEM_SND); |
| 283 } |
| 284 } |
| 285 return this.lastItem; |
| 286 }; |
| 287 |
| 288 /** |
| 289 * Goes to the next item and returns it; if this causes wrapping and there is a |
| 290 * tailTarget on the list, then this will act on that target and return null |
| 291 * instead. |
| 292 * |
| 293 * @return {Object?} The next item. Use item.elem to get at the DOM node. |
| 294 */ |
| 295 cvox.AxsNav.prototype.fwdItem = function() { |
| 296 var list = this.navArray[this.navListIdx]; |
| 297 var index = this.navItemIdxs[this.navListIdx]; |
| 298 if ((list.tailTarget !== null) && (index + 1 >= list.items.length)) { |
| 299 this.actOnTarget(list.tailTarget); |
| 300 this.navItemIdxs[this.navListIdx] = 0; |
| 301 return null; |
| 302 } |
| 303 var item = this.nextItem(); |
| 304 return item; |
| 305 }; |
| 306 |
| 307 /** |
| 308 * Goes to the previous item and returns it; if there is no previous item, this |
| 309 * will wrap to the last item in the list. |
| 310 * |
| 311 * @return {Object?} The previous item. Use item.elem to get at the DOM node. |
| 312 */ |
| 313 cvox.AxsNav.prototype.prevItem = function() { |
| 314 if (this.navArray.length < 1) { |
| 315 return null; |
| 316 } |
| 317 var currentList = this.navArray[this.navListIdx]; |
| 318 var items = currentList.items; |
| 319 if (items.length < 1) { |
| 320 return null; |
| 321 } |
| 322 if (this.lastItem) { |
| 323 var currentListId = this.getListId(currentList); |
| 324 var syncedIndex = this.lastItem.elem.AxsNavInfo[currentListId]; |
| 325 if (typeof (syncedIndex) != 'undefined') { |
| 326 this.navItemIdxs[this.navListIdx] = syncedIndex; |
| 327 } |
| 328 } |
| 329 this.navItemIdxs[this.navListIdx]--; |
| 330 var looped = false; |
| 331 if (this.navItemIdxs[this.navListIdx] < 0) { |
| 332 this.navItemIdxs[this.navListIdx] = items.length - 1; |
| 333 looped = true; |
| 334 } |
| 335 var itemIndex = this.navItemIdxs[this.navListIdx]; |
| 336 // Perform a validity check to determine if the xpaths should be |
| 337 // re-evaluated |
| 338 if (items[itemIndex].elem.parentNode === null) { |
| 339 // Clear the lens to avoid its contents interfering with the xpath |
| 340 if (this.lens_ !== null) { |
| 341 this.lens_.view(null); |
| 342 } |
| 343 currentList.items = this.makeItemsArray(currentList.origListObj); |
| 344 this.navItemIdxs[this.navListIdx] = currentList.items.length; |
| 345 itemIndex = this.navItemIdxs[this.navListIdx]; |
| 346 } |
| 347 this.lastItem = items[itemIndex]; |
| 348 if (this.snd_ !== null) { |
| 349 if (looped) { |
| 350 this.snd_.play(this.WRAP_SND); |
| 351 } else { |
| 352 this.snd_.play(this.ITEM_SND); |
| 353 } |
| 354 } |
| 355 return this.lastItem; |
| 356 }; |
| 357 |
| 358 /** |
| 359 * Goes to the previous item and returns it; if this causes wrapping and there |
| 360 * is a headTarget on the list, then this will act on that target and return |
| 361 * null instead. |
| 362 * |
| 363 * @return {Object?} The previous item. Use item.elem to get at the DOM node. |
| 364 */ |
| 365 cvox.AxsNav.prototype.backItem = function() { |
| 366 var list = this.navArray[this.navListIdx]; |
| 367 var index = this.navItemIdxs[this.navListIdx]; |
| 368 if ((list.headTarget !== null) && (index <= 0)) { |
| 369 this.actOnTarget(list.headTarget); |
| 370 this.navItemIdxs[this.navListIdx] = list.items.length - 1; |
| 371 return null; |
| 372 } |
| 373 var item = this.prevItem(); |
| 374 return item; |
| 375 }; |
| 376 |
| 377 /** |
| 378 * Returns the current item. |
| 379 * |
| 380 * @return {Object?} The current item. Use item.elem to get at the DOM node. |
| 381 */ |
| 382 cvox.AxsNav.prototype.currentItem = function() { |
| 383 if (this.navArray.length < 1) { |
| 384 return null; |
| 385 } |
| 386 var currentList = this.navArray[this.navListIdx]; |
| 387 if (this.lastItem) { |
| 388 var currentListId = this.getListId(currentList); |
| 389 var syncedIndex = this.lastItem.elem.AxsNavInfo[currentListId]; |
| 390 if (typeof (syncedIndex) != 'undefined') { |
| 391 this.navItemIdxs[this.navListIdx] = syncedIndex; |
| 392 } |
| 393 } |
| 394 var items = currentList.items; |
| 395 var itemIndex = this.navItemIdxs[this.navListIdx]; |
| 396 this.lastItem = items[itemIndex]; |
| 397 return this.lastItem; |
| 398 }; |
| 399 |
| 400 /** |
| 401 * Returns the callback function if the action is a valid callback; returns null |
| 402 * otherwise. |
| 403 * |
| 404 * @param {String?} |
| 405 * actionString The action string for an item or target. |
| 406 * @return {Function?} The callback function if there is a valid one. |
| 407 */ |
| 408 cvox.AxsNav.prototype.getCallbackFunction = function(actionString) { |
| 409 var callbackFunc = null; |
| 410 if ((actionString !== null) && actionString.indexOf && |
| 411 (actionString.indexOf('CALL:') === 0) && |
| 412 (actionString.indexOf('(') === -1)) { |
| 413 try { |
| 414 callbackFunc = /** @type {Function} */ |
| 415 (eval(actionString.substring(5))); |
| 416 } catch (e) { |
| 417 } |
| 418 } |
| 419 return callbackFunc; |
| 420 }; |
| 421 |
| 422 /** |
| 423 * This function will act on the item based on what action was specified in the |
| 424 * Content Navigation Listing. |
| 425 * |
| 426 * @param {Object?} |
| 427 * item The item to act on. Use item.elem to get at the DOM node. |
| 428 */ |
| 429 cvox.AxsNav.prototype.actOnItem = function(item) { |
| 430 if (item !== null) { |
| 431 var self = this; |
| 432 var doAction = function() { |
| 433 var func = self.getCallbackFunction(item.action); |
| 434 if (func) { |
| 435 func(item); |
| 436 } else { |
| 437 if (self.lens_ !== null) { |
| 438 self.lens_.view(item.elem); |
| 439 } |
| 440 self.axs_.goTo(item.elem); |
| 441 } |
| 442 }; |
| 443 // If there is a node that was focused, unfocus it so that |
| 444 // any keys the user presses after using the nav system will not |
| 445 // be sent to the wrong place. |
| 446 if (this.axs_.lastFocusedNode && this.axs_.lastFocusedNode.blur) { |
| 447 var oldNode = this.axs_.lastFocusedNode; |
| 448 // Set the lastFocusedNode to null to prevent AxsJAX's blur handler |
| 449 // from kicking in as that blur handler will conflict with the |
| 450 // temporary blur handler which results in screen readers not |
| 451 // speaking properly due to how the eventing system works. |
| 452 // Because we are not allowing the regular blur handler to work, |
| 453 // we need to make sure that we do the same work of cleaning up. |
| 454 this.axs_.lastFocusedNode = null; |
| 455 if (oldNode.removeAttribute) { |
| 456 this.axs_.removeAttributeOf(oldNode, 'aria-activedescendant'); |
| 457 } |
| 458 // The action needs to be done inside a temporary blur handler |
| 459 // because otherwise, there is a timing issue of when the events |
| 460 // get sent and screen readers won't speak. |
| 461 var tempBlurHandler = function(evt) { |
| 462 evt.target.removeEventListener('blur', tempBlurHandler, true); |
| 463 doAction(); |
| 464 }; |
| 465 oldNode.addEventListener('blur', tempBlurHandler, true); |
| 466 oldNode.blur(); |
| 467 } else { |
| 468 doAction(); |
| 469 } |
| 470 } else { |
| 471 var currentList = this.navArray[this.navListIdx]; |
| 472 if (currentList.type == 'dynamic') { |
| 473 this.axs_.speakTextViaNode(currentList.onEmpty); |
| 474 } |
| 475 } |
| 476 }; |
| 477 |
| 478 /** |
| 479 * This function creates the maps keypresses to a method on a given char and key |
| 480 * map. |
| 481 * |
| 482 * @param {Array} |
| 483 * keyArray Array of keys that will be associated with the method. |
| 484 * @param {Object} |
| 485 * charMap Dictionary that maps character codes to methods. |
| 486 * @param {Object} |
| 487 * keyMap Dictionary that maps key codes to methods. |
| 488 * @param {Function} |
| 489 * method Method to be be associated with the array of keys. |
| 490 */ |
| 491 cvox.AxsNav.prototype.assignKeysToMethod = function(keyArray, charMap, keyMap, |
| 492 method) { |
| 493 for (var i = 0, key; key = keyArray[i]; i++) { |
| 494 if (key == 'LEFT') { |
| 495 keyMap[37] = method; |
| 496 } else if (key == 'UP') { |
| 497 keyMap[38] = method; |
| 498 } else if (key == 'RIGHT') { |
| 499 keyMap[39] = method; |
| 500 } else if (key == 'DOWN') { |
| 501 keyMap[40] = method; |
| 502 } else if (key == 'PGUP') { |
| 503 keyMap[33] = method; |
| 504 } else if (key == 'PGDOWN') { |
| 505 keyMap[34] = method; |
| 506 } else if (key == 'ENTER') { |
| 507 keyMap[13] = method; |
| 508 } else if (key == 'ESC') { |
| 509 keyMap[27] = method; |
| 510 } else if (key == 'DEL') { |
| 511 keyMap[46] = method; |
| 512 } else { |
| 513 charMap[key.charCodeAt(0)] = method; |
| 514 } |
| 515 } |
| 516 }; |
| 517 |
| 518 /** |
| 519 * This function creates the mapping between keypresses and navigation behavior |
| 520 * for item keys. This mapping is only active when the user is in the |
| 521 * corresponding navList. |
| 522 * |
| 523 * @param {string} |
| 524 * keyStr String that indicates the keys to be used. |
| 525 * @param {number} |
| 526 * navListIdx Index of the list that these keypresses are associated |
| 527 * with. |
| 528 * @param {string} |
| 529 * navTaskStr "next","prev","fwd","back". |
| 530 */ |
| 531 cvox.AxsNav.prototype.assignItemKeys = function(keyStr, navListIdx, |
| 532 navTaskStr) { |
| 533 var keys = new Array(); |
| 534 if (keyStr === '') { |
| 535 return; |
| 536 } |
| 537 keys = keyStr.split(' '); |
| 538 var self = this; |
| 539 if (navTaskStr == 'prev') { |
| 540 this.assignKeysToMethod(keys, this.charCodeMaps[navListIdx], |
| 541 this.keyCodeMaps[navListIdx], function() { |
| 542 self.actOnItem(self.prevItem()); |
| 543 }); |
| 544 } else if (navTaskStr == 'next') { |
| 545 this.assignKeysToMethod(keys, this.charCodeMaps[navListIdx], |
| 546 this.keyCodeMaps[navListIdx], function() { |
| 547 self.actOnItem(self.nextItem()); |
| 548 }); |
| 549 } else if (navTaskStr == 'back') { |
| 550 this.assignKeysToMethod(keys, this.charCodeMaps[navListIdx], |
| 551 this.keyCodeMaps[navListIdx], function() { |
| 552 var backItem = self.backItem(); |
| 553 if (backItem !== null) { |
| 554 self.actOnItem(backItem); |
| 555 } |
| 556 }); |
| 557 } else { |
| 558 this.assignKeysToMethod(keys, this.charCodeMaps[navListIdx], |
| 559 this.keyCodeMaps[navListIdx], function() { |
| 560 var fwdItem = self.fwdItem(); |
| 561 if (fwdItem != null) { |
| 562 self.actOnItem(fwdItem); |
| 563 } |
| 564 }); |
| 565 } |
| 566 }; |
| 567 |
| 568 /** |
| 569 * This function creates the mapping between keypresses and navigation behavior |
| 570 * for hotkeys. This mapping is active all the time, regardless of which navList |
| 571 * the user is in. |
| 572 * |
| 573 * @param {string} |
| 574 * keyStr String that indicates the keys to be used. Pressing these |
| 575 * keys will cause the user to jump to the list that the key is |
| 576 * associated with and read the next item there. |
| 577 * @param {number} |
| 578 * navListIdx Index of the list that these keypresses are associated |
| 579 * with. |
| 580 * @param {string} |
| 581 * emptyMsg String to speak to the user if the list is empty. |
| 582 */ |
| 583 cvox.AxsNav.prototype.assignHotKeys = function(keyStr, navListIdx, emptyMsg) { |
| 584 var keys = new Array(); |
| 585 if (keyStr === '') { |
| 586 return; |
| 587 } |
| 588 keys = keyStr.split(' '); |
| 589 var self = this; |
| 590 this.assignKeysToMethod(keys, this.topCharCodeMap, this.topKeyCodeMap, |
| 591 function() { |
| 592 if (!self.validateList(self.navArray[navListIdx])) { |
| 593 self.axs_.speakTextViaNode(emptyMsg); |
| 594 return; |
| 595 } |
| 596 self.navListIdx = navListIdx; |
| 597 self.actOnItem(self.nextItem()); |
| 598 }); |
| 599 }; |
| 600 |
| 601 /** |
| 602 * For all keys that map to lists with no items, those keys should speak some |
| 603 * message to let the user know that the function was called but was |
| 604 * unsuccessful because there is no content. |
| 605 * |
| 606 * @param {string} |
| 607 * keyStr String that indicates the keys to be used. |
| 608 * |
| 609 * @param {string} |
| 610 * emptyMsg The message that should be spoken when the user presses |
| 611 * the key(s) to let them know that there is no content. |
| 612 */ |
| 613 cvox.AxsNav.prototype.assignEmptyMsgKeys = function(keyStr, emptyMsg) { |
| 614 var keys = new Array(); |
| 615 if (keyStr === '') { |
| 616 return; |
| 617 } |
| 618 keys = keyStr.split(' '); |
| 619 var self = this; |
| 620 this.assignKeysToMethod(keys, this.topCharCodeMap, this.topKeyCodeMap, |
| 621 function() { |
| 622 self.axs_.speakTextViaNode(emptyMsg); |
| 623 }); |
| 624 |
| 625 }; |
| 626 |
| 627 /** |
| 628 * This function creates the mapping between keypresses and target nodes. This |
| 629 * mapping is active all the time, regardless of which navList the user is in. |
| 630 * |
| 631 * @param {Object} |
| 632 * target Target object created from the <target> element. |
| 633 * @param {Object} |
| 634 * charMap Dictionary that maps character codes to methods. |
| 635 * @param {Object} |
| 636 * keyMap Dictionary that maps key codes to methods. |
| 637 */ |
| 638 cvox.AxsNav.prototype.assignTargetKeys = function(target, charMap, keyMap) { |
| 639 var keys = new Array(); |
| 640 if (target.hotkey === '') { |
| 641 return; |
| 642 } |
| 643 keys = target.hotkey.split(' '); |
| 644 var self = this; |
| 645 this.assignKeysToMethod(keys, charMap, keyMap, function() { |
| 646 self.actOnTarget(target); |
| 647 }); |
| 648 }; |
| 649 |
| 650 /** |
| 651 * This function will act on the target specified. |
| 652 * |
| 653 * @param {?Object} |
| 654 * target The target to act on. |
| 655 */ |
| 656 cvox.AxsNav.prototype.actOnTarget = function(target) { |
| 657 if (target.action == 'click') { |
| 658 this.clickOnTarget(target); |
| 659 } else { |
| 660 this.callActionOnTarget(target); |
| 661 } |
| 662 }; |
| 663 |
| 664 /** |
| 665 * Calls the appropriate action on a target. |
| 666 * |
| 667 * @param {Object} |
| 668 * target the target to act upon. |
| 669 */ |
| 670 cvox.AxsNav.prototype.callActionOnTarget = function(target) { |
| 671 var elems = this.getElementsForTarget(target); |
| 672 if (elems.length < 1) { |
| 673 this.axs_.speakTextViaNode(target.onEmpty); |
| 674 } else { |
| 675 var func = this.getCallbackFunction(target.action); |
| 676 if (func) { |
| 677 var item = new Object(); |
| 678 item.action = target.action; |
| 679 item.elem = elems[0]; |
| 680 func(item); |
| 681 this.axs_.markPosition(elems[0]); |
| 682 } else { |
| 683 throw new Error('Invalid action: ' + target.action); |
| 684 } |
| 685 } |
| 686 }; |
| 687 |
| 688 /** |
| 689 * Performs the click action of a target. |
| 690 * |
| 691 * @param {Object} |
| 692 * target the target to act upon. |
| 693 */ |
| 694 cvox.AxsNav.prototype.clickOnTarget = function(target) { |
| 695 var elems = this.getElementsForTarget(target); |
| 696 if (elems.length < 1) { |
| 697 this.axs_.speakTextViaNode(target.onEmpty); |
| 698 } else { |
| 699 this.axs_.clickElem(elems[0], false); |
| 700 elems[0].scrollIntoView(true); |
| 701 this.axs_.markPosition(elems[0]); |
| 702 } |
| 703 }; |
| 704 |
| 705 /** |
| 706 * Returns the elements referenced by a target |
| 707 * |
| 708 * @param {?Object} |
| 709 * target a target element. |
| 710 * |
| 711 * @return {Array} an array of elements. |
| 712 */ |
| 713 cvox.AxsNav.prototype.getElementsForTarget = function(target) { |
| 714 var xpath = target.xpath; |
| 715 var rootNode = this.axs_.getActiveDocument().documentElement; |
| 716 if (xpath.indexOf('.') === 0) { |
| 717 rootNode = this.currentItem().elem; |
| 718 } |
| 719 return this.axs_.evalXPath(xpath, rootNode); |
| 720 }; |
| 721 |
| 722 /** |
| 723 * Returns a function mapped to a key. |
| 724 * |
| 725 * @param {number} |
| 726 * keyCode A key code. |
| 727 * @param {number} |
| 728 * charCode A char code. |
| 729 * @return {Function?} A function mapped to the keyCode or charCode, undefined |
| 730 * if the mapping does not exist. |
| 731 */ |
| 732 cvox.AxsNav.prototype.getFunctionForKey = function(keyCode, charCode) { |
| 733 var command = null; |
| 734 var idx = this.navListIdx; |
| 735 if (idx < this.keyCodeMaps.length) { |
| 736 command = this.keyCodeMaps[idx][keyCode] || |
| 737 this.charCodeMaps[idx][charCode] || null; |
| 738 } |
| 739 if (command === null) { |
| 740 command = this.topKeyCodeMap[keyCode] || this.topCharCodeMap[charCode]; |
| 741 } |
| 742 return command; |
| 743 }; |
| 744 |
| 745 /** |
| 746 * Builds up the navigation system of lists of items. This system uses the idea |
| 747 * of multiple cursors and the visitor pattern. |
| 748 * |
| 749 * @param {string} |
| 750 * cnrString An XML string that contains the information needed to |
| 751 * build up the content navigation rule. |
| 752 * |
| 753 * @notypecheck {Function?} opt_customNavMethod. |
| 754 * |
| 755 * @param {Function?} |
| 756 * opt_customNavMethod A custom navigation method provided by the |
| 757 * caller. This navigation method will be given the DOM created from |
| 758 * the cnrString, the navigation array of lists of items, an array of |
| 759 * all the lists which had zero items, and an an array of targets. If |
| 760 * this is null, the default AxsJAX nav handler will be used. |
| 761 */ |
| 762 cvox.AxsNav.prototype.navInit = function(cnrString, opt_customNavMethod) { |
| 763 var cnrJson = new Object(); |
| 764 cnrJson.lists = new Array(); |
| 765 |
| 766 var parser = new DOMParser(); |
| 767 var cnrDOM = parser.parseFromString(cnrString, 'text/xml'); |
| 768 |
| 769 // Build up the navigation lists |
| 770 var lists = cnrDOM.getElementsByTagName('list'); |
| 771 |
| 772 var i; |
| 773 var listNode; |
| 774 for (i = 0, listNode; listNode = lists[i]; i++) { |
| 775 var navList = new Object(); |
| 776 navList.title = listNode.getAttribute('title'); |
| 777 navList.hotkey = listNode.getAttribute('hotkey'); |
| 778 navList.next = listNode.getAttribute('next'); |
| 779 navList.prev = listNode.getAttribute('prev'); |
| 780 navList.fwd = listNode.getAttribute('fwd'); |
| 781 navList.back = listNode.getAttribute('back'); |
| 782 navList.onEmpty = listNode.getAttribute('onEmpty'); |
| 783 navList.type = listNode.getAttribute('type'); |
| 784 |
| 785 var j; |
| 786 var entry; |
| 787 var k; |
| 788 var attributes; |
| 789 var length; |
| 790 // Parse items to JSON |
| 791 navList.items = new Array(); |
| 792 var cnrItems = listNode.getElementsByTagName('item'); |
| 793 for (j = 0; entry = cnrItems[j]; j++) { |
| 794 var item = new Object(); |
| 795 item.xpath = entry.textContent; |
| 796 if (entry.attributes instanceof NamedNodeMap) { |
| 797 attributes = entry.attributes; |
| 798 length = attributes.length; |
| 799 for (k = 0; k < length; k++) { |
| 800 var attrib = attributes.item(k); |
| 801 item[attrib.nodeName] = attrib.value; |
| 802 } |
| 803 } |
| 804 navList.items.push(item); |
| 805 } |
| 806 // Parse targets to JSON |
| 807 navList.targets = new Array(); |
| 808 var cnrTargets = listNode.getElementsByTagName('target'); |
| 809 for (j = 0; entry = cnrTargets[j]; j++) { |
| 810 var target = new Object(); |
| 811 target.xpath = entry.textContent; |
| 812 if (entry.attributes instanceof NamedNodeMap) { |
| 813 attributes = entry.attributes; |
| 814 length = attributes.length; |
| 815 for (k = 0; k < length; k++) { |
| 816 var attrib = attributes.item(k); |
| 817 target[attrib.nodeName] = attrib.value; |
| 818 } |
| 819 } |
| 820 navList.targets.push(target); |
| 821 } |
| 822 cnrJson.lists.push(navList); |
| 823 } |
| 824 |
| 825 // Build up the targets |
| 826 cnrJson.targets = new Array(); |
| 827 var currentNode; |
| 828 var cnrNode = cnrDOM.firstChild; |
| 829 for (i = 0, currentNode; currentNode = cnrNode.childNodes[i]; i++) { |
| 830 if (currentNode.tagName == 'target') { |
| 831 var target = new Object(); |
| 832 target.xpath = currentNode.textContent; |
| 833 if (currentNode.attributes instanceof NamedNodeMap) { |
| 834 attributes = currentNode.attributes; |
| 835 length = attributes.length; |
| 836 for (k = 0; k < length; k++) { |
| 837 var attrib = attributes.item(k); |
| 838 target[attrib.nodeName] = attrib.value; |
| 839 } |
| 840 } |
| 841 cnrJson.targets.push(target); |
| 842 } |
| 843 } |
| 844 |
| 845 // Get the next/prev list keys |
| 846 cnrJson.next = cnrNode.getAttribute('next'); |
| 847 cnrJson.prev = cnrNode.getAttribute('prev'); |
| 848 |
| 849 if ((opt_customNavMethod === null) || |
| 850 (typeof (opt_customNavMethod) == 'undefined')) { |
| 851 this.navInitJson(cnrJson, opt_customNavMethod); |
| 852 } else { |
| 853 // Wrapper function that will invoke the user's opt_customNavMethod |
| 854 // This will be called when navInitJson is done processing |
| 855 var func = new function(dummyJson, navArray, emptyLists, targetsArray) { |
| 856 opt_customNavMethod(cnrNode, navArray, emptyLists, targetsArray); |
| 857 }; |
| 858 this.navInitJson(cnrJson, func); |
| 859 } |
| 860 }; |
| 861 |
| 862 /** |
| 863 * Generates a help string for the globally available keys. Keys which are |
| 864 * specific to the current list are NOT included. |
| 865 * |
| 866 * @return {string} The help string for globally available keys. |
| 867 */ |
| 868 cvox.AxsNav.prototype.globalHelpString = function() { |
| 869 var globalActions = this.getGlobalActions(); |
| 870 |
| 871 var helpStr = ''; |
| 872 for (var i = 0, action; action = globalActions[i]; i++) { |
| 873 helpStr = helpStr + action.keys + ', ' + action.title + '. '; |
| 874 } |
| 875 |
| 876 helpStr = helpStr + this.nextListKeys + ', ' + cvox.AxsNav.str.NEXT_LIST; |
| 877 helpStr = helpStr + this.prevListKeys + ', ' + cvox.AxsNav.str.PREV_LIST; |
| 878 |
| 879 // Make the keys sound nicer when spoken |
| 880 helpStr = helpStr.replace('PGUP', cvox.AxsNav.str.PGUP); |
| 881 helpStr = helpStr.replace('PGDOWN', cvox.AxsNav.str.PGDOWN); |
| 882 helpStr = helpStr.replace('ENTER', cvox.AxsNav.str.ENTER); |
| 883 helpStr = helpStr.replace('DEL', cvox.AxsNav.str.DELETE); |
| 884 helpStr = helpStr.replace('UP', cvox.AxsNav.str.UP); |
| 885 helpStr = helpStr.replace('DOWN', cvox.AxsNav.str.DOWN); |
| 886 helpStr = helpStr.replace('LEFT', cvox.AxsNav.str.LEFT); |
| 887 helpStr = helpStr.replace('RIGHT', cvox.AxsNav.str.RIGHT); |
| 888 |
| 889 return helpStr; |
| 890 }; |
| 891 |
| 892 /** |
| 893 * Generates a help string for locally available keys. |
| 894 * |
| 895 * @return {string} The help string for locally available keys. |
| 896 */ |
| 897 cvox.AxsNav.prototype.localHelpString = function() { |
| 898 var localActions = this.getLocalActions(); |
| 899 |
| 900 var helpStr = ''; |
| 901 for (var i = 0, action; action = localActions[i]; i++) { |
| 902 helpStr = helpStr + action.keys + ', ' + action.title + '. '; |
| 903 } |
| 904 |
| 905 var list = this.currentList(); |
| 906 if (list.nextKeys !== '') { |
| 907 helpStr = helpStr + list.nextKeys + ', ' + cvox.AxsNav.str.CYCLE_NEXT_ITEM + |
| 908 '. '; |
| 909 } |
| 910 if (list.prevKeys !== '') { |
| 911 helpStr = helpStr + list.prevKeys + ', ' + cvox.AxsNav.str.CYCLE_PREV_ITEM + |
| 912 '. '; |
| 913 } |
| 914 if (list.fwdKeys !== '') { |
| 915 helpStr = helpStr + list.fwdKeys + ', ' + cvox.AxsNav.str.FORWARD_ITEM + |
| 916 '. '; |
| 917 } |
| 918 if (list.backKeys !== '') { |
| 919 helpStr = helpStr + list.backKeys + ', ' + cvox.AxsNav.str.BACKWARDS_ITEM + |
| 920 '. '; |
| 921 } |
| 922 |
| 923 // Make the keys sound nicer when spoken |
| 924 helpStr = helpStr.replace('PGUP', cvox.AxsNav.str.PGUP); |
| 925 helpStr = helpStr.replace('PGDOWN', cvox.AxsNav.str.PGDOWN); |
| 926 helpStr = helpStr.replace('ENTER', cvox.AxsNav.str.ENTER); |
| 927 helpStr = helpStr.replace('DEL', cvox.AxsNav.str.DELETE); |
| 928 helpStr = helpStr.replace('UP', cvox.AxsNav.str.UP); |
| 929 helpStr = helpStr.replace('DOWN', cvox.AxsNav.str.DOWN); |
| 930 helpStr = helpStr.replace('LEFT', cvox.AxsNav.str.LEFT); |
| 931 helpStr = helpStr.replace('RIGHT', cvox.AxsNav.str.RIGHT); |
| 932 |
| 933 return helpStr; |
| 934 }; |
| 935 |
| 936 /** |
| 937 * This function sets the lens to be used when going to an item's element. |
| 938 * |
| 939 * @param {Object?} |
| 940 * lens The AxsLens object to be used. If null, no lens will be used. |
| 941 */ |
| 942 cvox.AxsNav.prototype.setLens = function(lens) { |
| 943 this.lens_ = lens; |
| 944 }; |
| 945 |
| 946 /** |
| 947 * This function sets the sound object to be used when going to an item's |
| 948 * element. |
| 949 * |
| 950 * @param {Object?} |
| 951 * snd The AxsSound object to be used. The AxsSound object should |
| 952 * already have its verbosity set and be initialized. If null, no |
| 953 * sound object will be used. |
| 954 */ |
| 955 cvox.AxsNav.prototype.setSound = function(snd) { |
| 956 this.snd_ = snd; |
| 957 }; |
| 958 |
| 959 /** |
| 960 * Refreshes the dynamic list with the specified title. |
| 961 * |
| 962 * @param {string?} |
| 963 * listTitle The title of the list that should be refreshed. |
| 964 * @return {boolean} True if the list was successfully refreshed. |
| 965 */ |
| 966 cvox.AxsNav.prototype.refreshList = function(listTitle) { |
| 967 if (listTitle === null) { |
| 968 return false; |
| 969 } |
| 970 var reloaded = false; |
| 971 var listId = this.findListIndexByTitle(listTitle); |
| 972 if (listId > -1) { |
| 973 var navList = this.navArray[listId]; |
| 974 navList.items = new Array(); |
| 975 navList.targets = new Array(); |
| 976 reloaded = this.validateList(navList); |
| 977 } |
| 978 return reloaded; |
| 979 }; |
| 980 |
| 981 /** |
| 982 * Disables the default keyboard handler for the AxsNav object by detaching it |
| 983 * from the keypress event listener for the current document. |
| 984 */ |
| 985 cvox.AxsNav.prototype.disableNavKeys = function() { |
| 986 if (this.keyHandler !== null) { |
| 987 document.removeEventListener('keypress', this.keyHandler, true); |
| 988 } |
| 989 }; |
| 990 |
| 991 /** |
| 992 * Re-enables the default keyboard handler for the AxsNav object by reattaching |
| 993 * it to the keypress event listener for the current document. This function |
| 994 * assumes cvox.AxsNav.prototype.setUpNavKeys has already been called so that |
| 995 * this.keyHandler is already setup and ready to go. |
| 996 */ |
| 997 cvox.AxsNav.prototype.enableNavKeys = function() { |
| 998 if (this.keyHandler !== null) { |
| 999 // Remove it once so that the keyHandler is not accidentally added twice |
| 1000 // just in case enableNavKeys has already been called. |
| 1001 // If it has not already been added, this first removeEventListener call |
| 1002 // is a no-op. |
| 1003 document.removeEventListener('keypress', this.keyHandler, true); |
| 1004 document.addEventListener('keypress', this.keyHandler, true); |
| 1005 } |
| 1006 }; |
| 1007 |
| 1008 /** |
| 1009 * Shows a PowerKey input box for selecting an available action. Available |
| 1010 * actions are those that have nodes when their xPaths are evaluated when this |
| 1011 * function is called. |
| 1012 */ |
| 1013 cvox.AxsNav.prototype.showAvailableActionsSelector = function() { |
| 1014 // Fail silently if the PowerKey object is not set |
| 1015 if (this.pk_ === null) { |
| 1016 return; |
| 1017 } |
| 1018 |
| 1019 var actions = new Array(); |
| 1020 this.addLocalTargetActions_(actions); |
| 1021 this.addGlobalTargetActions_(actions); |
| 1022 if (actions.length === 0) { |
| 1023 this.axs_.speakTextViaNode(cvox.AxsNav.str.NO_AVAILABLE_ACTION); |
| 1024 return; |
| 1025 } |
| 1026 |
| 1027 var completionActionMap = new Object(); |
| 1028 for (var i = 0, action; action = actions[i]; i++) { |
| 1029 action[this.pk_.context] = this.getFunctionForHotkeyStr_(action.keys); |
| 1030 completionActionMap[action.title.toLowerCase()] = action; |
| 1031 } |
| 1032 this.pk_.setCompletionActionMap(completionActionMap); |
| 1033 |
| 1034 var completionList = new Array(); |
| 1035 for (var i = 0, action; action = actions[i]; i++) { |
| 1036 completionList.push(action.title); |
| 1037 } |
| 1038 this.pk_.setCompletionList(completionList); |
| 1039 |
| 1040 this.managedCompletionListName = ''; |
| 1041 this.pk_.setBrowseOnly(false); |
| 1042 this.pk_.setBrowseCallback(null); |
| 1043 this.pk_.updateCompletionField(PowerKey.status.VISIBLE, true, 40, 20); |
| 1044 }; |
| 1045 |
| 1046 /** |
| 1047 * Shows a PowerKey read-only box for exploring the available actions. |
| 1048 * The shown PowerKey instance that is composed of three categories: |
| 1049 * <ol> |
| 1050 * <li> |
| 1051 * Global actions - actions that can be triggered from any list. |
| 1052 * </li> |
| 1053 * <li> |
| 1054 * Local actions - actions that can be triggered only from the |
| 1055 * current list. |
| 1056 * </li> |
| 1057 * <li> |
| 1058 * Earcons - auditory clues used through the application. |
| 1059 * </li> |
| 1060 * </ol> |
| 1061 * <strong>Note:</strong> If no PowerKey instance is set via a call |
| 1062 * to cvox.AxsNav.prototype.stPowerKey(powerKey) calls to this method |
| 1063 * are NOOP. If no AxsSound instance is set via a call to |
| 1064 * cvox.AxsNav.prototype.setSound(axsSound) the Earcons category is |
| 1065 * omitted. |
| 1066 */ |
| 1067 cvox.AxsNav.prototype.showAvailableActionsHelp = function() { |
| 1068 if (!this.pk_) { |
| 1069 return; |
| 1070 } |
| 1071 |
| 1072 this.pk_.setBrowseOnly(true); |
| 1073 |
| 1074 // help instructions |
| 1075 var helpInstructionsTitles = new Array(); |
| 1076 helpInstructionsTitles.push( |
| 1077 cvox.AxsNav.str.CATEGORY_HELP_INSTRUCTIONS_EXPLORE_HELP); |
| 1078 helpInstructionsTitles.push( |
| 1079 cvox.AxsNav.str.CATEGORY_HELP_INSTRUCTIONS_STRUCTURE); |
| 1080 helpInstructionsTitles.push( |
| 1081 cvox.AxsNav.str.CATEGORY_HELP_INSTRUCTIONS_SHORTCUTS); |
| 1082 helpInstructionsTitles.push( |
| 1083 cvox.AxsNav.str.CATEGORY_HELP_INSTRUCTIONS_SELECTOR); |
| 1084 this.pk_.addCompletionListByName(cvox.AxsNav.str.CATEGORY_HELP_INSTRUCTIONS, |
| 1085 helpInstructionsTitles, cvox.AxsNav.str.CATEGORY_HELP_INSTRUCTIONS); |
| 1086 |
| 1087 // navigation actions category |
| 1088 var navigationActions = new Array(); |
| 1089 this.addGlobalNavigationActions_(navigationActions); |
| 1090 this.addLocalNavigationActions_(navigationActions); |
| 1091 var navigationActionTitles = new Array(); |
| 1092 for (var i = 0, action; action = navigationActions[i]; i++) { |
| 1093 navigationActionTitles.push(action.title); |
| 1094 } |
| 1095 this.pk_.addCompletionListByName(cvox.AxsNav.str.CATEGORY_NAVIGATION_ACTIONS, |
| 1096 navigationActionTitles, cvox.AxsNav.str.CATEGORY_NAVIGATION_ACTIONS); |
| 1097 |
| 1098 // application actions category |
| 1099 var applicationActions = new Array(); |
| 1100 this.addGlobalTargetActions_(applicationActions); |
| 1101 this.addLocalTargetActions_(applicationActions); |
| 1102 var applicationActionTitles = new Array(); |
| 1103 for (var i = 0, action; action = applicationActions[i]; i++) { |
| 1104 applicationActionTitles.push(action.title); |
| 1105 } |
| 1106 this.pk_.addCompletionListByName(cvox.AxsNav.str.CATEGORY_APPLICATION_ACTIONS, |
| 1107 applicationActionTitles, cvox.AxsNav.str.CATEGORY_APPLICATION_ACTIONS); |
| 1108 |
| 1109 // earcons category |
| 1110 if (this.snd_) { |
| 1111 var earconActions = new Array(); |
| 1112 this.addEarconActions_(earconActions); |
| 1113 var earconActionTitles = new Array(); |
| 1114 for (var i = 0, action; action = earconActions[i]; i++) { |
| 1115 earconActionTitles.push(action.title); |
| 1116 } |
| 1117 var self = this; |
| 1118 this.pk_.setBrowseCallback(function(completion) { |
| 1119 if (this.managedCompletionListName == cvox.AxsNav.str.CATEGORY_EARCONS) { |
| 1120 var earcon = self.titleToEarconMap[completion]; |
| 1121 self.snd_.play(earcon); |
| 1122 } |
| 1123 }); |
| 1124 this.pk_.addCompletionListByName(cvox.AxsNav.str.CATEGORY_EARCONS, |
| 1125 earconActionTitles, cvox.AxsNav.str.CATEGORY_EARCONS); |
| 1126 } |
| 1127 |
| 1128 // show PowerKey |
| 1129 this.pk_.setBrowseOnly(true); |
| 1130 this.pk_.setCompletionListByName(cvox.AxsNav.str.CATEGORY_HELP_INSTRUCTIONS); |
| 1131 this.pk_.updateCompletionField(PowerKey.status.VISIBLE, true, 40, 20); |
| 1132 }; |
| 1133 |
| 1134 /** |
| 1135 * Returns the first found function mapped to a hot key |
| 1136 * in the given hot key string. |
| 1137 * |
| 1138 * @param {string} hotkeyStr |
| 1139 * The hot key string. |
| 1140 * @return {Function?} The first function mapped to a hot key. |
| 1141 * @private |
| 1142 */ |
| 1143 cvox.AxsNav.prototype.getFunctionForHotkeyStr_ = function(hotkeyStr) { |
| 1144 var keys = hotkeyStr.split(' '); |
| 1145 for (var i = 0, key; key = keys[i]; i++) { |
| 1146 var keyCode = -1; |
| 1147 var charCode = -1; |
| 1148 if (key == 'LEFT') { |
| 1149 keyCode = 37; |
| 1150 } else if (key == 'UP') { |
| 1151 keyCode = 38; |
| 1152 } else if (key == 'RIGHT') { |
| 1153 keyCode = 39; |
| 1154 } else if (key == 'DOWN') { |
| 1155 keyCode = 40; |
| 1156 } else if (key == 'PGUP') { |
| 1157 keyCode = 33; |
| 1158 } else if (key == 'PGDOWN') { |
| 1159 keyCode = 34; |
| 1160 } else if (key == 'ENTER') { |
| 1161 keyCode = 13; |
| 1162 } else if (key == 'DEL') { |
| 1163 keyCode = 46; |
| 1164 } else if (key == 'ESC') { |
| 1165 keyCode = 27; |
| 1166 } else { |
| 1167 charCode = key.charCodeAt(0); |
| 1168 } |
| 1169 var command = this.getFunctionForKey(keyCode, charCode); |
| 1170 if (command) { |
| 1171 return command; |
| 1172 } |
| 1173 } |
| 1174 return null; |
| 1175 }; |
| 1176 |
| 1177 /** |
| 1178 * Gets the global available actions in the current context. Each action has a |
| 1179 * "title" member and a "keys" member. |
| 1180 * |
| 1181 * @return {Array} |
| 1182 * An array of the globally available actions. |
| 1183 */ |
| 1184 cvox.AxsNav.prototype.getGlobalActions = function() { |
| 1185 var globalActions = new Array(); |
| 1186 this.addGlobalNavigationActions_(globalActions); |
| 1187 this.addGlobalListNavigationActions_(globalActions); |
| 1188 this.addGlobalTargetActions_(globalActions); |
| 1189 return globalActions; |
| 1190 }; |
| 1191 |
| 1192 /** |
| 1193 * Add the global navigation actions. |
| 1194 * |
| 1195 * @param {Array} actions An array to which to add the actions. |
| 1196 * |
| 1197 * @private |
| 1198 */ |
| 1199 cvox.AxsNav.prototype.addGlobalNavigationActions_ = function(actions) { |
| 1200 // next list |
| 1201 this.addNewAction_(actions, cvox.AxsNav.str.NEXT_LIST, this.nextListKeys); |
| 1202 // previous list |
| 1203 this.addNewAction_(actions, cvox.AxsNav.str.PREV_LIST, this.prevListKeys); |
| 1204 |
| 1205 }; |
| 1206 |
| 1207 /** |
| 1208 * Add the global navigation actions for going to a list. |
| 1209 * |
| 1210 * @param {Array} actions An array to which to add the actions. |
| 1211 * |
| 1212 * @private |
| 1213 */ |
| 1214 cvox.AxsNav.prototype.addGlobalListNavigationActions_ = function(actions) { |
| 1215 for (var i = 0, list; list = this.navArray[i]; i++) { |
| 1216 this.addNewAction_(actions, list.title, list.hotKeys); |
| 1217 } |
| 1218 }; |
| 1219 |
| 1220 /** |
| 1221 * Add the global target actions. |
| 1222 * |
| 1223 * @param {Array} |
| 1224 * actions An array to which to add the actions. |
| 1225 * @private |
| 1226 */ |
| 1227 cvox.AxsNav.prototype.addGlobalTargetActions_ = function(actions) { |
| 1228 for (var j = 0, target; target = this.targetsArray[j]; j++) { |
| 1229 if (this.isValidTargetAction(target)) { |
| 1230 this.addNewAction_(actions, target.title, target.hotkey); |
| 1231 } |
| 1232 } |
| 1233 }; |
| 1234 |
| 1235 /** |
| 1236 * Gets the locally available actions in the current context. Each action has a |
| 1237 * "title" member and a "keys" member. |
| 1238 * |
| 1239 * @return {Array} |
| 1240 * An array of the locally available actions. |
| 1241 */ |
| 1242 cvox.AxsNav.prototype.getLocalActions = function() { |
| 1243 var localActions = new Array(); |
| 1244 this.addLocalNavigationActions_(localActions); |
| 1245 this.addLocalTargetActions_(localActions); |
| 1246 return localActions; |
| 1247 }; |
| 1248 |
| 1249 /** |
| 1250 * Add the local navigation actions. |
| 1251 * |
| 1252 * @param {Array} |
| 1253 * actions An array to which to add the actions. |
| 1254 * @private |
| 1255 */ |
| 1256 cvox.AxsNav.prototype.addLocalNavigationActions_ = function(actions) { |
| 1257 var list = this.currentList(); |
| 1258 if (!list) { |
| 1259 return; |
| 1260 } |
| 1261 // next item |
| 1262 this.addNewAction_(actions, cvox.AxsNav.str.CYCLE_NEXT_ITEM, list.nextKeys); |
| 1263 // previous item |
| 1264 this.addNewAction_(actions, cvox.AxsNav.str.CYCLE_PREV_ITEM, list.prevKeys); |
| 1265 // forward |
| 1266 this.addNewAction_(actions, cvox.AxsNav.str.FORWARD_ITEM, list.fwdKeys); |
| 1267 // backward |
| 1268 this.addNewAction_(actions, cvox.AxsNav.str.BACKWARDS_ITEM, list.backKeys); |
| 1269 }; |
| 1270 |
| 1271 /** |
| 1272 * Add the local target actions. |
| 1273 * |
| 1274 * @param {Array} |
| 1275 * actions An array to which to add the actions. |
| 1276 * @private |
| 1277 */ |
| 1278 cvox.AxsNav.prototype.addLocalTargetActions_ = function(actions) { |
| 1279 var targets = this.currentList().targets; |
| 1280 for (var i = 0, target; target = targets[i]; i++) { |
| 1281 if (this.isValidTargetAction(target)) { |
| 1282 this.addNewAction_(actions, target.title, target.hotkey); |
| 1283 } |
| 1284 } |
| 1285 }; |
| 1286 |
| 1287 /** |
| 1288 * Add the earcon actions. |
| 1289 * |
| 1290 * @param {Array} |
| 1291 * actions An array to which to add the actions. |
| 1292 * @private |
| 1293 */ |
| 1294 cvox.AxsNav.prototype.addEarconActions_ = function(actions) { |
| 1295 // list |
| 1296 this.addNewAction_(actions, cvox.AxsNav.str.CATEGORY_EARCONS_LIST, null); |
| 1297 // item |
| 1298 this.addNewAction_(actions, cvox.AxsNav.str.CATEGORY_EARCONS_ITEM, null); |
| 1299 // wrap |
| 1300 this.addNewAction_(actions, cvox.AxsNav.str.CATEGORY_EARCONS_WRAP, null); |
| 1301 }; |
| 1302 |
| 1303 /** |
| 1304 * Creates a PowerKey action and adds it at the end of the |
| 1305 * given actions array. |
| 1306 * |
| 1307 * @param {Array} |
| 1308 * actions The actions array to add. |
| 1309 * @param {Stirng} |
| 1310 * title The action title. |
| 1311 * @param {Stirng} |
| 1312 * keys The action key bindings. |
| 1313 * @private |
| 1314 */ |
| 1315 cvox.AxsNav.prototype.addNewAction_ = function(actions, title, keys) { |
| 1316 var action = new Object(); |
| 1317 action.title = title; |
| 1318 if (keys && keys !== '') { |
| 1319 action.title = action.title + ', ' + |
| 1320 keys.toLowerCase().replace(' ', ' ' + cvox.AxsNav.str.OR + ' '); |
| 1321 action.keys = keys; |
| 1322 } |
| 1323 actions.push(action); |
| 1324 }; |
| 1325 |
| 1326 /** |
| 1327 * Initializes the PowerKey instance that presents the valid actions. This |
| 1328 * method initializes the PowerKey object with reasonable default values. |
| 1329 */ |
| 1330 cvox.AxsNav.prototype.defaultInitPowerKeyObj = function() { |
| 1331 var parentElement = this.axs_.getActiveDocument().body; |
| 1332 this.pk_.createCompletionField(parentElement, 50, null, null, null, false); |
| 1333 this.pk_.setCompletionPromptStr(cvox.AxsNav.str.SELECT_ACTION); |
| 1334 this.pk_.setAutoHideCompletionField(true); |
| 1335 this.pk_.setDefaultCSSStyle(); |
| 1336 this.pk_.setManagedCompletionListCallback(function(name) { |
| 1337 this.managedCompletionListName = name; |
| 1338 }); |
| 1339 }; |
| 1340 |
| 1341 /** |
| 1342 * Returns true if the xPath of this target. evaluates to a non empty set of |
| 1343 * nodes. |
| 1344 * |
| 1345 * @param {Object} |
| 1346 * target A target object. |
| 1347 * @return {boolean} Whether the target action is valid. |
| 1348 */ |
| 1349 cvox.AxsNav.prototype.isValidTargetAction = function(target) { |
| 1350 var valid = false; |
| 1351 |
| 1352 if (target.hotkey !== '') { |
| 1353 var xPath = target.xpath; |
| 1354 var rootNode = this.axs_.getActiveDocument().body; |
| 1355 |
| 1356 // Handle relative XPath |
| 1357 if (xPath.indexOf('.') === 0) { |
| 1358 var currentItem = this.currentItem(); |
| 1359 if (currentItem) { |
| 1360 rootNode = currentItem.elem; |
| 1361 } |
| 1362 } |
| 1363 |
| 1364 // Find xPaths that return non empty set of nodes |
| 1365 var nodes = this.axs_.evalXPath(xPath, rootNode); |
| 1366 if (nodes.length > 0) { |
| 1367 valid = true; |
| 1368 } |
| 1369 } |
| 1370 return valid; |
| 1371 }; |
| 1372 |
| 1373 /** |
| 1374 * Builds up the navigation system of lists of items. This system uses the idea |
| 1375 * of multiple cursors and the visitor pattern. |
| 1376 * |
| 1377 * @param {Object} |
| 1378 * cnrJson The CNR as a JSON. |
| 1379 * |
| 1380 * @notypecheck {Function?} opt_customNavMethod. |
| 1381 * |
| 1382 * @param {Function?} |
| 1383 * opt_customNavMethod A custom navigation method provided by the |
| 1384 * caller. This navigation method will be given the original cnrJson, |
| 1385 * the navigation array of lists of items, an array of all the lists |
| 1386 * which had zero items, and an array of targets. If this is null, |
| 1387 * the default AxsJAX nav handler will be used. |
| 1388 */ |
| 1389 cvox.AxsNav.prototype.navInitJson = function(cnrJson, opt_customNavMethod) { |
| 1390 this.navArray = new Array(); |
| 1391 this.navListIdx = 0; |
| 1392 this.navItemIdxs = new Array(); |
| 1393 |
| 1394 var emptyLists = new Array(); |
| 1395 |
| 1396 var i; |
| 1397 var currentList; |
| 1398 for (i = 0, currentList; currentList = cnrJson.lists[i]; i++) { |
| 1399 var navList = new Object(); |
| 1400 currentList.listId = i; // create an id for the list |
| 1401 navList.cnrNode = null; |
| 1402 navList.origListObj = currentList; |
| 1403 navList.title = currentList.title || ''; |
| 1404 navList.hotKeys = currentList.hotkey || ''; |
| 1405 navList.nextKeys = currentList.next || ''; |
| 1406 navList.prevKeys = currentList.prev || ''; |
| 1407 navList.fwdKeys = currentList.fwd || ''; |
| 1408 navList.backKeys = currentList.back || ''; |
| 1409 navList.onEmpty = currentList.onEmpty || ''; |
| 1410 navList.type = currentList.type || ''; |
| 1411 navList.tailTarget = null; |
| 1412 navList.headTarget = null; |
| 1413 navList.entryTarget = null; |
| 1414 navList.items = this.makeItemsArray(currentList); |
| 1415 navList.targets = this.makeTargetsArray(currentList); |
| 1416 for (var j = 0, listTarget; listTarget = navList.targets[j]; j++) { |
| 1417 if (listTarget.trigger == 'listTail') { |
| 1418 navList.tailTarget = listTarget; |
| 1419 } else if (listTarget.trigger == 'listHead') { |
| 1420 navList.headTarget = listTarget; |
| 1421 } else if (listTarget.trigger == 'listEntry') { |
| 1422 navList.entryTarget = listTarget; |
| 1423 } |
| 1424 } |
| 1425 if (navList.items.length > 0 || navList.type == 'dynamic') { |
| 1426 // Only add nav lists that have content to the array |
| 1427 this.navArray.push(navList); |
| 1428 this.navItemIdxs.push(-1); |
| 1429 } else if (navList.hotKeys !== '') { |
| 1430 // Record empty nav lists if the user can jump to them directly |
| 1431 emptyLists.push(navList); |
| 1432 } |
| 1433 } |
| 1434 |
| 1435 // Build up the targets |
| 1436 var targets = new Array(); |
| 1437 this.targetsArray = new Array(); |
| 1438 this.targetsIdx = 0; |
| 1439 var currentTarget; |
| 1440 if (cnrJson.targets) { |
| 1441 for (i = 0, currentTarget; currentTarget = cnrJson.targets[i]; i++) { |
| 1442 var target = new Object(); |
| 1443 // Strip all leading and trailing spaces from the xpath |
| 1444 target.xpath = currentTarget.xpath; |
| 1445 target.xpath = target.xpath.replace(/^\s\s*/, '').replace(/\s\s*$/, |
| 1446 ''); |
| 1447 target.title = currentTarget.title || ''; |
| 1448 target.trigger = currentTarget.trigger || 'key'; |
| 1449 target.hotkey = currentTarget.hotkey || ''; |
| 1450 target.action = currentTarget.action || 'click'; |
| 1451 target.onEmpty = currentTarget.onEmpty || ''; |
| 1452 this.targetsArray.push(target); |
| 1453 } |
| 1454 } |
| 1455 |
| 1456 // Remove the previous event listener if there was one |
| 1457 if (this.keyHandler !== null) { |
| 1458 document.removeEventListener('keypress', this.keyHandler, true); |
| 1459 } |
| 1460 // Bind lists and targets to keys if there is no custom handler specified |
| 1461 if ((opt_customNavMethod === null) || |
| 1462 (typeof (opt_customNavMethod) == 'undefined')) { |
| 1463 this.setUpNavKeys(cnrJson, emptyLists); |
| 1464 } else { |
| 1465 opt_customNavMethod(cnrJson, this.navArray, emptyLists, |
| 1466 this.targetsArray); |
| 1467 } |
| 1468 }; |
| 1469 |
| 1470 /** |
| 1471 * Makes an array of items given a navigation list node and its index. Elements |
| 1472 * associated with a list will be marked as such. |
| 1473 * |
| 1474 * @param {Object} |
| 1475 * jsonListObj The navigation list node. |
| 1476 * @return {Array} The array of items. |
| 1477 */ |
| 1478 cvox.AxsNav.prototype.makeItemsArray = function(jsonListObj) { |
| 1479 var itemsArray = new Array(); |
| 1480 if (!jsonListObj.items) { |
| 1481 return itemsArray; |
| 1482 } |
| 1483 for (var i = 0, entry; entry = jsonListObj.items[i]; i++) { |
| 1484 // Do this in a try-catch block since there are multiple |
| 1485 // sets of items and even if one set does not exist as expected, |
| 1486 // the other sets should still be available. |
| 1487 try { |
| 1488 // Strip all leading and trailing spaces from the xpath |
| 1489 var xpath = entry.xpath.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); |
| 1490 var htmlElem = this.axs_.getActiveDocument().documentElement; |
| 1491 var elems = this.axs_.evalXPath(xpath, htmlElem); |
| 1492 var idxStr = entry.index || '0'; |
| 1493 var idx = parseInt(idxStr, 10); |
| 1494 var count = elems.length - idx; |
| 1495 var countStr = entry.count || '*'; |
| 1496 if (countStr != '*') { |
| 1497 count = parseInt(countStr, 10); |
| 1498 } |
| 1499 var end = count + idx; |
| 1500 var action = entry.action || null; |
| 1501 while (idx < end) { |
| 1502 var item = new Object(); |
| 1503 item.action = action; |
| 1504 item.elem = elems[idx]; |
| 1505 if (typeof (item.elem) != 'undefined') { |
| 1506 if (typeof (item.elem.AxsNavInfo) == 'undefined') { |
| 1507 item.elem.AxsNavInfo = new Object(); |
| 1508 } |
| 1509 if (typeof (jsonListObj.listId) == 'undefined') { |
| 1510 throw new Error('list does not have an id'); |
| 1511 } |
| 1512 item.elem.AxsNavInfo[jsonListObj.listId] = |
| 1513 itemsArray.length; |
| 1514 itemsArray.push(item); |
| 1515 } |
| 1516 idx++; |
| 1517 } |
| 1518 } catch (err) { |
| 1519 } |
| 1520 } |
| 1521 return itemsArray; |
| 1522 }; |
| 1523 |
| 1524 /** |
| 1525 * Returns an array of target objects for the given <list> node. |
| 1526 * |
| 1527 * @param {Object} |
| 1528 * jsonListObj A <list> node. |
| 1529 * @return {Array} An array of target objects. |
| 1530 */ |
| 1531 cvox.AxsNav.prototype.makeTargetsArray = function(jsonListObj) { |
| 1532 var targetsArray = new Array(); |
| 1533 if (!jsonListObj.targets) { |
| 1534 return targetsArray; |
| 1535 } |
| 1536 for (var i = 0, entry; entry = jsonListObj.targets[i]; i++) { |
| 1537 var target = new Object(); |
| 1538 // Strip all leading and trailing spaces from the xpath |
| 1539 target.xpath = entry.xpath.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); |
| 1540 target.title = entry.title || ''; |
| 1541 target.trigger = entry.trigger || 'key'; |
| 1542 target.hotkey = entry.hotkey || ''; |
| 1543 target.action = entry.action || 'click'; |
| 1544 target.onEmpty = entry.onEmpty || ''; |
| 1545 targetsArray.push(target); |
| 1546 } |
| 1547 return targetsArray; |
| 1548 }; |
| 1549 |
| 1550 /** |
| 1551 * This function attaches the default AxsJAX key handler for navigation. |
| 1552 * |
| 1553 * @param {Object} |
| 1554 * cnrJson The CNR as a JSON. |
| 1555 * @param {Array} |
| 1556 * emptyLists An array of lists which have zero items. |
| 1557 */ |
| 1558 cvox.AxsNav.prototype.setUpNavKeys = function(cnrJson, emptyLists) { |
| 1559 var self = this; |
| 1560 var i; |
| 1561 |
| 1562 this.topCharCodeMap = new Object(); |
| 1563 this.topKeyCodeMap = new Object(); |
| 1564 this.charCodeMaps = new Array(); |
| 1565 this.keyCodeMaps = new Array(); |
| 1566 |
| 1567 // ? for help |
| 1568 var test = new Array(); |
| 1569 test.push('?'); |
| 1570 this.assignKeysToMethod(test, this.topCharCodeMap, this.topKeyCodeMap, |
| 1571 function() { |
| 1572 self.showAvailableActionsHelp(); |
| 1573 }); |
| 1574 |
| 1575 // Acting on global targets |
| 1576 var target; |
| 1577 for (i = 0, target; target = this.targetsArray[i]; i++) { |
| 1578 this.assignTargetKeys(target, this.topCharCodeMap, this.topKeyCodeMap); |
| 1579 } |
| 1580 |
| 1581 // Moving through lists |
| 1582 var keys = new Array(); |
| 1583 this.nextListKeys = cnrJson.next || ''; |
| 1584 if (this.nextListKeys !== '') { |
| 1585 keys = this.nextListKeys.split(' '); |
| 1586 } |
| 1587 this.assignKeysToMethod(keys, this.topCharCodeMap, this.topKeyCodeMap, |
| 1588 function() { |
| 1589 self.nextList(); |
| 1590 self.doListEntryActions(); |
| 1591 }); |
| 1592 |
| 1593 keys = new Array(); |
| 1594 this.prevListKeys = cnrJson.prev || ''; |
| 1595 if (this.prevListKeys !== '') { |
| 1596 keys = this.prevListKeys.split(' '); |
| 1597 } |
| 1598 this.assignKeysToMethod(keys, this.topCharCodeMap, this.topKeyCodeMap, |
| 1599 function() { |
| 1600 self.prevList(); |
| 1601 self.doListEntryActions(); |
| 1602 }); |
| 1603 |
| 1604 // Moving through items and handling per-list targets |
| 1605 var list; |
| 1606 for (i = 0, list; list = this.navArray[i]; i++) { |
| 1607 var charMap = new Object(); |
| 1608 var keyMap = new Object(); |
| 1609 this.charCodeMaps.push(charMap); |
| 1610 this.keyCodeMaps.push(keyMap); |
| 1611 this.assignItemKeys(list.nextKeys, i, 'next'); |
| 1612 this.assignItemKeys(list.prevKeys, i, 'prev'); |
| 1613 this.assignItemKeys(list.fwdKeys, i, 'fwd'); |
| 1614 this.assignItemKeys(list.backKeys, i, 'back'); |
| 1615 this.assignHotKeys(list.hotKeys, i, list.onEmpty); |
| 1616 var j; |
| 1617 for (j = 0, target; target = list.targets[j]; j++) { |
| 1618 this.assignTargetKeys(target, charMap, keyMap); |
| 1619 } |
| 1620 } |
| 1621 |
| 1622 // Dealing with empty lists with hotkeys |
| 1623 var emptyList; |
| 1624 for (i = 0, emptyList; emptyList = emptyLists[i]; i++) { |
| 1625 this.assignEmptyMsgKeys(emptyList.hotKeys, emptyList.onEmpty); |
| 1626 } |
| 1627 |
| 1628 this.keyHandler = function(evt) { |
| 1629 // None of these commands involve Ctrl. |
| 1630 // If Ctrl is held, it must be for some AT. |
| 1631 if (evt.ctrlKey) |
| 1632 return true; |
| 1633 if (self.axs_.inputFocused) |
| 1634 return true; |
| 1635 |
| 1636 var command = self.getFunctionForKey(evt.keyCode, evt.charCode); |
| 1637 |
| 1638 if (command) { |
| 1639 command(); |
| 1640 return false; |
| 1641 } |
| 1642 |
| 1643 return true; |
| 1644 }; |
| 1645 |
| 1646 document.addEventListener('keypress', this.keyHandler, true); |
| 1647 }; |
| 1648 |
| 1649 /** |
| 1650 * Returns the id of a given list |
| 1651 * |
| 1652 * @param {Object} |
| 1653 * list a list. |
| 1654 * @return {Number} the id of the list. |
| 1655 */ |
| 1656 cvox.AxsNav.prototype.getListId = function(list) { |
| 1657 return list.origListObj.listId; |
| 1658 }; |
| 1659 |
| 1660 /** |
| 1661 * Takes the cursor before the beginning of the specified list. |
| 1662 * |
| 1663 * @param {Number} |
| 1664 * listIndex the index of the given list. |
| 1665 */ |
| 1666 cvox.AxsNav.prototype.goToListHead = function(listIndex) { |
| 1667 if (this.navArray.length < 1) { |
| 1668 return; |
| 1669 } |
| 1670 // Clear the last item |
| 1671 this.lastItem = null; |
| 1672 // Set the current list |
| 1673 this.navListIdx = listIndex; |
| 1674 // Update the list |
| 1675 this.validateList(this.navArray[listIndex]); |
| 1676 this.doListEntryActions(); |
| 1677 // Set the item at the top |
| 1678 this.navItemIdxs[listIndex] = -1; |
| 1679 }; |
| 1680 |
| 1681 /** |
| 1682 * Takes the cursor to the end of the specified list. |
| 1683 * |
| 1684 * @param {Number} |
| 1685 * listIndex the index of the given list. |
| 1686 */ |
| 1687 cvox.AxsNav.prototype.goToListTail = function(listIndex) { |
| 1688 if (this.navArray.length < 1) { |
| 1689 return; |
| 1690 } |
| 1691 this.goToListHead(listIndex); |
| 1692 // The navList object |
| 1693 var list = this.navArray[listIndex]; |
| 1694 // Set the item at the bottom |
| 1695 this.navItemIdxs[listIndex] = list.items.length - 1; |
| 1696 }; |
| 1697 |
| 1698 /** |
| 1699 * Finds a list id given it's name |
| 1700 * |
| 1701 * @param {string} |
| 1702 * listTitle the name of the list. |
| 1703 * @return {number} The index of the list with the given title or -1 if no list |
| 1704 * with the given title exists. |
| 1705 */ |
| 1706 cvox.AxsNav.prototype.findListIndexByTitle = function(listTitle) { |
| 1707 for (var i = 0; i < this.navArray.length; i++) { |
| 1708 var list = this.navArray[i]; |
| 1709 if (list.title == listTitle) { |
| 1710 break; |
| 1711 } |
| 1712 } |
| 1713 if (i < this.navArray.length) { |
| 1714 return i; |
| 1715 } else { |
| 1716 return -1; |
| 1717 } |
| 1718 }; |
| 1719 |
| 1720 /** |
| 1721 * Generates a custom walker based on the CNR for the given object. |
| 1722 * This custom walker can be fed into the ChromeVox nav manager so that all of |
| 1723 * the navigation controls can be cleanly integrated. |
| 1724 * |
| 1725 * @return {Object} A custom walker object. |
| 1726 */ |
| 1727 cvox.AxsNav.prototype.generateCustomWalker = function() { |
| 1728 var customWalker = new CustomWalker(); |
| 1729 var self = this; |
| 1730 customWalker.next = function() { |
| 1731 self.actOnItem(self.nextItem()); |
| 1732 return true; |
| 1733 }; |
| 1734 customWalker.previous = function() { |
| 1735 self.actOnItem(self.prevItem()); |
| 1736 return true; |
| 1737 }; |
| 1738 customWalker.goToCurrentItem = function() { |
| 1739 self.actOnItem(self.currentItem()); |
| 1740 return true; |
| 1741 }; |
| 1742 customWalker.actOnCurrentItem = function() { |
| 1743 var target = self.targetsArray[self.navListIdx]; |
| 1744 self.actOnTarget(target); |
| 1745 return true; |
| 1746 }; |
| 1747 customWalker.getCurrentNode = function() { |
| 1748 return self.currentItem().elem; |
| 1749 }; |
| 1750 customWalker.setCurrentNode = function(targetNode) { |
| 1751 self.goToListHead(self.navListIdx); |
| 1752 var currentItem = self.nextItem(); |
| 1753 while (currentItem) { |
| 1754 if (targetNode.compareDocumentPosition(currentItem.elem) == |
| 1755 Node.DOCUMENT_POSITION_PRECEDING) { |
| 1756 currentItem = self.nextItem(); |
| 1757 } else { |
| 1758 return; |
| 1759 } |
| 1760 } |
| 1761 }; |
| 1762 return customWalker; |
| 1763 }; |
| OLD | NEW |