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:
46 * Define a constructor that uses an input element as its underlying element.
48 * @extends {HTMLInputElement}
50 var PrefInputElement = cr.ui.define('input');
52 PrefInputElement.prototype = {
53 // Set up the prototype chain
54 __proto__: HTMLInputElement.prototype,
57 * Initialization function for the cr.ui framework.
59 decorate: function() {
62 // Listen for user events.
63 this.addEventListener('change', this.handleChange_.bind(this));
65 // Listen for pref changes.
66 Preferences.getInstance().addEventListener(this.pref, function(event) {
67 if (event.value.uncommitted && !self.dialogPref)
69 self.updateStateFromPref_(event);
70 updateDisabledState_(self, 'notUserModifiable', event.value.disabled);
71 self.controlledBy = event.value.controlledBy;
76 * Handle changes to the input element's state made by the user. If a custom
77 * change handler does not suppress it, a default handler is invoked that
78 * updates the associated pref.
79 * @param {Event} event Change event.
82 handleChange_: function(event) {
83 if (!this.customChangeHandler(event))
84 this.updatePrefFromState_();
88 * Handles changes to the pref. If a custom change handler does not suppress
89 * it, a default handler is invoked that update the input element's state.
90 * @param {Event} event Pref change event.
93 updateStateFromPref_: function(event) {
94 if (!this.customPrefChangeHandler(event))
95 this.value = event.value.value;
99 * See |updateDisabledState_| above.
101 setDisabled: function(reason, disabled) {
102 updateDisabledState_(this, reason, disabled);
106 * Custom change handler that is invoked first when the user makes changes
107 * to the input element's state. If it returns false, a default handler is
108 * invoked next that updates the associated pref. If it returns true, the
109 * default handler is suppressed (i.e., this works like stopPropagation or
111 * @param {Event} event Input element change event.
113 customChangeHandler: function(event) {
118 * Custom change handler that is invoked first when the preference
119 * associated with the input element changes. If it returns false, a default
120 * handler is invoked next that updates the input element. If it returns
121 * true, the default handler is suppressed.
122 * @param {Event} event Input element change event.
124 customPrefChangeHandler: function(event) {
130 * The name of the associated preference.
132 cr.defineProperty(PrefInputElement, 'pref', cr.PropertyKind.ATTR);
135 * The data type of the associated preference, only relevant for derived
136 * classes that support different data types.
138 cr.defineProperty(PrefInputElement, 'dataType', cr.PropertyKind.ATTR);
141 * Whether this input element is part of a dialog. If so, changes take effect
142 * in the settings UI immediately but are only actually committed when the
143 * user confirms the dialog. If the user cancels the dialog instead, the
144 * changes are rolled back in the settings UI and never committed.
146 cr.defineProperty(PrefInputElement, 'dialogPref', cr.PropertyKind.BOOL_ATTR);
149 * Whether the associated preference is controlled by a source other than the
150 * user's setting (can be 'policy', 'extension', 'recommended' or unset).
152 cr.defineProperty(PrefInputElement, 'controlledBy', cr.PropertyKind.ATTR);
155 * The user metric string.
157 cr.defineProperty(PrefInputElement, 'metric', cr.PropertyKind.ATTR);
159 /////////////////////////////////////////////////////////////////////////////
160 // PrefCheckbox class:
163 * Define a constructor that uses an input element as its underlying element.
165 * @extends {options.PrefInputElement}
167 var PrefCheckbox = cr.ui.define('input');
169 PrefCheckbox.prototype = {
170 // Set up the prototype chain
171 __proto__: PrefInputElement.prototype,
174 * Initialization function for the cr.ui framework.
176 decorate: function() {
177 PrefInputElement.prototype.decorate.call(this);
178 this.type = 'checkbox';
180 // Consider a checked dialog checkbox as a 'suggestion' which is committed
181 // once the user confirms the dialog.
182 if (this.dialogPref && this.checked)
183 this.updatePrefFromState_();
187 * Update the associated pref when when the user makes changes to the
191 updatePrefFromState_: function() {
192 var value = this.inverted_pref ? !this.checked : this.checked;
193 Preferences.setBooleanPref(this.pref, value,
194 !this.dialogPref, this.metric);
198 * Update the checkbox state when the associated pref changes.
199 * @param {Event} event Pref change event.
202 updateStateFromPref_: function(event) {
203 if (this.customPrefChangeHandler(event))
205 var value = Boolean(event.value.value);
206 this.checked = this.inverted_pref ? !value : value;
211 * Whether the mapping between checkbox state and associated pref is inverted.
213 cr.defineProperty(PrefCheckbox, 'inverted_pref', cr.PropertyKind.BOOL_ATTR);
215 /////////////////////////////////////////////////////////////////////////////
218 // Define a constructor that uses an input element as its underlying element.
219 var PrefNumber = cr.ui.define('input');
221 PrefNumber.prototype = {
222 // Set up the prototype chain
223 __proto__: PrefInputElement.prototype,
226 * Initialization function for the cr.ui framework.
228 decorate: function() {
229 PrefInputElement.prototype.decorate.call(this);
230 this.type = 'number';
234 * Update the associated pref when when the user inputs a number.
237 updatePrefFromState_: function() {
238 if (this.validity.valid) {
239 Preferences.setIntegerPref(this.pref, this.value,
240 !this.dialogPref, this.metric);
245 /////////////////////////////////////////////////////////////////////////////
248 //Define a constructor that uses an input element as its underlying element.
249 var PrefRadio = cr.ui.define('input');
251 PrefRadio.prototype = {
252 // Set up the prototype chain
253 __proto__: PrefInputElement.prototype,
256 * Initialization function for the cr.ui framework.
258 decorate: function() {
259 PrefInputElement.prototype.decorate.call(this);
264 * Update the associated pref when when the user selects the radio button.
267 updatePrefFromState_: function() {
268 if (this.value == 'true' || this.value == 'false') {
269 Preferences.setBooleanPref(this.pref,
270 this.value == String(this.checked),
271 !this.dialogPref, this.metric);
273 Preferences.setIntegerPref(this.pref, this.value,
274 !this.dialogPref, this.metric);
279 * Update the radio button state when the associated pref changes.
280 * @param {Event} event Pref change event.
283 updateStateFromPref_: function(event) {
284 if (!this.customPrefChangeHandler(event))
285 this.checked = this.value == String(event.value.value);
289 /////////////////////////////////////////////////////////////////////////////
293 * Define a constructor that uses an input element as its underlying element.
295 * @extends {options.PrefInputElement}
297 var PrefRange = cr.ui.define('input');
299 PrefRange.prototype = {
300 // Set up the prototype chain
301 __proto__: PrefInputElement.prototype,
304 * The map from slider position to corresponding pref value.
309 * Initialization function for the cr.ui framework.
311 decorate: function() {
312 PrefInputElement.prototype.decorate.call(this);
315 // Listen for user events.
316 // TODO(jhawkins): Add onmousewheel handling once the associated WK bug is
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));
326 * Update the associated pref when when the user releases the slider.
329 updatePrefFromState_: function() {
330 Preferences.setIntegerPref(
332 this.mapPositionToPref(parseInt(this.value, 10)),
338 * Ignore changes to the slider position made by the user while the slider
339 * has not been released.
342 handleChange_: function() {
346 * Handle changes to the slider position made by the user when the slider is
347 * released. If a custom change handler does not suppress it, a default
348 * handler is invoked that updates the associated pref.
349 * @param {Event} event Change event.
352 handleRelease_: function(event) {
353 if (!this.customChangeHandler(event))
354 this.updatePrefFromState_();
358 * Handles changes to the pref associated with the slider. If a custom
359 * change handler does not suppress it, a default handler is invoked that
360 * updates the slider position.
361 * @param {Event} event Pref change event.
364 updateStateFromPref_: function(event) {
365 if (this.customPrefChangeHandler(event))
367 var value = event.value.value;
368 this.value = this.valueMap ? this.valueMap.indexOf(value) : value;
372 * Map slider position to the range of values provided by the client,
373 * represented by |valueMap|.
374 * @param {number} position The slider position to map.
376 mapPositionToPref: function(position) {
377 return this.valueMap ? this.valueMap[position] : position;
381 /////////////////////////////////////////////////////////////////////////////
384 // Define a constructor that uses a select element as its underlying element.
385 var PrefSelect = cr.ui.define('select');
387 PrefSelect.prototype = {
388 // Set up the prototype chain
389 __proto__: PrefInputElement.prototype,
392 * Update the associated pref when when the user selects an item.
395 updatePrefFromState_: function() {
396 var value = this.options[this.selectedIndex].value;
397 switch (this.dataType) {
399 Preferences.setIntegerPref(this.pref, value,
400 !this.dialogPref, this.metric);
403 Preferences.setDoublePref(this.pref, value,
404 !this.dialogPref, this.metric);
407 Preferences.setBooleanPref(this.pref, value == 'true',
408 !this.dialogPref, this.metric);
411 Preferences.setStringPref(this.pref, value,
412 !this.dialogPref, this.metric);
415 console.error('Unknown data type for <select> UI element: ' +
421 * Update the selected item when the associated pref changes.
422 * @param {Event} event Pref change event.
425 updateStateFromPref_: function(event) {
426 if (this.customPrefChangeHandler(event))
429 // Make sure the value is a string, because the value is stored as a
430 // string in the HTMLOptionElement.
431 var value = String(event.value.value);
434 for (var i = 0; i < this.options.length; i++) {
435 if (this.options[i].value == value) {
436 this.selectedIndex = i;
441 // Item not found, select first item.
443 this.selectedIndex = 0;
445 // The "onchange" event automatically fires when the user makes a manual
446 // change. It should never be fired for a programmatic change. However,
447 // these two lines were here already and it is hard to tell who may be
450 this.onchange(event);
454 /////////////////////////////////////////////////////////////////////////////
455 // PrefTextField class:
457 // Define a constructor that uses an input element as its underlying element.
458 var PrefTextField = cr.ui.define('input');
460 PrefTextField.prototype = {
461 // Set up the prototype chain
462 __proto__: PrefInputElement.prototype,
465 * Initialization function for the cr.ui framework.
467 decorate: function() {
468 PrefInputElement.prototype.decorate.call(this);
471 // Listen for user events.
472 window.addEventListener('unload', function() {
473 if (document.activeElement == self)
479 * Update the associated pref when when the user inputs text.
482 updatePrefFromState_: function(event) {
483 switch (this.dataType) {
485 Preferences.setIntegerPref(this.pref, this.value,
486 !this.dialogPref, this.metric);
489 Preferences.setDoublePref(this.pref, this.value,
490 !this.dialogPref, this.metric);
493 Preferences.setURLPref(this.pref, this.value,
494 !this.dialogPref, this.metric);
497 Preferences.setStringPref(this.pref, this.value,
498 !this.dialogPref, this.metric);
504 /////////////////////////////////////////////////////////////////////////////
505 // PrefPortNumber class:
507 // Define a constructor that uses an input element as its underlying element.
508 var PrefPortNumber = cr.ui.define('input');
510 PrefPortNumber.prototype = {
511 // Set up the prototype chain
512 __proto__: PrefTextField.prototype,
515 * Initialization function for the cr.ui framework.
517 decorate: function() {
520 self.dataType = 'number';
521 PrefTextField.prototype.decorate.call(this);
522 self.oninput = function() {
523 // Note that using <input type="number"> is insufficient to restrict
524 // the input as it allows negative numbers and does not limit the
525 // number of charactes typed even if a range is set. Furthermore,
526 // it sometimes produces strange repaint artifacts.
527 var filtered = self.value.replace(/[^0-9]/g, '');
528 if (filtered != self.value)
529 self.value = filtered;
534 /////////////////////////////////////////////////////////////////////////////
537 // Define a constructor that uses a button element as its underlying element.
538 var PrefButton = cr.ui.define('button');
540 PrefButton.prototype = {
541 // Set up the prototype chain
542 __proto__: HTMLButtonElement.prototype,
545 * Initialization function for the cr.ui framework.
547 decorate: function() {
550 // Listen for pref changes.
551 // This element behaves like a normal button and does not affect the
552 // underlying preference; it just becomes disabled when the preference is
553 // managed, and its value is false. This is useful for buttons that should
554 // be disabled when the underlying Boolean preference is set to false by a
555 // policy or extension.
556 Preferences.getInstance().addEventListener(this.pref, function(event) {
557 updateDisabledState_(self, 'notUserModifiable',
558 event.value.disabled && !event.value.value);
559 self.controlledBy = event.value.controlledBy;
564 * See |updateDisabledState_| above.
566 setDisabled: function(reason, disabled) {
567 updateDisabledState_(this, reason, disabled);
572 * The name of the associated preference.
574 cr.defineProperty(PrefButton, 'pref', cr.PropertyKind.ATTR);
577 * Whether the associated preference is controlled by a source other than the
578 * user's setting (can be 'policy', 'extension', 'recommended' or unset).
580 cr.defineProperty(PrefButton, 'controlledBy', cr.PropertyKind.ATTR);
584 PrefCheckbox: PrefCheckbox,
585 PrefInputElement: PrefInputElement,
586 PrefNumber: PrefNumber,
587 PrefRadio: PrefRadio,
588 PrefRange: PrefRange,
589 PrefSelect: PrefSelect,
590 PrefTextField: PrefTextField,
591 PrefPortNumber: PrefPortNumber,
592 PrefButton: PrefButton