Project

General

Profile

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'];
(10-10/19)