No dual_mode on Win10+ shortcuts.
[chromium-blink-merge.git] / chrome / browser / resources / options / pref_ui.js
blobc0776fee447b9fc043dab46e16d958609c550cf1
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() {
6 var Preferences = options.Preferences;
8 /**
9 * Allows an element to be disabled for several reasons.
10 * The element is disabled if at least one reason is true, and the reasons
11 * can be set separately.
12 * @private
13 * @param {!HTMLElement} el The element to update.
14 * @param {string} reason The reason for disabling the element.
15 * @param {boolean} disabled Whether the element should be disabled or enabled
16 * for the given |reason|.
18 function updateDisabledState(el, reason, disabled) {
19 if (!el.disabledReasons)
20 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 (!el.disabled) {
29 // If the element is not disabled, there should be no reason, except for
30 // 'other'.
31 delete el.disabledReasons.other;
32 if (Object.keys(el.disabledReasons).length > 0)
33 console.error('Element is not disabled but should be');
36 if (disabled)
37 el.disabledReasons[reason] = true;
38 else
39 delete el.disabledReasons[reason];
41 el.disabled = Object.keys(el.disabledReasons).length > 0;
44 /////////////////////////////////////////////////////////////////////////////
45 // PrefInputElement class:
47 /**
48 * Define a constructor that uses an input element as its underlying element.
49 * @constructor
50 * @extends {HTMLInputElement}
52 var PrefInputElement = cr.ui.define('input');
54 PrefInputElement.prototype = {
55 // Set up the prototype chain
56 __proto__: HTMLInputElement.prototype,
58 /**
59 * Initialization function for the cr.ui framework.
61 decorate: function() {
62 var self = this;
64 // Listen for user events.
65 this.addEventListener('change', this.handleChange.bind(this));
67 // Listen for pref changes.
68 Preferences.getInstance().addEventListener(this.pref, function(event) {
69 if (event.value.uncommitted && !self.dialogPref)
70 return;
71 self.updateStateFromPref(event);
72 updateDisabledState(self, 'notUserModifiable', event.value.disabled);
73 self.controlledBy = event.value.controlledBy;
74 });
77 /**
78 * Handle changes to the input element's state made by the user. If a custom
79 * change handler does not suppress it, a default handler is invoked that
80 * updates the associated pref.
81 * @param {Event} event Change event.
82 * @protected
84 handleChange: function(event) {
85 if (!this.customChangeHandler(event))
86 this.updatePrefFromState();
89 /**
90 * Handles changes to the pref. If a custom change handler does not suppress
91 * it, a default handler is invoked that updates the input element's state.
92 * @param {Event} event Pref change event.
93 * @protected
95 updateStateFromPref: function(event) {
96 if (!this.customPrefChangeHandler(event))
97 this.value = event.value.value;
101 * An abstract method for all subclasses to override to update their
102 * preference from existing state.
103 * @protected
105 updatePrefFromState: assertNotReached,
108 * See |updateDisabledState| above.
110 setDisabled: function(reason, disabled) {
111 updateDisabledState(this, reason, disabled);
115 * Custom change handler that is invoked first when the user makes changes
116 * to the input element's state. If it returns false, a default handler is
117 * invoked next that updates the associated pref. If it returns true, the
118 * default handler is suppressed (i.e., this works like stopPropagation or
119 * cancelBubble).
120 * @param {Event} event Input element change event.
122 customChangeHandler: function(event) {
123 return false;
127 * Custom change handler that is invoked first when the preference
128 * associated with the input element changes. If it returns false, a default
129 * handler is invoked next that updates the input element. If it returns
130 * true, the default handler is suppressed.
131 * @param {Event} event Input element change event.
133 customPrefChangeHandler: function(event) {
134 return false;
139 * The name of the associated preference.
141 cr.defineProperty(PrefInputElement, 'pref', cr.PropertyKind.ATTR);
144 * The data type of the associated preference, only relevant for derived
145 * classes that support different data types.
147 cr.defineProperty(PrefInputElement, 'dataType', cr.PropertyKind.ATTR);
150 * Whether this input element is part of a dialog. If so, changes take effect
151 * in the settings UI immediately but are only actually committed when the
152 * user confirms the dialog. If the user cancels the dialog instead, the
153 * changes are rolled back in the settings UI and never committed.
155 cr.defineProperty(PrefInputElement, 'dialogPref', cr.PropertyKind.BOOL_ATTR);
158 * Whether the associated preference is controlled by a source other than the
159 * user's setting (can be 'policy', 'extension', 'recommended' or unset).
161 cr.defineProperty(PrefInputElement, 'controlledBy', cr.PropertyKind.ATTR);
164 * The user metric string.
166 cr.defineProperty(PrefInputElement, 'metric', cr.PropertyKind.ATTR);
168 /////////////////////////////////////////////////////////////////////////////
169 // PrefCheckbox class:
172 * Define a constructor that uses an input element as its underlying element.
173 * @constructor
174 * @extends {options.PrefInputElement}
176 var PrefCheckbox = cr.ui.define('input');
178 PrefCheckbox.prototype = {
179 // Set up the prototype chain
180 __proto__: PrefInputElement.prototype,
183 * Initialization function for the cr.ui framework.
185 decorate: function() {
186 PrefInputElement.prototype.decorate.call(this);
187 this.type = 'checkbox';
189 // Consider a checked dialog checkbox as a 'suggestion' which is committed
190 // once the user confirms the dialog.
191 if (this.dialogPref && this.checked)
192 this.updatePrefFromState();
196 * Update the associated pref when when the user makes changes to the
197 * checkbox state.
198 * @override
200 updatePrefFromState: function() {
201 var value = this.inverted_pref ? !this.checked : this.checked;
202 Preferences.setBooleanPref(this.pref, value,
203 !this.dialogPref, this.metric);
206 /** @override */
207 updateStateFromPref: function(event) {
208 if (!this.customPrefChangeHandler(event))
209 this.defaultPrefChangeHandler(event);
213 * @param {Event} event A pref change event.
215 defaultPrefChangeHandler: function(event) {
216 var value = Boolean(event.value.value);
217 this.checked = this.inverted_pref ? !value : value;
222 * Whether the mapping between checkbox state and associated pref is inverted.
224 cr.defineProperty(PrefCheckbox, 'inverted_pref', cr.PropertyKind.BOOL_ATTR);
226 /////////////////////////////////////////////////////////////////////////////
227 // PrefNumber class:
229 // Define a constructor that uses an input element as its underlying element.
230 var PrefNumber = cr.ui.define('input');
232 PrefNumber.prototype = {
233 // Set up the prototype chain
234 __proto__: PrefInputElement.prototype,
237 * Initialization function for the cr.ui framework.
239 decorate: function() {
240 PrefInputElement.prototype.decorate.call(this);
241 this.type = 'number';
245 * Update the associated pref when the user inputs a number.
246 * @override
248 updatePrefFromState: function() {
249 if (this.validity.valid) {
250 Preferences.setIntegerPref(this.pref, this.value,
251 !this.dialogPref, this.metric);
256 /////////////////////////////////////////////////////////////////////////////
257 // PrefRadio class:
259 //Define a constructor that uses an input element as its underlying element.
260 var PrefRadio = cr.ui.define('input');
262 PrefRadio.prototype = {
263 // Set up the prototype chain
264 __proto__: PrefInputElement.prototype,
267 * Initialization function for the cr.ui framework.
269 decorate: function() {
270 PrefInputElement.prototype.decorate.call(this);
271 this.type = 'radio';
275 * Update the associated pref when when the user selects the radio button.
276 * @override
278 updatePrefFromState: function() {
279 if (this.value == 'true' || this.value == 'false') {
280 Preferences.setBooleanPref(this.pref,
281 this.value == String(this.checked),
282 !this.dialogPref, this.metric);
283 } else {
284 Preferences.setIntegerPref(this.pref, this.value,
285 !this.dialogPref, this.metric);
289 /** @override */
290 updateStateFromPref: function(event) {
291 if (!this.customPrefChangeHandler(event))
292 this.checked = this.value == String(event.value.value);
296 /////////////////////////////////////////////////////////////////////////////
297 // PrefRange class:
300 * Define a constructor that uses an input element as its underlying element.
301 * @constructor
302 * @extends {options.PrefInputElement}
304 var PrefRange = cr.ui.define('input');
306 PrefRange.prototype = {
307 // Set up the prototype chain
308 __proto__: PrefInputElement.prototype,
311 * The map from slider position to corresponding pref value.
313 valueMap: undefined,
316 * Initialization function for the cr.ui framework.
318 decorate: function() {
319 PrefInputElement.prototype.decorate.call(this);
320 this.type = 'range';
322 // Listen for user events.
323 // TODO(jhawkins): Add onmousewheel handling once the associated WK bug is
324 // fixed.
325 // https://bugs.webkit.org/show_bug.cgi?id=52256
326 this.addEventListener('keyup', this.handleRelease_.bind(this));
327 this.addEventListener('mouseup', this.handleRelease_.bind(this));
328 this.addEventListener('touchcancel', this.handleRelease_.bind(this));
329 this.addEventListener('touchend', this.handleRelease_.bind(this));
333 * Update the associated pref when when the user releases the slider.
334 * @override
336 updatePrefFromState: function() {
337 Preferences.setIntegerPref(
338 this.pref,
339 this.mapPositionToPref(parseInt(this.value, 10)),
340 !this.dialogPref,
341 this.metric);
344 /** @override */
345 handleChange: function() {
346 // Ignore changes to the slider position made by the user while the slider
347 // has not been released.
351 * Handle changes to the slider position made by the user when the slider is
352 * released. If a custom change handler does not suppress it, a default
353 * handler is invoked that updates the associated pref.
354 * @param {Event} event Change event.
355 * @private
357 handleRelease_: function(event) {
358 if (!this.customChangeHandler(event))
359 this.updatePrefFromState();
363 * Handles changes to the pref associated with the slider. If a custom
364 * change handler does not suppress it, a default handler is invoked that
365 * updates the slider position.
366 * @override.
368 updateStateFromPref: function(event) {
369 if (this.customPrefChangeHandler(event))
370 return;
371 var value = event.value.value;
372 this.value = this.valueMap ? this.valueMap.indexOf(value) : value;
376 * Map slider position to the range of values provided by the client,
377 * represented by |valueMap|.
378 * @param {number} position The slider position to map.
380 mapPositionToPref: function(position) {
381 return this.valueMap ? this.valueMap[position] : position;
385 /////////////////////////////////////////////////////////////////////////////
386 // PrefSelect class:
388 // Define a constructor that uses a select element as its underlying element.
389 var PrefSelect = cr.ui.define('select');
391 PrefSelect.prototype = {
392 // Set up the prototype chain
393 __proto__: HTMLSelectElement.prototype,
395 /** @override */
396 decorate: PrefInputElement.prototype.decorate,
398 /** @override */
399 handleChange: PrefInputElement.prototype.handleChange,
402 * Update the associated pref when when the user selects an item.
403 * @override
405 updatePrefFromState: function() {
406 var value = this.options[this.selectedIndex].value;
407 switch (this.dataType) {
408 case 'number':
409 Preferences.setIntegerPref(this.pref, value,
410 !this.dialogPref, this.metric);
411 break;
412 case 'double':
413 Preferences.setDoublePref(this.pref, value,
414 !this.dialogPref, this.metric);
415 break;
416 case 'boolean':
417 Preferences.setBooleanPref(this.pref, value == 'true',
418 !this.dialogPref, this.metric);
419 break;
420 case 'string':
421 Preferences.setStringPref(this.pref, value,
422 !this.dialogPref, this.metric);
423 break;
424 default:
425 console.error('Unknown data type for <select> UI element: ' +
426 this.dataType);
430 /** @override */
431 updateStateFromPref: function(event) {
432 if (this.customPrefChangeHandler(event))
433 return;
435 // Make sure the value is a string, because the value is stored as a
436 // string in the HTMLOptionElement.
437 var value = String(event.value.value);
439 var found = false;
440 for (var i = 0; i < this.options.length; i++) {
441 if (this.options[i].value == value) {
442 this.selectedIndex = i;
443 found = true;
447 // Item not found, select first item.
448 if (!found)
449 this.selectedIndex = 0;
451 // The "onchange" event automatically fires when the user makes a manual
452 // change. It should never be fired for a programmatic change. However,
453 // these two lines were here already and it is hard to tell who may be
454 // relying on them.
455 if (this.onchange)
456 this.onchange(event);
459 /** @override */
460 setDisabled: PrefInputElement.prototype.setDisabled,
462 /** @override */
463 customChangeHandler: PrefInputElement.prototype.customChangeHandler,
465 /** @override */
466 customPrefChangeHandler: PrefInputElement.prototype.customPrefChangeHandler,
470 * The name of the associated preference.
472 cr.defineProperty(PrefSelect, 'pref', cr.PropertyKind.ATTR);
475 * The data type of the associated preference, only relevant for derived
476 * classes that support different data types.
478 cr.defineProperty(PrefSelect, 'dataType', cr.PropertyKind.ATTR);
481 * Whether this input element is part of a dialog. If so, changes take effect
482 * in the settings UI immediately but are only actually committed when the
483 * user confirms the dialog. If the user cancels the dialog instead, the
484 * changes are rolled back in the settings UI and never committed.
486 cr.defineProperty(PrefSelect, 'dialogPref', cr.PropertyKind.BOOL_ATTR);
489 * Whether the associated preference is controlled by a source other than the
490 * user's setting (can be 'policy', 'extension', 'recommended' or unset).
492 cr.defineProperty(PrefSelect, 'controlledBy', cr.PropertyKind.ATTR);
495 * The user metric string.
497 cr.defineProperty(PrefSelect, 'metric', cr.PropertyKind.ATTR);
499 /////////////////////////////////////////////////////////////////////////////
500 // PrefTextField class:
502 // Define a constructor that uses an input element as its underlying element.
503 var PrefTextField = cr.ui.define('input');
505 PrefTextField.prototype = {
506 // Set up the prototype chain
507 __proto__: PrefInputElement.prototype,
510 * Initialization function for the cr.ui framework.
512 decorate: function() {
513 PrefInputElement.prototype.decorate.call(this);
514 var self = this;
516 // Listen for user events.
517 window.addEventListener('unload', function() {
518 if (document.activeElement == self)
519 self.blur();
524 * Update the associated pref when when the user inputs text.
525 * @override
527 updatePrefFromState: function(event) {
528 switch (this.dataType) {
529 case 'number':
530 Preferences.setIntegerPref(this.pref, this.value,
531 !this.dialogPref, this.metric);
532 break;
533 case 'double':
534 Preferences.setDoublePref(this.pref, this.value,
535 !this.dialogPref, this.metric);
536 break;
537 case 'url':
538 Preferences.setURLPref(this.pref, this.value,
539 !this.dialogPref, this.metric);
540 break;
541 default:
542 Preferences.setStringPref(this.pref, this.value,
543 !this.dialogPref, this.metric);
544 break;
549 /////////////////////////////////////////////////////////////////////////////
550 // PrefPortNumber class:
552 // Define a constructor that uses an input element as its underlying element.
553 var PrefPortNumber = cr.ui.define('input');
555 PrefPortNumber.prototype = {
556 // Set up the prototype chain
557 __proto__: PrefTextField.prototype,
560 * Initialization function for the cr.ui framework.
562 decorate: function() {
563 var self = this;
564 self.type = 'text';
565 self.dataType = 'number';
566 PrefTextField.prototype.decorate.call(this);
567 self.oninput = function() {
568 // Note that using <input type="number"> is insufficient to restrict
569 // the input as it allows negative numbers and does not limit the
570 // number of charactes typed even if a range is set. Furthermore,
571 // it sometimes produces strange repaint artifacts.
572 var filtered = self.value.replace(/[^0-9]/g, '');
573 if (filtered != self.value)
574 self.value = filtered;
579 /////////////////////////////////////////////////////////////////////////////
580 // PrefButton class:
582 // Define a constructor that uses a button element as its underlying element.
583 var PrefButton = cr.ui.define('button');
585 PrefButton.prototype = {
586 // Set up the prototype chain
587 __proto__: HTMLButtonElement.prototype,
590 * Initialization function for the cr.ui framework.
592 decorate: function() {
593 var self = this;
595 // Listen for pref changes.
596 // This element behaves like a normal button and does not affect the
597 // underlying preference; it just becomes disabled when the preference is
598 // managed, and its value is false. This is useful for buttons that should
599 // be disabled when the underlying Boolean preference is set to false by a
600 // policy or extension.
601 Preferences.getInstance().addEventListener(this.pref, function(event) {
602 updateDisabledState(self, 'notUserModifiable',
603 event.value.disabled && !event.value.value);
604 self.controlledBy = event.value.controlledBy;
609 * See |updateDisabledState| above.
611 setDisabled: function(reason, disabled) {
612 updateDisabledState(this, reason, disabled);
617 * The name of the associated preference.
619 cr.defineProperty(PrefButton, 'pref', cr.PropertyKind.ATTR);
622 * Whether the associated preference is controlled by a source other than the
623 * user's setting (can be 'policy', 'extension', 'recommended' or unset).
625 cr.defineProperty(PrefButton, 'controlledBy', cr.PropertyKind.ATTR);
627 // Export
628 return {
629 PrefCheckbox: PrefCheckbox,
630 PrefInputElement: PrefInputElement,
631 PrefNumber: PrefNumber,
632 PrefRadio: PrefRadio,
633 PrefRange: PrefRange,
634 PrefSelect: PrefSelect,
635 PrefTextField: PrefTextField,
636 PrefPortNumber: PrefPortNumber,
637 PrefButton: PrefButton