1 /* 2 * Copyright (C) 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.LiveVariable"); 19 dojo.require("wm.base.components.ServiceVariable"); 20 dojo.require("wm.base.components.LiveView"); 21 22 /** 23 Component that marshalls a LiveView and can perform all data operations: read, insert, update, delete. 24 @name wm.LiveVariable 25 @class 26 @extends wm.ServiceVariable 27 */ 28 dojo.declare("wm.LiveVariable", wm.ServiceVariable, { 29 /** 30 @lends wm.LiveVariable.prototype 31 */ 32 autoUpdate: true, 33 startUpdate: true, 34 operation: "read", 35 /** First row of results */ 36 firstRow: 0, 37 /** Optional starting source data */ 38 sourceData: null, 39 /** Method by which data is filtered */ 40 matchMode: "start", 41 /** Toggle for data sorting to ignore alphabetical case or not. */ 42 ignoreCase: true, 43 /** Optional order by clause, example "asc: cityId, desc: city" */ 44 orderBy: "", 45 /** LiveView or LiveTable from which this LiveVariable gets its field information; can use a liveView or liveTable */ 46 liveSource: null, 47 /** our liveView **/ 48 liveView: null, 49 /** Maximum number of results to return */ 50 maxResults: 0, 51 designMaxResults: 500, 52 /** Field in view to use as our root object / type */ 53 _rootField: "", 54 destroy: function() { 55 this._unsubscribeLiveView(); 56 this.inherited(arguments); 57 }, 58 init: function() { 59 this.inherited(arguments); 60 this.filter = new wm.Variable({name: "filter", owner: this, type: this.type || "any" }); 61 this.sourceData = new wm.Variable({name: "sourceData", owner: this, type: this.type || "any" }); 62 this.subscribe(this.filter.getRuntimeId() + "-changed", this, "filterChanged"); 63 this.subscribe(this.sourceData.getRuntimeId() + "-changed", this, "sourceDataChanged"); 64 }, 65 postInit: function() { 66 this.inherited(arguments); 67 // initialize via liveSource or optionally directly with a liveView) 68 if (this.liveSource) 69 this.setLiveSource(this.liveSource); 70 else 71 this.setLiveView(this.liveView || this.createLiveView(this.type)); 72 this.doAutoUpdate(); 73 }, 74 _subscribeLiveView: function() { 75 this._unsubscribeLiveView(); 76 if (this.liveView) 77 this._liveViewSubscription = dojo.subscribe(this.liveView.getRuntimeId() + "-viewChanged", dojo.hitch(this, "_liveViewChanged")); 78 }, 79 _unsubscribeLiveView: function() { 80 dojo.unsubscribe(this._liveViewSubscription); 81 this._liveViewSubscription = null; 82 }, 83 isLiveType: function() { 84 return wm.typeManager.getLiveService(this.type); 85 }, 86 doAutoUpdate: function() { 87 if (this.isLiveType()) 88 this.inherited(arguments); 89 }, 90 filterChanged: function() { 91 this.doAutoUpdate(); 92 }, 93 sourceDataChanged: function() { 94 this.doAutoUpdate(); 95 }, 96 /** Set the filter used for read operations */ 97 setFilter: function(inFilter) { 98 if ((inFilter || 0).type == this.type) { 99 this.filter.setDataSet(inFilter); 100 } 101 }, 102 /** Set the orderBy property used for read operations */ 103 setOrderBy: function(inOrderBy) { 104 this.orderBy = inOrderBy; 105 this.doAutoUpdate(); 106 }, 107 /** Set the source data which is used for operations. */ 108 setSourceData: function(inSourceData) { 109 var liveType = this.isLiveType(); 110 if (!liveType || (inSourceData || 0).type == this.type) { 111 this.sourceData.setDataSet(inSourceData); 112 if (!liveType) { 113 this._updating++; 114 this.setLiveSource(this.sourceData.type); 115 this._updating--; 116 } 117 } 118 }, 119 // ========================================================== 120 // LiveView integration 121 // ========================================================== 122 /** Set the LiveView or LiveTable from which we will get data information */ 123 /* valid input: LiveView full id or LiveTable full name */ 124 setLiveSource: function(inLiveSource) { 125 var 126 s =this.liveSource = inLiveSource, 127 v = this.getRoot().app.getValueById(s) || this.createLiveView(s); 128 if (v) 129 this.setLiveView(v); 130 this.doAutoUpdate(); 131 }, 132 setLiveView: function(inLiveView) { 133 this.clearData(); 134 this.liveView = inLiveView; 135 this._subscribeLiveView(); 136 this.setType(this.getViewType()); 137 }, 138 createLiveView: function(inType) { 139 return new wm.LiveView({ 140 name: "liveView", 141 owner: this, 142 dataType: inType, 143 _defaultView: true 144 }); 145 }, 146 setType: function(inType) { 147 this.inherited(arguments); 148 this.filter.setType(this.type); 149 this.sourceData.setType(this.type); 150 if (!this._updating && this.$.binding) 151 this.$.binding.refresh(); 152 }, 153 _liveViewChanged: function() { 154 this.setType(this.liveView.dataType); 155 if (this.isDesignLoaded()) 156 this.doAutoUpdate(); 157 }, 158 // ========================================================== 159 // Server I/O 160 // ========================================================== 161 _getCanUpdate: function() { 162 return this.inherited(arguments) && 163 !(this.operation == "read" && this._isSourceDataBound() && wm.isEmpty(this.sourceData.getData()) ); 164 }, 165 // FIXME: need to zot this 166 operationChanged: function() { 167 }, 168 _update: function() { 169 // note: runtime service only available when application is deployed 170 // so must wait until here to set it. 171 this._service = wm.getRuntimeService(this); 172 //console.log(this.name, "update"); 173 return this.inherited(arguments); 174 }, 175 getArgs: function() { 176 var 177 d = this.sourceData.getData(), 178 t = this.sourceData.type || this.type, 179 s = wm.typeManager.getService(this.type), 180 args = [s, t, wm.isEmpty(d) ? null : d]; 181 if (this.operation == "read") { 182 args = args.concat(this._getReadArguments()); 183 } 184 return args; 185 }, 186 _getReadArguments: function() { 187 var 188 props = {properties: this._getEagerProps(this), filters: this._getFilters(), matchMode: this.matchMode, ignoreCase: this.ignoreCase}, 189 paging = this.orderBy ? {orderBy: (this.orderBy || "").split(",")} : {}, 190 max = this.isDesignLoaded() ? this.designMaxResults : this.maxResults, 191 results = max ? { maxResults: max, firstResult: this.firstRow } : {}; 192 dojo.mixin(paging, results); 193 return [props, paging]; 194 }, 195 _getFilters: function() { 196 return this._getFilterValues(this.filter.getData()); 197 }, 198 _getFilterValues: function(inData, inPrefix) { 199 var f = [], d, p; 200 for (var i in inData) { 201 d = inData[i]; 202 p = (inPrefix ? (inPrefix ||"") + "." : "") + i; 203 if (dojo.isObject(d) && d !== null) 204 f = f.concat(this._getFilterValues(d, p)); 205 else if (p !== undefined && d !== undefined) 206 f.push(p + "=" + d); 207 } 208 return f; 209 }, 210 _isSourceDataBound: function() { 211 var wires = this.$.binding.wires, w; 212 for (var i in wires) { 213 w = wires[i]; 214 if ((w.targetProperty || "").indexOf("sourceData") == 0) 215 return true; 216 } 217 }, 218 processResult: function(inResult) { 219 this.dataSetCount = this._service.fullResult.dataSetSize; 220 this.inherited(arguments); 221 }, 222 //=========================================================================== 223 // Paging 224 //=========================================================================== 225 /** Return the current data page; only relevant when maxResults is set. */ 226 getPage: function() { 227 return Math.floor(this.firstRow / (this.maxResults || 1)); 228 }, 229 /** Return the total number of data pages. */ 230 getTotalPages: function() { 231 return Math.ceil((this.dataSetCount || 1) / (this.maxResults || 1)); 232 }, 233 /** Set and retrieve the current data page. 234 @param {Number} inPageIndex the page number to set 235 */ 236 setPage: function(inPageIndex) { 237 inPageIndex = Math.max(0, Math.min(this.getTotalPages()-1, inPageIndex)); 238 this.firstRow = inPageIndex * (this.maxResults || 0); 239 this.update(); 240 }, 241 /** Set and retrieve the next page of data. */ 242 setNextPage: function() { 243 this.setPage(this.getPage()+1); 244 }, 245 /** Set and retrieve the previous page of data. */ 246 setPreviousPage: function() { 247 this.setPage(this.getPage()-1); 248 }, 249 /** Set and retrieve the first page of data. */ 250 setFirstPage: function() { 251 this.setPage(0); 252 }, 253 /** Set and retrieve the last page of data. */ 254 setLastPage: function() { 255 this.setPage(this.getTotalPages()-1); 256 } 257 }); 258 259 //=========================================================================== 260 // Design Only 261 //=========================================================================== 262 263 wm.Object.extendSchema(wm.LiveVariable, { 264 update: {ignore: 1, publicEvent: 1}, 265 related: { ignore: 1}, 266 view: { ignore: 1}, 267 service: { ignore: 1}, 268 dataType: { ignore: 1}, 269 operation: { group: "data", order: 0}, 270 input: {ignore: 1}, 271 liveSource: { group: "data", order: 1}, 272 liveView: { ignore: 1}, 273 sourceData: {ignore: 1, group: "data", order: 3, bindTarget: 1, categoryParent: "Properties", categoryProps: {component: "sourceData", inspector: "Data"}}, 274 filter: { ignore: 1, group: "data", order: 5, bindTarget: 1, categoryParent: "Properties", categoryProps: {component: "filter", inspector: "Data"}}, 275 matchMode: {group: "data", order: 10}, 276 firstRow: {group: "data", order: 15}, 277 maxResults: {group: "data", order: 17}, 278 designMaxResults: {group: "data", order: 18}, 279 orderBy: {group: "data", order: 19}, 280 ignoreCase: {group: "data", order: 20}, 281 configure: { ignore: 1 }, 282 dataSetCount: { ignore: 1 } 283 }); 284 285 wm.LiveVariable.extend({ 286 _operations: ["read", "insert", "update", "delete"], 287 _matchModes: ["start", "end", "anywhere", "exact"], 288 listProperties: function() { 289 var 290 p = this.inherited(arguments), 291 r = (this.operation == "read"); 292 p.matchMode.ignore = !r; 293 p.firstRow.ignore = !r; 294 p.maxResults.ignore = !r; 295 p.designMaxResults.ignore = !r; 296 p.orderBy.ignore = !r; 297 p.ignoreCase.ignore = !r; 298 p.filter.bindTarget = r; 299 p.filter.categoryParent = r ? "Properties" : ""; 300 return p; 301 }, 302 isListBindable: function() { 303 return this.operation == "read" ? !(this.sourceData && !wm.isEmpty(this.sourceData.getData())) : false; 304 }, 305 designCreate: function() { 306 this.inherited(arguments); 307 this.subscribe("wmwidget-idchanged", this, "componentNameChanged"); 308 309 }, 310 componentNameChanged: function(inOldId, inNewId, inOldRtId, inNewRtId) { 311 if (inOldId == this.liveSource) 312 this.setLiveSource(inNewId); 313 }, 314 set_operation: function(inOperation) { 315 this.operation = inOperation; 316 // just a good idea for safety 317 if (this.isDesignLoaded()) { 318 // automatically set autoUpdate to true if we're reading, 319 // since this is the default anyway, otherwise set to false. 320 this.setAutoUpdate(inOperation == "read"); 321 if (studio.selected == this) 322 studio.inspector.inspect(this); 323 } 324 }, 325 set_liveSource: function(inLiveSource) { 326 this.setLiveSource(inLiveSource); 327 if (this.isDesignLoaded() && studio.selected == this) 328 studio.inspector.inspect(this); 329 }, 330 set_sourceData: function(inSourceData) { 331 this.setSourceData(inSourceData); 332 if (this.isDesignLoaded() && studio.selected == this) 333 studio.inspector.inspect(this); 334 }, 335 set_filter: function(inFilter) { 336 this.setFilter(inFilter); 337 if (this.isDesignLoaded() && studio.selected == this) 338 studio.inspector.inspect(this); 339 }, 340 checkOrderBy: function(inOrderBy) { 341 var 342 orderParts = (inOrderBy || "").split(','), 343 re = new RegExp("^(?:asc|desc)\:", "i"); 344 for (var i=0, o; (o = orderParts[i]); i++) 345 if (!dojo.trim(o).match(re)) { 346 alert("Each property used in the orderBy clause must be of the form asc|desc: <propertyPath>. \"" + o + "\" does not match this format." + 347 " The current orderBy clause will generate an error and should be corrected."); 348 return; 349 } 350 return true; 351 }, 352 set_orderBy: function(inOrderBy) { 353 this.checkOrderBy(inOrderBy); 354 this.setOrderBy(inOrderBy); 355 }, 356 makePropEdit: function(inName, inValue, inDefault) { 357 switch (inName) { 358 case "liveSource": 359 return new wm.propEdit.LiveSourcesSelect({component: this, name: inName, value: inValue}); 360 case "matchMode": 361 return makeSelectPropEdit(inName, inValue, this._matchModes, inDefault); 362 case "operation": 363 return makeSelectPropEdit(inName, inValue, this._operations, inDefault); 364 } 365 return this.inherited(arguments); 366 } 367 }); 368