| 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.AxsJAX'); |
| 6 |
| 7 goog.require('cvox.ChromeVox'); |
| 8 goog.require('cvox.DomUtil'); |
| 9 goog.require('cvox.XpathUtil'); |
| 10 |
| 11 /** |
| 12 * @fileoverview AxsJAX - JavaScript library for enhancing the accessibility |
| 13 * of AJAX apps through WAI-ARIA. |
| 14 * Note that this is a ChromeVox specific version of AxsJAX that has been |
| 15 * optimized and refactored for ChromeVox. |
| 16 */ |
| 17 |
| 18 /** |
| 19 * Class of scripts for improving accessibility of Web 2.0 Apps. |
| 20 * @param {boolean} useTabKeyFix Whether or not to try syncing to the last |
| 21 * marked position when the user presses the |
| 22 * tab key. |
| 23 * @constructor |
| 24 */ |
| 25 cvox.AxsJAX = function(useTabKeyFix) { |
| 26 this.ID_NUM_ = 0; |
| 27 this.tabbingStartPosNode_ = null; |
| 28 this.tabKeyFixOn_ = false; |
| 29 this.lastFocusedNode = null; |
| 30 this.inputFocused = false; |
| 31 |
| 32 var self = this; |
| 33 |
| 34 //Monitor focus and blur |
| 35 this.addFocusBlurMonitors(); |
| 36 |
| 37 //Activate the tab key fix if needed |
| 38 if (useTabKeyFix) { |
| 39 this.tabKeyFixOn_ = true; |
| 40 document.addEventListener('keypress', |
| 41 function(event) { |
| 42 self.tabKeyHandler(event, self); |
| 43 }, |
| 44 true); |
| 45 // Record in a custom DOM property: |
| 46 document.body.AXSJAX_TABKEYFIX_ADDED = true; |
| 47 } |
| 48 }; |
| 49 |
| 50 |
| 51 /** |
| 52 * Adds focus/blur handlers to the current active document. |
| 53 * This should be invoked when AxsJAX is first initialized and |
| 54 * when a new active document has been set. |
| 55 */ |
| 56 cvox.AxsJAX.prototype.addFocusBlurMonitors = function() { |
| 57 var activeDoc = this.getActiveDocument(); |
| 58 var self = this; |
| 59 //These return "true" so that any page actions based on |
| 60 //focus and blur will still occur. |
| 61 var focusHandler = function(evt) { |
| 62 self.lastFocusedNode = evt.target; |
| 63 if ((evt.target.tagName == 'INPUT') || |
| 64 (evt.target.tagName == 'SELECT') || |
| 65 (evt.target.tagName == 'TEXTAREA')) { |
| 66 self.inputFocused = true; |
| 67 } |
| 68 return true; |
| 69 }; |
| 70 activeDoc.addEventListener('focus', focusHandler, true); |
| 71 |
| 72 var blurHandler = function(evt) { |
| 73 self.lastFocusedNode = null; |
| 74 if ((evt.target.tagName == 'INPUT') || |
| 75 (evt.target.tagName == 'SELECT') || |
| 76 (evt.target.tagName == 'TEXTAREA')) { |
| 77 self.inputFocused = false; |
| 78 } |
| 79 return true; |
| 80 }; |
| 81 activeDoc.addEventListener('blur', blurHandler, true); |
| 82 }; |
| 83 |
| 84 |
| 85 /** |
| 86 * This function is not needed in Chrome Vox and is a no-op. It is available |
| 87 * only as a compatibility shim for existing AxsJAX scripts. |
| 88 * |
| 89 * @param {Node} targetNode Dummy node - this is not used. |
| 90 */ |
| 91 cvox.AxsJAX.prototype.setActiveParent = function(targetNode) { |
| 92 }; |
| 93 |
| 94 |
| 95 /** |
| 96 * This function is not needed in Chrome Vox and is the same as just calling |
| 97 * "document". |
| 98 * Gets the document for the active parent. |
| 99 * @return {Node} The document that is the ancestor for the active parent. |
| 100 */ |
| 101 cvox.AxsJAX.prototype.getActiveDocument = function() { |
| 102 return document; |
| 103 }; |
| 104 |
| 105 |
| 106 /** |
| 107 * Speaks a given node using ChromeVox. |
| 108 * @param {Node} targetNode The HTML node to be spoken. |
| 109 */ |
| 110 cvox.AxsJAX.prototype.speakNode = function(targetNode) { |
| 111 ChromeVox.tts.speak(DomUtil.getText(targetNode), 0, null); |
| 112 }; |
| 113 |
| 114 |
| 115 /** |
| 116 * Speaks the given textString using ChromeVox. |
| 117 * @param {String} textString The text to be spoken. |
| 118 */ |
| 119 cvox.AxsJAX.prototype.speakText = function(textString) { |
| 120 ChromeVox.tts.speak(textString, 0, null); |
| 121 }; |
| 122 |
| 123 /** |
| 124 * Speaks the given textString using ChromeVox. |
| 125 * This function is the same as speakText as is available only as a |
| 126 * compatibility shim for existing AxsJAX scripts. |
| 127 * |
| 128 * @param {String} textString The text to be spoken. |
| 129 * |
| 130 */ |
| 131 cvox.AxsJAX.prototype.speakTextViaNode = function(textString) { |
| 132 ChromeVox.tts.speak(textString, 0, null); |
| 133 }; |
| 134 |
| 135 /** |
| 136 * Puts alt='' for all images that are children of the target node that |
| 137 * have no alt text defined. This is a bandage fix to prevent screen readers |
| 138 * from rambling on by reading the URL string of the image. |
| 139 * A real fix for this problem should be to either use appropriate alt text for |
| 140 * the images or explicitly put alt='' for images that have no semantic value. |
| 141 * @param {Node} targetNode The target node of this operation. |
| 142 */ |
| 143 cvox.AxsJAX.prototype.putNullForNoAltImages = function(targetNode) { |
| 144 var images = targetNode.getElementsByTagName('img'); |
| 145 for (var i = 0, image; image = images[i]; i++) { |
| 146 if (!image.alt) { |
| 147 image.alt = ''; |
| 148 } |
| 149 } |
| 150 }; |
| 151 |
| 152 |
| 153 /** |
| 154 * Dispatches a left click event on the element that is the targetNode. |
| 155 * Clicks go in the sequence of mousedown, mouseup, and click. |
| 156 * This functionality already exists in DomUtil, so this method simply |
| 157 * redirects to that. |
| 158 * |
| 159 * @param {Node} targetNode The target node of this operation. |
| 160 * @param {boolean} shiftKey Specifies if shift is held down. |
| 161 */ |
| 162 cvox.AxsJAX.prototype.clickElem = function(targetNode, shiftKey) { |
| 163 DomUtil.clickElem(targetNode, shiftKey); |
| 164 }; |
| 165 |
| 166 /** |
| 167 * Dispatches a key event on the element that is the targetNode. |
| 168 * @param {Node} targetNode The target node of this operation. |
| 169 * @param {String} theKey The key to use for this operation. |
| 170 * This can be any single printable character or ENTER. |
| 171 * @param {Boolean} holdCtrl Whether or not the Ctrl key should be held for |
| 172 * this operation. |
| 173 * @param {Boolean} holdAlt Whether or not the Alt key should be held for |
| 174 * this operation. |
| 175 * @param {Boolean} holdShift Whether or not the Shift key should be held |
| 176 * for this operation. |
| 177 */ |
| 178 cvox.AxsJAX.prototype.sendKey = function(targetNode, theKey, |
| 179 holdCtrl, holdAlt, holdShift) { |
| 180 var keyCode = 0; |
| 181 var charCode = 0; |
| 182 if (theKey == 'ENTER') { |
| 183 keyCode = 13; |
| 184 } |
| 185 else if (theKey.length == 1) { |
| 186 charCode = theKey.charCodeAt(0); |
| 187 } |
| 188 var activeDoc = this.getActiveDocument(); |
| 189 var evt = activeDoc.createEvent('KeyboardEvent'); |
| 190 evt.initKeyEvent('keypress', true, true, null, holdCtrl, |
| 191 holdAlt, holdShift, false, keyCode, charCode); |
| 192 targetNode.dispatchEvent(evt); |
| 193 }; |
| 194 |
| 195 /** |
| 196 * Assigns an ID to the targetNode. |
| 197 * If targetNode already has an ID, this is a no-op. |
| 198 * Always returns the ID of targetNode. |
| 199 * If targetNode is null, we return '' |
| 200 * @param {Node} targetNode The target node of this operation. |
| 201 * @param {String} opt_prefixString |
| 202 * Prefix to help ensure the uniqueness of the ID. |
| 203 * This is optional; if null, it will use "AxsJAX_ID_". |
| 204 * @return {string} The ID that the targetNode now has. |
| 205 */ |
| 206 cvox.AxsJAX.prototype.assignId = function(targetNode, opt_prefixString) { |
| 207 if (!targetNode) { |
| 208 return ''; |
| 209 } |
| 210 if (targetNode.id) { |
| 211 return targetNode.id; |
| 212 } |
| 213 var prefix = opt_prefixString || 'AxsJAX_ID_'; |
| 214 targetNode.id = prefix + this.ID_NUM_++; |
| 215 return targetNode.id; |
| 216 }; |
| 217 |
| 218 /** |
| 219 * Marks the current position by remembering what the last focusable node was. |
| 220 * The focusable node will be the targetNode if it has a focus() function, or |
| 221 * if it does not, the first descendent node that it has which does. |
| 222 * If the targetNode itself and all of its descendents have no focus() function, |
| 223 * this function will complete with failure. |
| 224 * If the cvox.AxsJAX.tabKeyHandler is used, then it will put the focus on this |
| 225 * node. |
| 226 * @param {Node} targetNode The target node of this operation. |
| 227 * @return {Boolean} True if the position was marked successfully. |
| 228 * False if failed. |
| 229 */ |
| 230 cvox.AxsJAX.prototype.markPosition = function(targetNode) { |
| 231 if (!targetNode) { |
| 232 return false; |
| 233 } |
| 234 ChromeVox.navigationManager.syncToNode(targetNode); |
| 235 if ((targetNode.tagName == 'A') || (targetNode.tagName == 'INPUT')) { |
| 236 this.tabbingStartPosNode_ = targetNode; |
| 237 return true; |
| 238 } |
| 239 var allDescendants = targetNode.getElementsByTagName('*'); |
| 240 for (var i = 0, currentNode; currentNode = allDescendants[i]; i++) { |
| 241 if ((currentNode.tagName == 'A') || |
| 242 (currentNode.tagName == 'INPUT') || |
| 243 (currentNode.hasAttribute('tabindex') && |
| 244 (currentNode.tabIndex != -1))) { |
| 245 this.tabbingStartPosNode_ = currentNode; |
| 246 return true; |
| 247 } |
| 248 } |
| 249 return false; |
| 250 }; |
| 251 |
| 252 /** |
| 253 * Restores the focus . |
| 254 * Usage: |
| 255 * var myAxsJAXObj = new AxsJAX(); |
| 256 * document.addEventListener('keypress', |
| 257 * function(event){ |
| 258 * myAxsJAXObj.tabKeyHandler(event,myAxsJAXObj); |
| 259 * }, |
| 260 * true); |
| 261 * @param {Event} evt The event. |
| 262 * @param {Object} selfRef The AxsJAX object. A self reference is needed here |
| 263 * since this in an event handler does NOT refer to the |
| 264 * AxsJAX object. |
| 265 * @return {Boolean} Always returns true to pass the tab key along. |
| 266 */ |
| 267 cvox.AxsJAX.prototype.tabKeyHandler = function(evt, selfRef) { |
| 268 if (!selfRef.tabKeyFixOn_) { |
| 269 return true; |
| 270 } |
| 271 if ((evt.keyCode == 9) && (selfRef.tabbingStartPosNode_)) { |
| 272 selfRef.tabbingStartPosNode_.focus(); |
| 273 selfRef.tabbingStartPosNode_ = null; |
| 274 } |
| 275 return true; |
| 276 }; |
| 277 |
| 278 /** |
| 279 * Scrolls to the targetNode and speaks it. |
| 280 * This will automatically mark the position; this should be used if you are |
| 281 * navigating through content. |
| 282 * @param {Node} targetNode The HTML node to be spoken. |
| 283 */ |
| 284 cvox.AxsJAX.prototype.goTo = function(targetNode) { |
| 285 this.speakNode(targetNode); |
| 286 targetNode.scrollIntoView(true); |
| 287 this.markPosition(targetNode); |
| 288 }; |
| 289 |
| 290 |
| 291 /** |
| 292 * Sets the attribute of the targetNode to the value. |
| 293 * Use this rather than a direct set attribute to abstract away ARIA |
| 294 * naming changes. |
| 295 * @param {Node} targetNode The HTML node to have the attribute set on. |
| 296 * @param {string} attribute The attribute to set. |
| 297 * @param {string?} value The value the attribute should be set to. |
| 298 */ |
| 299 cvox.AxsJAX.prototype.setAttributeOf = function(targetNode, attribute, value) { |
| 300 if (!targetNode) { |
| 301 return; |
| 302 } |
| 303 //Add the aria- to attributes |
| 304 attribute = attribute.toLowerCase(); |
| 305 switch (attribute) { |
| 306 case 'live': |
| 307 attribute = 'aria-live'; |
| 308 break; |
| 309 case 'activedescendant': |
| 310 attribute = 'aria-activedescendant'; |
| 311 break; |
| 312 case 'atomic': |
| 313 attribute = 'aria-atomic'; |
| 314 break; |
| 315 default: |
| 316 break; |
| 317 } |
| 318 targetNode.setAttribute(attribute, value); |
| 319 }; |
| 320 |
| 321 /** |
| 322 * Gets the attribute of the targetNode. |
| 323 * Use this rather than a direct get attribute to abstract away ARIA |
| 324 * naming changes. |
| 325 * @param {Node} targetNode The HTML node to get the attribute of. |
| 326 * @param {string} attribute The attribute to get the value of. |
| 327 * @return {string} The value of the attribute of the targetNode. |
| 328 */ |
| 329 cvox.AxsJAX.prototype.getAttributeOf = function(targetNode, attribute) { |
| 330 return targetNode.getAttribute(attribute); |
| 331 }; |
| 332 |
| 333 /** |
| 334 * Removes the attribute of the targetNode. |
| 335 * Use this rather than a direct remove attribute to abstract away ARIA |
| 336 * naming changes. |
| 337 * @param {Node} targetNode The HTML node to remove the attribute from. |
| 338 * @param {string} attribute The attribute to be removed. |
| 339 */ |
| 340 cvox.AxsJAX.prototype.removeAttributeOf = function(targetNode, attribute) { |
| 341 if (targetNode && targetNode.removeAttribute) { |
| 342 targetNode.removeAttribute(attribute); |
| 343 } |
| 344 }; |
| 345 |
| 346 /** |
| 347 * This is a no-op for ChromeVox since ChromeVox is the AT and does not need to |
| 348 * be synced. This function is available only as a compatibility shim. |
| 349 * |
| 350 * @param {Node} targetNode The HTML node to force the AT to sync to. |
| 351 */ |
| 352 cvox.AxsJAX.prototype.forceATSync = function(targetNode) { |
| 353 |
| 354 }; |
| 355 |
| 356 /** |
| 357 * Given an XPath expression and rootNode, it returns an array of children nodes |
| 358 * that match. |
| 359 * This functionality already exists in XpathUtil, so this method simply |
| 360 * redirects to that. |
| 361 * @param {string} expression The XPath expression to evaluate. |
| 362 * @param {Node} rootNode The HTML node to start evaluating the XPath from. |
| 363 * @return {Array} The array of children nodes that match. |
| 364 */ |
| 365 cvox.AxsJAX.prototype.evalXPath = function(expression, rootNode) { |
| 366 return cvox.XpathUtil.evalXPath(expression, rootNode); |
| 367 }; |
| 368 |
| 369 /** |
| 370 * This function initializes an AxsJAX script by calling a given initialization |
| 371 * routine after the page load event has been generated. Using |
| 372 * this method instead of attaching the event listener directly is recommended |
| 373 * to enable testability of the application. |
| 374 * |
| 375 * @param {Function!} initFunction The initialization function to invoke. |
| 376 */ |
| 377 cvox.AxsJAX.initializeOnLoad = function(initFunction) { |
| 378 window.addEventListener('load', function() { |
| 379 initFunction(); |
| 380 }, true); |
| 381 }; |
| OLD | NEW |