| 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 /** |
| 6 * Javascript class for providing keyboard interface enhancements for |
| 7 * Web 2.0 applications. |
| 8 * @param {string} context The user specified string value, which is the |
| 9 * starting context of the application. This can be changed later. |
| 10 * @param {AxsJAX} axsJAX The AxsJAX object provided by the user. |
| 11 * @constructor |
| 12 */ |
| 13 var PowerKey = function(context, axsJAX) { |
| 14 /** |
| 15 * Holds the current application context. |
| 16 * @type {string} |
| 17 */ |
| 18 this.context = context; |
| 19 |
| 20 /** |
| 21 * The div element holding the completion text field. |
| 22 * @type {Element?} |
| 23 */ |
| 24 this.cmpFloatElement = null; |
| 25 |
| 26 /** |
| 27 * The completion text field element. |
| 28 * @type {Element?} |
| 29 */ |
| 30 this.cmpTextElement = null; |
| 31 |
| 32 /** |
| 33 * The div element for the page-wide background of the completion field. |
| 34 * @type {Element?} |
| 35 */ |
| 36 this.backgroundDivElement = null; |
| 37 |
| 38 /** |
| 39 * The div element which holds the text of the selected list item. |
| 40 * @type {Element?} |
| 41 * @private |
| 42 */ |
| 43 this.listElement_ = null; |
| 44 |
| 45 /** |
| 46 * The div element inside the completion div, holding the selected list item. |
| 47 * @type {Element?} |
| 48 * @private |
| 49 */ |
| 50 this.cmpDivElement_ = null; |
| 51 |
| 52 /** |
| 53 * Variable to hold the completion mode prompt string. |
| 54 * @type {string} |
| 55 * @private |
| 56 */ |
| 57 this.completionPromptStr_ = 'Enter Completion'; |
| 58 |
| 59 /** |
| 60 * Variable to hold the completion mode prompt string when the completion |
| 61 * list is empty. |
| 62 * @type {string} |
| 63 * @private |
| 64 */ |
| 65 this.noCompletionStr_ = 'No completions found'; |
| 66 |
| 67 /** |
| 68 * AxsJAX object |
| 69 * @type {AxsJAX?} |
| 70 * @private |
| 71 */ |
| 72 this.axsJAX_ = null; |
| 73 |
| 74 /** |
| 75 * The list of completions to select from. |
| 76 * @type {Array} |
| 77 * @private |
| 78 */ |
| 79 this.cmpList_ = []; |
| 80 |
| 81 /** |
| 82 * The navigation position in the list. |
| 83 * @type {number} |
| 84 * @private |
| 85 */ |
| 86 this.listPos_ = -1; |
| 87 |
| 88 /** |
| 89 * A collection of completion lists. |
| 90 * @type {Object} |
| 91 * @private |
| 92 */ |
| 93 this.managedCmpLists_ = new Array(); |
| 94 |
| 95 /** |
| 96 * The current position in the managed completion lists. |
| 97 * @type {number} |
| 98 * @private |
| 99 */ |
| 100 this.managedCmpListsPos_ = -1; |
| 101 |
| 102 /** |
| 103 * Whether to hide completion field on blur. |
| 104 * @type {boolean} |
| 105 * @private |
| 106 */ |
| 107 this.hideCmdFieldOnBlur_ = false; |
| 108 |
| 109 /** |
| 110 * Whether to show the page-wide background. |
| 111 * @type {boolean} |
| 112 * @private |
| 113 */ |
| 114 this.showBackgroundDiv_ = false; |
| 115 |
| 116 /** |
| 117 * Color of the page-wide background. |
| 118 * @type {string} |
| 119 * @private |
| 120 */ |
| 121 this.backgroundColor_ = '#000000'; |
| 122 |
| 123 /** |
| 124 * Transparency of the page-wide background 100 being fully |
| 125 * opaque and 0 being fully transparent. |
| 126 * @type {number} |
| 127 * @private |
| 128 */ |
| 129 this.backgroundTransparency_ = 0; |
| 130 |
| 131 /** |
| 132 * Function to callback on browsing or filtering. |
| 133 * @type {?function(string, string)} |
| 134 * @private |
| 135 */ |
| 136 this.browseCallback_ = null; |
| 137 |
| 138 /** |
| 139 * The HashMap which provides a context-based mapping |
| 140 * from keys to functions. |
| 141 * @type {?Object} |
| 142 * @private |
| 143 */ |
| 144 this.completionActionMap_ = null; |
| 145 |
| 146 /** |
| 147 * The completion handler. |
| 148 * handler = function(completion, index, elementId, args) {}. |
| 149 * @type {?function(string, string, Node, Array)} |
| 150 * @private |
| 151 */ |
| 152 this.completionHandler_ = null; |
| 153 |
| 154 /** |
| 155 * Function to callback on changing the managed completion list. |
| 156 * @type {?function(string)} |
| 157 * @private |
| 158 */ |
| 159 this.managedCompletionListCallback_ = null; |
| 160 |
| 161 this.nodeMap = {}; |
| 162 this.indexList_ = {}; |
| 163 if (axsJAX && PowerKey.isGecko) { |
| 164 this.axsJAX_ = axsJAX; |
| 165 } |
| 166 var self = this; |
| 167 this.attachHandlerAndListen(window, PowerKey.Event.RESIZE, |
| 168 function(evt) { |
| 169 self.onPageResize_.call(self, evt); |
| 170 }, null); |
| 171 }; |
| 172 |
| 173 |
| 174 /** |
| 175 * The reg exp indicating the pattern of the parameter to a completion. It |
| 176 * should start with '<', end wit '>' and contain only characters and numbers. |
| 177 * @type {RegExp} |
| 178 */ |
| 179 PowerKey.CMD_PARAM = /^\<[A-Z|a-z|0-9|\-|\_]+\>$/; |
| 180 |
| 181 |
| 182 /** |
| 183 * The reg exp to check if there are spaces, new lines or carriage returns at |
| 184 * the beginning of a string. |
| 185 * @type {RegExp} |
| 186 */ |
| 187 PowerKey.LEFT_TRIMMABLE = /^(\s|\r|\n)+/; |
| 188 |
| 189 |
| 190 /** |
| 191 * The reg exp to check if there are spaces, new lines or carriage returns at |
| 192 * the end of a string. |
| 193 * @type {RegExp} |
| 194 */ |
| 195 PowerKey.RIGHT_TRIMMABLE = /(\s|\r|\n)+$/; |
| 196 |
| 197 |
| 198 /** |
| 199 * Attaches event listener and sets the user specified handler or the |
| 200 * default handler if the action map is provided. |
| 201 * @param {EventTarget?} target The element to attach the event listerner to. |
| 202 * @param {string} event The event to listen for. |
| 203 * @notypecheck {Function?} handler. |
| 204 * @param {Function?} handler The event handler. |
| 205 * @param {Object?} actionMap The HashMap which provides a context-based |
| 206 * mapping from keys to functions. |
| 207 */ |
| 208 PowerKey.prototype.attachHandlerAndListen = function(target, |
| 209 event, |
| 210 handler, |
| 211 actionMap) { |
| 212 // Firefox |
| 213 if (PowerKey.isGecko && handler) { |
| 214 target.addEventListener(event, handler, true); |
| 215 } else if (PowerKey.isIE && handler) { // IE |
| 216 target.attachEvent(event, function(event) { |
| 217 handler(event);}); |
| 218 } |
| 219 // Use default handler if the action map is provided. |
| 220 if (actionMap) { |
| 221 actionMap = this.expandActionMap(actionMap); |
| 222 var handlerObj = new PowerKey.DefaultHandler(actionMap); |
| 223 var pkObj = this; |
| 224 this.attachHandlerAndListen(target, event, function(evt) { |
| 225 handlerObj.handler(evt, handlerObj, pkObj); |
| 226 }, null); |
| 227 } |
| 228 }; |
| 229 |
| 230 |
| 231 /** |
| 232 * Expand the action map by splitting multiple keys specified in a single |
| 233 * mapping separated by commas. |
| 234 * @param {Object} map The action map. |
| 235 * @return {Object} Returns the updated action map. |
| 236 */ |
| 237 PowerKey.prototype.expandActionMap = function(map) { |
| 238 for (var key in map) { |
| 239 var toks = key.split(','); |
| 240 if (toks.length > 1) { |
| 241 for (var i in toks) { |
| 242 map[toks[i]] = map[key]; |
| 243 } |
| 244 map[key] = null; |
| 245 } |
| 246 } |
| 247 return map; |
| 248 }; |
| 249 |
| 250 |
| 251 /** |
| 252 * Detaches event listener with the user specified event and handler. |
| 253 * @param {Element} target The element to detach the event listerner from. |
| 254 * @param {string} event The event to detach. |
| 255 * @param {Function} handler The event handler to stop calling. |
| 256 */ |
| 257 PowerKey.prototype.detachHandler = function(target, event, handler) { |
| 258 // Firefox |
| 259 if (PowerKey.isGecko) { |
| 260 target.removeEventListener(event, handler, true); |
| 261 } else if (PowerKey.isIE) { // IE |
| 262 target.detachEvent(event, handler); |
| 263 } |
| 264 }; |
| 265 |
| 266 |
| 267 /** |
| 268 * Creates a floating element for holding the completion shell's text field. |
| 269 * @param {Element} parent The element whose child this element will be. |
| 270 * @param {number} size The size of the completion text field in # of |
| 271 * characters. |
| 272 * @param {Function?} handler The completion handler. |
| 273 * handler = function(completion, index, node, args) {}. |
| 274 * @param {Object?} actionMap The object consisting of completion strings as |
| 275 * keys and functions as values. |
| 276 * @param {Array?} completionList The array of completions. |
| 277 * @param {boolean} browseOnly Whether the completion list is browse-only. |
| 278 */ |
| 279 PowerKey.prototype.createCompletionField = function(parent, |
| 280 size, |
| 281 handler, |
| 282 actionMap, |
| 283 completionList, |
| 284 browseOnly) { |
| 285 var self = this; |
| 286 var floatId, fieldId, oldCmdNode, divId, bgDivId; |
| 287 // If the completion field already exists, remove it and create a new one. |
| 288 if (this.cmpFloatElement) { |
| 289 // TODO: remove the handlers attached to cmpTextElement before removing |
| 290 // the completion field. The inline handler needs to be moved outside. |
| 291 this.cmpFloatElement.parentNode.removeChild(this.cmpFloatElement); |
| 292 } |
| 293 do { |
| 294 floatId = 'completionField_' + Math.floor(Math.random() * 1001); |
| 295 fieldId = 'txt_' + floatId; |
| 296 divId = 'div_' + floatId; |
| 297 bgDivId = 'bgdiv_' + floatId; |
| 298 oldCmdNode = document.getElementById(floatId); |
| 299 } while (oldCmdNode); |
| 300 |
| 301 if (this.backgroundDivElement) { |
| 302 this.backgroundDivElement.parentNode.removeChild( |
| 303 this.backgroundDivElement); |
| 304 } |
| 305 |
| 306 // The background element |
| 307 var bgNode = document.createElement('div'); |
| 308 bgNode.id = bgDivId; |
| 309 bgNode.style.display = 'none'; |
| 310 |
| 311 // create completion field element |
| 312 var cmpNode = document.createElement('div'); |
| 313 cmpNode.id = floatId; |
| 314 cmpNode.style.position = 'absolute'; |
| 315 |
| 316 // text field in which the user types the completion. |
| 317 var txtNode = document.createElement('input'); |
| 318 txtNode.type = 'text'; |
| 319 txtNode.id = fieldId; |
| 320 txtNode.size = size; |
| 321 txtNode.value = ''; |
| 322 txtNode.setAttribute('aria-owns', divId); |
| 323 txtNode.onkeypress = |
| 324 function(evt) { |
| 325 evt.stopPropagation(); |
| 326 if (evt.keyCode == PowerKey.keyCodes.TAB) { |
| 327 return false; |
| 328 } |
| 329 }; |
| 330 txtNode.readOnly = browseOnly; |
| 331 |
| 332 if (browseOnly) { |
| 333 txtNode.style.fontSize = 0; |
| 334 } |
| 335 |
| 336 // This div element holds the currently selected completion list item. |
| 337 var divNode = document.createElement('div'); |
| 338 divNode.id = divId; |
| 339 divNode.setAttribute('tabindex', 0); |
| 340 divNode.setAttribute('role', 'row'); |
| 341 |
| 342 cmpNode.appendChild(divNode); |
| 343 cmpNode.appendChild(txtNode); |
| 344 parent.appendChild(bgNode); |
| 345 parent.appendChild(cmpNode); |
| 346 |
| 347 this.cmpFloatElement = cmpNode; |
| 348 this.cmpTextElement = txtNode; |
| 349 this.backgroundDivElement = bgNode; |
| 350 this.cmpDivElement_ = divNode; |
| 351 this.listElement_ = null; |
| 352 |
| 353 this.cmpFloatElement.className = 'pkHiddenStatus'; |
| 354 this.cmpTextElement.className = 'pkOpaqueCompletionText'; |
| 355 this.backgroundDivElement.className = 'pkBackgroundHide'; |
| 356 |
| 357 this.completionActionMap_ = actionMap; |
| 358 this.completionHandler_ = handler; |
| 359 |
| 360 // Initialize completion list |
| 361 if (completionList) { |
| 362 this.addCompletionListByName(this.context, completionList, |
| 363 this.completionPromptStr_); |
| 364 this.setCompletionListByName(this.context); |
| 365 } |
| 366 |
| 367 // filter the completion list on keyup if it is not UP or DOWN arrow. |
| 368 this.attachHandlerAndListen(this.cmpTextElement, PowerKey.Event.KEYUP, |
| 369 function(evt) { |
| 370 self.handleCompletionKeyUp_.call(self, evt); |
| 371 }, null); |
| 372 |
| 373 this.attachHandlerAndListen(this.cmpTextElement, PowerKey.Event.KEYDOWN, |
| 374 function(evt) { |
| 375 self.handleCompletionKeyDown_.call(self, evt); |
| 376 }, null); |
| 377 |
| 378 this.attachHandlerAndListen(this.cmpTextElement, PowerKey.Event.BLUR, |
| 379 function(evt) { |
| 380 if (self.hideCmdFieldOnBlur_) { |
| 381 self.updateCompletionField(PowerKey.status.HIDDEN); |
| 382 } |
| 383 }, null); |
| 384 }; |
| 385 |
| 386 |
| 387 /** |
| 388 * Sets if the completion field is browse only. |
| 389 * @param {boolean} browseOnly If the completion field is browse only. |
| 390 */ |
| 391 PowerKey.prototype.setBrowseOnly = function(browseOnly) { |
| 392 if (this.cmpTextElement) { |
| 393 this.cmpTextElement.readOnly = browseOnly; |
| 394 } |
| 395 }; |
| 396 |
| 397 |
| 398 /** |
| 399 * Tells whether to hide the completion field when focus is lost. |
| 400 * @param {boolean} hide If true, hide the completion field on blur. |
| 401 */ |
| 402 PowerKey.prototype.setAutoHideCompletionField = function(hide) { |
| 403 this.hideCmdFieldOnBlur_ = hide; |
| 404 }; |
| 405 |
| 406 |
| 407 /** |
| 408 * Sets the function to be called back when browsing/filtering the list. |
| 409 * @param {Function} callback The callback function. |
| 410 */ |
| 411 PowerKey.prototype.setBrowseCallback = function(callback) { |
| 412 this.browseCallback_ = callback; |
| 413 }; |
| 414 |
| 415 |
| 416 /** |
| 417 * Sets the map which provides a context-based mapping |
| 418 * from completion values (keys) to functions. Note that |
| 419 * setting this map implies use of the default completion |
| 420 * handler and setting you custom one will have no effect. |
| 421 * A mapping is triggered after the user has selected an |
| 422 * action form the auto-completion list via pressing |
| 423 * ENTER. |
| 424 * @param {Object} completionActionMap The map instance. |
| 425 */ |
| 426 PowerKey.prototype.setCompletionActionMap = function(completionActionMap) { |
| 427 this.completionActionMap_ = completionActionMap; |
| 428 }; |
| 429 |
| 430 |
| 431 /** |
| 432 * Sets the handler which provides custom actions for completion |
| 433 * values. Note that setting a completion map implies use of the |
| 434 * default completion handler therefore setting the completion |
| 435 * handler will have no effect. The handler signature is as follows: |
| 436 * |
| 437 * handler = function(completion, index, elementId, args) {}. |
| 438 * |
| 439 * @param {Function} completionHandler The map instance. |
| 440 */ |
| 441 PowerKey.prototype.setCompletionHandler = function(completionHandler) { |
| 442 this.completionHandler_ = completionHandler; |
| 443 }; |
| 444 |
| 445 |
| 446 /** |
| 447 * Sets the function to be called back when changing the managed |
| 448 * (named) completion list. |
| 449 * @param {Function} callback The callback function. |
| 450 */ |
| 451 PowerKey.prototype.setManagedCompletionListCallback = function(callback) { |
| 452 this.managedCompletionListCallback_ = callback; |
| 453 }; |
| 454 |
| 455 |
| 456 /** |
| 457 * Sets the label to be displayed and spoken, when the |
| 458 * completion field is made visible. |
| 459 * @param {string} str The string to display. |
| 460 */ |
| 461 PowerKey.prototype.setCompletionPromptStr = function(str) { |
| 462 this.completionPromptStr_ = str; |
| 463 }; |
| 464 |
| 465 |
| 466 /** |
| 467 * Sets the label to be displayed and spoken, when there are no completions |
| 468 * in the completion list. |
| 469 * @param {string} str The string to display. |
| 470 */ |
| 471 PowerKey.prototype.setNoCompletionStr = function(str) { |
| 472 this.noCompletionStr_ = str; |
| 473 }; |
| 474 |
| 475 |
| 476 /** |
| 477 * Sets the completion list. |
| 478 * @param {Array} list The array to be used as the completion list. |
| 479 */ |
| 480 PowerKey.prototype.setCompletionList = function(list) { |
| 481 if (!list) { |
| 482 return; |
| 483 } |
| 484 this.managedCmpListsPos_ = -1; |
| 485 this.cmpList_ = list; |
| 486 this.filterList_ = this.cmpList_; |
| 487 this.indexList_ = {}; |
| 488 for (var i = 0, cmp; cmp = this.cmpList_[i]; i++) { |
| 489 this.indexList_[cmp.toLowerCase()] = i; |
| 490 } |
| 491 this.listPos_ = -1; |
| 492 }; |
| 493 |
| 494 |
| 495 /** |
| 496 * Adds a completion list. A completion list is identified by its |
| 497 * name. If a completion list by that name already exists, |
| 498 * then the Ids of the strings in the existing list are removed. |
| 499 * @param {string} name The name of the completion list. |
| 500 * @param {Array} list The array to be used as the completion list. |
| 501 * @param {string} prompt The prompt for this completion list. |
| 502 */ |
| 503 PowerKey.prototype.addCompletionListByName = function(name, list, prompt) { |
| 504 var oldManagedCmpList = this.getManagedCompletionListByName_(name); |
| 505 if (oldManagedCmpList) { |
| 506 for (var i = 0, item; item = oldManagedCmpList.values[i]; i++) { |
| 507 this.nodeMap[item] = null; |
| 508 } |
| 509 oldManagedCmpList.list = list; |
| 510 oldManagedCmpList.completionPromptStr = prompt; |
| 511 } else { |
| 512 var newManagedCmpList = new Object(); |
| 513 newManagedCmpList.values = list; |
| 514 newManagedCmpList.name = name; |
| 515 newManagedCmpList.completionPromptStr = prompt; |
| 516 newManagedCmpList.index = this.managedCmpLists_.length; |
| 517 this.managedCmpLists_.push(newManagedCmpList); |
| 518 } |
| 519 }; |
| 520 |
| 521 |
| 522 /** |
| 523 * Sets the completion list. |
| 524 * @param {string} name The name of the list to be used for completion. |
| 525 */ |
| 526 PowerKey.prototype.setCompletionListByName = function(name) { |
| 527 var cmpList = this.getManagedCompletionListByName_(name); |
| 528 if (!cmpList) { |
| 529 return; |
| 530 } |
| 531 this.setManagedCompletionList_(cmpList.index); |
| 532 }; |
| 533 |
| 534 |
| 535 /** |
| 536 * Gets a completion list given its name. |
| 537 * @param {string} name The name of the completion list. |
| 538 * @return {Object?} The list with the given name. |
| 539 * @private |
| 540 */ |
| 541 PowerKey.prototype.getManagedCompletionListByName_ = function(name) { |
| 542 for (var i = 0, list; list = this.managedCmpLists_[i]; i++) { |
| 543 if (list.name == name) { |
| 544 return list; |
| 545 } |
| 546 } |
| 547 return null; |
| 548 }; |
| 549 |
| 550 |
| 551 /** |
| 552 * Sets the color and transparency of the background. |
| 553 * @param {string?} listName Name of the completion list for which to set |
| 554 * the background property. |
| 555 * @param {boolean} show Whether to show the background div when completion |
| 556 * field is made visible. |
| 557 * @param {string?} color The color of the background. |
| 558 * @param {number?} transparency The transparency level, 100 being fully |
| 559 * opaque and 0 being fully transparent. |
| 560 */ |
| 561 PowerKey.prototype.setBackgroundStyle = function(listName, |
| 562 show, |
| 563 color, |
| 564 transparency) { |
| 565 if (listName) { |
| 566 var managedCmpList = this.getManagedCompletionListByName_(listName); |
| 567 if (managedCmpList) { |
| 568 managedCmpList.backgroundShow = show; |
| 569 managedCmpList.backgroundColor = color; |
| 570 managedCmpList.backgroundTransparency = transparency; |
| 571 } |
| 572 } else { |
| 573 this.showBackgroundDiv_ = show; |
| 574 if (color) { |
| 575 this.backgroundColor_ = color; |
| 576 } |
| 577 if (transparency) { |
| 578 this.backgroundTransparency_ = transparency; |
| 579 } |
| 580 } |
| 581 }; |
| 582 |
| 583 |
| 584 /** |
| 585 * Sets the style of the completion field. |
| 586 * @param {string} textColor Color of the completion text. |
| 587 * @param {number} textSize Size of the completion text. |
| 588 * @param {string} bgColor Background color of the completion field. |
| 589 * @param {number} transparency Background tranparency, 100 being fully |
| 590 * opaque and 0 being fully transparent. |
| 591 * @param {string} fontStyle Font style of the completion text. |
| 592 * @param {string} fontFamily Font family of the completion text. |
| 593 */ |
| 594 PowerKey.prototype.setCompletionFieldStyle = function(textColor, |
| 595 textSize, |
| 596 bgColor, |
| 597 transparency, |
| 598 fontStyle, |
| 599 fontFamily) { |
| 600 if (textSize) { |
| 601 this.cmpFloatElement.style.fontSize = textSize + 'px'; |
| 602 this.cmpTextElement.style.fontSize = textSize + 'px'; |
| 603 this.cmpTextElement.style.height = (textSize + 5) + 'px'; |
| 604 } |
| 605 if (textColor) { |
| 606 this.cmpFloatElement.style.color = textColor; |
| 607 this.cmpTextElement.style.color = textColor; |
| 608 } |
| 609 if (bgColor) { |
| 610 this.cmpFloatElement.style.backgroundColor = bgColor; |
| 611 this.cmpTextElement.style.backgroundColor = bgColor; |
| 612 } |
| 613 if (transparency) { |
| 614 this.cmpFloatElement.style.setProperty('-moz-opacity', |
| 615 '' + (transparency / 100), ''); |
| 616 } |
| 617 if (fontStyle) { |
| 618 this.cmpFloatElement.style.fontStyle = fontStyle; |
| 619 this.cmpTextElement.style.fontStyle = fontStyle; |
| 620 } |
| 621 if (fontFamily) { |
| 622 this.cmpFloatElement.style.fontFamily = fontFamily; |
| 623 this.cmpTextElement.style.fontFamily = fontFamily; |
| 624 } |
| 625 }; |
| 626 |
| 627 |
| 628 /** |
| 629 * Updates the completion field element with the new visibility status |
| 630 * and location parameters. |
| 631 * @param {string} status Indicates whether the completion field should be |
| 632 * made PowerKey.status.VISIBLE or PowerKey.status.HIDDEN. |
| 633 * @param {boolean} opt_resize Indicates whether resizing is necessary. |
| 634 * @param {number} opt_top The y-coordinate pixel location of the top |
| 635 * border of the element. |
| 636 * @param {number} opt_left The x-coordinate pixel location of the left |
| 637 * border of the element. |
| 638 */ |
| 639 PowerKey.prototype.updateCompletionField = function(status, |
| 640 opt_resize, |
| 641 opt_top, |
| 642 opt_left) { |
| 643 if (!this.cmpFloatElement) { |
| 644 return; |
| 645 } |
| 646 var backgoundShow = this.showBackgroundDiv_; |
| 647 var backgroundColor = this.backgroundColor_; |
| 648 var backgroundTransparency = this.backgroundTransparency_; |
| 649 var managedCmpList = undefined; |
| 650 if (this.managedCmpListsPos_ > -1) { |
| 651 managedCmpList = this.managedCmpLists_[this.managedCmpListsPos_]; |
| 652 if (managedCmpList.backgoundShow) { |
| 653 backgoundShow = managedCmpList.backgoundShow; |
| 654 } |
| 655 if (managedCmpList.backgroundColor) { |
| 656 backgroundColor = managedCmpList.backgroundColor; |
| 657 } |
| 658 if (managedCmpList.backgroundTransparency) { |
| 659 managedCmpList.backgroundTransparency = backgroundTransparency; |
| 660 } |
| 661 } |
| 662 if (status == PowerKey.status.VISIBLE) { |
| 663 if (this.cmpFloatElement.className == 'pkHiddenStatus' || |
| 664 this.listPos_ < 0) { |
| 665 if (managedCmpList) { |
| 666 this.setListElement_(managedCmpList.completionPromptStr); |
| 667 } else { |
| 668 this.setListElement_(this.completionPromptStr_); |
| 669 } |
| 670 } |
| 671 this.showBackground_(backgoundShow, backgroundColor, |
| 672 backgroundTransparency); |
| 673 this.cmpFloatElement.className = 'pkVisibleStatus'; |
| 674 // Need to do this for IE. Setting focus immediately after making it |
| 675 // visible generates an error. Hence have to set the timeout. |
| 676 var elem = this.cmpTextElement; |
| 677 window.setTimeout(function() {elem.focus();}, 0); |
| 678 } else if (status == PowerKey.status.HIDDEN) { |
| 679 if (PowerKey.isIE && this.listElement_) { |
| 680 this.listElement_.innerText = ''; |
| 681 } else if (this.listElement_) { |
| 682 this.listElement_.textContent = ''; |
| 683 } |
| 684 this.showBackground_(false, backgroundColor, backgroundTransparency); |
| 685 this.cmpFloatElement.className = 'pkHiddenStatus'; |
| 686 this.cmpTextElement.value = ''; |
| 687 this.listPos_ = -1; |
| 688 } |
| 689 if (opt_resize) { |
| 690 var viewportSz = PowerKey.getViewportSize(); |
| 691 if (!opt_top) { |
| 692 opt_top = viewportSz.height - this.cmpFloatElement.offsetHeight; |
| 693 } |
| 694 if (!opt_left) { |
| 695 opt_left = 0; |
| 696 } |
| 697 this.cmpFloatElement.style.top = opt_top; |
| 698 this.cmpFloatElement.style.left = opt_left; |
| 699 } |
| 700 }; |
| 701 |
| 702 |
| 703 /** |
| 704 * Creates the list of completions from the text content of the elements |
| 705 * obtained from the xpath which satisfy the function func. |
| 706 * @param {string} tags The tags to be selected. |
| 707 * @param {Function} func Only those elements are considered for which |
| 708 * this function returns true. |
| 709 * @param {Function?} getText Function to get text for this list element. |
| 710 * @param {boolean} newList If this is true, all entries in idMap |
| 711 * are erased, and a new mapping of completions and IDs is created. |
| 712 * @return {Array} The array of completion strings. |
| 713 */ |
| 714 PowerKey.prototype.createCompletionList = function(tags, |
| 715 func, |
| 716 getText, |
| 717 newList) { |
| 718 var cmpList = new Array(); |
| 719 var tagArray = tags.split(/\s+/); |
| 720 if (newList) { |
| 721 delete this.nodeMap; |
| 722 this.nodeMap = new Object(); |
| 723 } |
| 724 for (var j = 0, tag; tag = tagArray[j]; j++) { |
| 725 var nodeArray = document.getElementsByTagName(tag); |
| 726 for (var i = 0, node; node = nodeArray[i]; i++) { |
| 727 if (func(node)) { |
| 728 if (getText) { |
| 729 var label = getText(node); |
| 730 } else { |
| 731 label = PowerKey.isIE ? |
| 732 node.innerText : node.textContent; |
| 733 } |
| 734 if (label) { |
| 735 label = PowerKey.rightTrim(PowerKey.leftTrim(label)); |
| 736 label = label.replace(/\n/g, ''); |
| 737 if (label.toLowerCase().indexOf('ctrl+') === 0) { |
| 738 label = label.substring(6); |
| 739 } |
| 740 cmpList.push(label); |
| 741 if (String(this.nodeMap[label.toLowerCase()]) == 'undefined') { |
| 742 this.nodeMap[label.toLowerCase()] = node; |
| 743 } |
| 744 } |
| 745 } |
| 746 } |
| 747 } |
| 748 return cmpList; |
| 749 }; |
| 750 |
| 751 |
| 752 /** |
| 753 * Resizes the background upon page resize if the background is |
| 754 * currently showing. |
| 755 * @param {Event} evt The resize event. |
| 756 * @private |
| 757 */ |
| 758 PowerKey.prototype.onPageResize_ = function(evt) { |
| 759 // wait for the resize to complete so that the new |
| 760 // viewport size is available. |
| 761 var self = this; |
| 762 window.setTimeout(function() { |
| 763 if (self.showBackgroundDiv_ && |
| 764 self.backgroundDivElement.style.display == 'block') { |
| 765 self.showBackground_(self.showBackgroundDiv_, self.backgroundColor_, |
| 766 self.backgroundTransparency_); |
| 767 } |
| 768 }, 0); |
| 769 }; |
| 770 |
| 771 |
| 772 /** |
| 773 * Handle keyup events. If the key is not an arrow key, filter the list by the |
| 774 * contents of the completion text field. |
| 775 * @param {Object} evt The key event object. |
| 776 * @private |
| 777 */ |
| 778 PowerKey.prototype.handleCompletionKeyUp_ = function(evt) { |
| 779 if (this.cmpTextElement.value.length === 0) { |
| 780 this.filterList_ = this.cmpList_; |
| 781 } |
| 782 if (evt.keyCode != PowerKey.keyCodes.ARROWUP && |
| 783 evt.keyCode != PowerKey.keyCodes.ARROWDOWN && |
| 784 evt.keyCode != PowerKey.keyCodes.ARROWLEFT && |
| 785 evt.keyCode != PowerKey.keyCodes.ARROWRIGHT && |
| 786 evt.keyCode != PowerKey.keyCodes.ENTER && |
| 787 evt.keyCode != PowerKey.keyCodes.TAB && |
| 788 evt.keyCode != PowerKey.keyCodes.ESC) { |
| 789 |
| 790 if (this.cmpTextElement.value.length) { |
| 791 this.filterList_ = this.getWordFilterMatches_(this.cmpList_, |
| 792 this.cmpTextElement.value, 50); |
| 793 this.listPos_ = -1; |
| 794 if (this.filterList_.length > 0) { |
| 795 this.setListElement_(this.filterList_[0]); |
| 796 this.listPos_ = 0; |
| 797 } else { |
| 798 this.setListElement_(this.noCompletionStr_); |
| 799 } |
| 800 } else { |
| 801 if (this.managedCmpListsPos_ > -1) { |
| 802 var managedCmpList = this.managedCmpLists_[this.managedCmpListsPos_]; |
| 803 this.setListElement_(managedCmpList.completionPromptStr); |
| 804 } else { |
| 805 this.setListElement_(this.completionPromptStr_); |
| 806 } |
| 807 } |
| 808 } |
| 809 // Handle ENTER key pressed in the completion field. |
| 810 if (evt.keyCode == PowerKey.keyCodes.ENTER) { |
| 811 if (this.cmpTextElement.readOnly) { |
| 812 return; |
| 813 } |
| 814 // Select current filtered list item. |
| 815 if (this.filterList_ && |
| 816 this.filterList_.length > 0 && |
| 817 this.filterList_[this.listPos_] && |
| 818 this.cmpTextElement.value != this.filterList_[this.listPos_] && |
| 819 // Does not have parameters or is incomplete |
| 820 (this.filterList_[this.listPos_].indexOf('<') < 0 || |
| 821 (this.filterList_[this.listPos_].indexOf('<') >= 0 && |
| 822 this.filterList_[this.listPos_].split(' ').length > |
| 823 this.cmpTextElement.value.split(' ').length))) { |
| 824 this.selectCurrentListItem_(); |
| 825 } |
| 826 var str = this.cmpTextElement.value; |
| 827 var originalCmd = (PowerKey.isIE ? this.listElement_.innerText : |
| 828 this.listElement_.textContent).toLowerCase(); |
| 829 // Change only the basic portion (non-argument) of the selection to lower |
| 830 // case. For ex: In 'Watch Video funnySeries', only 'Watch Video' is |
| 831 // changed to lower case. |
| 832 var pos = originalCmd.indexOf('<'); |
| 833 var baseCmd; |
| 834 if (pos >= 0) { |
| 835 baseCmd = str.substr(0, pos - 1).toLowerCase(); |
| 836 str = baseCmd + ' ' + str.substr(pos); |
| 837 } else { |
| 838 str = str.toLowerCase(); |
| 839 baseCmd = str; |
| 840 } |
| 841 var handled = false; |
| 842 if (this.completionActionMap_) { |
| 843 handled = this.actionHandler_.call(this, str, |
| 844 originalCmd, this.completionActionMap_); |
| 845 } |
| 846 if (this.completionHandler_ && !handled) { |
| 847 var args = this.getArguments_(str, originalCmd); |
| 848 this.completionHandler_(baseCmd, this.indexList_[originalCmd], |
| 849 this.nodeMap[originalCmd], args); |
| 850 } |
| 851 this.cmpTextElement.value = ''; |
| 852 } else if (evt.keyCode == PowerKey.keyCodes.ESC) { |
| 853 this.cmpTextElement.blur(); |
| 854 this.updateCompletionField(PowerKey.status.HIDDEN); |
| 855 } |
| 856 }; |
| 857 |
| 858 |
| 859 /** |
| 860 * Handle keydown events. If the key is an UP/DOWN arrow key, navigates to the |
| 861 * previous and next item in the filtered list. |
| 862 * @param {Object} evt The key event object. |
| 863 * @private |
| 864 */ |
| 865 PowerKey.prototype.handleCompletionKeyDown_ = function(evt) { |
| 866 // Handle UP arrow key |
| 867 if (evt.keyCode == PowerKey.keyCodes.ARROWUP && |
| 868 this.filterList_ && |
| 869 this.filterList_.length > 0) { |
| 870 this.prevListItem_(); |
| 871 } |
| 872 // Handle DOWN arrow key |
| 873 else if (evt.keyCode == PowerKey.keyCodes.ARROWDOWN && |
| 874 this.filterList_ && |
| 875 this.filterList_.length > 0) { |
| 876 this.nextListItem_(); |
| 877 if (evt.preventDefault) { |
| 878 evt.preventDefault(); |
| 879 } |
| 880 } |
| 881 // Handle LEFT arrow key |
| 882 if (evt.keyCode == PowerKey.keyCodes.ARROWLEFT && |
| 883 this.cmpTextElement.readOnly && |
| 884 this.managedCmpLists_.length > 0) { |
| 885 this.prevManagedCompletionList_(); |
| 886 } |
| 887 // Handle RIGHT arrow key |
| 888 else if (evt.keyCode == PowerKey.keyCodes.ARROWRIGHT && |
| 889 this.cmpTextElement.readOnly && |
| 890 this.managedCmpLists_.length > 0) { |
| 891 this.nextManagedCompletionList_(); |
| 892 } |
| 893 // On TAB, keep the focus in the completion field. |
| 894 else if (evt.keyCode == PowerKey.keyCodes.TAB) { |
| 895 if (this.filterList_ && this.filterList_.length > 0) { |
| 896 this.selectCurrentListItem_(); |
| 897 } |
| 898 if (evt.preventDefault) { |
| 899 evt.preventDefault(); |
| 900 } |
| 901 } |
| 902 }; |
| 903 |
| 904 |
| 905 /** |
| 906 * Shows or hides the page-wide background div. Used internally by |
| 907 * updateCompletionField(). |
| 908 * @param {boolean} show Whether to show the background or not. |
| 909 * @param {string} color The color of the background. |
| 910 * @param {number} transparency The transparency level, 100 being fully opaque |
| 911 * and 0 being fully transparent. |
| 912 * @private |
| 913 */ |
| 914 PowerKey.prototype.showBackground_ = function(show, color, transparency) { |
| 915 if (show) { |
| 916 this.backgroundDivElement.className = 'pkBackgroundShow'; |
| 917 this.backgroundDivElement.style.display = 'block'; |
| 918 var viewportSz = PowerKey.getViewportSize(); |
| 919 this.backgroundDivElement.style.width = viewportSz.width + 'px'; |
| 920 this.backgroundDivElement.style.height = viewportSz.height + 'px'; |
| 921 if (color) { |
| 922 this.backgroundDivElement.style.backgroundColor = color; |
| 923 } |
| 924 if (transparency) { |
| 925 this.backgroundDivElement.style.setProperty('-moz-opacity', |
| 926 '' + (transparency / 100), ''); |
| 927 } |
| 928 } else { |
| 929 this.backgroundDivElement.style.display = 'none'; |
| 930 } |
| 931 }; |
| 932 |
| 933 |
| 934 /** |
| 935 * Splits token into words and filters the rows by these words. |
| 936 * @param {Array} list An array of all completions. |
| 937 * @param {string} token Token to match. |
| 938 * @param {number} maxMatches Max number of matches to return. |
| 939 * @return {Array} matches Returns the array of matching rows. |
| 940 * @private |
| 941 */ |
| 942 PowerKey.prototype.getWordFilterMatches_ = function(list, token, maxMatches) { |
| 943 var matches = list; |
| 944 var rows = list; |
| 945 var words = token.split(' '); |
| 946 for (var i = 0, word; word = words[i]; i++) { |
| 947 rows = matches; |
| 948 matches = []; |
| 949 if (word !== '') { |
| 950 var escapedToken = PowerKey.regExpEscape(word); |
| 951 var matcher = new RegExp('(^|\\W+)' + escapedToken, 'i'); |
| 952 for (var j = 0, row; row = rows[j]; j++) { |
| 953 if (String(row).match(matcher)) { |
| 954 matches.push(row); |
| 955 } |
| 956 } |
| 957 } |
| 958 } |
| 959 rows = list; |
| 960 for (j = 0; row = rows[j]; j++) { |
| 961 var parts = row.split(' '); |
| 962 var cmpArray = []; |
| 963 var part; |
| 964 for (i = 0; part = parts[i]; i++) { |
| 965 if (part.charAt(0) == '<') { |
| 966 break; |
| 967 } |
| 968 cmpArray.push(part); |
| 969 } |
| 970 var cmp = cmpArray.join(' '); |
| 971 if (token.indexOf(cmp) === 0) { |
| 972 matches.push(row); |
| 973 } |
| 974 } |
| 975 if (matches.length > maxMatches) { |
| 976 matches.slice(0, maxMatches - 1); |
| 977 } |
| 978 return matches; |
| 979 }; |
| 980 |
| 981 |
| 982 /** |
| 983 * Compares the original and user-entered completion and returns the array |
| 984 * of arguments if any, otherwise returns null. |
| 985 * @param {string} str The string to parse for arguments. |
| 986 * @param {string} originalCmd The original completion. |
| 987 * @return {Array} Returns an array of completion arguments. |
| 988 * @private |
| 989 */ |
| 990 PowerKey.prototype.getArguments_ = function(str, originalCmd) { |
| 991 str = str.replace(/\s+/g, ' '); |
| 992 originalCmd = originalCmd.replace(/\s+/g, ' '); |
| 993 var pos = originalCmd.indexOf('<'); |
| 994 if (pos < 0) { |
| 995 return []; |
| 996 } |
| 997 originalCmd = originalCmd.substr(pos); |
| 998 str = str.substr(pos); |
| 999 var strTokens = str.split(','); |
| 1000 var ostrTokens = originalCmd.split(','); |
| 1001 if (strTokens.length != ostrTokens.length) { |
| 1002 return []; |
| 1003 } |
| 1004 var args = []; |
| 1005 for (var i = 0, j = 0, token1, token2; |
| 1006 (token1 = strTokens[i]) && (token2 = ostrTokens[i]); |
| 1007 i++) { |
| 1008 token1 = PowerKey.leftTrim(PowerKey.rightTrim(token1)); |
| 1009 token2 = PowerKey.leftTrim(PowerKey.rightTrim(token2)); |
| 1010 if (token2.match(PowerKey.CMD_PARAM)) { |
| 1011 args.push(token1); |
| 1012 } |
| 1013 } |
| 1014 return args; |
| 1015 }; |
| 1016 |
| 1017 |
| 1018 /** |
| 1019 * The default completion handler: executes the appropriate functions |
| 1020 * by looking at the action map. |
| 1021 * @param {string} act The action/selection to be handled. |
| 1022 * @param {string} originalCmd The original format of the completion without |
| 1023 * the final parameter values. |
| 1024 * @param {Object} actionMap The HashMap consisting of completion strings |
| 1025 * as keys and functions as values. |
| 1026 * @return {boolean} Whether the completion was successfully handled. |
| 1027 * @private |
| 1028 */ |
| 1029 PowerKey.prototype.actionHandler_ = function(act, |
| 1030 originalCmd, |
| 1031 actionMap) { |
| 1032 var actionObj = actionMap[originalCmd]; |
| 1033 if (actionObj && actionObj[this.context]) { |
| 1034 var func = actionObj[this.context]; |
| 1035 var args = this.getArguments_(act, originalCmd); |
| 1036 if (func instanceof Function) { |
| 1037 window.setTimeout(func(args), 0); |
| 1038 } else { |
| 1039 //TODO: Remove this if it is not used. Prefer the above instead. |
| 1040 window.setTimeout(func + '(args)', 0); |
| 1041 } |
| 1042 return true; |
| 1043 } else { |
| 1044 return false; |
| 1045 } |
| 1046 }; |
| 1047 |
| 1048 /** |
| 1049 * Displays the previous item in the filtered list. |
| 1050 * @private |
| 1051 */ |
| 1052 PowerKey.prototype.prevListItem_ = function() { |
| 1053 if (this.listPos_ < 0) { |
| 1054 this.listPos_ = 0; |
| 1055 } |
| 1056 this.listPos_ = (this.listPos_ || this.filterList_.length) - 1; |
| 1057 if (this.listPos_ >= 0) { |
| 1058 this.setListElement_(this.filterList_[this.listPos_]); |
| 1059 } |
| 1060 }; |
| 1061 |
| 1062 |
| 1063 /** |
| 1064 * Displays the next item in the filtered list. |
| 1065 * @private |
| 1066 */ |
| 1067 PowerKey.prototype.nextListItem_ = function() { |
| 1068 this.listPos_ = (this.listPos_ + 1) % this.filterList_.length; |
| 1069 if (this.listPos_ < this.filterList_.length) { |
| 1070 this.setListElement_(this.filterList_[this.listPos_]); |
| 1071 } |
| 1072 }; |
| 1073 |
| 1074 |
| 1075 /** |
| 1076 * Displays the previous managed (named) completion list. |
| 1077 * @private |
| 1078 */ |
| 1079 PowerKey.prototype.prevManagedCompletionList_ = function() { |
| 1080 var completionListsPos = this.managedCmpListsPos_ - 1; |
| 1081 if (completionListsPos < 0) { |
| 1082 completionListsPos = this.managedCmpLists_.length - 1; |
| 1083 } |
| 1084 this.setManagedCompletionList_(completionListsPos); |
| 1085 }; |
| 1086 |
| 1087 |
| 1088 /** |
| 1089 * Displays the next managed (named) completion list. |
| 1090 * @private |
| 1091 */ |
| 1092 PowerKey.prototype.nextManagedCompletionList_ = function() { |
| 1093 var completionListsPos = this.managedCmpListsPos_ + 1; |
| 1094 if (completionListsPos >= this.managedCmpLists_.length) { |
| 1095 completionListsPos = 0; |
| 1096 } |
| 1097 this.setManagedCompletionList_(completionListsPos); |
| 1098 }; |
| 1099 |
| 1100 |
| 1101 /** |
| 1102 * Sets the managed (named) completion list given its position. |
| 1103 * @param {number} managedCmpListsPos Always valid list position. |
| 1104 * @private |
| 1105 */ |
| 1106 PowerKey.prototype.setManagedCompletionList_ = function(managedCmpListsPos) { |
| 1107 var managedCmpList = this.managedCmpLists_[managedCmpListsPos]; |
| 1108 this.context = managedCmpList.name; |
| 1109 this.setCompletionList(managedCmpList.values); |
| 1110 this.managedCmpListsPos_ = managedCmpListsPos; |
| 1111 var status = (this.cmpFloatElement.className == 'pkVisibleStatus') ? |
| 1112 PowerKey.status.VISIBLE : PowerKey.status.HIDDEN; |
| 1113 this.updateCompletionField(status); |
| 1114 if (this.managedCompletionListCallback_) { |
| 1115 this.managedCompletionListCallback_(managedCmpList.name); |
| 1116 } |
| 1117 }; |
| 1118 |
| 1119 |
| 1120 /** |
| 1121 * Selects the current completion from the list, displays it in the completion |
| 1122 * text field and speaks it. |
| 1123 * @private |
| 1124 */ |
| 1125 PowerKey.prototype.selectCurrentListItem_ = function() { |
| 1126 this.cmpTextElement.value = |
| 1127 this.filterList_[this.listPos_ >= 0 ? this.listPos_ : 0]; |
| 1128 this.filterList_ = this.getWordFilterMatches_(this.cmpList_, |
| 1129 this.cmpTextElement.value, 50); |
| 1130 if (this.axsJAX_ && PowerKey.isGecko) { |
| 1131 this.axsJAX_.speakTextViaNode(this.cmpTextElement.value); |
| 1132 } |
| 1133 this.listPos_ = 0; |
| 1134 }; |
| 1135 |
| 1136 |
| 1137 /** |
| 1138 * Speaks the element of the filtered list which is currently selected. |
| 1139 * @param {string} text The text to be displayed as the completion field's |
| 1140 * label (usually the selected list element itself). |
| 1141 * @private |
| 1142 */ |
| 1143 PowerKey.prototype.setListElement_ = function(text) { |
| 1144 if (!this.listElement_) { |
| 1145 this.listElement_ = document.createElement('div'); |
| 1146 this.listElement_.id = 'listElem_' + Math.floor(Math.random() * 1001); |
| 1147 this.cmpDivElement_.appendChild(this.listElement_); |
| 1148 } |
| 1149 if (PowerKey.isIE) { |
| 1150 this.listElement_.innerText = text; |
| 1151 } else { |
| 1152 this.listElement_.textContent = text; |
| 1153 } |
| 1154 if (this.browseCallback_) { |
| 1155 this.browseCallback_(text, this.indexList_[text.toLowerCase()]); |
| 1156 } |
| 1157 if (this.axsJAX_ && PowerKey.isGecko) { |
| 1158 this.axsJAX_.speakNode(this.listElement_, false); |
| 1159 } |
| 1160 }; |
| 1161 |
| 1162 /** |
| 1163 * Calls the class level setDefaultCSSStyle function. |
| 1164 * This allows PowerKey objects to be used without causing |
| 1165 * an additional dependency if they are only used optionally. |
| 1166 */ |
| 1167 PowerKey.prototype.setDefaultCSSStyle = function() { |
| 1168 PowerKey.setDefaultCSSStyle(); |
| 1169 }; |
| 1170 |
| 1171 // Methods in the PowerKey class end here. |
| 1172 |
| 1173 |
| 1174 /** |
| 1175 * A class which creates an event handler with a pre-specified action map. |
| 1176 * @param {Object} map A HashMap which holds key-function bindings. |
| 1177 * @constructor |
| 1178 */ |
| 1179 PowerKey.DefaultHandler = function(map) { |
| 1180 /** |
| 1181 * HashMap holding the key-function bindings. |
| 1182 * @type {Object} |
| 1183 */ |
| 1184 this.actionMap = map; |
| 1185 }; |
| 1186 |
| 1187 |
| 1188 /** |
| 1189 * The event handler to be called called inside the original event handler. |
| 1190 * @param {Object} evt The event object passed to the event handler. |
| 1191 * @param {PowerKey.DefaultHandler} handlerObj A reference to this. |
| 1192 * @param {PowerKey} pkObj An object of the PowerKey class. |
| 1193 */ |
| 1194 PowerKey.DefaultHandler.prototype.handler = |
| 1195 function(evt, handlerObj, pkObj) { |
| 1196 if (!handlerObj.actionMap) { |
| 1197 return; |
| 1198 } |
| 1199 if (evt.keyCode) { |
| 1200 var mapkeyCode = '' + evt.keyCode; |
| 1201 var mapkeyChar = String.fromCharCode(evt.keyCode).toLowerCase(); |
| 1202 if (evt.ctrlKey) { |
| 1203 mapkeyCode = 'Ctrl+' + mapkeyCode; |
| 1204 mapkeyChar = 'Ctrl+' + mapkeyChar; |
| 1205 } |
| 1206 if (evt.altKey) { |
| 1207 mapkeyCode = 'Alt+' + mapkeyCode; |
| 1208 mapkeyChar = 'Alt+' + mapkeyChar; |
| 1209 } |
| 1210 if (evt.shiftKey) { |
| 1211 mapkeyCode = 'Shift+' + mapkeyCode; |
| 1212 mapkeyChar = 'Shift+' + mapkeyChar; |
| 1213 } |
| 1214 var actionObj = null; |
| 1215 actionObj = handlerObj.actionMap[mapkeyChar]; |
| 1216 if (!actionObj) { |
| 1217 actionObj = handlerObj.actionMap[mapkeyCode]; |
| 1218 } |
| 1219 if (actionObj) { |
| 1220 // If there is no action for the current context, try '*' |
| 1221 var funcObj = actionObj[pkObj.context] ? |
| 1222 actionObj[pkObj.context] : actionObj['*']; |
| 1223 if (funcObj) { |
| 1224 var func = funcObj[1]; |
| 1225 if (func) { |
| 1226 func(evt); |
| 1227 if (funcObj[0] != '*') { |
| 1228 pkObj.context = funcObj[0]; |
| 1229 } |
| 1230 } |
| 1231 } |
| 1232 } |
| 1233 } |
| 1234 }; |
| 1235 |
| 1236 |
| 1237 /** |
| 1238 * A class to store the height and width of the viewport. |
| 1239 * @param {number} width The width of the browser viewport. |
| 1240 * @param {number} height The height of the browser viewport. |
| 1241 * @constructor |
| 1242 */ |
| 1243 PowerKey.ViewportSize = function(width, height) { |
| 1244 this.width = width ? width : undefined; |
| 1245 this.height = height ? height : undefined; |
| 1246 }; |
| 1247 |
| 1248 |
| 1249 /** |
| 1250 * Trims spaces and new lines from the left end of the string. |
| 1251 * @param {string} str String to trim. |
| 1252 * @return {string} The left-trimmed string. |
| 1253 */ |
| 1254 PowerKey.leftTrim = function(str) { |
| 1255 return str.replace(PowerKey.LEFT_TRIMMABLE, ''); |
| 1256 }; |
| 1257 |
| 1258 |
| 1259 /** |
| 1260 * Trims spaces and new lines from the right end of the string. |
| 1261 * @param {string} str String to trim. |
| 1262 * @return {string} The right-trimmed string. |
| 1263 */ |
| 1264 PowerKey.rightTrim = function(str) { |
| 1265 return str.replace(PowerKey.RIGHT_TRIMMABLE, ''); |
| 1266 }; |
| 1267 |
| 1268 |
| 1269 /** |
| 1270 * Escapes special characters in the string so that it can be matched against |
| 1271 * a regular expression. |
| 1272 * @param {string} s String from which to escape characters. |
| 1273 * @return {string} Returns the escaped string. |
| 1274 */ |
| 1275 PowerKey.regExpEscape = function(s) { |
| 1276 return String(s).replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). |
| 1277 replace(/\x08/g, '\\x08'); |
| 1278 }; |
| 1279 |
| 1280 |
| 1281 /** |
| 1282 * Gets the size of the browser viewport. |
| 1283 * @return {PowerKey.ViewportSize} Returns the size of the viewport. |
| 1284 */ |
| 1285 PowerKey.getViewportSize = function() { |
| 1286 var myWidth = 0, myHeight = 0; |
| 1287 if (typeof(window.innerWidth) == 'number') { |
| 1288 //Non-IE |
| 1289 myWidth = window.innerWidth; |
| 1290 myHeight = window.innerHeight; |
| 1291 } else if (document.documentElement && |
| 1292 (document.documentElement.clientWidth || |
| 1293 document.documentElement.clientHeight)) { |
| 1294 //IE 6+ in 'standards compliant mode' |
| 1295 myWidth = document.documentElement.clientWidth; |
| 1296 myHeight = document.documentElement.clientHeight; |
| 1297 } else if (document.body && |
| 1298 (document.body.clientWidth || |
| 1299 document.body.clientHeight)) { |
| 1300 //IE 4 compatible |
| 1301 myWidth = document.body.clientWidth; |
| 1302 myHeight = document.body.clientHeight; |
| 1303 } |
| 1304 return new PowerKey.ViewportSize(myWidth, myHeight); |
| 1305 }; |
| 1306 |
| 1307 |
| 1308 /** |
| 1309 * Is the user agent Internet Explorer? |
| 1310 * @type {boolean} |
| 1311 */ |
| 1312 PowerKey.isIE = false; |
| 1313 |
| 1314 |
| 1315 /** |
| 1316 * Is the user agent Firefox? |
| 1317 * @type {boolean} |
| 1318 */ |
| 1319 PowerKey.isGecko = false; |
| 1320 |
| 1321 |
| 1322 /** |
| 1323 * Detects the browser type and version. |
| 1324 */ |
| 1325 PowerKey.setBrowser = function() { |
| 1326 var agt = navigator.userAgent.toLowerCase(); |
| 1327 PowerKey.isGecko = (agt.indexOf('gecko') != -1); |
| 1328 PowerKey.isIE = ((agt.indexOf('msie') != -1) && |
| 1329 (agt.indexOf('opera') == -1)); |
| 1330 }; |
| 1331 // Set browser type |
| 1332 PowerKey.setBrowser(); |
| 1333 |
| 1334 |
| 1335 /** |
| 1336 * Enumeration for events. |
| 1337 * @enum {string} |
| 1338 */ |
| 1339 PowerKey.Event = { |
| 1340 KEYUP: PowerKey.isIE ? 'onkeyup' : 'keyup', |
| 1341 KEYDOWN: PowerKey.isIE ? 'onkeydown' : 'keydown', |
| 1342 KEYPRESS: PowerKey.isIE ? 'onkeypress' : 'keypress', |
| 1343 CLICK: PowerKey.isIE ? 'onclick' : 'click', |
| 1344 RESIZE: PowerKey.isIE ? 'onresize' : 'resize', |
| 1345 FOCUS: PowerKey.isIE ? 'onfocus' : 'focus', |
| 1346 BLUR: PowerKey.isIE ? 'onblur' : 'blur' |
| 1347 }; |
| 1348 |
| 1349 |
| 1350 /** |
| 1351 * CSS styles. |
| 1352 * @type {string} |
| 1353 */ |
| 1354 PowerKey.cssStr = |
| 1355 '.pkHiddenStatus {display: none; position: absolute;}' + |
| 1356 '.pkVisibleStatus {display: block; position: absolute; left: 2px; top: 2px; ' + |
| 1357 'line-height: 1.2em; z-index: 10001; background-color: #000000; ' + |
| 1358 'padding: 2px; color: #fff; font-family: Arial, Sans-serif; ' + |
| 1359 'font-size: 20px; filter: alpha(opacity=80); -moz-opacity: .80;}' + |
| 1360 '.pkOpaqueCompletionText {border-style: none; background-color:transparent; ' + |
| 1361 'font-family: Arial, Helvetica, sans-serif; font-size: 35px; ' + |
| 1362 'font-weight: bold; color: #fff; width: 1000px; height: 50px;}' + |
| 1363 '.pkBackgroundShow {position: absolute; width: 0px;' + |
| 1364 'height: 0px; background-color: #000000; filter: alpha(opacity=70); ' + |
| 1365 ' -moz-opacity: .70; left: 0px; top: 0px; z-index: 10000;}'; |
| 1366 |
| 1367 |
| 1368 /** |
| 1369 * Adds the PowerKey CSS to the page. |
| 1370 */ |
| 1371 PowerKey.setDefaultCSSStyle = function() { |
| 1372 var head, style; |
| 1373 head = document.getElementsByTagName('head')[0]; |
| 1374 if (!head) { |
| 1375 return; |
| 1376 } |
| 1377 style = document.createElement('style'); |
| 1378 style.type = 'text/css'; |
| 1379 if (PowerKey.isIE) { |
| 1380 style.innerhtml = PowerKey.cssStr; |
| 1381 } else if (PowerKey.isGecko) { |
| 1382 style.innerHTML = PowerKey.cssStr; |
| 1383 } |
| 1384 head.appendChild(style); |
| 1385 }; |
| 1386 |
| 1387 |
| 1388 /** |
| 1389 * Adds the CSS tag to he DOM with href as the specified CSS file. |
| 1390 * @param {string} cssFile The CSS file to include. |
| 1391 */ |
| 1392 PowerKey.addCSSStyle = function(cssFile) { |
| 1393 var headID = document.getElementsByTagName('head')[0]; |
| 1394 var cssNode = document.createElement('link'); |
| 1395 cssNode.type = 'text/css'; |
| 1396 cssNode.rel = 'stylesheet'; |
| 1397 cssNode.href = cssFile; |
| 1398 headID.appendChild(cssNode); |
| 1399 }; |
| 1400 |
| 1401 |
| 1402 /** |
| 1403 * Constants for key codes. |
| 1404 * @enum {number} |
| 1405 */ |
| 1406 PowerKey.keyCodes = { |
| 1407 ARROWUP: 38, |
| 1408 ARROWDOWN: 40, |
| 1409 ARROWLEFT: 37, |
| 1410 ARROWRIGHT: 39, |
| 1411 ENTER: 13, |
| 1412 TAB: 9, |
| 1413 ESC: 27 |
| 1414 }; |
| 1415 |
| 1416 /** |
| 1417 * PowerKey string constants. |
| 1418 * @enum {string} |
| 1419 */ |
| 1420 PowerKey.str = { |
| 1421 DEFAULT_COMPLETION_LIST_NAME: 'Completion list', |
| 1422 DEFAULT_COMPLETION_PROMPT: 'Enter Completion', |
| 1423 DEFAULT_NO_COMPLETION: 'No completions found' |
| 1424 }; |
| 1425 |
| 1426 /** |
| 1427 * Visibility status of the completion field. |
| 1428 * @enum {string} |
| 1429 */ |
| 1430 PowerKey.status = { |
| 1431 VISIBLE: 'visible', |
| 1432 HIDDEN: 'hidden' |
| 1433 }; |
| 1434 |
| OLD | NEW |