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.LiveView");
 19 dojo.require("wm.base.Component");
 20 dojo.require('wm.base.lib.data');
 21 
 22 // View related utitlities
 23 wm.getViewField = function(inTypeSchema, inPropName) {
 24 	if (inTypeSchema) {
 25 		var propInfo = wm.typeManager.getPropertyInfoFromSchema(inTypeSchema, inPropName);
 26 		return {
 27 			caption: wm.capitalize(inPropName.split(".").pop()),
 28 			sortable: true,
 29 			dataIndex: inPropName,
 30 			type: propInfo.type,
 31 			displayType: wm.getPrimitiveDisplayType(propInfo.type),
 32 			required: propInfo.required,
 33 			readonly: dojo.indexOf(propInfo.noChange, "read") >= 0,
 34 			includeLists: true,
 35 			includeForms: true
 36 		};
 37 	}
 38 }
 39 
 40 wm.getDefaultView = function(inTypeName, inPropertyPath) {
 41 	inPropertyPath = inPropertyPath || "";
 42 	var
 43 		v = [], tm = wm.typeManager,
 44 		schema = tm.getTypeSchema(inTypeName),
 45 		propSchema = inPropertyPath ? tm.getTypeSchema(tm.getPropertyInfoFromSchema(schema, inPropertyPath).type) : schema,
 46 		fields = wm.typeManager.getSimplePropNames(propSchema);
 47 	wm.forEach(fields, function(f) {
 48 		v.push(wm.getViewField(schema, (inPropertyPath ? inPropertyPath + "." : "") + f));
 49 	});
 50 	return v;
 51 }
 52 
 53 /**
 54 	Component that provides information about live service, datatype,
 55 	related objects, and field information.
 56 	@name wm.LiveView
 57 	@class
 58 	@extends wm.Component
 59 */
 60 dojo.declare("wm.LiveView", wm.Component, {
 61 	/** @lends wm.LiveView.prototype */
 62 	/** Name of the service on which this view operates. */
 63 	service: "",
 64 	/** Fully qualified data type we operate on. */
 65 	dataType: "",
 66 	/** Fields to fetch */
 67 	related: [],
 68 	/** Fields to display */
 69 	view: [],
 70 	constructor: function() {
 71 		this.related = [];
 72 		this.view = [];
 73 	},
 74 	init: function() {
 75 		this.inherited(arguments);
 76 		this.setDataType(this.dataType);
 77 	},
 78 	loaded: function() {
 79 		this.inherited(arguments);
 80 		this.viewChanged();
 81 	},
 82 	viewChanged: function() {
 83 		dojo.publish(this.getRuntimeId() + "-viewChanged", [this.getId()]);
 84 	},
 85 	createDefaultView: function() {
 86 		this.setFields(this.related || [], wm.getDefaultView(this.dataType));
 87 	},
 88 	setFields: function(inRelated, inView) {
 89 		this.related = inRelated;
 90 		this._sortView(inView);
 91 		this.view = inView;
 92 	},
 93 	getFieldIndex: function(inField) {
 94 		var di = dojo.isObject(inField) ? inField.dataIndex : inField;
 95 		for (var i=0, view=this.view, f; f=view[i]; i++)
 96 			if (f.dataIndex == di)
 97 				return i;
 98 		return -1;
 99 	},
100 	hasField: function(inField) {
101 		return (this.getFieldIndex(inField) > -1);
102 	},
103 	getRelatedIndex: function(inRelated) {
104 		for (var i=0, related=this.related, r; r=related[i]; i++)
105 			if (r == inRelated)
106 				return i;
107 		return -1;
108 	},
109 	hasRelated: function(inRelated) {
110 		return (this.getRelatedIndex(inRelated) > -1);
111 	},
112 	addField: function(inField) {
113 		var f = inField && wm.getViewField(wm.typeManager.getTypeSchema(this.dataType), inField);
114 		if (f && !this.hasField(f)) {
115 			this.view.push(f)
116 			this._sortView(this.view);
117 		}
118 		return f;
119 	},
120 	removeField: function(inField) {
121 		var i = this.getFieldIndex(inField);
122 		if (i > -1)
123 			this.view.splice(i, 1);
124 	},
125 	addRelated: function(inRelated) {
126 		if (inRelated && !this.hasRelated(inRelated)) {
127 			this.related.push(inRelated);
128 			this.addRelatedDefaultView(inRelated);
129 		}
130 	},
131 	removeRelated: function(inRelated) {
132 		var i = this.getRelatedIndex(inRelated);
133 		if (i > -1)
134 			this.related.splice(i, 1);
135 	},
136 	addRelatedDefaultView: function(inRelated) {
137 		var relatedFields = wm.getDefaultView(this.dataType, inRelated);
138 		dojo.forEach(relatedFields, function(f) {
139 			if (!this.hasField(f))
140 				this.view.push(f);
141 		}, this);
142 		this._sortView();
143 	},
144 	
145 	_sortView: function(inView) {
146 		if (dojo.isArray(inView)) {
147 			var t = this.dataType;
148 			// sort view by order or alpha place property chain
149 			inView.sort(function(a, b) {
150 				// if either has order, compare by order
151 				if (wm.isNumber(a.order) || wm.isNumber(b.order)) {
152 					return wm.compareNumbers(a.order, b.order);
153 				// otherwise compare by "shallowest" or alpha
154 				} else {
155 					a = a.dataIndex;
156 					b = b.dataIndex;
157 					var al = a.split(".").length, bl = b.split(".").length;
158 					return al == bl ? wm.data.compare(a, b) : wm.compareNumbers(al, bl);
159 				}
160 			});
161 		}
162 	},
163 	_copyView: function(inView) {
164 		var view = [];
165 		for (var i=0, v; (v=inView[i]); i++)
166 			view.push(dojo.mixin({}, v));
167 		return view;
168 	},
169 	getViewById: function(inLiveViewId) {
170 		if (inLiveViewId instanceof wm.LiveView)
171 			return inLiveViewId;
172 		else if (inLiveViewId)
173 			return this.getRoot().app.getValueById(inLiveViewId);
174 	},
175 	copyLiveView: function(inLiveView) {
176 		var lv = this.getViewById(inLiveView);
177 		if (lv) {
178 			this.setService(lv.service);
179 			this.setDataType(lv.dataType);
180 			var v = this._copyView(lv.view);
181 			this.setFields(lv.related, v);
182 		} else
183 			this.clearView();
184 	},
185 	clearView: function() {
186 		this.setService("");
187 		this.setDataType("");
188 		this.setFields([], []);
189 	},
190 	setService: function(inService) {
191 		this.service = inService;
192 	},
193 	//$ Set the dataType for the dataView. This is a type that supports crud operations.
194 	setDataType: function(inType) {
195 		var t = this.dataType;
196 		this.dataType = inType;
197 		if (t != this.dataType)
198 			this.dataTypeChanged();
199 		if (this._defaultView)
200 			this.createDefaultView();
201 	},
202 	dataTypeChanged: function() {
203 		// FIXME: we need to do something smart here. changing the datatype should probably zot
204 		// the view info and may need to inform things bound to this and/or update.
205 		this.related = [];
206 		this.view = [];
207 	},
208 	hasRelatedProp: function(inRelatedProp) {
209 		for (var i=0, related=this.related, r; (r=related[i]); i++)
210 			if (r == inRelatedProp)
211 				return true;
212 	},
213 	getListView: function(inPropPath) {
214 		var schema = wm.typeManager.getTypeSchema(this.getSubType(inPropPath));
215 		return dojo.filter(this.getSubView(inPropPath), function(v) {
216 			return !wm.typeManager.isPropInList(schema, v.dataIndex);
217 		})
218 	},
219 	// get the type of a property path from our dataType
220 	getSubType: function(inPropPath) {
221 		if (inPropPath) {
222 			var schema = wm.typeManager.getTypeSchema(this.dataType);
223 			return (schema && (wm.typeManager.getPropertyInfoFromSchema(schema, inPropPath) || 0).type) || this.dataType;
224 		} else
225 			return this.dataType;
226 	},
227 	// get a related list starting at inPropPath
228 	getSubRelated: function(inPropPath) {
229 		inPropPath = inPropPath ? inPropPath + "." : "";
230 		if (inPropPath) {
231 			var list = [], l = inPropPath.length;
232 			dojo.forEach(this.related, function(r) {
233 				if (r.indexOf(inPropPath) == 0)
234 					list.push(r.substring(l));
235 			});
236 			return list;
237 		} else
238 			return this.related;
239 	},
240 	// get a view starting at inPropPath
241 	getSubView: function(inPropPath) {
242 		inPropPath = inPropPath ? inPropPath + "." : "";
243 		var view = this._copyView(this.view);
244 		if (inPropPath) {
245 			var list = [], l = inPropPath.length;
246 			dojo.forEach(view, function(v) {
247 				if (v.dataIndex.indexOf(inPropPath) == 0) {
248 					v.dataIndex = v.dataIndex.substring(l);
249 					list.push(v);
250 				}
251 			});
252 			return list;
253 		} else
254 			return view;
255 	}
256 });
257 
258 wm.Object.extendSchema(wm.LiveView, {
259 	related: { ignore: 1, writeonly: 1 },
260 	view: { ignore: 1, writeonly: 1 },
261 	service: { ignore: 1, writeonly: 1 },
262 	dataType: {ignore: 1, writeonly: 1}
263 });
264