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 var EditableTextField = cr.ui.define('div');
9 * Decorates an element as an editable text field.
10 * @param {!HTMLElement} el The element to decorate.
12 EditableTextField.decorate = function(el) {
13 el.__proto__ = EditableTextField.prototype;
17 EditableTextField.prototype = {
18 __proto__: HTMLDivElement.prototype,
21 * The actual input element in this field.
22 * @type {?HTMLElement}
28 * The static text displayed when this field isn't editable.
29 * @type {?HTMLElement}
35 * The data model for this field.
42 * Whether or not the current edit should be considered canceled, rather
43 * than committed, when editing ends.
50 decorate: function() {
51 this.classList.add('editable-text-field');
53 this.createEditableTextCell();
55 if (this.hasAttribute('i18n-placeholder-text')) {
56 var identifier = this.getAttribute('i18n-placeholder-text');
57 var localizedText = loadTimeData.getString(identifier);
59 this.setAttribute('placeholder-text', localizedText);
62 this.addEventListener('keydown', this.handleKeyDown_);
63 this.editField_.addEventListener('focus', this.handleFocus_.bind(this));
64 this.editField_.addEventListener('blur', this.handleBlur_.bind(this));
65 this.checkForEmpty_();
69 * Indicates that this field has no value in the model, and the placeholder
70 * text (if any) should be shown.
74 return this.hasAttribute('empty');
78 * The placeholder text to be used when the model or its value is empty.
81 get placeholderText() {
82 return this.getAttribute('placeholder-text');
84 set placeholderText(text) {
86 this.setAttribute('placeholder-text', text);
88 this.removeAttribute('placeholder-text');
90 this.checkForEmpty_();
94 * Returns the input element in this text field.
95 * @type {HTMLElement} The element that is the actual input field.
98 return this.editField_;
102 * Whether the user is currently editing the list item.
106 return this.hasAttribute('editing');
108 set editing(editing) {
109 if (this.editing == editing)
113 this.setAttribute('editing', '');
115 this.removeAttribute('editing');
118 this.editCanceled_ = false;
121 this.removeAttribute('empty');
123 this.editField.value = '';
125 if (this.editField) {
126 this.editField.focus();
127 this.editField.select();
130 if (!this.editCanceled_ && this.hasBeenEdited &&
131 this.currentInputIsValid) {
132 this.updateStaticValues_();
133 cr.dispatchSimpleEvent(this, 'commitedit', true);
135 this.resetEditableValues_();
136 cr.dispatchSimpleEvent(this, 'canceledit', true);
138 this.checkForEmpty_();
143 * Whether the item is editable.
147 return this.hasAttribute('editable');
149 set editable(editable) {
150 if (this.editable == editable)
154 this.setAttribute('editable', '');
156 this.removeAttribute('editable');
157 this.editable_ = editable;
161 * The data model for this field.
169 this.checkForEmpty_(); // This also updates the editField value.
170 this.updateStaticValues_();
174 * The HTML element that should have focus initially when editing starts,
175 * if a specific element wasn't clicked. Defaults to the first <input>
176 * element; can be overridden by subclasses if a different element should be
178 * @type {?HTMLElement}
180 get initialFocusElement() {
181 return this.querySelector('input');
185 * Whether the input in currently valid to submit. If this returns false
186 * when editing would be submitted, either editing will not be ended,
187 * or it will be cancelled, depending on the context. Can be overridden by
188 * subclasses to perform input validation.
191 get currentInputIsValid() {
196 * Returns true if the item has been changed by an edit. Can be overridden
197 * by subclasses to return false when nothing has changed to avoid
198 * unnecessary commits.
201 get hasBeenEdited() {
206 * Mutates the input during a successful commit. Can be overridden to
207 * provide a way to "clean up" valid input so that it conforms to a
208 * desired format. Will only be called when commit succeeds for valid
209 * input, or when the model is set.
210 * @param {string} value Input text to be mutated.
211 * @return {string} mutated text.
213 mutateInput: function(value) {
218 * Creates a div containing an <input>, as well as static text, keeping
219 * references to them so they can be manipulated.
220 * @param {string} text The text of the cell.
223 createEditableTextCell: function(text) {
224 // This function should only be called once.
228 var container = this.ownerDocument.createElement('div');
230 var textEl = this.ownerDocument.createElement('div');
231 textEl.className = 'static-text';
232 textEl.textContent = text;
233 textEl.setAttribute('displaymode', 'static');
234 this.appendChild(textEl);
235 this.staticText_ = textEl;
237 var inputEl = this.ownerDocument.createElement('input');
238 inputEl.className = 'editable-text';
239 inputEl.type = 'text';
240 inputEl.value = text;
241 inputEl.setAttribute('displaymode', 'edit');
242 inputEl.staticVersion = textEl;
243 this.appendChild(inputEl);
244 this.editField_ = inputEl;
248 * Resets the editable version of any controls created by
249 * createEditableTextCell to match the static text.
252 resetEditableValues_: function() {
253 var editField = this.editField_;
254 var staticLabel = editField.staticVersion;
258 if (editField instanceof HTMLInputElement)
259 editField.value = staticLabel.textContent;
261 editField.setCustomValidity('');
265 * Sets the static version of any controls created by createEditableTextCell
266 * to match the current value of the editable version. Called on commit so
267 * that there's no flicker of the old value before the model updates. Also
268 * updates the model's value with the mutated value of the edit field.
271 updateStaticValues_: function() {
272 var editField = this.editField_;
273 var staticLabel = editField.staticVersion;
277 if (editField instanceof HTMLInputElement) {
278 staticLabel.textContent = editField.value;
279 this.model_.value = this.mutateInput(editField.value);
284 * Checks to see if the model or its value are empty. If they are, then set
285 * the edit field to the placeholder text, if any, and if not, set it to the
289 checkForEmpty_: function() {
290 var editField = this.editField_;
294 if (!this.model_ || !this.model_.value) {
295 this.setAttribute('empty', '');
296 editField.value = this.placeholderText || '';
298 this.removeAttribute('empty');
299 editField.value = this.model_.value;
304 * Called when this widget receives focus.
305 * @param {Event} e the focus event.
308 handleFocus_: function(e) {
314 this.editField_.focus();
318 * Called when this widget loses focus.
319 * @param {Event} e the blur event.
322 handleBlur_: function(e) {
326 this.editing = false;
330 * Called when a key is pressed. Handles committing and canceling edits.
331 * @param {Event} e The key down event.
334 handleKeyDown_: function(e) {
339 switch (e.keyIdentifier) {
340 case 'U+001B': // Esc
341 this.editCanceled_ = true;
345 if (this.currentInputIsValid)
351 // Blurring will trigger the edit to end.
352 this.ownerDocument.activeElement.blur();
353 // Make sure that handled keys aren't passed on and double-handled.
354 // (e.g., esc shouldn't both cancel an edit and close a subpage)
361 * Takes care of committing changes to EditableTextField items when the
362 * window loses focus.
364 window.addEventListener('blur', function(e) {
365 var itemAncestor = findAncestor(document.activeElement, function(node) {
366 return node instanceof EditableTextField;
369 document.activeElement.blur();
373 EditableTextField: EditableTextField,