Home | History | Annotate | Download | only in options
      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 
      5 cr.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