Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(168)

Side by Side Diff: chrome/browser/resources/options/inline_editable_list.js

Issue 664583006: Improve keyboard navigation on 'Manage search engines' options page (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
60 editCancelled_: true, 60 editCancelled_: true,
61 61
62 /** 62 /**
63 * The editable item corresponding to the last click, if any. Used to decide 63 * The editable item corresponding to the last click, if any. Used to decide
64 * initial focus when entering edit mode. 64 * initial focus when entering edit mode.
65 * @type {HTMLElement} 65 * @type {HTMLElement}
66 * @private 66 * @private
67 */ 67 */
68 editClickTarget_: null, 68 editClickTarget_: null,
69 69
70 /**
71 * Index of currently focused column, or -1 for none.
72 * @type {number}
73 * @private
74 */
75 focusedColumnIndex_: -1,
76
77 /**
78 * Whether or not to ignore the next mouseup event.
79 * @type {boolean}
80 * @private
81 */
82 ignoreMouseUp_: false,
Dan Beam 2014/10/18 01:35:44 from this class' point of view, isn't this actuall
83
70 /** @override */ 84 /** @override */
71 decorate: function() { 85 decorate: function() {
72 DeletableItem.prototype.decorate.call(this); 86 DeletableItem.prototype.decorate.call(this);
73 87
74 this.editFields_ = []; 88 this.editFields_ = [];
75 this.addEventListener('mousedown', this.handleMouseDown_); 89 this.addEventListener('mousedown', this.handleMouseDown_);
90 this.addEventListener('mouseup', this.handleMouseUp_);
Dan Beam 2014/10/18 01:35:43 why is this necessary? what are we preventing the
76 this.addEventListener('keydown', this.handleKeyDown_); 91 this.addEventListener('keydown', this.handleKeyDown_);
77 this.addEventListener('leadChange', this.handleLeadChange_); 92 this.addEventListener('focusin', this.handleFocusIn_);
78 }, 93 },
79 94
80 /** @override */ 95 /** @override */
81 selectionChanged: function() { 96 selectionChanged: function() {
82 this.updateEditState(); 97 this.updateEditState();
83 }, 98 },
84 99
85 /** 100 /**
86 * Called when this element gains or loses 'lead' status. Updates editing 101 * Whether the fields of this InlineEditableItem can be focused using the
87 * mode accordingly. 102 * keyboard, e.g. by pressing the tab key.
88 * @private 103 * @type {boolean}
89 */ 104 */
90 handleLeadChange_: function() { 105 set focusable(focusable) {
bondd 2014/10/17 01:23:15 Add a getter for completeness, even though we neve
Dan Beam 2014/10/18 01:35:43 remove until needed
91 this.updateEditState(); 106 this.setEditableValuesFocusable_(focusable);
107 this.closeButtonFocusable = focusable;
92 }, 108 },
93 109
94 /** 110 /**
111 * Index of currently focused column, or -1 for none.
112 * @type {number}
113 */
114 get focusedColumnIndex() {
115 return this.focusedColumnIndex_;
Dan Beam 2014/10/18 01:35:44 wrong indent
116 },
117 set focusedColumnIndex(focusedColumnIndex) {
118 this.focusedColumnIndex_ = focusedColumnIndex;
Dan Beam 2014/10/18 01:35:44 how is this useful over just a normal property? e
119 },
120
121 /**
95 * Updates the edit state based on the current selected and lead states. 122 * Updates the edit state based on the current selected and lead states.
96 */ 123 */
97 updateEditState: function() { 124 updateEditState: function() {
98 if (this.editable) 125 if (this.editable)
99 this.editing = this.selected && this.lead; 126 this.editing = this.selected && this.lead;
100 }, 127 },
101 128
102 /** 129 /**
103 * Whether the user is currently editing the list item. 130 * Whether the user is currently editing the list item.
104 * @type {boolean} 131 * @type {boolean}
105 */ 132 */
106 get editing() { 133 get editing() {
107 return this.hasAttribute('editing'); 134 return this.hasAttribute('editing');
108 }, 135 },
109 set editing(editing) { 136 set editing(editing) {
110 if (this.editing == editing) 137 if (this.editing == editing)
111 return; 138 return;
112 139
113 if (editing) 140 if (editing)
114 this.setAttribute('editing', ''); 141 this.setAttribute('editing', '');
115 else 142 else
116 this.removeAttribute('editing'); 143 this.removeAttribute('editing');
117 144
118 if (editing) { 145 if (editing) {
119 this.editCancelled_ = false; 146 this.editCancelled_ = false;
120 147
121 cr.dispatchSimpleEvent(this, 'edit', true); 148 cr.dispatchSimpleEvent(this, 'edit', true);
122 149
Dan Beam 2014/10/18 01:35:44 can you add a comment to what all of this is doing
bondd 2014/10/29 21:12:39 Done.
123 var focusElement = this.editClickTarget_ || this.initialFocusElement; 150 var focusElement = this.initialFocusElement;
124 this.editClickTarget_ = null; 151 if (this.editClickTarget_) {
152 focusElement = this.editClickTarget_;
153 this.editClickTarget_ = null;
Dan Beam 2014/10/18 01:35:44 so why is this code better? is it subtly differen
154 }
Dan Beam 2014/10/18 01:35:44 } else if (...) { (else on same line)
155 else if (this.focusedColumnIndex_ != -1) {
156 focusElement =
157 this.getNearestColumnByIndex_(this.focusedColumnIndex_);
158 }
159 else {
160 var focusedColumn = this.getFocusedColumn_();
161 if (focusedColumn)
162 focusElement = focusedColumn;
163 }
125 164
126 if (focusElement) { 165 if (focusElement) {
127 var self = this; 166 var self = this;
128 // We should delay to give focus on |focusElement| if this is called 167 // We should delay to give focus on |focusElement| if this is called
129 // in mousedown event handler. If we did give focus immediately, Blink 168 // in mousedown event handler. If we did give focus immediately, Blink
130 // would try to focus on an ancestor of the mousedown target element, 169 // would try to focus on an ancestor of the mousedown target element,
131 // and remove focus from |focusElement|. 170 // and remove focus from |focusElement|.
132 if (focusElement.staticVersion && 171 if (focusElement.staticVersion &&
133 focusElement.staticVersion.hasAttribute('tabindex')) { 172 focusElement.staticVersion.hasAttribute('tabindex')) {
134 setTimeout(function() { 173 setTimeout(function() {
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after
232 * Returns a div containing an <input>, as well as static text if 271 * Returns a div containing an <input>, as well as static text if
233 * isPlaceholder is not true. 272 * isPlaceholder is not true.
234 * @param {string} text The text of the cell. 273 * @param {string} text The text of the cell.
235 * @return {HTMLElement} The HTML element for the cell. 274 * @return {HTMLElement} The HTML element for the cell.
236 * @private 275 * @private
237 */ 276 */
238 createEditableTextCell: function(text) { 277 createEditableTextCell: function(text) {
239 var container = /** @type {HTMLElement} */( 278 var container = /** @type {HTMLElement} */(
240 this.ownerDocument.createElement('div')); 279 this.ownerDocument.createElement('div'));
241 var textEl = null; 280 var textEl = null;
242 if (!this.isPlaceholder) {
243 textEl = this.ownerDocument.createElement('div');
244 textEl.className = 'static-text';
245 textEl.textContent = text;
246 textEl.setAttribute('displaymode', 'static');
247 container.appendChild(textEl);
248 }
Dan Beam 2014/10/18 01:35:44 did you mean to remove this? because you left |te
249 281
250 var inputEl = this.ownerDocument.createElement('input'); 282 var inputEl = this.ownerDocument.createElement('input');
251 inputEl.type = 'text'; 283 inputEl.type = 'text';
252 inputEl.value = text; 284 inputEl.value = text;
285 inputEl.tabIndex = -1;
253 if (!this.isPlaceholder) { 286 if (!this.isPlaceholder) {
254 inputEl.setAttribute('displaymode', 'edit'); 287 inputEl.setAttribute('displaymode', 'edit');
255 } else { 288 } else {
256 // At this point |this| is not attached to the parent list yet, so give 289 // At this point |this| is not attached to the parent list yet, so give
257 // a short timeout in order for the attachment to occur. 290 // a short timeout in order for the attachment to occur.
258 var self = this; 291 var self = this;
259 window.setTimeout(function() { 292 window.setTimeout(function() {
260 var list = self.parentNode; 293 var list = self.parentNode;
261 if (list && list.focusPlaceholder) { 294 if (list && list.focusPlaceholder) {
262 list.focusPlaceholder = false; 295 list.focusPlaceholder = false;
(...skipping 19 matching lines...) Expand all
282 }, 315 },
283 316
284 /** 317 /**
285 * Register an edit field. 318 * Register an edit field.
286 * @param {!Element} control An editable element. It's a form control 319 * @param {!Element} control An editable element. It's a form control
287 * element typically. 320 * element typically.
288 * @param {Element} staticElement An element representing non-editable 321 * @param {Element} staticElement An element representing non-editable
289 * state. 322 * state.
290 */ 323 */
291 addEditField: function(control, staticElement) { 324 addEditField: function(control, staticElement) {
292 control.staticVersion = staticElement; 325 control.staticVersion = staticElement;
bondd 2014/10/17 01:23:15 staticVersion is only set by content_settings_exce
Dan Beam 2014/10/18 01:35:43 if there's only 1 user of an API with no immediate
293 this.editFields_.push(control); 326 this.editFields_.push(control);
294 }, 327 },
295 328
296 /** 329 /**
330 * Set the column index for a child element of this InlineEditableItem.
331 * Only elements with a column index will be keyboard focusable, e.g. by
332 * pressing the tab key.
333 * @param {Element} element Element whose column index to set. Method does
334 * nothing if element is null.
335 * @param {number} columnIndex The new column index to set on the element.
336 * -1 removes the column index.
337 */
338 setFocusableColumnIndex: function(element, columnIndex) {
339 if(element) {
Dan Beam 2014/10/18 01:35:44 if (!element) return; ...
bondd 2014/10/29 21:12:39 Done.
340 if (columnIndex >= 0)
341 element.setAttribute('inlineeditable-column', columnIndex);
342 else
343 element.removeAttribute('inlineeditable-column');
344 }
345 },
346
347 /**
297 * Resets the editable version of any controls created by createEditable* 348 * Resets the editable version of any controls created by createEditable*
298 * to match the static text. 349 * to match the static text.
299 * @private 350 * @private
300 */ 351 */
301 resetEditableValues_: function() { 352 resetEditableValues_: function() {
302 var editFields = this.editFields_; 353 var editFields = this.editFields_;
303 for (var i = 0; i < editFields.length; i++) { 354 for (var i = 0; i < editFields.length; i++) {
304 var staticLabel = editFields[i].staticVersion; 355 var staticLabel = editFields[i].staticVersion;
305 if (!staticLabel && !this.isPlaceholder) 356 if (!staticLabel && !this.isPlaceholder)
306 continue; 357 continue;
(...skipping 21 matching lines...) Expand all
328 if (!staticLabel) 379 if (!staticLabel)
329 continue; 380 continue;
330 381
331 if (editFields[i].tagName == 'INPUT') 382 if (editFields[i].tagName == 'INPUT')
332 staticLabel.textContent = editFields[i].value; 383 staticLabel.textContent = editFields[i].value;
333 // Add more tag types here as new createEditable* methods are added. 384 // Add more tag types here as new createEditable* methods are added.
334 } 385 }
335 }, 386 },
336 387
337 /** 388 /**
389 * Called when lead status is gained/lost.
390 * @private
391 */
392 updateLeadState_: function() {
393 this.focusable = this.lead;
394 this.updateEditState();
395 },
396
397 /**
398 * Sets whether the editable values can be given focus using the keyboard,
399 * e.g. by pressing the tab key.
400 * @param {boolean}
401 * @private
402 */
403 setEditableValuesFocusable_: function(focusable) {
404 var newTabIndex = focusable ? 0 : -1;
405 var editFields = this.editFields_;
406 for (var i = 0; i < editFields.length; i++)
407 editFields[i].tabIndex = newTabIndex;
Dan Beam 2014/10/18 01:35:44 nit: curlies around 1-line for loops (no curlies a
408 },
409
410 /**
411 * Returns the element in the column that currently has focus, or null
412 * if no column has focus.
413 * @return {Element}
414 * @private
415 */
416 getFocusedColumn_: function() {
417 if (document.activeElement &&
Dan Beam 2014/10/18 01:35:43 there will always be an activeElement, despite wha
418 document.activeElement.hasAttribute('inlineeditable-column')) {
419 return document.activeElement;
420 }
421 return null;
422 },
423
424 /**
425 * Returns the element from the column that has the largest index where:
426 * where:
427 * + index <= startIndex, and
428 * + the element exists, and
429 * + the element is not disabled
430 * @return {Element}
431 * @private
432 */
433 getNearestColumnByIndex_: function(startIndex) {
434 for (var i = startIndex; i >= 0; --i) {
435 var el = this.querySelector('[inlineeditable-column="' + i + '"]');
436 if (el && !el.disabled)
Dan Beam 2014/10/18 01:35:44 nit: maybe add !el.hidden here unless you know tha
bondd 2014/10/29 21:12:39 Under the new split CL it's okay for the element t
437 return el;
438 }
439 return null;
440 },
441
442 /**
338 * Called when a key is pressed. Handles committing and canceling edits. 443 * Called when a key is pressed. Handles committing and canceling edits.
339 * @param {Event} e The key down event. 444 * @param {Event} e The key down event.
340 * @private 445 * @private
341 */ 446 */
342 handleKeyDown_: function(e) { 447 handleKeyDown_: function(e) {
343 if (!this.editing) 448 if (!this.editing)
344 return; 449 return;
345 450
346 var endEdit = false; 451 var endEdit = false;
347 var handledKey = true; 452 var handledKey = true;
(...skipping 20 matching lines...) Expand all
368 } 473 }
369 }, 474 },
370 475
371 /** 476 /**
372 * Called when the list item is clicked. If the click target corresponds to 477 * 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. 478 * an editable item, stores that item to focus when edit mode is started.
374 * @param {Event} e The mouse down event. 479 * @param {Event} e The mouse down event.
375 * @private 480 * @private
376 */ 481 */
377 handleMouseDown_: function(e) { 482 handleMouseDown_: function(e) {
378 if (!this.editable || this.editing) 483 if (!this.editable || this.editing || !e.target)
Dan Beam 2014/10/18 01:35:44 when can !e.target ever happen? is there some tes
bondd 2014/10/29 21:12:39 Done.
379 return; 484 return;
380 485
381 var clickTarget = e.target; 486 var clickTarget = e.target;
382 var editFields = this.editFields_; 487 var editFields = this.editFields_;
383 for (var i = 0; i < editFields.length; i++) { 488 for (var i = 0; i < editFields.length; i++) {
384 if (editFields[i].staticVersion == clickTarget) 489 if (editFields[i].staticVersion == clickTarget)
385 clickTarget.tabIndex = 0; 490 clickTarget.tabIndex = 0;
386 if (editFields[i] == clickTarget || 491 if (editFields[i] == clickTarget ||
387 editFields[i].staticVersion == clickTarget) { 492 editFields[i].staticVersion == clickTarget) {
388 this.editClickTarget_ = editFields[i]; 493 this.editClickTarget_ = editFields[i];
494 // Prevent mouse up event from deselecting text.
495 this.ignoreMouseUp_ = true;
389 return; 496 return;
390 } 497 }
391 } 498 }
392 }, 499 },
500
501 /**
502 * Called when mouse up happens on the list item or any of its children.
503 * @param {Event} e The mouse up event.
504 * @private
505 */
506 handleMouseUp_: function(e) {
507 if (this.ignoreMouseUp_) {
Dan Beam 2014/10/18 01:35:44 so what happens if you mouse down inside the item
508 this.ignoreMouseUp_ = false;
509 e.preventDefault();
510 }
511 },
512
513 /**
514 * Called when this InlineEditableItem or any of its children are given
515 * focus. Updates focusedColumnIndex_ with the index of the newly focused
516 * column, or -1 if the focused element does not have a column index.
517 * @param {Event} e The focusin event.
518 * @private
519 */
520 handleFocusIn_: function(e) {
521 if(e.target.hasAttribute('inlineeditable-column')) {
Dan Beam 2014/10/18 01:35:44 if( => if (
bondd 2014/10/29 21:12:39 Done.
522 this.focusedColumnIndex_ =
523 e.target.getAttribute('inlineeditable-column');
524 }
525 else
Dan Beam 2014/10/18 01:35:43 } else { // even though this part is a 1-liner,
526 this.focusColumnIndex_ = -1;
bondd 2014/10/28 22:31:27 focusedColumnIndex_
bondd 2014/10/29 21:12:39 Done.
527 },
393 }; 528 };
394 529
395 /** 530 /**
396 * Takes care of committing changes to inline editable list items when the 531 * Takes care of committing changes to inline editable list items when the
397 * window loses focus. 532 * window loses focus.
398 */ 533 */
399 function handleWindowBlurs() { 534 function handleWindowBlurs() {
400 window.addEventListener('blur', function(e) { 535 window.addEventListener('blur', function(e) {
401 var itemAncestor = findAncestor(document.activeElement, function(node) { 536 var itemAncestor = findAncestor(document.activeElement, function(node) {
402 return node instanceof InlineEditableItem; 537 return node instanceof InlineEditableItem;
(...skipping 18 matching lines...) Expand all
421 * @type {boolean} 556 * @type {boolean}
422 */ 557 */
423 focusPlaceholder: false, 558 focusPlaceholder: false,
424 559
425 /** @override */ 560 /** @override */
426 decorate: function() { 561 decorate: function() {
427 DeletableItemList.prototype.decorate.call(this); 562 DeletableItemList.prototype.decorate.call(this);
428 this.setAttribute('inlineeditable', ''); 563 this.setAttribute('inlineeditable', '');
429 this.addEventListener('hasElementFocusChange', 564 this.addEventListener('hasElementFocusChange',
430 this.handleListFocusChange_); 565 this.handleListFocusChange_);
566 this.removeAttribute('tabIndex');
Dan Beam 2014/10/18 01:35:43 tabindex
431 }, 567 },
432 568
433 /** 569 /**
434 * Called when the list hierarchy as a whole loses or gains focus; starts 570 * Called when the list hierarchy as a whole loses or gains focus; starts
435 * or ends editing for the lead item if necessary. 571 * or ends editing for the lead item if necessary.
436 * @param {Event} e The change event. 572 * @param {Event} e The change event.
437 * @private 573 * @private
438 */ 574 */
439 handleListFocusChange_: function(e) { 575 handleListFocusChange_: function(e) {
440 var leadItem = this.getListItemByIndex(this.selectionModel.leadIndex); 576 var leadItem = this.getListItemByIndex(this.selectionModel.leadIndex);
441 if (leadItem) { 577 if (leadItem) {
442 if (e.newValue) 578 if (e.newValue) {
579 leadItem.focusedColumnIndex = -1;
443 leadItem.updateEditState(); 580 leadItem.updateEditState();
581 }
444 else 582 else
445 leadItem.editing = false; 583 leadItem.editing = false;
446 } 584 }
447 }, 585 },
448 586
449 /** 587 /**
588 * Handles a change of the lead item from the selection model.
589 * @param {Event} pe The property change event.
Dan Beam 2014/10/18 01:35:44 pe => e, event, pce (in order of preference)
bondd 2014/10/29 21:12:39 Done.
590 * @override
591 */
592 handleLeadChange: function(pe) {
593 DeletableItemList.prototype.handleLeadChange.call(this, pe);
594
595 var focusedColumnIndex = -1;
596 var element;
597 if (pe.oldValue != -1) {
598 if ((element = this.getListItemByIndex(pe.oldValue))) {
Dan Beam 2014/10/18 01:35:44 don't do assignment in conditionals
bondd 2014/10/29 21:12:39 Done.
599 focusedColumnIndex = element.focusedColumnIndex;
600 element.updateLeadState_();
bondd 2014/10/28 22:31:27 updateLeadState_ is private to a different class
601 }
602 }
603
604 if (pe.newValue != -1) {
605 if ((element = this.getListItemByIndex(pe.newValue))) {
606 element.focusedColumnIndex = focusedColumnIndex;
607 element.updateLeadState_();
bondd 2014/10/28 22:31:27 updateLeadState_ is private to a different class
608 }
609 }
610 },
611
612 /**
613 * Called after the DataModel for the list has been set.
Dan Beam 2014/10/18 01:35:43 * @override
bondd 2014/10/28 22:31:27 Done.
614 */
615 onSetDataModelComplete: function() {
616 DeletableItemList.prototype.onSetDataModelComplete.call(this);
617
618 var firstItem = this.getListItemByIndex(0);
619 if (firstItem)
620 firstItem.focusable = true;
621 },
622
623 /**
450 * May be overridden by subclasses to disable focusing the placeholder. 624 * May be overridden by subclasses to disable focusing the placeholder.
451 * @return {boolean} True if the placeholder element should be focused on 625 * @return {boolean} True if the placeholder element should be focused on
452 * edit commit. 626 * edit commit.
453 */ 627 */
454 shouldFocusPlaceholder: function() { 628 shouldFocusPlaceholder: function() {
455 return true; 629 return true;
456 }, 630 },
457 }; 631 };
458 632
459 // Export 633 // Export
460 return { 634 return {
461 InlineEditableItem: InlineEditableItem, 635 InlineEditableItem: InlineEditableItem,
462 InlineEditableItemList: InlineEditableItemList, 636 InlineEditableItemList: InlineEditableItemList,
463 }; 637 };
464 }); 638 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698