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
;
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.
25 // Don't focus on the button when clicking it.
33 * Creates a new address list item.
34 * @param {Array} entry An array of the form [guid, label].
36 * @extends {options.DeletableItem}
38 function AddressListItem(entry
) {
39 var el
= cr
.doc
.createElement('div');
42 el
.__proto__
= AddressListItem
.prototype;
48 AddressListItem
.prototype = {
49 __proto__
: DeletableItem
.prototype,
52 decorate: function() {
53 DeletableItem
.prototype.decorate
.call(this);
56 var label
= this.ownerDocument
.createElement('div');
57 label
.className
= 'autofill-list-item';
58 label
.textContent
= this.label
;
59 this.contentElement
.appendChild(label
);
62 var editButtonEl
= AutofillEditProfileButton(
64 AutofillOptions
.loadAddressEditor
);
65 this.contentElement
.appendChild(editButtonEl
);
70 * Creates a new credit card list item.
71 * @param {Array} entry An array of the form [guid, label, icon].
73 * @extends {options.DeletableItem}
75 function CreditCardListItem(entry
) {
76 var el
= cr
.doc
.createElement('div');
80 el
.description
= entry
[3];
81 el
.__proto__
= CreditCardListItem
.prototype;
87 CreditCardListItem
.prototype = {
88 __proto__
: DeletableItem
.prototype,
91 decorate: function() {
92 DeletableItem
.prototype.decorate
.call(this);
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(
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
118 * @param {string} entry A string value.
120 * @extends {options.InlineEditableItem}
122 function ValuesListItem(list
, entry
) {
123 var el
= cr
.doc
.createElement('div');
125 el
.value
= entry
? entry
: '';
126 el
.__proto__
= ValuesListItem
.prototype;
132 ValuesListItem
.prototype = {
133 __proto__
: InlineEditableItem
.prototype,
136 decorate: function() {
137 InlineEditableItem
.prototype.decorate
.call(this);
139 // Note: This must be set prior to calling |createEditableTextCell|.
140 this.isPlaceholder
= !this.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.
160 return this.input
.value
;
164 * @param {*} value The value to test.
165 * @return {boolean} True if the given value is non-empty.
168 valueIsNonEmpty_: function(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.
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
192 * @param {Event} e The end event.
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
))) {
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
);
213 this.list
.validateAndSave(i
, 1, value
);
216 // Reject empty values and duplicates.
217 if (!this.isPlaceholder
)
218 this.list
.dataModel
.splice(i
, 1);
226 * Creates a new name value list item.
227 * @param {options.autofillOptions.AutofillNameValuesList} list The parent
229 * @param {Array.<string>} entry An array of [first, middle, last] names.
231 * @extends {options.autofillOptions.ValuesListItem}
233 function NameListItem(list
, entry
) {
234 var el
= cr
.doc
.createElement('div');
236 el
.first
= entry
? entry
[0] : '';
237 el
.middle
= entry
? entry
[1] : '';
238 el
.last
= entry
? entry
[2] : '';
239 el
.__proto__
= NameListItem
.prototype;
245 NameListItem
.prototype = {
246 __proto__
: ValuesListItem
.prototype,
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
;
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_
);
286 return [this.firstNameInput
.value
,
287 this.middleNameInput
.value
,
288 this.lastNameInput
.value
];
292 valueIsNonEmpty_: function(value
) {
293 return value
[0] || value
[1] || value
[2];
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];
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.
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.
335 onBlur_: function() {
336 this.selectionModel
.unselectAll();
341 * Create a new address list.
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);
355 activateItemAtIndex: function(index
) {
356 AutofillOptions
.loadAddressEditor(this.dataModel
.item(index
)[0]);
361 * @param {Array} entry
363 createItem: function(entry
) {
364 return new AddressListItem(entry
);
368 deleteItemAtIndex: function(index
) {
369 AutofillOptions
.removeData(this.dataModel
.item(index
)[0]);
374 * Create a new credit card list.
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);
388 activateItemAtIndex: function(index
) {
389 AutofillOptions
.loadCreditCardEditor(this.dataModel
.item(index
)[0]);
394 * @param {Array} entry
396 createItem: function(entry
) {
397 return new CreditCardListItem(entry
);
401 deleteItemAtIndex: function(index
) {
402 AutofillOptions
.removeData(this.dataModel
.item(index
)[0]);
407 * Create a new value list.
409 * @extends {options.InlineEditableItemList}
411 var AutofillValuesList
= cr
.ui
.define('list');
413 AutofillValuesList
.prototype = {
414 __proto__
: InlineEditableItemList
.prototype,
418 * @param {string} entry
420 createItem: function(entry
) {
421 return new ValuesListItem(this, entry
);
425 deleteItemAtIndex: function(index
) {
426 this.dataModel
.splice(index
, 1);
430 shouldFocusPlaceholder: function() {
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.
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
;
448 InlineEditableItemList
.prototype.handleListFocusChange_
.call(this, e
);
451 // When the list loses focus, unselect all the elements.
452 this.selectionModel
.unselectAll();
454 // When the list gains focus, select the first item if nothing else is
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.
477 * @extends {options.autofillOptions.AutofillValuesList}
479 var AutofillNameValuesList
= cr
.ui
.define('list');
481 AutofillNameValuesList
.prototype = {
482 __proto__
: AutofillValuesList
.prototype,
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.
496 * @extends {options.autofillOptions.AutofillValuesList}
498 var AutofillPhoneValuesList
= cr
.ui
.define('list');
500 AutofillPhoneValuesList
.prototype = {
501 __proto__
: AutofillValuesList
.prototype,
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();
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.
521 validationRequests_
: 0,
524 * Pending Promise resolver functions.
525 * @type {Array.<!Function>}
528 validationPromiseResolvers_
: [],
531 * This should be called when a reply of chrome.send('validatePhoneNumbers')
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
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
);
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
,