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
,