Move Webstore URL concepts to //extensions and out
[chromium-blink-merge.git] / chrome / browser / resources / options / autofill_options_list.js
blob565a62e6d353c7733f663f2c32fdc5792ab4b3e8
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.autofillOptions', function() {
6 /** @const */ var DeletableItem = options.DeletableItem;
7 /** @const */ var DeletableItemList = options.DeletableItemList;
8 /** @const */ var InlineEditableItem = options.InlineEditableItem;
9 /** @const */ var InlineEditableItemList = options.InlineEditableItemList;
11 /**
12 * @return {!HTMLButtonElement}
14 function AutofillEditProfileButton(guid, edit) {
15 var editButtonEl = /** @type {HTMLButtonElement} */(
16 document.createElement('button'));
17 editButtonEl.className = 'list-inline-button custom-appearance';
18 editButtonEl.textContent =
19 loadTimeData.getString('autofillEditProfileButton');
20 editButtonEl.onclick = function(e) { edit(guid); };
22 editButtonEl.onmousedown = function(e) {
23 // Don't select the row when clicking the button.
24 e.stopPropagation();
25 // Don't focus on the button when clicking it.
26 e.preventDefault();
29 return editButtonEl;
32 /**
33 * Creates a new address list item.
34 * @param {Array} entry An array of the form [guid, label].
35 * @constructor
36 * @extends {options.DeletableItem}
38 function AddressListItem(entry) {
39 var el = cr.doc.createElement('div');
40 el.guid = entry[0];
41 el.label = entry[1];
42 el.__proto__ = AddressListItem.prototype;
43 el.decorate();
45 return el;
48 AddressListItem.prototype = {
49 __proto__: DeletableItem.prototype,
51 /** @override */
52 decorate: function() {
53 DeletableItem.prototype.decorate.call(this);
55 // The stored label.
56 var label = this.ownerDocument.createElement('div');
57 label.className = 'autofill-list-item';
58 label.textContent = this.label;
59 this.contentElement.appendChild(label);
61 // The 'Edit' button.
62 var editButtonEl = AutofillEditProfileButton(
63 this.guid,
64 AutofillOptions.loadAddressEditor);
65 this.contentElement.appendChild(editButtonEl);
69 /**
70 * Creates a new credit card list item.
71 * @param {Array} entry An array of the form [guid, label, icon].
72 * @constructor
73 * @extends {options.DeletableItem}
75 function CreditCardListItem(entry) {
76 var el = cr.doc.createElement('div');
77 el.guid = entry[0];
78 el.label = entry[1];
79 el.icon = entry[2];
80 el.description = entry[3];
81 el.__proto__ = CreditCardListItem.prototype;
82 el.decorate();
84 return el;
87 CreditCardListItem.prototype = {
88 __proto__: DeletableItem.prototype,
90 /** @override */
91 decorate: function() {
92 DeletableItem.prototype.decorate.call(this);
94 // The stored label.
95 var label = this.ownerDocument.createElement('div');
96 label.className = 'autofill-list-item';
97 label.textContent = this.label;
98 this.contentElement.appendChild(label);
100 // The credit card icon.
101 var icon = this.ownerDocument.createElement('img');
102 icon.src = this.icon;
103 icon.alt = this.description;
104 this.contentElement.appendChild(icon);
106 // The 'Edit' button.
107 var editButtonEl = AutofillEditProfileButton(
108 this.guid,
109 AutofillOptions.loadCreditCardEditor);
110 this.contentElement.appendChild(editButtonEl);
115 * Creates a new value list item.
116 * @param {options.autofillOptions.AutofillValuesList} list The parent list of
117 * this item.
118 * @param {string} entry A string value.
119 * @constructor
120 * @extends {options.InlineEditableItem}
122 function ValuesListItem(list, entry) {
123 var el = cr.doc.createElement('div');
124 el.list = list;
125 el.value = entry ? entry : '';
126 el.__proto__ = ValuesListItem.prototype;
127 el.decorate();
129 return el;
132 ValuesListItem.prototype = {
133 __proto__: InlineEditableItem.prototype,
135 /** @override */
136 decorate: function() {
137 InlineEditableItem.prototype.decorate.call(this);
139 // Note: This must be set prior to calling |createEditableTextCell|.
140 this.isPlaceholder = !this.value;
142 // The stored value.
143 var cell = this.createEditableTextCell(String(this.value));
144 this.contentElement.appendChild(cell);
145 this.input = cell.querySelector('input');
147 if (this.isPlaceholder) {
148 this.input.placeholder = this.list.getAttribute('placeholder');
149 this.deletable = false;
152 this.addEventListener('commitedit', this.onEditCommitted_);
156 * @return {Array} This item's value.
157 * @protected
159 value_: function() {
160 return this.input.value;
164 * @param {*} value The value to test.
165 * @return {boolean} True if the given value is non-empty.
166 * @protected
168 valueIsNonEmpty_: function(value) {
169 return !!value;
173 * @return {boolean} True if value1 is logically equal to value2.
175 valuesAreEqual_: function(value1, value2) {
176 return value1 === value2;
180 * Clears the item's value.
181 * @protected
183 clearValue_: function() {
184 this.input.value = '';
188 * Called when committing an edit.
189 * If this is an "Add ..." item, committing a non-empty value adds that
190 * value to the end of the values list, but also leaves this "Add ..." item
191 * in place.
192 * @param {Event} e The end event.
193 * @private
195 onEditCommitted_: function(e) {
196 var value = this.value_();
197 var i = this.list.items.indexOf(this);
198 if (i < this.list.dataModel.length &&
199 this.valuesAreEqual_(value, this.list.dataModel.item(i))) {
200 return;
203 var entries = this.list.dataModel.slice();
204 if (this.valueIsNonEmpty_(value) &&
205 !entries.some(this.valuesAreEqual_.bind(this, value))) {
206 // Update with new value.
207 if (this.isPlaceholder) {
208 // It is important that updateIndex is done before validateAndSave.
209 // Otherwise we can not be sure about AddRow index.
210 this.list.dataModel.updateIndex(i);
211 this.list.validateAndSave(i, 0, value);
212 } else {
213 this.list.validateAndSave(i, 1, value);
215 } else {
216 // Reject empty values and duplicates.
217 if (!this.isPlaceholder)
218 this.list.dataModel.splice(i, 1);
219 else
220 this.clearValue_();
226 * Creates a new name value list item.
227 * @param {options.autofillOptions.AutofillNameValuesList} list The parent
228 * list of this item.
229 * @param {Array.<string>} entry An array of [first, middle, last] names.
230 * @constructor
231 * @extends {options.autofillOptions.ValuesListItem}
233 function NameListItem(list, entry) {
234 var el = cr.doc.createElement('div');
235 el.list = list;
236 el.first = entry ? entry[0] : '';
237 el.middle = entry ? entry[1] : '';
238 el.last = entry ? entry[2] : '';
239 el.__proto__ = NameListItem.prototype;
240 el.decorate();
242 return el;
245 NameListItem.prototype = {
246 __proto__: ValuesListItem.prototype,
248 /** @override */
249 decorate: function() {
250 InlineEditableItem.prototype.decorate.call(this);
252 // Note: This must be set prior to calling |createEditableTextCell|.
253 this.isPlaceholder = !this.first && !this.middle && !this.last;
255 // The stored value.
256 // For the simulated static "input element" to display correctly, the
257 // value must not be empty. We use a space to force the UI to render
258 // correctly when the value is logically empty.
259 var cell = this.createEditableTextCell(this.first);
260 this.contentElement.appendChild(cell);
261 this.firstNameInput = cell.querySelector('input');
263 cell = this.createEditableTextCell(this.middle);
264 this.contentElement.appendChild(cell);
265 this.middleNameInput = cell.querySelector('input');
267 cell = this.createEditableTextCell(this.last);
268 this.contentElement.appendChild(cell);
269 this.lastNameInput = cell.querySelector('input');
271 if (this.isPlaceholder) {
272 this.firstNameInput.placeholder =
273 loadTimeData.getString('autofillAddFirstNamePlaceholder');
274 this.middleNameInput.placeholder =
275 loadTimeData.getString('autofillAddMiddleNamePlaceholder');
276 this.lastNameInput.placeholder =
277 loadTimeData.getString('autofillAddLastNamePlaceholder');
278 this.deletable = false;
281 this.addEventListener('commitedit', this.onEditCommitted_);
284 /** @override */
285 value_: function() {
286 return [this.firstNameInput.value,
287 this.middleNameInput.value,
288 this.lastNameInput.value];
291 /** @override */
292 valueIsNonEmpty_: function(value) {
293 return value[0] || value[1] || value[2];
296 /** @override */
297 valuesAreEqual_: function(value1, value2) {
298 // First, check for null values.
299 if (!value1 || !value2)
300 return value1 == value2;
302 return value1[0] === value2[0] &&
303 value1[1] === value2[1] &&
304 value1[2] === value2[2];
307 /** @override */
308 clearValue_: function() {
309 this.firstNameInput.value = '';
310 this.middleNameInput.value = '';
311 this.lastNameInput.value = '';
316 * Base class for shared implementation between address and credit card lists.
317 * @constructor
318 * @extends {options.DeletableItemList}
320 var AutofillProfileList = cr.ui.define('list');
322 AutofillProfileList.prototype = {
323 __proto__: DeletableItemList.prototype,
325 decorate: function() {
326 DeletableItemList.prototype.decorate.call(this);
328 this.addEventListener('blur', this.onBlur_);
332 * When the list loses focus, unselect all items in the list.
333 * @private
335 onBlur_: function() {
336 this.selectionModel.unselectAll();
341 * Create a new address list.
342 * @constructor
343 * @extends {options.autofillOptions.AutofillProfileList}
345 var AutofillAddressList = cr.ui.define('list');
347 AutofillAddressList.prototype = {
348 __proto__: AutofillProfileList.prototype,
350 decorate: function() {
351 AutofillProfileList.prototype.decorate.call(this);
354 /** @override */
355 activateItemAtIndex: function(index) {
356 AutofillOptions.loadAddressEditor(this.dataModel.item(index)[0]);
360 * @override
361 * @param {Array} entry
363 createItem: function(entry) {
364 return new AddressListItem(entry);
367 /** @override */
368 deleteItemAtIndex: function(index) {
369 AutofillOptions.removeData(this.dataModel.item(index)[0]);
374 * Create a new credit card list.
375 * @constructor
376 * @extends {options.DeletableItemList}
378 var AutofillCreditCardList = cr.ui.define('list');
380 AutofillCreditCardList.prototype = {
381 __proto__: AutofillProfileList.prototype,
383 decorate: function() {
384 AutofillProfileList.prototype.decorate.call(this);
387 /** @override */
388 activateItemAtIndex: function(index) {
389 AutofillOptions.loadCreditCardEditor(this.dataModel.item(index)[0]);
393 * @override
394 * @param {Array} entry
396 createItem: function(entry) {
397 return new CreditCardListItem(entry);
400 /** @override */
401 deleteItemAtIndex: function(index) {
402 AutofillOptions.removeData(this.dataModel.item(index)[0]);
407 * Create a new value list.
408 * @constructor
409 * @extends {options.InlineEditableItemList}
411 var AutofillValuesList = cr.ui.define('list');
413 AutofillValuesList.prototype = {
414 __proto__: InlineEditableItemList.prototype,
417 * @override
418 * @param {string} entry
420 createItem: function(entry) {
421 return new ValuesListItem(this, entry);
424 /** @override */
425 deleteItemAtIndex: function(index) {
426 this.dataModel.splice(index, 1);
429 /** @override */
430 shouldFocusPlaceholder: function() {
431 return false;
435 * Called when the list hierarchy as a whole loses or gains focus.
436 * If the list was focused in response to a mouse click, call into the
437 * superclass's implementation. If the list was focused in response to a
438 * keyboard navigation, focus the first item.
439 * If the list loses focus, unselect all the elements.
440 * @param {Event} e The change event.
441 * @private
443 handleListFocusChange_: function(e) {
444 // We check to see whether there is a selected item as a proxy for
445 // distinguishing between mouse- and keyboard-originated focus events.
446 var selectedItem = this.selectedItem;
447 if (selectedItem)
448 InlineEditableItemList.prototype.handleListFocusChange_.call(this, e);
450 if (!e.newValue) {
451 // When the list loses focus, unselect all the elements.
452 this.selectionModel.unselectAll();
453 } else {
454 // When the list gains focus, select the first item if nothing else is
455 // selected.
456 var firstItem = this.getListItemByIndex(0);
457 if (!selectedItem && firstItem && e.newValue)
458 firstItem.handleFocus_();
463 * Called when a new list item should be validated; subclasses are
464 * responsible for implementing if validation is required.
465 * @param {number} index The index of the item that was inserted or changed.
466 * @param {number} remove The number items to remove.
467 * @param {string} value The value of the item to insert.
469 validateAndSave: function(index, remove, value) {
470 this.dataModel.splice(index, remove, value);
475 * Create a new value list for phone number validation.
476 * @constructor
477 * @extends {options.autofillOptions.AutofillValuesList}
479 var AutofillNameValuesList = cr.ui.define('list');
481 AutofillNameValuesList.prototype = {
482 __proto__: AutofillValuesList.prototype,
485 * @override
486 * @param {Array.<string>} entry
488 createItem: function(entry) {
489 return new NameListItem(this, entry);
494 * Create a new value list for phone number validation.
495 * @constructor
496 * @extends {options.autofillOptions.AutofillValuesList}
498 var AutofillPhoneValuesList = cr.ui.define('list');
500 AutofillPhoneValuesList.prototype = {
501 __proto__: AutofillValuesList.prototype,
503 /** @override */
504 validateAndSave: function(index, remove, value) {
505 var numbers = this.dataModel.slice(0, this.dataModel.length - 1);
506 numbers.splice(index, remove, value);
507 var info = new Array();
508 info[0] = index;
509 info[1] = numbers;
510 info[2] = document.querySelector(
511 '#autofill-edit-address-overlay [field=country]').value;
512 this.validationRequests_++;
513 chrome.send('validatePhoneNumbers', info);
517 * The number of ongoing validation requests.
518 * @type {number}
519 * @private
521 validationRequests_: 0,
524 * Pending Promise resolver functions.
525 * @type {Array.<!Function>}
526 * @private
528 validationPromiseResolvers_: [],
531 * This should be called when a reply of chrome.send('validatePhoneNumbers')
532 * is received.
534 didReceiveValidationResult: function() {
535 this.validationRequests_--;
536 assert(this.validationRequests_ >= 0);
537 if (this.validationRequests_ <= 0) {
538 while (this.validationPromiseResolvers_.length) {
539 this.validationPromiseResolvers_.pop()();
545 * Returns a Promise which is fulfilled when all of validation requests are
546 * completed.
547 * @return {!Promise} A promise.
549 doneValidating: function() {
550 if (this.validationRequests_ <= 0)
551 return Promise.resolve();
552 return new Promise(function(resolve) {
553 this.validationPromiseResolvers_.push(resolve);
554 }.bind(this));
558 return {
559 AutofillProfileList: AutofillProfileList,
560 AddressListItem: AddressListItem,
561 CreditCardListItem: CreditCardListItem,
562 ValuesListItem: ValuesListItem,
563 NameListItem: NameListItem,
564 AutofillAddressList: AutofillAddressList,
565 AutofillCreditCardList: AutofillCreditCardList,
566 AutofillValuesList: AutofillValuesList,
567 AutofillNameValuesList: AutofillNameValuesList,
568 AutofillPhoneValuesList: AutofillPhoneValuesList,