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) { | 139 this.focusAndMaybeSelect_(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 { | |
| 143 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 keyboard. | |
| 227 * @param {boolean} focusable The desired focusable state. | |
| 228 */ | |
| 229 setEditableValuesFocusable: function(focusable) { | |
| 230 focusable = focusable && this.editable; | |
| 231 var editFields = this.editFields_; | |
| 232 for (var i = 0; i < editFields.length; i++) { | |
| 233 editFields[i].tabIndex = focusable ? 0 : -1; | |
| 234 } | |
| 235 }, | |
| 236 | |
| 237 /** | |
| 238 * Sets whether the static values can be given focus using the keyboard. | |
| 239 * @param {boolean} focusable The desired focusable state. | |
| 240 */ | |
| 241 setStaticValuesFocusable: function(focusable) { | |
| 242 var editFields = this.editFields_; | |
| 243 for (var i = 0; i < editFields.length; i++) { | |
| 244 var staticVersion = editFields[i].staticVersion; | |
| 245 if (!staticVersion) | |
| 246 return; | |
|
Dan Beam
2014/10/30 22:20:43
shouldn't this be continue...? do you have any te
bondd
2014/10/30 22:45:29
Done.
Gah, my bad. Typo. There are currently no di
Dan Beam
2014/10/31 01:13:31
we should probably still have some tests for all t
| |
| 247 if (this.editable) { | |
| 248 staticVersion.tabIndex = focusable ? 0 : -1; | |
| 249 } else { | |
| 250 // staticVersion remains visible when !this.editable. Remove | |
| 251 // tabindex so that it will not become focused by clicking on it and | |
| 252 // have selection box drawn around it. | |
| 253 staticVersion.removeAttribute('tabindex'); | |
| 254 } | |
| 255 } | |
| 256 }, | |
| 257 | |
| 258 /** | |
| 259 * Sets whether the close button can be focused using the keyboard. | |
| 260 * @param {boolean} focusable The desired focusable state. | |
| 261 */ | |
| 262 setCloseButtonFocusable: function(focusable) { | |
| 263 this.closeButtonElement.tabIndex = | |
| 264 focusable && this.closeButtonFocusAllowed ? 0 : -1; | |
| 265 }, | |
| 266 | |
| 267 /** | |
| 232 * Returns a div containing an <input>, as well as static text if | 268 * Returns a div containing an <input>, as well as static text if |
| 233 * isPlaceholder is not true. | 269 * isPlaceholder is not true. |
| 234 * @param {string} text The text of the cell. | 270 * @param {string} text The text of the cell. |
| 235 * @return {HTMLElement} The HTML element for the cell. | 271 * @return {HTMLElement} The HTML element for the cell. |
| 236 * @private | 272 * @private |
| 237 */ | 273 */ |
| 238 createEditableTextCell: function(text) { | 274 createEditableTextCell: function(text) { |
| 239 var container = /** @type {HTMLElement} */( | 275 var container = /** @type {HTMLElement} */( |
| 240 this.ownerDocument.createElement('div')); | 276 this.ownerDocument.createElement('div')); |
| 241 var textEl = null; | 277 var textEl = null; |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 261 if (list && list.focusPlaceholder) { | 297 if (list && list.focusPlaceholder) { |
| 262 list.focusPlaceholder = false; | 298 list.focusPlaceholder = false; |
| 263 if (list.shouldFocusPlaceholder()) | 299 if (list.shouldFocusPlaceholder()) |
| 264 inputEl.focus(); | 300 inputEl.focus(); |
| 265 } | 301 } |
| 266 }, 50); | 302 }, 50); |
| 267 } | 303 } |
| 268 | 304 |
| 269 // In some cases 'focus' event may arrive before 'input'. | 305 // In some cases 'focus' event may arrive before 'input'. |
| 270 // To make sure revalidation is triggered we postpone 'focus' handling. | 306 // To make sure revalidation is triggered we postpone 'focus' handling. |
| 271 var handler = this.handleFocus_.bind(this); | 307 var handler = this.handleFocus.bind(this); |
| 272 inputEl.addEventListener('focus', function() { | 308 inputEl.addEventListener('focus', function() { |
| 273 window.setTimeout(function() { | 309 window.setTimeout(function() { |
| 274 if (inputEl.ownerDocument.activeElement == inputEl) | 310 if (inputEl.ownerDocument.activeElement == inputEl) |
| 275 handler(); | 311 handler(); |
| 276 }, 0); | 312 }, 0); |
| 277 }); | 313 }); |
| 278 container.appendChild(inputEl); | 314 container.appendChild(inputEl); |
| 279 this.addEditField(inputEl, textEl); | 315 this.addEditField(inputEl, textEl); |
| 280 | 316 |
| 281 return container; | 317 return container; |
| 282 }, | 318 }, |
| 283 | 319 |
| 284 /** | 320 /** |
| 285 * Register an edit field. | 321 * Register an edit field. |
| 286 * @param {!Element} control An editable element. It's a form control | 322 * @param {!Element} control An editable element. It's a form control |
| 287 * element typically. | 323 * element typically. |
| 288 * @param {Element} staticElement An element representing non-editable | 324 * @param {Element} staticElement An element representing non-editable |
| 289 * state. | 325 * state. |
| 290 */ | 326 */ |
| 291 addEditField: function(control, staticElement) { | 327 addEditField: function(control, staticElement) { |
| 292 control.staticVersion = staticElement; | 328 control.staticVersion = staticElement; |
| 329 if (this.editable) | |
| 330 control.tabIndex = -1; | |
| 331 | |
| 332 if (control.staticVersion) { | |
| 333 if (this.editable) | |
| 334 control.staticVersion.tabIndex = -1; | |
| 335 control.staticVersion.editableVersion = control; | |
| 336 control.staticVersion.addEventListener('focus', | |
| 337 this.handleFocus.bind(this)); | |
| 338 } | |
| 293 this.editFields_.push(control); | 339 this.editFields_.push(control); |
| 294 }, | 340 }, |
| 295 | 341 |
| 296 /** | 342 /** |
| 297 * Resets the editable version of any controls created by createEditable* | 343 * Resets the editable version of any controls created by createEditable* |
| 298 * to match the static text. | 344 * to match the static text. |
| 299 * @private | 345 * @private |
| 300 */ | 346 */ |
| 301 resetEditableValues_: function() { | 347 resetEditableValues_: function() { |
| 302 var editFields = this.editFields_; | 348 var editFields = this.editFields_; |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 368 } | 414 } |
| 369 }, | 415 }, |
| 370 | 416 |
| 371 /** | 417 /** |
| 372 * Called when the list item is clicked. If the click target corresponds to | 418 * 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. | 419 * an editable item, stores that item to focus when edit mode is started. |
| 374 * @param {Event} e The mouse down event. | 420 * @param {Event} e The mouse down event. |
| 375 * @private | 421 * @private |
| 376 */ | 422 */ |
| 377 handleMouseDown_: function(e) { | 423 handleMouseDown_: function(e) { |
| 378 if (!this.editable || this.editing) | 424 if (!this.editable) |
| 379 return; | 425 return; |
| 380 | 426 |
| 381 var clickTarget = e.target; | 427 var clickTarget = e.target; |
| 382 var editFields = this.editFields_; | 428 var editFields = this.editFields_; |
| 429 var editClickTarget; | |
| 383 for (var i = 0; i < editFields.length; i++) { | 430 for (var i = 0; i < editFields.length; i++) { |
| 384 if (editFields[i].staticVersion == clickTarget) | |
| 385 clickTarget.tabIndex = 0; | |
| 386 if (editFields[i] == clickTarget || | 431 if (editFields[i] == clickTarget || |
| 387 editFields[i].staticVersion == clickTarget) { | 432 editFields[i].staticVersion == clickTarget) { |
| 388 this.editClickTarget_ = editFields[i]; | 433 editClickTarget = editFields[i]; |
| 389 return; | 434 break; |
| 390 } | 435 } |
| 391 } | 436 } |
| 437 | |
| 438 if (this.editing) { | |
| 439 if (!editClickTarget) { | |
| 440 // Clicked on the list item outside of an edit field. Don't lose focus | |
| 441 // from currently selected edit field. | |
| 442 e.stopPropagation(); | |
| 443 e.preventDefault(); | |
| 444 } | |
| 445 return; | |
| 446 } | |
| 447 | |
| 448 if (editClickTarget && !editClickTarget.disabled) | |
| 449 this.editClickTarget_ = editClickTarget; | |
| 392 }, | 450 }, |
| 393 }; | 451 }; |
| 394 | 452 |
| 395 /** | 453 /** |
| 396 * Takes care of committing changes to inline editable list items when the | 454 * Takes care of committing changes to inline editable list items when the |
| 397 * window loses focus. | 455 * window loses focus. |
| 398 */ | 456 */ |
| 399 function handleWindowBlurs() { | 457 function handleWindowBlurs() { |
| 400 window.addEventListener('blur', function(e) { | 458 window.addEventListener('blur', function(e) { |
| 401 var itemAncestor = findAncestor(document.activeElement, function(node) { | 459 var itemAncestor = findAncestor(document.activeElement, function(node) { |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 435 | 493 |
| 436 /** | 494 /** |
| 437 * Called when the list hierarchy as a whole loses or gains focus; starts | 495 * Called when the list hierarchy as a whole loses or gains focus; starts |
| 438 * or ends editing for the lead item if necessary. | 496 * or ends editing for the lead item if necessary. |
| 439 * @param {Event} e The change event. | 497 * @param {Event} e The change event. |
| 440 * @private | 498 * @private |
| 441 */ | 499 */ |
| 442 handleListFocusChange_: function(e) { | 500 handleListFocusChange_: function(e) { |
| 443 var leadItem = this.getListItemByIndex(this.selectionModel.leadIndex); | 501 var leadItem = this.getListItemByIndex(this.selectionModel.leadIndex); |
| 444 if (leadItem) { | 502 if (leadItem) { |
| 445 if (e.newValue) | 503 if (e.newValue) { |
| 504 // Add focusability before making other changes. | |
| 505 leadItem.setEditableValuesFocusable(true); | |
| 506 leadItem.setCloseButtonFocusable(true); | |
| 446 leadItem.updateEditState(); | 507 leadItem.updateEditState(); |
| 447 else | 508 // Remove focusability after making other changes. |
| 509 leadItem.setStaticValuesFocusable(false); | |
| 510 } else { | |
| 511 // Add focusability before making other changes. | |
| 512 leadItem.setStaticValuesFocusable(true); | |
| 513 leadItem.setCloseButtonFocusable(true); | |
| 448 leadItem.editing = false; | 514 leadItem.editing = false; |
| 515 // Remove focusability after making other changes. | |
| 516 if (!leadItem.isPlaceholder) | |
| 517 leadItem.setEditableValuesFocusable(false); | |
| 518 } | |
| 449 } | 519 } |
| 450 }, | 520 }, |
| 451 | 521 |
| 522 /** | |
| 523 * Called after the DataModel for the list has been set. | |
| 524 * @override | |
| 525 */ | |
| 526 onSetDataModelComplete: function() { | |
| 527 DeletableItemList.prototype.onSetDataModelComplete.call(this); | |
| 528 | |
| 529 var firstItem = this.getListItemByIndex(0); | |
| 530 if (firstItem) { | |
| 531 firstItem.setStaticValuesFocusable(true); | |
| 532 firstItem.setCloseButtonFocusable(true); | |
| 533 if (firstItem.isPlaceholder) | |
| 534 firstItem.setEditableValuesFocusable(true); | |
| 535 } | |
| 536 }, | |
| 537 | |
| 452 /** | 538 /** |
| 453 * May be overridden by subclasses to disable focusing the placeholder. | 539 * May be overridden by subclasses to disable focusing the placeholder. |
| 454 * @return {boolean} True if the placeholder element should be focused on | 540 * @return {boolean} True if the placeholder element should be focused on |
| 455 * edit commit. | 541 * edit commit. |
| 456 */ | 542 */ |
| 457 shouldFocusPlaceholder: function() { | 543 shouldFocusPlaceholder: function() { |
| 458 return true; | 544 return true; |
| 459 }, | 545 }, |
| 460 }; | 546 }; |
| 461 | 547 |
| 462 // Export | 548 // Export |
| 463 return { | 549 return { |
| 464 InlineEditableItem: InlineEditableItem, | 550 InlineEditableItem: InlineEditableItem, |
| 465 InlineEditableItemList: InlineEditableItemList, | 551 InlineEditableItemList: InlineEditableItemList, |
| 466 }; | 552 }; |
| 467 }); | 553 }); |
| OLD | NEW |