      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