// 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'; // Consider a checked dialog checkbox as a 'suggestion' which is committed // once the user confirms the dialog. if (this.dialogPref && this.checked) this.updatePrefFromState_(); }, /** * 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 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 }; });