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