Only grant permissions to new extensions from sync if they have the expected version
[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
19      */
20     editField_: null,
22     /**
23      * The static text displayed when this field isn't editable.
24      * @type {?HTMLElement}
25      * @private
26      */
27     staticText_: null,
29     /**
30      * The data model for this field.
31      * @type {?Object}
32      * @private
33      */
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
41      */
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);
55       }
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_();
61     },
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}
67      */
68     get empty() {
69       return this.hasAttribute('empty');
70     },
72     /**
73      * The placeholder text to be used when the model or its value is empty.
74      * @type {string}
75      */
76     get placeholderText() {
77       return this.getAttribute('placeholder-text');
78     },
79     set placeholderText(text) {
80       if (text)
81         this.setAttribute('placeholder-text', text);
82       else
83         this.removeAttribute('placeholder-text');
85       this.checkForEmpty_();
86     },
88     /**
89      * Returns the input element in this text field.
90      * @type {HTMLElement} The element that is the actual input field.
91      */
92     get editField() {
93       return this.editField_;
94     },
96     /**
97      * Whether the user is currently editing the list item.
98      * @type {boolean}
99      */
100     get editing() {
101       return this.hasAttribute('editing');
102     },
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 = '';
119         }
120         if (this.editField) {
121           this.editField.focus();
122           this.editField.select();
123         }
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);
132         }
133         this.checkForEmpty_();
134       }
135     },
137     /**
138      * Whether the item is editable.
139      * @type {boolean}
140      */
141     get editable() {
142       return this.hasAttribute('editable');
143     },
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;
153     },
155     /**
156      * The data model for this field.
157      * @type {Object}
158      */
159     get model() {
160       return this.model_;
161     },
162     set model(model) {
163       this.model_ = model;
164       this.checkForEmpty_();  // This also updates the editField value.
165       this.updateStaticValues_();
166     },
168     /**
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}
174      */
175     get initialFocusElement() {
176       return this.querySelector('input');
177     },
179     /**
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}
185      */
186     get currentInputIsValid() {
187       return true;
188     },
190     /**
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}
195      */
196     get hasBeenEdited() {
197       return true;
198     },
200     /**
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.
207      */
208     mutateInput: function(value) {
209       return value;
210     },
212     /**
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
217      */
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;
242     },
244     /**
245      * Resets the editable version of any controls created by
246      * createEditableTextCell to match the static text.
247      * @private
248      */
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('');
259     },
261     /**
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
267      */
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);
277       }
278     },
280     /**
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
285      */
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;
297       }
298     },
300     /**
301      * Called when this widget receives focus.
302      * @param {Event} e the focus event.
303      * @private
304      */
305     handleFocus_: function(e) {
306       if (this.editing)
307         return;
309       this.editing = true;
310       if (this.editField_)
311         this.editField_.focus();
312     },
314     /**
315      * Called when this widget loses focus.
316      * @param {Event} e the blur event.
317      * @private
318      */
319     handleBlur_: function(e) {
320       if (!this.editing)
321         return;
323       this.editing = false;
324     },
326     /**
327      * Called when a key is pressed. Handles committing and canceling edits.
328      * @param {Event} e The key down event.
329      * @private
330      */
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;
345       }
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();
353       }
354     },
355   };
357   /**
358    * Takes care of committing changes to EditableTextField items when the
359    * window loses focus.
360    */
361   window.addEventListener('blur', function(e) {
362     var itemAncestor = findAncestor(document.activeElement, function(node) {
363       return node instanceof EditableTextField;
364     });
365     if (itemAncestor)
366       document.activeElement.blur();
367   });
369   return {
370     EditableTextField: EditableTextField,
371   };