1
|
/*
|
2
|
License: LGPL as per: http://www.gnu.org/copyleft/lesser.html
|
3
|
$Id$
|
4
|
*/
|
5
|
|
6
|
/**
|
7
|
* Base Model class to be inherited by all Model objects and provdes methods
|
8
|
* and properties common to all models.
|
9
|
* Stores the XML document as the .doc property of the model.
|
10
|
* Inherits from the Listener class so all models are also listener objects that
|
11
|
* can call registered listeners.
|
12
|
* @constructor
|
13
|
* @base Listener
|
14
|
* @author Cameron Shorter
|
15
|
* @param modelNode The model's XML object node from the configuration document.
|
16
|
* @param parentModel The model object that this model belongs to.
|
17
|
*/
|
18
|
function ModelBase(modelNode, parentModel) {
|
19
|
// Inherit the Listener functions and parameters
|
20
|
Listener.apply(this);
|
21
|
|
22
|
//models are loaded asynchronously by default;
|
23
|
this.async = true; //change to false for sync loading
|
24
|
this.contentType = "text/xml";
|
25
|
|
26
|
this.modelNode = modelNode;
|
27
|
var idAttr = modelNode.attributes.getNamedItem("id");
|
28
|
if (idAttr) {
|
29
|
this.id = idAttr.nodeValue;
|
30
|
} else {
|
31
|
//auto generated unique ID assigned to this model
|
32
|
this.id = "MbModel_" + mbIds.getId();
|
33
|
}
|
34
|
|
35
|
//get the human readable title for the model
|
36
|
var titleNode = modelNode.selectSingleNode("mb:title");
|
37
|
if (titleNode) {
|
38
|
this.title = titleNode.firstChild.nodeValue;
|
39
|
} else {
|
40
|
this.title = this.id;
|
41
|
}
|
42
|
|
43
|
// set an empty debug property which turns of alert messages for a
|
44
|
// particular model
|
45
|
if(modelNode.selectSingleNode("mb:debug"))this.debug="true";
|
46
|
|
47
|
/**
|
48
|
* set the initial model URL in config.
|
49
|
* the URL can also be passed in as a URL parameter by using the model ID
|
50
|
* as the parameter name (which takes precendence over the config file)
|
51
|
**/
|
52
|
if (window.cgiArgs[this.id]) {
|
53
|
this.url = window.cgiArgs[this.id];
|
54
|
} else if (window[this.id]) {
|
55
|
this.url = window[this.id];
|
56
|
} else if (modelNode.url) {
|
57
|
this.url = modelNode.url;
|
58
|
} else {
|
59
|
var defaultModel = modelNode.selectSingleNode("mb:defaultModelUrl");
|
60
|
if (defaultModel) this.url = defaultModel.firstChild.nodeValue;
|
61
|
}
|
62
|
|
63
|
//set the method property
|
64
|
var method = modelNode.selectSingleNode("mb:method");
|
65
|
if (method) {
|
66
|
this.method = method.firstChild.nodeValue;
|
67
|
} else {
|
68
|
this.method = "get";
|
69
|
}
|
70
|
|
71
|
//set the namespace property
|
72
|
var namespace = modelNode.selectSingleNode("mb:namespace");
|
73
|
if (namespace) {
|
74
|
this.namespace = namespace.firstChild.nodeValue;
|
75
|
}
|
76
|
|
77
|
var templateAttr = modelNode.attributes.getNamedItem("template");
|
78
|
if (templateAttr) {
|
79
|
this.template = (templateAttr.nodeValue=="true")?true:false;
|
80
|
this.modelNode.removeAttribute("template");
|
81
|
}
|
82
|
|
83
|
//get the xpath to select nodes from the parent doc
|
84
|
var nodeSelectXpath = modelNode.selectSingleNode("mb:nodeSelectXpath");
|
85
|
if (nodeSelectXpath) {
|
86
|
this.nodeSelectXpath = nodeSelectXpath.firstChild.nodeValue;
|
87
|
}
|
88
|
|
89
|
/**
|
90
|
* Get the value of a node as selected by an XPath expression.1
|
91
|
* @param objRef Reference to this node.
|
92
|
* @param xpath XPath of the node to update.
|
93
|
* @return value of the node or null if XPath does not find a node.
|
94
|
*/
|
95
|
this.getXpathValue=function(objRef,xpath){
|
96
|
if (!objRef.doc) return null;
|
97
|
node=objRef.doc.selectSingleNode(xpath);
|
98
|
if(node && node.firstChild){
|
99
|
return node.firstChild.nodeValue;
|
100
|
}else{
|
101
|
return null;
|
102
|
}
|
103
|
}
|
104
|
|
105
|
/**
|
106
|
* Update the value of a node within this model's XML document.
|
107
|
* Triggers a refresh event from the model.
|
108
|
* @param objRef Reference to this node.
|
109
|
* @param xpath Xpath of the node to update.
|
110
|
* @param value Node's new value.
|
111
|
* @param refresh determines if the model should be refreshed (optional).
|
112
|
* @return Returns false if Xpath does not find a node.
|
113
|
*/
|
114
|
this.setXpathValue=function(objRef,xpath,value,refresh){
|
115
|
if (refresh==null) refresh=true;
|
116
|
var node=objRef.doc.selectSingleNode(xpath);
|
117
|
if(node){
|
118
|
if(node.firstChild){
|
119
|
node.firstChild.nodeValue=value;
|
120
|
}else{
|
121
|
dom=Sarissa.getDomDocument();
|
122
|
v=dom.createTextNode(value);
|
123
|
node.appendChild(v);
|
124
|
}
|
125
|
if (refresh) objRef.setParam("refresh");
|
126
|
return true;
|
127
|
}else{
|
128
|
return false;
|
129
|
}
|
130
|
}
|
131
|
|
132
|
/**
|
133
|
* Load a Model's document.
|
134
|
* This will only occur if the model.url property is set.
|
135
|
* Calling this method triggers several events:
|
136
|
* modelStatus - to indicate that the model state is changing
|
137
|
* newModel - to give widgets a chance to clear themselves before the doc is loaded
|
138
|
* loadModel - to indicate that the document is loaded successfully
|
139
|
*
|
140
|
* @param objRef Pointer to the model object being loaded.
|
141
|
*/
|
142
|
this.loadModelDoc = function(objRef){
|
143
|
//alert("loading:"+objRef.url);
|
144
|
|
145
|
if (objRef.url) {
|
146
|
objRef.callListeners( "newModel" );
|
147
|
objRef.setParam("modelStatus","loading");
|
148
|
|
149
|
if (objRef.contentType == "image") {
|
150
|
//image models are set as a DOM image object
|
151
|
objRef.doc = new Image();
|
152
|
objRef.doc.src = objRef.url;
|
153
|
//objRef.doc.onload = callback //TBD: for when image is loaded
|
154
|
|
155
|
} else {
|
156
|
//XML content type
|
157
|
var xmlHttp = new XMLHttpRequest();
|
158
|
|
159
|
var sUri = objRef.url;
|
160
|
if ( sUri.indexOf("http://")==0 ) {
|
161
|
if (objRef.method == "get") {
|
162
|
sUri = getProxyPlusUrl(sUri);
|
163
|
} else {
|
164
|
sUri = config.proxyUrl;
|
165
|
}
|
166
|
}
|
167
|
xmlHttp.open(objRef.method, sUri, objRef.async);
|
168
|
if (objRef.method == "post") {
|
169
|
xmlHttp.setRequestHeader("content-type",objRef.contentType);
|
170
|
xmlHttp.setRequestHeader("serverUrl",objRef.url);
|
171
|
}
|
172
|
|
173
|
xmlHttp.onreadystatechange = function() {
|
174
|
objRef.setParam("modelStatus",httpStatusMsg[xmlHttp.readyState]);
|
175
|
if (xmlHttp.readyState==4) {
|
176
|
if (xmlHttp.status >= 400) { //http errors status start at 400
|
177
|
var errorMsg = "error loading document: " + sUri + " - " + xmlHttp.statusText + "-" + xmlHttp.responseText;
|
178
|
alert(errorMsg);
|
179
|
objRef.setParam("modelStatus",errorMsg);
|
180
|
return;
|
181
|
} else {
|
182
|
//alert(xmlHttp.getResponseHeader("Content-Type"));
|
183
|
if ( null==xmlHttp.responseXML ) {
|
184
|
alert( "null XML response:" + xmlHttp.responseText );
|
185
|
} else {
|
186
|
objRef.doc = xmlHttp.responseXML;
|
187
|
//if (objRef.doc.documentElement.nodeName.search(/exception/i)>=0) {
|
188
|
// objRef.setParam("modelStatus",-1);
|
189
|
// alert("Exception:"+Sarissa.serialize(xmlHttp.responseText));
|
190
|
//}
|
191
|
}
|
192
|
objRef.finishLoading();
|
193
|
}
|
194
|
}
|
195
|
}
|
196
|
|
197
|
xmlHttp.send(objRef.postData);
|
198
|
|
199
|
if (!objRef.async) {
|
200
|
if (xmlHttp.status >= 400) { //http errors status start at 400
|
201
|
var errorMsg = "error loading document: " + sUri + " - " + xmlHttp.statusText + "-" + xmlHttp.responseText;
|
202
|
alert(errorMsg);
|
203
|
this.objRef.setParam("modelStatus",errorMsg);
|
204
|
return;
|
205
|
} else {
|
206
|
//alert(xmlHttp.getResponseHeader("Content-Type"));
|
207
|
if ( null==xmlHttp.responseXML ) alert( "null XML response:" + xmlHttp.responseText );
|
208
|
objRef.doc = xmlHttp.responseXML;
|
209
|
objRef.finishLoading();
|
210
|
}
|
211
|
}
|
212
|
|
213
|
//objRef.doc.validateOnParse=false; //IE6 SP2 parsing bug
|
214
|
}
|
215
|
}
|
216
|
}
|
217
|
|
218
|
/**
|
219
|
* Set the model's XML document using an XML object as a parameter.
|
220
|
* @param objRef Pointer to this object.
|
221
|
* @param newModel XML object to be inserted into the new model.
|
222
|
*/
|
223
|
this.setModel=function(objRef,newModel){
|
224
|
objRef.callListeners("newModel");
|
225
|
objRef.doc=newModel;
|
226
|
if ((newModel == null) && objRef.url) {
|
227
|
objRef.url = null;
|
228
|
}
|
229
|
objRef.finishLoading();
|
230
|
}
|
231
|
|
232
|
/**
|
233
|
* Common steps to be carried out after all manner of model loading
|
234
|
* Called to set the namespace for XPath selections and call the loadModel
|
235
|
* listeners.
|
236
|
*/
|
237
|
this.finishLoading = function() {
|
238
|
// the following two lines are needed for IE; set the namespace for selection
|
239
|
if(this.doc){
|
240
|
this.doc.setProperty("SelectionLanguage", "XPath");
|
241
|
if(this.namespace) Sarissa.setXpathNamespaces(this.doc, this.namespace);
|
242
|
|
243
|
// Show the newly loaded XML document
|
244
|
if(this.debug) alert("Loading Model:"+this.id+" "+Sarissa.serialize(this.doc));
|
245
|
this.callListeners("contextLoaded"); //PGC
|
246
|
this.callListeners("loadModel");
|
247
|
}
|
248
|
}
|
249
|
|
250
|
/**
|
251
|
* Load XML for a model from an httpPayload object. This will also handle
|
252
|
* instantiating template models if they have the "template" attribute set.
|
253
|
* To update model data, use:<br/>
|
254
|
* httpPayload=new Object();<br/>
|
255
|
* httpPayload.url="url" or null. If set to null, all dependant widgets
|
256
|
* will be removed from the display.<br/>
|
257
|
* httpPayload.httpMethod="post" or "get"<br/>
|
258
|
* httpPayload.postData=XML or null<br/>
|
259
|
* @param objRef Pointer to the model object being loaded.
|
260
|
* @param httpPayload an object to fully specify the request to be made
|
261
|
*/
|
262
|
this.newRequest = function(objRef, httpPayload){
|
263
|
var model = objRef;
|
264
|
// if the targetModel is a template model, then create new model object and
|
265
|
// assign it an id
|
266
|
if (objRef.template) {
|
267
|
var parentNode = objRef.modelNode.parentNode;
|
268
|
var newConfigNode;
|
269
|
if(_SARISSA_IS_IE) {
|
270
|
newConfigNode = parentNode.appendChild(modelNode.cloneNode(true));
|
271
|
} else {
|
272
|
newConfigNode = parentNode.appendChild(objRef.modelNode.ownerDocument.importNode(objRef.modelNode,true));
|
273
|
}
|
274
|
newConfigNode.removeAttribute("id"); //this will get created automatically
|
275
|
//set defaultModelUrl config properties
|
276
|
model = objRef.createObject(newConfigNode);
|
277
|
model.callListeners("init");
|
278
|
if (!objRef.templates) objRef.templates = new Array();
|
279
|
objRef.templates.push(model);
|
280
|
}
|
281
|
|
282
|
//set the payload in the model and issue the request
|
283
|
model.url = httpPayload.url;
|
284
|
if (!model.url) model.doc=null;
|
285
|
model.method = httpPayload.method;
|
286
|
model.postData = httpPayload.postData;
|
287
|
model.loadModelDoc(model);
|
288
|
}
|
289
|
|
290
|
/**
|
291
|
* deletes all template models and clears their widgets
|
292
|
*/
|
293
|
this.deleteTemplates = function() {
|
294
|
if (this.templates) {
|
295
|
while( model=this.templates.pop() ) {
|
296
|
model.setParam("newModel");
|
297
|
var parentNode = this.modelNode.parentNode;
|
298
|
parentNode.removeChild(model.modelNode);
|
299
|
}
|
300
|
}
|
301
|
}
|
302
|
|
303
|
/**
|
304
|
* save the model by posting it to the serializeUrl, which is defined as a
|
305
|
* property of config.
|
306
|
* @param objRef Pointer to this object.
|
307
|
*/
|
308
|
this.saveModel = function(objRef) {
|
309
|
if (config.serializeUrl) {
|
310
|
var response = postLoad(config.serializeUrl, objRef.doc);
|
311
|
response.setProperty("SelectionLanguage", "XPath");
|
312
|
Sarissa.setXpathNamespaces(response, "xmlns:xlink='http://www.w3.org/1999/xlink'");
|
313
|
var onlineResource = response.selectSingleNode("//OnlineResource");
|
314
|
var fileUrl = onlineResource.attributes.getNamedItem("xlink:href").nodeValue;
|
315
|
objRef.setParam("modelSaved", fileUrl);
|
316
|
} else {
|
317
|
alert("serializeUrl must be specified in config to save a model");
|
318
|
}
|
319
|
}
|
320
|
|
321
|
/**
|
322
|
* Creates all mapbuilder JavaScript objects based on the Object nodes defined
|
323
|
* in the configuration file.
|
324
|
* A reference to the created model is stored as a property of the config.objects
|
325
|
* property using the model's ID; you can always get a reference to a mapbuilder
|
326
|
* object as: "config.objects.objectId"
|
327
|
* @param configNode The node from config for the model to be created
|
328
|
*/
|
329
|
this.createObject = function(configNode) {
|
330
|
var objectType = configNode.nodeName;
|
331
|
var evalStr = "new " + objectType + "(configNode,this);";
|
332
|
var newObject = eval( evalStr );
|
333
|
if ( newObject ) {
|
334
|
config.objects[newObject.id] = newObject;
|
335
|
return newObject;
|
336
|
} else {
|
337
|
alert("error creating object:" + objectType);
|
338
|
}
|
339
|
}
|
340
|
|
341
|
/**
|
342
|
* Creates all the mapbuilder objects from the config file as selected by the
|
343
|
* XPath value passed in.
|
344
|
* @param objectXpath The XPath for the set of nodes being created
|
345
|
*/
|
346
|
this.loadObjects = function(objectXpath) {
|
347
|
//loop through all nodes selected from config
|
348
|
var configObjects = this.modelNode.selectNodes( objectXpath );
|
349
|
for (var i=0; i<configObjects.length; i++ ) {
|
350
|
this.createObject( configObjects[i]);
|
351
|
}
|
352
|
}
|
353
|
|
354
|
/**
|
355
|
* Initialization of all javascript model, widget and tool objects for this model.
|
356
|
* Calling this method triggers an init event for this model.
|
357
|
* @param objRef Pointer to this object.
|
358
|
*/
|
359
|
this.parseConfig = function(objRef) {
|
360
|
objRef.loadObjects("mb:widgets/*");
|
361
|
objRef.loadObjects("mb:tools/*");
|
362
|
objRef.loadObjects("mb:models/*");
|
363
|
}
|
364
|
|
365
|
/**
|
366
|
* Listener registered with the parent model to call refresh listeners when
|
367
|
* the model document is loaded
|
368
|
* @param objRef Pointer to this object.
|
369
|
*/
|
370
|
this.refresh = function(objRef) {
|
371
|
objRef.setParam("refresh",true);
|
372
|
}
|
373
|
this.addListener("loadModel",this.refresh, this);
|
374
|
|
375
|
/**
|
376
|
* Listener registered with the parent model to call init listeners when
|
377
|
* the parent model is init'ed
|
378
|
* @param objRef Pointer to this object.
|
379
|
*/
|
380
|
this.init = function(objRef) {
|
381
|
objRef.callListeners("init");
|
382
|
}
|
383
|
|
384
|
/**
|
385
|
* Listener registered with the parent model to remove the doc and url
|
386
|
* of child models whenever the parent is reloaded.
|
387
|
* @param objRef Pointer to this object.
|
388
|
*/
|
389
|
this.clearModel = function(objRef) {
|
390
|
objRef.doc=null;
|
391
|
//objRef.url=null;
|
392
|
}
|
393
|
|
394
|
//don't load in models and widgets if this is the config doc,
|
395
|
//defer that to an explcit config.init() call in mapbuilder.js
|
396
|
if (parentModel) {
|
397
|
this.parentModel = parentModel;
|
398
|
this.parentModel.addListener("init",this.init, this);
|
399
|
this.parentModel.addListener("loadModel",this.loadModelDoc, this);
|
400
|
this.parentModel.addListener("newModel", this.clearModel, this);
|
401
|
this.parseConfig(this);
|
402
|
}
|
403
|
}
|
404
|
|
405
|
//ModelBase.prototype.httpStatusMsg = ['uninitialized','loading','loaded','interactive','completed'];
|
406
|
var httpStatusMsg = ['uninitialized','loading','loaded','interactive','completed'];
|