      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