Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / options / autofill_options_list.js
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.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;
10
11   /**
12    * @return {!HTMLButtonElement}
13    */
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); };
21
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();
27     };
28
29     return editButtonEl;
30   }
31
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}
37    */
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();
44
45     return el;
46   }
47
48   AddressListItem.prototype = {
49     __proto__: DeletableItem.prototype,
50
51     /** @override */
52     decorate: function() {
53       DeletableItem.prototype.decorate.call(this);
54
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);
60
61       // The 'Edit' button.
62       var editButtonEl = AutofillEditProfileButton(
63         this.guid,
64         AutofillOptions.loadAddressEditor);
65       this.contentElement.appendChild(editButtonEl);
66     },
67   };
68
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}
74    */
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();
83
84     return el;
85   }
86
87   CreditCardListItem.prototype = {
88     __proto__: DeletableItem.prototype,
89
90     /** @override */
91     decorate: function() {
92       DeletableItem.prototype.decorate.call(this);
93
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);
99
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);
105
106       // The 'Edit' button.
107       var editButtonEl = AutofillEditProfileButton(
108         this.guid,
109         AutofillOptions.loadCreditCardEditor);
110       this.contentElement.appendChild(editButtonEl);
111     },
112   };
113
114   /**
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}
121    */
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();
128
129     return el;
130   }
131
132   ValuesListItem.prototype = {
133     __proto__: InlineEditableItem.prototype,
134
135     /** @override */
136     decorate: function() {
137       InlineEditableItem.prototype.decorate.call(this);
138
139       // Note: This must be set prior to calling |createEditableTextCell|.
140       this.isPlaceholder = !this.value;
141
142       // The stored value.
143       var cell = this.createEditableTextCell(String(this.value));
144       this.contentElement.appendChild(cell);
145       this.input = cell.querySelector('input');
146
147       if (this.isPlaceholder) {
148         this.input.placeholder = this.list.getAttribute('placeholder');
149         this.deletable = false;
150       }
151
152       this.addEventListener('commitedit', this.onEditCommitted_);
153     },
154
155     /**
156      * @return {Array} This item's value.
157      * @protected
158      */
159     value_: function() {
160       return this.input.value;
161     },
162
163     /**
164      * @param {*} value The value to test.
165      * @return {boolean} True if the given value is non-empty.
166      * @protected
167      */
168     valueIsNonEmpty_: function(value) {
169       return !!value;
170     },
171
172     /**
173      * @return {boolean} True if value1 is logically equal to value2.
174      */
175     valuesAreEqual_: function(value1, value2) {
176       return value1 === value2;
177     },
178
179     /**
180      * Clears the item's value.
181      * @protected
182      */
183     clearValue_: function() {
184       this.input.value = '';
185     },
186
187     /**
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
194      */
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;
201       }
202
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);
214         }
215       } else {
216         // Reject empty values and duplicates.
217         if (!this.isPlaceholder)
218           this.list.dataModel.splice(i, 1);
219         else
220           this.clearValue_();
221       }
222     },
223   };
224
225   /**
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}
232    */
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();
241
242     return el;
243   }
244
245   NameListItem.prototype = {
246     __proto__: ValuesListItem.prototype,
247
248     /** @override */
249     decorate: function() {
250       InlineEditableItem.prototype.decorate.call(this);
251
252       // Note: This must be set prior to calling |createEditableTextCell|.
253       this.isPlaceholder = !this.first && !this.middle && !this.last;
254
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');
262
263       cell = this.createEditableTextCell(this.middle);
264       this.contentElement.appendChild(cell);
265       this.middleNameInput = cell.querySelector('input');
266
267       cell = this.createEditableTextCell(this.last);
268       this.contentElement.appendChild(cell);
269       this.lastNameInput = cell.querySelector('input');
270
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;
279       }
280
281       this.addEventListener('commitedit', this.onEditCommitted_);
282     },
283
284     /** @override */
285     value_: function() {
286       return [this.firstNameInput.value,
287               this.middleNameInput.value,
288               this.lastNameInput.value];
289     },
290
291     /** @override */
292     valueIsNonEmpty_: function(value) {
293       return value[0] || value[1] || value[2];
294     },
295
296     /** @override */
297     valuesAreEqual_: function(value1, value2) {
298       // First, check for null values.
299       if (!value1 || !value2)
300         return value1 == value2;
301
302       return value1[0] === value2[0] &&
303              value1[1] === value2[1] &&
304              value1[2] === value2[2];
305     },
306
307     /** @override */
308     clearValue_: function() {
309       this.firstNameInput.value = '';
310       this.middleNameInput.value = '';
311       this.lastNameInput.value = '';
312     },
313   };
314
315   /**
316    * Base class for shared implementation between address and credit card lists.
317    * @constructor
318    * @extends {options.DeletableItemList}
319    */
320   var AutofillProfileList = cr.ui.define('list');
321
322   AutofillProfileList.prototype = {
323     __proto__: DeletableItemList.prototype,
324
325     decorate: function() {
326       DeletableItemList.prototype.decorate.call(this);
327
328       this.addEventListener('blur', this.onBlur_);
329     },
330
331     /**
332      * When the list loses focus, unselect all items in the list.
333      * @private
334      */
335     onBlur_: function() {
336       this.selectionModel.unselectAll();
337     },
338   };
339
340   /**
341    * Create a new address list.
342    * @constructor
343    * @extends {options.autofillOptions.AutofillProfileList}
344    */
345   var AutofillAddressList = cr.ui.define('list');
346
347   AutofillAddressList.prototype = {
348     __proto__: AutofillProfileList.prototype,
349
350     decorate: function() {
351       AutofillProfileList.prototype.decorate.call(this);
352     },
353
354     /** @override */
355     activateItemAtIndex: function(index) {
356       AutofillOptions.loadAddressEditor(this.dataModel.item(index)[0]);
357     },
358
359     /**
360      * @override
361      * @param {Array} entry
362      */
363     createItem: function(entry) {
364       return new AddressListItem(entry);
365     },
366
367     /** @override */
368     deleteItemAtIndex: function(index) {
369       AutofillOptions.removeData(this.dataModel.item(index)[0]);
370     },
371   };
372
373   /**
374    * Create a new credit card list.
375    * @constructor
376    * @extends {options.DeletableItemList}
377    */
378   var AutofillCreditCardList = cr.ui.define('list');
379
380   AutofillCreditCardList.prototype = {
381     __proto__: AutofillProfileList.prototype,
382
383     decorate: function() {
384       AutofillProfileList.prototype.decorate.call(this);
385     },
386
387     /** @override */
388     activateItemAtIndex: function(index) {
389       AutofillOptions.loadCreditCardEditor(this.dataModel.item(index)[0]);
390     },
391
392     /**
393      * @override
394      * @param {Array} entry
395      */
396     createItem: function(entry) {
397       return new CreditCardListItem(entry);
398     },
399
400     /** @override */
401     deleteItemAtIndex: function(index) {
402       AutofillOptions.removeData(this.dataModel.item(index)[0]);
403     },
404   };
405
406   /**
407    * Create a new value list.
408    * @constructor
409    * @extends {options.InlineEditableItemList}
410    */
411   var AutofillValuesList = cr.ui.define('list');
412
413   AutofillValuesList.prototype = {
414     __proto__: InlineEditableItemList.prototype,
415
416     /**
417      * @override
418      * @param {string} entry
419      */
420     createItem: function(entry) {
421       return new ValuesListItem(this, entry);
422     },
423
424     /** @override */
425     deleteItemAtIndex: function(index) {
426       this.dataModel.splice(index, 1);
427     },
428
429     /** @override */
430     shouldFocusPlaceholder: function() {
431       return false;
432     },
433
434     /**
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
442      */
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);
449
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_();
459       }
460     },
461
462     /**
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.
468      */
469     validateAndSave: function(index, remove, value) {
470       this.dataModel.splice(index, remove, value);
471     },
472   };
473
474   /**
475    * Create a new value list for phone number validation.
476    * @constructor
477    * @extends {options.autofillOptions.AutofillValuesList}
478    */
479   var AutofillNameValuesList = cr.ui.define('list');
480
481   AutofillNameValuesList.prototype = {
482     __proto__: AutofillValuesList.prototype,
483
484     /**
485      * @override
486      * @param {Array.<string>} entry
487      */
488     createItem: function(entry) {
489       return new NameListItem(this, entry);
490     },
491   };
492
493   /**
494    * Create a new value list for phone number validation.
495    * @constructor
496    * @extends {options.autofillOptions.AutofillValuesList}
497    */
498   var AutofillPhoneValuesList = cr.ui.define('list');
499
500   AutofillPhoneValuesList.prototype = {
501     __proto__: AutofillValuesList.prototype,
502
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);
514     },
515
516     /**
517      * The number of ongoing validation requests.
518      * @type {number}
519      * @private
520      */
521     validationRequests_: 0,
522
523     /**
524      * Pending Promise resolver functions.
525      * @type {Array.<!Function>}
526      * @private
527      */
528     validationPromiseResolvers_: [],
529
530     /**
531      * This should be called when a reply of chrome.send('validatePhoneNumbers')
532      * is received.
533      */
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()();
540         }
541       }
542     },
543
544     /**
545      * Returns a Promise which is fulfilled when all of validation requests are
546      * completed.
547      * @return {!Promise} A promise.
548      */
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));
555     }
556   };
557
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,
569   };
570 });