Project

General

Profile

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