| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 cr.define('options', function() { | 5 cr.define('options', function() { |
| 6 /** @const */ var DeletableItem = options.DeletableItem; | 6 /** @const */ var DeletableItem = options.DeletableItem; |
| 7 /** @const */ var DeletableItemList = options.DeletableItemList; | 7 /** @const */ var DeletableItemList = options.DeletableItemList; |
| 8 | 8 |
| 9 /** | 9 /** |
| 10 * Creates a new list item with support for inline editing. | 10 * Creates a new list item with support for inline editing. |
| (...skipping 13 matching lines...) Expand all Loading... |
| 24 */ | 24 */ |
| 25 InlineEditableItem.decorate = function(el) { | 25 InlineEditableItem.decorate = function(el) { |
| 26 el.__proto__ = InlineEditableItem.prototype; | 26 el.__proto__ = InlineEditableItem.prototype; |
| 27 el.decorate(); | 27 el.decorate(); |
| 28 }; | 28 }; |
| 29 | 29 |
| 30 InlineEditableItem.prototype = { | 30 InlineEditableItem.prototype = { |
| 31 __proto__: DeletableItem.prototype, | 31 __proto__: DeletableItem.prototype, |
| 32 | 32 |
| 33 /** | 33 /** |
| 34 * Index of currently focused column, or -1 for none. |
| 35 * @type {number} |
| 36 */ |
| 37 focusedColumnIndex: -1, |
| 38 |
| 39 /** |
| 34 * Whether or not this item can be edited. | 40 * Whether or not this item can be edited. |
| 35 * @type {boolean} | 41 * @type {boolean} |
| 36 * @private | 42 * @private |
| 37 */ | 43 */ |
| 38 editable_: true, | 44 editable_: true, |
| 39 | 45 |
| 40 /** | 46 /** |
| 41 * Whether or not this is a placeholder for adding a new item. | 47 * Whether or not this is a placeholder for adding a new item. |
| 42 * @type {boolean} | 48 * @type {boolean} |
| 43 * @private | 49 * @private |
| (...skipping 23 matching lines...) Expand all Loading... |
| 67 */ | 73 */ |
| 68 editClickTarget_: null, | 74 editClickTarget_: null, |
| 69 | 75 |
| 70 /** @override */ | 76 /** @override */ |
| 71 decorate: function() { | 77 decorate: function() { |
| 72 DeletableItem.prototype.decorate.call(this); | 78 DeletableItem.prototype.decorate.call(this); |
| 73 | 79 |
| 74 this.editFields_ = []; | 80 this.editFields_ = []; |
| 75 this.addEventListener('mousedown', this.handleMouseDown_); | 81 this.addEventListener('mousedown', this.handleMouseDown_); |
| 76 this.addEventListener('keydown', this.handleKeyDown_); | 82 this.addEventListener('keydown', this.handleKeyDown_); |
| 77 this.addEventListener('leadChange', this.handleLeadChange_); | 83 this.addEventListener('focusin', this.handleFocusIn_); |
| 78 }, | 84 }, |
| 79 | 85 |
| 80 /** @override */ | 86 /** @override */ |
| 81 selectionChanged: function() { | 87 selectionChanged: function() { |
| 82 this.updateEditState(); | 88 this.updateEditState(); |
| 83 }, | 89 }, |
| 84 | 90 |
| 85 /** | 91 /** |
| 86 * Called when this element gains or loses 'lead' status. Updates editing | 92 * Called when this element gains or loses 'lead' status. Updates editing |
| 87 * mode accordingly. | 93 * mode accordingly. |
| 88 * @private | |
| 89 */ | 94 */ |
| 90 handleLeadChange_: function() { | 95 updateLeadState: function() { |
| 91 // Add focusability before call to updateEditState. | 96 // Add focusability before call to updateEditState. |
| 92 if (this.lead) { | 97 if (this.lead) { |
| 93 this.setEditableValuesFocusable(true); | 98 this.setEditableValuesFocusable(true); |
| 94 this.setCloseButtonFocusable(true); | 99 this.setCloseButtonFocusable(true); |
| 95 } | 100 } |
| 96 | 101 |
| 97 this.updateEditState(); | 102 this.updateEditState(); |
| 98 | 103 |
| 99 // Remove focusability after call to updateEditState. | 104 // Remove focusability after call to updateEditState. |
| 100 this.setStaticValuesFocusable(false); | 105 this.setStaticValuesFocusable(false); |
| (...skipping 25 matching lines...) Expand all Loading... |
| 126 if (editing) | 131 if (editing) |
| 127 this.setAttribute('editing', ''); | 132 this.setAttribute('editing', ''); |
| 128 else | 133 else |
| 129 this.removeAttribute('editing'); | 134 this.removeAttribute('editing'); |
| 130 | 135 |
| 131 if (editing) { | 136 if (editing) { |
| 132 this.editCancelled_ = false; | 137 this.editCancelled_ = false; |
| 133 | 138 |
| 134 cr.dispatchSimpleEvent(this, 'edit', true); | 139 cr.dispatchSimpleEvent(this, 'edit', true); |
| 135 | 140 |
| 136 var focusElement = this.editClickTarget_ || this.initialFocusElement; | 141 var focusElement = this.getEditFocusElement_(); |
| 137 this.editClickTarget_ = null; | |
| 138 if (focusElement) | 142 if (focusElement) |
| 139 this.focusAndMaybeSelect_(focusElement); | 143 this.focusAndMaybeSelect_(focusElement); |
| 140 } else { | 144 } else { |
| 141 if (!this.editCancelled_ && this.hasBeenEdited && | 145 if (!this.editCancelled_ && this.hasBeenEdited && |
| 142 this.currentInputIsValid) { | 146 this.currentInputIsValid) { |
| 143 if (this.isPlaceholder) | 147 if (this.isPlaceholder) |
| 144 this.parentNode.focusPlaceholder = true; | 148 this.parentNode.focusPlaceholder = true; |
| 145 | 149 |
| 146 this.updateStaticValues_(); | 150 this.updateStaticValues_(); |
| 147 cr.dispatchSimpleEvent(this, 'commitedit', true); | 151 cr.dispatchSimpleEvent(this, 'commitedit', true); |
| 148 } else { | 152 } else { |
| 149 this.resetEditableValues_(); | 153 this.resetEditableValues_(); |
| 150 cr.dispatchSimpleEvent(this, 'canceledit', true); | 154 cr.dispatchSimpleEvent(this, 'canceledit', true); |
| 151 } | 155 } |
| 152 } | 156 } |
| 153 }, | 157 }, |
| 154 | 158 |
| 155 /** | 159 /** |
| 160 * Return editable element that should be focused, or null for none. |
| 161 * @private |
| 162 */ |
| 163 getEditFocusElement_: function() { |
| 164 // If an edit field was clicked on then use the clicked element. |
| 165 if (this.editClickTarget_) { |
| 166 var result = this.editClickTarget_; |
| 167 this.editClickTarget_ = null; |
| 168 return result; |
| 169 } |
| 170 |
| 171 // If focusedColumnIndex is valid then use the element in that column. |
| 172 if (this.focusedColumnIndex != -1) { |
| 173 var nearestColumn = |
| 174 this.getNearestColumnByIndex_(this.focusedColumnIndex); |
| 175 if (nearestColumn) |
| 176 return nearestColumn; |
| 177 } |
| 178 |
| 179 // It's possible that focusedColumnIndex hasn't been updated yet. |
| 180 // Check getFocusedColumnIndex_ directly. |
| 181 // This can't completely replace the above focusedColumnIndex check |
| 182 // because InlineEditableItemList may have set focusedColumnIndex to a |
| 183 // different value. |
| 184 var columnIndex = this.getFocusedColumnIndex_(); |
| 185 if (columnIndex != -1) { |
| 186 var nearestColumn = this.getNearestColumnByIndex_(columnIndex); |
| 187 if (nearestColumn) |
| 188 return nearestColumn; |
| 189 } |
| 190 |
| 191 // Everything else failed so return the default. |
| 192 return this.initialFocusElement; |
| 193 }, |
| 194 |
| 195 /** |
| 156 * Focus on the specified element, and select the editable text in it | 196 * Focus on the specified element, and select the editable text in it |
| 157 * if possible. | 197 * if possible. |
| 158 * @param {!Element} control An element to be focused. | 198 * @param {!Element} control An element to be focused. |
| 159 * @private | 199 * @private |
| 160 */ | 200 */ |
| 161 focusAndMaybeSelect_: function(control) { | 201 focusAndMaybeSelect_: function(control) { |
| 162 control.focus(); | 202 control.focus(); |
| 163 if (control.tagName == 'INPUT') | 203 if (control.tagName == 'INPUT') |
| 164 control.select(); | 204 control.select(); |
| 165 }, | 205 }, |
| (...skipping 167 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 333 if (this.editable) | 373 if (this.editable) |
| 334 control.staticVersion.tabIndex = -1; | 374 control.staticVersion.tabIndex = -1; |
| 335 control.staticVersion.editableVersion = control; | 375 control.staticVersion.editableVersion = control; |
| 336 control.staticVersion.addEventListener('focus', | 376 control.staticVersion.addEventListener('focus', |
| 337 this.handleFocus.bind(this)); | 377 this.handleFocus.bind(this)); |
| 338 } | 378 } |
| 339 this.editFields_.push(control); | 379 this.editFields_.push(control); |
| 340 }, | 380 }, |
| 341 | 381 |
| 342 /** | 382 /** |
| 383 * Set the column index for a child element of this InlineEditableItem. |
| 384 * Only elements with a column index will be keyboard focusable, e.g. by |
| 385 * pressing the tab key. |
| 386 * @param {Element} element Element whose column index to set. Method does |
| 387 * nothing if element is null. |
| 388 * @param {number} columnIndex The new column index to set on the element. |
| 389 * -1 removes the column index. |
| 390 */ |
| 391 setFocusableColumnIndex: function(element, columnIndex) { |
| 392 if (!element) |
| 393 return; |
| 394 |
| 395 if (columnIndex >= 0) |
| 396 element.setAttribute('inlineeditable-column', columnIndex); |
| 397 else |
| 398 element.removeAttribute('inlineeditable-column'); |
| 399 }, |
| 400 |
| 401 /** |
| 343 * Resets the editable version of any controls created by createEditable* | 402 * Resets the editable version of any controls created by createEditable* |
| 344 * to match the static text. | 403 * to match the static text. |
| 345 * @private | 404 * @private |
| 346 */ | 405 */ |
| 347 resetEditableValues_: function() { | 406 resetEditableValues_: function() { |
| 348 var editFields = this.editFields_; | 407 var editFields = this.editFields_; |
| 349 for (var i = 0; i < editFields.length; i++) { | 408 for (var i = 0; i < editFields.length; i++) { |
| 350 var staticLabel = editFields[i].staticVersion; | 409 var staticLabel = editFields[i].staticVersion; |
| 351 if (!staticLabel && !this.isPlaceholder) | 410 if (!staticLabel && !this.isPlaceholder) |
| 352 continue; | 411 continue; |
| (...skipping 21 matching lines...) Expand all Loading... |
| 374 if (!staticLabel) | 433 if (!staticLabel) |
| 375 continue; | 434 continue; |
| 376 | 435 |
| 377 if (editFields[i].tagName == 'INPUT') | 436 if (editFields[i].tagName == 'INPUT') |
| 378 staticLabel.textContent = editFields[i].value; | 437 staticLabel.textContent = editFields[i].value; |
| 379 // Add more tag types here as new createEditable* methods are added. | 438 // Add more tag types here as new createEditable* methods are added. |
| 380 } | 439 } |
| 381 }, | 440 }, |
| 382 | 441 |
| 383 /** | 442 /** |
| 443 * Returns the index of the column that currently has focus, or -1 if no |
| 444 * column has focus. |
| 445 * @return {number} |
| 446 * @private |
| 447 */ |
| 448 getFocusedColumnIndex_: function() { |
| 449 var element = document.activeElement.editableVersion || |
| 450 document.activeElement; |
| 451 |
| 452 if (element.hasAttribute('inlineeditable-column')) |
| 453 return parseInt(element.getAttribute('inlineeditable-column'), 10); |
| 454 return -1; |
| 455 }, |
| 456 |
| 457 /** |
| 458 * Returns the element from the column that has the largest index where: |
| 459 * where: |
| 460 * + index <= startIndex, and |
| 461 * + the element exists, and |
| 462 * + the element is not disabled |
| 463 * @return {Element} |
| 464 * @private |
| 465 */ |
| 466 getNearestColumnByIndex_: function(startIndex) { |
| 467 for (var i = startIndex; i >= 0; --i) { |
| 468 var el = this.querySelector('[inlineeditable-column="' + i + '"]'); |
| 469 if (el && !el.disabled) |
| 470 return el; |
| 471 } |
| 472 return null; |
| 473 }, |
| 474 |
| 475 /** |
| 384 * Called when a key is pressed. Handles committing and canceling edits. | 476 * Called when a key is pressed. Handles committing and canceling edits. |
| 385 * @param {Event} e The key down event. | 477 * @param {Event} e The key down event. |
| 386 * @private | 478 * @private |
| 387 */ | 479 */ |
| 388 handleKeyDown_: function(e) { | 480 handleKeyDown_: function(e) { |
| 389 if (!this.editing) | 481 if (!this.editing) |
| 390 return; | 482 return; |
| 391 | 483 |
| 392 var endEdit = false; | 484 var endEdit = false; |
| 393 var handledKey = true; | 485 var handledKey = true; |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 441 // from currently selected edit field. | 533 // from currently selected edit field. |
| 442 e.stopPropagation(); | 534 e.stopPropagation(); |
| 443 e.preventDefault(); | 535 e.preventDefault(); |
| 444 } | 536 } |
| 445 return; | 537 return; |
| 446 } | 538 } |
| 447 | 539 |
| 448 if (editClickTarget && !editClickTarget.disabled) | 540 if (editClickTarget && !editClickTarget.disabled) |
| 449 this.editClickTarget_ = editClickTarget; | 541 this.editClickTarget_ = editClickTarget; |
| 450 }, | 542 }, |
| 543 |
| 544 /** |
| 545 * Called when this InlineEditableItem or any of its children are given |
| 546 * focus. Updates focusedColumnIndex with the index of the newly focused |
| 547 * column, or -1 if the focused element does not have a column index. |
| 548 * @param {Event} e The focusin event. |
| 549 * @private |
| 550 */ |
| 551 handleFocusIn_: function(e) { |
| 552 var target = e.target.editableVersion || e.target; |
| 553 this.focusedColumnIndex = target.hasAttribute('inlineeditable-column') ? |
| 554 parseInt(target.getAttribute('inlineeditable-column'), 10) : -1; |
| 555 }, |
| 451 }; | 556 }; |
| 452 | 557 |
| 453 /** | 558 /** |
| 454 * Takes care of committing changes to inline editable list items when the | 559 * Takes care of committing changes to inline editable list items when the |
| 455 * window loses focus. | 560 * window loses focus. |
| 456 */ | 561 */ |
| 457 function handleWindowBlurs() { | 562 function handleWindowBlurs() { |
| 458 window.addEventListener('blur', function(e) { | 563 window.addEventListener('blur', function(e) { |
| 459 var itemAncestor = findAncestor(document.activeElement, function(node) { | 564 var itemAncestor = findAncestor(document.activeElement, function(node) { |
| 460 return node instanceof InlineEditableItem; | 565 return node instanceof InlineEditableItem; |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 497 * @param {Event} e The change event. | 602 * @param {Event} e The change event. |
| 498 * @private | 603 * @private |
| 499 */ | 604 */ |
| 500 handleListFocusChange_: function(e) { | 605 handleListFocusChange_: function(e) { |
| 501 var leadItem = this.getListItemByIndex(this.selectionModel.leadIndex); | 606 var leadItem = this.getListItemByIndex(this.selectionModel.leadIndex); |
| 502 if (leadItem) { | 607 if (leadItem) { |
| 503 if (e.newValue) { | 608 if (e.newValue) { |
| 504 // Add focusability before making other changes. | 609 // Add focusability before making other changes. |
| 505 leadItem.setEditableValuesFocusable(true); | 610 leadItem.setEditableValuesFocusable(true); |
| 506 leadItem.setCloseButtonFocusable(true); | 611 leadItem.setCloseButtonFocusable(true); |
| 612 leadItem.focusedColumnIndex = -1; |
| 507 leadItem.updateEditState(); | 613 leadItem.updateEditState(); |
| 508 // Remove focusability after making other changes. | 614 // Remove focusability after making other changes. |
| 509 leadItem.setStaticValuesFocusable(false); | 615 leadItem.setStaticValuesFocusable(false); |
| 510 } else { | 616 } else { |
| 511 // Add focusability before making other changes. | 617 // Add focusability before making other changes. |
| 512 leadItem.setStaticValuesFocusable(true); | 618 leadItem.setStaticValuesFocusable(true); |
| 513 leadItem.setCloseButtonFocusable(true); | 619 leadItem.setCloseButtonFocusable(true); |
| 514 leadItem.editing = false; | 620 leadItem.editing = false; |
| 515 // Remove focusability after making other changes. | 621 // Remove focusability after making other changes. |
| 516 if (!leadItem.isPlaceholder) | 622 if (!leadItem.isPlaceholder) |
| 517 leadItem.setEditableValuesFocusable(false); | 623 leadItem.setEditableValuesFocusable(false); |
| 518 } | 624 } |
| 519 } | 625 } |
| 520 }, | 626 }, |
| 521 | 627 |
| 522 /** | 628 /** @override */ |
| 523 * Called after the DataModel for the list has been set. | 629 handleLeadChange: function(e) { |
| 524 * @override | 630 DeletableItemList.prototype.handleLeadChange.call(this, e); |
| 525 */ | 631 |
| 632 var focusedColumnIndex = -1; |
| 633 if (e.oldValue != -1) { |
| 634 var element = this.getListItemByIndex(e.oldValue); |
| 635 if (element) { |
| 636 focusedColumnIndex = element.focusedColumnIndex; |
| 637 element.updateLeadState(); |
| 638 } |
| 639 } |
| 640 |
| 641 if (e.newValue != -1) { |
| 642 var element = this.getListItemByIndex(e.newValue); |
| 643 if (element) { |
| 644 element.focusedColumnIndex = focusedColumnIndex; |
| 645 element.updateLeadState(); |
| 646 } |
| 647 } |
| 648 }, |
| 649 |
| 650 /** @override */ |
| 526 onSetDataModelComplete: function() { | 651 onSetDataModelComplete: function() { |
| 527 DeletableItemList.prototype.onSetDataModelComplete.call(this); | 652 DeletableItemList.prototype.onSetDataModelComplete.call(this); |
| 528 | 653 |
| 529 var firstItem = this.getListItemByIndex(0); | 654 var firstItem = this.getListItemByIndex(0); |
| 530 if (firstItem) { | 655 if (firstItem) { |
| 531 firstItem.setStaticValuesFocusable(true); | 656 firstItem.setStaticValuesFocusable(true); |
| 532 firstItem.setCloseButtonFocusable(true); | 657 firstItem.setCloseButtonFocusable(true); |
| 533 if (firstItem.isPlaceholder) | 658 if (firstItem.isPlaceholder) |
| 534 firstItem.setEditableValuesFocusable(true); | 659 firstItem.setEditableValuesFocusable(true); |
| 535 } | 660 } |
| 536 }, | 661 }, |
| 537 | 662 |
| 538 /** | 663 /** |
| 539 * May be overridden by subclasses to disable focusing the placeholder. | 664 * May be overridden by subclasses to disable focusing the placeholder. |
| 540 * @return {boolean} True if the placeholder element should be focused on | 665 * @return {boolean} True if the placeholder element should be focused on |
| 541 * edit commit. | 666 * edit commit. |
| 542 */ | 667 */ |
| 543 shouldFocusPlaceholder: function() { | 668 shouldFocusPlaceholder: function() { |
| 544 return true; | 669 return true; |
| 545 }, | 670 }, |
| 546 }; | 671 }; |
| 547 | 672 |
| 548 // Export | 673 // Export |
| 549 return { | 674 return { |
| 550 InlineEditableItem: InlineEditableItem, | 675 InlineEditableItem: InlineEditableItem, |
| 551 InlineEditableItemList: InlineEditableItemList, | 676 InlineEditableItemList: InlineEditableItemList, |
| 552 }; | 677 }; |
| 553 }); | 678 }); |
| OLD | NEW |