1// Copyright (c) 2011 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
5cr.define('options', function() {
6
7  var Preferences = options.Preferences;
8
9  /**
10   * Helper function update element's state from pref change event.
11   * @private
12   * @param {!HTMLElement} el The element to update.
13   * @param {!Event} event The pref change event.
14   */
15  function updateElementState_(el, event) {
16    el.managed = event.value && event.value['managed'] != undefined ?
17        event.value['managed'] : false;
18
19    // Managed UI elements can only be disabled as a result of being
20    // managed. They cannot be enabled as a result of a pref being
21    // unmanaged.
22    if (el.managed)
23      el.disabled = true;
24
25    // Disable UI elements if backend says so.
26    if (!el.disabled && event.value && event.value['disabled'])
27      el.disabled = true;
28  }
29
30  /////////////////////////////////////////////////////////////////////////////
31  // PrefCheckbox class:
32  // TODO(jhawkins): Refactor all this copy-pasted code!
33
34  // Define a constructor that uses an input element as its underlying element.
35  var PrefCheckbox = cr.ui.define('input');
36
37  PrefCheckbox.prototype = {
38    // Set up the prototype chain
39    __proto__: HTMLInputElement.prototype,
40
41    /**
42     * Initialization function for the cr.ui framework.
43     */
44    decorate: function() {
45      this.type = 'checkbox';
46      var self = this;
47
48      self.initializeValueType(self.getAttribute('value-type'));
49
50      // Listen to pref changes.
51      Preferences.getInstance().addEventListener(
52          this.pref,
53          function(event) {
54            var value = event.value && event.value['value'] != undefined ?
55                event.value['value'] : event.value;
56
57            // Invert pref value if inverted_pref == true.
58            if (self.inverted_pref)
59              self.checked = !Boolean(value);
60            else
61              self.checked = Boolean(value);
62
63            updateElementState_(self, event);
64          });
65
66      // Listen to user events.
67      this.addEventListener(
68          'change',
69          function(e) {
70            var value = self.inverted_pref ? !self.checked : self.checked;
71            switch(self.valueType) {
72              case 'number':
73                Preferences.setIntegerPref(self.pref,
74                    Number(value), self.metric);
75                break;
76              case 'boolean':
77                Preferences.setBooleanPref(self.pref,
78                    value, self.metric);
79                break;
80            }
81          });
82    },
83
84    /**
85     * Sets up options in checkbox element.
86     * @param {String} valueType The preference type for this checkbox.
87     */
88    initializeValueType: function(valueType) {
89      this.valueType = valueType || 'boolean';
90    },
91  };
92
93  /**
94   * The preference name.
95   * @type {string}
96   */
97  cr.defineProperty(PrefCheckbox, 'pref', cr.PropertyKind.ATTR);
98
99  /**
100   * The user metric string.
101   * @type {string}
102   */
103  cr.defineProperty(PrefCheckbox, 'metric', cr.PropertyKind.ATTR);
104
105  /**
106   * Whether to use inverted pref value.
107   * @type {boolean}
108   */
109  cr.defineProperty(PrefCheckbox, 'inverted_pref', cr.PropertyKind.BOOL_ATTR);
110
111  /////////////////////////////////////////////////////////////////////////////
112  // PrefRadio class:
113
114  //Define a constructor that uses an input element as its underlying element.
115  var PrefRadio = cr.ui.define('input');
116
117  PrefRadio.prototype = {
118    // Set up the prototype chain
119    __proto__: HTMLInputElement.prototype,
120
121    /**
122     * Initialization function for the cr.ui framework.
123     */
124    decorate: function() {
125      this.type = 'radio';
126      var self = this;
127
128      // Listen to pref changes.
129      Preferences.getInstance().addEventListener(this.pref,
130          function(event) {
131            var value = event.value && event.value['value'] != undefined ?
132                event.value['value'] : event.value;
133            self.checked = String(value) == self.value;
134
135            updateElementState_(self, event);
136          });
137
138      // Listen to user events.
139      // Use the 'click' event instead of 'change', because of a bug in WebKit
140      // which prevents 'change' from being sent when the user changes selection
141      // using the keyboard.
142      // https://bugs.webkit.org/show_bug.cgi?id=32013
143      this.addEventListener('click',
144          function(e) {
145            if(self.value == 'true' || self.value == 'false') {
146              Preferences.setBooleanPref(self.pref,
147                  self.value == 'true', self.metric);
148            } else {
149              Preferences.setIntegerPref(self.pref,
150                  parseInt(self.value, 10), self.metric);
151            }
152          });
153    },
154  };
155
156  /**
157   * The preference name.
158   * @type {string}
159   */
160  cr.defineProperty(PrefRadio, 'pref', cr.PropertyKind.ATTR);
161
162  /**
163   * The user metric string.
164   * @type {string}
165   */
166  cr.defineProperty(PrefRadio, 'metric', cr.PropertyKind.ATTR);
167
168  /////////////////////////////////////////////////////////////////////////////
169  // PrefNumeric class:
170
171  // Define a constructor that uses an input element as its underlying element.
172  var PrefNumeric = function() {};
173  PrefNumeric.prototype = {
174    // Set up the prototype chain
175    __proto__: HTMLInputElement.prototype,
176
177    /**
178     * Initialization function for the cr.ui framework.
179     */
180    decorate: function() {
181      var self = this;
182
183      // Listen to pref changes.
184      Preferences.getInstance().addEventListener(this.pref,
185          function(event) {
186            self.value = event.value && event.value['value'] != undefined ?
187                event.value['value'] : event.value;
188
189            updateElementState_(self, event);
190          });
191
192      // Listen to user events.
193      this.addEventListener('change',
194          function(e) {
195            if (this.validity.valid) {
196              Preferences.setIntegerPref(self.pref, self.value, self.metric);
197            }
198          });
199    }
200  };
201
202  /**
203   * The preference name.
204   * @type {string}
205   */
206  cr.defineProperty(PrefNumeric, 'pref', cr.PropertyKind.ATTR);
207
208  /**
209   * The user metric string.
210   * @type {string}
211   */
212  cr.defineProperty(PrefNumeric, 'metric', cr.PropertyKind.ATTR);
213
214  /////////////////////////////////////////////////////////////////////////////
215  // PrefNumber class:
216
217  // Define a constructor that uses an input element as its underlying element.
218  var PrefNumber = cr.ui.define('input');
219
220  PrefNumber.prototype = {
221    // Set up the prototype chain
222    __proto__: PrefNumeric.prototype,
223
224    /**
225     * Initialization function for the cr.ui framework.
226     */
227    decorate: function() {
228      this.type = 'number';
229      PrefNumeric.prototype.decorate.call(this);
230
231      // Listen to user events.
232      this.addEventListener('input',
233          function(e) {
234            if (this.validity.valid) {
235              Preferences.setIntegerPref(self.pref, self.value, self.metric);
236            }
237          });
238    }
239  };
240
241  /////////////////////////////////////////////////////////////////////////////
242  // PrefRange class:
243
244  // Define a constructor that uses an input element as its underlying element.
245  var PrefRange = cr.ui.define('input');
246
247  PrefRange.prototype = {
248    // Set up the prototype chain
249    __proto__: HTMLInputElement.prototype,
250
251    /**
252     * The map from input range value to the corresponding preference value.
253     */
254    valueMap: undefined,
255
256    /**
257     * If true, the associated pref will be modified on each onchange event;
258     * otherwise, the pref will only be modified on the onmouseup event after
259     * the drag.
260     */
261    continuous: true,
262
263    /**
264     * Initialization function for the cr.ui framework.
265     */
266    decorate: function() {
267      this.type = 'range';
268
269      // Update the UI when the pref changes.
270      Preferences.getInstance().addEventListener(
271          this.pref, this.onPrefChange_.bind(this));
272
273      // Listen to user events.
274      // TODO(jhawkins): Add onmousewheel handling once the associated WK bug is
275      // fixed.
276      // https://bugs.webkit.org/show_bug.cgi?id=52256
277      this.onchange = this.onChange_.bind(this);
278      this.onkeyup = this.onmouseup = this.onInputUp_.bind(this);
279    },
280
281    /**
282     * Event listener that updates the UI when the underlying pref changes.
283     * @param {Event} event The event that details the pref change.
284     * @private
285     */
286    onPrefChange_: function(event) {
287      var value = event.value && event.value['value'] != undefined ?
288          event.value['value'] : event.value;
289      if (value != undefined)
290        this.value = this.valueMap ? this.valueMap.indexOf(value) : value;
291    },
292
293    /**
294     * onchange handler that sets the pref when the user changes the value of
295     * the input element.
296     * @private
297     */
298    onChange_: function(event) {
299      if (this.continuous)
300        this.setRangePref_();
301
302      if (this.notifyChange)
303        this.notifyChange(this, this.mapValueToRange_(this.value));
304    },
305
306    /**
307     * Sets the integer value of |pref| to the value of this element.
308     * @private
309     */
310    setRangePref_: function() {
311      Preferences.setIntegerPref(
312          this.pref, this.mapValueToRange_(this.value), this.metric);
313
314      if (this.notifyPrefChange)
315        this.notifyPrefChange(this, this.mapValueToRange_(this.value));
316    },
317
318    /**
319     * onkeyup/onmouseup handler that modifies the pref if |continuous| is
320     * false.
321     * @private
322     */
323    onInputUp_: function(event) {
324      if (!this.continuous)
325        this.setRangePref_();
326    },
327
328    /**
329     * Maps the value of this element into the range provided by the client,
330     * represented by |valueMap|.
331     * @param {number} value The value to map.
332     * @private
333     */
334    mapValueToRange_: function(value) {
335      return this.valueMap ? this.valueMap[value] : value;
336    },
337
338    /**
339     * Called when the client has specified non-continuous mode and the value of
340     * the range control changes.
341     * @param {Element} el This element.
342     * @param {number} value The value of this element.
343     */
344    notifyChange: function(el, value) {
345    },
346  };
347
348  /**
349   * The preference name.
350   * @type {string}
351   */
352  cr.defineProperty(PrefRange, 'pref', cr.PropertyKind.ATTR);
353
354  /**
355   * The user metric string.
356   * @type {string}
357   */
358  cr.defineProperty(PrefRange, 'metric', cr.PropertyKind.ATTR);
359
360  /////////////////////////////////////////////////////////////////////////////
361  // PrefSelect class:
362
363  // Define a constructor that uses a select element as its underlying element.
364  var PrefSelect = cr.ui.define('select');
365
366  PrefSelect.prototype = {
367    // Set up the prototype chain
368    __proto__: HTMLSelectElement.prototype,
369
370    /**
371    * Initialization function for the cr.ui framework.
372    */
373    decorate: function() {
374      var self = this;
375
376      // Listen to pref changes.
377      Preferences.getInstance().addEventListener(this.pref,
378          function(event) {
379            var value = event.value && event.value['value'] != undefined ?
380                event.value['value'] : event.value;
381
382            // Make sure |value| is a string, because the value is stored as a
383            // string in the HTMLOptionElement.
384            value = value.toString();
385
386            updateElementState_(self, event);
387
388            var found = false;
389            for (var i = 0; i < self.options.length; i++) {
390              if (self.options[i].value == value) {
391                self.selectedIndex = i;
392                found = true;
393              }
394            }
395
396            // Item not found, select first item.
397            if (!found)
398              self.selectedIndex = 0;
399
400            if (self.onchange != undefined)
401              self.onchange(event);
402          });
403
404      // Listen to user events.
405      this.addEventListener('change',
406          function(e) {
407            if (!self.dataType) {
408              console.error('undefined data type for <select> pref');
409              return;
410            }
411
412            switch(self.dataType) {
413              case 'number':
414                Preferences.setIntegerPref(self.pref,
415                    self.options[self.selectedIndex].value, self.metric);
416                break;
417              case 'double':
418                Preferences.setDoublePref(self.pref,
419                    self.options[self.selectedIndex].value, self.metric);
420                break;
421              case 'boolean':
422                var option = self.options[self.selectedIndex];
423                var value = (option.value == 'true') ? true : false;
424                Preferences.setBooleanPref(self.pref, value, self.metric);
425                break;
426              case 'string':
427                Preferences.setStringPref(self.pref,
428                    self.options[self.selectedIndex].value, self.metric);
429                break;
430              default:
431                console.error('unknown data type for <select> pref: ' +
432                              self.dataType);
433            }
434          });
435    },
436  };
437
438  /**
439   * The preference name.
440   * @type {string}
441   */
442  cr.defineProperty(PrefSelect, 'pref', cr.PropertyKind.ATTR);
443
444  /**
445   * The user metric string.
446   * @type {string}
447   */
448  cr.defineProperty(PrefSelect, 'metric', cr.PropertyKind.ATTR);
449
450  /**
451   * The data type for the preference options.
452   * @type {string}
453   */
454  cr.defineProperty(PrefSelect, 'dataType', cr.PropertyKind.ATTR);
455
456  /////////////////////////////////////////////////////////////////////////////
457  // PrefTextField class:
458
459  // Define a constructor that uses an input element as its underlying element.
460  var PrefTextField = cr.ui.define('input');
461
462  PrefTextField.prototype = {
463    // Set up the prototype chain
464    __proto__: HTMLInputElement.prototype,
465
466    /**
467     * Initialization function for the cr.ui framework.
468     */
469    decorate: function() {
470      var self = this;
471
472      // Listen to pref changes.
473      Preferences.getInstance().addEventListener(this.pref,
474          function(event) {
475            self.value = event.value && event.value['value'] != undefined ?
476                event.value['value'] : event.value;
477
478            updateElementState_(self, event);
479          });
480
481      // Listen to user events.
482      this.addEventListener('change',
483          function(e) {
484            switch(self.dataType) {
485              case 'number':
486                Preferences.setIntegerPref(self.pref, self.value, self.metric);
487                break;
488              case 'double':
489                Preferences.setDoublePref(self.pref, self.value, self.metric);
490                break;
491              default:
492                Preferences.setStringPref(self.pref, self.value, self.metric);
493                break;
494            }
495          });
496
497      window.addEventListener('unload',
498          function() {
499            if (document.activeElement == self)
500              self.blur();
501          });
502    }
503  };
504
505  /**
506   * The preference name.
507   * @type {string}
508   */
509  cr.defineProperty(PrefTextField, 'pref', cr.PropertyKind.ATTR);
510
511  /**
512   * The user metric string.
513   * @type {string}
514   */
515  cr.defineProperty(PrefTextField, 'metric', cr.PropertyKind.ATTR);
516
517  /**
518   * The data type for the preference options.
519   * @type {string}
520   */
521  cr.defineProperty(PrefTextField, 'dataType', cr.PropertyKind.ATTR);
522
523  // Export
524  return {
525    PrefCheckbox: PrefCheckbox,
526    PrefNumber: PrefNumber,
527    PrefNumeric: PrefNumeric,
528    PrefRadio: PrefRadio,
529    PrefRange: PrefRange,
530    PrefSelect: PrefSelect,
531    PrefTextField: PrefTextField
532  };
533
534});
535