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 * Handles changes to the pref. If a custom change handler does not suppress
85 * it, a default handler is invoked that update the input element's state.
86 * @param {Event} event Pref change event.
89 updateStateFromPref_: function(event) {
90 if (!this.customPrefChangeHandler(event))
91 this.value = event.value.value;
95 * See |updateDisabledState_| above.
97 setDisabled: function(reason, disabled) {
98 updateDisabledState_(this, reason, disabled);
102 * Custom change handler that is invoked first when the user makes changes
103 * to the input element's state. If it returns false, a default handler is
104 * invoked next that updates the associated pref. If it returns true, the
105 * default handler is suppressed (i.e., this works like stopPropagation or
107 * @param {Event} event Input element change event.
109 customChangeHandler: function(event) {
114 * Custom change handler that is invoked first when the preference
115 * associated with the input element changes. If it returns false, a default
116 * handler is invoked next that updates the input element. If it returns
117 * true, the default handler is suppressed.
118 * @param {Event} event Input element change event.
120 customPrefChangeHandler: function(event) {
126 * The name of the associated preference.
129 cr.defineProperty(PrefInputElement, 'pref', cr.PropertyKind.ATTR);
132 * The data type of the associated preference, only relevant for derived
133 * classes that support different data types.
136 cr.defineProperty(PrefInputElement, 'dataType', cr.PropertyKind.ATTR);
139 * Whether this input element is part of a dialog. If so, changes take effect
140 * in the settings UI immediately but are only actually committed when the
141 * user confirms the dialog. If the user cancels the dialog instead, the
142 * changes are rolled back in the settings UI and never committed.
145 cr.defineProperty(PrefInputElement, 'dialogPref', cr.PropertyKind.BOOL_ATTR);
148 * Whether the associated preference is controlled by a source other than the
149 * user's setting (can be 'policy', 'extension', 'recommended' or unset).
152 cr.defineProperty(PrefInputElement, 'controlledBy', cr.PropertyKind.ATTR);
155 * The user metric string.
158 cr.defineProperty(PrefInputElement, 'metric', cr.PropertyKind.ATTR);
160 /////////////////////////////////////////////////////////////////////////////
161 // PrefCheckbox class:
163 // Define a constructor that uses an input element as its underlying element.
164 var PrefCheckbox = cr.ui.define('input');
166 PrefCheckbox.prototype = {
167 // Set up the prototype chain
168 __proto__: PrefInputElement.prototype,
171 * Initialization function for the cr.ui framework.
173 decorate: function() {
174 PrefInputElement.prototype.decorate.call(this);
175 this.type = 'checkbox';
177 // Consider a checked dialog checkbox as a 'suggestion' which is committed
178 // once the user confirms the dialog.
179 if (this.dialogPref && this.checked)
180 this.updatePrefFromState_();
184 * Update the associated pref when when the user makes changes to the
188 updatePrefFromState_: function() {
189 var value = this.inverted_pref ? !this.checked : this.checked;
190 Preferences.setBooleanPref(this.pref, value,
191 !this.dialogPref, this.metric);
195 * Update the checkbox state when the associated pref changes.
196 * @param {Event} event Pref change event.
199 updateStateFromPref_: function(event) {
200 if (this.customPrefChangeHandler(event))
202 var value = Boolean(event.value.value);
203 this.checked = this.inverted_pref ? !value : value;
208 * Whether the mapping between checkbox state and associated pref is inverted.
211 cr.defineProperty(PrefCheckbox, 'inverted_pref', cr.PropertyKind.BOOL_ATTR);
213 /////////////////////////////////////////////////////////////////////////////
216 // Define a constructor that uses an input element as its underlying element.
217 var PrefNumber = cr.ui.define('input');
219 PrefNumber.prototype = {
220 // Set up the prototype chain
221 __proto__: PrefInputElement.prototype,
224 * Initialization function for the cr.ui framework.
226 decorate: function() {
227 PrefInputElement.prototype.decorate.call(this);
228 this.type = 'number';
232 * Update the associated pref when when the user inputs a number.
235 updatePrefFromState_: function() {
236 if (this.validity.valid) {
237 Preferences.setIntegerPref(this.pref, this.value,
238 !this.dialogPref, this.metric);
243 /////////////////////////////////////////////////////////////////////////////
246 //Define a constructor that uses an input element as its underlying element.
247 var PrefRadio = cr.ui.define('input');
249 PrefRadio.prototype = {
250 // Set up the prototype chain
251 __proto__: PrefInputElement.prototype,
254 * Initialization function for the cr.ui framework.
256 decorate: function() {
257 PrefInputElement.prototype.decorate.call(this);
262 * Update the associated pref when when the user selects the radio button.
265 updatePrefFromState_: function() {
266 if (this.value == 'true' || this.value == 'false') {
267 Preferences.setBooleanPref(this.pref,
268 this.value == String(this.checked),
269 !this.dialogPref, this.metric);
271 Preferences.setIntegerPref(this.pref, this.value,
272 !this.dialogPref, this.metric);
277 * Update the radio button state when the associated pref changes.
278 * @param {Event} event Pref change event.
281 updateStateFromPref_: function(event) {
282 if (!this.customPrefChangeHandler(event))
283 this.checked = this.value == String(event.value.value);
287 /////////////////////////////////////////////////////////////////////////////
290 // Define a constructor that uses an input element as its underlying element.
291 var PrefRange = cr.ui.define('input');
293 PrefRange.prototype = {
294 // Set up the prototype chain
295 __proto__: PrefInputElement.prototype,
298 * The map from slider position to corresponding pref value.
303 * Initialization function for the cr.ui framework.
305 decorate: function() {
306 PrefInputElement.prototype.decorate.call(this);
309 // Listen for user events.
310 // TODO(jhawkins): Add onmousewheel handling once the associated WK bug is
312 // https://bugs.webkit.org/show_bug.cgi?id=52256
313 this.addEventListener('keyup', this.handleRelease_.bind(this));
314 this.addEventListener('mouseup', this.handleRelease_.bind(this));
315 this.addEventListener('touchcancel', this.handleRelease_.bind(this));
316 this.addEventListener('touchend', this.handleRelease_.bind(this));
320 * Update the associated pref when when the user releases the slider.
323 updatePrefFromState_: function() {
324 Preferences.setIntegerPref(this.pref, this.mapPositionToPref(this.value),
325 !this.dialogPref, this.metric);
329 * Ignore changes to the slider position made by the user while the slider
330 * has not been released.
333 handleChange_: function() {
337 * Handle changes to the slider position made by the user when the slider is
338 * released. If a custom change handler does not suppress it, a default
339 * handler is invoked that updates the associated pref.
340 * @param {Event} event Change event.
343 handleRelease_: function(event) {
344 if (!this.customChangeHandler(event))
345 this.updatePrefFromState_();
349 * Handles changes to the pref associated with the slider. If a custom
350 * change handler does not suppress it, a default handler is invoked that
351 * updates the slider position.
352 * @param {Event} event Pref change event.
355 updateStateFromPref_: function(event) {
356 if (this.customPrefChangeHandler(event))
358 var value = event.value.value;
359 this.value = this.valueMap ? this.valueMap.indexOf(value) : value;
363 * Map slider position to the range of values provided by the client,
364 * represented by |valueMap|.
365 * @param {number} position The slider position to map.
367 mapPositionToPref: function(position) {
368 return this.valueMap ? this.valueMap[position] : position;
372 /////////////////////////////////////////////////////////////////////////////
375 // Define a constructor that uses a select element as its underlying element.
376 var PrefSelect = cr.ui.define('select');
378 PrefSelect.prototype = {
379 // Set up the prototype chain
380 __proto__: PrefInputElement.prototype,
383 * Update the associated pref when when the user selects an item.
386 updatePrefFromState_: function() {
387 var value = this.options[this.selectedIndex].value;
388 switch (this.dataType) {
390 Preferences.setIntegerPref(this.pref, value,
391 !this.dialogPref, this.metric);
394 Preferences.setDoublePref(this.pref, value,
395 !this.dialogPref, this.metric);
398 Preferences.setBooleanPref(this.pref, value == 'true',
399 !this.dialogPref, this.metric);
402 Preferences.setStringPref(this.pref, value,
403 !this.dialogPref, this.metric);
406 console.error('Unknown data type for <select> UI element: ' +
412 * Update the selected item when the associated pref changes.
413 * @param {Event} event Pref change event.
416 updateStateFromPref_: function(event) {
417 if (this.customPrefChangeHandler(event))
420 // Make sure the value is a string, because the value is stored as a
421 // string in the HTMLOptionElement.
422 value = String(event.value.value);
425 for (var i = 0; i < this.options.length; i++) {
426 if (this.options[i].value == value) {
427 this.selectedIndex = i;
432 // Item not found, select first item.
434 this.selectedIndex = 0;
436 // The "onchange" event automatically fires when the user makes a manual
437 // change. It should never be fired for a programmatic change. However,
438 // these two lines were here already and it is hard to tell who may be
441 this.onchange(event);
445 /////////////////////////////////////////////////////////////////////////////
446 // PrefTextField class:
448 // Define a constructor that uses an input element as its underlying element.
449 var PrefTextField = cr.ui.define('input');
451 PrefTextField.prototype = {
452 // Set up the prototype chain
453 __proto__: PrefInputElement.prototype,
456 * Initialization function for the cr.ui framework.
458 decorate: function() {
459 PrefInputElement.prototype.decorate.call(this);
462 // Listen for user events.
463 window.addEventListener('unload', function() {
464 if (document.activeElement == self)
470 * Update the associated pref when when the user inputs text.
473 updatePrefFromState_: function(event) {
474 switch (this.dataType) {
476 Preferences.setIntegerPref(this.pref, this.value,
477 !this.dialogPref, this.metric);
480 Preferences.setDoublePref(this.pref, this.value,
481 !this.dialogPref, this.metric);
484 Preferences.setURLPref(this.pref, this.value,
485 !this.dialogPref, this.metric);
488 Preferences.setStringPref(this.pref, this.value,
489 !this.dialogPref, this.metric);
495 /////////////////////////////////////////////////////////////////////////////
496 // PrefPortNumber class:
498 // Define a constructor that uses an input element as its underlying element.
499 var PrefPortNumber = cr.ui.define('input');
501 PrefPortNumber.prototype = {
502 // Set up the prototype chain
503 __proto__: PrefTextField.prototype,
506 * Initialization function for the cr.ui framework.
508 decorate: function() {
511 self.dataType = 'number';
512 PrefTextField.prototype.decorate.call(this);
513 self.oninput = function() {
514 // Note that using <input type="number"> is insufficient to restrict
515 // the input as it allows negative numbers and does not limit the
516 // number of charactes typed even if a range is set. Furthermore,
517 // it sometimes produces strange repaint artifacts.
518 var filtered = self.value.replace(/[^0-9]/g, '');
519 if (filtered != self.value)
520 self.value = filtered;
525 /////////////////////////////////////////////////////////////////////////////
528 // Define a constructor that uses a button element as its underlying element.
529 var PrefButton = cr.ui.define('button');
531 PrefButton.prototype = {
532 // Set up the prototype chain
533 __proto__: HTMLButtonElement.prototype,
536 * Initialization function for the cr.ui framework.
538 decorate: function() {
541 // Listen for pref changes.
542 // This element behaves like a normal button and does not affect the
543 // underlying preference; it just becomes disabled when the preference is
544 // managed, and its value is false. This is useful for buttons that should
545 // be disabled when the underlying Boolean preference is set to false by a
546 // policy or extension.
547 Preferences.getInstance().addEventListener(this.pref, function(event) {
548 updateDisabledState_(self, 'notUserModifiable',
549 event.value.disabled && !event.value.value);
550 self.controlledBy = event.value.controlledBy;
555 * See |updateDisabledState_| above.
557 setDisabled: function(reason, disabled) {
558 updateDisabledState_(this, reason, disabled);
563 * The name of the associated preference.
566 cr.defineProperty(PrefButton, 'pref', cr.PropertyKind.ATTR);
569 * Whether the associated preference is controlled by a source other than the
570 * user's setting (can be 'policy', 'extension', 'recommended' or unset).
573 cr.defineProperty(PrefButton, 'controlledBy', cr.PropertyKind.ATTR);
577 PrefCheckbox: PrefCheckbox,
578 PrefNumber: PrefNumber,
579 PrefRadio: PrefRadio,
580 PrefRange: PrefRange,
581 PrefSelect: PrefSelect,
582 PrefTextField: PrefTextField,
583 PrefPortNumber: PrefPortNumber,
584 PrefButton: PrefButton