Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / options / pref_ui.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', function() {
6   var Preferences = options.Preferences;
7
8   /**
9    * Allows an element to be disabled for several reasons.
10    * The element is disabled if at least one reason is true, and the reasons
11    * can be set separately.
12    * @private
13    * @param {!HTMLElement} el The element to update.
14    * @param {string} reason The reason for disabling the element.
15    * @param {boolean} disabled Whether the element should be disabled or enabled
16    * for the given |reason|.
17    */
18   function updateDisabledState(el, reason, disabled) {
19     if (!el.disabledReasons)
20       el.disabledReasons = {};
21
22     if (el.disabled && (Object.keys(el.disabledReasons).length == 0)) {
23       // The element has been previously disabled without a reason, so we add
24       // one to keep it disabled.
25       el.disabledReasons.other = true;
26     }
27
28     if (!el.disabled) {
29       // If the element is not disabled, there should be no reason, except for
30       // 'other'.
31       delete el.disabledReasons.other;
32       if (Object.keys(el.disabledReasons).length > 0)
33         console.error('Element is not disabled but should be');
34     }
35
36     if (disabled)
37       el.disabledReasons[reason] = true;
38     else
39       delete el.disabledReasons[reason];
40
41     el.disabled = Object.keys(el.disabledReasons).length > 0;
42   }
43
44   /////////////////////////////////////////////////////////////////////////////
45   // PrefInputElement class:
46
47   /**
48    * Define a constructor that uses an input element as its underlying element.
49    * @constructor
50    * @extends {HTMLInputElement}
51    */
52   var PrefInputElement = cr.ui.define('input');
53
54   PrefInputElement.prototype = {
55     // Set up the prototype chain
56     __proto__: HTMLInputElement.prototype,
57
58     /**
59      * Initialization function for the cr.ui framework.
60      */
61     decorate: function() {
62       var self = this;
63
64       // Listen for user events.
65       this.addEventListener('change', this.handleChange.bind(this));
66
67       // Listen for pref changes.
68       Preferences.getInstance().addEventListener(this.pref, function(event) {
69         if (event.value.uncommitted && !self.dialogPref)
70           return;
71         self.updateStateFromPref(event);
72         updateDisabledState(self, 'notUserModifiable', event.value.disabled);
73         self.controlledBy = event.value.controlledBy;
74       });
75     },
76
77     /**
78      * Handle changes to the input element's state made by the user. If a custom
79      * change handler does not suppress it, a default handler is invoked that
80      * updates the associated pref.
81      * @param {Event} event Change event.
82      * @protected
83      */
84     handleChange: function(event) {
85       if (!this.customChangeHandler(event))
86         this.updatePrefFromState();
87     },
88
89     /**
90      * Handles changes to the pref. If a custom change handler does not suppress
91      * it, a default handler is invoked that updates the input element's state.
92      * @param {Event} event Pref change event.
93      * @protected
94      */
95     updateStateFromPref: function(event) {
96       if (!this.customPrefChangeHandler(event))
97         this.value = event.value.value;
98     },
99
100     /**
101      * See |updateDisabledState| above.
102      */
103     setDisabled: function(reason, disabled) {
104       updateDisabledState(this, reason, disabled);
105     },
106
107     /**
108      * Custom change handler that is invoked first when the user makes changes
109      * to the input element's state. If it returns false, a default handler is
110      * invoked next that updates the associated pref. If it returns true, the
111      * default handler is suppressed (i.e., this works like stopPropagation or
112      * cancelBubble).
113      * @param {Event} event Input element change event.
114      */
115     customChangeHandler: function(event) {
116       return false;
117     },
118
119     /**
120      * Custom change handler that is invoked first when the preference
121      * associated with the input element changes. If it returns false, a default
122      * handler is invoked next that updates the input element. If it returns
123      * true, the default handler is suppressed.
124      * @param {Event} event Input element change event.
125      */
126     customPrefChangeHandler: function(event) {
127       return false;
128     },
129   };
130
131   /**
132    * The name of the associated preference.
133    */
134   cr.defineProperty(PrefInputElement, 'pref', cr.PropertyKind.ATTR);
135
136   /**
137    * The data type of the associated preference, only relevant for derived
138    * classes that support different data types.
139    */
140   cr.defineProperty(PrefInputElement, 'dataType', cr.PropertyKind.ATTR);
141
142   /**
143    * Whether this input element is part of a dialog. If so, changes take effect
144    * in the settings UI immediately but are only actually committed when the
145    * user confirms the dialog. If the user cancels the dialog instead, the
146    * changes are rolled back in the settings UI and never committed.
147    */
148   cr.defineProperty(PrefInputElement, 'dialogPref', cr.PropertyKind.BOOL_ATTR);
149
150   /**
151    * Whether the associated preference is controlled by a source other than the
152    * user's setting (can be 'policy', 'extension', 'recommended' or unset).
153    */
154   cr.defineProperty(PrefInputElement, 'controlledBy', cr.PropertyKind.ATTR);
155
156   /**
157    * The user metric string.
158    */
159   cr.defineProperty(PrefInputElement, 'metric', cr.PropertyKind.ATTR);
160
161   /////////////////////////////////////////////////////////////////////////////
162   // PrefCheckbox class:
163
164   /**
165    * Define a constructor that uses an input element as its underlying element.
166    * @constructor
167    * @extends {options.PrefInputElement}
168    */
169   var PrefCheckbox = cr.ui.define('input');
170
171   PrefCheckbox.prototype = {
172     // Set up the prototype chain
173     __proto__: PrefInputElement.prototype,
174
175     /**
176      * Initialization function for the cr.ui framework.
177      */
178     decorate: function() {
179       PrefInputElement.prototype.decorate.call(this);
180       this.type = 'checkbox';
181
182       // Consider a checked dialog checkbox as a 'suggestion' which is committed
183       // once the user confirms the dialog.
184       if (this.dialogPref && this.checked)
185         this.updatePrefFromState();
186     },
187
188     /**
189      * Update the associated pref when when the user makes changes to the
190      * checkbox state.
191      * @override
192      */
193     updatePrefFromState: function() {
194       var value = this.inverted_pref ? !this.checked : this.checked;
195       Preferences.setBooleanPref(this.pref, value,
196                                  !this.dialogPref, this.metric);
197     },
198
199     /** @override */
200     updateStateFromPref: function(event) {
201       if (!this.customPrefChangeHandler(event))
202         this.defaultPrefChangeHandler(event);
203     },
204
205     /**
206      * @param {Event} event A pref change event.
207      */
208     defaultPrefChangeHandler: function(event) {
209       var value = Boolean(event.value.value);
210       this.checked = this.inverted_pref ? !value : value;
211     },
212   };
213
214   /**
215    * Whether the mapping between checkbox state and associated pref is inverted.
216    */
217   cr.defineProperty(PrefCheckbox, 'inverted_pref', cr.PropertyKind.BOOL_ATTR);
218
219   /////////////////////////////////////////////////////////////////////////////
220   // PrefNumber class:
221
222   // Define a constructor that uses an input element as its underlying element.
223   var PrefNumber = cr.ui.define('input');
224
225   PrefNumber.prototype = {
226     // Set up the prototype chain
227     __proto__: PrefInputElement.prototype,
228
229     /**
230      * Initialization function for the cr.ui framework.
231      */
232     decorate: function() {
233       PrefInputElement.prototype.decorate.call(this);
234       this.type = 'number';
235     },
236
237     /**
238      * Update the associated pref when the user inputs a number.
239      * @override
240      */
241     updatePrefFromState: function() {
242       if (this.validity.valid) {
243         Preferences.setIntegerPref(this.pref, this.value,
244                                    !this.dialogPref, this.metric);
245       }
246     },
247   };
248
249   /////////////////////////////////////////////////////////////////////////////
250   // PrefRadio class:
251
252   //Define a constructor that uses an input element as its underlying element.
253   var PrefRadio = cr.ui.define('input');
254
255   PrefRadio.prototype = {
256     // Set up the prototype chain
257     __proto__: PrefInputElement.prototype,
258
259     /**
260      * Initialization function for the cr.ui framework.
261      */
262     decorate: function() {
263       PrefInputElement.prototype.decorate.call(this);
264       this.type = 'radio';
265     },
266
267     /**
268      * Update the associated pref when when the user selects the radio button.
269      * @override
270      */
271     updatePrefFromState: function() {
272       if (this.value == 'true' || this.value == 'false') {
273         Preferences.setBooleanPref(this.pref,
274                                    this.value == String(this.checked),
275                                    !this.dialogPref, this.metric);
276       } else {
277         Preferences.setIntegerPref(this.pref, this.value,
278                                    !this.dialogPref, this.metric);
279       }
280     },
281
282     /** @override */
283     updateStateFromPref: function(event) {
284       if (!this.customPrefChangeHandler(event))
285         this.checked = this.value == String(event.value.value);
286     },
287   };
288
289   /////////////////////////////////////////////////////////////////////////////
290   // PrefRange class:
291
292   /**
293    * Define a constructor that uses an input element as its underlying element.
294    * @constructor
295    * @extends {options.PrefInputElement}
296    */
297   var PrefRange = cr.ui.define('input');
298
299   PrefRange.prototype = {
300     // Set up the prototype chain
301     __proto__: PrefInputElement.prototype,
302
303     /**
304      * The map from slider position to corresponding pref value.
305      */
306     valueMap: undefined,
307
308     /**
309      * Initialization function for the cr.ui framework.
310      */
311     decorate: function() {
312       PrefInputElement.prototype.decorate.call(this);
313       this.type = 'range';
314
315       // Listen for user events.
316       // TODO(jhawkins): Add onmousewheel handling once the associated WK bug is
317       // fixed.
318       // https://bugs.webkit.org/show_bug.cgi?id=52256
319       this.addEventListener('keyup', this.handleRelease_.bind(this));
320       this.addEventListener('mouseup', this.handleRelease_.bind(this));
321       this.addEventListener('touchcancel', this.handleRelease_.bind(this));
322       this.addEventListener('touchend', this.handleRelease_.bind(this));
323     },
324
325     /**
326      * Update the associated pref when when the user releases the slider.
327      * @override
328      */
329     updatePrefFromState: function() {
330       Preferences.setIntegerPref(
331           this.pref,
332           this.mapPositionToPref(parseInt(this.value, 10)),
333           !this.dialogPref,
334           this.metric);
335     },
336
337     /** @override */
338     handleChange: function() {
339       // Ignore changes to the slider position made by the user while the slider
340       // has not been released.
341     },
342
343     /**
344      * Handle changes to the slider position made by the user when the slider is
345      * released. If a custom change handler does not suppress it, a default
346      * handler is invoked that updates the associated pref.
347      * @param {Event} event Change event.
348      * @private
349      */
350     handleRelease_: function(event) {
351       if (!this.customChangeHandler(event))
352         this.updatePrefFromState();
353     },
354
355     /**
356      * Handles changes to the pref associated with the slider. If a custom
357      * change handler does not suppress it, a default handler is invoked that
358      * updates the slider position.
359      * @override.
360      */
361     updateStateFromPref: function(event) {
362       if (this.customPrefChangeHandler(event))
363         return;
364       var value = event.value.value;
365       this.value = this.valueMap ? this.valueMap.indexOf(value) : value;
366     },
367
368     /**
369      * Map slider position to the range of values provided by the client,
370      * represented by |valueMap|.
371      * @param {number} position The slider position to map.
372      */
373     mapPositionToPref: function(position) {
374       return this.valueMap ? this.valueMap[position] : position;
375     },
376   };
377
378   /////////////////////////////////////////////////////////////////////////////
379   // PrefSelect class:
380
381   // Define a constructor that uses a select element as its underlying element.
382   var PrefSelect = cr.ui.define('select');
383
384   PrefSelect.prototype = {
385     // Set up the prototype chain
386     __proto__: PrefInputElement.prototype,
387
388     /**
389      * Update the associated pref when when the user selects an item.
390      * @override
391      */
392     updatePrefFromState: function() {
393       var value = this.options[this.selectedIndex].value;
394       switch (this.dataType) {
395         case 'number':
396           Preferences.setIntegerPref(this.pref, value,
397                                      !this.dialogPref, this.metric);
398           break;
399         case 'double':
400           Preferences.setDoublePref(this.pref, value,
401                                     !this.dialogPref, this.metric);
402           break;
403         case 'boolean':
404           Preferences.setBooleanPref(this.pref, value == 'true',
405                                      !this.dialogPref, this.metric);
406           break;
407         case 'string':
408           Preferences.setStringPref(this.pref, value,
409                                     !this.dialogPref, this.metric);
410           break;
411         default:
412           console.error('Unknown data type for <select> UI element: ' +
413                         this.dataType);
414       }
415     },
416
417     /** @override */
418     updateStateFromPref: function(event) {
419       if (this.customPrefChangeHandler(event))
420         return;
421
422       // Make sure the value is a string, because the value is stored as a
423       // string in the HTMLOptionElement.
424       var value = String(event.value.value);
425
426       var found = false;
427       for (var i = 0; i < this.options.length; i++) {
428         if (this.options[i].value == value) {
429           this.selectedIndex = i;
430           found = true;
431         }
432       }
433
434       // Item not found, select first item.
435       if (!found)
436         this.selectedIndex = 0;
437
438       // The "onchange" event automatically fires when the user makes a manual
439       // change. It should never be fired for a programmatic change. However,
440       // these two lines were here already and it is hard to tell who may be
441       // relying on them.
442       if (this.onchange)
443         this.onchange(event);
444     },
445   };
446
447   /////////////////////////////////////////////////////////////////////////////
448   // PrefTextField class:
449
450   // Define a constructor that uses an input element as its underlying element.
451   var PrefTextField = cr.ui.define('input');
452
453   PrefTextField.prototype = {
454     // Set up the prototype chain
455     __proto__: PrefInputElement.prototype,
456
457     /**
458      * Initialization function for the cr.ui framework.
459      */
460     decorate: function() {
461       PrefInputElement.prototype.decorate.call(this);
462       var self = this;
463
464       // Listen for user events.
465       window.addEventListener('unload', function() {
466         if (document.activeElement == self)
467           self.blur();
468       });
469     },
470
471     /**
472      * Update the associated pref when when the user inputs text.
473      * @override
474      */
475     updatePrefFromState: function(event) {
476       switch (this.dataType) {
477         case 'number':
478           Preferences.setIntegerPref(this.pref, this.value,
479                                      !this.dialogPref, this.metric);
480           break;
481         case 'double':
482           Preferences.setDoublePref(this.pref, this.value,
483                                     !this.dialogPref, this.metric);
484           break;
485         case 'url':
486           Preferences.setURLPref(this.pref, this.value,
487                                  !this.dialogPref, this.metric);
488           break;
489         default:
490           Preferences.setStringPref(this.pref, this.value,
491                                     !this.dialogPref, this.metric);
492           break;
493       }
494     },
495   };
496
497   /////////////////////////////////////////////////////////////////////////////
498   // PrefPortNumber class:
499
500   // Define a constructor that uses an input element as its underlying element.
501   var PrefPortNumber = cr.ui.define('input');
502
503   PrefPortNumber.prototype = {
504     // Set up the prototype chain
505     __proto__: PrefTextField.prototype,
506
507     /**
508      * Initialization function for the cr.ui framework.
509      */
510     decorate: function() {
511       var self = this;
512       self.type = 'text';
513       self.dataType = 'number';
514       PrefTextField.prototype.decorate.call(this);
515       self.oninput = function() {
516         // Note that using <input type="number"> is insufficient to restrict
517         // the input as it allows negative numbers and does not limit the
518         // number of charactes typed even if a range is set.  Furthermore,
519         // it sometimes produces strange repaint artifacts.
520         var filtered = self.value.replace(/[^0-9]/g, '');
521         if (filtered != self.value)
522           self.value = filtered;
523       };
524     }
525   };
526
527   /////////////////////////////////////////////////////////////////////////////
528   // PrefButton class:
529
530   // Define a constructor that uses a button element as its underlying element.
531   var PrefButton = cr.ui.define('button');
532
533   PrefButton.prototype = {
534     // Set up the prototype chain
535     __proto__: HTMLButtonElement.prototype,
536
537     /**
538      * Initialization function for the cr.ui framework.
539      */
540     decorate: function() {
541       var self = this;
542
543       // Listen for pref changes.
544       // This element behaves like a normal button and does not affect the
545       // underlying preference; it just becomes disabled when the preference is
546       // managed, and its value is false. This is useful for buttons that should
547       // be disabled when the underlying Boolean preference is set to false by a
548       // policy or extension.
549       Preferences.getInstance().addEventListener(this.pref, function(event) {
550         updateDisabledState(self, 'notUserModifiable',
551                             event.value.disabled && !event.value.value);
552         self.controlledBy = event.value.controlledBy;
553       });
554     },
555
556     /**
557      * See |updateDisabledState| above.
558      */
559     setDisabled: function(reason, disabled) {
560       updateDisabledState(this, reason, disabled);
561     },
562   };
563
564   /**
565    * The name of the associated preference.
566    */
567   cr.defineProperty(PrefButton, 'pref', cr.PropertyKind.ATTR);
568
569   /**
570    * Whether the associated preference is controlled by a source other than the
571    * user's setting (can be 'policy', 'extension', 'recommended' or unset).
572    */
573   cr.defineProperty(PrefButton, 'controlledBy', cr.PropertyKind.ATTR);
574
575   // Export
576   return {
577     PrefCheckbox: PrefCheckbox,
578     PrefInputElement: PrefInputElement,
579     PrefNumber: PrefNumber,
580     PrefRadio: PrefRadio,
581     PrefRange: PrefRange,
582     PrefSelect: PrefSelect,
583     PrefTextField: PrefTextField,
584     PrefPortNumber: PrefPortNumber,
585     PrefButton: PrefButton
586   };
587 });