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