| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| cr.define('options', function() { |
| |
| var Preferences = options.Preferences; |
| |
| /** |
| * Allows an element to be disabled for several reasons. |
| * The element is disabled if at least one reason is true, and the reasons |
| * can be set separately. |
| * @private |
| * @param {!HTMLElement} el The element to update. |
| * @param {string} reason The reason for disabling the element. |
| * @param {boolean} disabled Whether the element should be disabled or enabled |
| * for the given |reason|. |
| */ |
| function updateDisabledState_(el, reason, disabled) { |
| if (!el.disabledReasons) |
| el.disabledReasons = {}; |
| if (el.disabled && (Object.keys(el.disabledReasons).length == 0)) { |
| // The element has been previously disabled without a reason, so we add |
| // one to keep it disabled. |
| el.disabledReasons.other = true; |
| } |
| if (!el.disabled) { |
| // If the element is not disabled, there should be no reason, except for |
| // 'other'. |
| delete el.disabledReasons.other; |
| if (Object.keys(el.disabledReasons).length > 0) |
| console.error('Element is not disabled but should be'); |
| } |
| if (disabled) { |
| el.disabledReasons[reason] = true; |
| } else { |
| delete el.disabledReasons[reason]; |
| } |
| el.disabled = Object.keys(el.disabledReasons).length > 0; |
| } |
| |
| ///////////////////////////////////////////////////////////////////////////// |
| // PrefInputElement class: |
| |
| // Define a constructor that uses an input element as its underlying element. |
| var PrefInputElement = cr.ui.define('input'); |
| |
| PrefInputElement.prototype = { |
| // Set up the prototype chain |
| __proto__: HTMLInputElement.prototype, |
| |
| /** |
| * Initialization function for the cr.ui framework. |
| */ |
| decorate: function() { |
| var self = this; |
| |
| // Listen for user events. |
| this.addEventListener('change', this.handleChange_.bind(this)); |
| |
| // Listen for pref changes. |
| Preferences.getInstance().addEventListener(this.pref, function(event) { |
| if (event.value.uncommitted && !self.dialogPref) |
| return; |
| self.updateStateFromPref_(event); |
| updateDisabledState_(self, 'notUserModifiable', event.value.disabled); |
| self.controlledBy = event.value.controlledBy; |
| }); |
| }, |
| |
| /** |
| * Handle changes to the input element's state made by the user. If a custom |
| * change handler does not suppress it, a default handler is invoked that |
| * updates the associated pref. |
| * @param {Event} event Change event. |
| * @private |
| */ |
| handleChange_: function(event) { |
| if (!this.customChangeHandler(event)) |
| this.updatePrefFromState_(); |
| }, |
| |
| /** |
| * Update the input element's state when the associated pref changes. |
| * @param {Event} event Pref change event. |
| * @private |
| */ |
| updateStateFromPref_: function(event) { |
| this.value = event.value.value; |
| }, |
| |
| /** |
| * See |updateDisabledState_| above. |
| */ |
| setDisabled: function(reason, disabled) { |
| updateDisabledState_(this, reason, disabled); |
| }, |
| |
| /** |
| * Custom change handler that is invoked first when the user makes changes |
| * to the input element's state. If it returns false, a default handler is |
| * invoked next that updates the associated pref. If it returns true, the |
| * default handler is suppressed (i.e., this works like stopPropagation or |
| * cancelBubble). |
| * @param {Event} event Input element change event. |
| */ |
| customChangeHandler: function(event) { |
| return false; |
| }, |
| }; |
| |
| /** |
| * The name of the associated preference. |
| * @type {string} |
| */ |
| cr.defineProperty(PrefInputElement, 'pref', cr.PropertyKind.ATTR); |
| |
| /** |
| * The data type of the associated preference, only relevant for derived |
| * classes that support different data types. |
| * @type {string} |
| */ |
| cr.defineProperty(PrefInputElement, 'dataType', cr.PropertyKind.ATTR); |
| |
| /** |
| * Whether this input element is part of a dialog. If so, changes take effect |
| * in the settings UI immediately but are only actually committed when the |
| * user confirms the dialog. If the user cancels the dialog instead, the |
| * changes are rolled back in the settings UI and never committed. |
| * @type {boolean} |
| */ |
| cr.defineProperty(PrefInputElement, 'dialogPref', cr.PropertyKind.BOOL_ATTR); |
| |
| /** |
| * Whether the associated preference is controlled by a source other than the |
| * user's setting (can be 'policy', 'extension', 'recommended' or unset). |
| * @type {string} |
| */ |
| cr.defineProperty(PrefInputElement, 'controlledBy', cr.PropertyKind.ATTR); |
| |
| /** |
| * The user metric string. |
| * @type {string} |
| */ |
| cr.defineProperty(PrefInputElement, 'metric', cr.PropertyKind.ATTR); |
| |
| ///////////////////////////////////////////////////////////////////////////// |
| // PrefCheckbox class: |
| |
| // Define a constructor that uses an input element as its underlying element. |
| var PrefCheckbox = cr.ui.define('input'); |
| |
| PrefCheckbox.prototype = { |
| // Set up the prototype chain |
| __proto__: PrefInputElement.prototype, |
| |
| /** |
| * Initialization function for the cr.ui framework. |
| */ |
| decorate: function() { |
| PrefInputElement.prototype.decorate.call(this); |
| this.type = 'checkbox'; |
| }, |
| |
| /** |
| * Update the associated pref when when the user makes changes to the |
| * checkbox state. |
| * @private |
| */ |
| updatePrefFromState_: function() { |
| var value = this.inverted_pref ? !this.checked : this.checked; |
| Preferences.setBooleanPref(this.pref, value, |
| !this.dialogPref, this.metric); |
| }, |
| |
| /** |
| * Update the checkbox state when the associated pref changes. |
| * @param {Event} event Pref change event. |
| * @private |
| */ |
| updateStateFromPref_: function(event) { |
| var value = Boolean(event.value.value); |
| this.checked = this.inverted_pref ? !value : value; |
| }, |
| }; |
| |
| /** |
| * Whether the mapping between checkbox state and associated pref is inverted. |
| * @type {boolean} |
| */ |
| cr.defineProperty(PrefCheckbox, 'inverted_pref', cr.PropertyKind.BOOL_ATTR); |
| |
| ///////////////////////////////////////////////////////////////////////////// |
| // PrefNumber class: |
| |
| // Define a constructor that uses an input element as its underlying element. |
| var PrefNumber = cr.ui.define('input'); |
| |
| PrefNumber.prototype = { |
| // Set up the prototype chain |
| __proto__: PrefInputElement.prototype, |
| |
| /** |
| * Initialization function for the cr.ui framework. |
| */ |
| decorate: function() { |
| PrefInputElement.prototype.decorate.call(this); |
| this.type = 'number'; |
| }, |
| |
| /** |
| * Update the associated pref when when the user inputs a number. |
| * @private |
| */ |
| updatePrefFromState_: function() { |
| if (this.validity.valid) { |
| Preferences.setIntegerPref(this.pref, this.value, |
| !this.dialogPref, this.metric); |
| } |
| }, |
| }; |
| |
| ///////////////////////////////////////////////////////////////////////////// |
| // PrefRadio class: |
| |
| //Define a constructor that uses an input element as its underlying element. |
| var PrefRadio = cr.ui.define('input'); |
| |
| PrefRadio.prototype = { |
| // Set up the prototype chain |
| __proto__: PrefInputElement.prototype, |
| |
| /** |
| * Initialization function for the cr.ui framework. |
| */ |
| decorate: function() { |
| PrefInputElement.prototype.decorate.call(this); |
| this.type = 'radio'; |
| }, |
| |
| /** |
| * Update the associated pref when when the user selects the radio button. |
| * @private |
| */ |
| updatePrefFromState_: function() { |
| if (this.value == 'true' || this.value == 'false') { |
| Preferences.setBooleanPref(this.pref, |
| this.value == String(this.checked), |
| !this.dialogPref, this.metric); |
| } else { |
| Preferences.setIntegerPref(this.pref, this.value, |
| !this.dialogPref, this.metric); |
| } |
| }, |
| |
| /** |
| * Update the radio button state when the associated pref changes. |
| * @param {Event} event Pref change event. |
| * @private |
| */ |
| updateStateFromPref_: function(event) { |
| this.checked = this.value == String(event.value.value); |
| }, |
| }; |
| |
| ///////////////////////////////////////////////////////////////////////////// |
| // PrefRange class: |
| |
| // Define a constructor that uses an input element as its underlying element. |
| var PrefRange = cr.ui.define('input'); |
| |
| PrefRange.prototype = { |
| // Set up the prototype chain |
| __proto__: PrefInputElement.prototype, |
| |
| /** |
| * The map from slider position to corresponding pref value. |
| */ |
| valueMap: undefined, |
| |
| /** |
| * Initialization function for the cr.ui framework. |
| */ |
| decorate: function() { |
| PrefInputElement.prototype.decorate.call(this); |
| this.type = 'range'; |
| |
| // Listen for user events. |
| // TODO(jhawkins): Add onmousewheel handling once the associated WK bug is |
| // fixed. |
| // https://bugs.webkit.org/show_bug.cgi?id=52256 |
| this.addEventListener('keyup', this.handleRelease_.bind(this)); |
| this.addEventListener('mouseup', this.handleRelease_.bind(this)); |
| }, |
| |
| /** |
| * Update the associated pref when when the user releases the slider. |
| * @private |
| */ |
| updatePrefFromState_: function() { |
| Preferences.setIntegerPref(this.pref, this.mapPositionToPref(this.value), |
| !this.dialogPref, this.metric); |
| }, |
| |
| /** |
| * Ignore changes to the slider position made by the user while the slider |
| * has not been released. |
| * @private |
| */ |
| handleChange_: function() { |
| }, |
| |
| /** |
| * Handle changes to the slider position made by the user when the slider is |
| * released. If a custom change handler does not suppress it, a default |
| * handler is invoked that updates the associated pref. |
| * @param {Event} event Change event. |
| * @private |
| */ |
| handleRelease_: function(event) { |
| if (!this.customChangeHandler(event)) |
| this.updatePrefFromState_(); |
| }, |
| |
| /** |
| * Update the slider position when the associated pref changes. |
| * @param {Event} event Pref change event. |
| * @private |
| */ |
| updateStateFromPref_: function(event) { |
| var value = event.value.value; |
| this.value = this.valueMap ? this.valueMap.indexOf(value) : value; |
| }, |
| |
| /** |
| * Map slider position to the range of values provided by the client, |
| * represented by |valueMap|. |
| * @param {number} position The slider position to map. |
| */ |
| mapPositionToPref: function(position) { |
| return this.valueMap ? this.valueMap[position] : position; |
| }, |
| }; |
| |
| ///////////////////////////////////////////////////////////////////////////// |
| // PrefSelect class: |
| |
| // Define a constructor that uses a select element as its underlying element. |
| var PrefSelect = cr.ui.define('select'); |
| |
| PrefSelect.prototype = { |
| // Set up the prototype chain |
| __proto__: PrefInputElement.prototype, |
| |
| /** |
| * Update the associated pref when when the user selects an item. |
| * @private |
| */ |
| updatePrefFromState_: function() { |
| var value = this.options[this.selectedIndex].value; |
| switch (this.dataType) { |
| case 'number': |
| Preferences.setIntegerPref(this.pref, value, |
| !this.dialogPref, this.metric); |
| break; |
| case 'double': |
| Preferences.setDoublePref(this.pref, value, |
| !this.dialogPref, this.metric); |
| break; |
| case 'boolean': |
| Preferences.setBooleanPref(this.pref, value == 'true', |
| !this.dialogPref, this.metric); |
| break; |
| case 'string': |
| Preferences.setStringPref(this.pref, value, |
| !this.dialogPref, this.metric); |
| break; |
| default: |
| console.error('Unknown data type for <select> UI element: ' + |
| this.dataType); |
| } |
| }, |
| |
| /** |
| * Update the selected item when the associated pref changes. |
| * @param {Event} event Pref change event. |
| * @private |
| */ |
| updateStateFromPref_: function(event) { |
| // Make sure the value is a string, because the value is stored as a |
| // string in the HTMLOptionElement. |
| value = String(event.value.value); |
| |
| var found = false; |
| for (var i = 0; i < this.options.length; i++) { |
| if (this.options[i].value == value) { |
| this.selectedIndex = i; |
| found = true; |
| } |
| } |
| |
| // Item not found, select first item. |
| if (!found) |
| this.selectedIndex = 0; |
| |
| // The "onchange" event automatically fires when the user makes a manual |
| // change. It should never be fired for a programmatic change. However, |
| // these two lines were here already and it is hard to tell who may be |
| // relying on them. |
| if (this.onchange) |
| this.onchange(event); |
| }, |
| }; |
| |
| ///////////////////////////////////////////////////////////////////////////// |
| // PrefTextField class: |
| |
| // Define a constructor that uses an input element as its underlying element. |
| var PrefTextField = cr.ui.define('input'); |
| |
| PrefTextField.prototype = { |
| // Set up the prototype chain |
| __proto__: PrefInputElement.prototype, |
| |
| /** |
| * Initialization function for the cr.ui framework. |
| */ |
| decorate: function() { |
| PrefInputElement.prototype.decorate.call(this); |
| var self = this; |
| |
| // Listen for user events. |
| window.addEventListener('unload', function() { |
| if (document.activeElement == self) |
| self.blur(); |
| }); |
| }, |
| |
| /** |
| * Update the associated pref when when the user inputs text. |
| * @private |
| */ |
| updatePrefFromState_: function(event) { |
| switch (this.dataType) { |
| case 'number': |
| Preferences.setIntegerPref(this.pref, this.value, |
| !this.dialogPref, this.metric); |
| break; |
| case 'double': |
| Preferences.setDoublePref(this.pref, this.value, |
| !this.dialogPref, this.metric); |
| break; |
| case 'url': |
| Preferences.setURLPref(this.pref, this.value, |
| !this.dialogPref, this.metric); |
| break; |
| default: |
| Preferences.setStringPref(this.pref, this.value, |
| !this.dialogPref, this.metric); |
| break; |
| } |
| }, |
| }; |
| |
| ///////////////////////////////////////////////////////////////////////////// |
| // PrefPortNumber class: |
| |
| // Define a constructor that uses an input element as its underlying element. |
| var PrefPortNumber = cr.ui.define('input'); |
| |
| PrefPortNumber.prototype = { |
| // Set up the prototype chain |
| __proto__: PrefTextField.prototype, |
| |
| /** |
| * Initialization function for the cr.ui framework. |
| */ |
| decorate: function() { |
| var self = this; |
| self.type = 'text'; |
| self.dataType = 'number'; |
| PrefTextField.prototype.decorate.call(this); |
| self.oninput = function() { |
| // Note that using <input type="number"> is insufficient to restrict |
| // the input as it allows negative numbers and does not limit the |
| // number of charactes typed even if a range is set. Furthermore, |
| // it sometimes produces strange repaint artifacts. |
| var filtered = self.value.replace(/[^0-9]/g, ''); |
| if (filtered != self.value) |
| self.value = filtered; |
| }; |
| } |
| }; |
| |
| ///////////////////////////////////////////////////////////////////////////// |
| // PrefButton class: |
| |
| // Define a constructor that uses a button element as its underlying element. |
| var PrefButton = cr.ui.define('button'); |
| |
| PrefButton.prototype = { |
| // Set up the prototype chain |
| __proto__: HTMLButtonElement.prototype, |
| |
| /** |
| * Initialization function for the cr.ui framework. |
| */ |
| decorate: function() { |
| var self = this; |
| |
| // Listen for pref changes. |
| // This element behaves like a normal button and does not affect the |
| // underlying preference; it just becomes disabled when the preference is |
| // managed, and its value is false. This is useful for buttons that should |
| // be disabled when the underlying Boolean preference is set to false by a |
| // policy or extension. |
| Preferences.getInstance().addEventListener(this.pref, function(event) { |
| updateDisabledState_(self, 'notUserModifiable', |
| event.value.disabled && !event.value.value); |
| self.controlledBy = event.value.controlledBy; |
| }); |
| }, |
| |
| /** |
| * See |updateDisabledState_| above. |
| */ |
| setDisabled: function(reason, disabled) { |
| updateDisabledState_(this, reason, disabled); |
| }, |
| }; |
| |
| /** |
| * The name of the associated preference. |
| * @type {string} |
| */ |
| cr.defineProperty(PrefButton, 'pref', cr.PropertyKind.ATTR); |
| |
| /** |
| * Whether the associated preference is controlled by a source other than the |
| * user's setting (can be 'policy', 'extension', 'recommended' or unset). |
| * @type {string} |
| */ |
| cr.defineProperty(PrefButton, 'controlledBy', cr.PropertyKind.ATTR); |
| |
| // Export |
| return { |
| PrefCheckbox: PrefCheckbox, |
| PrefNumber: PrefNumber, |
| PrefRadio: PrefRadio, |
| PrefRange: PrefRange, |
| PrefSelect: PrefSelect, |
| PrefTextField: PrefTextField, |
| PrefPortNumber: PrefPortNumber, |
| PrefButton: PrefButton |
| }; |
| |
| }); |