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

Unified Diff: chrome/browser/resources/options2/inline_editable_list.js

Issue 8895023: Options2: Pull the trigger. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: DIAF. Created 9 years 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 side-by-side diff with in-line comments
Download patch
Index: chrome/browser/resources/options2/inline_editable_list.js
diff --git a/chrome/browser/resources/options2/inline_editable_list.js b/chrome/browser/resources/options2/inline_editable_list.js
new file mode 100644
index 0000000000000000000000000000000000000000..8aed93beedf423fe5d24ca8c6f317fa50979672e
--- /dev/null
+++ b/chrome/browser/resources/options2/inline_editable_list.js
@@ -0,0 +1,414 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ const DeletableItem = options.DeletableItem;
+ const DeletableItemList = options.DeletableItemList;
+
+ /**
+ * Creates a new list item with support for inline editing.
+ * @constructor
+ * @extends {options.DeletableListItem}
+ */
+ function InlineEditableItem() {
+ var el = cr.doc.createElement('div');
+ InlineEditableItem.decorate(el);
+ return el;
+ }
+
+ /**
+ * Decorates an element as a inline-editable list item. Note that this is
+ * a subclass of DeletableItem.
+ * @param {!HTMLElement} el The element to decorate.
+ */
+ InlineEditableItem.decorate = function(el) {
+ el.__proto__ = InlineEditableItem.prototype;
+ el.decorate();
+ };
+
+ InlineEditableItem.prototype = {
+ __proto__: DeletableItem.prototype,
+
+ /**
+ * Whether or not this item can be edited.
+ * @type {boolean}
+ * @private
+ */
+ editable_: true,
+
+ /**
+ * Whether or not this is a placeholder for adding a new item.
+ * @type {boolean}
+ * @private
+ */
+ isPlaceholder_: false,
+
+ /**
+ * Fields associated with edit mode.
+ * @type {array}
+ * @private
+ */
+ editFields_: null,
+
+ /**
+ * Whether or not the current edit should be considered cancelled, rather
+ * than committed, when editing ends.
+ * @type {boolean}
+ * @private
+ */
+ editCancelled_: true,
+
+ /**
+ * The editable item corresponding to the last click, if any. Used to decide
+ * initial focus when entering edit mode.
+ * @type {HTMLElement}
+ * @private
+ */
+ editClickTarget_: null,
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItem.prototype.decorate.call(this);
+
+ this.editFields_ = [];
+ this.addEventListener('mousedown', this.handleMouseDown_);
+ this.addEventListener('keydown', this.handleKeyDown_);
+ this.addEventListener('leadChange', this.handleLeadChange_);
+ },
+
+ /** @inheritDoc */
+ selectionChanged: function() {
+ this.updateEditState();
+ },
+
+ /**
+ * Called when this element gains or loses 'lead' status. Updates editing
+ * mode accordingly.
+ * @private
+ */
+ handleLeadChange_: function() {
+ this.updateEditState();
+ },
+
+ /**
+ * Updates the edit state based on the current selected and lead states.
+ */
+ updateEditState: function() {
+ if (this.editable)
+ this.editing = this.selected && this.lead;
+ },
+
+ /**
+ * Whether the user is currently editing the list item.
+ * @type {boolean}
+ */
+ get editing() {
+ return this.hasAttribute('editing');
+ },
+ set editing(editing) {
+ if (this.editing == editing)
+ return;
+
+ if (editing)
+ this.setAttribute('editing', '');
+ else
+ this.removeAttribute('editing');
+
+ if (editing) {
+ this.editCancelled_ = false;
+
+ cr.dispatchSimpleEvent(this, 'edit', true);
+
+ var focusElement = this.editClickTarget_ || this.initialFocusElement;
+ this.editClickTarget_ = null;
+
+ // When this is called in response to the selectedChange event,
+ // the list grabs focus immediately afterwards. Thus we must delay
+ // our focus grab.
+ var self = this;
+ if (focusElement) {
+ window.setTimeout(function() {
+ // Make sure we are still in edit mode by the time we execute.
+ if (self.editing) {
+ focusElement.focus();
+ focusElement.select();
+ }
+ }, 50);
+ }
+ } else {
+ if (!this.editCancelled_ && this.hasBeenEdited &&
+ this.currentInputIsValid) {
+ if (this.isPlaceholder)
+ this.parentNode.focusPlaceholder = true;
+
+ this.updateStaticValues_();
+ cr.dispatchSimpleEvent(this, 'commitedit', true);
+ } else {
+ this.resetEditableValues_();
+ cr.dispatchSimpleEvent(this, 'canceledit', true);
+ }
+ }
+ },
+
+ /**
+ * Whether the item is editable.
+ * @type {boolean}
+ */
+ get editable() {
+ return this.editable_;
+ },
+ set editable(editable) {
+ this.editable_ = editable;
+ if (!editable)
+ this.editing = false;
+ },
+
+ /**
+ * Whether the item is a new item placeholder.
+ * @type {boolean}
+ */
+ get isPlaceholder() {
+ return this.isPlaceholder_;
+ },
+ set isPlaceholder(isPlaceholder) {
+ this.isPlaceholder_ = isPlaceholder;
+ if (isPlaceholder)
+ this.deletable = false;
+ },
+
+ /**
+ * The HTML element that should have focus initially when editing starts,
+ * if a specific element wasn't clicked.
+ * Defaults to the first <input> element; can be overriden by subclasses if
+ * a different element should be focused.
+ * @type {HTMLElement}
+ */
+ get initialFocusElement() {
+ return this.contentElement.querySelector('input');
+ },
+
+ /**
+ * Whether the input in currently valid to submit. If this returns false
+ * when editing would be submitted, either editing will not be ended,
+ * or it will be cancelled, depending on the context.
+ * Can be overrided by subclasses to perform input validation.
+ * @type {boolean}
+ */
+ get currentInputIsValid() {
+ return true;
+ },
+
+ /**
+ * Returns true if the item has been changed by an edit.
+ * Can be overrided by subclasses to return false when nothing has changed
+ * to avoid unnecessary commits.
+ * @type {boolean}
+ */
+ get hasBeenEdited() {
+ return true;
+ },
+
+ /**
+ * Returns a div containing an <input>, as well as static text if
+ * isPlaceholder is not true.
+ * @param {string} text The text of the cell.
+ * @return {HTMLElement} The HTML element for the cell.
+ * @private
+ */
+ createEditableTextCell: function(text) {
+ var container = this.ownerDocument.createElement('div');
+
+ if (!this.isPlaceholder) {
+ var textEl = this.ownerDocument.createElement('div');
+ textEl.className = 'static-text';
+ textEl.textContent = text;
+ textEl.setAttribute('displaymode', 'static');
+ container.appendChild(textEl);
+ }
+
+ var inputEl = this.ownerDocument.createElement('input');
+ inputEl.type = 'text';
+ inputEl.value = text;
+ if (!this.isPlaceholder) {
+ inputEl.setAttribute('displaymode', 'edit');
+ inputEl.staticVersion = textEl;
+ } else {
+ // At this point |this| is not attached to the parent list yet, so give
+ // a short timeout in order for the attachment to occur.
+ var self = this;
+ window.setTimeout(function() {
+ var list = self.parentNode;
+ if (list && list.focusPlaceholder) {
+ list.focusPlaceholder = false;
+ if (list.shouldFocusPlaceholder())
+ inputEl.focus();
+ }
+ }, 50);
+ }
+
+ inputEl.addEventListener('focus', this.handleFocus_.bind(this));
+ container.appendChild(inputEl);
+ this.editFields_.push(inputEl);
+
+ return container;
+ },
+
+ /**
+ * Resets the editable version of any controls created by createEditable*
+ * to match the static text.
+ * @private
+ */
+ resetEditableValues_: function() {
+ var editFields = this.editFields_;
+ for (var i = 0; i < editFields.length; i++) {
+ var staticLabel = editFields[i].staticVersion;
+ if (!staticLabel && !this.isPlaceholder)
+ continue;
+
+ if (editFields[i].tagName == 'INPUT') {
+ editFields[i].value =
+ this.isPlaceholder ? '' : staticLabel.textContent;
+ }
+ // Add more tag types here as new createEditable* methods are added.
+
+ editFields[i].setCustomValidity('');
+ }
+ },
+
+ /**
+ * Sets the static version of any controls created by createEditable*
+ * to match the current value of the editable version. Called on commit so
+ * that there's no flicker of the old value before the model updates.
+ * @private
+ */
+ updateStaticValues_: function() {
+ var editFields = this.editFields_;
+ for (var i = 0; i < editFields.length; i++) {
+ var staticLabel = editFields[i].staticVersion;
+ if (!staticLabel)
+ continue;
+
+ if (editFields[i].tagName == 'INPUT')
+ staticLabel.textContent = editFields[i].value;
+ // Add more tag types here as new createEditable* methods are added.
+ }
+ },
+
+ /**
+ * Called a key is pressed. Handles committing and cancelling edits.
+ * @param {Event} e The key down event.
+ * @private
+ */
+ handleKeyDown_: function(e) {
+ if (!this.editing)
+ return;
+
+ var endEdit = false;
+ switch (e.keyIdentifier) {
+ case 'U+001B': // Esc
+ this.editCancelled_ = true;
+ endEdit = true;
+ break;
+ case 'Enter':
+ if (this.currentInputIsValid)
+ endEdit = true;
+ break;
+ }
+
+ if (endEdit) {
+ // Blurring will trigger the edit to end; see InlineEditableItemList.
+ this.ownerDocument.activeElement.blur();
+ // Make sure that handled keys aren't passed on and double-handled.
+ // (e.g., esc shouldn't both cancel an edit and close a subpage)
+ e.stopPropagation();
+ }
+ },
+
+ /**
+ * Called when the list item is clicked. If the click target corresponds to
+ * an editable item, stores that item to focus when edit mode is started.
+ * @param {Event} e The mouse down event.
+ * @private
+ */
+ handleMouseDown_: function(e) {
+ if (!this.editable || this.editing)
+ return;
+
+ var clickTarget = e.target;
+ var editFields = this.editFields_;
+ for (var i = 0; i < editFields.length; i++) {
+ if (editFields[i] == clickTarget ||
+ editFields[i].staticVersion == clickTarget) {
+ this.editClickTarget_ = editFields[i];
+ return;
+ }
+ }
+ },
+ };
+
+ /**
+ * Takes care of committing changes to inline editable list items when the
+ * window loses focus.
+ */
+ function handleWindowBlurs() {
+ window.addEventListener('blur', function(e) {
+ var itemAncestor = findAncestor(document.activeElement, function(node) {
+ return node instanceof InlineEditableItem;
+ });
+ if (itemAncestor);
+ document.activeElement.blur();
+ });
+ }
+ handleWindowBlurs();
+
+ var InlineEditableItemList = cr.ui.define('list');
+
+ InlineEditableItemList.prototype = {
+ __proto__: DeletableItemList.prototype,
+
+ /**
+ * Focuses the input element of the placeholder if true.
+ * @type {boolean}
+ */
+ focusPlaceholder: false,
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItemList.prototype.decorate.call(this);
+ this.setAttribute('inlineeditable', '');
+ this.addEventListener('hasElementFocusChange',
+ this.handleListFocusChange_);
+ },
+
+ /**
+ * Called when the list hierarchy as a whole loses or gains focus; starts
+ * or ends editing for the lead item if necessary.
+ * @param {Event} e The change event.
+ * @private
+ */
+ handleListFocusChange_: function(e) {
+ var leadItem = this.getListItemByIndex(this.selectionModel.leadIndex);
+ if (leadItem) {
+ if (e.newValue)
+ leadItem.updateEditState();
+ else
+ leadItem.editing = false;
+ }
+ },
+
+ /**
+ * May be overridden by subclasses to disable focusing the placeholder.
+ * @return true if the placeholder element should be focused on edit commit.
+ */
+ shouldFocusPlaceholder: function() {
+ return true;
+ },
+ };
+
+ // Export
+ return {
+ InlineEditableItem: InlineEditableItem,
+ InlineEditableItemList: InlineEditableItemList,
+ };
+});

Powered by Google App Engine
This is Rietveld 408576698