OLD | NEW |
| (Empty) |
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 * 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. | |
13 * @private | |
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|. | |
18 */ | |
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; | |
26 } | |
27 if (!el.disabled) { | |
28 // If the element is not disabled, there should be no reason, except for | |
29 // 'other'. | |
30 delete el.disabledReasons['other']; | |
31 if (Object.keys(el.disabledReasons).length > 0) | |
32 console.error("Element is not disabled but should be"); | |
33 } | |
34 if (disabled) { | |
35 el.disabledReasons[reason] = true; | |
36 } else { | |
37 delete el.disabledReasons[reason]; | |
38 } | |
39 el.disabled = Object.keys(el.disabledReasons).length > 0; | |
40 } | |
41 | |
42 /** | |
43 * Helper function to update element's state from pref change event. | |
44 * @private | |
45 * @param {!HTMLElement} el The element to update. | |
46 * @param {!Event} event The pref change event. | |
47 */ | |
48 function updateElementState_(el, event) { | |
49 el.controlledBy = null; | |
50 | |
51 if (!event.value) | |
52 return; | |
53 | |
54 updateDisabledState_(el, 'notUserModifiable', event.value.disabled); | |
55 | |
56 el.controlledBy = event.value['controlledBy']; | |
57 | |
58 OptionsPage.updateManagedBannerVisibility(); | |
59 } | |
60 | |
61 ///////////////////////////////////////////////////////////////////////////// | |
62 // PrefCheckbox class: | |
63 // TODO(jhawkins): Refactor all this copy-pasted code! | |
64 | |
65 // Define a constructor that uses an input element as its underlying element. | |
66 var PrefCheckbox = cr.ui.define('input'); | |
67 | |
68 PrefCheckbox.prototype = { | |
69 // Set up the prototype chain | |
70 __proto__: HTMLInputElement.prototype, | |
71 | |
72 /** | |
73 * Initialization function for the cr.ui framework. | |
74 */ | |
75 decorate: function() { | |
76 this.type = 'checkbox'; | |
77 var self = this; | |
78 | |
79 self.initializeValueType(self.getAttribute('value-type')); | |
80 | |
81 // Listen to pref changes. | |
82 Preferences.getInstance().addEventListener( | |
83 this.pref, | |
84 function(event) { | |
85 var value = event.value && event.value['value'] != undefined ? | |
86 event.value['value'] : event.value; | |
87 | |
88 // Invert pref value if inverted_pref == true. | |
89 if (self.inverted_pref) | |
90 self.checked = !Boolean(value); | |
91 else | |
92 self.checked = Boolean(value); | |
93 | |
94 updateElementState_(self, event); | |
95 }); | |
96 | |
97 // Listen to user events. | |
98 this.addEventListener( | |
99 'change', | |
100 function(e) { | |
101 if (self.customChangeHandler(e)) | |
102 return; | |
103 var value = self.inverted_pref ? !self.checked : self.checked; | |
104 switch(self.valueType) { | |
105 case 'number': | |
106 Preferences.setIntegerPref(self.pref, | |
107 Number(value), self.metric); | |
108 break; | |
109 case 'boolean': | |
110 Preferences.setBooleanPref(self.pref, | |
111 value, self.metric); | |
112 break; | |
113 } | |
114 }); | |
115 }, | |
116 | |
117 /** | |
118 * Sets up options in checkbox element. | |
119 * @param {String} valueType The preference type for this checkbox. | |
120 */ | |
121 initializeValueType: function(valueType) { | |
122 this.valueType = valueType || 'boolean'; | |
123 }, | |
124 | |
125 /** | |
126 * See |updateDisabledState_| above. | |
127 */ | |
128 setDisabled: function(reason, disabled) { | |
129 updateDisabledState_(this, reason, disabled); | |
130 }, | |
131 | |
132 /** | |
133 * This method is called first while processing an onchange event. If it | |
134 * returns false, regular onchange processing continues (setting the | |
135 * associated pref, etc). If it returns true, the rest of the onchange is | |
136 * not performed. I.e., this works like stopPropagation or cancelBubble. | |
137 * @param {Event} event Change event. | |
138 */ | |
139 customChangeHandler: function(event) { | |
140 return false; | |
141 }, | |
142 }; | |
143 | |
144 /** | |
145 * The preference name. | |
146 * @type {string} | |
147 */ | |
148 cr.defineProperty(PrefCheckbox, 'pref', cr.PropertyKind.ATTR); | |
149 | |
150 /** | |
151 * Whether the preference is controlled by something else than the user's | |
152 * settings (either 'policy' or 'extension'). | |
153 * @type {string} | |
154 */ | |
155 cr.defineProperty(PrefCheckbox, 'controlledBy', cr.PropertyKind.ATTR); | |
156 | |
157 /** | |
158 * The user metric string. | |
159 * @type {string} | |
160 */ | |
161 cr.defineProperty(PrefCheckbox, 'metric', cr.PropertyKind.ATTR); | |
162 | |
163 /** | |
164 * Whether to use inverted pref value. | |
165 * @type {boolean} | |
166 */ | |
167 cr.defineProperty(PrefCheckbox, 'inverted_pref', cr.PropertyKind.BOOL_ATTR); | |
168 | |
169 ///////////////////////////////////////////////////////////////////////////// | |
170 // PrefRadio class: | |
171 | |
172 //Define a constructor that uses an input element as its underlying element. | |
173 var PrefRadio = cr.ui.define('input'); | |
174 | |
175 PrefRadio.prototype = { | |
176 // Set up the prototype chain | |
177 __proto__: HTMLInputElement.prototype, | |
178 | |
179 /** | |
180 * Initialization function for the cr.ui framework. | |
181 */ | |
182 decorate: function() { | |
183 this.type = 'radio'; | |
184 var self = this; | |
185 | |
186 // Listen to pref changes. | |
187 Preferences.getInstance().addEventListener(this.pref, | |
188 function(event) { | |
189 var value = event.value && event.value['value'] != undefined ? | |
190 event.value['value'] : event.value; | |
191 self.checked = String(value) == self.value; | |
192 | |
193 updateElementState_(self, event); | |
194 }); | |
195 | |
196 // Listen to user events. | |
197 this.addEventListener('change', | |
198 function(e) { | |
199 if(self.value == 'true' || self.value == 'false') { | |
200 Preferences.setBooleanPref(self.pref, | |
201 self.value == 'true', self.metric); | |
202 } else { | |
203 Preferences.setIntegerPref(self.pref, | |
204 parseInt(self.value, 10), self.metric); | |
205 } | |
206 }); | |
207 }, | |
208 | |
209 /** | |
210 * See |updateDisabledState_| above. | |
211 */ | |
212 setDisabled: function(reason, disabled) { | |
213 updateDisabledState_(this, reason, disabled); | |
214 }, | |
215 }; | |
216 | |
217 /** | |
218 * The preference name. | |
219 * @type {string} | |
220 */ | |
221 cr.defineProperty(PrefRadio, 'pref', cr.PropertyKind.ATTR); | |
222 | |
223 /** | |
224 * Whether the preference is controlled by something else than the user's | |
225 * settings (either 'policy' or 'extension'). | |
226 * @type {string} | |
227 */ | |
228 cr.defineProperty(PrefRadio, 'controlledBy', cr.PropertyKind.ATTR); | |
229 | |
230 /** | |
231 * The user metric string. | |
232 * @type {string} | |
233 */ | |
234 cr.defineProperty(PrefRadio, 'metric', cr.PropertyKind.ATTR); | |
235 | |
236 ///////////////////////////////////////////////////////////////////////////// | |
237 // PrefNumeric class: | |
238 | |
239 // Define a constructor that uses an input element as its underlying element. | |
240 var PrefNumeric = function() {}; | |
241 PrefNumeric.prototype = { | |
242 // Set up the prototype chain | |
243 __proto__: HTMLInputElement.prototype, | |
244 | |
245 /** | |
246 * Initialization function for the cr.ui framework. | |
247 */ | |
248 decorate: function() { | |
249 var self = this; | |
250 | |
251 // Listen to pref changes. | |
252 Preferences.getInstance().addEventListener(this.pref, | |
253 function(event) { | |
254 self.value = event.value && event.value['value'] != undefined ? | |
255 event.value['value'] : event.value; | |
256 | |
257 updateElementState_(self, event); | |
258 }); | |
259 | |
260 // Listen to user events. | |
261 this.addEventListener('change', | |
262 function(e) { | |
263 if (this.validity.valid) { | |
264 Preferences.setIntegerPref(self.pref, self.value, self.metric); | |
265 } | |
266 }); | |
267 }, | |
268 | |
269 /** | |
270 * See |updateDisabledState_| above. | |
271 */ | |
272 setDisabled: function(reason, disabled) { | |
273 updateDisabledState_(this, reason, disabled); | |
274 }, | |
275 }; | |
276 | |
277 /** | |
278 * The preference name. | |
279 * @type {string} | |
280 */ | |
281 cr.defineProperty(PrefNumeric, 'pref', cr.PropertyKind.ATTR); | |
282 | |
283 /** | |
284 * Whether the preference is controlled by something else than the user's | |
285 * settings (either 'policy' or 'extension'). | |
286 * @type {string} | |
287 */ | |
288 cr.defineProperty(PrefNumeric, 'controlledBy', cr.PropertyKind.ATTR); | |
289 | |
290 /** | |
291 * The user metric string. | |
292 * @type {string} | |
293 */ | |
294 cr.defineProperty(PrefNumeric, 'metric', cr.PropertyKind.ATTR); | |
295 | |
296 ///////////////////////////////////////////////////////////////////////////// | |
297 // PrefNumber class: | |
298 | |
299 // Define a constructor that uses an input element as its underlying element. | |
300 var PrefNumber = cr.ui.define('input'); | |
301 | |
302 PrefNumber.prototype = { | |
303 // Set up the prototype chain | |
304 __proto__: PrefNumeric.prototype, | |
305 | |
306 /** | |
307 * Initialization function for the cr.ui framework. | |
308 */ | |
309 decorate: function() { | |
310 this.type = 'number'; | |
311 PrefNumeric.prototype.decorate.call(this); | |
312 | |
313 // Listen to user events. | |
314 this.addEventListener('input', | |
315 function(e) { | |
316 if (this.validity.valid) { | |
317 Preferences.setIntegerPref(self.pref, self.value, self.metric); | |
318 } | |
319 }); | |
320 }, | |
321 | |
322 /** | |
323 * See |updateDisabledState_| above. | |
324 */ | |
325 setDisabled: function(reason, disabled) { | |
326 updateDisabledState_(this, reason, disabled); | |
327 }, | |
328 }; | |
329 | |
330 ///////////////////////////////////////////////////////////////////////////// | |
331 // PrefRange class: | |
332 | |
333 // Define a constructor that uses an input element as its underlying element. | |
334 var PrefRange = cr.ui.define('input'); | |
335 | |
336 PrefRange.prototype = { | |
337 // Set up the prototype chain | |
338 __proto__: HTMLInputElement.prototype, | |
339 | |
340 /** | |
341 * The map from input range value to the corresponding preference value. | |
342 */ | |
343 valueMap: undefined, | |
344 | |
345 /** | |
346 * If true, the associated pref will be modified on each onchange event; | |
347 * otherwise, the pref will only be modified on the onmouseup event after | |
348 * the drag. | |
349 */ | |
350 continuous: true, | |
351 | |
352 /** | |
353 * Initialization function for the cr.ui framework. | |
354 */ | |
355 decorate: function() { | |
356 this.type = 'range'; | |
357 | |
358 // Update the UI when the pref changes. | |
359 Preferences.getInstance().addEventListener( | |
360 this.pref, this.onPrefChange_.bind(this)); | |
361 | |
362 // Listen to user events. | |
363 // TODO(jhawkins): Add onmousewheel handling once the associated WK bug is | |
364 // fixed. | |
365 // https://bugs.webkit.org/show_bug.cgi?id=52256 | |
366 this.onchange = this.onChange_.bind(this); | |
367 this.onkeyup = this.onmouseup = this.onInputUp_.bind(this); | |
368 }, | |
369 | |
370 /** | |
371 * Event listener that updates the UI when the underlying pref changes. | |
372 * @param {Event} event The event that details the pref change. | |
373 * @private | |
374 */ | |
375 onPrefChange_: function(event) { | |
376 var value = event.value && event.value['value'] != undefined ? | |
377 event.value['value'] : event.value; | |
378 if (value != undefined) | |
379 this.value = this.valueMap ? this.valueMap.indexOf(value) : value; | |
380 }, | |
381 | |
382 /** | |
383 * onchange handler that sets the pref when the user changes the value of | |
384 * the input element. | |
385 * @private | |
386 */ | |
387 onChange_: function(event) { | |
388 if (this.continuous) | |
389 this.setRangePref_(); | |
390 | |
391 if (this.notifyChange) | |
392 this.notifyChange(this, this.mapValueToRange_(this.value)); | |
393 }, | |
394 | |
395 /** | |
396 * Sets the integer value of |pref| to the value of this element. | |
397 * @private | |
398 */ | |
399 setRangePref_: function() { | |
400 Preferences.setIntegerPref( | |
401 this.pref, this.mapValueToRange_(this.value), this.metric); | |
402 | |
403 if (this.notifyPrefChange) | |
404 this.notifyPrefChange(this, this.mapValueToRange_(this.value)); | |
405 }, | |
406 | |
407 /** | |
408 * onkeyup/onmouseup handler that modifies the pref if |continuous| is | |
409 * false. | |
410 * @private | |
411 */ | |
412 onInputUp_: function(event) { | |
413 if (!this.continuous) | |
414 this.setRangePref_(); | |
415 }, | |
416 | |
417 /** | |
418 * Maps the value of this element into the range provided by the client, | |
419 * represented by |valueMap|. | |
420 * @param {number} value The value to map. | |
421 * @private | |
422 */ | |
423 mapValueToRange_: function(value) { | |
424 return this.valueMap ? this.valueMap[value] : value; | |
425 }, | |
426 | |
427 /** | |
428 * Called when the client has specified non-continuous mode and the value of | |
429 * the range control changes. | |
430 * @param {Element} el This element. | |
431 * @param {number} value The value of this element. | |
432 */ | |
433 notifyChange: function(el, value) { | |
434 }, | |
435 | |
436 /** | |
437 * See |updateDisabledState_| above. | |
438 */ | |
439 setDisabled: function(reason, disabled) { | |
440 updateDisabledState_(this, reason, disabled); | |
441 }, | |
442 }; | |
443 | |
444 /** | |
445 * The preference name. | |
446 * @type {string} | |
447 */ | |
448 cr.defineProperty(PrefRange, 'pref', cr.PropertyKind.ATTR); | |
449 | |
450 /** | |
451 * Whether the preference is controlled by something else than the user's | |
452 * settings (either 'policy' or 'extension'). | |
453 * @type {string} | |
454 */ | |
455 cr.defineProperty(PrefRange, 'controlledBy', cr.PropertyKind.ATTR); | |
456 | |
457 /** | |
458 * The user metric string. | |
459 * @type {string} | |
460 */ | |
461 cr.defineProperty(PrefRange, 'metric', cr.PropertyKind.ATTR); | |
462 | |
463 ///////////////////////////////////////////////////////////////////////////// | |
464 // PrefSelect class: | |
465 | |
466 // Define a constructor that uses a select element as its underlying element. | |
467 var PrefSelect = cr.ui.define('select'); | |
468 | |
469 PrefSelect.prototype = { | |
470 // Set up the prototype chain | |
471 __proto__: HTMLSelectElement.prototype, | |
472 | |
473 /** | |
474 * Initialization function for the cr.ui framework. | |
475 */ | |
476 decorate: function() { | |
477 var self = this; | |
478 | |
479 // Listen to pref changes. | |
480 Preferences.getInstance().addEventListener(this.pref, | |
481 function(event) { | |
482 var value = event.value && event.value['value'] != undefined ? | |
483 event.value['value'] : event.value; | |
484 | |
485 // Make sure |value| is a string, because the value is stored as a | |
486 // string in the HTMLOptionElement. | |
487 value = value.toString(); | |
488 | |
489 updateElementState_(self, event); | |
490 | |
491 var found = false; | |
492 for (var i = 0; i < self.options.length; i++) { | |
493 if (self.options[i].value == value) { | |
494 self.selectedIndex = i; | |
495 found = true; | |
496 } | |
497 } | |
498 | |
499 // Item not found, select first item. | |
500 if (!found) | |
501 self.selectedIndex = 0; | |
502 | |
503 if (self.onchange != undefined) | |
504 self.onchange(event); | |
505 }); | |
506 | |
507 // Listen to user events. | |
508 this.addEventListener('change', | |
509 function(e) { | |
510 if (!self.dataType) { | |
511 console.error('undefined data type for <select> pref'); | |
512 return; | |
513 } | |
514 | |
515 switch(self.dataType) { | |
516 case 'number': | |
517 Preferences.setIntegerPref(self.pref, | |
518 self.options[self.selectedIndex].value, self.metric); | |
519 break; | |
520 case 'double': | |
521 Preferences.setDoublePref(self.pref, | |
522 self.options[self.selectedIndex].value, self.metric); | |
523 break; | |
524 case 'boolean': | |
525 var option = self.options[self.selectedIndex]; | |
526 var value = (option.value == 'true') ? true : false; | |
527 Preferences.setBooleanPref(self.pref, value, self.metric); | |
528 break; | |
529 case 'string': | |
530 Preferences.setStringPref(self.pref, | |
531 self.options[self.selectedIndex].value, self.metric); | |
532 break; | |
533 default: | |
534 console.error('unknown data type for <select> pref: ' + | |
535 self.dataType); | |
536 } | |
537 }); | |
538 }, | |
539 | |
540 /** | |
541 * See |updateDisabledState_| above. | |
542 */ | |
543 setDisabled: function(reason, disabled) { | |
544 updateDisabledState_(this, reason, disabled); | |
545 }, | |
546 }; | |
547 | |
548 /** | |
549 * The preference name. | |
550 * @type {string} | |
551 */ | |
552 cr.defineProperty(PrefSelect, 'pref', cr.PropertyKind.ATTR); | |
553 | |
554 /** | |
555 * Whether the preference is controlled by something else than the user's | |
556 * settings (either 'policy' or 'extension'). | |
557 * @type {string} | |
558 */ | |
559 cr.defineProperty(PrefSelect, 'controlledBy', cr.PropertyKind.ATTR); | |
560 | |
561 /** | |
562 * The user metric string. | |
563 * @type {string} | |
564 */ | |
565 cr.defineProperty(PrefSelect, 'metric', cr.PropertyKind.ATTR); | |
566 | |
567 /** | |
568 * The data type for the preference options. | |
569 * @type {string} | |
570 */ | |
571 cr.defineProperty(PrefSelect, 'dataType', cr.PropertyKind.ATTR); | |
572 | |
573 ///////////////////////////////////////////////////////////////////////////// | |
574 // PrefTextField class: | |
575 | |
576 // Define a constructor that uses an input element as its underlying element. | |
577 var PrefTextField = cr.ui.define('input'); | |
578 | |
579 PrefTextField.prototype = { | |
580 // Set up the prototype chain | |
581 __proto__: HTMLInputElement.prototype, | |
582 | |
583 /** | |
584 * Initialization function for the cr.ui framework. | |
585 */ | |
586 decorate: function() { | |
587 var self = this; | |
588 | |
589 // Listen to pref changes. | |
590 Preferences.getInstance().addEventListener(this.pref, | |
591 function(event) { | |
592 self.value = event.value && event.value['value'] != undefined ? | |
593 event.value['value'] : event.value; | |
594 | |
595 updateElementState_(self, event); | |
596 }); | |
597 | |
598 // Listen to user events. | |
599 this.addEventListener('change', | |
600 function(e) { | |
601 switch(self.dataType) { | |
602 case 'number': | |
603 Preferences.setIntegerPref(self.pref, self.value, self.metric); | |
604 break; | |
605 case 'double': | |
606 Preferences.setDoublePref(self.pref, self.value, self.metric); | |
607 break; | |
608 case 'url': | |
609 Preferences.setURLPref(self.pref, self.value, self.metric); | |
610 break; | |
611 default: | |
612 Preferences.setStringPref(self.pref, self.value, self.metric); | |
613 break; | |
614 } | |
615 }); | |
616 | |
617 window.addEventListener('unload', | |
618 function() { | |
619 if (document.activeElement == self) | |
620 self.blur(); | |
621 }); | |
622 }, | |
623 | |
624 /** | |
625 * See |updateDisabledState_| above. | |
626 */ | |
627 setDisabled: function(reason, disabled) { | |
628 updateDisabledState_(this, reason, disabled); | |
629 }, | |
630 }; | |
631 | |
632 /** | |
633 * The preference name. | |
634 * @type {string} | |
635 */ | |
636 cr.defineProperty(PrefTextField, 'pref', cr.PropertyKind.ATTR); | |
637 | |
638 /** | |
639 * Whether the preference is controlled by something else than the user's | |
640 * settings (either 'policy' or 'extension'). | |
641 * @type {string} | |
642 */ | |
643 cr.defineProperty(PrefTextField, 'controlledBy', cr.PropertyKind.ATTR); | |
644 | |
645 /** | |
646 * The user metric string. | |
647 * @type {string} | |
648 */ | |
649 cr.defineProperty(PrefTextField, 'metric', cr.PropertyKind.ATTR); | |
650 | |
651 /** | |
652 * The data type for the preference options. | |
653 * @type {string} | |
654 */ | |
655 cr.defineProperty(PrefTextField, 'dataType', cr.PropertyKind.ATTR); | |
656 | |
657 ///////////////////////////////////////////////////////////////////////////// | |
658 // PrefButton class: | |
659 | |
660 // Define a constructor that uses a button element as its underlying element. | |
661 var PrefButton = cr.ui.define('button'); | |
662 | |
663 PrefButton.prototype = { | |
664 // Set up the prototype chain | |
665 __proto__: HTMLButtonElement.prototype, | |
666 | |
667 /** | |
668 * Initialization function for the cr.ui framework. | |
669 */ | |
670 decorate: function() { | |
671 var self = this; | |
672 | |
673 // Listen to pref changes. This element behaves like a normal button and | |
674 // doesn't affect the underlying preference; it just becomes disabled | |
675 // when the preference is managed, and its value is false. | |
676 // This is useful for buttons that should be disabled when the underlying | |
677 // boolean preference is set to false by a policy or extension. | |
678 Preferences.getInstance().addEventListener(this.pref, | |
679 function(event) { | |
680 var e = { | |
681 value: { | |
682 'disabled': event.value['disabled'] && !event.value['value'], | |
683 'controlledBy': event.value['controlledBy'] | |
684 } | |
685 }; | |
686 updateElementState_(self, e); | |
687 }); | |
688 }, | |
689 | |
690 /** | |
691 * See |updateDisabledState_| above. | |
692 */ | |
693 setDisabled: function(reason, disabled) { | |
694 updateDisabledState_(this, reason, disabled); | |
695 }, | |
696 }; | |
697 | |
698 /** | |
699 * The preference name. | |
700 * @type {string} | |
701 */ | |
702 cr.defineProperty(PrefButton, 'pref', cr.PropertyKind.ATTR); | |
703 | |
704 /** | |
705 * Whether the preference is controlled by something else than the user's | |
706 * settings (either 'policy' or 'extension'). | |
707 * @type {string} | |
708 */ | |
709 cr.defineProperty(PrefButton, 'controlledBy', cr.PropertyKind.ATTR); | |
710 | |
711 // Export | |
712 return { | |
713 PrefCheckbox: PrefCheckbox, | |
714 PrefNumber: PrefNumber, | |
715 PrefNumeric: PrefNumeric, | |
716 PrefRadio: PrefRadio, | |
717 PrefRange: PrefRange, | |
718 PrefSelect: PrefSelect, | |
719 PrefTextField: PrefTextField, | |
720 PrefButton: PrefButton | |
721 }; | |
722 | |
723 }); | |
OLD | NEW |