Upstream version 7.35.139.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   function AutofillEditProfileButton(guid, edit) {
12     var editButtonEl = document.createElement('button');
13     editButtonEl.className = 'list-inline-button custom-appearance';
14     editButtonEl.textContent =
15         loadTimeData.getString('autofillEditProfileButton');
16     editButtonEl.onclick = function(e) { edit(guid); };
17
18     editButtonEl.onmousedown = function(e) {
19       // Don't select the row when clicking the button.
20       e.stopPropagation();
21       // Don't focus on the button when clicking it.
22       e.preventDefault();
23     };
24
25     return editButtonEl;
26   }
27
28   /**
29    * Creates a new address list item.
30    * @param {Array} entry An array of the form [guid, label].
31    * @constructor
32    * @extends {options.DeletableItem}
33    */
34   function AddressListItem(entry) {
35     var el = cr.doc.createElement('div');
36     el.guid = entry[0];
37     el.label = entry[1];
38     el.__proto__ = AddressListItem.prototype;
39     el.decorate();
40
41     return el;
42   }
43
44   AddressListItem.prototype = {
45     __proto__: DeletableItem.prototype,
46
47     /** @override */
48     decorate: function() {
49       DeletableItem.prototype.decorate.call(this);
50
51       // The stored label.
52       var label = this.ownerDocument.createElement('div');
53       label.className = 'autofill-list-item';
54       label.textContent = this.label;
55       this.contentElement.appendChild(label);
56
57       // The 'Edit' button.
58       var editButtonEl = new AutofillEditProfileButton(
59         this.guid,
60         AutofillOptions.loadAddressEditor);
61       this.contentElement.appendChild(editButtonEl);
62     },
63   };
64
65   /**
66    * Creates a new credit card list item.
67    * @param {Array} entry An array of the form [guid, label, icon].
68    * @constructor
69    * @extends {options.DeletableItem}
70    */
71   function CreditCardListItem(entry) {
72     var el = cr.doc.createElement('div');
73     el.guid = entry[0];
74     el.label = entry[1];
75     el.icon = entry[2];
76     el.description = entry[3];
77     el.__proto__ = CreditCardListItem.prototype;
78     el.decorate();
79
80     return el;
81   }
82
83   CreditCardListItem.prototype = {
84     __proto__: DeletableItem.prototype,
85
86     /** @override */
87     decorate: function() {
88       DeletableItem.prototype.decorate.call(this);
89
90       // The stored label.
91       var label = this.ownerDocument.createElement('div');
92       label.className = 'autofill-list-item';
93       label.textContent = this.label;
94       this.contentElement.appendChild(label);
95
96       // The credit card icon.
97       var icon = this.ownerDocument.createElement('img');
98       icon.src = this.icon;
99       icon.alt = this.description;
100       this.contentElement.appendChild(icon);
101
102       // The 'Edit' button.
103       var editButtonEl = new AutofillEditProfileButton(
104         this.guid,
105         AutofillOptions.loadCreditCardEditor);
106       this.contentElement.appendChild(editButtonEl);
107     },
108   };
109
110   /**
111    * Creates a new value list item.
112    * @param {AutofillValuesList} list The parent list of this item.
113    * @param {string} entry A string value.
114    * @constructor
115    * @extends {options.InlineEditableItem}
116    */
117   function ValuesListItem(list, entry) {
118     var el = cr.doc.createElement('div');
119     el.list = list;
120     el.value = entry ? entry : '';
121     el.__proto__ = ValuesListItem.prototype;
122     el.decorate();
123
124     return el;
125   }
126
127   ValuesListItem.prototype = {
128     __proto__: InlineEditableItem.prototype,
129
130     /** @override */
131     decorate: function() {
132       InlineEditableItem.prototype.decorate.call(this);
133
134       // Note: This must be set prior to calling |createEditableTextCell|.
135       this.isPlaceholder = !this.value;
136
137       // The stored value.
138       var cell = this.createEditableTextCell(this.value);
139       this.contentElement.appendChild(cell);
140       this.input = cell.querySelector('input');
141
142       if (this.isPlaceholder) {
143         this.input.placeholder = this.list.getAttribute('placeholder');
144         this.deletable = false;
145       }
146
147       this.addEventListener('commitedit', this.onEditCommitted_);
148     },
149
150     /**
151      * @return {string} This item's value.
152      * @protected
153      */
154     value_: function() {
155       return this.input.value;
156     },
157
158     /**
159      * @param {Object} value The value to test.
160      * @return {boolean} True if the given value is non-empty.
161      * @protected
162      */
163     valueIsNonEmpty_: function(value) {
164       return !!value;
165     },
166
167     /**
168      * @return {boolean} True if value1 is logically equal to value2.
169      */
170     valuesAreEqual_: function(value1, value2) {
171       return value1 === value2;
172     },
173
174     /**
175      * Clears the item's value.
176      * @protected
177      */
178     clearValue_: function() {
179       this.input.value = '';
180     },
181
182     /**
183      * Called when committing an edit.
184      * If this is an "Add ..." item, committing a non-empty value adds that
185      * value to the end of the values list, but also leaves this "Add ..." item
186      * in place.
187      * @param {Event} e The end event.
188      * @private
189      */
190     onEditCommitted_: function(e) {
191       var value = this.value_();
192       var i = this.list.items.indexOf(this);
193       if (i < this.list.dataModel.length &&
194           this.valuesAreEqual_(value, this.list.dataModel.item(i))) {
195         return;
196       }
197
198       var entries = this.list.dataModel.slice();
199       if (this.valueIsNonEmpty_(value) &&
200           !entries.some(this.valuesAreEqual_.bind(this, value))) {
201         // Update with new value.
202         if (this.isPlaceholder) {
203           // It is important that updateIndex is done before validateAndSave.
204           // Otherwise we can not be sure about AddRow index.
205           this.list.dataModel.updateIndex(i);
206           this.list.validateAndSave(i, 0, value);
207         } else {
208           this.list.validateAndSave(i, 1, value);
209         }
210       } else {
211         // Reject empty values and duplicates.
212         if (!this.isPlaceholder)
213           this.list.dataModel.splice(i, 1);
214         else
215           this.clearValue_();
216       }
217     },
218   };
219
220   /**
221    * Creates a new name value list item.
222    * @param {AutofillNameValuesList} list The parent list of this item.
223    * @param {array} entry An array of [first, middle, last] names.
224    * @constructor
225    * @extends {options.ValuesListItem}
226    */
227   function NameListItem(list, entry) {
228     var el = cr.doc.createElement('div');
229     el.list = list;
230     el.first = entry ? entry[0] : '';
231     el.middle = entry ? entry[1] : '';
232     el.last = entry ? entry[2] : '';
233     el.__proto__ = NameListItem.prototype;
234     el.decorate();
235
236     return el;
237   }
238
239   NameListItem.prototype = {
240     __proto__: ValuesListItem.prototype,
241
242     /** @override */
243     decorate: function() {
244       InlineEditableItem.prototype.decorate.call(this);
245
246       // Note: This must be set prior to calling |createEditableTextCell|.
247       this.isPlaceholder = !this.first && !this.middle && !this.last;
248
249       // The stored value.
250       // For the simulated static "input element" to display correctly, the
251       // value must not be empty.  We use a space to force the UI to render
252       // correctly when the value is logically empty.
253       var cell = this.createEditableTextCell(this.first);
254       this.contentElement.appendChild(cell);
255       this.firstNameInput = cell.querySelector('input');
256
257       cell = this.createEditableTextCell(this.middle);
258       this.contentElement.appendChild(cell);
259       this.middleNameInput = cell.querySelector('input');
260
261       cell = this.createEditableTextCell(this.last);
262       this.contentElement.appendChild(cell);
263       this.lastNameInput = cell.querySelector('input');
264
265       if (this.isPlaceholder) {
266         this.firstNameInput.placeholder =
267             loadTimeData.getString('autofillAddFirstNamePlaceholder');
268         this.middleNameInput.placeholder =
269             loadTimeData.getString('autofillAddMiddleNamePlaceholder');
270         this.lastNameInput.placeholder =
271             loadTimeData.getString('autofillAddLastNamePlaceholder');
272         this.deletable = false;
273       }
274
275       this.addEventListener('commitedit', this.onEditCommitted_);
276     },
277
278     /** @override */
279     value_: function() {
280       return [this.firstNameInput.value,
281               this.middleNameInput.value,
282               this.lastNameInput.value];
283     },
284
285     /** @override */
286     valueIsNonEmpty_: function(value) {
287       return value[0] || value[1] || value[2];
288     },
289
290     /** @override */
291     valuesAreEqual_: function(value1, value2) {
292       // First, check for null values.
293       if (!value1 || !value2)
294         return value1 == value2;
295
296       return value1[0] === value2[0] &&
297              value1[1] === value2[1] &&
298              value1[2] === value2[2];
299     },
300
301     /** @override */
302     clearValue_: function() {
303       this.firstNameInput.value = '';
304       this.middleNameInput.value = '';
305       this.lastNameInput.value = '';
306     },
307   };
308
309   /**
310    * Base class for shared implementation between address and credit card lists.
311    * @constructor
312    * @extends {options.DeletableItemList}
313    */
314   var AutofillProfileList = cr.ui.define('list');
315
316   AutofillProfileList.prototype = {
317     __proto__: DeletableItemList.prototype,
318
319     decorate: function() {
320       DeletableItemList.prototype.decorate.call(this);
321
322       this.addEventListener('blur', this.onBlur_);
323     },
324
325     /**
326      * When the list loses focus, unselect all items in the list.
327      * @private
328      */
329     onBlur_: function() {
330       this.selectionModel.unselectAll();
331     },
332   };
333
334   /**
335    * Create a new address list.
336    * @constructor
337    * @extends {options.AutofillProfileList}
338    */
339   var AutofillAddressList = cr.ui.define('list');
340
341   AutofillAddressList.prototype = {
342     __proto__: AutofillProfileList.prototype,
343
344     decorate: function() {
345       AutofillProfileList.prototype.decorate.call(this);
346     },
347
348     /** @override */
349     activateItemAtIndex: function(index) {
350       AutofillOptions.loadAddressEditor(this.dataModel.item(index)[0]);
351     },
352
353     /** @override */
354     createItem: function(entry) {
355       return new AddressListItem(entry);
356     },
357
358     /** @override */
359     deleteItemAtIndex: function(index) {
360       AutofillOptions.removeData(this.dataModel.item(index)[0]);
361     },
362   };
363
364   /**
365    * Create a new credit card list.
366    * @constructor
367    * @extends {options.DeletableItemList}
368    */
369   var AutofillCreditCardList = cr.ui.define('list');
370
371   AutofillCreditCardList.prototype = {
372     __proto__: AutofillProfileList.prototype,
373
374     decorate: function() {
375       AutofillProfileList.prototype.decorate.call(this);
376     },
377
378     /** @override */
379     activateItemAtIndex: function(index) {
380       AutofillOptions.loadCreditCardEditor(this.dataModel.item(index)[0]);
381     },
382
383     /** @override */
384     createItem: function(entry) {
385       return new CreditCardListItem(entry);
386     },
387
388     /** @override */
389     deleteItemAtIndex: function(index) {
390       AutofillOptions.removeData(this.dataModel.item(index)[0]);
391     },
392   };
393
394   /**
395    * Create a new value list.
396    * @constructor
397    * @extends {options.InlineEditableItemList}
398    */
399   var AutofillValuesList = cr.ui.define('list');
400
401   AutofillValuesList.prototype = {
402     __proto__: InlineEditableItemList.prototype,
403
404     /** @override */
405     createItem: function(entry) {
406       return new ValuesListItem(this, entry);
407     },
408
409     /** @override */
410     deleteItemAtIndex: function(index) {
411       this.dataModel.splice(index, 1);
412     },
413
414     /** @override */
415     shouldFocusPlaceholder: function() {
416       return false;
417     },
418
419     /**
420      * Called when the list hierarchy as a whole loses or gains focus.
421      * If the list was focused in response to a mouse click, call into the
422      * superclass's implementation.  If the list was focused in response to a
423      * keyboard navigation, focus the first item.
424      * If the list loses focus, unselect all the elements.
425      * @param {Event} e The change event.
426      * @private
427      */
428     handleListFocusChange_: function(e) {
429       // We check to see whether there is a selected item as a proxy for
430       // distinguishing between mouse- and keyboard-originated focus events.
431       var selectedItem = this.selectedItem;
432       if (selectedItem)
433         InlineEditableItemList.prototype.handleListFocusChange_.call(this, e);
434
435       if (!e.newValue) {
436         // When the list loses focus, unselect all the elements.
437         this.selectionModel.unselectAll();
438       } else {
439         // When the list gains focus, select the first item if nothing else is
440         // selected.
441         var firstItem = this.getListItemByIndex(0);
442         if (!selectedItem && firstItem && e.newValue)
443           firstItem.handleFocus_();
444       }
445     },
446
447     /**
448      * Called when a new list item should be validated; subclasses are
449      * responsible for implementing if validation is required.
450      * @param {number} index The index of the item that was inserted or changed.
451      * @param {number} remove The number items to remove.
452      * @param {string} value The value of the item to insert.
453      */
454     validateAndSave: function(index, remove, value) {
455       this.dataModel.splice(index, remove, value);
456     },
457   };
458
459   /**
460    * Create a new value list for phone number validation.
461    * @constructor
462    * @extends {options.AutofillValuesList}
463    */
464   var AutofillNameValuesList = cr.ui.define('list');
465
466   AutofillNameValuesList.prototype = {
467     __proto__: AutofillValuesList.prototype,
468
469     /** @override */
470     createItem: function(entry) {
471       return new NameListItem(this, entry);
472     },
473   };
474
475   /**
476    * Create a new value list for phone number validation.
477    * @constructor
478    * @extends {options.AutofillValuesList}
479    */
480   var AutofillPhoneValuesList = cr.ui.define('list');
481
482   AutofillPhoneValuesList.prototype = {
483     __proto__: AutofillValuesList.prototype,
484
485     /** @override */
486     validateAndSave: function(index, remove, value) {
487       var numbers = this.dataModel.slice(0, this.dataModel.length - 1);
488       numbers.splice(index, remove, value);
489       var info = new Array();
490       info[0] = index;
491       info[1] = numbers;
492       info[2] = $('country').value;
493       this.validationRequests_++;
494       chrome.send('validatePhoneNumbers', info);
495     },
496
497     /**
498      * The number of ongoing validation requests.
499      * @type {number}
500      * @private
501      */
502     validationRequests_: 0,
503
504     /**
505      * Pending Promise resolver functions.
506      * @type {Array.<!Function>}
507      * @private
508      */
509     validationPromiseResolvers_: [],
510
511     /**
512      * This should be called when a reply of chrome.send('validatePhoneNumbers')
513      * is received.
514      */
515     didReceiveValidationResult: function() {
516       this.validationRequests_--;
517       assert(this.validationRequests_ >= 0);
518       if (this.validationRequests_ <= 0) {
519         while (this.validationPromiseResolvers_.length) {
520           this.validationPromiseResolvers_.pop()();
521         }
522       }
523     },
524
525     /**
526      * Returns a Promise which is fulfilled when all of validation requests are
527      * completed.
528      * @return {!Promise} A promise.
529      */
530     doneValidating: function() {
531       if (this.validationRequests_ <= 0)
532         return Promise.resolve();
533       return new Promise(function(resolve) {
534         this.validationPromiseResolvers_.push(resolve);
535       }.bind(this));
536     }
537   };
538
539   return {
540     AddressListItem: AddressListItem,
541     CreditCardListItem: CreditCardListItem,
542     ValuesListItem: ValuesListItem,
543     NameListItem: NameListItem,
544     AutofillAddressList: AutofillAddressList,
545     AutofillCreditCardList: AutofillCreditCardList,
546     AutofillValuesList: AutofillValuesList,
547     AutofillNameValuesList: AutofillNameValuesList,
548     AutofillPhoneValuesList: AutofillPhoneValuesList,
549   };
550 });