1 // Copyright (c) 2011 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. 4 5 cr.define('options', function() { 6 7 var Preferences = options.Preferences; 8 9 /** 10 * Helper function update element's state from pref change event. 11 * @private 12 * @param {!HTMLElement} el The element to update. 13 * @param {!Event} event The pref change event. 14 */ 15 function updateElementState_(el, event) { 16 el.managed = event.value && event.value['managed'] != undefined ? 17 event.value['managed'] : false; 18 19 // Managed UI elements can only be disabled as a result of being 20 // managed. They cannot be enabled as a result of a pref being 21 // unmanaged. 22 if (el.managed) 23 el.disabled = true; 24 25 // Disable UI elements if backend says so. 26 if (!el.disabled && event.value && event.value['disabled']) 27 el.disabled = true; 28 } 29 30 ///////////////////////////////////////////////////////////////////////////// 31 // PrefCheckbox class: 32 // TODO(jhawkins): Refactor all this copy-pasted code! 33 34 // Define a constructor that uses an input element as its underlying element. 35 var PrefCheckbox = cr.ui.define('input'); 36 37 PrefCheckbox.prototype = { 38 // Set up the prototype chain 39 __proto__: HTMLInputElement.prototype, 40 41 /** 42 * Initialization function for the cr.ui framework. 43 */ 44 decorate: function() { 45 this.type = 'checkbox'; 46 var self = this; 47 48 self.initializeValueType(self.getAttribute('value-type')); 49 50 // Listen to pref changes. 51 Preferences.getInstance().addEventListener( 52 this.pref, 53 function(event) { 54 var value = event.value && event.value['value'] != undefined ? 55 event.value['value'] : event.value; 56 57 // Invert pref value if inverted_pref == true. 58 if (self.inverted_pref) 59 self.checked = !Boolean(value); 60 else 61 self.checked = Boolean(value); 62 63 updateElementState_(self, event); 64 }); 65 66 // Listen to user events. 67 this.addEventListener( 68 'change', 69 function(e) { 70 var value = self.inverted_pref ? !self.checked : self.checked; 71 switch(self.valueType) { 72 case 'number': 73 Preferences.setIntegerPref(self.pref, 74 Number(value), self.metric); 75 break; 76 case 'boolean': 77 Preferences.setBooleanPref(self.pref, 78 value, self.metric); 79 break; 80 } 81 }); 82 }, 83 84 /** 85 * Sets up options in checkbox element. 86 * @param {String} valueType The preference type for this checkbox. 87 */ 88 initializeValueType: function(valueType) { 89 this.valueType = valueType || 'boolean'; 90 }, 91 }; 92 93 /** 94 * The preference name. 95 * @type {string} 96 */ 97 cr.defineProperty(PrefCheckbox, 'pref', cr.PropertyKind.ATTR); 98 99 /** 100 * The user metric string. 101 * @type {string} 102 */ 103 cr.defineProperty(PrefCheckbox, 'metric', cr.PropertyKind.ATTR); 104 105 /** 106 * Whether to use inverted pref value. 107 * @type {boolean} 108 */ 109 cr.defineProperty(PrefCheckbox, 'inverted_pref', cr.PropertyKind.BOOL_ATTR); 110 111 ///////////////////////////////////////////////////////////////////////////// 112 // PrefRadio class: 113 114 //Define a constructor that uses an input element as its underlying element. 115 var PrefRadio = cr.ui.define('input'); 116 117 PrefRadio.prototype = { 118 // Set up the prototype chain 119 __proto__: HTMLInputElement.prototype, 120 121 /** 122 * Initialization function for the cr.ui framework. 123 */ 124 decorate: function() { 125 this.type = 'radio'; 126 var self = this; 127 128 // Listen to pref changes. 129 Preferences.getInstance().addEventListener(this.pref, 130 function(event) { 131 var value = event.value && event.value['value'] != undefined ? 132 event.value['value'] : event.value; 133 self.checked = String(value) == self.value; 134 135 updateElementState_(self, event); 136 }); 137 138 // Listen to user events. 139 // Use the 'click' event instead of 'change', because of a bug in WebKit 140 // which prevents 'change' from being sent when the user changes selection 141 // using the keyboard. 142 // https://bugs.webkit.org/show_bug.cgi?id=32013 143 this.addEventListener('click', 144 function(e) { 145 if(self.value == 'true' || self.value == 'false') { 146 Preferences.setBooleanPref(self.pref, 147 self.value == 'true', self.metric); 148 } else { 149 Preferences.setIntegerPref(self.pref, 150 parseInt(self.value, 10), self.metric); 151 } 152 }); 153 }, 154 }; 155 156 /** 157 * The preference name. 158 * @type {string} 159 */ 160 cr.defineProperty(PrefRadio, 'pref', cr.PropertyKind.ATTR); 161 162 /** 163 * The user metric string. 164 * @type {string} 165 */ 166 cr.defineProperty(PrefRadio, 'metric', cr.PropertyKind.ATTR); 167 168 ///////////////////////////////////////////////////////////////////////////// 169 // PrefNumeric class: 170 171 // Define a constructor that uses an input element as its underlying element. 172 var PrefNumeric = function() {}; 173 PrefNumeric.prototype = { 174 // Set up the prototype chain 175 __proto__: HTMLInputElement.prototype, 176 177 /** 178 * Initialization function for the cr.ui framework. 179 */ 180 decorate: function() { 181 var self = this; 182 183 // Listen to pref changes. 184 Preferences.getInstance().addEventListener(this.pref, 185 function(event) { 186 self.value = event.value && event.value['value'] != undefined ? 187 event.value['value'] : event.value; 188 189 updateElementState_(self, event); 190 }); 191 192 // Listen to user events. 193 this.addEventListener('change', 194 function(e) { 195 if (this.validity.valid) { 196 Preferences.setIntegerPref(self.pref, self.value, self.metric); 197 } 198 }); 199 } 200 }; 201 202 /** 203 * The preference name. 204 * @type {string} 205 */ 206 cr.defineProperty(PrefNumeric, 'pref', cr.PropertyKind.ATTR); 207 208 /** 209 * The user metric string. 210 * @type {string} 211 */ 212 cr.defineProperty(PrefNumeric, 'metric', cr.PropertyKind.ATTR); 213 214 ///////////////////////////////////////////////////////////////////////////// 215 // PrefNumber class: 216 217 // Define a constructor that uses an input element as its underlying element. 218 var PrefNumber = cr.ui.define('input'); 219 220 PrefNumber.prototype = { 221 // Set up the prototype chain 222 __proto__: PrefNumeric.prototype, 223 224 /** 225 * Initialization function for the cr.ui framework. 226 */ 227 decorate: function() { 228 this.type = 'number'; 229 PrefNumeric.prototype.decorate.call(this); 230 231 // Listen to user events. 232 this.addEventListener('input', 233 function(e) { 234 if (this.validity.valid) { 235 Preferences.setIntegerPref(self.pref, self.value, self.metric); 236 } 237 }); 238 } 239 }; 240 241 ///////////////////////////////////////////////////////////////////////////// 242 // PrefRange class: 243 244 // Define a constructor that uses an input element as its underlying element. 245 var PrefRange = cr.ui.define('input'); 246 247 PrefRange.prototype = { 248 // Set up the prototype chain 249 __proto__: HTMLInputElement.prototype, 250 251 /** 252 * The map from input range value to the corresponding preference value. 253 */ 254 valueMap: undefined, 255 256 /** 257 * If true, the associated pref will be modified on each onchange event; 258 * otherwise, the pref will only be modified on the onmouseup event after 259 * the drag. 260 */ 261 continuous: true, 262 263 /** 264 * Initialization function for the cr.ui framework. 265 */ 266 decorate: function() { 267 this.type = 'range'; 268 269 // Update the UI when the pref changes. 270 Preferences.getInstance().addEventListener( 271 this.pref, this.onPrefChange_.bind(this)); 272 273 // Listen to user events. 274 // TODO(jhawkins): Add onmousewheel handling once the associated WK bug is 275 // fixed. 276 // https://bugs.webkit.org/show_bug.cgi?id=52256 277 this.onchange = this.onChange_.bind(this); 278 this.onkeyup = this.onmouseup = this.onInputUp_.bind(this); 279 }, 280 281 /** 282 * Event listener that updates the UI when the underlying pref changes. 283 * @param {Event} event The event that details the pref change. 284 * @private 285 */ 286 onPrefChange_: function(event) { 287 var value = event.value && event.value['value'] != undefined ? 288 event.value['value'] : event.value; 289 if (value != undefined) 290 this.value = this.valueMap ? this.valueMap.indexOf(value) : value; 291 }, 292 293 /** 294 * onchange handler that sets the pref when the user changes the value of 295 * the input element. 296 * @private 297 */ 298 onChange_: function(event) { 299 if (this.continuous) 300 this.setRangePref_(); 301 302 if (this.notifyChange) 303 this.notifyChange(this, this.mapValueToRange_(this.value)); 304 }, 305 306 /** 307 * Sets the integer value of |pref| to the value of this element. 308 * @private 309 */ 310 setRangePref_: function() { 311 Preferences.setIntegerPref( 312 this.pref, this.mapValueToRange_(this.value), this.metric); 313 314 if (this.notifyPrefChange) 315 this.notifyPrefChange(this, this.mapValueToRange_(this.value)); 316 }, 317 318 /** 319 * onkeyup/onmouseup handler that modifies the pref if |continuous| is 320 * false. 321 * @private 322 */ 323 onInputUp_: function(event) { 324 if (!this.continuous) 325 this.setRangePref_(); 326 }, 327 328 /** 329 * Maps the value of this element into the range provided by the client, 330 * represented by |valueMap|. 331 * @param {number} value The value to map. 332 * @private 333 */ 334 mapValueToRange_: function(value) { 335 return this.valueMap ? this.valueMap[value] : value; 336 }, 337 338 /** 339 * Called when the client has specified non-continuous mode and the value of 340 * the range control changes. 341 * @param {Element} el This element. 342 * @param {number} value The value of this element. 343 */ 344 notifyChange: function(el, value) { 345 }, 346 }; 347 348 /** 349 * The preference name. 350 * @type {string} 351 */ 352 cr.defineProperty(PrefRange, 'pref', cr.PropertyKind.ATTR); 353 354 /** 355 * The user metric string. 356 * @type {string} 357 */ 358 cr.defineProperty(PrefRange, 'metric', cr.PropertyKind.ATTR); 359 360 ///////////////////////////////////////////////////////////////////////////// 361 // PrefSelect class: 362 363 // Define a constructor that uses a select element as its underlying element. 364 var PrefSelect = cr.ui.define('select'); 365 366 PrefSelect.prototype = { 367 // Set up the prototype chain 368 __proto__: HTMLSelectElement.prototype, 369 370 /** 371 * Initialization function for the cr.ui framework. 372 */ 373 decorate: function() { 374 var self = this; 375 376 // Listen to pref changes. 377 Preferences.getInstance().addEventListener(this.pref, 378 function(event) { 379 var value = event.value && event.value['value'] != undefined ? 380 event.value['value'] : event.value; 381 382 // Make sure |value| is a string, because the value is stored as a 383 // string in the HTMLOptionElement. 384 value = value.toString(); 385 386 updateElementState_(self, event); 387 388 var found = false; 389 for (var i = 0; i < self.options.length; i++) { 390 if (self.options[i].value == value) { 391 self.selectedIndex = i; 392 found = true; 393 } 394 } 395 396 // Item not found, select first item. 397 if (!found) 398 self.selectedIndex = 0; 399 400 if (self.onchange != undefined) 401 self.onchange(event); 402 }); 403 404 // Listen to user events. 405 this.addEventListener('change', 406 function(e) { 407 if (!self.dataType) { 408 console.error('undefined data type for <select> pref'); 409 return; 410 } 411 412 switch(self.dataType) { 413 case 'number': 414 Preferences.setIntegerPref(self.pref, 415 self.options[self.selectedIndex].value, self.metric); 416 break; 417 case 'double': 418 Preferences.setDoublePref(self.pref, 419 self.options[self.selectedIndex].value, self.metric); 420 break; 421 case 'boolean': 422 var option = self.options[self.selectedIndex]; 423 var value = (option.value == 'true') ? true : false; 424 Preferences.setBooleanPref(self.pref, value, self.metric); 425 break; 426 case 'string': 427 Preferences.setStringPref(self.pref, 428 self.options[self.selectedIndex].value, self.metric); 429 break; 430 default: 431 console.error('unknown data type for <select> pref: ' + 432 self.dataType); 433 } 434 }); 435 }, 436 }; 437 438 /** 439 * The preference name. 440 * @type {string} 441 */ 442 cr.defineProperty(PrefSelect, 'pref', cr.PropertyKind.ATTR); 443 444 /** 445 * The user metric string. 446 * @type {string} 447 */ 448 cr.defineProperty(PrefSelect, 'metric', cr.PropertyKind.ATTR); 449 450 /** 451 * The data type for the preference options. 452 * @type {string} 453 */ 454 cr.defineProperty(PrefSelect, 'dataType', cr.PropertyKind.ATTR); 455 456 ///////////////////////////////////////////////////////////////////////////// 457 // PrefTextField class: 458 459 // Define a constructor that uses an input element as its underlying element. 460 var PrefTextField = cr.ui.define('input'); 461 462 PrefTextField.prototype = { 463 // Set up the prototype chain 464 __proto__: HTMLInputElement.prototype, 465 466 /** 467 * Initialization function for the cr.ui framework. 468 */ 469 decorate: function() { 470 var self = this; 471 472 // Listen to pref changes. 473 Preferences.getInstance().addEventListener(this.pref, 474 function(event) { 475 self.value = event.value && event.value['value'] != undefined ? 476 event.value['value'] : event.value; 477 478 updateElementState_(self, event); 479 }); 480 481 // Listen to user events. 482 this.addEventListener('change', 483 function(e) { 484 switch(self.dataType) { 485 case 'number': 486 Preferences.setIntegerPref(self.pref, self.value, self.metric); 487 break; 488 case 'double': 489 Preferences.setDoublePref(self.pref, self.value, self.metric); 490 break; 491 default: 492 Preferences.setStringPref(self.pref, self.value, self.metric); 493 break; 494 } 495 }); 496 497 window.addEventListener('unload', 498 function() { 499 if (document.activeElement == self) 500 self.blur(); 501 }); 502 } 503 }; 504 505 /** 506 * The preference name. 507 * @type {string} 508 */ 509 cr.defineProperty(PrefTextField, 'pref', cr.PropertyKind.ATTR); 510 511 /** 512 * The user metric string. 513 * @type {string} 514 */ 515 cr.defineProperty(PrefTextField, 'metric', cr.PropertyKind.ATTR); 516 517 /** 518 * The data type for the preference options. 519 * @type {string} 520 */ 521 cr.defineProperty(PrefTextField, 'dataType', cr.PropertyKind.ATTR); 522 523 // Export 524 return { 525 PrefCheckbox: PrefCheckbox, 526 PrefNumber: PrefNumber, 527 PrefNumeric: PrefNumeric, 528 PrefRadio: PrefRadio, 529 PrefRange: PrefRange, 530 PrefSelect: PrefSelect, 531 PrefTextField: PrefTextField 532 }; 533 534 }); 535