Roll src/third_party/WebKit d9c6159:8139f33 (svn 201974:201975)
[chromium-blink-merge.git] / chrome / browser / resources / options / editable_text_field.js
blob9c04b62da7cafc6c6a431326e4137f6f9d44f0b6
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() {
6 /**
7 * @constructor
8 * @extends {HTMLDivElement}
9 */
10 var EditableTextField = cr.ui.define('div');
12 EditableTextField.prototype = {
13 __proto__: HTMLDivElement.prototype,
15 /**
16 * The actual input element in this field.
17 * @type {?HTMLElement}
18 * @private
20 editField_: null,
22 /**
23 * The static text displayed when this field isn't editable.
24 * @type {?HTMLElement}
25 * @private
27 staticText_: null,
29 /**
30 * The data model for this field.
31 * @type {?Object}
32 * @private
34 model_: null,
36 /**
37 * Whether or not the current edit should be considered canceled, rather
38 * than committed, when editing ends.
39 * @type {boolean}
40 * @private
42 editCanceled_: true,
44 /** @protected */
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);
53 if (localizedText)
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_();
63 /**
64 * Indicates that this field has no value in the model, and the placeholder
65 * text (if any) should be shown.
66 * @type {boolean}
68 get empty() {
69 return this.hasAttribute('empty');
72 /**
73 * The placeholder text to be used when the model or its value is empty.
74 * @type {string}
76 get placeholderText() {
77 return this.getAttribute('placeholder-text');
79 set placeholderText(text) {
80 if (text)
81 this.setAttribute('placeholder-text', text);
82 else
83 this.removeAttribute('placeholder-text');
85 this.checkForEmpty_();
88 /**
89 * Returns the input element in this text field.
90 * @type {HTMLElement} The element that is the actual input field.
92 get editField() {
93 return this.editField_;
96 /**
97 * Whether the user is currently editing the list item.
98 * @type {boolean}
100 get editing() {
101 return this.hasAttribute('editing');
103 set editing(editing) {
104 if (this.editing == editing)
105 return;
107 if (editing)
108 this.setAttribute('editing', '');
109 else
110 this.removeAttribute('editing');
112 if (editing) {
113 this.editCanceled_ = false;
115 if (this.empty) {
116 this.removeAttribute('empty');
117 if (this.editField)
118 this.editField.value = '';
120 if (this.editField) {
121 this.editField.focus();
122 this.editField.select();
124 } else {
125 if (!this.editCanceled_ && this.hasBeenEdited &&
126 this.currentInputIsValid) {
127 this.updateStaticValues_();
128 cr.dispatchSimpleEvent(this, 'commitedit', true);
129 } else {
130 this.resetEditableValues_();
131 cr.dispatchSimpleEvent(this, 'canceledit', true);
133 this.checkForEmpty_();
138 * Whether the item is editable.
139 * @type {boolean}
141 get editable() {
142 return this.hasAttribute('editable');
144 set editable(editable) {
145 if (this.editable == editable)
146 return;
148 if (editable)
149 this.setAttribute('editable', '');
150 else
151 this.removeAttribute('editable');
152 this.editable_ = editable;
156 * The data model for this field.
157 * @type {Object}
159 get model() {
160 return this.model_;
162 set model(model) {
163 this.model_ = model;
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
172 * focused.
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.
184 * @type {boolean}
186 get currentInputIsValid() {
187 return true;
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.
194 * @type {boolean}
196 get hasBeenEdited() {
197 return true;
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) {
209 return 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.
216 * @private
218 createEditableTextCell: function(text) {
219 // This function should only be called once.
220 if (this.editField_)
221 return;
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.
247 * @private
249 resetEditableValues_: function() {
250 var editField = this.editField_;
251 var staticLabel = editField.staticVersion;
252 if (!staticLabel)
253 return;
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.
266 * @private
268 updateStaticValues_: function() {
269 var editField = this.editField_;
270 var staticLabel = editField.staticVersion;
271 if (!staticLabel)
272 return;
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
283 * model's value.
284 * @private
286 checkForEmpty_: function() {
287 var editField = this.editField_;
288 if (!editField)
289 return;
291 if (!this.model_ || !this.model_.value) {
292 this.setAttribute('empty', '');
293 editField.value = this.placeholderText || '';
294 } else {
295 this.removeAttribute('empty');
296 editField.value = this.model_.value;
301 * Called when this widget receives focus.
302 * @param {Event} e the focus event.
303 * @private
305 handleFocus_: function(e) {
306 if (this.editing)
307 return;
309 this.editing = true;
310 if (this.editField_)
311 this.editField_.focus();
315 * Called when this widget loses focus.
316 * @param {Event} e the blur event.
317 * @private
319 handleBlur_: function(e) {
320 if (!this.editing)
321 return;
323 this.editing = false;
327 * Called when a key is pressed. Handles committing and canceling edits.
328 * @param {Event} e The key down event.
329 * @private
331 handleKeyDown_: function(e) {
332 if (!this.editing)
333 return;
335 var endEdit;
336 switch (e.keyIdentifier) {
337 case 'U+001B': // Esc
338 this.editCanceled_ = true;
339 endEdit = true;
340 break;
341 case 'Enter':
342 if (this.currentInputIsValid)
343 endEdit = true;
344 break;
347 if (endEdit) {
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)
352 e.stopPropagation();
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;
365 if (itemAncestor)
366 document.activeElement.blur();
369 return {
370 EditableTextField: EditableTextField,