Chromium Code Reviews| 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 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 81 selectionChanged: function() { | 81 selectionChanged: function() { |
| 82 this.updateEditState(); | 82 this.updateEditState(); |
| 83 }, | 83 }, |
| 84 | 84 |
| 85 /** | 85 /** |
| 86 * Called when this element gains or loses 'lead' status. Updates editing | 86 * Called when this element gains or loses 'lead' status. Updates editing |
| 87 * mode accordingly. | 87 * mode accordingly. |
| 88 * @private | 88 * @private |
| 89 */ | 89 */ |
| 90 handleLeadChange_: function() { | 90 handleLeadChange_: function() { |
| 91 // Add focusability before call to updateEditState. | |
| 92 if (this.lead) { | |
| 93 this.setEditableValuesFocusable(true); | |
| 94 this.setCloseButtonFocusable(true); | |
| 95 } | |
| 96 | |
| 91 this.updateEditState(); | 97 this.updateEditState(); |
| 98 | |
| 99 // Remove focusability after call to updateEditState. | |
| 100 this.setStaticValuesFocusable(false); | |
| 101 if (!this.lead) { | |
| 102 this.setEditableValuesFocusable(false); | |
| 103 this.setCloseButtonFocusable(false); | |
| 104 } | |
| 92 }, | 105 }, |
| 93 | 106 |
| 94 /** | 107 /** |
| 95 * Updates the edit state based on the current selected and lead states. | 108 * Updates the edit state based on the current selected and lead states. |
| 96 */ | 109 */ |
| 97 updateEditState: function() { | 110 updateEditState: function() { |
| 98 if (this.editable) | 111 if (this.editable) |
| 99 this.editing = this.selected && this.lead; | 112 this.editing = this.selected && this.lead; |
| 100 }, | 113 }, |
| 101 | 114 |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 115 else | 128 else |
| 116 this.removeAttribute('editing'); | 129 this.removeAttribute('editing'); |
| 117 | 130 |
| 118 if (editing) { | 131 if (editing) { |
| 119 this.editCancelled_ = false; | 132 this.editCancelled_ = false; |
| 120 | 133 |
| 121 cr.dispatchSimpleEvent(this, 'edit', true); | 134 cr.dispatchSimpleEvent(this, 'edit', true); |
| 122 | 135 |
| 123 var focusElement = this.editClickTarget_ || this.initialFocusElement; | 136 var focusElement = this.editClickTarget_ || this.initialFocusElement; |
| 124 this.editClickTarget_ = null; | 137 this.editClickTarget_ = null; |
| 125 | 138 if (focusElement) |
| 126 if (focusElement) { | |
| 127 var self = this; | |
| 128 // We should delay to give focus on |focusElement| if this is called | |
| 129 // in mousedown event handler. If we did give focus immediately, Blink | |
| 130 // would try to focus on an ancestor of the mousedown target element, | |
| 131 // and remove focus from |focusElement|. | |
| 132 if (focusElement.staticVersion && | |
| 133 focusElement.staticVersion.hasAttribute('tabindex')) { | |
| 134 setTimeout(function() { | |
| 135 if (self.editing) { | |
| 136 if (focusElement.disabled) | |
| 137 self.parentNode.focus(); | |
| 138 self.focusAndMaybeSelect_(focusElement); | |
| 139 } | |
| 140 focusElement.staticVersion.removeAttribute('tabindex'); | |
| 141 }, 0); | |
| 142 } else { | |
|
bondd
2014/10/28 22:29:57
No longer required for new way of focusing element
| |
| 143 this.focusAndMaybeSelect_(focusElement); | 139 this.focusAndMaybeSelect_(focusElement); |
| 144 } | |
| 145 } | |
| 146 } else { | 140 } else { |
| 147 if (!this.editCancelled_ && this.hasBeenEdited && | 141 if (!this.editCancelled_ && this.hasBeenEdited && |
| 148 this.currentInputIsValid) { | 142 this.currentInputIsValid) { |
| 149 if (this.isPlaceholder) | 143 if (this.isPlaceholder) |
| 150 this.parentNode.focusPlaceholder = true; | 144 this.parentNode.focusPlaceholder = true; |
| 151 | 145 |
| 152 this.updateStaticValues_(); | 146 this.updateStaticValues_(); |
| 153 cr.dispatchSimpleEvent(this, 'commitedit', true); | 147 cr.dispatchSimpleEvent(this, 'commitedit', true); |
| 154 } else { | 148 } else { |
| 155 this.resetEditableValues_(); | 149 this.resetEditableValues_(); |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 222 * Returns true if the item has been changed by an edit. | 216 * Returns true if the item has been changed by an edit. |
| 223 * Can be overridden by subclasses to return false when nothing has changed | 217 * Can be overridden by subclasses to return false when nothing has changed |
| 224 * to avoid unnecessary commits. | 218 * to avoid unnecessary commits. |
| 225 * @type {boolean} | 219 * @type {boolean} |
| 226 */ | 220 */ |
| 227 get hasBeenEdited() { | 221 get hasBeenEdited() { |
| 228 return true; | 222 return true; |
| 229 }, | 223 }, |
| 230 | 224 |
| 231 /** | 225 /** |
| 226 * Sets whether the editable values can be given focus using the | |
| 227 * keyboard+mouse, mouse only, or neither. | |
|
Dan Beam
2014/10/29 01:37:13
using the keyboard.
bondd
2014/10/29 17:19:08
Done.
| |
| 228 * | |
| 229 * Result: | |
| 230 * If this item is not editable: focusable by neither | |
| 231 * else if the field is disabled: focusable by mouse only | |
| 232 * else: | |
| 233 * if wantFocusable == true: focusable by mouse+keyboard | |
| 234 * else: focusable by mouse only | |
|
Dan Beam
2014/10/29 01:37:13
^ I don't think this is required, unfocusable mean
bondd
2014/10/29 17:19:07
I removed the entire "Result" comment block. Is th
| |
| 235 * | |
| 236 * @param {boolean} wantFocusable The desired focusable state. | |
| 237 */ | |
| 238 setEditableValuesFocusable: function(wantFocusable) { | |
|
Dan Beam
2014/10/29 01:37:13
wantFocusable => focusable
Dan Beam
2014/10/29 01:37:13
this.editable is loop invariant, I believe, so it'
bondd
2014/10/29 17:19:08
Done.
| |
| 239 var editFields = this.editFields_; | |
| 240 for (var i = 0; i < editFields.length; i++) { | |
| 241 if (this.editable) { | |
| 242 if (wantFocusable && !editFields[i].disabled) | |
|
Dan Beam
2014/10/29 01:37:13
it does seem like [disabled][tabindex=0] is actual
Dan Beam
2014/10/29 01:37:14
can't this whole loop body be
editFields[i].tabIn
bondd
2014/10/29 17:19:08
Done. I had guessed that condensing logic like thi
bondd
2014/10/29 17:19:08
Did you mean "it doesn't seem"? That button you li
Dan Beam
2014/10/29 22:28:11
doesn't**
| |
| 243 editFields[i].tabIndex = 0; | |
|
Dan Beam
2014/10/29 01:37:13
indent off
| |
| 244 else | |
| 245 editFields[i].tabIndex = -1; | |
| 246 } | |
|
Dan Beam
2014/10/29 01:37:13
} else {
| |
| 247 else { | |
| 248 editFields[i].removeAttribute('tabindex'); | |
|
bondd
2014/10/29 17:19:08
I got rid of this per your above comment re: conde
| |
| 249 } | |
| 250 } | |
| 251 }, | |
| 252 | |
| 253 /** | |
| 254 * Sets whether the static values can be given focus using the | |
| 255 * keyboard+mouse, mouse only, or neither. | |
| 256 * | |
| 257 * Result: | |
| 258 * If this item is not editable: focusable by neither | |
| 259 * else if the field is disabled: focusable by mouse only | |
| 260 * else: | |
| 261 * if wantFocusable == true: focusable by mouse+keyboard | |
| 262 * else: focusable by mouse only | |
| 263 * | |
| 264 * @param {boolean} wantFocusable The desired focusable state. | |
| 265 */ | |
| 266 setStaticValuesFocusable: function(wantFocusable) { | |
| 267 var editFields = this.editFields_; | |
| 268 for (var i = 0; i < editFields.length; i++) { | |
| 269 var staticVersion = editFields[i].staticVersion; | |
| 270 if (!staticVersion) | |
| 271 continue; | |
| 272 | |
| 273 if (this.editable) { | |
|
Dan Beam
2014/10/29 01:37:13
same general nits here as in setEditableValuesFocu
bondd
2014/10/29 17:19:08
Done.
| |
| 274 if (wantFocusable && !staticVersion.disabled) | |
| 275 staticVersion.tabIndex = 0; | |
| 276 else | |
| 277 staticVersion.tabIndex = -1; | |
| 278 } | |
| 279 else { | |
| 280 staticVersion.removeAttribute('tabindex'); | |
| 281 } | |
|
Dan Beam
2014/10/29 01:37:13
indent off (-2 \s)
| |
| 282 } | |
| 283 }, | |
| 284 | |
| 285 /** | |
| 286 * Sets whether the close button can be focused using the keyboard. | |
| 287 * | |
| 288 * @param {boolean} wantFocusable The desired focusable state. | |
| 289 */ | |
| 290 setCloseButtonFocusable: function(wantFocusable) { | |
|
Dan Beam
2014/10/29 01:37:13
why can't we just use an ES5 getter/setter for thi
bondd
2014/10/29 17:19:08
To keep it consistent with setStaticValuesFocusabl
Dan Beam
2014/10/29 22:28:11
that doesn't apply to Chrome code, as you've deter
| |
| 291 if (this.closeButtonFocusAllowed || !wantFocusable) | |
| 292 this.closeButtonElement.tabIndex = wantFocusable ? 0 : -1; | |
| 293 }, | |
| 294 | |
| 295 /** | |
| 232 * Returns a div containing an <input>, as well as static text if | 296 * Returns a div containing an <input>, as well as static text if |
| 233 * isPlaceholder is not true. | 297 * isPlaceholder is not true. |
| 234 * @param {string} text The text of the cell. | 298 * @param {string} text The text of the cell. |
| 235 * @return {HTMLElement} The HTML element for the cell. | 299 * @return {HTMLElement} The HTML element for the cell. |
| 236 * @private | 300 * @private |
| 237 */ | 301 */ |
| 238 createEditableTextCell: function(text) { | 302 createEditableTextCell: function(text) { |
| 239 var container = /** @type {HTMLElement} */( | 303 var container = /** @type {HTMLElement} */( |
| 240 this.ownerDocument.createElement('div')); | 304 this.ownerDocument.createElement('div')); |
| 241 var textEl = null; | 305 var textEl = null; |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 261 if (list && list.focusPlaceholder) { | 325 if (list && list.focusPlaceholder) { |
| 262 list.focusPlaceholder = false; | 326 list.focusPlaceholder = false; |
| 263 if (list.shouldFocusPlaceholder()) | 327 if (list.shouldFocusPlaceholder()) |
| 264 inputEl.focus(); | 328 inputEl.focus(); |
| 265 } | 329 } |
| 266 }, 50); | 330 }, 50); |
| 267 } | 331 } |
| 268 | 332 |
| 269 // In some cases 'focus' event may arrive before 'input'. | 333 // In some cases 'focus' event may arrive before 'input'. |
| 270 // To make sure revalidation is triggered we postpone 'focus' handling. | 334 // To make sure revalidation is triggered we postpone 'focus' handling. |
| 271 var handler = this.handleFocus_.bind(this); | 335 var handler = this.handleFocus.bind(this); |
| 272 inputEl.addEventListener('focus', function() { | 336 inputEl.addEventListener('focus', function() { |
| 273 window.setTimeout(function() { | 337 window.setTimeout(function() { |
| 274 if (inputEl.ownerDocument.activeElement == inputEl) | 338 if (inputEl.ownerDocument.activeElement == inputEl) |
| 275 handler(); | 339 handler(); |
| 276 }, 0); | 340 }, 0); |
| 277 }); | 341 }); |
| 278 container.appendChild(inputEl); | 342 container.appendChild(inputEl); |
| 279 this.addEditField(inputEl, textEl); | 343 this.addEditField(inputEl, textEl); |
| 280 | 344 |
| 281 return container; | 345 return container; |
| 282 }, | 346 }, |
| 283 | 347 |
| 284 /** | 348 /** |
| 285 * Register an edit field. | 349 * Register an edit field. |
| 286 * @param {!Element} control An editable element. It's a form control | 350 * @param {!Element} control An editable element. It's a form control |
| 287 * element typically. | 351 * element typically. |
| 288 * @param {Element} staticElement An element representing non-editable | 352 * @param {Element} staticElement An element representing non-editable |
| 289 * state. | 353 * state. |
| 290 */ | 354 */ |
| 291 addEditField: function(control, staticElement) { | 355 addEditField: function(control, staticElement) { |
| 292 control.staticVersion = staticElement; | 356 control.staticVersion = staticElement; |
| 357 if (this.editable) | |
| 358 control.tabIndex = -1; | |
| 359 | |
| 360 if (control.staticVersion) { | |
| 361 if (this.editable) | |
| 362 control.staticVersion.tabIndex = -1; | |
| 363 control.staticVersion.editableVersion = control; | |
| 364 | |
| 365 var handler = this.handleFocus.bind(this); | |
| 366 control.staticVersion.addEventListener('focus', function() { | |
|
Dan Beam
2014/10/29 01:37:13
control.staticVersion.addEventListener('focus', th
bondd
2014/10/29 17:19:08
Done.
| |
| 367 handler(); | |
| 368 }); | |
| 369 } | |
| 293 this.editFields_.push(control); | 370 this.editFields_.push(control); |
| 294 }, | 371 }, |
| 295 | 372 |
| 296 /** | 373 /** |
| 297 * Resets the editable version of any controls created by createEditable* | 374 * Resets the editable version of any controls created by createEditable* |
| 298 * to match the static text. | 375 * to match the static text. |
| 299 * @private | 376 * @private |
| 300 */ | 377 */ |
| 301 resetEditableValues_: function() { | 378 resetEditableValues_: function() { |
| 302 var editFields = this.editFields_; | 379 var editFields = this.editFields_; |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 368 } | 445 } |
| 369 }, | 446 }, |
| 370 | 447 |
| 371 /** | 448 /** |
| 372 * Called when the list item is clicked. If the click target corresponds to | 449 * Called when the list item is clicked. If the click target corresponds to |
| 373 * an editable item, stores that item to focus when edit mode is started. | 450 * an editable item, stores that item to focus when edit mode is started. |
| 374 * @param {Event} e The mouse down event. | 451 * @param {Event} e The mouse down event. |
| 375 * @private | 452 * @private |
| 376 */ | 453 */ |
| 377 handleMouseDown_: function(e) { | 454 handleMouseDown_: function(e) { |
| 378 if (!this.editable || this.editing) | 455 if (!this.editable) |
| 379 return; | 456 return; |
| 380 | 457 |
| 381 var clickTarget = e.target; | 458 var clickTarget = e.target; |
| 382 var editFields = this.editFields_; | 459 var editFields = this.editFields_; |
| 460 var clickField; | |
|
Dan Beam
2014/10/29 01:37:14
clickField => editClickTarget
bondd
2014/10/29 17:19:08
Done.
Dan Beam
2014/10/29 22:28:11
editClickTarget (note the "Target")
bondd
2014/10/29 23:08:54
Done.
| |
| 383 for (var i = 0; i < editFields.length; i++) { | 461 for (var i = 0; i < editFields.length; i++) { |
| 384 if (editFields[i].staticVersion == clickTarget) | 462 if ((editFields[i] == clickTarget || |
| 385 clickTarget.tabIndex = 0; | 463 editFields[i].staticVersion == clickTarget)) { |
|
bondd
2014/10/28 22:29:57
Oops, double parens (( ))
bondd
2014/10/29 17:19:08
Done.
| |
| 386 if (editFields[i] == clickTarget || | 464 clickField = editFields[i]; |
| 387 editFields[i].staticVersion == clickTarget) { | 465 break; |
| 388 this.editClickTarget_ = editFields[i]; | |
| 389 return; | |
| 390 } | 466 } |
| 391 } | 467 } |
| 468 | |
| 469 if (this.editing) { | |
| 470 if (!clickField) { | |
| 471 // Clicked on the list item outside of an edit field. Don't lose | |
| 472 // focus from currently selected edit field. | |
|
Dan Beam
2014/10/29 01:37:13
wrap at 80 cols
bondd
2014/10/29 17:19:08
Done.
| |
| 473 e.stopPropagation(); | |
| 474 e.preventDefault(); | |
| 475 } | |
| 476 return; | |
| 477 } | |
| 478 | |
| 479 if (clickField && !clickField.disabled) | |
| 480 this.editClickTarget_ = clickField; | |
| 392 }, | 481 }, |
| 393 }; | 482 }; |
| 394 | 483 |
| 395 /** | 484 /** |
| 396 * Takes care of committing changes to inline editable list items when the | 485 * Takes care of committing changes to inline editable list items when the |
| 397 * window loses focus. | 486 * window loses focus. |
| 398 */ | 487 */ |
| 399 function handleWindowBlurs() { | 488 function handleWindowBlurs() { |
| 400 window.addEventListener('blur', function(e) { | 489 window.addEventListener('blur', function(e) { |
| 401 var itemAncestor = findAncestor(document.activeElement, function(node) { | 490 var itemAncestor = findAncestor(document.activeElement, function(node) { |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 435 | 524 |
| 436 /** | 525 /** |
| 437 * Called when the list hierarchy as a whole loses or gains focus; starts | 526 * Called when the list hierarchy as a whole loses or gains focus; starts |
| 438 * or ends editing for the lead item if necessary. | 527 * or ends editing for the lead item if necessary. |
| 439 * @param {Event} e The change event. | 528 * @param {Event} e The change event. |
| 440 * @private | 529 * @private |
| 441 */ | 530 */ |
| 442 handleListFocusChange_: function(e) { | 531 handleListFocusChange_: function(e) { |
| 443 var leadItem = this.getListItemByIndex(this.selectionModel.leadIndex); | 532 var leadItem = this.getListItemByIndex(this.selectionModel.leadIndex); |
| 444 if (leadItem) { | 533 if (leadItem) { |
| 445 if (e.newValue) | 534 if (e.newValue) { |
| 535 // Add focusability before making other changes. | |
| 536 leadItem.setEditableValuesFocusable(true); | |
| 537 leadItem.setCloseButtonFocusable(true); | |
| 446 leadItem.updateEditState(); | 538 leadItem.updateEditState(); |
| 447 else | 539 // Remove focusability after making other changes. |
| 540 leadItem.setStaticValuesFocusable(false); | |
| 541 } | |
|
Dan Beam
2014/10/29 01:37:13
} else {
bondd
2014/10/29 17:19:08
Done.
| |
| 542 else { | |
| 543 // Add focusability before making other changes. | |
| 544 leadItem.setStaticValuesFocusable(true); | |
| 545 leadItem.setCloseButtonFocusable(true); | |
| 448 leadItem.editing = false; | 546 leadItem.editing = false; |
| 547 // Remove focusability after making other changes. | |
| 548 if (!leadItem.isPlaceholder) | |
| 549 leadItem.setEditableValuesFocusable(false); | |
| 550 } | |
| 449 } | 551 } |
| 450 }, | 552 }, |
| 451 | 553 |
| 554 /** | |
| 555 * Called after the DataModel for the list has been set. | |
| 556 * @override | |
| 557 */ | |
| 558 onSetDataModelComplete: function() { | |
| 559 DeletableItemList.prototype.onSetDataModelComplete.call(this); | |
| 560 | |
| 561 var firstItem = this.getListItemByIndex(0); | |
| 562 if (firstItem) { | |
| 563 firstItem.setStaticValuesFocusable(true); | |
| 564 firstItem.setCloseButtonFocusable(true); | |
| 565 if (firstItem.isPlaceholder) | |
| 566 firstItem.setEditableValuesFocusable(true); | |
| 567 } | |
| 568 }, | |
| 569 | |
| 452 /** | 570 /** |
| 453 * May be overridden by subclasses to disable focusing the placeholder. | 571 * May be overridden by subclasses to disable focusing the placeholder. |
| 454 * @return {boolean} True if the placeholder element should be focused on | 572 * @return {boolean} True if the placeholder element should be focused on |
| 455 * edit commit. | 573 * edit commit. |
| 456 */ | 574 */ |
| 457 shouldFocusPlaceholder: function() { | 575 shouldFocusPlaceholder: function() { |
| 458 return true; | 576 return true; |
| 459 }, | 577 }, |
| 460 }; | 578 }; |
| 461 | 579 |
| 462 // Export | 580 // Export |
| 463 return { | 581 return { |
| 464 InlineEditableItem: InlineEditableItem, | 582 InlineEditableItem: InlineEditableItem, |
| 465 InlineEditableItemList: InlineEditableItemList, | 583 InlineEditableItemList: InlineEditableItemList, |
| 466 }; | 584 }; |
| 467 }); | 585 }); |
| OLD | NEW |