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. 4 5 'use strict'; 6 7 base.require('base.events'); 8 9 base.exportTo('base', function() { 10 /** 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. 16 */ 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; 24 25 var error; 26 e.throwError = function(err) { // workaround CR 239648 27 error = err; 28 }; 29 30 target.dispatchEvent(e); 31 if (error) 32 throw error; 33 } 34 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); 42 } 43 44 /** 45 * Converts a camelCase javascript property name to a hyphenated-lower-case 46 * attribute name. 47 * @param {string} jsName The javascript camelCase property name. 48 * @return {string} The equivalent hyphenated-lower-case attribute name. 49 */ 50 function getAttributeName(jsName) { 51 return jsName.replace(/([A-Z])/g, '-$1').toLowerCase(); 52 } 53 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 57 */ 58 function getPrivateName(name) { 59 return name + '_base_'; 60 } 61 62 /** 63 * The kind of property to define in {@code defineProperty}. 64 * @enum {number} 65 * @const 66 */ 67 var PropertyKind = { 68 /** 69 * Plain old JS property where the backing data is stored as a 'private' 70 * field on the object. 71 */ 72 JS: 'js', 73 74 /** 75 * The property backing data is stored as an attribute on an element. 76 */ 77 ATTR: 'attr', 78 79 /** 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. 82 */ 83 BOOL_ATTR: 'boolAttr' 84 }; 85 86 /** 87 * Helper function for defineProperty that returns the getter to use for the 88 * property. 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. 92 */ 93 function getGetter(name, kind) { 94 switch (kind) { 95 case PropertyKind.JS: 96 var privateName = getPrivateName(name); 97 return function() { 98 return this[privateName]; 99 }; 100 case PropertyKind.ATTR: 101 var attributeName = getAttributeName(name); 102 return function() { 103 return this.getAttribute(attributeName); 104 }; 105 case PropertyKind.BOOL_ATTR: 106 var attributeName = getAttributeName(name); 107 return function() { 108 return this.hasAttribute(attributeName); 109 }; 110 } 111 } 112 113 /** 114 * Helper function for defineProperty that returns the setter of the right 115 * kind. 116 * @param {string} name The name of the property we are defining the setter 117 * for. 118 * @param {base.PropertyKind} kind The kind of property we are getting the 119 * setter for. 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 124 * can be prevented. 125 * @return {function(*):void} The function to use as a setter. 126 */ 127 function getSetter(name, kind, opt_setHook, opt_bubbles, opt_cancelable) { 128 switch (kind) { 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; 135 if (opt_setHook) 136 opt_setHook.call(this, value, oldValue); 137 dispatchPropertyChange(this, name, value, oldValue, 138 opt_bubbles, opt_cancelable); 139 } 140 }; 141 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); 149 else 150 this.setAttribute(attributeName, value); 151 if (opt_setHook) 152 opt_setHook.call(this, value, oldValue); 153 dispatchPropertyChange(this, name, value, oldValue, 154 opt_bubbles, opt_cancelable); 155 } 156 }; 157 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) { 163 if (value) 164 this.setAttribute(attributeName, name); 165 else 166 this.removeAttribute(attributeName); 167 if (opt_setHook) 168 opt_setHook.call(this, value, oldValue); 169 dispatchPropertyChange(this, name, value, oldValue, 170 opt_bubbles, opt_cancelable); 171 } 172 }; 173 } 174 } 175 176 /** 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 182 * use. 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 187 * can be prevented. 188 */ 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') 193 obj = obj.prototype; 194 195 var kind = opt_kind || PropertyKind.JS; 196 197 if (!obj.__lookupGetter__(name)) 198 obj.__defineGetter__(name, getGetter(name, kind)); 199 200 if (!obj.__lookupSetter__(name)) 201 obj.__defineSetter__(name, getSetter(name, kind, opt_setHook, 202 opt_bubbles, opt_cancelable)); 203 } 204 205 return { 206 PropertyKind: PropertyKind, 207 defineProperty: defineProperty, 208 dispatchPropertyChange: dispatchPropertyChange, 209 setPropertyAndDispatchChange: setPropertyAndDispatchChange 210 }; 211 }); 212