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.
5 cr.define('options', function() {
7 var Preferences = options.Preferences;
10 * Allows an element to be disabled for several reasons.
11 * The element is disabled if at least one reason is true, and the reasons
12 * can be set separately.
14 * @param {!HTMLElement} el The element to update.
15 * @param {string} reason The reason for disabling the element.
16 * @param {boolean} disabled Whether the element should be disabled or enabled
17 * for the given |reason|.
19 function updateDisabledState_(el, reason, disabled) {
20 if (!el.disabledReasons)
21 el.disabledReasons = {};
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;
28 // If the element is not disabled, there should be no reason, except for
30 delete el.disabledReasons.other;
31 if (Object.keys(el.disabledReasons).length > 0)
32 console.error('Element is not disabled but should be');
35 el.disabledReasons[reason] = true;
37 delete el.disabledReasons[reason];
39 el.disabled = Object.keys(el.disabledReasons).length > 0;
42 /////////////////////////////////////////////////////////////////////////////
43 // PrefInputElement class:
45 // Define a constructor that uses an input element as its underlying element.
46 var PrefInputElement = cr.ui.define('input');
48 PrefInputElement.prototype = {
49 // Set up the prototype chain
50 __proto__: HTMLInputElement.prototype,
53 * Initialization function for the cr.ui framework.
55 decorate: function() {
58 // Listen for user events.
59 this.addEventListener('change', this.handleChange_.bind(this));
61 // Listen for pref changes.
62 Preferences.getInstance().addEventListener(this.pref, function(event) {
63 if (event.value.uncommitted && !self.dialogPref)
65 self.updateStateFromPref_(event);
66 updateDisabledState_(self, 'notUserModifiable', event.value.disabled);
67 self.controlledBy = event.value.controlledBy;
72 * Handle changes to the input element's state made by the user. If a custom
73 * change handler does not suppress it, a default handler is invoked that
74 * updates the associated pref.
75 * @param {Event} event Change event.
78 handleChange_: function(event) {
79 if (!this.customChangeHandler(event))
80 this.updatePrefFromState_();
84 * Update the input element's state when the associated pref changes.
85 * @param {Event} event Pref change event.
88 updateStateFromPref_: function(event) {
89 this.value = event.value.value;
93 * See |updateDisabledState_| above.
95 setDisabled: function(reason, disabled) {
96 updateDisabledState_(this, reason, disabled);
100 * Custom change handler that is invoked first when the user makes changes
101 * to the input element's state. If it returns false, a default handler is
102 * invoked next that updates the associated pref. If it returns true, the
103 * default handler is suppressed (i.e., this works like stopPropagation or
105 * @param {Event} event Input element change event.
107 customChangeHandler: function(event) {
113 * The name of the associated preference.
116 cr.defineProperty(PrefInputElement, 'pref', cr.PropertyKind.ATTR);
119 * The data type of the associated preference, only relevant for derived
120 * classes that support different data types.
123 cr.defineProperty(PrefInputElement, 'dataType', cr.PropertyKind.ATTR);
126 * Whether this input element is part of a dialog. If so, changes take effect
127 * in the settings UI immediately but are only actually committed when the
128 * user confirms the dialog. If the user cancels the dialog instead, the
129 * changes are rolled back in the settings UI and never committed.
132 cr.defineProperty(PrefInputElement, 'dialogPref', cr.PropertyKind.BOOL_ATTR);
135 * Whether the associated preference is controlled by a source other than the
136 * user's setting (can be 'policy', 'extension', 'recommended' or unset).
139 cr.defineProperty(PrefInputElement, 'controlledBy', cr.PropertyKind.ATTR);
142 * The user metric string.
145 cr.defineProperty(PrefInputElement, 'metric', cr.PropertyKind.ATTR);
147 /////////////////////////////////////////////////////////////////////////////
148 // PrefCheckbox class:
150 // Define a constructor that uses an input element as its underlying element.
151 var PrefCheckbox = cr.ui.define('input');
153 PrefCheckbox.prototype = {
154 // Set up the prototype chain
155 __proto__: PrefInputElement.prototype,
158 * Initialization function for the cr.ui framework.
160 decorate: function() {
161 PrefInputElement.prototype.decorate.call(this);
162 this.type = 'checkbox';
164 // Consider a checked dialog checkbox as a 'suggestion' which is committed
165 // once the user confirms the dialog.
166 if (this.dialogPref && this.checked)
167 this.updatePrefFromState_();
171 * Update the associated pref when when the user makes changes to the
175 updatePrefFromState_: function() {
176 var value = this.inverted_pref ? !this.checked : this.checked;
177 Preferences.setBooleanPref(this.pref, value,
178 !this.dialogPref, this.metric);
182 * Update the checkbox state when the associated pref changes.
183 * @param {Event} event Pref change event.
186 updateStateFromPref_: function(event) {
187 var value = Boolean(event.value.value);
188 this.checked = this.inverted_pref ? !value : value;
193 * Whether the mapping between checkbox state and associated pref is inverted.
196 cr.defineProperty(PrefCheckbox, 'inverted_pref', cr.PropertyKind.BOOL_ATTR);
198 /////////////////////////////////////////////////////////////////////////////
201 // Define a constructor that uses an input element as its underlying element.
202 var PrefNumber = cr.ui.define('input');
204 PrefNumber.prototype = {
205 // Set up the prototype chain
206 __proto__: PrefInputElement.prototype,
209 * Initialization function for the cr.ui framework.
211 decorate: function() {
212 PrefInputElement.prototype.decorate.call(this);
213 this.type = 'number';
217 * Update the associated pref when when the user inputs a number.
220 updatePrefFromState_: function() {
221 if (this.validity.valid) {
222 Preferences.setIntegerPref(this.pref, this.value,
223 !this.dialogPref, this.metric);
228 /////////////////////////////////////////////////////////////////////////////
231 //Define a constructor that uses an input element as its underlying element.
232 var PrefRadio = cr.ui.define('input');
234 PrefRadio.prototype = {
235 // Set up the prototype chain
236 __proto__: PrefInputElement.prototype,
239 * Initialization function for the cr.ui framework.
241 decorate: function() {
242 PrefInputElement.prototype.decorate.call(this);
247 * Update the associated pref when when the user selects the radio button.
250 updatePrefFromState_: function() {
251 if (this.value == 'true' || this.value == 'false') {
252 Preferences.setBooleanPref(this.pref,
253 this.value == String(this.checked),
254 !this.dialogPref, this.metric);
256 Preferences.setIntegerPref(this.pref, this.value,
257 !this.dialogPref, this.metric);
262 * Update the radio button state when the associated pref changes.
263 * @param {Event} event Pref change event.
266 updateStateFromPref_: function(event) {
267 this.checked = this.value == String(event.value.value);
271 /////////////////////////////////////////////////////////////////////////////
274 // Define a constructor that uses an input element as its underlying element.
275 var PrefRange = cr.ui.define('input');
277 PrefRange.prototype = {
278 // Set up the prototype chain
279 __proto__: PrefInputElement.prototype,
282 * The map from slider position to corresponding pref value.
287 * Initialization function for the cr.ui framework.
289 decorate: function() {
290 PrefInputElement.prototype.decorate.call(this);
293 // Listen for user events.
294 // TODO(jhawkins): Add onmousewheel handling once the associated WK bug is
296 // https://bugs.webkit.org/show_bug.cgi?id=52256
297 this.addEventListener('keyup', this.handleRelease_.bind(this));
298 this.addEventListener('mouseup', this.handleRelease_.bind(this));
302 * Update the associated pref when when the user releases the slider.
305 updatePrefFromState_: function() {
306 Preferences.setIntegerPref(this.pref, this.mapPositionToPref(this.value),
307 !this.dialogPref, this.metric);
311 * Ignore changes to the slider position made by the user while the slider
312 * has not been released.
315 handleChange_: function() {
319 * Handle changes to the slider position made by the user when the slider is
320 * released. If a custom change handler does not suppress it, a default
321 * handler is invoked that updates the associated pref.
322 * @param {Event} event Change event.
325 handleRelease_: function(event) {
326 if (!this.customChangeHandler(event))
327 this.updatePrefFromState_();
331 * Update the slider position when the associated pref changes.
332 * @param {Event} event Pref change event.
335 updateStateFromPref_: function(event) {
336 var value = event.value.value;
337 this.value = this.valueMap ? this.valueMap.indexOf(value) : value;
341 * Map slider position to the range of values provided by the client,
342 * represented by |valueMap|.
343 * @param {number} position The slider position to map.
345 mapPositionToPref: function(position) {
346 return this.valueMap ? this.valueMap[position] : position;
350 /////////////////////////////////////////////////////////////////////////////
353 // Define a constructor that uses a select element as its underlying element.
354 var PrefSelect = cr.ui.define('select');
356 PrefSelect.prototype = {
357 // Set up the prototype chain
358 __proto__: PrefInputElement.prototype,
361 * Update the associated pref when when the user selects an item.
364 updatePrefFromState_: function() {
365 var value = this.options[this.selectedIndex].value;
366 switch (this.dataType) {
368 Preferences.setIntegerPref(this.pref, value,
369 !this.dialogPref, this.metric);
372 Preferences.setDoublePref(this.pref, value,
373 !this.dialogPref, this.metric);
376 Preferences.setBooleanPref(this.pref, value == 'true',
377 !this.dialogPref, this.metric);
380 Preferences.setStringPref(this.pref, value,
381 !this.dialogPref, this.metric);
384 console.error('Unknown data type for <select> UI element: ' +
390 * Update the selected item when the associated pref changes.
391 * @param {Event} event Pref change event.
394 updateStateFromPref_: function(event) {
395 // Make sure the value is a string, because the value is stored as a
396 // string in the HTMLOptionElement.
397 value = String(event.value.value);
400 for (var i = 0; i < this.options.length; i++) {
401 if (this.options[i].value == value) {
402 this.selectedIndex = i;
407 // Item not found, select first item.
409 this.selectedIndex = 0;
411 // The "onchange" event automatically fires when the user makes a manual
412 // change. It should never be fired for a programmatic change. However,
413 // these two lines were here already and it is hard to tell who may be
416 this.onchange(event);
420 /////////////////////////////////////////////////////////////////////////////
421 // PrefTextField class:
423 // Define a constructor that uses an input element as its underlying element.
424 var PrefTextField = cr.ui.define('input');
426 PrefTextField.prototype = {
427 // Set up the prototype chain
428 __proto__: PrefInputElement.prototype,
431 * Initialization function for the cr.ui framework.
433 decorate: function() {
434 PrefInputElement.prototype.decorate.call(this);
437 // Listen for user events.
438 window.addEventListener('unload', function() {
439 if (document.activeElement == self)
445 * Update the associated pref when when the user inputs text.
448 updatePrefFromState_: function(event) {
449 switch (this.dataType) {
451 Preferences.setIntegerPref(this.pref, this.value,
452 !this.dialogPref, this.metric);
455 Preferences.setDoublePref(this.pref, this.value,
456 !this.dialogPref, this.metric);
459 Preferences.setURLPref(this.pref, this.value,
460 !this.dialogPref, this.metric);
463 Preferences.setStringPref(this.pref, this.value,
464 !this.dialogPref, this.metric);
470 /////////////////////////////////////////////////////////////////////////////
471 // PrefPortNumber class:
473 // Define a constructor that uses an input element as its underlying element.
474 var PrefPortNumber = cr.ui.define('input');
476 PrefPortNumber.prototype = {
477 // Set up the prototype chain
478 __proto__: PrefTextField.prototype,
481 * Initialization function for the cr.ui framework.
483 decorate: function() {
486 self.dataType = 'number';
487 PrefTextField.prototype.decorate.call(this);
488 self.oninput = function() {
489 // Note that using <input type="number"> is insufficient to restrict
490 // the input as it allows negative numbers and does not limit the
491 // number of charactes typed even if a range is set. Furthermore,
492 // it sometimes produces strange repaint artifacts.
493 var filtered = self.value.replace(/[^0-9]/g, '');
494 if (filtered != self.value)
495 self.value = filtered;
500 /////////////////////////////////////////////////////////////////////////////
503 // Define a constructor that uses a button element as its underlying element.
504 var PrefButton = cr.ui.define('button');
506 PrefButton.prototype = {
507 // Set up the prototype chain
508 __proto__: HTMLButtonElement.prototype,
511 * Initialization function for the cr.ui framework.
513 decorate: function() {
516 // Listen for pref changes.
517 // This element behaves like a normal button and does not affect the
518 // underlying preference; it just becomes disabled when the preference is
519 // managed, and its value is false. This is useful for buttons that should
520 // be disabled when the underlying Boolean preference is set to false by a
521 // policy or extension.
522 Preferences.getInstance().addEventListener(this.pref, function(event) {
523 updateDisabledState_(self, 'notUserModifiable',
524 event.value.disabled && !event.value.value);
525 self.controlledBy = event.value.controlledBy;
530 * See |updateDisabledState_| above.
532 setDisabled: function(reason, disabled) {
533 updateDisabledState_(this, reason, disabled);
538 * The name of the associated preference.
541 cr.defineProperty(PrefButton, 'pref', cr.PropertyKind.ATTR);
544 * Whether the associated preference is controlled by a source other than the
545 * user's setting (can be 'policy', 'extension', 'recommended' or unset).
548 cr.defineProperty(PrefButton, 'controlledBy', cr.PropertyKind.ATTR);
552 PrefCheckbox: PrefCheckbox,
553 PrefNumber: PrefNumber,
554 PrefRadio: PrefRadio,
555 PrefRange: PrefRange,
556 PrefSelect: PrefSelect,
557 PrefTextField: PrefTextField,
558 PrefPortNumber: PrefPortNumber,
559 PrefButton: PrefButton