Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(351)

Side by Side Diff: chrome/browser/resources/options2/inline_editable_list.js

Issue 10809005: Options: Rename chrome/browser/resources/options2 -> chrome/browser/resources/options. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix. Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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.
4
5 cr.define('options', function() {
6 /** @const */ var DeletableItem = options.DeletableItem;
7 /** @const */ var DeletableItemList = options.DeletableItemList;
8
9 /**
10 * Creates a new list item with support for inline editing.
11 * @constructor
12 * @extends {options.DeletableListItem}
13 */
14 function InlineEditableItem() {
15 var el = cr.doc.createElement('div');
16 InlineEditableItem.decorate(el);
17 return el;
18 }
19
20 /**
21 * Decorates an element as a inline-editable list item. Note that this is
22 * a subclass of DeletableItem.
23 * @param {!HTMLElement} el The element to decorate.
24 */
25 InlineEditableItem.decorate = function(el) {
26 el.__proto__ = InlineEditableItem.prototype;
27 el.decorate();
28 };
29
30 InlineEditableItem.prototype = {
31 __proto__: DeletableItem.prototype,
32
33 /**
34 * Whether or not this item can be edited.
35 * @type {boolean}
36 * @private
37 */
38 editable_: true,
39
40 /**
41 * Whether or not this is a placeholder for adding a new item.
42 * @type {boolean}
43 * @private
44 */
45 isPlaceholder_: false,
46
47 /**
48 * Fields associated with edit mode.
49 * @type {array}
50 * @private
51 */
52 editFields_: null,
53
54 /**
55 * Whether or not the current edit should be considered cancelled, rather
56 * than committed, when editing ends.
57 * @type {boolean}
58 * @private
59 */
60 editCancelled_: true,
61
62 /**
63 * The editable item corresponding to the last click, if any. Used to decide
64 * initial focus when entering edit mode.
65 * @type {HTMLElement}
66 * @private
67 */
68 editClickTarget_: null,
69
70 /** @inheritDoc */
71 decorate: function() {
72 DeletableItem.prototype.decorate.call(this);
73
74 this.editFields_ = [];
75 this.addEventListener('mousedown', this.handleMouseDown_);
76 this.addEventListener('keydown', this.handleKeyDown_);
77 this.addEventListener('leadChange', this.handleLeadChange_);
78 },
79
80 /** @inheritDoc */
81 selectionChanged: function() {
82 this.updateEditState();
83 },
84
85 /**
86 * Called when this element gains or loses 'lead' status. Updates editing
87 * mode accordingly.
88 * @private
89 */
90 handleLeadChange_: function() {
91 this.updateEditState();
92 },
93
94 /**
95 * Updates the edit state based on the current selected and lead states.
96 */
97 updateEditState: function() {
98 if (this.editable)
99 this.editing = this.selected && this.lead;
100 },
101
102 /**
103 * Whether the user is currently editing the list item.
104 * @type {boolean}
105 */
106 get editing() {
107 return this.hasAttribute('editing');
108 },
109 set editing(editing) {
110 if (this.editing == editing)
111 return;
112
113 if (editing)
114 this.setAttribute('editing', '');
115 else
116 this.removeAttribute('editing');
117
118 if (editing) {
119 this.editCancelled_ = false;
120
121 cr.dispatchSimpleEvent(this, 'edit', true);
122
123 var focusElement = this.editClickTarget_ || this.initialFocusElement;
124 this.editClickTarget_ = null;
125
126 // When this is called in response to the selectedChange event,
127 // the list grabs focus immediately afterwards. Thus we must delay
128 // our focus grab.
129 var self = this;
130 if (focusElement) {
131 window.setTimeout(function() {
132 // Make sure we are still in edit mode by the time we execute.
133 if (self.editing) {
134 focusElement.focus();
135 focusElement.select();
136 }
137 }, 50);
138 }
139 } else {
140 if (!this.editCancelled_ && this.hasBeenEdited &&
141 this.currentInputIsValid) {
142 if (this.isPlaceholder)
143 this.parentNode.focusPlaceholder = true;
144
145 this.updateStaticValues_();
146 cr.dispatchSimpleEvent(this, 'commitedit', true);
147 } else {
148 this.resetEditableValues_();
149 cr.dispatchSimpleEvent(this, 'canceledit', true);
150 }
151 }
152 },
153
154 /**
155 * Whether the item is editable.
156 * @type {boolean}
157 */
158 get editable() {
159 return this.editable_;
160 },
161 set editable(editable) {
162 this.editable_ = editable;
163 if (!editable)
164 this.editing = false;
165 },
166
167 /**
168 * Whether the item is a new item placeholder.
169 * @type {boolean}
170 */
171 get isPlaceholder() {
172 return this.isPlaceholder_;
173 },
174 set isPlaceholder(isPlaceholder) {
175 this.isPlaceholder_ = isPlaceholder;
176 if (isPlaceholder)
177 this.deletable = false;
178 },
179
180 /**
181 * The HTML element that should have focus initially when editing starts,
182 * if a specific element wasn't clicked.
183 * Defaults to the first <input> element; can be overridden by subclasses if
184 * a different element should be focused.
185 * @type {HTMLElement}
186 */
187 get initialFocusElement() {
188 return this.contentElement.querySelector('input');
189 },
190
191 /**
192 * Whether the input in currently valid to submit. If this returns false
193 * when editing would be submitted, either editing will not be ended,
194 * or it will be cancelled, depending on the context.
195 * Can be overridden by subclasses to perform input validation.
196 * @type {boolean}
197 */
198 get currentInputIsValid() {
199 return true;
200 },
201
202 /**
203 * Returns true if the item has been changed by an edit.
204 * Can be overridden by subclasses to return false when nothing has changed
205 * to avoid unnecessary commits.
206 * @type {boolean}
207 */
208 get hasBeenEdited() {
209 return true;
210 },
211
212 /**
213 * Returns a div containing an <input>, as well as static text if
214 * isPlaceholder is not true.
215 * @param {string} text The text of the cell.
216 * @return {HTMLElement} The HTML element for the cell.
217 * @private
218 */
219 createEditableTextCell: function(text) {
220 var container = this.ownerDocument.createElement('div');
221
222 if (!this.isPlaceholder) {
223 var textEl = this.ownerDocument.createElement('div');
224 textEl.className = 'static-text';
225 textEl.textContent = text;
226 textEl.setAttribute('displaymode', 'static');
227 container.appendChild(textEl);
228 }
229
230 var inputEl = this.ownerDocument.createElement('input');
231 inputEl.type = 'text';
232 inputEl.value = text;
233 if (!this.isPlaceholder) {
234 inputEl.setAttribute('displaymode', 'edit');
235 inputEl.staticVersion = textEl;
236 } else {
237 // At this point |this| is not attached to the parent list yet, so give
238 // a short timeout in order for the attachment to occur.
239 var self = this;
240 window.setTimeout(function() {
241 var list = self.parentNode;
242 if (list && list.focusPlaceholder) {
243 list.focusPlaceholder = false;
244 if (list.shouldFocusPlaceholder())
245 inputEl.focus();
246 }
247 }, 50);
248 }
249
250 inputEl.addEventListener('focus', this.handleFocus_.bind(this));
251 container.appendChild(inputEl);
252 this.editFields_.push(inputEl);
253
254 return container;
255 },
256
257 /**
258 * Resets the editable version of any controls created by createEditable*
259 * to match the static text.
260 * @private
261 */
262 resetEditableValues_: function() {
263 var editFields = this.editFields_;
264 for (var i = 0; i < editFields.length; i++) {
265 var staticLabel = editFields[i].staticVersion;
266 if (!staticLabel && !this.isPlaceholder)
267 continue;
268
269 if (editFields[i].tagName == 'INPUT') {
270 editFields[i].value =
271 this.isPlaceholder ? '' : staticLabel.textContent;
272 }
273 // Add more tag types here as new createEditable* methods are added.
274
275 editFields[i].setCustomValidity('');
276 }
277 },
278
279 /**
280 * Sets the static version of any controls created by createEditable*
281 * to match the current value of the editable version. Called on commit so
282 * that there's no flicker of the old value before the model updates.
283 * @private
284 */
285 updateStaticValues_: function() {
286 var editFields = this.editFields_;
287 for (var i = 0; i < editFields.length; i++) {
288 var staticLabel = editFields[i].staticVersion;
289 if (!staticLabel)
290 continue;
291
292 if (editFields[i].tagName == 'INPUT')
293 staticLabel.textContent = editFields[i].value;
294 // Add more tag types here as new createEditable* methods are added.
295 }
296 },
297
298 /**
299 * Called when a key is pressed. Handles committing and canceling edits.
300 * @param {Event} e The key down event.
301 * @private
302 */
303 handleKeyDown_: function(e) {
304 if (!this.editing)
305 return;
306
307 var endEdit = false;
308 switch (e.keyIdentifier) {
309 case 'U+001B': // Esc
310 this.editCancelled_ = true;
311 endEdit = true;
312 break;
313 case 'Enter':
314 if (this.currentInputIsValid)
315 endEdit = true;
316 break;
317 }
318
319 if (endEdit) {
320 // Blurring will trigger the edit to end; see InlineEditableItemList.
321 this.ownerDocument.activeElement.blur();
322 // Make sure that handled keys aren't passed on and double-handled.
323 // (e.g., esc shouldn't both cancel an edit and close a subpage)
324 e.stopPropagation();
325 }
326 },
327
328 /**
329 * Called when the list item is clicked. If the click target corresponds to
330 * an editable item, stores that item to focus when edit mode is started.
331 * @param {Event} e The mouse down event.
332 * @private
333 */
334 handleMouseDown_: function(e) {
335 if (!this.editable || this.editing)
336 return;
337
338 var clickTarget = e.target;
339 var editFields = this.editFields_;
340 for (var i = 0; i < editFields.length; i++) {
341 if (editFields[i] == clickTarget ||
342 editFields[i].staticVersion == clickTarget) {
343 this.editClickTarget_ = editFields[i];
344 return;
345 }
346 }
347 },
348 };
349
350 /**
351 * Takes care of committing changes to inline editable list items when the
352 * window loses focus.
353 */
354 function handleWindowBlurs() {
355 window.addEventListener('blur', function(e) {
356 var itemAncestor = findAncestor(document.activeElement, function(node) {
357 return node instanceof InlineEditableItem;
358 });
359 if (itemAncestor)
360 document.activeElement.blur();
361 });
362 }
363 handleWindowBlurs();
364
365 var InlineEditableItemList = cr.ui.define('list');
366
367 InlineEditableItemList.prototype = {
368 __proto__: DeletableItemList.prototype,
369
370 /**
371 * Focuses the input element of the placeholder if true.
372 * @type {boolean}
373 */
374 focusPlaceholder: false,
375
376 /** @inheritDoc */
377 decorate: function() {
378 DeletableItemList.prototype.decorate.call(this);
379 this.setAttribute('inlineeditable', '');
380 this.addEventListener('hasElementFocusChange',
381 this.handleListFocusChange_);
382 },
383
384 /**
385 * Called when the list hierarchy as a whole loses or gains focus; starts
386 * or ends editing for the lead item if necessary.
387 * @param {Event} e The change event.
388 * @private
389 */
390 handleListFocusChange_: function(e) {
391 var leadItem = this.getListItemByIndex(this.selectionModel.leadIndex);
392 if (leadItem) {
393 if (e.newValue)
394 leadItem.updateEditState();
395 else
396 leadItem.editing = false;
397 }
398 },
399
400 /**
401 * May be overridden by subclasses to disable focusing the placeholder.
402 * @return {boolean} True if the placeholder element should be focused on
403 * edit commit.
404 */
405 shouldFocusPlaceholder: function() {
406 return true;
407 },
408 };
409
410 // Export
411 return {
412 InlineEditableItem: InlineEditableItem,
413 InlineEditableItemList: InlineEditableItemList,
414 };
415 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698