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