1 /* 2 * Copyright (C) 2008-2009 WaveMaker Software, Inc. 3 * 4 * This file is part of the WaveMaker Client Runtime. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 dojo.provide("wm.base.Object"); 19 dojo.require('wm.base.lib.util'); 20 21 /** 22 Base class that supports property inspection and binding. 23 <br/><br/> 24 Almost all objects in WaveMaker are instances of <i>wm.Object</i>. 25 In particular, all Components and Widgets descend from <i>wm.Object</i> 26 <br/><br/> 27 <i>wm.Object</i> supports a generalized property system: in order to 28 access or modify properties on a <i>wm.Object</i> use the 29 <a href="#getValue">getValue</a>/<a href="#setValue">setValue</a> API. 30 <br/><br/> 31 <a href="#getValue">getValue</a> takes the name of the property to examine. 32 <a href="#setValue">setValue</a> takes 33 the name of the property and the value to set. 34 <a href="#getValue">getValue</a>/<a href="#setValue">setValue</a> support dot notation. 35 <br/><br/> 36 For all objects that descend from <i>wm.Object</i>.use the 37 <a href="#getValue">getValue</a>/<a href="#setValue">setValue</a> 38 API to access documented properties 39 <br/><br/> 40 Examples 41 @example 42 // To examine the name of a Component 43 var n = this.myComponent.getValue("name"); 44 <br/> 45 // To change the name of the Component 46 this.myComponent.setValue("name", "newName"); 47 <br/> 48 //"panel1" contains an object named "label1" 49 this.panel1.setValue("label1.caption", "hello world"); 50 51 @name wm.Object 52 @class 53 */ 54 dojo.declare("wm.Object", null, { 55 /** @lends wm.Object.prototype */ 56 // hey ma, no props! 57 //=========================================================================== 58 // Construction 59 //=========================================================================== 60 constructor: function() { 61 this.type = this.declaredClass; 62 }, 63 /** 64 Returns a string identifier (primarily for debugging). 65 */ 66 toString: function() { 67 return '[' + this.declaredClass + ']'; 68 }, 69 //=========================================================================== 70 // Properties 71 //=========================================================================== 72 /** @private */ 73 getProp: function(inPropertyName) { 74 var g = this._getPropWorker(this, inPropertyName, "get"); 75 if (g) 76 return g.call(this, inPropertyName); 77 else 78 return this._getProp(inPropertyName); 79 }, 80 _getProp: function(inProp) { 81 return this[inProp]; 82 }, 83 /** @private */ 84 setProp: function(inProp, inValue) { 85 var s = this._getPropWorker(this, inProp, "set"); 86 if (s) 87 s.call(this, inValue); 88 else 89 this._setProp(inProp, inValue); 90 this.valueChanged(inProp, this.getProp(inProp)); 91 }, 92 _setProp: function(inProp, inValue) { 93 if (inProp in this) 94 this[inProp] = inValue; 95 }, 96 _getPropWorker: function(inObj, inProp, inPrefix) { 97 //if (inProp=="dataValue" || inProp=="value") 98 // return null; 99 var w = inObj[inPrefix + "_" + inProp] || this[inPrefix + inProp.slice(0, 1).toUpperCase() + inProp.slice(1)]; 100 if (dojo.isFunction(w)) 101 return w; 102 }, 103 //=========================================================================== 104 // Values 105 //=========================================================================== 106 /** @private */ 107 valueChanged: function(inProp, inValue) { 108 }, 109 _getValue: function(inProp) { 110 // private API for getting a named value/property 111 // for Object, values are props 112 return this.getProp(inProp); 113 }, 114 _setValue: function(inProp, inValue) { 115 // private API for setting a named value/property 116 // for Object, values are props 117 this.setProp(inProp, inValue); 118 }, 119 /** 120 Get the value of a named property. 121 122 Supports dot notation, e.g. 123 @example this.getValue("customer.name.first") 124 125 @param {String} inName Name of property 126 127 @see <a href="#setValue">setValue</a> 128 */ 129 getValue: function(inName) { 130 // public API for getting a named value/property using dot-notation 131 // all *actual* getting is delegated, we only manage dots here 132 // inProp is like "foo.bar.baz" or ["foo", "bar", "baz"] 133 if (!inName) 134 return; 135 var parts = dojo.isString(inName) ? inName.split('.') : inName, o=this, p; 136 while (parts.length > 1) { 137 p = parts.shift(); 138 o = o.getValue ? o.getValue(p) : o[p]; 139 if (!o) { 140 wm.logging && console.debug(this, "notice: Object.getValue: couldn't marshall property ", p, " for ", inName); 141 return; 142 } 143 if (o.getValue) 144 return o.getValue(parts); 145 } 146 p = parts.shift(); 147 return o._getValue ? o._getValue(p) : o[p]; 148 }, 149 /** 150 Set the value of a named property. 151 Using this method to set properties is <b>required</b> to support binding. 152 153 Supports dot notation, e.g. 154 @example this.setValue("customer.name.first", "Harry") 155 156 @param {String} inName Name of property 157 @param {Any} inValue Value to set on property 158 159 @see <a href="#setValue">getValue</a> 160 */ 161 setValue: function(inName, inValue) { 162 // public API for setting a named value/property using dot-notation 163 // all *actual* setting is delegated, we only manage dots here 164 // inProp is like "foo.bar.baz" or ["foo", "bar", "baz"] 165 var parts = dojo.isString(inName) ? inName.split('.') : inName, o=this, p; 166 while (parts.length > 1) { 167 o = o.getValue(parts.shift()); 168 // it's possible this value is not yet settable 169 if (!o) 170 return; 171 if (o instanceof wm.Object) 172 return o.setValue(parts, inValue); 173 } 174 p = parts.shift(); 175 o._setValue ? o._setValue(p, inValue) : o[p] = inValue; 176 } 177 }); 178 179 //=========================================================================== 180 // Class Properties 181 //=========================================================================== 182 /** @lends wm.Object */ 183 dojo.mixin(wm.Object, { 184 /** 185 @private 186 Object metadata (aka "schema") is stored using function prototypes 187 (aka classes) to take advantage of built-in copy-on-write 188 prototype chaining. 189 Schema class is stored in a class-property called "schemaClass", 190 and an instance of it is made available in the related class prototype 191 as "schema". 192 */ 193 //FIXME: have I confused myself into using a overly complex mechanism? 194 makeSchema: function(inClass) { 195 //console.info("makeSchema:", inClass.prototype); 196 // make an empty function so we get a prototype 197 inClass.schemaClass = function(){}; 198 // if we have a superclass, chain to it's schema 199 if (inClass.superclass) { 200 var ctor = this.getSchemaClass(inClass.superclass.constructor); 201 inClass.schemaClass.prototype = new ctor(); 202 } 203 inClass.prototype.schema = new inClass.schemaClass(); 204 return inClass.schemaClass; 205 }, 206 /** @private */ 207 // Get the schema class for class inClass. Manufacture the schema class if necessary. 208 getSchemaClass: function(inClass) { 209 return inClass.schemaClass || wm.Object.makeSchema(inClass); 210 }, 211 /** 212 Add entries to a class schema. 213 Note that "inClass" is a class (function), not a class-name (string). 214 215 @param {Function} inClass Add schema entries to this class. 216 @param {Object} inSchema Schema entries in object notation. 217 218 @example 219 wm.Object.extendSchema(wm.MyButton, { 220 confirmPrompt: { writeonly: 1} // configure flags for confirmPrompt property 221 }); 222 */ 223 extendSchema: function(inClass, inSchema) { 224 dojo.extend(wm.Object.getSchemaClass(inClass), inSchema); 225 // expunge memoized property information 226 delete inClass._publishedProps; 227 } 228 }); 229 230 //=========================================================================== 231 // Design Schema 232 //=========================================================================== 233 wm.Object.extendSchema(wm.Object, { 234 declaredClass: { ignore: 1 }, 235 schema: { ignore: 1 }, 236 schemaClass: { ignore: 1 }, 237 type: { ignore: 1 } 238 }); 239 240 //=========================================================================== 241 // Design Time Extensions 242 //=========================================================================== 243 /** @lends wm.Object.prototype */ 244 /**#@+ @design */ 245 wm.Object.extend({ 246 //=========================================================================== 247 // Extensions for property enumeration 248 //=========================================================================== 249 /** 250 Hook for subclasses to add flags to the typeInfo structure 251 for property <i>inName</i>. 252 Called from <a href="#getPropertyType">getPropertyType</a>. 253 @param {String} inName Name of property. 254 @param {Object} inTypeInfo Type info structure to modify. 255 */ 256 getPropFlags: function(inName, inTypeInfo) { 257 }, 258 /** 259 Get type information for a property. 260 Returns a structure containing schema information for property <i>inName</i>, 261 including at least the following fields: 262 <ul> 263 <li>type: <i>(string) name of type</i></li> 264 <li>isObject: <i>(boolean) true if property is itself a wm.Object</i></li> 265 <li>isEvent: <i>(boolean) true if property represents an event</i></li> 266 </ul> 267 */ 268 getPropertyType: function(inName) { 269 var v = this.getProp(inName); 270 var t = { 271 type: v && v.type || typeof v, 272 isObject: v instanceof wm.Object 273 } 274 this.getPropFlags(inName, t); 275 var s = this.schema[inName] || { 276 noprop: Boolean((v === undefined) || (v === null) || inName.charAt(0)=='_' || dojo.isFunction(v) || dojo.isObject(v)) 277 }; 278 return dojo.mixin(t, s); 279 }, 280 // $ Build property information into ioProps from the properties of 281 // $ inSchema filtered by inGetTypeInfo function (or getPropertyType by default). 282 _listSchemaProperties: function(ioProps, inSchema, inGetTypeInfo) { 283 var getInfo = this[inGetTypeInfo||"getPropertyType"], op = Object.prototype; 284 for (var p in inSchema) { 285 if (!(p in ioProps) && !(p in op)) { 286 var t = getInfo.call(this, p); 287 if (!t.noprop) 288 ioProps[p] = t; 289 } 290 } 291 return ioProps; 292 }, 293 //$ Combine property information from basic reflection with 294 //$ explicit schema information to form a list 295 //$ of property information records. 296 _listProperties: function() { 297 var props = {}; 298 this._listSchemaProperties(props, this); 299 return this._listSchemaProperties(props, this.schema); 300 }, 301 /** 302 Return memoized list of property information records. 303 */ 304 listProperties: function() { 305 return this.constructor._publishedProps || (this.constructor._publishedProps = this._listProperties()); 306 }, 307 /** 308 Return memoized list of value information records. 309 wm.Object does not distinguish properties from values, so 310 the base implementation just calls <a href="#listProperties">listProperties</a>. 311 */ 312 listDataProperties: function() { 313 return this.listProperties(); 314 } 315 }); 316 /**#@-*/ 317 318 //=========================================================================== 319 // One-stop wm.Objects 320 //=========================================================================== 321 wm.define = function(inClass, inSuperclasses, inProperties) { 322 if (arguments.length < 3) { 323 inProperties = inSuperclasses; 324 inSuperclasses = wm.Control; 325 } 326 var schema = inProperties.published; 327 delete inProperties.published; 328 var ctor = dojo.declare(inClass, inSuperclasses, inProperties); 329 wm.Object.extendSchema(ctor, schema); 330 return ctor; 331 } 332