1 // Copyright (c) 2013 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.
7 base.require('base.events');
9 base.exportTo('base', function() {
11 * Fires a property change event on the target.
12 * @param {EventTarget} target The target to dispatch the event on.
13 * @param {string} propertyName The name of the property that changed.
14 * @param {*} newValue The new value for the property.
15 * @param {*} oldValue The old value for the property.
17 function dispatchPropertyChange(target, propertyName, newValue, oldValue,
18 opt_bubbles, opt_cancelable) {
19 var e = new base.Event(propertyName + 'Change',
20 opt_bubbles, opt_cancelable);
21 e.propertyName = propertyName;
22 e.newValue = newValue;
23 e.oldValue = oldValue;
26 e.throwError = function(err) { // workaround CR 239648
30 target.dispatchEvent(e);
35 function setPropertyAndDispatchChange(obj, propertyName, newValue) {
36 var privateName = propertyName + '_';
37 var oldValue = obj[propertyName];
38 obj[privateName] = newValue;
39 if (oldValue !== newValue)
40 base.dispatchPropertyChange(obj, propertyName,
41 newValue, oldValue, true, false);
45 * Converts a camelCase javascript property name to a hyphenated-lower-case
47 * @param {string} jsName The javascript camelCase property name.
48 * @return {string} The equivalent hyphenated-lower-case attribute name.
50 function getAttributeName(jsName) {
51 return jsName.replace(/([A-Z])/g, '-$1').toLowerCase();
54 /* Creates a private name unlikely to collide with object properties names
55 * @param {string} name The defineProperty name
56 * @return {string} an obfuscated name
58 function getPrivateName(name) {
59 return name + '_base_';
63 * The kind of property to define in {@code defineProperty}.
69 * Plain old JS property where the backing data is stored as a 'private'
70 * field on the object.
75 * The property backing data is stored as an attribute on an element.
80 * The property backing data is stored as an attribute on an element. If the
81 * element has the attribute then the value is true.
87 * Helper function for defineProperty that returns the getter to use for the
89 * @param {string} name The name of the property.
90 * @param {base.PropertyKind} kind The kind of the property.
91 * @return {function():*} The getter for the property.
93 function getGetter(name, kind) {
96 var privateName = getPrivateName(name);
98 return this[privateName];
100 case PropertyKind.ATTR:
101 var attributeName = getAttributeName(name);
103 return this.getAttribute(attributeName);
105 case PropertyKind.BOOL_ATTR:
106 var attributeName = getAttributeName(name);
108 return this.hasAttribute(attributeName);
114 * Helper function for defineProperty that returns the setter of the right
116 * @param {string} name The name of the property we are defining the setter
118 * @param {base.PropertyKind} kind The kind of property we are getting the
120 * @param {function(*):void=} opt_setHook A function to run after the property
121 * is set, but before the propertyChange event is fired.
122 * @param {boolean=} opt_bubbles Whether the event bubbles or not.
123 * @param {boolean=} opt_cancelable Whether the default action of the event
125 * @return {function(*):void} The function to use as a setter.
127 function getSetter(name, kind, opt_setHook, opt_bubbles, opt_cancelable) {
129 case PropertyKind.JS:
130 var privateName = getPrivateName(name);
131 return function(value) {
132 var oldValue = this[privateName];
133 if (value !== oldValue) {
134 this[privateName] = value;
136 opt_setHook.call(this, value, oldValue);
137 dispatchPropertyChange(this, name, value, oldValue,
138 opt_bubbles, opt_cancelable);
142 case PropertyKind.ATTR:
143 var attributeName = getAttributeName(name);
144 return function(value) {
145 var oldValue = this.getAttribute(attributeName);
146 if (value !== oldValue) {
147 if (value == undefined)
148 this.removeAttribute(attributeName);
150 this.setAttribute(attributeName, value);
152 opt_setHook.call(this, value, oldValue);
153 dispatchPropertyChange(this, name, value, oldValue,
154 opt_bubbles, opt_cancelable);
158 case PropertyKind.BOOL_ATTR:
159 var attributeName = getAttributeName(name);
160 return function(value) {
161 var oldValue = (this.getAttribute(attributeName) === name);
162 if (value !== oldValue) {
164 this.setAttribute(attributeName, name);
166 this.removeAttribute(attributeName);
168 opt_setHook.call(this, value, oldValue);
169 dispatchPropertyChange(this, name, value, oldValue,
170 opt_bubbles, opt_cancelable);
177 * Defines a property on an object. When the setter changes the value a
178 * property change event with the type {@code name + 'Change'} is fired.
179 * @param {!Object} obj The object to define the property for.
180 * @param {string} name The name of the property.
181 * @param {base.PropertyKind=} opt_kind What kind of underlying storage to
183 * @param {function(*):void=} opt_setHook A function to run after the
184 * property is set, but before the propertyChange event is fired.
185 * @param {boolean=} opt_bubbles Whether the event bubbles or not.
186 * @param {boolean=} opt_cancelable Whether the default action of the event
189 function defineProperty(obj, name, opt_kind, opt_setHook,
190 opt_bubbles, opt_cancelable) {
191 console.error("Don't use base.defineProperty");
192 if (typeof obj == 'function')
195 var kind = opt_kind || PropertyKind.JS;
197 if (!obj.__lookupGetter__(name))
198 obj.__defineGetter__(name, getGetter(name, kind));
200 if (!obj.__lookupSetter__(name))
201 obj.__defineSetter__(name, getSetter(name, kind, opt_setHook,
202 opt_bubbles, opt_cancelable));
206 PropertyKind: PropertyKind,
207 defineProperty: defineProperty,
208 dispatchPropertyChange: dispatchPropertyChange,
209 setPropertyAndDispatchChange: setPropertyAndDispatchChange