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.components.Variable");
 19 dojo.require("wm.base.Component");
 20 
 21 // FIXME: because we cannot guarantee the global "app" is a component's application 
 22 // (because studio has an app) and the runtimeService must be local to a project
 23 // get the app corresponding to the given component.
 24 wm.getRuntimeService = function(inComponent) {
 25 	var a = dojo.getObject("studio.wip.app") || app;
 26 	return wm.fire(a, "getRuntimeService");
 27 };
 28 
 29 /**
 30 	Base class for all data handling components.
 31 	@name wm.Variable
 32 	@class
 33 	@extends wm.Component
 34 */
 35 dojo.declare("wm.Variable", wm.Component, {
 36 	/** @lends wm.Variable.prototype */
 37 	json: "",
 38 	/** 
 39 		Type of data stored in the variable, or type of each item in the list.
 40 		@type String
 41 	*/
 42 	type: "",
 43 	/** 
 44 		True if this variable contains a list (aka array).
 45 		@type Boolean
 46 	*/
 47 	isList: false,
 48 	_updating: 0,
 49 	_dataSchema: {},
 50 	_greedyLoadProps: false,
 51 	_allowLazyLoad: true,
 52 	cursor: 0,
 53 	constructor: function() {
 54 		this.subscribe("wmtypes-changed", this, "wmTypesChanged");
 55 	},
 56 	postInit: function() {
 57 		this.inherited(arguments);
 58 		// optimization: we should never need bindings on subNards so not creating them
 59 		if (!this._subNard && !this.$.binding)
 60 			new wm.Binding({name: "binding", owner: this});
 61 		this.setType(this.type);
 62 		if (this.json)
 63 			this.setJson(this.json);
 64 		else
 65 			this._clearData();
 66 		// need to reinitialize after type is set
 67 		if (!this._updating && this.$.binding)
 68 			this.$.binding.refresh();
 69 	},
 70 	//===========================================================================
 71 	// Type Information
 72 	//===========================================================================
 73 	wmTypesChanged: function() {
 74 		if (this.isPrimitive || wm.typeManager.isType(this.type))
 75 			this.setType(this.type);
 76 	},
 77 	canSetType: function(inType) {
 78 		// type is locked to dataSet type if it is set
 79 		if (this.dataSet) {
 80 			wm.logging && console.debug(this.name, "cannot set variable type because this variable has a dataSet");
 81 			return;
 82 		}
 83 		return true;
 84 	},
 85 	setType: function(inType) {
 86 		if (!this.canSetType(inType))
 87 			return;
 88 		//
 89 		var t = inType;
 90 		if (wm.isListType(t)) {
 91 			this.isList = true;
 92 			t = t.slice(1, -1);
 93 		// don't reset isList if we have data
 94 		} else if (!(this.data && this.data.list))
 95 			this.isList = false;
 96 		this.type = t;
 97 		//
 98 		if (this._proxy)
 99 			this._proxy.setType(this.type);
100 		this.typeChanged(this.type);
101 	},
102 	typeChanged: function(inType) {
103 		var t = inType;
104 		var primitive = wm.typeManager.getPrimitiveType(t) || !t || t == "wm.Variable";
105 		this.isPrimitive = Boolean(primitive);
106 		var schema = this._getSchemaForType(t);
107 		if (schema)
108 			this.setDataSchema(schema);
109 	},
110 	_getSchemaForType: function(inType) {
111 		if (this.isPrimitive) {
112 			var p = wm.typeManager.getPrimitiveType(inType)
113 			// we're a string primitive by default
114 			return {dataValue: p || "String"};
115 		} else
116 			return wm.typeManager.getTypeSchema(inType) || {};
117 	},
118 	setDataSchema: function(inSchema) {
119 		this._dataSchema = inSchema;
120 	},
121 	setJson: function(inJson) {
122 		this.json = inJson;
123 		try { 
124 			var d = eval("(" + inJson + ")");
125 			this.setData(d);
126 		} catch(e) {
127 		}
128 	},
129 	hasList: function() {
130 		return this.data && ("list" in this.data);
131 	},
132 	getDataTypeInfo: function(inProp) {
133 		return this._dataSchema[inProp];
134 	},
135 	listDataProperties: function() {
136 		var list = this._listSchemaProperties({}, this._dataSchema, "getDataTypeInfo");
137 		for (var i in list) { 
138 			list[i].bindable = true; 
139 		};
140 		return list;
141 	},
142 	//===========================================================================
143 	// Update Buffering
144 	//===========================================================================
145 	beginUpdate: function() {
146 		this._updating++;
147 	},
148 	endUpdate: function() {
149 		this._updating--;
150 	},
151 	isUpdating: function() {
152 		return this._updating > 0;
153 	},
154 	//===========================================================================
155 	// Data API
156 	//===========================================================================
157 	/**
158 		Clear all data values.
159 	*/
160 	clearData: function() {
161 		this._clearData();
162 		this.setType(this.type);
163 		this.notify();
164 	},
165 	_clearData: function() {
166 		this._isNull = false;
167 		this._nostub = false;
168 		if (!this.data)
169 			this.data = {};
170 		if (this.isList)
171 			this.data = {list: []};
172 		else {
173 			// maintain any subNards, but otherwise clear data
174 			var d;
175 			for (var i in this.data) {
176 				d = this.data[i];
177 				if (d instanceof wm.Variable)
178 					d._clearData();
179 				else
180 					delete this.data[i];
181 			}
182 		}
183 	},
184 	_setNull: function(inNull) {
185 		this._isNull = inNull;
186 		// owner null can be unset but not set. consequence: all null values != null
187 		if (!inNull && this._subNard && this.owner) {
188 			this.owner._setNull(inNull);
189 		}
190 	},
191 	/**
192 		Copy data into this variable.<br/>
193 		<br/>
194 		Input data can be a primitive value, an array, a plain old JavaScript object (POJSO), or a wm.Variable.
195 		Success of setData requires that the type of the input is compatible with the type of this variable.
196 		@param {Any} inData Input data.
197 	*/
198 	// NB: input can be a POJSO or a Variable
199 	setData: function(inData) {
200 		if (inData instanceof wm.Variable)
201 			this._setVariableData(inData);
202 		else if (dojo.isArray(inData))
203 			this._setArrayData(inData);
204 		else if (this.isPrimitive)
205 			this._setPrimitiveData(inData);
206 		else
207 			this._setObjectData(inData);
208 		this.notify();
209 	},
210 	notify: function() {
211 		this.dataOwnerChanged();
212 		this.dataChanged();
213 	},
214 	_setPrimitiveData: function(inValue) {
215 		this.data = { dataValue: inValue };
216 		this.isList = false;
217 	},
218 	_setVariableData: function(inVariable) {
219 		this.setData(inVariable.getData());
220 	},
221 	_setArrayData: function(inArray) {
222 		this.data = { list: inArray };
223 		this.isList = true;
224 	},
225 	_setObjectData: function(inObject) {
226 		this.beginUpdate();
227 		this._clearData();
228 		this.isList = false;
229 		if (!("list" in this._dataSchema))
230 			delete this.data.list;
231 		var d, v, nv, isNull = inObject === null, empty = wm.isEmpty(inObject);
232 		for (var i in this._dataSchema) {
233 			d = this.data[i];
234 			v = !empty ? inObject[i] : undefined;
235 			// nv is parent null or v, called null-checked value
236 			nv = isNull ? null : v;
237 			if (this._isVariableProp(i)) {
238 				// for existing variable props, set null-checked value iff it exists
239 				if (d instanceof wm.Variable) {
240 					if (nv !== undefined) {
241 						// we don't need to propagate messages from variable properties
242 						// since this variable will propagete them
243 						d.beginUpdate();
244 						d.setData(nv);
245 						d.endUpdate();
246 					}
247 				// for non-existing variable props, set *value* iff it exists
248 				// (we do not set null values here because that can prompt infinite marshalling)
249 				} else if (v !== undefined)
250 					this._setDataValue(i, v);
251 			// for non-variable props, set null-checked value iff it exists
252 			} else {
253 				if (nv !== undefined)
254 					this._setDataValue(i, nv);
255 			}
256 		}
257 		this._setNull(isNull);
258 		this.endUpdate();
259 	},
260 	/**
261 		Export data from this variable into a plain old JavaScript object (POJSO).<br/>
262 		@returns Object
263 	*/
264 	// NB: output is POJSO
265 	getData: function() {
266 		if (!this.data)
267 			return;
268 		if (this._isNull)
269 			return null;
270 		else if (this.isList) {
271 			var data = [];
272 			for (var i=0, l= this.getCount(), v; i<l; i++) {
273 				v = (this.getItem(i) || 0).getData();
274 				if (v)
275 					data.push(v);
276 			}
277 			return data;
278 		} else {
279 			var data = {};
280 			var props = this.listDataProperties();
281 			for (var i in props) {
282 				var v = this.data[i];
283 				// we may not always want all related junk
284 				if (v !== undefined) {
285 					v = v instanceof wm.Variable ? v.getData() : v;
286 					// don't return undefined or empty, non-null variables properties
287 					if (v === undefined || (v !== null && typeof v == "object" && wm.isEmpty(v)))
288 						continue;
289 					data[i] = v;
290 				}
291 			}
292 			if (!wm.isEmpty(data))
293 				return data;
294 		}
295 	},
296 	//===========================================================================
297 	// Value API
298 	//===========================================================================
299 	_getDataValue: function(n) {
300 		if (!this.data)
301 				this.data = {};
302 		var d, f;
303 		if (this.isList) {
304 			f = this.getCursorItem();
305 			d = f && f.data;
306 		} else
307 			d = this.data;
308 		var v = d && d[n], typeInfo = this._dataSchema[n];
309 		// FIXME: Encountered a project where _isVariableProp(n) was true, but v was a string
310 		if (this._isVariableProp(n) && (!v || (v._isStub && v._isStub()))) {
311 			v = d[n] = (f || this).marshallVariable(n, typeInfo, v);
312 		}
313 		return v;
314 	},
315 	_setDataValue: function(n, v) {
316 		// NOTE: variable value is null iff it has been explicitly set to null
317 		// and no value has subsequently been set to any value, including null.
318 		if (this._isNull && v !== undefined)
319 			this._setNull(false);
320 		this.beginUpdate();
321 		var o = this._getDataValue(n);
322 		this.endUpdate();
323 		if (o instanceof wm.Variable) {
324 			// if we are updating, o's listeners will be notified by us
325 			// o doesn't need to message them directly
326 			if (this._updating)
327 				o._updating++;
328 			o.setData(v);
329 			if (this._updating)
330 				o._updating--;
331 			return;
332 		}
333 		if (!(v instanceof wm.Variable)) {
334 			this.data[n] = v;
335 			this.dataValueChanged(n, v);
336 		}
337 	},
338 	//===========================================================================
339 	// List API
340 	//===========================================================================
341 	/**
342 		Return the number of items in the list owned by this variable (only valid if <a href="#isList">isList</a> is true).
343 		@returns Number
344 	*/
345 	getCount: function() {
346 		return this._isNull ? 0 : (this.isList ? ((this.data || 0).list || 0).length : 1);
347 	},
348 	// Returns a Variable representing item inIndex
349 	// If the item is currently raw data, it's replaced
350 	// with a new Variable. Created Variable is initialized 
351 	// with the raw list data unless inData is supplied.
352 	// If inData is supplied the Variable is populated with 
353 	// inData.
354 	_needItem: function(inIndex, inData) {
355 		// fetch the stored data object
356 		var item = this.data.list[inIndex];
357 		// optional raw data to initialize the object with
358 		var data = inData;
359 		if (!(item instanceof wm.Variable)) {
360 			// we want to populate with original raw data
361 			// unless override data iss provided
362 			data = inData || item;
363 			// create a new Variable to represent this data
364 			item = this.createVariable({/*name: "itemProxy",*/ type: this.type, _subNard: true, itemIndex: inIndex});
365 			this.data.list[inIndex] = item;
366 		}
367 		if (data !== undefined) {
368 			item.beginUpdate();
369 			item.setData(data);
370 			item.endUpdate();
371 		}
372 		return item;
373 	},
374 	/**
375 		Return an item by numeric index in the list owned by this variable (only valid if <a href="#isList">isList</a> is true).
376 		@param {Number} inIndex The numeric index of the item to fetch
377 		@returns Any 
378 	*/
379 	getItem: function(inIndex) {
380 		return this.isList && this._needItem(inIndex);
381 	},
382 	_populateItems: function() {
383 		for (var i=0, c = this.getCount(); i<c; i++)
384 			this.getItem(i);
385 	},
386 	// note: low level sort that requires a comparator function to be used.
387 	sort: function(inComparator) {
388 		this._populateItems();
389 		var l = this.isList && this.data && this.data.list;
390 		l && l.sort(inComparator);
391 	},
392 	/**
393 		Set the cursor by index. When data forms a list, the cursor indicates the item used in calls to getValue.
394 		@param {Number} inCursor The numeric index of the item to use as the Variable's 
395 		@returns Any 
396 	*/
397 	setCursor: function(inCursor) {
398 		this.cursor = Math.max(0, Math.min(this.getCount()-1, inCursor));
399 		this.notify();
400 	},
401 	/**
402 		Increments the cursor.
403 		@returns Any 
404 	*/
405 	setNext: function() {
406 		this.setCursor(this.cursor+1);
407 	},
408 	/**
409 		Decrements the cursor.
410 		@returns Any 
411 	*/
412 	setPrevious: function() {
413 		this.setCursor(this.cursor-1);
414 	},
415 	/**
416 		Sets the cursor to the first item.
417 		@returns Any 
418 	*/
419 	setFirst: function() {
420 		this.setCursor(0);
421 	},
422 	/**
423 		Sets the cursor to the last item.
424 		@returns Any 
425 	*/
426 	setLast: function() {
427 		this.setCursor(this.getCount()-1);
428 	},
429 	/**
430 		Retrieves the data item at the current list cursor. If data is not a list, returns the Variable
431 		@returns wm.Variable
432 	*/
433 	getCursorItem: function() {
434 		return this.getItem(this.cursor || 0) || this;
435 	},
436 	/**
437 		Set an item by numeric index in the list owned by this variable (only valid if <a href="#isList">isList</a> is true).
438 		@param {Number} inIndex The numeric index of the item to set
439 		@param {Any} inData The data to store
440 	*/
441 	setItem: function(inIndex, inData) {
442 		this._setItem(inIndex, inData);
443 		this.cursor = inIndex;
444 		this.notify();
445 	},
446 	_setItem: function(inIndex, inData) {
447 		if (this.isList)
448 			this._needItem(inIndex, inData);
449 	},
450 	/**
451 		Adds an item to the list of data. Only functions if data forms a list.
452 		@param {wm.Variable or Object} inData The data to add, either a an Object or wm.Variable
453 		@param {Number} inIndex (Optional) The numeric index at which to insert the data.
454 		@returns Any
455 	*/
456 	addItem: function(inData, inIndex) {
457 		this._addItem(inData, inIndex);
458 		this.cursor = inIndex;
459 		this.notify();
460 	},
461 	_addItem: function(inData, inIndex) {
462 		if (this.isList) {
463 			var c = this.getCount();
464 			if (inIndex >= 0 && inIndex < c)
465 				this.data.list.splice(inIndex, 0, {});
466 			else
467 				inIndex = this.getCount();
468 			this._setItem(inIndex, inData);
469 		}
470 	},
471 	/**
472 		Removes an item from the list of data. Only functions if data forms a list.
473 		@param {Number} inIndex The numeric index of the item to remove.
474 		@returns Any
475 	*/
476 	removeItem: function(inIndex) {
477 		this._removeItem(inIndex);
478 		this.cursor = 0;
479 		this.notify();
480 	},
481 	_removeItem: function(inIndex) {
482 		if (this.isList)
483 			this.data.list.splice(inIndex, 1);
484 	},
485 	// should we store this for faster access? (items have itemIndex, but this is not maintained)
486 	getItemIndex: function(inVariable) {
487 		if (!this.isList)
488 			return;
489 		var list = (this.data || 0).list || [];
490 		for (var i=0, l = list.length; i < l; i++) {
491 			if (inVariable == list[i])
492 				return i;
493 		}
494 	},
495 	//===========================================================================
496 	// Update Messaging
497 	//===========================================================================
498 	dataRootChanged: function() {
499 		if (this._subNard)
500 			return;
501 		// find first owner after root and send change message on that.
502 		// this should trigger rule #3 for bindings.
503 		var o = this.owner, p, root = this.getRoot();
504 		while (o && o != root) {
505 			p = o;
506 			o = o && o.owner;
507 		}
508 		var n = p ? p.getRuntimeId() : this.getRuntimeId();
509 		topic = n + "-rootChanged";
510 		wm.logging && console.group("<== ROOTCHANGED [", topic, "] published by Variable.dataRootChanged");
511 		dojo.publish(topic, [n]);
512 		wm.logging && console.groupEnd();
513 	},
514 	dataOwnerChanged: function() {
515 		if (this._updating)
516 			return;
517 		var n = this.getRuntimeId();
518 		var topic = n + "-ownerChanged";
519 		wm.logging && console.group("<== OWNERCHANGED [", topic, "] published by Variable.dataOwnerChanged");
520 		dojo.publish(topic, [n]);
521 		wm.logging && console.groupEnd();
522 		//
523 		// send root changed message
524 		if (this._allowLazyLoad)
525 			this.dataRootChanged();
526 		//
527 		var v = this.getCursorItem();
528 		for (var i in v.data) {
529 			wm.fire(v.data[i], "dataOwnerChanged");
530 		}
531 	},
532 	dataChanged: function() {
533 		if (this._updating)
534 			return;
535 		var id = this.getRuntimeId();
536 		var topic=[id, "-changed"].join('');
537 		wm.logging && console.group("<== CHANGED [", topic, "] published by Variable.dataChanged");
538 		dojo.publish(topic, [this]);
539 		// Rule: change notification is propagated up through owners
540 		// propagate change up only if this is a subNard.
541 		if (this._subNard)
542 			wm.fire(this.owner, "dataChanged");
543 		wm.logging && console.groupEnd();
544 	},
545 	// id-based notification
546 	dataValueChanged: function(inProp, inValue) {
547 		if (!this._updating) {
548 			// Can't simply call valueChanged; see note below.
549 			wm.Component.prototype.valueChanged.call(this, inProp, inValue);
550 			this.dataChanged();
551 		}
552 	},
553 	// id-based notification
554 	valueChanged: function(inProp, inValue) {
555 		// Code exists to deal with collisions between component props and data props in this class.
556 		// However, the distinction is lost in change notifications. Likely, data props should have
557 		// special ids to distinguish them. Until then, we simply avoid sending change notification
558 		// for properties when there is a collision.
559 		if (!this.isDataProp(inProp))
560 			this.inherited(arguments);
561 	},
562 	//===========================================================================
563 	// Referencing
564 	//===========================================================================
565 	setDataSet: function(inDataSet) {
566 		this.dataSet = "";
567 		if (inDataSet instanceof wm.Variable) {
568 			this.setType(inDataSet ? inDataSet.type : "wm.Variable");
569 			this.dataSet = inDataSet;
570 			this.cursor = inDataSet.cursor;
571 		}
572 		this.setData(inDataSet);
573 	},
574 	//===========================================================================
575 	// Property API
576 	//===========================================================================
577 	_isVariableProp: function(inPropName) {
578 		var typeInfo = this._dataSchema[inPropName];
579 		return Boolean(typeInfo && wm.typeManager.isStructuredType(typeInfo.type));
580 	},
581 	isDataProp: function(inProp) {
582 		return inProp in this._dataSchema;
583 	},
584 	_getValue: function(inProp) {
585 		return this.isDataProp(inProp) ? this._getDataValue(inProp) : this.inherited(arguments);
586 	},
587 	_setValue: function(n, v) {
588 		// if setting to default, then don't do data setting
589 		if ((this.schema[n]||0).defaultBindTarget || !this.isDataProp(n))
590 			this.inherited(arguments);
591 		else
592 			this._setDataValue(n, v);
593 	},
594 	//===========================================================================
595 	// Data Marshalling / Lazy Loading
596 	//===========================================================================
597 	createVariable: function(inProps, inPropName) {
598 		var v = new wm.Variable(inProps);
599 		v.owner = this;
600 		return v;
601 	},
602 	marshallVariable: function(inPropName, inTypeInfo, inVariable) {
603 		var
604 			p = inPropName, v = inVariable,
605 			t = inTypeInfo.isList ? '[' + inTypeInfo.type + ']' : inTypeInfo.type;
606 		if (!(v instanceof wm.Variable)) {
607 			v = this.createVariable({name: p, type: t, _subNard: true}, p);
608 			if (inVariable || inVariable === null) {
609 				v.beginUpdate();
610 				v.setData(inVariable);
611 				v.endUpdate();
612 			}
613 		}
614 		// lazy load!
615 		if (v._isStub() && this.canLazyLoad(inTypeInfo)) {
616 			this.beginUpdate();
617 			this.lazyLoadData(p, v);
618 			this.endUpdate();
619 		}
620 		return v;
621 	},
622 	_isStub: function() {
623 		if (!this._nostub && !this._isNull /*&& (!this.isList || !this.hasList())*/) {
624 			// stub if there is no data
625 			if (this.data === undefined)
626 				return true;
627 			// stub if we're a list and there's no list data
628 			if (this.isList || this.hasList())
629 				return !this.data.list || !this.data.list.length;
630 			// optionally treat as stub if there is any data v. if there is missing data
631 			// stub if dont' have data for any property not structured / list
632 			if (this._greedyLoadProps) {
633 				var schema = this._dataSchema, s;
634 				for (var i in schema) {
635 					s = schema[i];
636 					if (!s.isList && (this.data[i] === undefined) 
637 						&& !wm.typeManager.isStructuredType(s.type))
638 						return true;
639 				}
640 			// stub if we have no data
641 			} else if (wm.isEmpty(this.data))
642 				return true;
643 		}
644 		this._nostub = true;
645 		return false;
646 	},
647 	lazyLoadData: function(inPropName, inVariable) {
648 		var s = wm.getRuntimeService(this), v = inVariable;
649 		try{
650 			if (s.ready) {
651 				var d = this.getData();
652 				if (!wm.isEmpty(d)) {
653 					var args = [null, this.type, d, {properties: [inPropName]}];
654 					wm.logging && console.log("lazyLoad", inVariable.owner && inVariable.owner.getId(), args);
655 					s.requestSync("read", args);
656 					var r = s.result, propData = r && r[inPropName];
657 					if (propData) {
658 						v.beginUpdate();
659 						v.setData(propData);
660 						v.endUpdate();
661 					}
662 				};
663 			}
664 		}catch(x){}
665 	},
666 	canLazyLoad: function(inTypeInfo) {
667 		if (this._updating || !wm.typeManager.getLiveService(inTypeInfo.type))
668 			return;
669 		// FIXME: prevent lazy loading if livelayout is not ready
670 		// reference to studio especially bad.
671 		if (this.isDesignLoaded() && !studio.isLiveLayoutReady())
672 			return false;
673 		var o = this;
674 		// if this variable or any owner does not allow lazy loading then cannot lazy load!
675 		while (o instanceof wm.Variable) {
676 			if (!o._allowLazyLoad)
677 				return false;
678 			o = o.owner;
679 		}
680 		// lazy load if the type is a list or we have required data to read.
681 		return inTypeInfo.isList || this._hasRequiredReadData();
682 	},
683 	// check our schema and data to see if 
684 	// we have all necessary data that is required
685 	// for the lazy load "read" operation
686 	_hasRequiredReadData: function() {
687 		var ds = this._dataSchema, s, d;
688 		for (var i in ds) {
689 			s = ds[i];
690 			if (s.include && dojo.indexOf(s.include, "read") > -1) {
691 				d = this.data[i];
692 				if (d === undefined || d === null)
693 					return false;
694 			}
695 		}
696 		return true;
697 	}
698 });
699 
700 // FIXME: variable should have a data loader which can optionally have a liveView.
701 // A difficulty is that liveView is responsible both for data to load and storing field info
702 // that can be used to create ui.
703 // The issue is made worse by the need to copy variables (and associated liveViews)
704 // extension to extend Variable to load data with a liveView
705 wm.Variable.extend({
706 	_includeListProps: false,
707 	createVariable: function(inProps, inPropName) {
708 		inProps = inProps || {};
709 		inProps.liveView = this.liveView;
710 		var r = this._rootField, n = inPropName;
711 		inProps._rootField = r && inPropName ? r + "." + inPropName : (inPropName || "");
712 		var v = new wm.Variable(inProps);
713 		v.owner = this;
714 		return v;
715 	},
716 	setDataSet: function(inDataSet) {
717 		this.dataSet = "";
718 		if (inDataSet instanceof wm.Variable) {
719 			this._rootField = inDataSet._rootField || "";
720 			this.setLiveView(inDataSet.liveView);
721 			this.setType(inDataSet ? inDataSet.type : "wm.Variable");
722 			this.dataSet = inDataSet;
723 			this.cursor = inDataSet.cursor;
724 		}
725 		this.setData(inDataSet);
726 	},
727 	_getEagerProps: function(inVariable) {
728 		var
729 			v = inVariable,
730 			props = this.liveView ? this.liveView.getSubRelated(v._rootField) : [],
731 			schema = wm.typeManager.getTypeSchema(v.type);
732 		return this._includeListProps ? props :
733 			dojo.filter(props, function(r) {
734 				return !wm.typeManager.isPropInList(schema, r);
735 			});
736 	},
737 	_getLoadProps: function(inPropName, inVariable) {
738 		return [inPropName].concat(dojo.map(this._getEagerProps(inVariable), function(r) {
739 			return [inPropName, r].join(".");
740 		}));
741 	},
742 	// FIXME: avoid sync request
743 	lazyLoadData: function(inPropName, inVariable) {
744 		var s = wm.getRuntimeService(this), v = inVariable;
745 		try{
746 			if (s.ready) {
747 				var d = this.getData();
748 				if (!wm.isEmpty(d)) {
749 					var
750 						props = this.liveView ? this._getLoadProps(inPropName, v) : inPropName,
751 						args = [null, this.type, d, {properties: props}];
752 					//console.log("lazyLoad", this.getId(), args);
753 					wm.logging && console.log("lazyLoad", inVariable.owner && inVariable.owner.getId(), args);
754 					s.requestSync("read", args);
755 					var r = s.result, propData = r && r[inPropName];
756 					if (propData) {
757 						v.beginUpdate();
758 						v.setData(propData);
759 						v.endUpdate();
760 					}
761 					// FIXME: non-sync, need to protect against multiple requests?
762 					// create a queue of requests?
763 					/*if (!this._inflight) {
764 						var def = s.requestAsync("read", args);
765 						this._inflight = true;
766 						def.addBoth(dojo.hitch(this, function(r) {
767 							this._inflight = false;
768 							return r;
769 						}));
770 						def.addCallback(dojo.hitch(this, function(r) {
771 							var propData = r && r[inPropName];
772 							if (propData) {
773 								v.beginUpdate();
774 								v.setData(propData);
775 								v.endUpdate();
776 								console.log("got data!", "notify!", this.getId(), this._updating);
777 								this.owner.notify();
778 							}
779 							return r;
780 						}));
781 					}*/
782 				}
783 			}
784 		}catch(x){
785 			wm.logging && console.log("Failed to lazy load.", args);
786 		}
787 	},
788 	setLiveView: function(inLiveView) {
789 		this.liveView = inLiveView;
790 	},
791 	getViewType: function() {
792 		return this.liveView  && this.liveView.getSubType(this._rootField);
793 	},
794 	getViewFields: function() {
795 		return (this.liveView && this.liveView.getSubView(this._rootField)) || [];
796 	},
797 	getViewListFields: function() {
798 		return (this.liveView && this.liveView.getListView(this._rootField)) || [];
799 	},
800 	getViewRelated: function() {
801 		return (this.liveView && this.liveView.getSubRelated(this._rootField)) || [];
802 	}
803 });
804 
805 //===========================================================================
806 // Design Time Extensions
807 //===========================================================================
808 wm.Object.extendSchema(wm.Variable, {
809 	data: { ignore: 1 },
810 	isList: { ignore: 1 },
811 	cursor: { ignore: 1},
812 	isPrimitive: { ignore: 1},
813 	type: { ignore: 0, group: "common", order: 1},
814 	json: { group: "data", order: 5},
815 	dataSet: { readonly: 1, bindable: 1, group: "data", order: 0, defaultBindTarget: 1, isObject: true, type: "any", categoryParent: "Properties", categoryProps: {content: "dataSet", inspector: "Data"} }
816 });
817 
818 /**#@+ @design */
819 wm.Variable.extend({
820 	/** @lends wm.Variable.prototype */
821 	makePropEdit: function(inName, inValue, inDefault) {
822 		switch (inName) {
823 			case "type":
824 				return new wm.propEdit.DataTypesSelect({component: this, name: inName, value: inValue});
825 			case "json":
826 				return makeTextPropEdit(inName, inValue, inDefault)
827 		}
828 		return this.inherited(arguments);
829 	},
830 	isListBindable: function() {
831 		return this.isList;
832 	}
833 });
834 /**#@- @design */
835