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