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.
13 * @see chrome/browser/ui/webui/options/autofill_options_handler.cc
15 var AutofillEntityMetadata;
17 cr.define('options.autofillOptions', function() {
18 /** @const */ var DeletableItem = options.DeletableItem;
19 /** @const */ var DeletableItemList = options.DeletableItemList;
20 /** @const */ var InlineEditableItem = options.InlineEditableItem;
21 /** @const */ var InlineEditableItemList = options.InlineEditableItemList;
24 * @return {!HTMLButtonElement}
26 function AutofillEditProfileButton(edit) {
27 var editButtonEl = /** @type {HTMLButtonElement} */(
28 document.createElement('button'));
29 editButtonEl.className =
30 'list-inline-button hide-until-hover custom-appearance';
31 editButtonEl.textContent =
32 loadTimeData.getString('autofillEditProfileButton');
33 editButtonEl.onclick = edit;
35 editButtonEl.onmousedown = function(e) {
36 // Don't select the row when clicking the button.
38 // Don't focus on the button when clicking it.
45 /** @return {!Element} */
46 function CreateGoogleAccountLabel() {
47 var label = document.createElement('div');
48 label.className = 'deemphasized hides-on-hover';
49 label.textContent = loadTimeData.getString('autofillFromGoogleAccount');
54 * Creates a new address list item.
56 * @param {AutofillEntityMetadata} metadata Details about an address profile.
57 * @extends {options.DeletableItem}
58 * @see chrome/browser/ui/webui/options/autofill_options_handler.cc
60 function AddressListItem(metadata) {
61 var el = cr.doc.createElement('div');
62 el.__proto__ = AddressListItem.prototype;
64 el.metadata_ = metadata;
70 AddressListItem.prototype = {
71 __proto__: DeletableItem.prototype,
74 decorate: function() {
75 DeletableItem.prototype.decorate.call(this);
77 var label = this.ownerDocument.createElement('div');
78 label.className = 'autofill-list-item';
79 label.textContent = this.metadata_.label;
80 this.contentElement.appendChild(label);
82 var sublabel = this.ownerDocument.createElement('div');
83 sublabel.className = 'deemphasized';
84 sublabel.textContent = this.metadata_.sublabel;
85 this.contentElement.appendChild(sublabel);
87 if (!this.metadata_.isLocal) {
88 this.deletable = false;
89 this.contentElement.appendChild(CreateGoogleAccountLabel());
93 var metadata = this.metadata_;
94 var editButtonEl = AutofillEditProfileButton(
95 AddressListItem.prototype.loadAddressEditor.bind(this));
96 this.contentElement.appendChild(editButtonEl);
100 * For local Autofill data, this function causes the AutofillOptionsHandler
101 * to call showEditAddressOverlay(). For Wallet data, the user is
102 * redirected to the Wallet web interface.
104 loadAddressEditor: function() {
105 if (this.metadata_.isLocal)
106 chrome.send('loadAddressEditor', [this.metadata_.guid]);
108 window.open(loadTimeData.getString('manageWalletAddressesUrl'));
113 * Creates a new credit card list item.
114 * @param {AutofillEntityMetadata} metadata Details about a credit card.
116 * @extends {options.DeletableItem}
118 function CreditCardListItem(metadata) {
119 var el = cr.doc.createElement('div');
120 el.__proto__ = CreditCardListItem.prototype;
122 el.metadata_ = metadata;
128 CreditCardListItem.prototype = {
129 __proto__: DeletableItem.prototype,
132 decorate: function() {
133 DeletableItem.prototype.decorate.call(this);
135 var label = this.ownerDocument.createElement('div');
136 label.className = 'autofill-list-item';
137 label.textContent = this.metadata_.label;
138 this.contentElement.appendChild(label);
140 var sublabel = this.ownerDocument.createElement('div');
141 sublabel.className = 'deemphasized';
142 sublabel.textContent = this.metadata_.sublabel;
143 this.contentElement.appendChild(sublabel);
145 if (!this.metadata_.isLocal) {
146 this.deletable = false;
147 this.contentElement.appendChild(CreateGoogleAccountLabel());
150 var guid = this.metadata_.guid;
151 if (this.metadata_.isCached) {
152 var localCopyText = this.ownerDocument.createElement('span');
153 localCopyText.className = 'hide-until-hover deemphasized';
154 localCopyText.textContent =
155 loadTimeData.getString('autofillDescribeLocalCopy');
156 this.contentElement.appendChild(localCopyText);
158 var clearLocalCopyButton = AutofillEditProfileButton(
159 function() { chrome.send('clearLocalCardCopy', [guid]); });
160 clearLocalCopyButton.textContent =
161 loadTimeData.getString('autofillClearLocalCopyButton');
162 this.contentElement.appendChild(clearLocalCopyButton);
165 // The 'Edit' button.
166 var metadata = this.metadata_;
167 var editButtonEl = AutofillEditProfileButton(
168 CreditCardListItem.prototype.loadCreditCardEditor.bind(this));
169 this.contentElement.appendChild(editButtonEl);
173 * For local Autofill data, this function causes the AutofillOptionsHandler
174 * to call showEditCreditCardOverlay(). For Wallet data, the user is
175 * redirected to the Wallet web interface.
177 loadCreditCardEditor: function() {
178 if (this.metadata_.isLocal)
179 chrome.send('loadCreditCardEditor', [this.metadata_.guid]);
181 window.open(loadTimeData.getString('manageWalletPaymentMethodsUrl'));
186 * Creates a new value list item.
187 * @param {options.autofillOptions.AutofillValuesList} list The parent list of
189 * @param {string} entry A string value.
191 * @extends {options.InlineEditableItem}
193 function ValuesListItem(list, entry) {
194 var el = cr.doc.createElement('div');
196 el.value = entry ? entry : '';
197 el.__proto__ = ValuesListItem.prototype;
203 ValuesListItem.prototype = {
204 __proto__: InlineEditableItem.prototype,
207 decorate: function() {
208 InlineEditableItem.prototype.decorate.call(this);
210 // Note: This must be set prior to calling |createEditableTextCell|.
211 this.isPlaceholder = !this.value;
214 var cell = this.createEditableTextCell(String(this.value));
215 this.contentElement.appendChild(cell);
216 this.input = cell.querySelector('input');
218 if (this.isPlaceholder) {
219 this.input.placeholder = this.list.getAttribute('placeholder');
220 this.deletable = false;
223 this.addEventListener('commitedit', this.onEditCommitted_);
224 this.closeButtonFocusAllowed = true;
225 this.setFocusableColumnIndex(this.input, 0);
226 this.setFocusableColumnIndex(this.closeButtonElement, 1);
230 * @return {Array} This item's value.
234 return this.input.value;
238 * @param {*} value The value to test.
239 * @return {boolean} True if the given value is non-empty.
242 valueIsNonEmpty_: function(value) {
247 * @return {boolean} True if value1 is logically equal to value2.
249 valuesAreEqual_: function(value1, value2) {
250 return value1 === value2;
254 * Clears the item's value.
257 clearValue_: function() {
258 this.input.value = '';
262 * Called when committing an edit.
263 * If this is an "Add ..." item, committing a non-empty value adds that
264 * value to the end of the values list, but also leaves this "Add ..." item
266 * @param {Event} e The end event.
269 onEditCommitted_: function(e) {
270 var value = this.value_();
271 var i = this.list.items.indexOf(this);
272 if (i < this.list.dataModel.length &&
273 this.valuesAreEqual_(value, this.list.dataModel.item(i))) {
277 var entries = this.list.dataModel.slice();
278 if (this.valueIsNonEmpty_(value) &&
279 !entries.some(this.valuesAreEqual_.bind(this, value))) {
280 // Update with new value.
281 if (this.isPlaceholder) {
282 // It is important that updateIndex is done before validateAndSave.
283 // Otherwise we can not be sure about AddRow index.
284 this.list.ignoreChangeEvents(function() {
285 this.list.dataModel.updateIndex(i);
287 this.list.validateAndSave(i, 0, value);
289 this.list.validateAndSave(i, 1, value);
292 // Reject empty values and duplicates.
293 if (!this.isPlaceholder) {
294 this.list.ignoreChangeEvents(function() {
295 this.list.dataModel.splice(i, 1);
297 this.list.selectIndexWithoutFocusing(i);
306 * Creates a new name value list item.
307 * @param {options.autofillOptions.AutofillNameValuesList} list The parent
309 * @param {Array<string>} entry An array of [first, middle, last] names.
311 * @extends {options.autofillOptions.ValuesListItem}
313 function NameListItem(list, entry) {
314 var el = cr.doc.createElement('div');
316 el.first = entry ? entry[0] : '';
317 el.middle = entry ? entry[1] : '';
318 el.last = entry ? entry[2] : '';
319 el.__proto__ = NameListItem.prototype;
325 NameListItem.prototype = {
326 __proto__: ValuesListItem.prototype,
329 decorate: function() {
330 InlineEditableItem.prototype.decorate.call(this);
332 // Note: This must be set prior to calling |createEditableTextCell|.
333 this.isPlaceholder = !this.first && !this.middle && !this.last;
336 // For the simulated static "input element" to display correctly, the
337 // value must not be empty. We use a space to force the UI to render
338 // correctly when the value is logically empty.
339 var cell = this.createEditableTextCell(this.first);
340 this.contentElement.appendChild(cell);
341 this.firstNameInput = cell.querySelector('input');
343 cell = this.createEditableTextCell(this.middle);
344 this.contentElement.appendChild(cell);
345 this.middleNameInput = cell.querySelector('input');
347 cell = this.createEditableTextCell(this.last);
348 this.contentElement.appendChild(cell);
349 this.lastNameInput = cell.querySelector('input');
351 if (this.isPlaceholder) {
352 this.firstNameInput.placeholder =
353 loadTimeData.getString('autofillAddFirstNamePlaceholder');
354 this.middleNameInput.placeholder =
355 loadTimeData.getString('autofillAddMiddleNamePlaceholder');
356 this.lastNameInput.placeholder =
357 loadTimeData.getString('autofillAddLastNamePlaceholder');
358 this.deletable = false;
361 this.addEventListener('commitedit', this.onEditCommitted_);
366 return [this.firstNameInput.value,
367 this.middleNameInput.value,
368 this.lastNameInput.value];
372 valueIsNonEmpty_: function(value) {
373 return value[0] || value[1] || value[2];
377 valuesAreEqual_: function(value1, value2) {
378 // First, check for null values.
379 if (!value1 || !value2)
380 return value1 == value2;
382 return value1[0] === value2[0] &&
383 value1[1] === value2[1] &&
384 value1[2] === value2[2];
388 clearValue_: function() {
389 this.firstNameInput.value = '';
390 this.middleNameInput.value = '';
391 this.lastNameInput.value = '';
396 * Base class for shared implementation between address and credit card lists.
398 * @extends {options.DeletableItemList}
400 var AutofillProfileList = cr.ui.define('list');
402 AutofillProfileList.prototype = {
403 __proto__: DeletableItemList.prototype,
405 decorate: function() {
406 DeletableItemList.prototype.decorate.call(this);
408 this.addEventListener('blur', this.onBlur_);
412 * When the list loses focus, unselect all items in the list.
415 onBlur_: function() {
416 this.selectionModel.unselectAll();
421 * Create a new address list.
423 * @extends {options.autofillOptions.AutofillProfileList}
425 var AutofillAddressList = cr.ui.define('list');
427 AutofillAddressList.prototype = {
428 __proto__: AutofillProfileList.prototype,
430 decorate: function() {
431 AutofillProfileList.prototype.decorate.call(this);
435 activateItemAtIndex: function(index) {
436 this.getListItemByIndex(index).loadAddressEditor();
441 * @param {AutofillEntityMetadata} metadata
443 createItem: function(metadata) {
444 return new AddressListItem(metadata);
448 deleteItemAtIndex: function(index) {
449 AutofillOptions.removeData(this.dataModel.item(index).guid,
450 'Options_AutofillAddressDeleted');
455 * Create a new credit card list.
457 * @extends {options.DeletableItemList}
459 var AutofillCreditCardList = cr.ui.define('list');
461 AutofillCreditCardList.prototype = {
462 __proto__: AutofillProfileList.prototype,
464 decorate: function() {
465 AutofillProfileList.prototype.decorate.call(this);
469 activateItemAtIndex: function(index) {
470 this.getListItemByIndex(index).loadCreditCardEditor();
475 * @param {AutofillEntityMetadata} metadata
477 createItem: function(metadata) {
478 return new CreditCardListItem(metadata);
482 deleteItemAtIndex: function(index) {
483 AutofillOptions.removeData(this.dataModel.item(index).guid,
484 'Options_AutofillCreditCardDeleted');
489 * Create a new value list.
491 * @extends {options.InlineEditableItemList}
493 var AutofillValuesList = cr.ui.define('list');
495 AutofillValuesList.prototype = {
496 __proto__: InlineEditableItemList.prototype,
500 * @param {string} entry
502 createItem: function(entry) {
503 return new ValuesListItem(this, entry);
507 deleteItemAtIndex: function(index) {
508 this.dataModel.splice(index, 1);
512 shouldFocusPlaceholderOnEditCommit: function() {
517 * Called when a new list item should be validated; subclasses are
518 * responsible for implementing if validation is required.
519 * @param {number} index The index of the item that was inserted or changed.
520 * @param {number} remove The number items to remove.
521 * @param {string} value The value of the item to insert.
523 validateAndSave: function(index, remove, value) {
524 this.ignoreChangeEvents(function() {
525 this.dataModel.splice(index, remove, value);
527 this.selectIndexWithoutFocusing(index);
532 * Create a new value list for phone number validation.
534 * @extends {options.autofillOptions.AutofillValuesList}
536 var AutofillNameValuesList = cr.ui.define('list');
538 AutofillNameValuesList.prototype = {
539 __proto__: AutofillValuesList.prototype,
543 * @param {Array<string>} entry
545 createItem: function(entry) {
546 return new NameListItem(this, entry);
551 * Create a new value list for phone number validation.
553 * @extends {options.autofillOptions.AutofillValuesList}
555 var AutofillPhoneValuesList = cr.ui.define('list');
557 AutofillPhoneValuesList.prototype = {
558 __proto__: AutofillValuesList.prototype,
561 validateAndSave: function(index, remove, value) {
562 var numbers = this.dataModel.slice(0, this.dataModel.length - 1);
563 numbers.splice(index, remove, value);
564 var info = new Array();
567 info[2] = document.querySelector(
568 '#autofill-edit-address-overlay [field=country]').value;
569 this.validationRequests_++;
570 chrome.send('validatePhoneNumbers', info);
574 * The number of ongoing validation requests.
578 validationRequests_: 0,
581 * Pending Promise resolver functions.
582 * @type {Array<!Function>}
585 validationPromiseResolvers_: [],
588 * This should be called when a reply of chrome.send('validatePhoneNumbers')
591 didReceiveValidationResult: function() {
592 this.validationRequests_--;
593 assert(this.validationRequests_ >= 0);
594 if (this.validationRequests_ <= 0) {
595 while (this.validationPromiseResolvers_.length) {
596 this.validationPromiseResolvers_.pop()();
602 * Returns a Promise which is fulfilled when all of validation requests are
604 * @return {!Promise} A promise.
606 doneValidating: function() {
607 if (this.validationRequests_ <= 0)
608 return Promise.resolve();
609 return new Promise(function(resolve) {
610 this.validationPromiseResolvers_.push(resolve);
616 AutofillProfileList: AutofillProfileList,
617 AddressListItem: AddressListItem,
618 CreditCardListItem: CreditCardListItem,
619 ValuesListItem: ValuesListItem,
620 NameListItem: NameListItem,
621 AutofillAddressList: AutofillAddressList,
622 AutofillCreditCardList: AutofillCreditCardList,
623 AutofillValuesList: AutofillValuesList,
624 AutofillNameValuesList: AutofillNameValuesList,
625 AutofillPhoneValuesList: AutofillPhoneValuesList,