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');
12 EditableTextField.prototype = {
13 __proto__: HTMLDivElement.prototype,
16 * The actual input element in this field.
17 * @type {?HTMLElement}
23 * The static text displayed when this field isn't editable.
24 * @type {?HTMLElement}
30 * The data model for this field.
37 * Whether or not the current edit should be considered canceled, rather
38 * than committed, when editing ends.
45 decorate: function() {
46 this.classList.add('editable-text-field');
48 this.createEditableTextCell('');
50 if (this.hasAttribute('i18n-placeholder-text')) {
51 var identifier = this.getAttribute('i18n-placeholder-text');
52 var localizedText = loadTimeData.getString(identifier);
54 this.setAttribute('placeholder-text', localizedText);
57 this.addEventListener('keydown', this.handleKeyDown_);
58 this.editField_.addEventListener('focus', this.handleFocus_.bind(this));
59 this.editField_.addEventListener('blur', this.handleBlur_.bind(this));
60 this.checkForEmpty_();
64 * Indicates that this field has no value in the model, and the placeholder
65 * text (if any) should be shown.
69 return this.hasAttribute('empty');
73 * The placeholder text to be used when the model or its value is empty.
76 get placeholderText() {
77 return this.getAttribute('placeholder-text');
79 set placeholderText(text) {
81 this.setAttribute('placeholder-text', text);
83 this.removeAttribute('placeholder-text');
85 this.checkForEmpty_();
89 * Returns the input element in this text field.
90 * @type {HTMLElement} The element that is the actual input field.
93 return this.editField_;
97 * Whether the user is currently editing the list item.
101 return this.hasAttribute('editing');
103 set editing(editing) {
104 if (this.editing == editing)
108 this.setAttribute('editing', '');
110 this.removeAttribute('editing');
113 this.editCanceled_ = false;
116 this.removeAttribute('empty');
118 this.editField.value = '';
120 if (this.editField) {
121 this.editField.focus();
122 this.editField.select();
125 if (!this.editCanceled_ && this.hasBeenEdited &&
126 this.currentInputIsValid) {
127 this.updateStaticValues_();
128 cr.dispatchSimpleEvent(this, 'commitedit', true);
130 this.resetEditableValues_();
131 cr.dispatchSimpleEvent(this, 'canceledit', true);
133 this.checkForEmpty_();
138 * Whether the item is editable.
142 return this.hasAttribute('editable');
144 set editable(editable) {
145 if (this.editable == editable)
149 this.setAttribute('editable', '');
151 this.removeAttribute('editable');
152 this.editable_ = editable;
156 * The data model for this field.
164 this.checkForEmpty_(); // This also updates the editField value.
165 this.updateStaticValues_();
169 * The HTML element that should have focus initially when editing starts,
170 * if a specific element wasn't clicked. Defaults to the first <input>
171 * element; can be overridden by subclasses if a different element should be
173 * @type {?HTMLElement}
175 get initialFocusElement() {
176 return this.querySelector('input');
180 * Whether the input in currently valid to submit. If this returns false
181 * when editing would be submitted, either editing will not be ended,
182 * or it will be cancelled, depending on the context. Can be overridden by
183 * subclasses to perform input validation.
186 get currentInputIsValid() {
191 * Returns true if the item has been changed by an edit. Can be overridden
192 * by subclasses to return false when nothing has changed to avoid
193 * unnecessary commits.
196 get hasBeenEdited() {
201 * Mutates the input during a successful commit. Can be overridden to
202 * provide a way to "clean up" valid input so that it conforms to a
203 * desired format. Will only be called when commit succeeds for valid
204 * input, or when the model is set.
205 * @param {string} value Input text to be mutated.
206 * @return {string} mutated text.
208 mutateInput: function(value) {
213 * Creates a div containing an <input>, as well as static text, keeping
214 * references to them so they can be manipulated.
215 * @param {string} text The text of the cell.
218 createEditableTextCell: function(text) {
219 // This function should only be called once.
223 var container = this.ownerDocument.createElement('div');
225 var textEl = /** @type {HTMLElement} */(
226 this.ownerDocument.createElement('div'));
227 textEl.className = 'static-text';
228 textEl.textContent = text;
229 textEl.setAttribute('displaymode', 'static');
230 this.appendChild(textEl);
231 this.staticText_ = textEl;
233 var inputEl = /** @type {HTMLElement} */(
234 this.ownerDocument.createElement('input'));
235 inputEl.className = 'editable-text';
236 inputEl.type = 'text';
237 inputEl.value = text;
238 inputEl.setAttribute('displaymode', 'edit');
239 inputEl.staticVersion = textEl;
240 this.appendChild(inputEl);
241 this.editField_ = inputEl;
245 * Resets the editable version of any controls created by
246 * createEditableTextCell to match the static text.
249 resetEditableValues_: function() {
250 var editField = this.editField_;
251 var staticLabel = editField.staticVersion;
255 if (editField instanceof HTMLInputElement)
256 editField.value = staticLabel.textContent;
258 editField.setCustomValidity('');
262 * Sets the static version of any controls created by createEditableTextCell
263 * to match the current value of the editable version. Called on commit so
264 * that there's no flicker of the old value before the model updates. Also
265 * updates the model's value with the mutated value of the edit field.
268 updateStaticValues_: function() {
269 var editField = this.editField_;
270 var staticLabel = editField.staticVersion;
274 if (editField instanceof HTMLInputElement) {
275 staticLabel.textContent = editField.value;
276 this.model_.value = this.mutateInput(editField.value);
281 * Checks to see if the model or its value are empty. If they are, then set
282 * the edit field to the placeholder text, if any, and if not, set it to the
286 checkForEmpty_: function() {
287 var editField = this.editField_;
291 if (!this.model_ || !this.model_.value) {
292 this.setAttribute('empty', '');
293 editField.value = this.placeholderText || '';
295 this.removeAttribute('empty');
296 editField.value = this.model_.value;
301 * Called when this widget receives focus.
302 * @param {Event} e the focus event.
305 handleFocus_: function(e) {
311 this.editField_.focus();
315 * Called when this widget loses focus.
316 * @param {Event} e the blur event.
319 handleBlur_: function(e) {
323 this.editing = false;
327 * Called when a key is pressed. Handles committing and canceling edits.
328 * @param {Event} e The key down event.
331 handleKeyDown_: function(e) {
336 switch (e.keyIdentifier) {
337 case 'U+001B': // Esc
338 this.editCanceled_ = true;
342 if (this.currentInputIsValid)
348 // Blurring will trigger the edit to end.
349 this.ownerDocument.activeElement.blur();
350 // Make sure that handled keys aren't passed on and double-handled.
351 // (e.g., esc shouldn't both cancel an edit and close a subpage)
358 * Takes care of committing changes to EditableTextField items when the
359 * window loses focus.
361 window.addEventListener('blur', function(e) {
362 var itemAncestor = findAncestor(document.activeElement, function(node) {
363 return node instanceof EditableTextField;
366 document.activeElement.blur();
370 EditableTextField: EditableTextField,