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
3 // found in the LICENSE file.
5 cr.define('options', function() {
8 * @extends {HTMLDivElement}
10 var EditableTextField = cr.ui.define('div');
13 * Decorates an element as an editable text field.
14 * @param {!HTMLElement} el The element to decorate.
16 EditableTextField.decorate = function(el) {
17 el.__proto__ = EditableTextField.prototype;
21 EditableTextField.prototype = {
22 __proto__: HTMLDivElement.prototype,
25 * The actual input element in this field.
26 * @type {?HTMLElement}
32 * The static text displayed when this field isn't editable.
33 * @type {?HTMLElement}
39 * The data model for this field.
46 * Whether or not the current edit should be considered canceled, rather
47 * than committed, when editing ends.
54 decorate: function() {
55 this.classList.add('editable-text-field');
57 this.createEditableTextCell('');
59 if (this.hasAttribute('i18n-placeholder-text')) {
60 var identifier = this.getAttribute('i18n-placeholder-text');
61 var localizedText = loadTimeData.getString(identifier);
63 this.setAttribute('placeholder-text', localizedText);
66 this.addEventListener('keydown', this.handleKeyDown_);
67 this.editField_.addEventListener('focus', this.handleFocus_.bind(this));
68 this.editField_.addEventListener('blur', this.handleBlur_.bind(this));
69 this.checkForEmpty_();
73 * Indicates that this field has no value in the model, and the placeholder
74 * text (if any) should be shown.
78 return this.hasAttribute('empty');
82 * The placeholder text to be used when the model or its value is empty.
85 get placeholderText() {
86 return this.getAttribute('placeholder-text');
88 set placeholderText(text) {
90 this.setAttribute('placeholder-text', text);
92 this.removeAttribute('placeholder-text');
94 this.checkForEmpty_();
98 * Returns the input element in this text field.
99 * @type {HTMLElement} The element that is the actual input field.
102 return this.editField_;
106 * Whether the user is currently editing the list item.
110 return this.hasAttribute('editing');
112 set editing(editing) {
113 if (this.editing == editing)
117 this.setAttribute('editing', '');
119 this.removeAttribute('editing');
122 this.editCanceled_ = false;
125 this.removeAttribute('empty');
127 this.editField.value = '';
129 if (this.editField) {
130 this.editField.focus();
131 this.editField.select();
134 if (!this.editCanceled_ && this.hasBeenEdited &&
135 this.currentInputIsValid) {
136 this.updateStaticValues_();
137 cr.dispatchSimpleEvent(this, 'commitedit', true);
139 this.resetEditableValues_();
140 cr.dispatchSimpleEvent(this, 'canceledit', true);
142 this.checkForEmpty_();
147 * Whether the item is editable.
151 return this.hasAttribute('editable');
153 set editable(editable) {
154 if (this.editable == editable)
158 this.setAttribute('editable', '');
160 this.removeAttribute('editable');
161 this.editable_ = editable;
165 * The data model for this field.
173 this.checkForEmpty_(); // This also updates the editField value.
174 this.updateStaticValues_();
178 * The HTML element that should have focus initially when editing starts,
179 * if a specific element wasn't clicked. Defaults to the first <input>
180 * element; can be overridden by subclasses if a different element should be
182 * @type {?HTMLElement}
184 get initialFocusElement() {
185 return this.querySelector('input');
189 * Whether the input in currently valid to submit. If this returns false
190 * when editing would be submitted, either editing will not be ended,
191 * or it will be cancelled, depending on the context. Can be overridden by
192 * subclasses to perform input validation.
195 get currentInputIsValid() {
200 * Returns true if the item has been changed by an edit. Can be overridden
201 * by subclasses to return false when nothing has changed to avoid
202 * unnecessary commits.
205 get hasBeenEdited() {
210 * Mutates the input during a successful commit. Can be overridden to
211 * provide a way to "clean up" valid input so that it conforms to a
212 * desired format. Will only be called when commit succeeds for valid
213 * input, or when the model is set.
214 * @param {string} value Input text to be mutated.
215 * @return {string} mutated text.
217 mutateInput: function(value) {
222 * Creates a div containing an <input>, as well as static text, keeping
223 * references to them so they can be manipulated.
224 * @param {string} text The text of the cell.
227 createEditableTextCell: function(text) {
228 // This function should only be called once.
232 var container = this.ownerDocument.createElement('div');
234 var textEl = /** @type {HTMLElement} */(
235 this.ownerDocument.createElement('div'));
236 textEl.className = 'static-text';
237 textEl.textContent = text;
238 textEl.setAttribute('displaymode', 'static');
239 this.appendChild(textEl);
240 this.staticText_ = textEl;
242 var inputEl = /** @type {HTMLElement} */(
243 this.ownerDocument.createElement('input'));
244 inputEl.className = 'editable-text';
245 inputEl.type = 'text';
246 inputEl.value = text;
247 inputEl.setAttribute('displaymode', 'edit');
248 inputEl.staticVersion = textEl;
249 this.appendChild(inputEl);
250 this.editField_ = inputEl;
254 * Resets the editable version of any controls created by
255 * createEditableTextCell to match the static text.
258 resetEditableValues_: function() {
259 var editField = this.editField_;
260 var staticLabel = editField.staticVersion;
264 if (editField instanceof HTMLInputElement)
265 editField.value = staticLabel.textContent;
267 editField.setCustomValidity('');
271 * Sets the static version of any controls created by createEditableTextCell
272 * to match the current value of the editable version. Called on commit so
273 * that there's no flicker of the old value before the model updates. Also
274 * updates the model's value with the mutated value of the edit field.
277 updateStaticValues_: function() {
278 var editField = this.editField_;
279 var staticLabel = editField.staticVersion;
283 if (editField instanceof HTMLInputElement) {
284 staticLabel.textContent = editField.value;
285 this.model_.value = this.mutateInput(editField.value);
290 * Checks to see if the model or its value are empty. If they are, then set
291 * the edit field to the placeholder text, if any, and if not, set it to the
295 checkForEmpty_: function() {
296 var editField = this.editField_;
300 if (!this.model_ || !this.model_.value) {
301 this.setAttribute('empty', '');
302 editField.value = this.placeholderText || '';
304 this.removeAttribute('empty');
305 editField.value = this.model_.value;
310 * Called when this widget receives focus.
311 * @param {Event} e the focus event.
314 handleFocus_: function(e) {
320 this.editField_.focus();
324 * Called when this widget loses focus.
325 * @param {Event} e the blur event.
328 handleBlur_: function(e) {
332 this.editing = false;
336 * Called when a key is pressed. Handles committing and canceling edits.
337 * @param {Event} e The key down event.
340 handleKeyDown_: function(e) {
345 switch (e.keyIdentifier) {
346 case 'U+001B': // Esc
347 this.editCanceled_ = true;
351 if (this.currentInputIsValid)
357 // Blurring will trigger the edit to end.
358 this.ownerDocument.activeElement.blur();
359 // Make sure that handled keys aren't passed on and double-handled.
360 // (e.g., esc shouldn't both cancel an edit and close a subpage)
367 * Takes care of committing changes to EditableTextField items when the
368 * window loses focus.
370 window.addEventListener('blur', function(e) {
371 var itemAncestor = findAncestor(document.activeElement, function(node) {
372 return node instanceof EditableTextField;
375 document.activeElement.blur();
379 EditableTextField: EditableTextField,