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