1 /* See the NOTICE file distributed with
  2  * this work for additional information regarding copyright ownership.
  3  * Esri Inc. licenses this file to You under the Apache License, Version 2.0
  4  * (the "License"); you may not use this file except in compliance with
  5  * the License.  You may obtain a copy of the License at
  6  *
  7  *     http://www.apache.org/licenses/LICENSE-2.0
  8  *
  9  * Unless required by applicable law or agreed to in writing, software
 10  * distributed under the License is distributed on an "AS IS" BASIS,
 11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  * See the License for the specific language governing permissions and
 13  * limitations under the License.
 14  */
 15 /**
 16  * gxe.js (v1.1.1)
 17  * Geoportal XML editor.
 18  */
 19 
 20 dojo.require("dijit.Dialog");
 21 
 22 /**
 23  * @fileOverview Geoportal XML Editor (GXE).
 24  * @version 1.1.1
 25  */
 26 
 27 /**
 28  * @class GXE configuration object structure (JSON).
 29  * <br/>Represents the logical structure for the response of a request
 30  * for a Geoportal XML editor definition.
 31  * @name cfgObject
 32  * @property {String} name the qualified configuration name
 33  * @property {String} value the value (optional)
 34  * @property {cfgAttribute[]} attributes the configuration attributes
 35  * @property {cfgObject[]} children the children (optional)
 36  */
 37 
 38 /**
 39  * @class GXE configuration attribute structure (JSON).
 40  * @name cfgAttribute
 41  * @property {String} name the qualified configuration name
 42  * @property {String} value the value
 43  */
 44 
 45 /**
 46  * @class Primary namespace for the Geoportal XML Editor (GXE).
 47  * @static
 48  * @name gxe
 49  */
 50 var gxe = {
 51 
 52   /**
 53    * @class Static utilities for processing JSON based configuration objects associated
 54    * with an editor definition.
 55    * @static
 56    * @name gxe.cfg
 57    * @property {String} pfxGxe the GXE namespace prefix 
 58    *   <br/>(="g")
 59    * @property {String} pfxHtml the GXE HTML namespace prefix 
 60    *   <br/>(="h")
 61    * @property {String} uriGxe the GXE namespace URI 
 62    *   <br/>(="http://www.esri.com/geoportal/gxe")
 63    * @property {String} uriGxeHtml the GXE HTML namespace URI 
 64    *   <br/>(="http://www.esri.com/geoportal/gxe/html")
 65    */
 66   cfg: {
 67     pfxGxe: "g",
 68     pfxHtml: "h",
 69     uriGxe: "http://www.esri.com/geoportal/gxe",
 70     uriGxeHtml: "http://www.esri.com/geoportal/gxe/html",
 71   
 72     /**
 73      * Finds an immediate child of a configuration object.
 74      * @example Example:
 75      * gxe.cfg.findChild(cfgObject,"http://www.esri.com/geoportal/gxe","options");
 76      * @function 
 77      * @name findChild
 78      * @memberOf gxe.cfg
 79      * @param {cfgObject} cfgObject the configuration object to process
 80      * @param {String} namespace the configuration namespace of the child to find
 81      * @param {String} name the configuration name of the child to find
 82      * @return {cfgObject} the located child (null if not found)
 83      */
 84     findChild: function(cfgObject,namespace,name) {
 85       var children = cfgObject.children;
 86       var nChildren = 0;
 87       if (children != null) nChildren = children.length;
 88       for (var i=0; i<nChildren; i++) {
 89         var child = children[i];
 90         if ((child.namespace == namespace) && (child.name == name)) {
 91           return child;
 92         }
 93       }
 94       return null;
 95     },
 96     
 97     /**
 98      * Finds an immediate child of a configuration object within the GXE namespace.
 99      * <br/>(i.e. namespace "http://www.esri.com/geoportal/gxe")
100      * @example Example:
101      * gxe.cfg.findGxeChild(cfgObject,"options");
102      * @function 
103      * @name findGxeChild
104      * @memberOf gxe.cfg
105      * @param {cfgObject} cfgObject the configuration object to process
106      * @param {String} name the configuration name of the child to find
107      * @return {cfgObject} the located child (null if not found)
108      */
109     findGxeChild: function(cfgObject,name) {
110       return this.findChild(cfgObject,this.uriGxe,name);
111     },
112 
113     /**
114      * Executes a function for each immediate and matching child of a configuration object.
115      * <br/>
116      * <br/>The callback function will have the following signature: function(cfgChild) {}
117      * <br/>The callback function can return the String "break" to terminate the loop.
118      * @function 
119      * @name forEachChild
120      * @memberOf gxe.cfg
121      * @param {cfgObject} cfgObject the configuration object to process
122      * @param {String} namespace the configuration namesapce to match (* to match any)
123      * @param {String} name the configuration name to match (* to match any)
124      * @param {function} callback the callback function 
125      */
126     forEachChild: function(cfgObject,namespace,name,callback) {
127       var children = cfgObject.children;
128       var nChildren = 0;
129       if (children != null) nChildren = children.length;
130       for (var i=0; i<nChildren; i++) {
131         var child = children[i];
132         var bMatch = ((namespace == "*") || (child.namespace == namespace));
133         if (bMatch) bMatch = ((name == "*") || (child.name == name));
134         if (bMatch) {
135           var _ret = callback(child);
136           if ((typeof(_ret) == "string") && (_ret == "break")) break;
137         }
138       }
139     },
140 
141     /**
142      * Executes a function for each HTML attribute associated with a configuration object.
143      * <br/>(i.e. each attribute within namespace "http://www.esri.com/geoportal/gxe/html")
144      * <br/>
145      * <br/>The callback function will have the following signature: function(cfgAttribute) {}
146      * <br/>The callback function can return the String "break" to terminate the loop.
147      * @function 
148      * @name forEachHtmlAttribute
149      * @memberOf gxe.cfg
150      * @param {cfgObject} cfgObject the configuration object to process
151      * @param {function} callback the callback function 
152      */
153     forEachHtmlAttribute: function(cfgObject,callback) {
154       var attributes = cfgObject.attributes;
155       var nAttributes = 0;
156       if (attributes != null) nAttributes = attributes.length;
157       for (var i=0; i<nAttributes; i++) {
158         var attribute = attributes[i];
159         if (attribute.namespace == this.uriGxeHtml) {
160           var _ret = callback(attribute);
161           if ((typeof(_ret) == "string") && (_ret == "break")) break;
162         }
163       }
164     },
165   
166     /**
167      * Gets an attribute value.
168      * @example Example:
169      * gxe.cfg.getAttributeValue(cfgObject,"http://www.esri.com/geoportal/gxe","minOccurs");
170      * @function 
171      * @name getAttributeValue
172      * @memberOf gxe.cfg
173      * @param {cfgObject} cfgObject the configuration object to process
174      * @param {String} namespace the configuration namespace of the attribute to find
175      * @param {String} name the configuration name of the attribute to find
176      * @return {Object} the attribute value (null if not found)
177      */
178     getAttributeValue: function(cfgObject,namespace,name) {
179       var attributes = cfgObject.attributes;
180       var nAttributes = 0;
181       if (attributes != null) nAttributes = attributes.length;
182       for (var i=0; i<nAttributes; i++) {
183         var attribute = attributes[i];
184         if ((attribute.namespace == namespace) && (attribute.name == name)) {
185           return attribute.value;
186         }
187       }
188       return null;
189     },
190   
191     /**
192      * Gets an attribute value within the GXE namespace.
193      * <br/>(i.e. namespace "http://www.esri.com/geoportal/gxe")
194      * @example Example:
195      * gxe.cfg.getGxeAttributeValue(cfgObject,"minOccurs");
196      * @function 
197      * @name getGxeAttributeValue
198      * @memberOf gxe.cfg
199      * @param {cfgObject} cfgObject the configuration object to process
200      * @param {String} name the configuration name of the attribute to find
201      * @return {Object} the attribute value (null if not found)
202      */
203     getGxeAttributeValue: function(cfgObject,name) {
204       return this.getAttributeValue(cfgObject,this.uriGxe,name);
205     },
206 
207     /**
208      * Gets an attribute value within the GXE HTML namespace.
209      * <br/>(i.e. namespace "http://www.esri.com/geoportal/gxe/html")
210      * @example Example:
211      * gxe.cfg.getGxeHtmlAttributeValue(cfgObject,"maxlength");
212      * @function 
213      * @name getGxeHtmlAttributeValue
214      * @memberOf gxe.cfg
215      * @param {cfgObject} cfgObject the configuration object to process
216      * @param {String} name the configuration name of the attribute to find
217      * @return {Object} the attribute value (null if not found)
218      */
219     getGxeHtmlAttributeValue: function(cfgObject,name) {
220       return this.getAttributeValue(cfgObject,this.uriGxeHtml,name);
221     },
222     
223     /**
224      * Gets the g:label attribute value associated with a configuration object.
225      * <br/>If null, the associated XML target name will be returned.
226      * @function 
227      * @name getLabelText
228      * @memberOf gxe.cfg
229      * @param {cfgObject} cfgObject the configuration object to process
230      * @return {String} the attribute value (null if not found) 
231      */
232     getLabelText: function(cfgObject) {
233       var sLabel = this.getGxeAttributeValue(cfgObject,"label");
234       if ((sLabel != null) && (sLabel.length > 0)) {
235         return sLabel;
236       } else {
237         return this.getGxeAttributeValue(cfgObject,"targetName");
238       }
239     },
240     
241     /**
242      * Gets the g:maxOccurs attribute value associated with a configuration object.
243      * @function 
244      * @name getMaxOccurs
245      * @memberOf gxe.cfg
246      * @param {cfgObject} cfgObject the configuration object to process
247      * @return {String} the attribute value (null if not found) 
248      */
249     getMaxOccurs: function(cfgObject) {
250       return this.getGxeAttributeValue(cfgObject,"maxOccurs");
251     },
252 
253     /**
254      * Gets the g:minOccurs attribute value associated with a configuration object.
255      * @function 
256      * @name getMinOccurs
257      * @memberOf gxe.cfg
258      * @param {cfgObject} cfgObject the configuration object to process
259      * @return {String} the attribute value (null if not found) 
260      */
261     getMinOccurs: function(cfgObject) {
262       return this.getGxeAttributeValue(cfgObject,"minOccurs");
263     },
264 
265     /**
266      * Gets the name for the XML target associated with a configuration object.
267      * @function 
268      * @name getTargetName
269      * @memberOf gxe.cfg
270      * @param {cfgObject} cfgObject the configuration object to process
271      * @return {String} the target name
272      */
273     getTargetName: function(cfgObject) {
274       if (cfgObject.targetNameOverride != null) return cfgObject.targetNameOverride;
275       else return this.getGxeAttributeValue(cfgObject,"targetName");
276     },
277 
278     /**
279      * Gets the namespace for the XML target associated with a configuration object.
280      * <br/>If the supplied configuration object was not directly configured
281      * with a g:targetNS attribute, then the value will be inherited from the first 
282      * applicable ancestor.
283      * @function 
284      * @name getTargetNS
285      * @memberOf gxe.cfg
286      * @param {cfgObject} cfgObject the configuration object to process
287      * @return {String} the target namespace
288      */
289     getTargetNS: function(cfgObject) {
290       if (cfgObject.targetNSOverride != null) return cfgObject.targetNSOverride;
291       var cfgCheck = cfgObject;
292       while ((cfgCheck != undefined) && (cfgCheck != null)) {
293         var ns = this.getGxeAttributeValue(cfgCheck,"targetNS");
294         if (ns != null) return ns;
295         cfgCheck = cfgCheck.parent;
296       }
297     },
298     
299     /**
300      * Initializes the configured definition for an editor. 
301      * @function 
302      * @name initialize
303      * @memberOf gxe.cfg
304      * @param {cfgObject} cfgDefinition the configured editor definition (JSON)
305      */
306     initialize: function(cfgDefinition) {
307       this._initializeObject(cfgDefinition,null,null,false);
308       var namespaces = new gxe.xml.XmlNamespaces();
309       var cfgRoot = gxe.cfg.findChild(cfgDefinition,this.uriGxe,"rootElement");
310       if (cfgRoot != null) {
311         this._initializeObject(cfgRoot,null,null,false);
312         var cfgNamespaces = this.findChild(cfgRoot,this.uriGxe,"namespaces");
313         if (cfgNamespaces != null) {
314           this._initializeObject(cfgNamespaces,cfgRoot,null,false);
315           gxe.cfg.forEachChild(cfgNamespaces,this.uriGxe,"namespace",dojo.hitch(this,function(cfgNS) {
316             this._initializeObject(cfgNS,cfgNamespaces,null,false);
317             var pfx = this.getGxeAttributeValue(cfgNS,"prefix");
318             var uri = this.getGxeAttributeValue(cfgNS,"uri");
319             namespaces.add(new gxe.xml.XmlNamespace(pfx,uri));
320           }));
321         }
322       }
323       this._initializeObject(cfgDefinition,null,namespaces,true);
324     },
325 
326     // initializes a configuration object
327     _initializeObject: function(cfgObject,cfgParent,gxeNamespaces,bRecurse) {
328       cfgObject.parent = cfgParent;
329       var attributes = cfgObject.attributes;
330       var nAttributes = 0;
331       if (attributes != null) nAttributes = attributes.length;
332       for (var i=0; i<nAttributes; i++) {
333         var attribute = attributes[i];
334         attribute.parent = cfgObject;
335         this._initializeObjectNS(attribute);
336       }
337       var children = cfgObject.children;
338       var nChildren = 0;
339       if (children != null) nChildren = children.length;
340       for (var i=0; i<nChildren; i++) {
341         var child = children[i];
342         this._initializeObjectNS(child);
343         if (bRecurse) this._initializeObject(child,cfgObject,gxeNamespaces,true);
344       }
345       this._initializeTargetNS(cfgObject,gxeNamespaces);     
346     },
347 
348     // initializes a configuration object name and namespace
349     _initializeObjectNS: function(cfgObject) {
350       var namespace = cfgObject.namespace;
351       var name = cfgObject.name;
352       var prefix = null;
353       var localName = name;
354       var nIdx = name.indexOf(":");
355       if (nIdx != -1) {
356         prefix = name.substring(0,nIdx);
357         localName = name.substring(nIdx+1);
358       }
359       if (namespace == null) {
360         if (prefix == this.pfxGxe) {
361           cfgObject.namespace = this.uriGxe;
362           cfgObject.name = localName;
363         } else if (prefix == this.pfxHtml) {
364           cfgObject.namespace = this.uriGxeHtml;
365           cfgObject.name = localName;
366         }
367       }      
368     },
369 
370     // initializes the XML target name  and namespace associated with a configuration object
371     _initializeTargetNS: function(cfgObject,gxeNamespaces) {
372       if (gxeNamespaces != null) {
373         var namespace = this.getGxeAttributeValue(cfgObject,"targetNS");
374         var name = this.getGxeAttributeValue(cfgObject,"targetName");
375         if ((name != null) && (name.length > 0)) {
376           var nIdx = name.indexOf(":");
377           if (nIdx != -1) {
378             var prefix = name.substring(0,nIdx);
379             var localName = name.substring(nIdx+1);
380             var uri = gxeNamespaces.getUri(prefix);
381             if (uri == null) {
382             } else if ((namespace != null) && (namespace != uri)) {
383             } else {
384               cfgObject.targetNSOverride = uri;
385               cfgObject.targetNameOverride = localName;
386             }
387           }
388         }
389       }
390     }
391 
392   }
393   
394 };
395 
396 /**
397  * @class Provides client functionality for executing AJAX calls to the server. 
398  * @name gxe.Client
399  */
400 dojo.provide("gxe.Client");
401 dojo.declare("gxe.Client",null,{
402   
403   /**
404    * Handles an error condition.
405    * @function 
406    * @name onError
407    * @memberOf gxe.Client#
408    * @param {Error} error the error 
409    * @param {Object} ioArgs the Dojo i/o arguments 
410    */
411   onError: function(error,ioArgs) {  
412     var msg = null;
413     if (ioArgs == null) {
414       msg = error.message;
415     } else {
416       if ((ioArgs.xhr.status >= 200) && (ioArgs.xhr.status < 300)) {
417         msg = error.message;
418       } else {
419         msg = " HTTP: " +ioArgs.xhr.status+", "+ioArgs.args.url;
420       }
421     }
422     if (msg != null) alert(msg);
423   },
424   
425   /**
426    * Loads a JSON based editor definition.
427    * <br/><br/>This method is geared towards the Geoportal Server end-point
428    * for loading an editor definition:<br/>[context path]/gxe/definition<br/>
429    * @function 
430    * @name queryDefinition
431    * @memberOf gxe.Client#
432    * @param {gxe.Context} context the editor context  
433    * @param {String} sParam a URL parameter name (key|loc)
434    * @param {String} sParamValue a URL parameter value 
435    *   <br/>when sParam="key", use the key for the standard (e.g. "fgdc")
436    *   <br/>when sParam="loc", use the location (e.g. "gpt/gxe/fgdc/fgdc-editor.xml")
437    * @param {function} callback function to call once the definition has been 
438    *   successfully retrieved 
439    *   <br/>signature: function(responseObject,ioArgs)
440    *   <br/>--- where responseObject is the JSON definition for the editor
441    */
442   queryDefinition: function(context,sParam,sParamValue,callback) {
443     var u = context.contextPath+"/gxe/definition"
444     u += "?"+encodeURIComponent(sParam)+"="+encodeURIComponent(sParamValue)+"&f=json";
445     dojo.xhrGet({
446       handleAs: "json",
447       preventCache: true,
448       url: u,
449       error: dojo.hitch(this,"onError"),
450       load: dojo.hitch(this,function(responseObject,ioArgs) {
451         callback(responseObject,ioArgs);
452       })
453     });
454   },
455   
456   /**
457    * Loads an XML document.
458    * <br/><br/>This method is geared towards the Geoportal Server rest end-point
459    * for document management:<br/>[context path]/rest/manage/document<br/>
460    * @function 
461    * @name queryDocument
462    * @memberOf gxe.Client#
463    * @param {gxe.Context} context the editor context  
464    * @param {String} id the document identifier 
465    * @param {function} callback function to call once the document has been 
466    *   successfully retrieved 
467    *   <br/>signature: function(responseObject,ioArgs) 
468    *   <br/>--- where responseObject is the XML DOM
469    */
470   queryDocument: function(context,id,callback) {
471     var u = context.contextPath+"/rest/manage/document?id="+encodeURIComponent(id);
472     dojo.xhrGet({
473       handleAs: "xml",
474       preventCache: true,
475       url: u,
476       error: dojo.hitch(this,"onError"),
477       load: dojo.hitch(this,function(responseObject,ioArgs) {
478         context.openDocumentId = id;
479         callback(responseObject,ioArgs);
480       })
481     });
482   },
483   
484   /**
485    * Saves an XML document.
486    * <br/><br/>This method is geared towards the Geoportal Server rest end-point
487    * for document management:<br/>[context path]/rest/manage/document<br/>
488    * @function 
489    * @name saveDocument
490    * @memberOf gxe.Client#
491    * @param {gxe.Context} context the editor context  
492    * @param {String} id the document identifier 
493    *   (can be null for documents that are internally identified)
494    * @param {String} sXml the XML to save 
495    * @param {boolean} asDraft true if document is being saved as a draft
496    * @param {function} callback optional function to call once the save has 
497    *   successfully executed 
498    *   <br/>signature: function(responseObject,ioArgs)
499    */
500   saveDocument: function(context,id,sXml,asDraft,callback) {
501     var u = context.contextPath+"/rest/manage/document?publicationMethod=editor&errorsAsJson=jErr";
502     if (id == null) id = context.openDocumentId;
503     if (id == null) id = context.newDocumentId;
504     if ((id != null) && (id.length > 0)) u += "&id="+encodeURIComponent(id);
505     if (asDraft) u += "&asDraft=true";
506    
507     var dialog = new dijit.Dialog({
508       title: context.getI18NString("client.saving.title"),
509       style: "width: 300px; display: none;"
510     });
511     dojo.addClass(dialog.domNode,"tundra");
512     dialog.show();
513     
514     dojo.xhrPut({
515       handleAs: "text",
516       preventCache: true,
517       url: u,
518       putData: sXml,
519       error: dojo.hitch(this,function(errorObject,ioArgs) {
520         dialog.hide();
521         dialog.destroy();
522         this.onError(errorObject,ioArgs);
523       }),
524       load: dojo.hitch(this,function(responseObject,ioArgs) {
525         dialog.hide();
526         dialog.destroy();
527         try {
528           if (responseObject!=null) {
529             jErr = null;
530             eval(responseObject);
531             if (jErr!=null) {
532               if (jErr.errors!=null && jErr.errors.length>0) {
533                 for (var m=0; m<jErr.errors.length; m++) {
534                   context.messageArea.addError(jErr.errors[m]);
535                 }
536               } else {
537                 context.messageArea.addError(jErr.message);
538               }
539               jErr = null;
540             }
541           }
542         } catch (err) {
543           // handle eval error
544         }
545         if (typeof(callback) == "function") callback(responseObject,ioArgs);
546       })
547     });
548   }
549   
550 });
551 
552 /**
553  * @class Provides a context for the editor. 
554  * @name gxe.Context
555  * @property {String} contextPath the wep-app context path
556  * @property {cfgObject} cfgContext the g:context portion configured editor definition (JSON)
557  * @property {cfgObject} cfgDefinition the configured editor definition (JSON)
558  * @property {GptMapConfig} gptMapConfig the interactive map configuration
559  * @property {Element} htmlParentElement the parent HTML element
560  * @property {String} newDocumentId the ID to use for a newly created document
561  * @property {String} openDocumentId the ID for the document that was opened
562  * @property {gxe.xml.XmlDocument} xmlDocument the target XML document for the editor
563  * @property {String} idPrefix the prefix to use when generating HTML element IDs
564  * @property {gxe.control.MessageArea} messageArea the message area
565  */
566 dojo.provide("gxe.Context");
567 dojo.declare("gxe.Context",null,{
568   contextPath: null,
569   cfgContext: null,
570   cfgDefinition: null,
571   gptMapConfig: null,
572   htmlParentElement: null,
573   newDocumentId: null,
574   openDocumentId: null,
575   xmlDocument: null,
576   idPrefix: "gxeId",
577   messageArea: null,
578   _uniqueId: 0,
579 
580   /**
581    * Builds the editor user interface.
582    * @function 
583    * @name buildUI
584    * @memberOf gxe.Context#
585    * @param {cfgObject} cfgDefinition the configured editor definition (JSON)
586    * @param {Element} htmlParentElement the parent HTML element (the editor
587    *   will be appended to this parent)
588    * @param {DOM} domDocument the XML target document
589    *   (can be null, used when opening an existing document)
590    */
591   buildUI: function(cfgDefinition,htmlParentElement,domDocument) {
592     this.cfgDefinition = cfgDefinition;
593     this.cfgContext = gxe.cfg.findGxeChild(cfgDefinition,"context");
594     this.htmlParentElement = htmlParentElement;
595 
596     var elMessageArea = dojo.byId("gxeMessageArea");
597     this.messageArea = new gxe.control.MessageArea();
598     this.messageArea.context = this;
599     this.messageArea.build(dojo.byId(elMessageArea),null,null);    
600     
601     this.xmlDocument = new gxe.xml.XmlDocument();
602     var xmlRoot = this.xmlDocument.initializeRoot(this,cfgDefinition);
603     var domProcessor = null;
604     var domRoot = null;
605     if (domDocument != null) {
606       var ndRoot = domDocument.documentElement;
607       var processor = new gxe.xml.DomProcessor();
608       if (processor.isMatching(ndRoot,xmlRoot.cfgObject)) {
609         domProcessor = processor;
610         domRoot = ndRoot;
611       } else {
612         throw new Error("The XML root element does not match the editor definition.");
613       }
614     }
615 
616     var ctl = this.makeXhtmlControl(xmlRoot.cfgObject,null,true);
617     ctl.xmlParentElement = null;
618     ctl.xmlNode = xmlRoot;
619     ctl.build(htmlParentElement,domProcessor,domRoot);
620   },
621   
622   /**
623    * Generates a unique ID.
624    * <br/>(String, prefixed with this.idPrefix).
625    * @function 
626    * @name generateUniqueId
627    * @memberOf gxe.Context#
628    * @return {String} the ID
629    */
630   generateUniqueId: function() {
631     this._uniqueId++;
632     return this.idPrefix+this._uniqueId;
633   },
634   
635   /**
636    * Gets a localized message string associated with the editor context.
637    * @function 
638    * @name getI18NString
639    * @memberOf gxe.Context#
640    * @param {String} sKey the key for the message string
641    * @return {String} the message string
642    */
643   getI18NString: function(sKey) {
644     var sValue = sKey;
645     if (this.cfgContext != null) {
646       gxe.cfg.forEachChild(this.cfgContext,gxe.cfg.uriGxe,"i18n",dojo.hitch(this,function(cfgChild) {
647         var sk = gxe.cfg.getGxeAttributeValue(cfgChild,"key");
648         if (sk == sKey) {
649           sValue = cfgChild.value;
650           // return "break"; // quicker but disable to allow profile override
651         }
652       }));
653     }
654     return sValue;
655   },
656 
657   /**
658    * Makes a GXE HTML based user interface control.
659    * <br/>By default, a new gxe.control.Control object will be instantiated. If the supplied
660    * configuration object has a configured g:jsClass attribute, the attribute value will be used 
661    * to instantiatethe control object (it is assumed that any supplied g:jsClass will extend from
662    * gxe.control.control).
663    * @function 
664    * @name makeXhtmlControl
665    * @memberOf gxe.Context#
666    * @param {cfgObject} cfgObject the associated editor configuration object
667    * @param {gxe.control.Control} ctlParent the parent control
668    * @param {boolean} bInitialize if true then run new control's initialize function
669    * @return {String} the new control
670    */
671   makeXhtmlControl: function(cfgObject,ctlParent,bInitialize) {
672     var ctl = null;
673     var sJsClass = gxe.cfg.getGxeAttributeValue(cfgObject,"jsClass");
674     if ((sJsClass != null) && (sJsClass.length > 0)) {
675       ctl = eval("new "+sJsClass+"()");
676     } else {
677       ctl = new gxe.control.Control();
678     }
679     ctl.parentControl = ctlParent;
680     if (bInitialize) {
681       ctl.initialize(this,cfgObject);
682     }
683     return ctl;
684   }
685   
686 });
687 
688 
689 /* Utility classes =================================================== */
690 
691 /**
692  * @class Simulates some methods associated with an ArrayList data structure.
693  * @name gxe.util.ArrayList
694  * @property {Array} _array The underlying JavaScript array.
695  */
696 dojo.provide("gxe.util.ArrayList");
697 dojo.declare("gxe.util.ArrayList",null,{
698   _array: null,
699 
700   /** @constructor */
701   constructor: function() {
702     this._array = new Array();
703   },
704 
705   /**
706    * Appends an object to the collection (same as push()).
707    * @function 
708    * @name add
709    * @memberOf gxe.util.ArrayList#
710    * @param {Object} obj the object to add
711    */
712   add: function(obj) {
713     this._array.push(obj);
714   },
715 
716   /**
717    * Gets the item at the specified index.
718    * @function 
719    * @name getItem
720    * @memberOf gxe.util.ArrayList#
721    * @param {Integer} nIndex the index
722    * @returns {Object} the corresponding object
723    */
724   getItem: function(nIndex) {
725     return this._array[nIndex];
726   },
727 
728   /**
729    * Gets the length of the array.
730    * @function 
731    * @name getLength
732    * @memberOf gxe.util.ArrayList#
733    * @returns {Integer} the length
734    */
735   getLength: function() {
736     return this._array.length;
737   },
738 
739   /**
740    * Inserts an object at a specified index.
741    * @function 
742    * @name insertAt
743    * @memberOf gxe.util.ArrayList#
744    * @param {Integer} nIndex the index (same as JavaScript Array.splice)
745    * @param {Object} obj the object to insert
746    */
747   insertAt: function(nIndex,obj) {
748     this._array.splice(nIndex,0,obj);
749   },
750 
751   /**
752    * Appends an object to the collection.
753    * @function 
754    * @name push
755    * @memberOf gxe.util.ArrayList#
756    * @param {Object} obj the object to add
757    */
758   push: function(obj) {
759     this._array.push(obj);
760   },
761 
762   /**
763    * Removes the object at the specified index from the collection.
764    * @function 
765    * @name removeIndex
766    * @memberOf gxe.util.ArrayList#
767    * @param {Integer} nIndex the index of the object to remove
768    */
769   removeIndex: function(nIndex) {
770     this._array.splice(nIndex,1);
771   },
772 
773   /**
774    * Swaps the positions of two objects within the collection.
775    * @function 
776    * @name swapPosition
777    * @memberOf gxe.util.ArrayList#
778    * @param {Integer} nFromIndex the from index
779    * @param {Integer} nToIndex the to index
780    */
781   swapPosition: function(nFromIndex,nToIndex) {
782     var a = this._array[nFromIndex];
783     var b = this._array[nToIndex];
784     this._array[nFromIndex] = b;
785     this._array[nToIndex] = a;
786   }
787 
788 });
789 
790 /**
791  * @class Simulates some methods associated with an StringBuffer data structure.
792  * @name gxe.util.StringBuffer
793  * @property {String} _text The underlying JavaScript String.
794  */
795 dojo.provide("gxe.util.StringBuffer");
796 dojo.declare("gxe.util.StringBuffer",null,{
797   _text: "",
798 
799   /**
800    * Constructor.
801    * @function
802    * @name constructor
803    * @constructor
804    * @memberOf gxe.util.StringBuffer#
805    * @param {String} text the initial text
806    * @returns {gxe.util.StringBuffer} the new instance
807    */
808   constructor: function(text) {
809     if (text != null) this._text = text;
810   },
811 
812   /**
813    * Appends a string.
814    * @function 
815    * @name append
816    * @memberOf gxe.util.StringBuffer#
817    * @param {String} s the string to append
818    * @returns {gxe.util.StringBuffer} this instance
819    */
820   append: function(s) {
821     this._text += s;
822     return this;
823   },
824 
825   /**
826    * Returns the associated string.
827    * @function 
828    * @name toString
829    * @memberOf gxe.util.StringBuffer#
830    * @returns {String} this string
831    */
832   toString: function() {
833     return this._text;
834   }
835 });
836 
837 /**
838  * @class Represents an HTML based attribute.
839  * @name gxe.html.HtmlAttribute
840  * @property {String} name The attribute name.
841  * @property {Object} value The attribute value.
842  */
843 dojo.provide("gxe.html.HtmlAttribute");
844 dojo.declare("gxe.html.HtmlAttribute",null,{
845   name: null,
846   value: null
847 });
848 
849 /**
850  * @class A collection of HTML based attributes.
851  * @name gxe.html.HtmlAttributes
852  * @extends gxe.util.ArrayList
853  */
854 dojo.provide("gxe.html.HtmlAttributes");
855 dojo.declare("gxe.html.HtmlAttributes",gxe.util.ArrayList,{
856 
857   /**
858    * Applies the attribute collection to an HTML DOM Element.
859    * (i.e. sets all attribute values)
860    * @function 
861    * @name apply
862    * @memberOf gxe.html.HtmlAttributes#
863    * @param {Element} elHtml the corresponding HTML DOM Element
864    */
865   apply: function(elHtml) {
866     var n = this.getLength();
867     for (var i=0; i<n; i++) {
868       var attr = this.getItem(i);
869       if (attr != null) {
870         if ((attr.name != null) && (attr.value != null)) {
871           var value = attr.value;
872           if (typeof(value) =="string") {
873             if (value.indexOf("$fire.") == 0) value = null;
874           }
875           if (value != null) {
876             var s = attr.name.toLowerCase();
877             if (s != "tag") {
878               elHtml.setAttribute(attr.name,value);
879               if (dojo.isIE <= 8) {
880                 if (attr.name == "class") {
881                   elHtml.className = value; 
882                   //
883                 } else if (attr.name = "readonly")  {
884                   //elHtml.readOnly = true;
885                 }
886               }
887             }
888           }  
889         } 
890       }
891     }
892   },
893 
894   /**
895    * Finds an attribute with given name.
896    * @function 
897    * @name find
898    * @memberOf gxe.html.HtmlAttributes#
899    * @param {String} name the name of the attribute to find
900    * @returns {gxe.html.HtmlAttribute} the corresponding attribute (null if not found)
901    */
902   find: function(name) {
903     if (name != null) {
904       name = dojo.trim(name);
905       if (name.length > 0) {
906         var lc = name.toLowerCase();
907         var n = this.getLength();
908         for (var i=0; i<n; i++) {
909           var attr = this.getItem(i);
910           if (attr != null) {
911             if ((attr.name != null) && (attr.name.toLowerCase() == lc)) {
912               return attr;
913             } 
914           }
915         }
916       }
917     }
918     return null;
919   }, 
920 
921   /**
922    * Adds an HTML attribute to the collection.
923    * If an attribute with the supplied name previously exists, its value will be updated.
924    * @function 
925    * @name set
926    * @memberOf gxe.html.HtmlAttributes#
927    * @param {String} name the name of the attribute
928    * @param {Object} value the value of the attribute
929    */
930   set: function(name,value) {
931     if (name != null) {
932       name = dojo.trim(name);
933       if (name.length > 0) {
934         var attr = this.find(name);
935         if (attr != null) {
936           attr.value = value;
937         } else {
938           attr = new gxe.html.HtmlAttribute();
939           attr.name = name;
940           attr.value = value;
941           this.add(attr);
942         }
943       }
944     }
945   } 
946 });
947 
948 
949 /* XML related classes =============================================== */
950 
951 /**
952  * @class Provides utility functions for processing an XML document.
953  * <br/>(used when opening an existing document)
954  * @name gxe.xml.DomProcessor
955  */
956 dojo.provide("gxe.xml.DomProcessor");
957 dojo.declare("gxe.xml.DomProcessor",null,{
958   
959   // some new methods have been added that can be fully leveraged at a later date
960   // forEachElementNode, forEachMatchingElementNode, splitQualifiedName
961 
962   // DOM node types
963   nodeTypes: {
964     ELEMENT_NODE: 1,
965     ATTRIBUTE_NODE: 2,
966     TEXT_NODE: 3,
967     CDATA_SECTION_NODE: 4,
968     ENTITY_REFERENCE_NODE: 5,
969     ENTITY_NODE: 6,
970     PROCESSING_INSTRUCTION_NODE: 7,
971     COMMENT_NODE: 8,
972     DOCUMENT_NODE: 9,
973     DOCUMENT_TYPE_NODE: 10,
974     DOCUMENT_FRAGMENT_NODE: 11,
975     NOTATION_NODE: 12
976   },
977   
978   // this function is not in use, development only
979   buildUI: function(context,cfgDefinition,htmlParentElement,sXml) {
980     if (window.DOMParser) {
981       var parser = new DOMParser();
982       var dom = parser.parseFromString(sXml,"text/xml");
983       context.buildUI(cfgDefinition,htmlParentElement,dom);
984     } else if (window.ActiveXObject) {
985       var dom = new ActiveXObject("MSXML2.DOMDocument");
986       dom.async = "false";
987       dom.loadXML(sXml);
988       context.buildUI(cfgDefinition,htmlParentElement,dom);
989     }
990   },
991   
992   /**
993    * Attempts to find the attribute of a DOM Node that matches the XML target 
994    * associated with an editor configuration object.
995    * @see gxe.xml.DomProcessor#isMatching
996    * @function 
997    * @name findMatchingChildAttribute
998    * @memberOf gxe.xml.DomProcessor#
999    * @param {Node} domParentNode the DOM node whose attributes will be searched
1000    * @param {Object} cfgChild the editor configuration object that 
1001    *   will be used to determine a match
1002    * @returns {Node} a matching DOM attribute (null if no match)
1003    */
1004   findMatchingChildAttribute: function(domParentNode,cfgChild) {
1005     var attributes = domParentNode.attributes;
1006     if ((attributes != null) && (attributes.length > 0)) {
1007       var n = attributes.length;
1008       for (var i=0; i<n; i++) {
1009         var attribute = attributes.item(i);
1010         if (this.isMatching(attribute,cfgChild)) return attribute;
1011       }
1012     }
1013     return null;
1014   },
1015 
1016   /**
1017    * Attempts to find an immediate child element of a DOM Node that matches 
1018    * the XML target associated with an editor configuration object.
1019    * @see gxe.xml.DomProcessor#isMatching
1020    * @function 
1021    * @name findMatchingChildElement
1022    * @memberOf gxe.xml.DomProcessor#
1023    * @param {Node} domParentNode the DOM node whose children will be searched
1024    * @param {Object} cfgChild the editor configuration object that 
1025    *   will be used to determine a match
1026    * @returns {Node} a matching DOM element (null if no match)
1027    */
1028   findMatchingChildElement: function(domParentNode,cfgChild) {
1029     var children = domParentNode.childNodes;
1030     if ((children != null) && (children.length > 0)) {
1031       var n = children.length;
1032       for (var i=0; i<n; i++) {
1033         var child = children[i];
1034         if (child.nodeType == this.nodeTypes.ELEMENT_NODE) {
1035           if (this.isMatching(child,cfgChild)) return child;
1036         }
1037       }
1038     }
1039     return null;
1040   },
1041 
1042   /**
1043    * Attempts to find the immediate child elements of a DOM Node that match 
1044    * the XML target associated with an editor configuration object.
1045    * @see gxe.xml.DomProcessor#isMatching
1046    * @function 
1047    * @name findMatchingChildElements
1048    * @memberOf gxe.xml.DomProcessor#
1049    * @param {Node} domParentNode the DOM node whose children will be searched
1050    * @param {Object} cfgChild the editor configuration object that 
1051    *   will be used to determine a match
1052    * @returns {Node[]} the matching DOM elements (null if no match)
1053    */
1054   findMatchingChildElements: function(domParentNode,cfgChild) {
1055     var matches = null;
1056     var children = domParentNode.childNodes;
1057     if ((children != null) && (children.length > 0)) {
1058       var n = children.length;
1059       for (var i=0; i<n; i++) {
1060         var child = children[i];
1061         if (child.nodeType == this.nodeTypes.ELEMENT_NODE) {
1062           if (this.isMatching(child,cfgChild)) {
1063             if (matches == null) matches = new Array();
1064             matches.push(child);
1065           }
1066         }
1067       }
1068     }
1069     return matches;
1070   },
1071   
1072   /**
1073    * Executes a function for each immediate child element of a DOM Node.
1074    * <br/>Only child elements of nodeType=1 (ELEMENT_NODE) will be considered.
1075    * <br/>
1076    * <br/>The callback function will have the following signature: function(domChildNode) {}
1077    * <br/>The callback function can return the String "break" to terminate the loop.
1078    * @function 
1079    * @name forEachElementNode
1080    * @memberOf gxe.xml.DomProcessor#
1081    * @param {Node} domParentNode the DOM node whose children will be searched
1082    * @param {function} callback the callback function 
1083    */
1084   forEachElementNode: function(domParentNode,callback) {
1085     var children = domParentNode.childNodes;
1086     if ((children != null) && (children.length > 0)) {
1087       var n = children.length;
1088       for (var i=0; i<n; i++) {
1089         var child = children[i];
1090         if (child.nodeType == this.nodeTypes.ELEMENT_NODE) {
1091           var _ret = callback(child);
1092           if ((typeof(_ret) == "string") && (_ret == "break")) break;
1093         }
1094       }
1095     }
1096   },
1097   
1098   
1099   /**
1100    * Executes a function for each immediate child element of a DOM Node that matches
1101    * a supplied namespace and loca name.
1102    * <br/>Only child elements of nodeType=1 (ELEMENT_NODE) will be considered.
1103    * <br/>
1104    * <br/>The callback function will have the following signature: function(domChildNode) {}
1105    * <br/>The callback function can return the String "break" to terminate the loop.
1106    * @function 
1107    * @name forEachMatchingElementNode
1108    * @memberOf gxe.xml.DomProcessor#
1109    * @param {Node} domParentNode the DOM node whose children will be searched
1110    * @param {String} sNamespaceUri the namespace URI to match (can be null)
1111    * @param {String} sLocalName the local node name to match (i.e unqualified name)
1112    * @param {function} callback the callback function 
1113    */
1114   forEachMatchingElementNode: function(domParentNode,sNamespaceUri,sLocalName,callback) {  
1115     var targetNS = sNamespaceUri;
1116     if ((targetNS != null) && (targetNS.length == 0)) targetNS = null;
1117     this.forEachElementNode(domParentNode,dojo.hitch(this,function(domChildNode) {
1118       if (domChildNode.namespaceURI == targetNS) {
1119         var pfxPlusLocal = this.splitQualifiedName(domChildNode.nodeName);
1120         if (pfxPlusLocal.localName == sLocalName) {
1121           var _ret = callback(domChildNode);
1122           if ((typeof(_ret) == "string") && (_ret == "break")) return "break";
1123         }
1124       }
1125     }));
1126   },
1127 
1128   /**
1129    * Gets the text content of a DOM Node (element or attribute).
1130    * @function 
1131    * @name getNodeText
1132    * @memberOf gxe.xml.DomProcessor#
1133    * @param {Node} domNode the DOM node that is actively being processed 
1134    * @returns {String} the text content (can be null)
1135    */
1136   getNodeText: function(domNode) {
1137     var s;
1138     if (domNode.nodeType == this.nodeTypes.ELEMENT_NODE) {
1139       var children = domNode.childNodes;
1140       if ((children != null) && (children.length > 0)) {
1141         var n = children.length;
1142         for (var i=0; i<n; i++) {
1143           var child = children[i];
1144           if (child.nodeType == this.nodeTypes.TEXT_NODE) {
1145             s = child.nodeValue;
1146             if (s != null) s = dojo.trim(s);
1147             return s;
1148           }
1149         }
1150       }
1151       return "";
1152     } else {
1153       s = domNode.nodeValue;
1154       if (s != null) s = dojo.trim(s);
1155       return s;
1156     }  
1157     return null;
1158   },
1159   
1160   /**
1161    * Determines if a DOM Node has either: attributes, child elements or element text.
1162    * @function 
1163    * @name hasChildrenOrAttributes
1164    * @memberOf gxe.xml.DomProcessor#
1165    * @param {Node} domNode the DOM node that is actively being processed 
1166    * @returns {boolean} true if there is a match
1167    */
1168   hasChildrenOrAttributes: function(domNode) {
1169     if (domNode != null) {
1170       var attributes = domNode.attributes;
1171       if ((attributes != null) && (attributes.length > 0)) return true;
1172       var children = domNode.childNodes;
1173       if ((children != null) && (children.length > 0)) {
1174         var n = children.length;
1175         for (var i=0; i<n; i++) {
1176           if (children[i].nodeType == this.nodeTypes.ELEMENT_NODE) {
1177             return true;
1178           } else if (children[i].nodeType == this.nodeTypes.TEXT_NODE) {
1179             var s = children[i].nodeValue;
1180             if ((s != null) && (dojo.trim(s).length > 0)) return true;
1181           }
1182         }
1183       }
1184     }
1185     return false;
1186   },
1187   
1188   /**
1189    * Determines if the qualified name associated with a DOM Node matches the XML target 
1190    * associated with an editor configuration object.
1191    * <br/>Editor configuration example for referencing a target XML element:<br/>
1192    * <g:element g:targetName="pfx:name" ..
1193    * @function 
1194    * @name isMatching
1195    * @memberOf gxe.xml.DomProcessor#
1196    * @param {Node} domNode the DOM node that is actively being processed 
1197    * @param {Object} cfgObject the associated editor configuration object
1198    * @returns {boolean} true if there is a match
1199    */
1200   isMatching: function(domNode,cfgObject) {
1201     var targetNS = gxe.cfg.getTargetNS(cfgObject);
1202     if ((targetNS != null) && (targetNS.length == 0)) targetNS = null;
1203     
1204     if (dojo.isIE <= 8) {
1205       if (targetNS == null) targetNS = "";
1206     } 
1207     
1208     if (domNode.namespaceURI == targetNS) {
1209       var targetName = gxe.cfg.getTargetName(cfgObject);
1210       var nodeName = domNode.nodeName;
1211       var prefix = null;
1212       var localName = nodeName;
1213       var nIdx = nodeName.indexOf(":");
1214       if (nIdx != -1) {
1215         prefix = nodeName.substring(0,nIdx);
1216         localName = nodeName.substring(nIdx+1);
1217       }
1218       if (localName == targetName) return true;
1219     }
1220     return false;
1221   },
1222   
1223   /**
1224    * Determines if there is a match between a supplied DOM Node and a descendant condition.
1225    * <br/><br/>Note: matchTopElement does not support full XPath expressions
1226    * @function 
1227    * @name matchTopElement
1228    * @memberOf gxe.xml.DomProcessor#
1229    * @param {gxe.xml.XmlNamespaces} xmlNamespaces a configured list of target namespaces
1230    * @param {Node} domNode the DOM node that is actively being processed 
1231    * @param {String} sMatchPath the relative path for the element to match
1232    *   (a simple path relative to the supplied parent DOM node)
1233    * @param {String} sMatchTextNodeValue the text node value to match 
1234    *   (null indicates no test should be made) 
1235    * @param {boolean} bMust true indicates that there must be a match 
1236    *   (false indicates must not)
1237    * @returns {boolean} true if there is a match
1238    */
1239   
1240   matchTopElement: function(xmlNamespaces,domNode,sMatchPath,sMatchTextNodeValue,bMust) {
1241     var tokens = sMatchPath.split("/");
1242     var nTokens = tokens.length;
1243     var domFinalMatches = new Array();
1244     var domCurrentNodes = new Array();
1245     domCurrentNodes.push(domNode);
1246     
1247     for (var i=0; i<nTokens; i++) {
1248       var bIsLast = (i == (nTokens - 1));
1249       var uri = null;
1250       var pfxPlusLocal = this.splitQualifiedName(tokens[i]);
1251       var localName = pfxPlusLocal.localName;
1252       if (pfxPlusLocal.prefix != null) {
1253         uri = xmlNamespaces.getUri(pfxPlusLocal.prefix);
1254       }
1255 
1256       var domCurrentMatches = new Array();
1257       for (var j=0; j<domCurrentNodes.length; j++) {
1258         this.forEachMatchingElementNode(domCurrentNodes[j],uri,localName,
1259           dojo.hitch(this,function(domChildNode) {
1260             if (bIsLast) {
1261               if (sMatchTextNodeValue == null) {
1262                 domCurrentMatches.push(domChildNode);
1263               } else {
1264                 var s = this.getNodeText(domChildNode);
1265                 if (s == sMatchTextNodeValue) {
1266                   domCurrentMatches.push(domChildNode);
1267                 }
1268               }
1269             } else {
1270               domCurrentMatches.push(domChildNode);
1271             }
1272           }
1273         ));
1274       }
1275       domCurrentNodes = domCurrentMatches;
1276       if (domCurrentNodes.length == 0) break;
1277       if (bIsLast) domFinalMatches = domCurrentNodes;
1278     }
1279     
1280     if (bMust) return (domFinalMatches.length > 0);
1281     else return (domFinalMatches.length == 0);
1282   },
1283   
1284   /**
1285    * Splits a qualified name into a prefix plus localName pair. 
1286    * @function 
1287    * @name splitQualifiedName
1288    * @memberOf gxe.xml.DomProcessor#
1289    * @param {String} sQualifiedName the qualified name
1290    * @returns {"prefix":{String}, "localName":{String}} the prefix plus localName pair
1291    */
1292   splitQualifiedName: function(sQualifiedName) {
1293     var prefixPlusLocalName = {"prefix": null, "localName": sQualifiedName};
1294     var tokens = sQualifiedName.split(":");
1295     if (tokens.length == 2) {
1296       prefixPlusLocalName.prefix = tokens[0];
1297       prefixPlusLocalName.localName = tokens[1];
1298     }
1299     return prefixPlusLocalName;
1300   }
1301   
1302 });
1303 
1304 /**
1305  * @class Serializes an XML document.
1306  * @name gxe.xml.Generator
1307  * @property {gxe.Context} context the editor context 
1308  * @property {String} documentTitle the document title
1309  * @property {boolean} hadValidationErrors true if validation errors were encountered
1310  * @property {boolean} isValidating true if all content should be validated
1311  * @property {boolean} isValidatingTitleOnly true if only the title should be validated
1312  * @property {boolean} isSaveAsDraft true if serialization is for a draft document
1313  */
1314 dojo.provide("gxe.xml.Generator");
1315 dojo.declare("gxe.xml.Generator",null,{
1316   context: null,
1317   documentTitle: null,
1318   hadValidationErrors: false,
1319   isValidating: false,
1320   isValidatingTitleOnly: false,
1321   isSaveAsDraft: false,
1322   
1323   /**
1324    * Executes the text content of an XML attribute. 
1325    * @function 
1326    * @name escAttribute
1327    * @memberOf gxe.xml.Generator#
1328    * @param {String} s the string to escape
1329    * @returns {String} the escaped string
1330    */
1331   escAttribute: function(s) {
1332     return this._execEscape(s,true);
1333   },
1334   
1335   /**
1336    * Executes the text content of an XML element. 
1337    * @function 
1338    * @name escElement
1339    * @memberOf gxe.xml.Generator#
1340    * @param {String} s the string to escape
1341    * @returns {String} the escaped string
1342    */
1343   escElement: function(s) {
1344     return this._execEscape(s,true);
1345   },
1346   
1347   /**
1348    * Executes and XML escape against a string. 
1349    * @function 
1350    * @name _execEscape
1351    * @memberOf gxe.xml.Generator#
1352    * @param {String} s the string to escape
1353    * @param {boolean} bEscapeApostrophe true if apostrophies should be escaped 
1354    * @returns {String} the escaped string
1355    */
1356   _execEscape: function(s,bEscapeApostrophe) {
1357     if (s == null) {
1358       return null;
1359     } else if (s.length == 0) {
1360       return s;
1361     } else {
1362       var sApos = "'";
1363       if (!bEscapeApostrophe) sApos = "'";
1364       var sb = "";
1365       for (var i=0; i<s.length; i++) {
1366         var c = s.charAt(i);
1367         if      (c == "&")  sb += "&";
1368         else if (c == "<")  sb += "<";
1369         else if (c == '>')  sb += ">";
1370         else if (c == '\'') sb += sApos;
1371         else if (c == '"')  sb += """;
1372         else                sb += c;
1373       }
1374       return sb;
1375     }
1376   },
1377 
1378   /**
1379    * Serializes an XML document. 
1380    * @function 
1381    * @name generate
1382    * @memberOf gxe.xml.Generator#
1383    * @param {gxe.Context} context the editor context
1384    * @param {boolean} asDraft true if the serialization is for a draft document 
1385    *   (minimal validation)
1386    * @returns {String} the serialized string
1387    */
1388   generate: function(context,asDraft) {
1389     this.context = context;
1390     this.documentTitle = null;
1391     this.isValidating = true;
1392     this.isValidatingTitleOnly = false;
1393     this.isSaveAsDraft = asDraft;
1394     this.context.messageArea.clearAll();
1395     var stringBuffer = new gxe.util.StringBuffer();
1396     context.xmlDocument.rootElement.echo(this,stringBuffer,0);
1397       
1398     var sXml = stringBuffer.toString();
1399     sXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+sXml; 
1400     return sXml;
1401   },
1402 
1403   /**
1404    * Handles a validation error. 
1405    * @function 
1406    * @name handleValidationError
1407    * @memberOf gxe.xml.Generator#
1408    * @param {sMessage} sMessage the message
1409    * @param {gxe.xml.XmlNode} xmlNode the target XML node that threw the error
1410    * @returns {gxe.control.InputBase} inputControl the input control for the target node
1411    */
1412   handleValidationError: function(sMessage,xmlNode,inputControl) {
1413     var bHadErrors = false;
1414     if (!this.isSaveAsDraft) {
1415       this.hadValidationErrors = true;
1416       this.context.messageArea.addValidationError(sMessage,xmlNode,inputControl);
1417     } else {
1418       var bIsTitle = xmlNode.nodeInfo.isDocumentTitle;
1419       if (bIsTitle) {
1420         this.hadValidationErrors = true;
1421         this.context.messageArea.addValidationError(sMessage,xmlNode,inputControl);
1422       }
1423     }
1424     if (!bHadErrors && this.hadValidationErrors) {
1425       dojo.addClass(this.context.htmlParentElement,"gxeRepairMode");
1426     }
1427   }
1428 
1429 });
1430 
1431 /**
1432  * @class A target XML document.
1433  * @name gxe.xml.XmlDocument
1434  * @property {gxe.Context} context the editor context 
1435  * @property {gxe.xml.XmlNamespaces} namespaces the namespaces associated with the document
1436  * @property {gxe.xml.XmlElement} rootElement the root element for the document
1437  */
1438 dojo.provide("gxe.xml.XmlDocument");
1439 dojo.declare("gxe.xml.XmlDocument",null,{
1440   context: null,
1441   namespaces: null,
1442   rootElement: null,
1443 
1444   /** constructor */
1445   constructor: function() {
1446     this.namespaces = new gxe.xml.XmlNamespaces();
1447   },
1448 
1449   /**
1450    * Initializes the root element and XML namespaces. 
1451    * @function 
1452    * @name initializeRoot
1453    * @memberOf gxe.xml.XmlDocument#
1454    * @param {gxe.Context} context the editor context 
1455    * @param {Object} cfgDefinition the configured editor definition (JSON)
1456    * @returns {gxe.xml.XmlElement} the root element for the document
1457    */
1458   initializeRoot: function(context,cfgDefinition) {
1459     this.context = context;
1460     this.namespaces = new gxe.xml.XmlNamespaces();
1461     
1462     var cfgRoot = gxe.cfg.findGxeChild(cfgDefinition,"rootElement");
1463     if (cfgRoot == null) {
1464       throw new Error("The editor definition contains no g:rootElement.");
1465     }
1466     
1467     var cfgItems = gxe.cfg.findChild(cfgRoot,gxe.cfg.uriGxe,"namespaces");
1468     if (cfgItems != null) {
1469       gxe.cfg.forEachChild(cfgItems,gxe.cfg.uriGxe,"namespace",dojo.hitch(this,function(cfgNS) {
1470         var pfx = gxe.cfg.getGxeAttributeValue(cfgNS,"prefix");
1471         var uri = gxe.cfg.getGxeAttributeValue(cfgNS,"uri");
1472         this.namespaces.add(new gxe.xml.XmlNamespace(pfx,uri));
1473       }));
1474     }
1475 
1476     this.rootElement = new gxe.xml.XmlElement(this,null,cfgRoot);
1477 
1478     // assume all namespace prefixes are specified on the root node
1479     var n = this.namespaces.getLength();
1480     for (var i=0; i<n; i++) {
1481       var ns = this.namespaces.getItem(i);
1482       if ((ns != null) && (ns.uri != null) && (ns.uri.length > 0)) {
1483         var ni = new gxe.xml.XmlNodeInfo();
1484         if ((ns.prefix != null) && (ns.prefix.length > 0)) {
1485           ni.namespacePrefix ="xmlns";
1486           ni.localName = ns.prefix;
1487           ni.nodeValue = ns.uri;
1488         } else {
1489           ni.localName ="xmlns";
1490           ni.nodeValue = ns.uri;
1491         }
1492         var attr = new gxe.xml.XmlAttribute(this,this.rootElement,null);
1493         attr.nodeInfo = ni;
1494         this.rootElement.attributes.add(attr);
1495       }                           
1496     }
1497     
1498     return this.rootElement;
1499   }
1500   
1501 });
1502 
1503 /**
1504  * @class A target XML node (element or attribute).
1505  * @name gxe.xml.XmlNode
1506  * @property {Object} cfgObject the associated editor configuration object
1507  * @property {boolean} isOptionalPlaceHolder true if this node is an optional place holder only 
1508  *   (no serialization)
1509  * @property {boolean} isPlaceHolder true if this node is a place holder only 
1510  *   (no serialization)
1511  * @property {gxe.xml.XmlNodeInfo} nodeInfo the node information
1512  * @property {gxe.xml.XmlDocument} parentDocument the parent XML document
1513  * @property {Element} parentElement the parent HTML element
1514  */
1515 dojo.provide("gxe.xml.XmlNode");
1516 dojo.declare("gxe.xml.XmlNode",null,{
1517   _dataExclusiveRadioButton: null,
1518   _dataInputControl: null,
1519   cfgObject: null,
1520   isOptionalPlaceHolder: false,
1521   isPlaceHolder: false,
1522   nodeInfo: null,
1523   parentDocument: null,
1524   parentElement: null,
1525   
1526   /**
1527    * Constructor. 
1528    * @function 
1529    * @name constructor
1530    * @memberOf gxe.xml.XmlNode#
1531    * @param {gxe.xml.XmlDocument} parentDocument the parent XML document
1532    * @param {Element} parentElement the parent HTML element
1533    * @param {Object} cfgObject the associated editor configuration object
1534    */
1535   constructor: function(parentDocument,parentElement,cfgObject) {
1536     this.parentDocument = parentDocument;
1537     this.parentElement = parentElement;
1538     this.cfgObject = cfgObject;
1539     this.intitalizeNodeInfo();
1540   },
1541 
1542   /**
1543    * Serializes XML content. 
1544    * @function 
1545    * @name echo
1546    * @memberOf gxe.xml.XmlNode#
1547    * @param {gxe.xml.Generator} xmlGenerator the XML generator
1548    * @param {gxe.util.StringBuffer} stringBuffer the buffer to which content will be written
1549    * @param {Integer} nDepth the indentation depth
1550    */
1551   echo: function(xmlGenerator,stringBuffer,nDepth) {},
1552   
1553   /**
1554    * Formats a validation message.
1555    * @function 
1556    * @name formatValidationMessage
1557    * @memberOf gxe.xml.XmlNode#
1558    * @param {gxe.control.InputBase} inputControl the associated input control
1559    * @param {String} i18nContextKey the context key associated with the localized message string
1560    * @returns {String} the message
1561    */
1562   formatValidationMessage: function(inputControl,i18nContextKey) {
1563     var l = inputControl.findParentLabelText(this);
1564     var f = this.parentDocument.context.getI18NString("validate.format");
1565     var m = this.parentDocument.context.getI18NString(i18nContextKey);
1566     var s = l+" "+m;
1567     if (f != null) {
1568       if ((f.indexOf("{label}") != -1) && (f.indexOf("{message}") != -1)) {
1569         s = f.replace("{label}",l).replace("{message}",m);
1570       }
1571     }
1572     
1573     /*
1574     if ((i18nContextKey != "validate.ok") && (inputControl.htmlElement != null)) {
1575       var sTip = inputControl.htmlElement.title;
1576       if ((typeof(sTip) != "undefined") && (sTip != null)) {
1577         sTip = dojo.trim(sTip);
1578         if (sTip.length > 0) s += " "+sTip;
1579       }
1580     }
1581     */
1582     
1583     return s;
1584   },
1585 
1586   /**
1587    * Indicates if this node should be serialized even if its content is empty.
1588    * <br/>Based upon cfg attribute g:serializeIfEmpty.
1589    * @function 
1590    * @name getSerializeIfEmpty
1591    * @memberOf gxe.xml.XmlNode#
1592    * @returns {boolean} true if this node should be serialized when empty
1593    */
1594   getSerializeIfEmpty: function() {
1595     if (this.cfgObject == null) return false;
1596     var s = gxe.cfg.getGxeAttributeValue(this.cfgObject,"serializeIfEmpty");
1597     return (s == "true");
1598   },
1599 
1600 
1601   /** this pair no longer in use */
1602   getExclusiveRadioButton: function() {return this._dataExclusiveRadioButton;},
1603   setExclusiveRadioButton: function(ctl) {this._dataExclusiveRadioButton = ctl;},
1604 
1605   /**
1606    * Gets the input control associated with this node.
1607    * @function 
1608    * @name getInputControl
1609    * @memberOf gxe.xml.XmlNode#
1610    * @returns {gxe.control.InputBase} the input control (can be null)
1611    */
1612   getInputControl: function() {return this._dataInputControl;},
1613   
1614   /**
1615    * Sets the input control associated with this node.
1616    * @function 
1617    * @name setInputControl
1618    * @memberOf gxe.xml.XmlNode#
1619    * @param {gxe.control.InputBase} ctl the input control
1620    */
1621   setInputControl: function(ctl) {this._dataInputControl = ctl;},
1622 
1623   /**
1624    * Gets the default label text for the node.
1625    * @function 
1626    * @name getLabelText
1627    * @memberOf gxe.xml.XmlNode#
1628    * @returns {String} the label text
1629    */
1630   getLabelText: function() {
1631     return gxe.cfg.getLabelText(this.cfgObject);
1632   },
1633 
1634   /**
1635    * Gets the qualified URI for the node.
1636    * <br/>Format: namespaceURI#localName
1637    * @function 
1638    * @name getQualifiedUri
1639    * @memberOf gxe.xml.XmlNode#
1640    * @returns {String} the qualified URI
1641    */
1642   getQualifiedUri: function() {
1643     return this.nodeInfo.namespaceURI+"#"+this.nodeInfo.localName;
1644   },
1645 
1646   /**
1647    * Initializes the node info.
1648    * @function 
1649    * @name intitalizeNodeInfo
1650    * @memberOf gxe.xml.XmlNode#
1651    */
1652   intitalizeNodeInfo: function() {
1653     this.nodeInfo = new gxe.xml.XmlNodeInfo();
1654     if (this.cfgObject == null) return;
1655     if (this.parentDocument == null) return;
1656     
1657     var ni = this.nodeInfo;
1658     ni.namespaceURI = gxe.cfg.getTargetNS(this.cfgObject);
1659     ni.namespacePrefix = this.parentDocument.namespaces.getPrefix(ni.namespaceURI);
1660     ni.localName = gxe.cfg.getTargetName(this.cfgObject);
1661 
1662     var sIsTitle = gxe.cfg.getGxeAttributeValue(this.cfgObject,"isDocumentTitle");
1663     if (sIsTitle == "true") ni.isDocumentTitle = true;
1664     var isIsoCLV = gxe.cfg.getGxeAttributeValue(this.cfgObject,"isIsoCLV");
1665     if (isIsoCLV == "true") ni.isIsoCodeListValue = true;
1666     var isIsoWMVL = gxe.cfg.getGxeAttributeValue(this.cfgObject,"isIsoWMVL");
1667     if (isIsoWMVL == "true") ni.isIsoWrappedMultiValueList = true;
1668   },
1669 
1670   /**
1671    * Determines if the node represents an XML attribute.
1672    * @function 
1673    * @name isAttribute
1674    * @memberOf gxe.xml.XmlNode#
1675    * @returns {boolean} true if the node is an attribute
1676    */
1677   isAttribute: function() {return false;},
1678   
1679   /**
1680    * Determines if the node is repeatable.
1681    * @function 
1682    * @name isRepeatable
1683    * @memberOf gxe.xml.XmlNode#
1684    * @returns {boolean} true if the node is repeatable
1685    */
1686   isRepeatable: function() {return false;},
1687   
1688   /**
1689    * Resolves the minimum number of occurrences for the node.
1690    * @function 
1691    * @name resolveMinOccurs
1692    * @memberOf gxe.xml.XmlNode#
1693    * @returns {Integer} the minimum number of occurrences
1694    */
1695   resolveMinOccurs: function() {
1696     var nMinOccurs = null;
1697     var sMinOccurs = gxe.cfg.getGxeAttributeValue(this.cfgObject,"minOccurs");
1698     if (sMinOccurs != null) {
1699       var pe = this.parentElement;
1700       if ((sMinOccurs == "$parent") && (pe != null)) {
1701         sMinOccurs = gxe.cfg.getGxeAttributeValue(pe.cfgObject,"minOccurs");
1702         if (sMinOccurs == "$parent") {
1703           pe = pe.parentElement;
1704           if (pe != null) {
1705             sMinOccurs = gxe.cfg.getGxeAttributeValue(pe.cfgObject,"minOccurs");
1706           }
1707         }
1708       }
1709       var n = parseInt(sMinOccurs);
1710       if (!isNaN(n)) {
1711         nMinOccurs = n;
1712         if (nMinOccurs < 0) nMinOccurs = 0;
1713         if ((nMinOccurs > 1) && this.isAttribute()) nMinOccurs = 1;
1714       }
1715     }
1716     if (nMinOccurs == null) {
1717       var sUse = gxe.cfg.getGxeAttributeValue(this.cfgObject,"use");
1718       if (sUse == "optional") nMinOccurs = 0;
1719       else if (sUse == "required") nMinOccurs = 1;
1720     }
1721     if (nMinOccurs == null) {
1722       if (this.isAttribute()) nMinOccurs = 0;
1723       else nMinOccurs = 1;
1724     }
1725     return nMinOccurs;
1726   },
1727 
1728   /**
1729    * Validates the content of an input control.
1730    * @function 
1731    * @name validateInput
1732    * @memberOf gxe.xml.XmlNode#
1733    * @param {gxe.control.InputBase} inputControl the input control
1734    * @param {boolean} bInFeedbackMode true if the request is part of validation feedback
1735    * @returns {"isValid":{boolean}, "message":{String}} the validation status
1736    */
1737   validateInput: function(inputControl,bInFeedbackMode) {
1738     if (inputControl.getSupportsMultipleValues()) {
1739       return this.validateInputValues(inputControl,inputControl.getInputValues(bInFeedbackMode));
1740     } else {
1741       return this.validateInputValue(inputControl,inputControl.getInputValue(bInFeedbackMode));
1742     }
1743   },
1744 
1745   /**
1746    * Validates an input value associated with a control.
1747    * @function 
1748    * @name validateInputValue
1749    * @memberOf gxe.xml.XmlNode#
1750    * @param {gxe.control.InputBase} inputControl the input control that generated the value
1751    * @param {String} value the input value
1752    * @returns {"isValid":{boolean}, "message":{String}} the validation status
1753    */
1754   validateInputValue: function(inputControl,value) {
1755     var regexp;
1756     var status = {"isValid": true, "message": "?ok"};
1757     
1758     var sLabel = inputControl.findParentLabelText(this);
1759     status.message = this.formatValidationMessage(inputControl,"validate.ok");
1760 
1761     // check for empty input
1762     if ((value == null) || (dojo.trim(value).length == 0)) {
1763       var nMinOccurs = this.resolveMinOccurs();   
1764       if ((nMinOccurs >= 1) && !this.getSerializeIfEmpty()) {
1765         status.isValid = false;
1766         status.message = this.formatValidationMessage(inputControl,"validate.empty");
1767         return status;
1768       } else {
1769         return status;
1770       }
1771     }
1772     
1773     // check for acceptable alternate values
1774     if (!status.isValid) return status;
1775     var sAlternates = gxe.cfg.getGxeAttributeValue(this.cfgObject,"alternateValues");
1776     if (sAlternates != null) {
1777       var aAlternates = sAlternates.split(",");
1778       for (var i=0;i<aAlternates.length;i++) {
1779         var sAlternate = dojo.trim(aAlternates[i]);
1780         if ((sAlternate.length > 0) && (sAlternate == value)) return status;
1781       }
1782     }
1783     
1784     // check types, TODO not all xs: types are implemented
1785     if (!status.isValid) return status;
1786     var sType = gxe.cfg.getGxeAttributeValue(this.cfgObject,"valueType");
1787     if (sType != null) sType = dojo.trim(sType);
1788    
1789     if ((sType == "integer") || (sType == "xs:integer") || (sType == "xsd:integer")) {
1790       // the expression is not definitive
1791       //regexp = /^[-]?[0-9]+$/;
1792       regexp  = /(^-?\d\d*$)/;
1793       if (!regexp.test(value)) {
1794         status.isValid = false;
1795         status.message = this.formatValidationMessage(inputControl,"validate.integer");
1796       }
1797       
1798     } else if ((sType == "decimal") || (sType == "xs:decimal") || (sType == "xsd:decimal") ||
1799                (sType == "double") || (sType == "xs:double") || (sType == "xsd:double") ||
1800                (sType == "float") || (sType == "xs:float") || (sType == "xsd:float") ||
1801                (sType == "number")) {
1802       // same expression for any non-integer type, should be more explicit
1803       // the expression is not definitive
1804       regexp = /(^-?\d\d*\.\d*$)|(^-?\d\d*$)|(^-?\.\d\d*$)/;
1805       if (!regexp.test(value)) {
1806         status.isValid = false;
1807         status.message = this.formatValidationMessage(inputControl,"validate.number");
1808       }
1809       
1810     } else if ((sType == "date") || (sType == "xs:date") || (sType == "xsd:date")) {
1811 
1812       // allows yyyy-mm-ddZ or yyyy-mm-dd or yyyy-mm or yyyy
1813       var bOk = false;
1814       var regexp1 = /^(\d{4})$/;
1815       var regexp2 = /^(\d{2})$/;
1816       var parts = value.split("-");
1817             
1818       if (regexp1.test(parts[0])) {
1819         if (parts.length > 1) {
1820           if (regexp2.test(parts[1])) {
1821             if (parts.length > 2) {
1822               if (parts.length == 3) {
1823                 if (parts[2].charAt(parts[2].length-1) == 'Z') {
1824                   parts[2] = parts[2].substring(0,parts[2].length-1);
1825                 }
1826                 if (regexp2.test(parts[2])) bOk = true;
1827               }
1828             } else bOk = true;
1829           }
1830         } else bOk = true;
1831       }
1832 
1833       if (!bOk) {
1834         status.isValid = false;
1835         status.message = this.formatValidationMessage(inputControl,"validate.date");
1836       }
1837       
1838     } else if ((sType == "dateTime") || (sType == "xs:dateTime") || (sType == "xsd:dateTime")) {
1839       // TODO not handled
1840       
1841     } else if (sType == "fgdc:date") {
1842 
1843       // allows yyyymmdd or yyyymm or yyyy
1844       var bOk = false;
1845       var regexp1 = /^(\d{4})$/;
1846       var regexp2 = /^(\d{2})$/;
1847       var parts = new Array();
1848       if (value.length == 8) {
1849         parts[0] = value.substring(0,4);
1850         parts[1] = value.substring(4,6);
1851         parts[2] = value.substring(6,8);
1852       } else if (value.length == 6) {
1853         parts[0] = value.substring(0,4);
1854         parts[1] = value.substring(4,6);
1855       } else if (value.length == 4) {
1856         parts[0] = value.substring(0,4); 
1857       }
1858       if (parts.length > 0) {
1859         if (regexp1.test(parts[0])) {
1860           if (parts.length > 1) {
1861             if (regexp2.test(parts[1])) {
1862               if (parts.length > 2) {
1863                 if (parts.length == 3) {
1864                   if (parts[2].charAt(parts[2].length-1) == 'Z') {
1865                     parts[2] = parts[2].substring(0,parts[2].length-1);
1866                   }
1867                   if (regexp2.test(parts[2])) bOk = true;
1868                 }
1869               } else bOk = true;
1870             }
1871           } else bOk = true;
1872         }
1873       }
1874       if (!bOk) {
1875         status.isValid = false;
1876         status.message = this.formatValidationMessage(inputControl,"validate.date");
1877       }
1878 
1879     } else if (sType == "fgdc:time") {
1880       
1881       // (hours minutes seconds) examples: hh hhmm hhmmss 
1882       // (offset from GMT) examples: hh+hhmm hhmmss-hhmm
1883       // (suffixed with Z for Zulu time) examples: hhZ hhmmZ hhmmssZ
1884       // (decimal seconds are ssssssss)
1885       var regexp1 = /^\d{2}(\d{2}(\d{2,})?)?$/;
1886       var regexp2 = /^\d{2}(\d{2}(\d{2,})?)?[+\-]\d{4}$/;
1887       var regexp3 = /^\d{2}(\d{2}(\d{2,})?)?Z$/;
1888       if (!regexp1.test(value) && !regexp2.test(value) && !regexp3.test(value)) {
1889         status.isValid = false;
1890         status.message = this.formatValidationMessage(inputControl,"validate.other");
1891       }
1892       
1893     }
1894 
1895     // check restrictions, TODO not all xs: restrictions are implemented
1896     if (!status.isValid) return status;
1897     gxe.cfg.forEachChild(this.cfgObject,gxe.cfg.uriGxe,"restriction",dojo.hitch(this,function(cfgRestriction) {
1898       gxe.cfg.forEachChild(cfgRestriction,gxe.cfg.uriGxe,"*",dojo.hitch(this,function(cfgChild) {        
1899 
1900         if (cfgChild.name == "pattern") {
1901           var pattern = gxe.cfg.getGxeAttributeValue(cfgChild,"value");
1902           if (pattern != null) {
1903             try {
1904               // TODO what about ["g"|"i"|"gi"]
1905               // var regExp = new RegExp("PATTERN", ["g"|"i"|"gi"]);
1906               regexp = new RegExp(pattern);
1907               var obj = regexp.exec(value);
1908               if (!regexp.test(value)) {
1909                 status.isValid = false;
1910                 //status.message = "?invalid "+sLabel+" "+pattern;
1911                 status.message = this.formatValidationMessage(inputControl,"validate.other");
1912                 return "break";
1913               }  
1914             } catch (err) {
1915               console.log(err+", "+gxe.cfg.getGxeAttributeValue(this.cfgObject,"targetName")+
1916                   ", the g:"+cfgChild.name+" is incorrectly defined, "+pattern);
1917             }
1918           }
1919         }
1920         
1921         if ((cfgChild.name == "minExclusive") || (cfgChild.name == "minInclusive") || 
1922             (cfgChild.name == "maxExclusive") || (cfgChild.name == "maxInclusive")) {
1923           var nValue = new Number(value);
1924           var sBound = gxe.cfg.getGxeAttributeValue(cfgChild,"value");
1925           if (!isNaN(nValue)) {
1926             var nBound = new Number(sBound);
1927             if ((sBound != null) && !isNaN(nBound)) {
1928               if (cfgChild.name == "minExclusive") {
1929                 if (nValue <= nBound) status.isValid = false;
1930               } else if (cfgChild.name == "minInclusive") {
1931                 if (nValue < nBound) status.isValid = false;
1932               } else if (cfgChild.name == "maxExclusive") {
1933                 if (nValue >= nBound) status.isValid = false;
1934               } else if (cfgChild.name == "maxInclusive") {
1935                 if (nValue > nBound) status.isValid = false;
1936               }
1937               if (!status.isValid) {
1938                status.message = this.formatValidationMessage(inputControl,"validate.other");
1939               }
1940             } else {
1941               console.log(gxe.cfg.getGxeAttributeValue(this.cfgObject,"targetName")+
1942                   ", the g:"+cfgChild.name+" bound is incorrectly defined, "+sBound);
1943             }
1944           } else {
1945             status.isValid = false;
1946             status.message = this.formatValidationMessage(inputControl,"validate.other");
1947           }
1948         }
1949         
1950         if ((cfgChild.name == "length") ||  
1951             (cfgChild.name == "minLength") || (cfgChild.name == "maxLength")) {
1952           var nLength = value.length;
1953           var sBound = gxe.cfg.getGxeAttributeValue(cfgChild,"value");
1954           var nBound = new Number(sBound);
1955           if ((sBound != null) && !isNaN(nBound)) {
1956             if (cfgChild.name == "length") {
1957               if (nLength != nBound) status.isValid = false;
1958             } else if (cfgChild.name == "minLength") {
1959               if (nLength < nBound) status.isValid = false;
1960             } else if (cfgChild.name == "maxLength") {
1961               if (nLength > nBound) status.isValid = false;
1962             }
1963             if (!status.isValid) {
1964              status.message = this.formatValidationMessage(inputControl,"validate.other");
1965             }
1966           } else {
1967             console.log(gxe.cfg.getGxeAttributeValue(this.cfgObject,"targetName")+
1968                 ", the g:"+cfgChild.name+" bound is incorrectly defined, "+sBound);
1969           }
1970         }
1971         
1972       })); 
1973       if (!status.isValid) return "break";
1974     }));
1975 
1976     return status;
1977   },
1978 
1979   /**
1980    * Validates multiple input values associated with a control.
1981    * @function 
1982    * @name validateInputValues
1983    * @memberOf gxe.xml.XmlNode#
1984    * @param {gxe.control.InputBase} inputControl the input control that generated the values
1985    * @param {Array} values the value array
1986    * @returns {"isValid":{boolean}, "message":{String}} the validation status
1987    */
1988   validateInputValues: function(inputControl,values) {
1989     var status = {"isValid": false, "message": null};
1990     if ((values == null) || (values.length == 0)) {
1991       var nMinOccurs = this.resolveMinOccurs(); 
1992       if ((nMinOccurs >= 1) && !this.getSerializeIfEmpty()) {
1993         status.isValid = false;
1994         status.message = this.formatValidationMessage(inputControl,"validate.empty");
1995       } else {
1996         status.isValid = true;
1997       }
1998     } else {
1999       status.isValid = true;
2000     }
2001     return status;
2002   },
2003   
2004   /**
2005    * Determines if this element wraps an ISO19139 multi-value list.
2006    * <br/>(e.g. MD_TopicCategoryCode - InputSelectMany)
2007    * @function 
2008    * @name wrapsIsoMultiValueList
2009    * @memberOf gxe.xml.XmlNode#
2010    * @returns {boolean}
2011    */
2012   wrapsIsoMultiValueList: function() {
2013     return false;
2014   }
2015   
2016 });
2017 
2018 /**
2019  * @class A target XML attribute.
2020  * @name gxe.xml.XmlAttribute
2021  * @extends gxe.xml.XmlNode
2022  */
2023 dojo.provide("gxe.xml.XmlAttribute");
2024 dojo.declare("gxe.xml.XmlAttribute",gxe.xml.XmlNode,{
2025 
2026   /** Override gxe.xml.XmlNode.echo() */
2027   echo: function(xmlGenerator,stringBuffer,nDepth) {
2028     if (this.isPlaceHolder || this.isOptionalPlaceHolder) return;
2029     
2030     var bSerialize = true;
2031     var bValidating = xmlGenerator.isValidating;
2032     var bIsTitle = this.nodeInfo.isDocumentTitle;
2033     if (bIsTitle && !bValidating) {
2034       if (xmlGenerator.isValidatingTitleOnly) bValidating = true;
2035     }
2036     var bSerializeIfEmpty = (this.getSerializeIfEmpty() || !bValidating);
2037 
2038     var sNodeValue = this.nodeInfo.nodeValue;
2039     var inputControl = this.getInputControl();
2040     if (inputControl != null) {
2041       this.nodeInfo.nodeValue = inputControl.getInputValue();
2042       sNodeValue = this.nodeInfo.nodeValue;
2043     }
2044     if (sNodeValue == null) sNodeValue = "";
2045     else sNodeValue = dojo.trim(sNodeValue);
2046     var bIsEmpty = (sNodeValue.length == 0);
2047 
2048     if ((inputControl != null) && bValidating) {
2049       var status = this.validateInputValue(inputControl,sNodeValue);
2050       if (!status.isValid) {
2051         bSerialize = false;
2052         xmlGenerator.handleValidationError(status.message,this,inputControl);
2053       }
2054     }
2055     if (bIsTitle) xmlGenerator.documentTitle = sNodeValue;
2056     
2057     if (bSerialize && (!bIsEmpty || bSerializeIfEmpty)) {
2058       var sNodeName = this.nodeInfo.localName;
2059       if (this.nodeInfo.namespacePrefix != null) {
2060         sNodeName = this.nodeInfo.namespacePrefix+":"+sNodeName;
2061       }
2062       sNodeValue = xmlGenerator.escAttribute(sNodeValue);
2063       stringBuffer.append(" "+sNodeName+"=\""+sNodeValue+"\"");
2064     }
2065   },
2066 
2067   /** Override gxe.xml.XmlNode.isAttribute() */
2068   isAttribute: function() {return true;},
2069   
2070   /** Override gxe.xml.XmlNode.isRepeatable() */
2071   isRepeatable: function() {return false;}
2072 });
2073 
2074 /**
2075  * @class A collection of XML attributes (of type gxe.xml.XmlAttribute).
2076  * @name gxe.xml.XmlAttributes
2077  * @extends gxe.util.ArrayList
2078  */
2079 dojo.provide("gxe.xml.XmlAttributes");
2080 dojo.declare("gxe.xml.XmlAttributes",gxe.util.ArrayList,{
2081 
2082   /**
2083    * Serializes XML content. 
2084    * @function 
2085    * @name echo
2086    * @memberOf gxe.xml.XmlAttributes#
2087    * @param {gxe.xml.Generator} xmlGenerator the XML generator
2088    * @param {gxe.util.StringBuffer} stringBuffer the buffer to which content will be written
2089    * @param {Integer} nDepth the indentation depth
2090    */
2091   echo: function(xmlGenerator,stringBuffer,nDepth) {
2092     var n = this.getLength();
2093     for (var i=0; i<n; i++) this.getItem(i).echo(xmlGenerator,stringBuffer,nDepth);
2094   }
2095 });
2096 
2097 /**
2098  * @class A target XML element.
2099  * @name gxe.xml.XmlElement
2100  * @extends gxe.xml.XmlNode
2101  */
2102 dojo.provide("gxe.xml.XmlElement");
2103 dojo.declare("gxe.xml.XmlElement",gxe.xml.XmlNode,{
2104   attributes: null,
2105   children: null,
2106   exclusiveChoiceControl: null,
2107   repeatablesContainer: null,
2108 
2109   /** constructor */
2110   constructor: function() {
2111     this.attributes = new gxe.xml.XmlAttributes();
2112     this.children = new gxe.xml.XmlElements();
2113   },
2114 
2115   /** Override gxe.xml.XmlNode.echo() */
2116   echo: function(xmlGenerator,stringBuffer,nDepth) {
2117     if (this.isPlaceHolder || this.isOptionalPlaceHolder) return;
2118     
2119     var pfx = "\r\n";
2120     for (var i=0; i<nDepth; i++) pfx += "\t";
2121     if (this.exclusiveChoiceControl != null) {
2122       if (!this.exclusiveChoiceControl.isElementSelected(this)) return;
2123     }
2124 
2125     var bValidating = xmlGenerator.isValidating;
2126     var bSerializeIfEmpty = (this.getSerializeIfEmpty() || !bValidating);
2127     var bIsTitle = this.nodeInfo.isDocumentTitle;
2128     var inputValues = null;
2129     var inputControl = this.getInputControl();
2130     if (inputControl != null) {
2131       this.nodeInfo.nodeValue = null;
2132       if (inputControl.getSupportsMultipleValues()) {
2133 
2134         inputValues = inputControl.getInputValues();
2135         if ((inputValues != null) && (inputValues.length > 0)) {
2136           this.nodeInfo.nodeValue = inputValues[0];
2137         }
2138 
2139         if (bValidating || (bIsTitle && xmlGenerator.isValidatingTitleOnly)) {
2140           var status = this.validateInputValues(inputControl,inputValues);
2141           if (!status.isValid) {
2142             bSerializeIfEmpty = false;
2143             this.nodeInfo.nodeValue = null;
2144             inputValues = null;
2145             xmlGenerator.handleValidationError(status.message,this,inputControl);
2146           }
2147         }
2148         
2149       } else {
2150         var inputValue = inputControl.getInputValue();
2151         if (inputValue != null) {
2152           this.nodeInfo.nodeValue = inputValue;
2153         }
2154         if (bValidating || (bIsTitle && xmlGenerator.isValidatingTitleOnly)) {
2155           var status = this.validateInputValue(inputControl,this.nodeInfo.nodeValue);
2156           if (!status.isValid) {
2157             bSerializeIfEmpty = false;
2158             this.nodeInfo.nodeValue = null;
2159             xmlGenerator.handleValidationError(status.message,this,inputControl);
2160           }
2161         }
2162       }
2163     }
2164     if (bIsTitle) xmlGenerator.documentTitle = this.nodeInfo.nodeValue;
2165 
2166     var nodeName = this.nodeInfo.localName;
2167     if (this.nodeInfo.namespacePrefix != null) {
2168       nodeName = this.nodeInfo.namespacePrefix+":"+nodeName;
2169     }
2170     var nodeValue = this.nodeInfo.nodeValue;
2171 
2172     var sbAttributePortion = new gxe.util.StringBuffer();
2173     this.attributes.echo(xmlGenerator,sbAttributePortion,nDepth);
2174     var sAttributePortion = sbAttributePortion.toString();
2175     var bHasAttributePortion = (sAttributePortion.length > 0);
2176     
2177     var bIsIsoCodeListValue = this.nodeInfo.isIsoCodeListValue;
2178     if (bIsIsoCodeListValue) {
2179       var n = this.attributes.getLength();
2180       for (var i=0; i<n; i++) {
2181         var attr = this.attributes.getItem(i);
2182         if (attr.nodeInfo.localName == "codeListValue") {
2183           var ic = attr.getInputControl();
2184           if (ic != null) {
2185             if (!ic.getSupportsMultipleValues()) {
2186               var sCode = ic.getInputValue(true);
2187               if ((sCode == null) || (sCode.length == 0)) return;
2188               else nodeValue = sCode;
2189             }
2190           }
2191         }
2192       }
2193     }
2194     
2195     // isIsoWrappedMultiValueList, e.g. MD_TopicCategoryCode - InputSelectMany
2196     if (this.wrapsIsoMultiValueList()) {
2197       this.children.getItem(0).echo(xmlGenerator,stringBuffer,nDepth);
2198       return;
2199     } else if (this.nodeInfo.isIsoWrappedMultiValueList) {
2200       if ((inputValues != null) && (inputValues.length > 0)) {
2201         var pNodeInfo = this.parentElement.nodeInfo;
2202         var pNodeName = pNodeInfo.localName;
2203         if (pNodeInfo.namespacePrefix != null) {
2204           pNodeName = pNodeInfo.namespacePrefix+":"+pNodeName;
2205         }
2206         var n = inputValues.length;
2207         for (var i=0; i<n; i++) {
2208           stringBuffer.append(pfx+"<"+pNodeName+">");
2209           stringBuffer.append(pfx+"\t<"+nodeName+">");
2210           stringBuffer.append(xmlGenerator.escElement(inputValues[i]));
2211           stringBuffer.append("</"+nodeName+">");
2212           stringBuffer.append(pfx+"</"+pNodeName+">");
2213         }
2214       }
2215       return;    
2216     }
2217    
2218     if (nodeValue != null) nodeValue = xmlGenerator.escElement(nodeValue);
2219     var bHasValue = ((nodeValue != null) && (nodeValue.length > 0));
2220 
2221     var sbChildPortion = new gxe.util.StringBuffer();
2222     this.children.echo(xmlGenerator,sbChildPortion,nDepth);
2223     var sChildPortion = sbChildPortion.toString();
2224     var bHasChildPortion = (sChildPortion.length > 0);
2225 
2226     if (bHasValue || bHasAttributePortion || bHasChildPortion) {
2227       var sbElement = new gxe.util.StringBuffer();
2228       sbElement.append(pfx+"<"+nodeName);
2229       if (bHasAttributePortion) sbElement.append(sAttributePortion);
2230       if (bHasValue || bHasChildPortion) {
2231         sbElement.append(">");
2232         if (bHasValue) sbElement.append(nodeValue);
2233         if (bHasChildPortion) {
2234           sbElement.append(sChildPortion);
2235           sbElement.append(pfx);
2236         } 
2237         sbElement.append("</"+nodeName+">");
2238       } else {
2239         sbElement.append("/>");
2240       }
2241       stringBuffer.append(sbElement.toString());
2242     } else {
2243       if (bSerializeIfEmpty) stringBuffer.append(pfx+"<"+nodeName+"/>");
2244     }
2245 
2246     if ((inputValues != null) && (inputValues.length > 1)) {
2247       var n = inputValues.length;
2248       for (var i=1; i<n; i++) {
2249         var xmlSibling = new gxe.xml.XmlElement(this.parentDocument,this.parentElement,this.cfgObject);
2250         xmlSibling.nodeInfo.nodeValue = inputValues[i];
2251         xmlSibling.echo(xmlGenerator,stringBuffer,nDepth);
2252       }
2253     }
2254   },
2255 
2256   /** Override gxe.xml.XmlNode.isRepeatable() */
2257   isRepeatable: function() {
2258     var sMaxOccurs = gxe.cfg.getGxeAttributeValue(this.cfgObject,"maxOccurs");
2259     if (sMaxOccurs == "unbounded") {
2260       return true;
2261     } else {
2262       var nMaxOccurs = parseInt(sMaxOccurs);
2263       if (isNaN(nMaxOccurs)) nMaxOccurs = 1;
2264       return (nMaxOccurs > 1);
2265     }
2266     return false;
2267   },
2268   
2269   /** Override gxe.xml.XmlNode.wrapsIsoMultiValueList() */
2270   wrapsIsoMultiValueList: function() {
2271     if (this.children.getLength() == 1) {
2272       return this.children.getItem(0).nodeInfo.isIsoWrappedMultiValueList;
2273     }
2274     return false;
2275   }
2276   
2277 });
2278 
2279 /**
2280  * @class A collection of XML elements (of type gxe.xml.XmlElement).
2281  * @name gxe.xml.XmlElements
2282  * @extends gxe.util.ArrayList
2283  */
2284 dojo.provide("gxe.xml.XmlElements");
2285 dojo.declare("gxe.xml.XmlElements",gxe.util.ArrayList,{
2286 
2287   /**
2288    * Serializes XML content. 
2289    * @function 
2290    * @name echo
2291    * @memberOf gxe.xml.XmlElements#
2292    * @param {gxe.xml.Generator} xmlGenerator the XML generator
2293    * @param {gxe.util.StringBuffer} stringBuffer the buffer to which content will be written
2294    * @param {Integer} nDepth the indentation depth
2295    */
2296   echo: function(xmlGenerator,stringBuffer,nDepth) {
2297     var n = this.getLength();
2298     for (var i=0; i<n; i++) this.getItem(i).echo(xmlGenerator,stringBuffer,nDepth+1);
2299   },
2300 
2301   /**
2302    * Finds the array index associated with an element.
2303    * @function 
2304    * @name findIndex
2305    * @memberOf gxe.xml.XmlElements#
2306    * @param {gxe.xml.XmlElement} xmlElement the subject element
2307    * @returns {Integer} the associated index (-1 if not located)
2308    */
2309   findIndex: function(xmlElement) {
2310     var n = this.getLength();
2311     for (var i=0; i<n; i++) {
2312       if (this.getItem(i) == xmlElement) return i;
2313     }
2314     return -1;
2315   }
2316   
2317 });
2318 
2319 /**
2320  * @class An XML namespace.
2321  * @name gxe.xml.XmlNamespace
2322  * @extends gxe.util.ArrayList
2323  * @property {String} prefix the prefix
2324  * @property {String} uri the URI
2325  */
2326 dojo.provide("gxe.xml.XmlNamespace");
2327 dojo.declare("gxe.xml.XmlNamespace",null,{
2328   prefix: null,
2329   uri: null,
2330   
2331   /**
2332    * Constructor.
2333    * @function 
2334    * @name constructor
2335    * @memberOf gxe.xml.XmlNamespaces#
2336    * @param {String} prefix the prefix
2337    * @param {String} uri the URI
2338    */
2339   constructor: function(prefix,uri) {
2340     this.prefix = prefix;
2341     this.uri = uri;
2342   }
2343 });
2344 
2345 /**
2346  * @class A collection of XML namespaces.
2347  * @name gxe.xml.XmlNamespaces
2348  * @extends gxe.util.ArrayList
2349  */
2350 dojo.provide("gxe.xml.XmlNamespaces");
2351 dojo.declare("gxe.xml.XmlNamespaces",gxe.util.ArrayList,{
2352   
2353   /**
2354    * Gets the namespace prefix associated with a URI.
2355    * @function 
2356    * @name getPrefix
2357    * @memberOf gxe.xml.XmlNamespaces#
2358    * @param {String} uri the URI
2359    * @returns {String} the namespace prefix for the URI (null if not located)
2360    */
2361   getPrefix: function(uri) {
2362     var n = this.getLength();
2363     for (var i=0; i<n; i++) {
2364       var ns = this.getItem(i);
2365       if ((ns != null) && (ns.uri == uri)) {
2366         return ns.prefix;
2367       }                           
2368     }
2369     return null;
2370   },
2371 
2372   /**
2373    * Gets the namespace URI associated with a prefix.
2374    * @function 
2375    * @name getUri
2376    * @memberOf gxe.xml.XmlNamespaces#
2377    * @param {String} prefix the prefix
2378    * @returns {String} the namespace URI for the prefix (null if not located)
2379    */
2380   getUri: function(prefix) {
2381     var n = this.getLength();
2382     for (var i=0; i<n; i++) {
2383       var ns = this.getItem(i);
2384       if ((ns != null) && (ns.prefix == prefix)) {
2385         return ns.uri;
2386       }                           
2387     }
2388     return null;
2389   }
2390 });
2391 
2392 /**
2393  * @class Provides information about a target XML node.
2394  * @name gxe.xml.XmlNodeInfo
2395  * @property {boolean} isDocumentTitle true if this node represents the document title
2396  * @property {boolean} isIsoCodeListValue true if this node represents an
2397  *   ISO19139 code list value
2398  * @property {boolean} isIsoWrappedMultiValueList true if this node represents an
2399  *   ISO19139 wrapped multi-value list (e.g. MD_TopicCategoryCode - InputSelectMany)
2400  * @property {String} localName the local node name
2401  * @property {String} namespacePrefix the namespace prefix
2402  * @property {String} namespaceURI the namespace URI
2403  * @property {String} nodeValue the node value
2404  */
2405 dojo.provide("gxe.xml.XmlNodeInfo");
2406 dojo.declare("gxe.xml.XmlNodeInfo",null,{
2407   localName: null,
2408   isDocumentTitle: false,
2409   isIsoCodeListValue: false,
2410   isIsoWrappedMultiValueList: false,
2411   namespacePrefix: null,
2412   namespaceURI: null,
2413   nodeValue: null
2414 });
2415 
2416 
2417 /* UI controls ======================================================= */
2418 
2419 
2420 /**
2421  * @class Base class for all renderable controls.
2422  * <br/><br/>
2423  * Controls are created and rendered based upon the configuration objects associated
2424  * with an editor (i.e. elements that define the editor).
2425  * <br/><br/>
2426  * Controls will only be rendered if an associated HTML tag name can be determined.
2427  * <ul>
2428  * <li>If a configuration object explicitly defines an h:tag attribute, it will be used to
2429  * create a corresponding HTML DOM element on the page.</li>
2430  * <li>If a configuration object is defined within the GXE HTML namespace, then the local name
2431  * of the configuration object will be used to create a corresponding HTML DOM element 
2432  * on the page.</li>
2433  * </ul>
2434  * 
2435  * <br/><br/>
2436  * The processing flow is as follows:
2437  * <ul>
2438  * <li>instantiate</li>
2439  * <li>...initialize</li>
2440  * <li>...build</li>
2441  * <li>...execBuild</li>
2442  * <li>......importCfgProperties</li>
2443  * <li>.........importHtmlAttributes</li>
2444  * <li>......createHtmlElement</li>
2445  * <li>......onHtmlElementCreated</li>
2446  * <li>......processChildren</li>
2447  * <li>......... (recursive on this process)</li>
2448  * <li>......onHtmlChildrenCreated</li>
2449  * <li>......parentControl.onChildControlCreated</li>
2450  * </ul>
2451  *
2452  * @name gxe.control.Control
2453  * @property {cfgObject} cfgObject the associated editor configuration object 
2454  *   (i.e. an editor definition element)
2455  * @property {gxe.Context} context the editor context
2456  * @property {gxe.html.HtmlAttributes} htmlAttributes the configured HTML attributes for the control
2457  * @property {Element} htmlElement the HTML element to which the control is attached
2458  * @property {String} htmlTag the configured tag name for the HTML element
2459  * @property {String} htmlTextContent the configured text node content for the HTML element
2460  * @property {String} gxeId a unique auto-generated ID for the control
2461  * @property {gxe.control.Control} parentControl the parent of this control
2462  * @property {gxe.xml.XmlNode} xmlNode the targeted XML node.
2463  *   This is section based, many component controls target the same node. 
2464  *   (e.g. a header div, a label, an input text box can all target the same XML node)
2465  * @property {gxe.xml.XmlElement} xmlParentElement the parent element of the targeted XML node
2466  */
2467 dojo.provide("gxe.control.Control");
2468 dojo.declare("gxe.control.Control",null,{
2469   
2470   cfgObject: null,
2471   context: null,
2472   htmlAttributes: null,
2473   htmlClass: null,
2474   htmlElement: null,
2475   htmlTag: null,
2476   htmlTextContent: null,
2477   gxeId: null,
2478   parentControl: null,
2479   xmlNode: null,
2480   xmlParentElement: null,
2481 
2482   /**
2483    * Initializes the instance.
2484    * @function 
2485    * @name initialize
2486    * @memberOf gxe.control.Control#
2487    * @param {gxe.Context} context the editor context
2488    * @param {cfgObject} cfgObject the associated editor configuration object 
2489    *   (i.e. an editor definition element)
2490    */
2491   initialize: function(context,cfgObject) {
2492     this.context = context;
2493     this.cfgObject = cfgObject;
2494     this.gxeId = this.context.generateUniqueId();
2495     this.htmlAttributes = new gxe.html.HtmlAttributes();
2496     this.htmlAttributes.set("id",this.gxeId);
2497     if (this.cfgObject != null) {
2498       var targetName = gxe.cfg.getGxeAttributeValue(this.cfgObject,"targetName");
2499       if (targetName != null) {
2500         this.htmlAttributes.set("gxeTargetName",targetName);
2501       } 
2502     }
2503   },
2504 
2505   /**
2506    * Builds the user interface control.
2507    * @function 
2508    * @name build
2509    * @memberOf gxe.control.Control#
2510    * @param {Element} htmlParentElement the parent HTML element (a new 
2511    *   control will be appended to this parent)
2512    * @param {gxe.xml.DomProcessor} domProcessor an XML processor 
2513    *   (when opening an existing document)
2514    * @param {Node} domNode the DOM node that is actively being processed 
2515    *   (when opening an existing document)
2516    */
2517   build: function(htmlParentElement,domProcessor,domNode) {
2518     this.execBuild(htmlParentElement,domProcessor,domNode);
2519   },
2520 
2521   /**
2522    * Executes the build of the the user interface control.
2523    * @function 
2524    * @name execBuild
2525    * @memberOf gxe.control.Control#
2526    * @param {Element} htmlParentElement the parent HTML element (a new 
2527    *   control will be appended to this parent)
2528    * @param {gxe.xml.DomProcessor} domProcessor an XML processor 
2529    *   (when opening an existing document)
2530    * @param {Node} domNode the DOM node that is actively being processed 
2531    *   (when opening an existing document)
2532    */
2533   execBuild: function(htmlParentElement,domProcessor,domNode) {
2534     this.importCfgProperties(this.cfgObject);
2535 
2536     var bRendered = true;
2537     var oRendered = this.evaluateValue(gxe.cfg.getGxeAttributeValue(this.cfgObject,"rendered"));
2538     if (oRendered != null) {
2539       if (typeof(oRendered) == "boolean") {
2540         bRendered = (oRendered == true);
2541       } else if (typeof(oRendered) == "string") {
2542         if (oRendered == "$editor.isExpertMode") {
2543           bRendered = false;
2544         } else {
2545           bRendered = (oRendered == "true");
2546         }
2547       }
2548     }
2549     if (!bRendered) return;
2550     
2551     this.createHtmlElement();
2552     if (this.htmlElement != null) {
2553       this.htmlElement.gxeControl = this;
2554       if (htmlParentElement != null) htmlParentElement.appendChild(this.htmlElement);
2555 
2556 
2557       gxe.cfg.forEachHtmlAttribute(this.cfgObject,dojo.hitch(this,function(cfgAttribute) {
2558         var name = cfgAttribute.name;
2559         var value = cfgAttribute.value;
2560         if ((name != null) && (value != null) && (typeof(value) == "string")) {
2561           if (value.indexOf("$fire.") == 0) {
2562             var htmlEventName = name;
2563             var gxeEventName = dojo.trim(value.substring(6));
2564             this.htmlElement.setAttribute("gxeEventName",gxeEventName);
2565             dojo.connect(this.htmlElement,htmlEventName,this,dojo.hitch(this,function(e) {
2566               this.onEvent(e,this,htmlEventName,gxeEventName);               
2567             }));
2568           }
2569         }
2570       }));
2571 
2572       this.onHtmlElementCreated(domProcessor,domNode);
2573       this.processChildren(this.cfgObject,this.htmlElement,this.xmlNode,domProcessor,domNode);
2574       this.onHtmlChildrenCreated(domProcessor,domNode);
2575       if (this.parentControl != null) {
2576         this.parentControl.onChildControlCreated(this,domProcessor,domNode);
2577       }
2578     }
2579     
2580   },
2581 
2582   /**
2583    * Creates the HTML element associated with the control.
2584    * @function 
2585    * @name createHtmlElement
2586    * @memberOf gxe.control.Control#
2587    * @returns {Element} the HTML element (can be null)
2588    */
2589   createHtmlElement: function() {
2590     var el = null;
2591     if (this.htmlTag != null) {
2592       var el = document.createElement(this.htmlTag);
2593       if (this.htmlClass != null) el.className = this.htmlClass;
2594       if (this.htmlAttributes != null) this.htmlAttributes.apply(el);
2595       if (this.htmlTextContent != null) {
2596         var text = dojo.trim(this.htmlTextContent);
2597         if (text.length > 0) {
2598           el.appendChild(document.createTextNode(text));
2599         }
2600       }
2601     }
2602     this.htmlElement = el;
2603   },
2604 
2605   /**
2606    * Ensures the visibility of the HTML element associated with the control.
2607    * <br/>By default, no action is taken and the ensureVisibility method is triggered for the 
2608    * parent control.
2609    * @function 
2610    * @name ensureVisibility
2611    * @memberOf gxe.control.Control#
2612    * @param {gxe.xml.XmlNode} subjectXmlNode the subject XML node (can be null)
2613    */
2614   ensureVisibility: function(subjectXmlNode) {
2615     if (this.parentControl != null) {
2616       this.parentControl.ensureVisibility(subjectXmlNode);
2617     }
2618   },
2619 
2620   /**
2621    * Evaluates a configuration value.
2622    * @function 
2623    * @name evaluateValue
2624    * @memberOf gxe.control.Control#
2625    * @param {Object} value the value
2626    * @returns {Object} the evaluated value
2627    */
2628   evaluateValue: function(value) {
2629     if ((value != null) && (typeof(value) == "string")) {
2630       if (value.indexOf("$eval.") == 0) {
2631         var sEval = value.substring(6);
2632         value = eval(sEval);
2633       }
2634     }
2635     return value;
2636   },
2637 
2638   /**
2639    * Finds the first child control that matches a Dojo query expression.
2640    * @function 
2641    * @name findFirstChildControl
2642    * @memberOf gxe.control.Control#
2643    * @param {String} sDojoSelector the Dojo query expression
2644    * @param {gxe.control.Control} the first matching child control (null if none)
2645    */
2646   findFirstChildControl: function(sDojoSelector) {
2647     var ctlFirst = null;
2648     if ((this.htmlElement != null) && (typeof(sDojoSelector) != "undefined") && (sDojoSelector != null)) {
2649       dojo.query(sDojoSelector,this.htmlElement).forEach(dojo.hitch(this,function(item) {
2650         if (ctlFirst == null) {
2651           var ctl = item.gxeControl;
2652           if ((typeof(ctl) != "undefined") && (ctl != null)) {
2653             ctlFirst = ctl;
2654           }
2655         }
2656       }));
2657     }
2658     return ctlFirst;
2659   },
2660   
2661   /**
2662    * Explicitly fires the onEvent method.
2663    * @event 
2664    * @name fireOnEvent
2665    * @memberOf gxe.control.Control#
2666    * @param {Event} e the underlying browser event
2667    * @deprecated 
2668    */
2669   fireOnEvent: function(e) {
2670     this.onEvent(e,this,null,null);
2671   },
2672 
2673   /**
2674    * Ensures the visibility of and focuses the HTML element associated with the control.
2675    * @function 
2676    * @name focus
2677    * @memberOf gxe.control.Control#
2678    * @param {gxe.xml.XmlNode} subjectXmlNode the subject XML node (can be null)
2679    */
2680   focus: function(subjectXmlNode) {
2681     if (this.htmlElement != null) {
2682       this.ensureVisibility(subjectXmlNode);
2683       this.htmlElement.focus();
2684     }
2685   },
2686 
2687   /**
2688    * Gets the label text associated with a control.
2689    * @function 
2690    * @name getLabelText
2691    * @memberOf gxe.control.Control#
2692    * @return the label text (can be null)
2693    */
2694   getLabelText: function() {
2695     var sLabel = gxe.cfg.getLabelText(this.cfgObject);
2696     if (sLabel == null) sLabel = this.xmlNode.getLabelText();
2697     return sLabel;
2698   },
2699   
2700   /**
2701    * Imports configuration properties into the current control.
2702    * @function 
2703    * @name importCfgProperties
2704    * @memberOf gxe.control.Control#
2705    * @param {cfgObject} cfgObject the associated configuration object
2706    */
2707   importCfgProperties: function(cfgObject) {
2708     if ((this.htmlAttributes != null) && (cfgObject != null)) {
2709       var value = this.evaluateValue(cfgObject.value);
2710       var sHtmlTag = gxe.cfg.getGxeHtmlAttributeValue(cfgObject,"tag");
2711       if (sHtmlTag != null) {
2712         if (sHtmlTag.length > 0) this.htmlTag = sHtmlTag;
2713       } else {
2714         if (cfgObject.namespace == gxe.cfg.uriGxeHtml) {
2715           this.htmlTag = cfgObject.name;
2716         } 
2717       }
2718       this.htmlTextContent = value;
2719       this.importHtmlAttributes(cfgObject);
2720       gxe.cfg.forEachChild(cfgObject,gxe.cfg.uriGxe,"code",dojo.hitch(this,function(cfgCode) {
2721         if (cfgCode.value != null) eval(cfgCode.value);
2722       }));
2723     }
2724   },
2725 
2726   /**
2727    * Imports configuration attributes within the GXE HTML namespace into the current control.
2728    * <br/>(i.e. namespace "http://www.esri.com/geoportal/gxe/html")
2729    * @function 
2730    * @name importHtmlAttributes
2731    * @memberOf gxe.control.Control#
2732    * @param {cfgObject} cfgObject the associated configuration object
2733    */
2734   importHtmlAttributes: function(cfgObject) {
2735     if (cfgObject != null) {
2736       this.htmlAttributes.set("gxename",cfgObject.name);
2737       gxe.cfg.forEachHtmlAttribute(cfgObject,dojo.hitch(this,function(cfgAttribute) {
2738         var sName = cfgAttribute.name.toLowerCase();
2739         var value = this.evaluateValue(cfgAttribute.value);
2740         if (sName!= "id") this.htmlAttributes.set(sName,value);
2741       }));
2742     }
2743   },
2744 
2745   /**
2746    * Fired on a parent control when a child has been created.
2747    * <br/>This event fires after the child has been fully processed.
2748    * @event 
2749    * @name onChildControlCreated
2750    * @memberOf gxe.control.Control#
2751    * @param {gxe.control.Control} control the child control
2752    * @param {gxe.xml.DomProcessor} domProcessor an XML processor 
2753    *   (when opening an existing document)
2754    * @param {Node} domNode the DOM node that is actively being processed 
2755    *   (when opening an existing document)
2756    */
2757   onChildControlCreated: function(control,domProcessor,domNode) {},
2758   
2759   /**
2760    * Fired for certain events triggered by the control.
2761    * @event 
2762    * @name onEvent
2763    * @memberOf gxe.control.Control#
2764    * @param {Event} e the underlying browser event
2765    * @param {gxe.control.Control} gxeControl the control that fired the event
2766    * @param {String} htmlEventName the HTML event name (e.g. "onclick")
2767    * @param {String} gxeEventName a GXE name for the event (e.g. "onLabelClicked")
2768    */
2769   onEvent: function(e,gxeControl,htmlEventName,gxeEventName) {},
2770 
2771   /**
2772    * Fired when all of the children for a control have been created.
2773    * <br/>This event fires after all child controls have been added to the HTML DOM.
2774    * @event 
2775    * @name onHtmlChildrenCreated
2776    * @memberOf gxe.control.Control#
2777    * @param {gxe.xml.DomProcessor} domProcessor an XML processor 
2778    *   (when opening an existing document)
2779    * @param {Node} domNode the DOM node that is actively being processed 
2780    *   (when opening an existing document)
2781    */
2782   onHtmlChildrenCreated: function(domProcessor,domNode) {},
2783 
2784   /**
2785    * Fired when the HTML element for a control has been created.
2786    * <br/>This event fires prior to the creation of child controls.
2787    * @event 
2788    * @name onHtmlElementCreated
2789    * @memberOf gxe.control.Control#
2790    * @param {gxe.xml.DomProcessor} domProcessor an XML processor 
2791    *   (when opening an existing document)
2792    * @param {Node} domNode the DOM node that is actively being processed 
2793    *   (when opening an existing document)
2794    */
2795   onHtmlElementCreated: function(domProcessor,domNode) {},
2796 
2797   /**
2798    * Processes a configuration object associated with a targeted XML attribute (g:attribute).
2799    * @function 
2800    * @name processCfgAttribute
2801    * @memberOf gxe.control.Control#
2802    * @param {cfgObject} cfgAttribute the configuration object to process
2803    * @param {Element} htmlParentElement the parent HTML element
2804    *   (new controls will be appended to this parent)
2805    * @param {gxe.xml.XmlElement} xmlParentElement the targeted XML parent element
2806    * @param {gxe.xml.DomProcessor} domProcessor an XML processor 
2807    *   (when opening an existing document)
2808    * @param {Node} domNode the DOM node that is actively being processed 
2809    *   (when opening an existing document)
2810    */
2811   processCfgAttribute: function(cfgAttribute,htmlParentElement,xmlParentElement,domProcessor,domNode) {
2812     var sTargetName = gxe.cfg.getGxeAttributeValue(cfgAttribute,"targetName");
2813     var xmlDocument = xmlParentElement.parentDocument;
2814     var xmlAttribute = new gxe.xml.XmlAttribute(xmlDocument,xmlParentElement,cfgAttribute);
2815     xmlParentElement.attributes.add(xmlAttribute);
2816     var domMatch = null;
2817     var sDefault = gxe.cfg.getGxeAttributeValue(this.cfgObject,"fixedValue");
2818     if (sDefault == null) sDefault = gxe.cfg.getGxeAttributeValue(cfgAttribute,"value");
2819     if ((domProcessor != null) && (domNode != null)) {
2820       domMatch = domProcessor.findMatchingChildAttribute(domNode,cfgAttribute);
2821     } else {
2822       if (sDefault != null) xmlAttribute.nodeInfo.nodeValue = sDefault;
2823     }
2824 
2825     var ctl = this.context.makeXhtmlControl(cfgAttribute,this,true);
2826     ctl.xmlNode = xmlAttribute;
2827     ctl.xmlParentElement = xmlParentElement;
2828     ctl.build(htmlParentElement,domProcessor,domMatch);
2829     
2830     if ((domMatch != null) && (xmlAttribute.getInputControl() == null)) {
2831       // can this put the document in an undesirable state?
2832       if (sDefault != null) {
2833         xmlAttribute.nodeInfo.nodeValue = sDefault;
2834       } else {
2835         var sNodeValue = domMatch.nodeValue;
2836         xmlAttribute.nodeInfo.nodeValue = sNodeValue;
2837       }
2838     }
2839   },
2840 
2841   /**
2842    * Processes a configuration object associated with a targeted XML element (g:element).
2843    * @function 
2844    * @name processCfgElement
2845    * @memberOf gxe.control.Control#
2846    * @param {cfgObject} cfgElement the configuration object to process
2847    * @param {Element} htmlParentElement the parent HTML element
2848    *   (new controls will be appended to this parent)
2849    * @param {gxe.xml.XmlElement} xmlParentElement the targeted XML parent element
2850    * @param {gxe.xml.DomProcessor} domProcessor an XML processor 
2851    *   (when opening an existing document)
2852    * @param {Node} domNode the DOM node that is actively being processed 
2853    *   (when opening an existing document)
2854    */
2855   processCfgElement: function(cfgElement,htmlParentElement,xmlParentElement,domProcessor,domNode) {
2856     var sTargetName = gxe.cfg.getGxeAttributeValue(cfgElement,"targetName");
2857     var xmlDocument = xmlParentElement.parentDocument;
2858     var xmlElement = new gxe.xml.XmlElement(xmlDocument,xmlParentElement,cfgElement);
2859     xmlParentElement.children.add(xmlElement);
2860     var bRepeatable = xmlElement.isRepeatable();
2861     
2862     var domMatch = null;
2863     var domMatches = null;
2864     if ((domProcessor != null) && (domNode != null)) {
2865       
2866       var cfgMatchTop = gxe.cfg.findGxeChild(cfgElement,"matchTopElements");
2867       
2868       if (cfgMatchTop == null) {
2869         if (!bRepeatable) {
2870           domMatch = domProcessor.findMatchingChildElement(domNode,cfgElement);
2871           if (domMatch != null) {
2872             domMatches = new Array();
2873             domMatches.push(domMatch);
2874           } 
2875         } else {
2876           domMatches = domProcessor.findMatchingChildElements(domNode,cfgElement);
2877           if ((domMatches != null) && (domMatches.length > 0)) domMatch = domMatches[0];
2878         }
2879         
2880       } else {
2881         domMatches = domProcessor.findMatchingChildElements(domNode,cfgElement);
2882         if ((domMatches != null) && (domMatches.length > 0)) {
2883           var ns = xmlDocument.namespaces;
2884           var topMatches = new Array();
2885           for (var i=0; i<domMatches.length; i++) {
2886             var nd = domMatches[i];
2887             var bMatched = true;
2888             var nConditions = 0;
2889             var nConditionsMatched = 0;
2890             gxe.cfg.forEachChild(cfgMatchTop,gxe.cfg.uriGxe,"match",
2891               dojo.hitch(this,function(cfgChild) {
2892                 nConditions++;
2893                 var qPath = gxe.cfg.getGxeAttributeValue(cfgChild,"qPath");
2894                 var qValue = gxe.cfg.getGxeAttributeValue(cfgChild,"qValue");
2895                 var qMode = gxe.cfg.getGxeAttributeValue(cfgChild,"qMode");
2896                 var bMust = (qMode != "mustNot");
2897                 if (qPath != null) {
2898                   var b = domProcessor.matchTopElement(ns,nd,qPath,qValue,bMust);
2899                   if (b) nConditionsMatched++;
2900                 }
2901               }
2902             ));
2903             if (nConditions == nConditionsMatched) topMatches.push(nd);
2904           }
2905           domMatches = topMatches;
2906           if (topMatches.length > 0) domMatch = topMatches[0];
2907         }
2908       }
2909     }  
2910 
2911     var ctl = this.context.makeXhtmlControl(cfgElement,this,true);
2912     ctl.xmlNode = xmlElement;
2913     ctl.xmlParentElement = xmlParentElement;
2914     ctl.build(htmlParentElement,domProcessor,domMatch);
2915     
2916     if (bRepeatable && (domMatches != null) && (domMatches.length > 1)) {
2917       var ctlRepeatables = xmlElement.repeatablesContainer;
2918       if (ctlRepeatables != null) {
2919         var bRepeat = true;
2920         var ctlInput = xmlElement.getInputControl();
2921         if ((ctlInput != null) && ctlInput.getSupportsMultipleValues()) bRepeat = false;
2922         else if (xmlElement.wrapsIsoMultiValueList()) bRepeat = false;
2923         if (bRepeat) {
2924           for (var i=1; i<domMatches.length; i++ ) {
2925             ctlRepeatables.repeatSection(domProcessor,domMatches[i]);
2926           }
2927         }
2928       }
2929     }
2930     
2931   },
2932 
2933   /**
2934    * Processes the children of a configuration object.
2935    * @function 
2936    * @name processChildren
2937    * @memberOf gxe.control.Control#
2938    * @param {cfgObject} cfgObject the configuration object to process
2939    * @param {Element} htmlParentElement the parent HTML element
2940    *   (new controls will be appended to this parent)
2941    * @param {gxe.xml.XmlNode} xmlNode the target XML node
2942    * @param {gxe.xml.DomProcessor} domProcessor an XML processor 
2943    *   (when opening an existing document)
2944    * @param {Node} domNode the DOM node that is actively being processed 
2945    *   (when opening an existing document)
2946    */
2947   processChildren: function(cfgObject,htmlParentElement,xmlNode,domProcessor,domNode) {
2948     gxe.cfg.forEachChild(cfgObject,"*","*",dojo.hitch(this,function(cfgChild) {
2949       if (cfgChild.namespace == gxe.cfg.uriGxe) {
2950         
2951         if (cfgChild.name == "attribute") {
2952           this.processCfgAttribute(cfgChild,htmlParentElement,xmlNode,domProcessor,domNode);          
2953 
2954         } else if ((cfgChild.name == "body") || 
2955                    (cfgChild.name == "elementText") || 
2956                    (cfgChild.name == "header")) {
2957           var ctl = this.context.makeXhtmlControl(cfgChild,this,true);
2958           ctl.xmlNode = xmlNode;
2959           ctl.xmlParentElement = xmlNode.parentElement;
2960           ctl.build(htmlParentElement,domProcessor,domNode);
2961           
2962         } else if (cfgChild.name == "element") {
2963           this.processCfgElement(cfgChild,htmlParentElement,xmlNode,domProcessor,domNode);
2964         } else {
2965 
2966           var sHtmlTag = gxe.cfg.getGxeHtmlAttributeValue(cfgChild,"tag");
2967           if (sHtmlTag != null) {
2968             var ctl = this.context.makeXhtmlControl(cfgChild,this,true);
2969             ctl.xmlNode = xmlNode;
2970             ctl.xmlParentElement = xmlNode.parentElement;
2971             ctl.build(htmlParentElement,domProcessor,domNode);
2972           }
2973         }
2974 
2975       } else if (cfgChild.namespace == gxe.cfg.uriGxeHtml) {
2976         var ctl = this.context.makeXhtmlControl(cfgChild,this,true);
2977         ctl.xmlNode = xmlNode;
2978         ctl.xmlParentElement = xmlNode.parentElement;
2979         ctl.build(htmlParentElement,domProcessor,domNode);
2980       }
2981     }));
2982   }
2983 
2984 });
2985 
2986 /**
2987  * @class An array backed collection of GXE controls.
2988  * @name gxe.control.ControlArray
2989  * @extends gxe.control.Control
2990  * @property {boolean} displayInline indicates if the control should be displayed inline 
2991  *   (default=false, i.e. block display)
2992  * @property {boolean} isExclusive indicates if the control is using exclusive display 
2993  *   (default=false)
2994  */
2995 dojo.provide("gxe.control.ControlArray");
2996 dojo.declare("gxe.control.ControlArray",gxe.control.Control,{
2997   _activeIndex: -1,
2998   _array: null,
2999   displayInline: false,
3000   isExclusive: false,
3001 
3002   /** Override gxe.control.Control.initialize() */
3003   initialize: function(context,cfgObject) {
3004     this._array = new Array();
3005     this.inherited(arguments);
3006     var s = gxe.cfg.getGxeAttributeValue(this.cfgObject,"displayInline");
3007     this.displayInline = (s == "true");
3008   },
3009 
3010   /**
3011    * Activates the control at a given index.
3012    * @function
3013    * @name activateIndex
3014    * @memberOf gxe.control.ControlArray#
3015    * @param {Integer} nIndex the index of the control to activate
3016    */
3017   activateIndex: function(nIndex) {
3018     this._activeIndex = nIndex;
3019     this.syncDisplay(true);
3020   },
3021 
3022   /**
3023    * Finds the index associated with a control.
3024    * @function
3025    * @name findIndex
3026    * @memberOf gxe.control.ControlArray#
3027    * @param {gxe.control.Control} control the subject control
3028    * @returns {Integer} the associated index (-1 if not located)
3029    */
3030   findIndex: function(control) {
3031     var n = this._array.length;;
3032     for (var i=0; i<n; i++) {
3033       if (this.getItem(i) == control) return i;
3034     }
3035     return -1;
3036   },
3037 
3038   /**
3039    * Finds the index associated with a target XML node.
3040    * @function
3041    * @name findIndexByXmlNode
3042    * @memberOf gxe.control.ControlArray#
3043    * @param {gxe.xml.XmlNode} subjectXmlNode the subject XML node (element or attribute)
3044    * @returns {Integer} the associated index (-1 if not located)
3045    */
3046   findIndexByXmlNode: function(subjectXmlNode) {
3047     if (subjectXmlNode != null) {
3048       var inputControl = subjectXmlNode.getInputControl();
3049       var n = this._array.length;;
3050       for (var i=0; i<n; i++) {
3051         var ctl = this.getItem(i);
3052         if (ctl.xmlNode == subjectXmlNode) return i;
3053         if ((inputControl != null) && (ctl.htmlElement != null)) {
3054           var s = "[id='"+inputControl.gxeId+"']";
3055           var bLocated = false;
3056           dojo.query(s,ctl.htmlElement).forEach(dojo.hitch(this,function(item) {
3057             var ctl2 = item.gxeControl;
3058             if ((typeof(ctl2) != "undefined") && (ctl2 != null)) {
3059               if (ctl2 == inputControl) bLocated = true;
3060             }
3061           }));
3062           if (bLocated) return i;
3063         }
3064       }
3065       return -1;
3066     }
3067   },
3068 
3069   /**
3070    * Fires the onArrayModified event.
3071    * @function
3072    * @name fireOnArrayModified
3073    * @memberOf gxe.control.ControlArray#
3074    */
3075   fireOnArrayModified: function() {
3076     this.onArrayModified(this.getLength(),this._activeIndex);
3077   },
3078 
3079   /**
3080    * Gets the active index.
3081    * @function
3082    * @name getActiveIndex
3083    * @memberOf gxe.control.ControlArray#
3084    * @returns {Integer}the active index
3085    */
3086   getActiveIndex: function() {
3087     return this._activeIndex;
3088   },
3089   
3090   /**
3091    * Gets the item at the specified index.
3092    * @function 
3093    * @name getItem
3094    * @memberOf gxe.control.ControlArray#
3095    * @param {Integer} nIndex the index
3096    * @returns {gxe.control.Control} the corresponding control
3097    */
3098   getItem: function(nIndex) {
3099     return this._array[nIndex];
3100   },
3101 
3102   /**
3103    * Gets the length of the array.
3104    * @function 
3105    * @name getLength
3106    * @memberOf gxe.control.ControlArray#
3107    * @returns {Integer} the length
3108    */
3109   getLength: function() {
3110     return this._array.length;
3111   },
3112 
3113   /**
3114    * Inserts a control at a specified index.
3115    * @function 
3116    * @name insertAt
3117    * @memberOf gxe.control.ControlArray#
3118    * @param {Integer} nIndex the index (same as JavaScript Array.splice)
3119    * @param {gxe.control.Control} control the control to insert
3120    * @param {boolean} bActivate (not currently used)
3121    */
3122   insertAt: function(nIndex,control,bActivate) {
3123     var elRef = null;
3124     var nIdx = -1;
3125     var childNodes = this.htmlElement.childNodes;
3126     for (var i=0; i<childNodes.length; i++) {
3127       if (childNodes[i].nodeType == 1) {
3128         nIdx++;
3129         if (nIdx == nIndex) {
3130           elRef = childNodes[i];
3131           break;
3132         }
3133       }
3134     }
3135     this._array.splice(nIndex,0,control);
3136     this.htmlElement.insertBefore(control.htmlElement,elRef);
3137     this.fireOnArrayModified();
3138   },
3139 
3140   /**
3141    * Fired when the array structure has been modified.
3142    * @event
3143    * @name onArrayModified
3144    * @memberOf gxe.control.ControlArray#
3145    * @param {Integer} count the number of controls in the array
3146    * @param {Integer} activeIndex the index of the active control
3147    */
3148   onArrayModified: function(count,activeIndex) {},
3149 
3150   /**
3151    * Appends a control to the array.
3152    * @function 
3153    * @name push
3154    * @memberOf gxe.control.ControlArray#
3155    * @param {gxe.control.Control} control the control to add
3156    * @param {boolean} bActivate if true then activate the newly added control
3157    */
3158   push: function(control,bActivate) {
3159     this._array.push(control);
3160     if (!this.isExclusive) {
3161       this.htmlElement.appendChild(control.htmlElement);
3162     }
3163     if (bActivate) {
3164       this._activeIndex = this.getLength() - 1;
3165       this.syncDisplay(true);
3166     }
3167     this.fireOnArrayModified();
3168   },
3169 
3170   /**
3171    * Removes the control at the specified index from the array.
3172    * @function 
3173    * @name removeIndex
3174    * @memberOf gxe.control.ControlArray#
3175    * @param {Integer} nIndex the index of the control to remove
3176    * @param {boolean} bActivate if true then activate an appropriate index following removal
3177    */
3178   removeIndex: function(nIndex,bActivate) {
3179     var el = this.getItem(nIndex).htmlElement;
3180     el.parentNode.removeChild(el);
3181     this._array.splice(nIndex,1);
3182 
3183     // which one is active following delete?
3184     if (bActivate) {
3185       if (this.getLength() == 1) {
3186         this._activeIndex = 0;
3187         this.syncDisplay(true);
3188       } else if (this.getLength() >= nIndex) {
3189         this._activeIndex = nIndex;
3190         if (this.getLength() == nIndex) this._activeIndex--;
3191         this.syncDisplay(true);
3192       } else {
3193         this._activeIndex = -1;
3194         this.syncDisplay(false);
3195       }
3196     }
3197     this.fireOnArrayModified();
3198   },
3199   
3200   /**
3201    * Explicitly sets the display style for the control array.
3202    * @function 
3203    * @name setDisplayStyle
3204    * @memberOf gxe.control.ControlArray#
3205    * @param {boolean} bShow if true then show the control
3206    */
3207   setDisplayStyle: function(bShow) {
3208     var el = this.htmlElement;
3209     if (bShow) {
3210       if (this.displayInline) el.style.display = "inline";
3211       else el.style.display = "block";
3212     } else {
3213       el.style.display = "none";
3214     }
3215   },
3216 
3217   /**
3218    * Swaps the positions of two controls within the array.
3219    * @function 
3220    * @name swapPosition
3221    * @memberOf gxe.control.ControlArray#
3222    * @param {Integer} nFromIndex the from index
3223    * @param {Integer} nToIndex the to index
3224    * @param {boolean} bActivate if true then activate the "to" index following the swap
3225    */
3226   swapPosition: function(nFromIndex,nToIndex,bActivate) {
3227 
3228     if (nFromIndex == nToIndex) return;
3229     var a = this._array[nFromIndex];
3230     var b = this._array[nToIndex];
3231 
3232     if (this.isExclusive) {
3233       this._array[nFromIndex] = b;
3234       this._array[nToIndex] = a;
3235       if (bActivate) {
3236         this._activeIndex = nToIndex;
3237         this.syncDisplay(true);
3238       }
3239       
3240     } else {
3241       var bUp = (nFromIndex > nToIndex);
3242       var elA = a.htmlElement;
3243       var elB = b.htmlElement;
3244       var nRefIndex = nFromIndex;
3245       if (bUp) {
3246         elA = b.htmlElement;
3247         elB = a.htmlElement;
3248         nRefIndex = nToIndex;
3249       }
3250       this._array[nFromIndex] = b;
3251       this._array[nToIndex] = a;
3252   
3253       var elARem = elA.parentNode.removeChild(elA);
3254       elB.parentNode.insertBefore(elARem,elB);
3255       var elRef = null;
3256       var nIdx = -1;
3257       var childNodes = elB.parentNode.childNodes;
3258       for (var i=0; i<childNodes.length; i++) {
3259         if (childNodes[i].nodeType == 1) {
3260           nIdx++;
3261           if (nIdx == nRefIndex) {
3262             elRef = childNodes[i];
3263             break;
3264           }
3265         }
3266       }
3267       var elBRem = elB.parentNode.removeChild(elB);
3268       elARem.parentNode.insertBefore(elBRem,elRef);
3269     }
3270     
3271     this.fireOnArrayModified();
3272   },
3273 
3274   /**
3275    * Ensures that the active control for an exclusively displayed array is properly displayed.
3276    * @function 
3277    * @name syncDisplay
3278    * @memberOf gxe.control.ControlArray#
3279    * @param {boolean} bForce if true then ensure that the control array itself is properly displayed
3280    */
3281   syncDisplay: function(bForce) {
3282     if (this.isExclusive) {
3283       var n = this._array.length;
3284       for (var i=0; i<n; i++) {
3285         var el = this._array[i].htmlElement;
3286         if (i == this._activeIndex) {
3287           if (this.htmlElement.childNodes.length == 0) {
3288             this.htmlElement.appendChild(el);
3289           } else {
3290             this.htmlElement.replaceChild(el,this.htmlElement.childNodes[0]);
3291           }
3292           break;
3293         }
3294       }
3295       if (this._activeIndex == -1) {
3296         if (this.htmlElement.childNodes.length == 1) {
3297           this.htmlElement.removeChild(this.htmlElement.childNodes[0]);
3298         }
3299       }
3300       if (bForce) {
3301         if (this.displayInline) this.htmlElement.style.display = "inline";
3302         else this.htmlElement.style.display = "block";
3303       }
3304     }
3305   },
3306 
3307   /**
3308    * Toggles the display style of the control array.
3309    * @function 
3310    * @name toggleDisplay
3311    * @memberOf gxe.control.ControlArray#
3312    */
3313   toggleDisplay: function() {
3314     var el = this.htmlElement;
3315     if (el.style.display == "none") {
3316       if (this.displayInline) el.style.display = "inline";
3317       else el.style.display = "block";
3318     } else el.style.display = "none";
3319   }
3320 
3321 });
3322 
3323 /**
3324  * @class A control array using exclusive display.
3325  * @name gxe.control.ExclusiveControlArray
3326  * @extends gxe.control.ControlArray
3327  * @property {boolean} isExclusive true
3328  */
3329 dojo.provide("gxe.control.ExclusiveControlArray");
3330 dojo.declare("gxe.control.ExclusiveControlArray",gxe.control.ControlArray,{
3331   isExclusive: true,
3332 
3333   /** Override gxe.control.Control.onChildControlCreated() */
3334   onChildControlCreated: function(control,domProcessor,domNode) {
3335     this.push(control,false);
3336     this.syncDisplay(true);
3337   }
3338 });
3339 
3340 /**
3341  * @class A control array using non-exclusive display.
3342  * @name gxe.control.NonExclusiveControlArray
3343  * @extends gxe.control.ControlArray
3344  * @property {boolean} isExclusive false
3345  */
3346 dojo.provide("gxe.control.NonExclusiveControlArray");
3347 dojo.declare("gxe.control.NonExclusiveControlArray",gxe.control.ControlArray,{
3348   isExclusive: false,
3349 
3350   /** Override gxe.control.Control.onChildControlCreated() */
3351   onChildControlCreated: function(control,domProcessor,domNode) {
3352     this.push(control,false);
3353     this.syncDisplay(true);
3354   }
3355 });
3356 
3357 /**
3358  * @class Provides a container for repeatable element controls (multiplicity > 1).
3359  * @name gxe.control.RepeatablesContainer
3360  * @extends gxe.control.ControlArray
3361  */
3362 dojo.provide("gxe.control.RepeatablesContainer");
3363 dojo.declare("gxe.control.RepeatablesContainer",gxe.control.ControlArray,{
3364   cfgSectionBody: null,
3365   isExclusive: false,
3366   multiplicityTools: null,
3367   xmlElementCfgObject: null,
3368   xmlParentElement: null,
3369   xmlPlaceHolder: null,
3370 
3371   /**
3372    * Determines if elements can be removed.
3373    * @function 
3374    * @name canRemove
3375    * @memberOf gxe.control.RepeatablesContainer#
3376    * @returns {boolean} true if elements can be removed
3377    */
3378   canRemove: function() {
3379     var bCanRemove = false;
3380     if (this.isConfigured()) {
3381       var nSections = 0;
3382       var nSimiliar = this.countSimilarSections();
3383       if (nSimiliar > 0) {
3384         var sMinOccurs = gxe.cfg.getMinOccurs(this.xmlElementCfgObject);
3385         var nMinOccurs = parseInt(sMinOccurs);
3386         if (isNaN(nMinOccurs)) nMinOccurs = 1;
3387         if (nMinOccurs < nSimiliar) {
3388           if (nSimiliar == 1) bCanRemove = false;
3389           else bCanRemove = true;          
3390         }
3391       }
3392     }
3393     return bCanRemove;
3394   },
3395 
3396   /**
3397    * Determines if elements can be repeated.
3398    * @function 
3399    * @name canRepeat
3400    * @memberOf gxe.control.RepeatablesContainer#
3401    * @returns {boolean} true if elements can be repeated
3402    */
3403   canRepeat: function() {
3404     var bCanRepeat = false;
3405     if (this.isConfigured()) {
3406       var sMaxOccurs = gxe.cfg.getMaxOccurs(this.xmlElementCfgObject);
3407       if (sMaxOccurs == "unbounded") {  
3408         bCanRepeat = true;
3409       } else {
3410         var nSimiliar = this.countSimilarSections();
3411         var nMaxOccurs = parseInt(sMaxOccurs);
3412         if (isNaN(nMaxOccurs)) nMaxOccurs = 1;
3413         if ((nMaxOccurs > 0) && (nSimiliar < nMaxOccurs)) {
3414           bCanRepeat = true;
3415         }
3416       }
3417     }
3418     return bCanRepeat;
3419   },
3420   
3421   /**
3422    * Counts the number of similar sections.
3423    * @function 
3424    * @name countSimilarSections
3425    * @memberOf gxe.control.RepeatablesContainer#
3426    * @returns {Integer} the number of similiar sections
3427    */
3428   countSimilarSections: function() {
3429     return this.getLength();
3430   },
3431 
3432   /** Override gxe.control.Control.ensureVisibility() */
3433   ensureVisibility: function(subjectXmlNode) {
3434     if (this.isExclusive) {
3435       var nIndex = this.findIndexByXmlNode(subjectXmlNode);
3436       if ((nIndex != -1) && (nIndex != this.getActiveIndex())) {
3437         this.activateIndex(nIndex);
3438         this.fireOnArrayModified();
3439       }
3440     }
3441     this.inherited(arguments);
3442   },
3443 
3444   /**
3445    * Handles multiplicity related events .
3446    * <br/>(repeatSection removeSection moveSectionUp moveSectionDown)
3447    * @event 
3448    * @name handleEvent
3449    * @memberOf gxe.control.RepeatablesContainer#
3450    * @param {Event} e the underlying browser event
3451    * @param {gxe.control.Section} sectionControl the section from which the event was fired
3452    * @param {String} htmlEventName the HTML event name (e.g. "onclick")
3453    * @param {String} gxeEventName a GXE name for the event (e.g. "repeatSection")
3454    */
3455   handleEvent: function(e,sectionControl,htmlEventName,gxeEventName) {
3456     if (gxeEventName == "repeatSection") {
3457       if (!this.isExclusive) {
3458         this.activateIndex(this.findIndex(sectionControl));
3459         var nTargetIndex = this.getActiveIndex();
3460         var nTargetLength = this.getLength();
3461         var bOk = (nTargetIndex == (nTargetLength - 1));
3462         if (bOk) this.repeatSection(null,null);
3463       } else {
3464         this.repeatSection(null,null);
3465       }
3466     } else if (gxeEventName == "removeSection") {
3467       if (!this.isExclusive) this.activateIndex(this.findIndex(sectionControl));
3468       this.removeSection();
3469     } else if (gxeEventName == "moveSectionUp") {
3470       if (!this.isExclusive) this.activateIndex(this.findIndex(sectionControl));
3471       this.moveSection(true);
3472     } else if (gxeEventName == "moveSectionDown") {
3473       if (!this.isExclusive) this.activateIndex(this.findIndex(sectionControl));
3474       this.moveSection(false);
3475     } 
3476   },
3477 
3478   /**
3479    * Determines if the control has been properly configured.
3480    * @function 
3481    * @name isConfigured
3482    * @memberOf gxe.control.RepeatablesContainer#
3483    * @returns {boolean} true if properly configured
3484    */
3485   isConfigured: function() {
3486     return (this.xmlElementCfgObject != null) && (this.xmlParentElement != null);
3487   },
3488 
3489   /**
3490    * Moves a section.
3491    * @function 
3492    * @name moveSection
3493    * @memberOf gxe.control.RepeatablesContainer#
3494    * @param {boolean} bUp true if the section (i.e. element should be move up within the target XML document)
3495    */
3496   moveSection: function(bUp) {
3497     if (!this.isConfigured()) return;
3498     
3499     // move up/down refers to up/down within the XML document
3500     
3501     // determine the target and xml indices
3502     var nTargetIndex = this.getActiveIndex();
3503     var nTargetLength = this.getLength();
3504     if ((nTargetIndex < 0) || (nTargetLength <= 0)) return;
3505     if (bUp && (nTargetIndex == 0)) return;
3506     if (!bUp && (nTargetIndex == (nTargetLength - 1))) return;
3507     var xmlElement = this.getItem(nTargetIndex).xmlNode;
3508     var nXmlIndex = xmlElement.parentElement.children.findIndex(xmlElement);
3509     if (nXmlIndex == -1) return;
3510   
3511     // determine the reposition indices
3512     var nNewTargetIndex = nTargetIndex + 1;
3513     var nNewXmlIndex = nXmlIndex + 1;
3514     if (bUp) {
3515       nNewTargetIndex = nTargetIndex - 1;
3516       nNewXmlIndex = nXmlIndex - 1;
3517     }
3518     if (nTargetIndex == nNewTargetIndex) return;
3519     
3520     // reposition
3521     xmlElement.parentElement.children.swapPosition(nXmlIndex,nNewXmlIndex);
3522     this.swapPosition(nTargetIndex,nNewTargetIndex,true);
3523   },
3524   
3525   /** Override gxe.control.ControlArray.onArrayModified() */
3526   onArrayModified: function(control,domProcessor,domNode) {
3527     this.syncTools();
3528   },
3529 
3530   /** Override gxe.control.Control.onChildControlCreated() */
3531   onChildControlCreated: function(control,domProcessor,domNode) {
3532     var bActivate = false;
3533     if (this.isExclusive) {
3534       bActivate = (domProcessor == null) || (this.getLength() == 0);
3535     }
3536     this.push(control,bActivate);
3537     this.syncDisplay(true);
3538   },
3539 
3540   /**
3541    * Removes a section.
3542    * @function 
3543    * @name removeSection
3544    * @memberOf gxe.control.RepeatablesContainer#
3545    */
3546   removeSection: function() {
3547     if (!this.canRemove()) return;
3548     
3549     var nSections = this.getLength();
3550     var nTargetIndex = this.getActiveIndex();
3551     if (nTargetIndex < 0) return;
3552     var xmlElement = this.getItem(nTargetIndex).xmlNode;
3553     var nXmlIndex = xmlElement.parentElement.children.findIndex(xmlElement);
3554     if (nXmlIndex == -1) return;
3555 
3556     if (nSections > 1) {
3557       xmlElement.parentElement.children.removeIndex(nXmlIndex);
3558       this.removeIndex(nTargetIndex,true);
3559     } else if (nSections == 1) {
3560       if (this.isExclusive) {
3561         xmlElement.isPlaceHolder = true;
3562         this.xmlPlaceHolder = xmlElement;
3563         this.removeIndex(nTargetIndex,true);
3564       } else {
3565         var ctlLast = this.getItem(0);
3566         var ctlBodyContainer = ctlLast.sectionBodyContainer;
3567         if ((typeof(ctlBodyContainer) != "undefined") && (ctlBodyContainer != null)) {
3568           this.cfgSectionBody = ctlBodyContainer.cfgSectionBody;
3569           xmlElement.isPlaceHolder = true;
3570           this.xmlPlaceHolder = xmlElement;
3571           ctlBodyContainer.removeIndex(0,true);
3572         } 
3573       }
3574     }
3575     
3576   },
3577   
3578   /**
3579    * Repeats a section.
3580    * @function 
3581    * @name repeatSection
3582    * @memberOf gxe.control.RepeatablesContainer#
3583    * @param {gxe.xml.DomProcessor} domProcessor an XML processor 
3584    *   (when opening an existing document)
3585    * @param {Node} domNode the DOM node that is actively being processed 
3586    *   (when opening an existing document)
3587    */
3588   repeatSection: function(domProcessor,domNode) {
3589 
3590     // initialize
3591     if (!this.canRepeat()) return;
3592     var nSections = this.getLength();      
3593     var xmlParent = this.xmlParentElement;
3594     var xmlDocument = xmlParent.parentDocument;
3595     var newXmlElement = new gxe.xml.XmlElement(xmlDocument,xmlParent,this.xmlElementCfgObject);
3596     var bUseAppend = true;
3597 
3598     // determine the insertion index
3599     var nTargetIndex = -1;
3600     var nXmlIndex = -1;
3601     if (this.xmlPlaceHolder != null) {
3602       nXmlIndex = xmlParent.children.findIndex(this.xmlPlaceHolder);
3603       if (nXmlIndex != -1) nXmlIndex++; // insert after
3604     } else {
3605       if (bUseAppend) {
3606         var xmlLast = this.getItem(nSections - 1).xmlNode;
3607         nXmlIndex = xmlParent.children.findIndex(xmlLast);
3608         if (nXmlIndex != -1) nXmlIndex++; // insert after
3609       } else {
3610         nTargetIndex = this.getActiveIndex();
3611         var xmlLast = this.getItem(this.getActiveIndex()).xmlNode;
3612         nXmlIndex = xmlParent.children.findIndex(xmlLast);
3613         if (nXmlIndex != -1) nXmlIndex++; // insert after
3614       }
3615     }
3616     if (nXmlIndex == -1) return; // exception message here??
3617           
3618     // repeat
3619     var elTmp = document.createElement("div"); 
3620     
3621     if (this.xmlPlaceHolder == null) {
3622       if (this.isExclusive) {
3623         xmlParent.children.insertAt(nXmlIndex,newXmlElement);
3624         this.processChildren(this.cfgObject,elTmp,newXmlElement,domProcessor,domNode);
3625       } else {
3626         xmlParent.children.insertAt(nXmlIndex,newXmlElement);
3627         var cfgElement = this.xmlElementCfgObject;
3628         var ctl = this.context.makeXhtmlControl(cfgElement,this,true);
3629         ctl.xmlNode = newXmlElement;
3630         ctl.xmlParentElement = newXmlElement.parentElement;
3631         ctl.build(elTmp,domProcessor,domNode);        
3632       }
3633 
3634     } else {
3635       var nPlaceHolderIndex = xmlParent.children.findIndex(this.xmlPlaceHolder);
3636       if (nPlaceHolderIndex == -1) return; // exception message here??
3637       
3638       if (this.isExclusive) {
3639         xmlParent.children.insertAt(nPlaceHolderIndex,newXmlElement);
3640         this.processChildren(this.cfgObject,elTmp,newXmlElement,domProcessor,domNode);
3641         xmlParent.children.removeIndex(nPlaceHolderIndex+1);
3642         this.xmlPlaceHolder = null;
3643         
3644       } else {
3645         var ctlLast = this.getItem(0);
3646         ctlLast.xmlNode = newXmlElement;
3647         ctlLast.xmlParentElement = this.xmlNode.parentElement;
3648         var ctlBodyContainer = ctlLast.sectionBodyContainer;
3649         if ((typeof(ctlBodyContainer) != "undefined") && (ctlBodyContainer != null)) {
3650           xmlParent.children.insertAt(nPlaceHolderIndex,newXmlElement);
3651           var cfgElement = this.xmlElementCfgObject;
3652           var ctl = this.context.makeXhtmlControl(this.cfgSectionBody,ctlBodyContainer,true);
3653           ctl.xmlNode = newXmlElement;
3654           ctl.xmlParentElement = newXmlElement.parentElement;
3655           ctl.build(elTmp,domProcessor,domNode);
3656           xmlParent.children.removeIndex(nPlaceHolderIndex+1);
3657           this.xmlPlaceHolder = null;          
3658           
3659         } 
3660       }
3661     } 
3662   },
3663     
3664   /**
3665    * Synchronizes the multiplicity tools for a section.
3666    * @function 
3667    * @name syncTools
3668    * @memberOf gxe.control.RepeatablesContainer#
3669    */
3670   syncTools: function() {
3671     if (!this.isConfigured()) return;
3672     var aToolbars = new Array();
3673     if (this.isExclusive) {
3674       aToolbars.push(this.multiplicityTools)
3675     } else {
3676       for (var i=0; i<this.getLength(); i++) aToolbars.push(this.getItem(i).multiplicityTools);
3677     }
3678     if (aToolbars.length > 0) {
3679       var _toggle = function(el,bEnabled) {
3680         if (bEnabled) {
3681           dojo.removeClass(el,"gxeDisabled");
3682           el.disabled = false;
3683         } else {
3684           dojo.addClass(el,"gxeDisabled");
3685           el.disabled = true;
3686         }
3687       };
3688     
3689       var nTargetIndex = this.getActiveIndex();
3690       var nTargetLength = this.getLength();
3691       for (var i=0; i<aToolbars.length; i++) {
3692         var ctlTools = aToolbars[i];
3693         if ((typeof(ctlTools) != "undefined") &&  (ctlTools != null)) {
3694           if (!this.isExclusive) nTargetIndex = i;
3695           dojo.query("[gxeToolName='repeatSection']",ctlTools.htmlElement).forEach(dojo.hitch(this,function(item) {
3696             var b = this.canRepeat();
3697             if (b && !this.isExclusive) b = (nTargetIndex == (nTargetLength - 1));
3698             _toggle(item,b);
3699           }));
3700           dojo.query("[gxeToolName='removeSection']",ctlTools.htmlElement).forEach(dojo.hitch(this,function(item) {
3701             _toggle(item,this.canRemove());
3702           }));
3703           dojo.query("[gxeToolName='moveSectionUp']",ctlTools.htmlElement).forEach(dojo.hitch(this,function(item) {
3704             _toggle(item,(nTargetIndex > 0));
3705           }));
3706           dojo.query("[gxeToolName='moveSectionDown']",ctlTools.htmlElement).forEach(dojo.hitch(this,function(item) {
3707             _toggle(item,((nTargetLength > 1) && (nTargetIndex < (nTargetLength - 1))));
3708           }));
3709         }
3710       }
3711     }
3712   }
3713   
3714 });
3715 
3716 /**
3717  * @class Container supporting the display of an array of tabs.
3718  * <br/><br/>Typically associated with:
3719  * <br/>gpt/gxe/core/ui/Tabs.xml
3720  * <br/>gpt/gxe/core/xml/ElementChoice.xml
3721  * <br/><br/>Also extended by:
3722  * <br/>gxe.control.IndexedTabArray
3723  * <br/>
3724  * @name gxe.control.TabArray
3725  * @extends gxe.control.Control
3726  */
3727 dojo.provide("gxe.control.TabArray");
3728 dojo.declare("gxe.control.TabArray",gxe.control.Control,{
3729   _activeIndex: -1,
3730   _array: null,
3731 
3732   /** Override gxe.control.Control.initialize() */
3733   initialize: function(context,cfgObject) {
3734     this._array = new Array();
3735     this.inherited(arguments);
3736   },
3737 
3738   /**
3739    * Activates the tab at a given index.
3740    * Once activated, the onTabClicked event is fired.
3741    * @function
3742    * @name activateIndex
3743    * @memberOf gxe.control.TabArray#
3744    * @param {Integer} nIndex the index of the tab to activate
3745    */
3746   activateIndex: function(nIndex) {
3747     this._activeIndex = nIndex;
3748     var nTabs = this._array.length;
3749     for (var i=0; i<nTabs; i++) this._setTabStyle(i);
3750     this.onTabClicked(this._activeIndex);
3751   },
3752   
3753   /**
3754    * Appends a radio button tab to the array.
3755    * @function
3756    * @name appendRadio
3757    * @memberOf gxe.control.TabArray#
3758    * @param {Integer} nIndex the index of the tab
3759    * @param {String} sLabel the label for the tab
3760    * @param {boolean} bIsActive true if the new tab should be active (i.e. selected)
3761    */
3762   appendRadio: function(nIndex,sLabel,bIsActive) {
3763     var elItem = document.createElement("li");
3764     this.htmlElement.appendChild(elItem);
3765     
3766     var elLink = document.createElement("a");
3767     elLink.tabindex = (nIndex + 1);
3768     elLink._gxeTabIndex = nIndex;
3769     elLink.setAttribute("href","javascript:void(0);");
3770     if (bIsActive) elLink.className = "current";
3771     elItem.appendChild(elLink);
3772     
3773     var sChoices = this.gxeId+"Option";
3774     var sOptionId = this.context.generateUniqueId();
3775     var elOption = document.createElement("input");
3776     if(dojo.isIE <= 8) {
3777       if (bIsActive) {
3778         elOption = document.createElement(
3779             "<input type=\"radio\" name=\""+sChoices+"\" checked=\"checked\"/>");
3780       } else {
3781         elOption = document.createElement("<input type=\"radio\" name=\""+sChoices+"\"/>");
3782       }
3783     } else  {
3784       elOption.setAttribute("name",sChoices);
3785       elOption.setAttribute("type","radio");
3786       if (bIsActive) elOption.setAttribute("checked","checked");
3787     }
3788     
3789     elOption.setAttribute("id",sOptionId);
3790     elOption.setAttribute("value",nIndex);    
3791     elLink.appendChild(elOption);
3792     
3793     var elLabel = document.createElement("label");
3794     if(dojo.isIE <= 8) {
3795       elLabel = document.createElement("<label for=\""+sOptionId+"\"/>");
3796     } else {
3797       elLabel.setAttribute("for",sOptionId);
3798     }
3799     
3800     elLabel.appendChild(document.createTextNode(sLabel));
3801     elLink.appendChild(elLabel);
3802     
3803     dojo.connect(elOption,"onclick",this,dojo.hitch(this,function(e) {
3804       this.activateIndex(elLink._gxeTabIndex);           
3805     }));
3806     this._array.push(elLink);
3807     if (bIsActive) this.activateIndex(nIndex);
3808     return elLink;
3809   },
3810   
3811   /**
3812    * Appends a tab to the array.
3813    * @function
3814    * @name appendTab
3815    * @memberOf gxe.control.TabArray#
3816    * @param {Integer} nIndex the index of the tab
3817    * @param {String} sLabel the label for the tab
3818    * @param {boolean} bIsActive true if the new tab should be active (i.e. selected)
3819    */
3820   appendTab: function(nIndex,sLabel,bIsActive) {
3821     var elItem = document.createElement("li");
3822     var elLink = document.createElement("a");
3823     elLink.tabindex = (nIndex + 1);
3824     elLink._gxeTabIndex = nIndex;
3825     elLink.setAttribute("href","javascript:void(0);");
3826     if (bIsActive) elLink.className = "current";
3827     elLink.appendChild(document.createTextNode(sLabel));
3828     elItem.appendChild(elLink);
3829     dojo.connect(elLink,"onclick",this,dojo.hitch(this,function(e) {
3830       this.activateIndex(elLink._gxeTabIndex);           
3831     }));
3832     this.htmlElement.appendChild(elItem);
3833     this._array.push(elLink);
3834     if (bIsActive) this.activateIndex(nIndex);
3835     return elLink;
3836   },
3837   
3838   /**
3839    * Fired when a tab is clicked.
3840    * @event 
3841    * @name onTabClicked
3842    * @memberOf gxe.control.TabArray#
3843    * @param {Integer} nIndex the index of the clicked tab
3844    */
3845   onTabClicked: function(nIndex) {},
3846   
3847   /**
3848    * Sets the CSS class name for a tab.
3849    * @function
3850    * @name _setTabStyle
3851    * @memberOf gxe.control.TabArray#
3852    * @param {Integer} nIndex the index of the tab
3853    */
3854   _setTabStyle: function(nIndex) {
3855     var elLink = this._array[nIndex];
3856     var nTabIndex = elLink._gxeTabIndex;
3857     var bIsActive = (nTabIndex == this._activeIndex);
3858     if (bIsActive && elLink.className != "current") {
3859       elLink.className = "current";
3860     } else if (!bIsActive && elLink.className == "current") {
3861       elLink.className = "";
3862     }
3863   }
3864 
3865 });
3866 
3867 /**
3868  * @class Provides an array of tabs indexed by the multiplicity of a targeted XML element.
3869  * <br/>An indexed tab array is used when the body of the target is display exclusively.
3870  * @name gxe.control.IndexedTabArray
3871  * @extends gxe.control.TabArray
3872  */
3873 dojo.provide("gxe.control.IndexedTabArray");
3874 dojo.declare("gxe.control.IndexedTabArray",gxe.control.TabArray,{
3875   
3876   /**
3877    * Auto configures the control.
3878    * @function 
3879    * @name autoConfigure
3880    * @memberOf gxe.control.IndexedTabArray#
3881    * @param {gxe.Context} context the editor context 
3882    * @param {gxe.control.Control} parentControl the parent control
3883    */
3884   autoConfigure: function(context,parentControl) {
3885     var cfgAttr;
3886     var cfgObj = new Object();
3887     cfgObj.namespace = gxe.cfg.uriGxeHtml;
3888     cfgObj.name = "ul";
3889     cfgObj.parent = parentControl.cfgObject;
3890     cfgObj.attributes = new Array();
3891     this.cfgObject = cfgObj;
3892   
3893     cfgAttr = new Object();
3894     cfgAttr.namespace = gxe.cfg.uriGxeHtml;
3895     cfgAttr.name = "class";
3896     cfgAttr.value = "gxeTabArray";
3897     cfgAttr.parent = cfgObj;
3898     cfgObj.attributes.push(cfgAttr);
3899     this.initialize(context,cfgObj);
3900   },
3901   
3902   /**
3903    * Synchronizes the array of indexed tabs.
3904    * @function 
3905    * @name synchronize
3906    * @memberOf gxe.control.IndexedTabArray#
3907    * @param {Integer} count the number of repeated XML elements
3908    * @param {Integer} activeIndex the index of the active element
3909    */
3910   synchronize: function(count,activeIndex) {
3911     var nTabs = this._array.length;
3912     if (nTabs < count) {
3913       for (var i=nTabs; i<count; i++) {
3914         var nTabIndex = this._array.length;
3915         var bIsActive = (nTabIndex == activeIndex);
3916         this.appendTab(nTabIndex,""+(nTabIndex+1),bIsActive);
3917       }
3918     } else if (nTabs > count) {
3919       var n = (nTabs - count);
3920       for (var i=(nTabs - 1); i>=count; i--) {
3921         var elTab = this._array[i];
3922         elTab.parentNode.removeChild(elTab);
3923         this._array.pop();
3924       }
3925     }
3926     this._activeIndex = activeIndex;
3927     nTabs = this._array.length;
3928     for (var i=0; i<nTabs; i++) {
3929       var elLink = this._array[i];
3930       elLink.tabindex = (i + 1);
3931       elLink._gxeTabIndex = i;
3932       this._setTabStyle(i);
3933     }
3934     if (nTabs < 2) this.htmlElement.style.display = "none";
3935     else this.htmlElement.style.display = "inline";
3936   }
3937 
3938 });
3939 
3940 /**
3941  * @class Supports the message area section of the page. 
3942  * @name gxe.control.MessageArea
3943  * @extends gxe.control.Control
3944  */
3945 dojo.provide("gxe.control.MessageArea");
3946 dojo.declare("gxe.control.MessageArea",gxe.control.Control,{
3947   ul: null,
3948 
3949   /**
3950    * Adds an error message.
3951    * @function 
3952    * @name addError
3953    * @memberOf gxe.control.MessageArea#
3954    * @param {String} sMessage the message
3955    */
3956   addError: function(sMessage) {
3957     this.addMessage(sMessage,"error");
3958   },
3959 
3960   /**
3961    * Adds a message.
3962    * @function 
3963    * @name addMessage
3964    * @memberOf gxe.control.MessageArea#
3965    * @param {String} sMessage the message
3966    * @param {String} sClass the CSS class name (success|warning|error)
3967    */
3968   addMessage: function(sMessage,sClass) {
3969     var elItem = document.createElement("li");
3970     if (sClass != null) elItem.className = sClass;
3971     elItem.appendChild(document.createTextNode(sMessage));
3972     this.ul.appendChild(elItem);
3973     this.ensureVisibility();
3974   },
3975   
3976   /**
3977    * Adds a success message.
3978    * @function 
3979    * @name addSuccess
3980    * @memberOf gxe.control.MessageArea#
3981    * @param {String} sMessage the message
3982    */
3983   addSuccess: function(sMessage) {
3984     this.addMessage(sMessage,"success");
3985   },
3986 
3987   /**
3988    * Adds a validation error to the control.
3989    * <br/>Validation errors are added as warning messages.
3990    * @function 
3991    * @name addValidationError
3992    * @memberOf gxe.control.MessageArea#
3993    * @param {String} sMessage the message
3994    * @param {gxe.xml.XmlNode} xmlNode the targeted XML node (element or attribute)
3995    * @param {gxe.control.InputBase} the input control associated with the target
3996    */
3997   addValidationError: function(sMessage,xmlNode,inputControl) {
3998     if (sMessage == null) sMessage = "";
3999 
4000     if (inputControl == null) {
4001       this.addMessage(sMessage,"error");
4002     } else {
4003       
4004       var elItem = document.createElement("li");
4005       elItem.className = "warning";
4006       this.ul.appendChild(elItem);
4007       var elLink = document.createElement("a");
4008       elLink.setAttribute("href","javascript:void(0);");
4009       elLink.appendChild(document.createTextNode(sMessage));
4010       if (inputControl.htmlElement != null) {
4011         var sTip = inputControl.htmlElement.title;
4012         if ((typeof(sTip) != "undefined") && (sTip != null)) {
4013           sTip = dojo.trim(sTip);
4014           if (sTip.length > 0) elLink.title = sTip;
4015         }
4016       }
4017       
4018       elItem.appendChild(elLink);
4019       var handle = dojo.connect(elLink,"onclick",this,dojo.hitch(this,function(e) {
4020         inputControl.focus(xmlNode);              
4021       }));
4022       
4023       dojo.connect(inputControl,"onInputChanged",this,dojo.hitch(this,function(inputControl2,value) {
4024         var status =  xmlNode.validateInput(inputControl2,true);
4025         if (status.isValid) {
4026           elItem.className = "success"; 
4027         } else {
4028           elItem.className = "warning"; 
4029         }  
4030         if (status.message != null) {
4031           while (elLink.childNodes.length >= 1) elLink.removeChild(elLink.firstChild);
4032           elLink.appendChild(document.createTextNode(status.message));
4033         }              
4034       }));
4035       
4036       this.ensureVisibility();
4037     }    
4038   },
4039 
4040   /**
4041    * Adds a warning message.
4042    * @function 
4043    * @name addWarning
4044    * @memberOf gxe.control.MessageArea#
4045    * @param {String} sMessage the message
4046    */
4047   addWarning: function(sMessage) {
4048     this.addMessage(sMessage,"warning");
4049   },
4050 
4051   /** Override gxe.control.Control.build() */
4052   build: function(htmlParentElement,domProcessor,domNode) {
4053     this.htmlElement = htmlParentElement;
4054     this.htmlElement.style.display = "none";
4055     
4056     var elClear = document.createElement("a");
4057     elClear.setAttribute("href","javascript:void(0);");
4058     elClear.className = "gxeClearAll";
4059     elClear.appendChild(document.createTextNode(this.context.getI18NString("button.clearMessages")));
4060     dojo.connect(elClear,"onclick",this,"clearAll");
4061     this.htmlElement.appendChild(elClear);
4062 
4063     /*
4064     var elClear = document.createElement("button");
4065     elClear.setAttribute("type","button");
4066     elClear.appendChild(document.createTextNode(this.context.getI18NString("button.clearMessages")));
4067     dojo.connect(elClear,"onclick",this,"clearAll");
4068     this.htmlElement.appendChild(elClear);
4069     */
4070     
4071     var elWrapper = document.createElement("div");
4072     elWrapper.className = "gxeMessageListWrapper";
4073     this.htmlElement.appendChild(elWrapper);
4074     
4075     this.ul = document.createElement("ul");
4076     this.ul.className = "gxeMessageList";
4077     elWrapper.appendChild(this.ul);
4078   },
4079 
4080   /**
4081    * Clears add messages and hides the message area.
4082    * @function 
4083    * @name clearAll
4084    * @memberOf gxe.control.MessageArea#
4085    */
4086   clearAll: function() {
4087     var aRemove = new Array();
4088     var childNodes = this.ul.childNodes;
4089     for (var i=0; i<childNodes.length; i++) {
4090       if (childNodes[i].nodeType == 1) {
4091         aRemove.push(childNodes[i]);
4092       }
4093     }
4094     for (var i=0; i<aRemove.length; i++) {
4095       this.ul.removeChild(aRemove[i]);
4096     }
4097     this.htmlElement.style.display = "none";
4098     if (dojo.hasClass(this.context.htmlParentElement,"gxeRepairMode")) {
4099       dojo.removeClass(this.context.htmlParentElement,"gxeRepairMode");
4100     }
4101   },
4102 
4103   /** Override gxe.control.Control.ensureVisibility() */
4104   ensureVisibility: function() {
4105     if (this.htmlElement.style.display == "none") this.htmlElement.style.display = "block";
4106   },
4107 
4108   /**
4109    * Handles an exception by adding an error message to the control.
4110    * @function 
4111    * @name handleException
4112    * @memberOf gxe.control.MessageArea#
4113    * @param {String} exception the exception
4114    */
4115   handleException: function(exception) {
4116     this.addMessage(exception,"error");
4117   }
4118   
4119 });
4120 
4121 /**
4122  * @class Supports a UI section. 
4123  * <br/>A section typically consists of a header and a body.
4124  * <br/><br/>Typically associated with:
4125  * <br/>gpt/gxe/core/ui/Section.xml
4126  * <br/><br/>Also referenced by:
4127  * <br/>gpt/gxe/core/xml/Attribute.xml
4128  * <br/>gpt/gxe/core/xml/Element.xml
4129  * <br/>gpt/gxe/core/xml/ElementTextOnly.xml
4130  * <br/>
4131  * @name gxe.control.Section
4132  * @extends gxe.control.Control
4133  */
4134 dojo.provide("gxe.control.Section");
4135 dojo.declare("gxe.control.Section",gxe.control.Control,{
4136   repeatablesContainer: null,
4137   sectionContainer: null,
4138   sectionHeader: null,
4139   sectionBodyContainer: null,
4140   sectionBody: null,
4141   uiLabelText: null,
4142   useExclusiveDisplay: true,
4143 
4144   cfgSectionBody: null,
4145   indexedTabArray: null,
4146   tabArray: null,
4147 
4148   /** Override gxe.control.Control.initialize() */
4149   initialize: function(context,cfgObject) {
4150     var s = gxe.cfg.getGxeAttributeValue(cfgObject,"useExclusiveDisplay");
4151     if (this.useExclusiveDisplay) this.useExclusiveDisplay = (s != "false");
4152     else this.useExclusiveDisplay = (s == "true");
4153     this.inherited(arguments);
4154   },
4155 
4156   /** Override gxe.control.Control.build() */
4157   build: function(htmlParentElement,domProcessor,domNode) {
4158     var bRepeatable = this.xmlNode.isRepeatable();
4159     if ((this.parentControl == null) || this.useExclusiveDisplay || !bRepeatable) {
4160       this.inherited(arguments);
4161       return;
4162     } 
4163 
4164     var ctlContainer = this.parentControl.sectionContainer;
4165     if ((typeof(ctlContainer) == "undefined") || (ctlContainer == null)) {
4166 
4167       
4168       var cfgParent = this.parentControl.cfgObject;
4169       var cfgChild = this.cfgObject;
4170 
4171       var cfgContainer = new Object();
4172       cfgContainer.namespace = gxe.cfg.uriGxeHtml;
4173       cfgContainer.name = "div";
4174       cfgContainer.parent = cfgParent;
4175 
4176       var cfgAttribute;
4177       cfgContainer.attributes = new Array();
4178 
4179       cfgAttribute = new Object();
4180       cfgAttribute.namespace = gxe.cfg.uriGxeHtml;
4181       cfgAttribute.name = "class";
4182       cfgAttribute.value = "gxeSectionContainer";
4183       cfgAttribute.parent = cfgContainer;
4184       cfgContainer.attributes.push(cfgAttribute);
4185       
4186       cfgAttribute = new Object();
4187       cfgAttribute.namespace = gxe.cfg.uriGxe;
4188       cfgAttribute.name = "jsClass";
4189       cfgAttribute.value = "gxe.control.RepeatablesContainer";
4190       cfgAttribute.parent = cfgContainer;
4191       cfgContainer.attributes.push(cfgAttribute);
4192 
4193       ctlContainer = this.context.makeXhtmlControl(cfgContainer,this.parentControl,true);
4194       ctlContainer.isExclusive = false;
4195       
4196       ctlContainer.sectionContainer = ctlContainer;
4197       this.sectionContainer = ctlContainer;      
4198       ctlContainer.xmlNode = this.xmlNode;
4199       ctlContainer.xmlParentElement = this.xmlNode.parentElement;
4200       
4201       ctlContainer.build(htmlParentElement,domProcessor,domNode);
4202       var elHtmlParent = ctlContainer.htmlElement;
4203       cfgContainer.children = new Array();
4204       cfgContainer.children.push(this.cfgObject);
4205 
4206       this.parentControl = ctlContainer;
4207       this.execBuild(elHtmlParent,domProcessor,domNode);
4208     } else {
4209       this.sectionContainer = ctlContainer;
4210       this.inherited(arguments);
4211     }
4212     
4213   },
4214   
4215   /**
4216    * Initializes events associated with the section header label.
4217    * <br/>Adds a checkbox to the label when a targeted XML node is optional.
4218    * @function 
4219    * @name initializeLabelEvents
4220    * @memberOf gxe.control.Section#
4221    * @param {gxe.xml.XmlNode} xmlNode the targeted XML node (element or attribute)
4222    * @param {gxe.control.SectionMenu} associated ctlMenu the section menu 
4223    *   (may be null)
4224    * @param {gxe.control.IndexedtabArray} ctlIndexedIabArray associated indexed tab array 
4225    *   (may be null)
4226    * @param {gxe.xml.DomProcessor} domProcessor an XML processor 
4227    *   (when opening an existing document)
4228    * @param {Node} domNode the DOM node that is actively being processed 
4229    *   (when opening an existing document)
4230    */
4231   initializeLabelEvents: function(xmlNode,ctlMenu,ctlIndexedIabArray,domProcessor,domNode) {
4232     
4233     var ctlHeader = this.findFirstChildControl(">[gxename='header']");
4234     if (ctlHeader != null) {
4235       
4236       var ctlLabel = ctlHeader.findFirstChildControl(">[gxename='label']");
4237       if (ctlLabel != null) {
4238         
4239         var cn = ctlLabel.htmlElement.childNodes;
4240         for (var i=0; i<cn.length; i++) {
4241           if (cn[i].nodeType == 3) {
4242             this.uiLabelText = cn[i].nodeValue; // (nodeType == 3) is a TEXT_NODE
4243             break;
4244           }
4245         }
4246         
4247         if (ctlMenu == null) {
4248           var sInfo = gxe.cfg.getGxeAttributeValue(this.cfgObject,"info");
4249           if (sInfo != null) this.makeControlMenu(ctlHeader,true);
4250         }
4251         
4252         var nMinOccurs = xmlNode.resolveMinOccurs();   
4253         if (nMinOccurs >= 1) {
4254           ctlLabel.htmlElement.className = "required";
4255         } else {
4256           
4257           var bChecked = true;
4258           if (domProcessor == null) {
4259             bChecked = false;
4260             if (gxe.cfg.getGxeAttributeValue(this.cfgObject,"preferOpen") == "true") bChecked = true;
4261           } else {
4262             bChecked = domProcessor.hasChildrenOrAttributes(domNode);
4263           }
4264           
4265           var sId = this.context.generateUniqueId();
4266           var el = document.createElement("input");
4267           el.setAttribute("type","checkbox");
4268           el.setAttribute("id",sId);
4269           var elLabel = ctlLabel.htmlElement;
4270           elLabel.parentNode.insertBefore(el,elLabel);
4271           if (bChecked) el.setAttribute("checked","checked");
4272           elLabel.setAttribute("for",sId);
4273           
4274           var _toggleVisibility = function(ctl,bVisible) {
4275             if (ctl != null) {
4276               if (bVisible) ctl.htmlElement.style.visibility = "visible";
4277               else ctl.htmlElement.style.visibility = "hidden";
4278             }
4279           };
4280           
4281           var _onCheckBoxClicked = function(sbc,bWasChecked) {
4282             xmlNode.isOptionalPlaceHolder = !bWasChecked;
4283             _toggleVisibility(ctlMenu,bWasChecked);
4284             _toggleVisibility(ctlIndexedIabArray,bWasChecked);
4285             if (sbc != null) {
4286               sbc.setDisplayStyle(bWasChecked);
4287               var n = sbc.getLength();
4288               for ( var i=0; i<sbc.getLength(); i++) {
4289                 sbc.getItem(i).xmlNode.isOptionalPlaceHolder = !bWasChecked;
4290               }
4291             }
4292           };
4293           
4294           dojo.connect(el,"onclick",this,dojo.hitch(this,function(e) { 
4295             _onCheckBoxClicked(this.sectionBodyContainer,el.checked);
4296           }));
4297           
4298           if (!bChecked) _onCheckBoxClicked(this.sectionBodyContainer,bChecked);
4299           
4300         }
4301         
4302       }
4303       
4304       var nSubHeaders = 0;
4305       dojo.query("[gxename='header']",this.htmlElement).forEach(dojo.hitch(this,function(item) {
4306         nSubHeaders++;
4307       }));
4308       if (nSubHeaders == 1) this.htmlElement.style.border = "none";
4309     }
4310   },
4311 
4312   /**
4313    * Fired for certain events triggered from the section header.
4314    * @event 
4315    * @name onHeaderEvent
4316    * @memberOf gxe.control.Section#
4317    * @param {Event} e the underlying browser event
4318    * @param {gxe.control.Control} gxeControl the control that fired the event
4319    * @param {String} htmlEventName the HTML event name (e.g. "onclick")
4320    * @param {String} gxeEventName a GXE name for the event (e.g. "onLabelClicked")
4321    *   within the configuration object for the section control (for popup help)
4322    */
4323   onHeaderEvent: function(e,gxeControl,htmlEventName,gxeEventName) {
4324     if (gxeEventName == "onLabelClicked") {
4325       if (this.sectionBodyContainer != null) {
4326         this.sectionBodyContainer.toggleDisplay();
4327       }
4328     } else {
4329       if (this.repeatablesContainer != null) {
4330         this.repeatablesContainer.handleEvent(e,this,htmlEventName,gxeEventName);
4331       }
4332     }
4333   },
4334 
4335   /** Override gxe.control.Control.onHtmlChildrenCreated() */
4336   onHtmlChildrenCreated: function(domProcessor,domNode) {
4337     var ctlContainer = this.sectionContainer;
4338     var ctlBodyContainer = this.sectionBodyContainer;
4339     var ctlHeader = this.findFirstChildControl("[gxename='header']");
4340     var ctlBody = this.findFirstChildControl("[gxename='body']");
4341     var ctlTabArray = null;
4342     var ctlIndexedTabs = null;
4343 
4344     if (ctlHeader != null) {
4345       ctlTabArray = ctlHeader.findFirstChildControl("[gxename='tabArray']");
4346       ctlIndexedTabs = ctlHeader.findFirstChildControl("[gxename='indexedTabArray']");
4347       
4348       /*
4349       dojo.query("[gxeEventName]",ctlHeader.htmlElement).forEach(dojo.hitch(this,function(item) {
4350         var ctl = item.gxeControl;
4351         if ((typeof(ctl) != "undefined") && (ctl != null)) {
4352           dojo.connect(ctl,"onEvent",this,"onHeaderEvent");
4353         }
4354       }));
4355       */
4356     }
4357 
4358     if (!this.useExclusiveDisplay) {
4359       if (ctlContainer != null) this.repeatablesContainer = ctlContainer;
4360     } else {
4361       if (ctlBodyContainer != null) {
4362         this.repeatablesContainer = ctlBodyContainer;
4363         if (ctlIndexedTabs != null) {
4364           dojo.connect(ctlBodyContainer,"onArrayModified",ctlIndexedTabs,"synchronize");
4365           dojo.connect(ctlIndexedTabs,"onTabClicked",ctlBodyContainer,"activateIndex");
4366         }
4367       }
4368     }
4369     if (this.repeatablesContainer != null) {
4370       this.repeatablesContainer.xmlElementCfgObject = this.xmlNode.cfgObject;
4371       this.repeatablesContainer.xmlParentElement = this.xmlNode.parentElement;
4372       this.xmlNode.repeatablesContainer = this.repeatablesContainer;
4373     }
4374   },
4375   
4376   /**
4377    * Makes a menu bar for the section within the section header.
4378    * @function 
4379    * @name makeControlMenu
4380    * @memberOf gxe.control.Section#
4381    * @param {gxe.control.SectionHeader} ctlHeader the section header
4382    * @param {boolean} bCheckForInfo if true then check for "g:info" attribute
4383    *   within the configuration object for the section control (for popup help)
4384    * @returns {gxe.control.SectionMenu} the section menu
4385    */
4386   makeControlMenu: function(ctlHeader,bCheckForInfo) {
4387     var ctlMenu = new gxe.control.SectionMenu();
4388     ctlMenu.autoConfigure(this.context,ctlHeader);
4389     ctlMenu.build(ctlHeader.htmlElement,null,null);
4390     if (bCheckForInfo) {
4391       var sInfo = gxe.cfg.getGxeAttributeValue(this.cfgObject,"info");
4392       if (sInfo != null) sInfo = dojo.trim(sInfo);
4393       if ((sInfo != null) && (sInfo.length > 0)) {
4394         var sImages = this.context.contextPath+"/catalog/images/";
4395         ctlMenu.appendImageButton("info",sImages+"gxe-info.png",sInfo);
4396         // TODO: implement a popup bubble,
4397         // use XHR to call the server to return the content of an i18n key
4398       }
4399     }
4400     return ctlMenu;
4401   }
4402 
4403 });
4404 
4405 /**
4406  * @class Supports the header portion of a UI section.
4407  * <br/><br/>Typically associated with:<br/>gpt/gxe/core/ui/Section.xml<br/>
4408  * @name gxe.control.SectionHeader
4409  * @extends gxe.control.Control
4410  */
4411 dojo.provide("gxe.control.SectionHeader");
4412 dojo.declare("gxe.control.SectionHeader",gxe.control.Control,{
4413 });
4414 
4415 /**
4416  * @class Supports a menu bar associated with the header portion of a UI section.
4417  * <br/><br/>This class is not currently associated with any editor definition files.<br/><br/>
4418  * @name gxe.control.SectionMenu
4419  * @extends gxe.control.Control
4420  */
4421 dojo.provide("gxe.control.SectionMenu");
4422 dojo.declare("gxe.control.SectionMenu",gxe.control.Control,{
4423   
4424   /**
4425    * Appends an image button to the menu bar.
4426    * @function 
4427    * @name appendImageButton
4428    * @memberOf gxe.control.SectionMenu#
4429    * @param {String} gxeEventName to associate with the click event of the new img element 
4430    * @param {String} sSrc the src for the new img element
4431    * @param {String} sTip the tool tip for the new img element
4432    */
4433   appendImageButton: function(gxeEventName,sSrc,sTip) {
4434     var el = document.createElement("img");
4435     el.alt = sTip;
4436     el.title = sTip;
4437     el.src = sSrc;
4438     el.setAttribute("gxeToolName",gxeEventName); 
4439     this.htmlElement.appendChild(el);
4440     dojo.connect(el,"onclick",this,dojo.hitch(this,function(e) { 
4441       this.onEvent(e,this,"onclick",gxeEventName);
4442     }));
4443     return el;
4444   },
4445   
4446   /**
4447    * Auto configures the control.
4448    * @function 
4449    * @name autoConfigure
4450    * @memberOf gxe.control.SectionMenu#
4451    * @param {gxe.Context} context the editor context 
4452    * @param {gxe.control.Control} parentControl the parent control
4453    */
4454   autoConfigure: function(context,parentControl) {
4455     var cfgAttr;
4456     var cfgObj = new Object();
4457     cfgObj.namespace = gxe.cfg.uriGxeHtml;
4458     cfgObj.name = "span";
4459     cfgObj.parent = parentControl.cfgObject;
4460     cfgObj.attributes = new Array();
4461     this.cfgObject = cfgObj;
4462 
4463     cfgAttr = new Object();
4464     cfgAttr.namespace = gxe.cfg.uriGxeHtml;
4465     cfgAttr.name = "class";
4466     cfgAttr.value = "gxeSectionMenu";
4467     cfgAttr.parent = cfgObj;
4468     cfgObj.attributes.push(cfgAttr);
4469     this.initialize(context,cfgObj);
4470   }
4471 
4472 });
4473 
4474 /**
4475  * @class Supports the body portion of a UI section.
4476  * <br/><br/>Typically associated with:<br/>gpt/gxe/core/ui/Section.xml<br/>
4477  * @name gxe.control.SectionBody
4478  * @extends gxe.control.Control
4479  */
4480 dojo.provide("gxe.control.SectionBody");
4481 dojo.declare("gxe.control.SectionBody",gxe.control.Control,{
4482 
4483   /** Override gxe.control.Control.build() */
4484   build: function(htmlParentElement,domProcessor,domNode) {
4485   
4486     var ctlContainer = this.parentControl.sectionBodyContainer;
4487     if ((typeof(ctlContainer) == "undefined") || (ctlContainer == null)) {
4488       var cfgParent = this.parentControl.cfgObject;
4489       var cfgChild = this.cfgObject;
4490 
4491       var cfgContainer = new Object();
4492       cfgContainer.namespace = gxe.cfg.uriGxeHtml;
4493       cfgContainer.name = "div";
4494       cfgContainer.parent = cfgParent;
4495 
4496       var cfgAttribute;
4497       cfgContainer.attributes = new Array();
4498 
4499       cfgAttribute = new Object();
4500       cfgAttribute.namespace = gxe.cfg.uriGxeHtml;
4501       cfgAttribute.name = "class";
4502       cfgAttribute.value = "gxeSectionBodyContainer";
4503       cfgAttribute.parent = cfgContainer;
4504       cfgContainer.attributes.push(cfgAttribute);
4505       
4506       cfgAttribute = new Object();
4507       cfgAttribute.namespace = gxe.cfg.uriGxe;
4508       cfgAttribute.name = "jsClass";
4509       cfgAttribute.value = "gxe.control.RepeatablesContainer";
4510       cfgAttribute.parent = cfgContainer;
4511       cfgContainer.attributes.push(cfgAttribute);
4512       
4513       ctlContainer = this.context.makeXhtmlControl(cfgContainer,this.parentControl,true);
4514       ctlContainer.isExclusive = true;
4515       ctlContainer.sectionBodyContainer = ctlContainer;
4516       ctlContainer.cfgSectionBody = this.cfgObject;
4517       this.parentControl.sectionBodyContainer = ctlContainer;
4518       ctlContainer.xmlNode = this.xmlNode;
4519       ctlContainer.xmlParentElement = this.xmlNode.parentElement;
4520       
4521       ctlContainer.build(htmlParentElement,domProcessor,domNode);
4522       var elHtmlParent = ctlContainer.htmlElement;
4523       cfgContainer.children = new Array();
4524       cfgContainer.children.push(this.cfgObject);
4525 
4526       this.parentControl = ctlContainer;
4527       this.execBuild(elHtmlParent,domProcessor,domNode);
4528     } else {
4529       this.inherited(arguments);
4530     }
4531     
4532   },
4533 
4534   /** Override gxe.control.Control.ensureVisibility() */
4535   ensureVisibility: function(subjectXmlNode) {
4536     if (this.parentControl.sectionBodyContainer != null) {
4537       var el = this.parentControl.sectionBodyContainer.htmlElement;
4538       if (el.style.display == "none") {
4539         this.parentControl.sectionBodyContainer.toggleDisplay();
4540       }
4541     }
4542     this.inherited(arguments);
4543   }
4544 
4545 });
4546 
4547 /**
4548  * @class Supports a UI section.
4549  * @name gxe.control.Parameter
4550  * @extends gxe.control.Section
4551  * @deprecated use gxe.control.Section instead
4552  */
4553 dojo.provide("gxe.control.Parameter");
4554 dojo.declare("gxe.control.Parameter",gxe.control.Section,{
4555 });
4556 
4557 /**
4558  * @class Supports a UI section associated with a targeted XML element.
4559  * <br/><br/>Typically associated with:
4560  * <br/>gpt/gxe/core/xml/Element.xml
4561  * <br/>gpt/gxe/core/xml/ElementTextOnly.xml
4562  * <br/>
4563  * @name gxe.control.Element
4564  * @extends gxe.control.Section
4565  */
4566 dojo.provide("gxe.control.Element");
4567 dojo.declare("gxe.control.Element",gxe.control.Section,{
4568   multiplicityTools: null,
4569   
4570   /** Override gxe.control.Control.onHtmlChildrenCreated() */
4571   onHtmlChildrenCreated: function(domProcessor,domNode) {
4572     this.inherited(arguments);
4573     
4574     var ctlMenu = null;
4575     var ctlIndexedIabArray = null;
4576     var sImages = this.context.contextPath+"/catalog/images/";
4577     var ctlHeader = this.findFirstChildControl(">[gxename='header']");
4578     if (ctlHeader != null) {
4579       
4580       var bBuildRepeatables = false;
4581       if ((this.xmlNode != null) && this.xmlNode.isRepeatable()) {
4582         bBuildRepeatables = true;
4583         var ctlInput = this.xmlNode.getInputControl();
4584         if ((ctlInput != null) && ctlInput.getSupportsMultipleValues()) {
4585           bBuildRepeatables = false;
4586         } else if (this.xmlNode.wrapsIsoMultiValueList()) {
4587           bBuildRepeatables = false;
4588         }
4589       }
4590           
4591       if (bBuildRepeatables) {
4592         ctlMenu = this.makeControlMenu(ctlHeader,true);
4593         
4594         ctlMenu.appendImageButton("repeatSection",sImages+"gxe-repeat.png",
4595             this.context.getI18NString("button.repeatSection"));
4596         ctlMenu.appendImageButton("removeSection",sImages+"gxe-remove.png",
4597             this.context.getI18NString("button.removeSection"));
4598         if (this.useExclusiveDisplay) {
4599           ctlMenu.appendImageButton("moveSectionUp",sImages+"gxe-move-left.png",
4600               this.context.getI18NString("button.moveSectionLeft"));
4601           ctlMenu.appendImageButton("moveSectionDown",sImages+"gxe-move-right.png",
4602               this.context.getI18NString("button.moveSectionRight"));
4603         } else {
4604           ctlMenu.appendImageButton("moveSectionUp",sImages+"gxe-move-up.png",
4605               this.context.getI18NString("button.moveSectionUp"));
4606           ctlMenu.appendImageButton("moveSectionDown",sImages+"gxe-move-down.png",
4607               this.context.getI18NString("button.moveSectionDown"));
4608         }
4609         dojo.connect(ctlMenu,"onEvent",this,"onHeaderEvent");
4610         this.multiplicityTools = ctlMenu;
4611         
4612         var ctlRepeatables = this.repeatablesContainer;
4613         if (ctlRepeatables != null) ctlRepeatables.multiplicityTools = ctlMenu;
4614         if (this.useExclusiveDisplay && (ctlRepeatables != null)) {
4615           ctlIndexedIabArray = new gxe.control.IndexedTabArray();
4616           ctlIndexedIabArray.autoConfigure(this.context,ctlHeader);
4617           ctlIndexedIabArray.build(ctlHeader.htmlElement,domProcessor,domNode);
4618           dojo.connect(ctlRepeatables,"onArrayModified",ctlIndexedIabArray,"synchronize");
4619           dojo.connect(ctlIndexedIabArray,"onTabClicked",ctlRepeatables,"activateIndex");
4620           dojo.connect(ctlIndexedIabArray,"onTabClicked",ctlRepeatables,"syncTools");
4621         }
4622         if (ctlRepeatables != null) ctlRepeatables.syncTools();
4623         
4624       }
4625     }
4626     
4627     this.initializeLabelEvents(this.xmlNode,ctlMenu,ctlIndexedIabArray,domProcessor,domNode);
4628   }
4629 
4630 });
4631 
4632 /**
4633  * @class Supports a UI section associated with a targeted XML attribute.
4634  * <br/><br/>Typically associated with:<br/>gpt/gxe/core/xml/Attribute.xml<br/>
4635  * @name gxe.control.Attribute
4636  * @extends gxe.control.Parameter
4637  */
4638 dojo.provide("gxe.control.Attribute");
4639 dojo.declare("gxe.control.Attribute",gxe.control.Parameter,{
4640   
4641   /** Override gxe.control.Control.onHtmlChildrenCreated() */
4642   onHtmlChildrenCreated: function(domProcessor,domNode) {
4643     this.inherited(arguments);
4644     this.initializeLabelEvents(this.xmlNode,null,null,domProcessor,domNode);
4645   }
4646 });
4647 
4648 /**
4649  * @class Supports the display of a label associated with a targeted XML node (element or attribute).
4650  * <br/><br/>Typically associated with:
4651  * <br/>gpt/gxe/core/ui/Section.xml
4652  * <br/>gpt/gxe/core/ui/TargetLabel.xml
4653  * <br/>
4654  * @name gxe.control.TargetLabel
4655  * @extends gxe.control.Control
4656  */
4657 dojo.provide("gxe.control.TargetLabel");
4658 dojo.declare("gxe.control.TargetLabel",gxe.control.Control,{
4659 
4660   /** Override gxe.control.Control.createHtmlElement() */
4661   createHtmlElement: function() {
4662     var s = this.htmlTextContent;
4663     if ((typeof(s) == "undefined") || (s == null) || (s.length == 0)) {
4664       this.htmlTextContent = this.getLabelText();
4665     }
4666     this.inherited(arguments);
4667   }
4668 
4669 });
4670 
4671 /**
4672  * @class Supports the display of an exclusive element choice.
4673  * <br/><br/>Typically associated with:<br/>gpt/gxe/core/xml/ElementChoice.xml<br/>
4674  * @name gxe.control.ElementChoice
4675  * @extends gxe.control.Control
4676  */
4677 dojo.provide("gxe.control.ElementChoice");
4678 dojo.declare("gxe.control.ElementChoice",gxe.control.Control,{
4679   choiceBody: null,
4680 
4681   /**
4682    * Determines if an XML element is associated with the selected choice.
4683    * <br/>The anchor is enabled when text has been entered into the associated input text box.
4684    * @function 
4685    * @name isElementSelected
4686    * @memberOf gxe.control.ElementChoice#
4687    * @param {gxe.xml.XmlElement} xmlElement the XML element to test
4688    * @return {boolean} true if the XML element is selected
4689    */
4690   isElementSelected: function(xmlElement) {
4691     if (this.choiceBody != null) {
4692       var nActiveIndex = this.choiceBody.getActiveIndex();
4693       if (nActiveIndex >= 0) {
4694         var item = this.choiceBody.getItem(nActiveIndex);
4695         return (item.xmlNode == xmlElement);
4696       }
4697     }
4698     return false;
4699   },
4700 
4701   /** Override gxe.control.Control.onHtmlChildrenCreated() */
4702   onHtmlChildrenCreated: function(domProcessor,domNode) {
4703 
4704     var bAllowNone = (gxe.cfg.getMinOccurs(this.cfgObject) == "0");
4705     var sLabel = gxe.cfg.getGxeAttributeValue(this.cfgObject,"label");
4706     var sLabelNone = gxe.cfg.getGxeAttributeValue(this.cfgObject,"labelNone");
4707     if (sLabelNone == null) sLabelNone = "??none??";
4708 
4709     // find the component controls, build the tabs based upon the content of the body
4710     var ctlHeader = this.findFirstChildControl("[gxename='header']");
4711     if (ctlHeader != null) {
4712       var ctlTabArray = ctlHeader.findFirstChildControl("[gxename='tabArray']");
4713       if (ctlTabArray != null) {
4714         var ctlBody = this.findFirstChildControl("[gxename='body']");
4715         if (ctlBody != null) {
4716           this.choiceBody = ctlBody;
4717           dojo.connect(ctlTabArray,"onTabClicked",ctlBody,"activateIndex");
4718 
4719           var nSelectedIndex = ctlBody.defaultSelectedIndex;
4720           if (typeof(nSelectedIndex) != "number") nSelectedIndex = -1;
4721           if (bAllowNone) {
4722             //ctlTabArray.appendTab(-1,sLabelNone,(nSelectedIndex == -1));
4723             ctlTabArray.appendRadio(-1,sLabelNone,(nSelectedIndex == -1));
4724           } 
4725           
4726           var n = ctlBody.getLength();
4727           for (var i=0; i<n; i++) {
4728             var bIsSelected = false;
4729             if (nSelectedIndex != -1) bIsSelected = (i == nSelectedIndex);
4730             else if (!bAllowNone && (i == 0)) bIsSelected = true;
4731             var ctl = ctlBody.getItem(i);
4732             var sLabel = ctl.getLabelText();
4733             //ctlTabArray.appendTab(i,sLabel,bIsSelected);
4734             ctlTabArray.appendRadio(i,sLabel,bIsSelected);
4735             ctl.xmlNode.exclusiveChoiceControl = this;
4736           }
4737         }
4738       }
4739     }
4740   }
4741 
4742 });
4743 
4744 /**
4745  * @class Supports the body section associated with an exclusive element choice.
4746  * <br/><br/>Typically associated with:<br/>gpt/gxe/core/xml/ElementChoice.xml<br/>
4747  * @name gxe.control.ElementChoiceBody
4748  * @extends gxe.control.ExclusiveControlArray
4749  */
4750 dojo.provide("gxe.control.ElementChoiceBody");
4751 dojo.declare("gxe.control.ElementChoiceBody",gxe.control.ExclusiveControlArray,{
4752   defaultSelectedIndex: -1,
4753 
4754   /** Override gxe.control.Control.processChildren() */
4755   processChildren: function(cfgObject,htmlParentElement,xmlNode,domProcessor,domNode) {
4756   
4757     // Don't process all the children we only want the child "element"s
4758     var nIndex = -1;
4759     var nSelectedIndex = -1;
4760     gxe.cfg.forEachChild(this.cfgObject,"*","*",dojo.hitch(this,function(cfgChild) {
4761       if (cfgChild.namespace == gxe.cfg.uriGxe) {
4762         if (cfgChild.name == "element") {
4763           nIndex++;
4764           if (nSelectedIndex == -1) {
4765             if ((domProcessor != null) && (domNode != null)) {
4766               var domMatch = domProcessor.findMatchingChildElement(domNode,cfgChild);
4767               if (domMatch != null) nSelectedIndex = nIndex;
4768             } else {
4769               var sSelected = gxe.cfg.getGxeAttributeValue(cfgChild,"selected");
4770               if (sSelected == "true") nSelectedIndex = nIndex;
4771             }
4772           }
4773         }
4774       }
4775     }));
4776     this.defaultSelectedIndex = nSelectedIndex;
4777 
4778     gxe.cfg.forEachChild(this.cfgObject,"*","*",dojo.hitch(this,function(cfgChild) {
4779       if (cfgChild.namespace == gxe.cfg.uriGxe) {
4780         if (cfgChild.name == "element") {
4781           var elTmp = document.createElement("div");
4782           this.processCfgElement(cfgChild,elTmp,this.xmlNode,domProcessor,domNode);
4783         } 
4784       }
4785     }));
4786   }
4787 
4788 });
4789 
4790 /**
4791  * @class Supports the display of a set of tabs.
4792  * <br/><br/>Typically associated with:<br/>gpt/gxe/core/ui/Tabs.xml<br/>
4793  * @name gxe.control.Tabs
4794  * @extends gxe.control.Control
4795  */
4796 dojo.provide("gxe.control.Tabs");
4797 dojo.declare("gxe.control.Tabs",gxe.control.Control,{
4798   tabArray: null,
4799   tabsBody: null,
4800 
4801   /** Override gxe.control.Control.ensureVisibility() */
4802   ensureVisibility: function(subjectXmlNode) {
4803     if ((this.tabArray != null) && (this.tabsBody != null)) {
4804       var nIndex = this.tabsBody.findIndexByXmlNode(subjectXmlNode);
4805       if ((nIndex != -1) && (nIndex != this.tabsBody.getActiveIndex())) {
4806         this.tabArray.activateIndex(nIndex);
4807       }
4808     }
4809     this.inherited(arguments);
4810   },
4811 
4812   /** Override gxe.control.Control.onHtmlChildrenCreated() */
4813   onHtmlChildrenCreated: function(domProcessor,domNode) {
4814 
4815     // find the component controls, build the tabs based upon the content of the body
4816     var ctlHeader = this.findFirstChildControl("[gxename='header']");
4817     if (ctlHeader != null) {
4818       var ctlTabArray = ctlHeader.findFirstChildControl("[gxename='tabArray']");
4819       if (ctlTabArray != null) {
4820         this.tabArray = ctlTabArray;
4821         var ctlBody = this.findFirstChildControl("[gxename='body']");
4822         if (ctlBody != null) {
4823           this.tabsBody = ctlBody;
4824           dojo.connect(ctlTabArray,"onTabClicked",ctlBody,"activateIndex");
4825           var nSelectedIndex = -1;
4826           var n = ctlBody.getLength();
4827           for (var i=0; i<n; i++) {
4828             var ctl = ctlBody.getItem(i);
4829             var sLabel = ctl.getLabelText();
4830             var sSelected = gxe.cfg.getGxeAttributeValue(ctl.cfgObject,"selected");
4831             if (sSelected == "true") nSelectedIndex = i;
4832             ctlTabArray.appendTab(i,sLabel,(nSelectedIndex == i));
4833           }
4834           if ((n > 0) && (nSelectedIndex == -1)) ctlTabArray.activateIndex(0);
4835         }
4836       }
4837     }
4838   }
4839 
4840 });
4841 
4842 /**
4843  * @class Supports the body section associated with a set of tabs.
4844  * <br/><br/>Typically associated with:<br/>gpt/gxe/core/ui/Tabs.xml<br/>
4845  * @name gxe.control.TabsBody
4846  * @extends gxe.control.ExclusiveControlArray
4847  */
4848 dojo.provide("gxe.control.TabsBody");
4849 dojo.declare("gxe.control.TabsBody",gxe.control.ExclusiveControlArray,{
4850 });
4851 
4852 
4853 /* Input controls ==================================================== */
4854 
4855 /**
4856  * @class Base class for all input controls.
4857  * @name gxe.control.InputBase
4858  * @extends gxe.control.Control
4859  * @property {boolean} supportsMultipleValues indicates whether or not the control supports
4860  *   the input of multiple values
4861  */
4862 dojo.provide("gxe.control.InputBase");
4863 dojo.declare("gxe.control.InputBase",gxe.control.Control,{
4864   supportsMultipleValues: false,
4865 
4866   /**
4867    * Attempts to find label text associated with the input control.
4868    * The label text is used for validation feedback within the gxe.control.MessageArea 
4869    * portion of the page.
4870    * @function 
4871    * @name findParentLabelText
4872    * @memberOf gxe.control.InputBase#
4873    * @param {gxe.xml.XmlNode} xmlNode the targeted XML node for input control
4874    * @return {String} the label text
4875    */
4876   findParentLabelText: function(xmlNode) {
4877     var ctl = this.parentControl;
4878     while ((typeof(ctl) != "undefined") && (ctl != null)) {
4879       var s = ctl.uiLabelText;
4880       if ((typeof(s) != "undefined") && (s != null)) return s;
4881       ctl = ctl.parentControl;
4882     }
4883     return "?"+xmlNode.nodeInfo.localName;
4884   },
4885   
4886   /**
4887    * Fires the onInputChanged() event.
4888    * @function 
4889    * @name fireInputChanged
4890    * @memberOf gxe.control.InputBase#
4891    * @param {Event} e the underlying browser event
4892    */  
4893   fireInputChanged: function(e) {
4894     this.onInputChanged(this);
4895   },
4896 
4897   /**
4898    * Fires the onInputChanged() event based upon a browser onkeyup() event.
4899    * The onInputChanged() event will only be fired if the user key is 
4900    * not 13 (carriage return) and not 9 (tab).
4901    * @function 
4902    * @name fireInputChangedOnKeyUp
4903    * @memberOf gxe.control.InputBase#
4904    * @param {Event} e the underlying browser event
4905    */  
4906   fireInputChangedOnKeyUp: function(e) {
4907     if (!e) e = window.event;
4908     if (e) {
4909       var nKey = (e.keyCode) ? e.keyCode : e.which;
4910       // ignore carriage return and tab
4911       if ((nKey != 13) && (nKey != 9)) this.fireInputChanged(e);
4912     }
4913   },
4914   
4915   /**
4916    * Gets the value associated with the input control.
4917    * This method should be overridden for all sub-classes that support single valued
4918    * input (i.e. where this.supportsMultipleValues == false).
4919    * @function 
4920    * @name getInputValue
4921    * @memberOf gxe.control.InputBase#
4922    * @param {boolean} bInFeedbackMode true if the value is being requested as validation feedback
4923    * @return {Object} the input value
4924    */  
4925   getInputValue: function(bInFeedbackMode) {return null;},
4926   
4927   /**
4928    * Gets the values associated with the input control.
4929    * This method should be overridden for all sub-classes that support multi-valued
4930    * input (i.e. where this.supportsMultipleValues == true).
4931    * @function 
4932    * @name getInputValues
4933    * @memberOf gxe.control.InputBase#
4934    * @param {boolean} bInFeedbackMode true if the value is being requested as validation feedback
4935    * @return {Object[]} the input values
4936    */  
4937   getInputValues: function(bInFeedbackMode) {return null;},
4938 
4939   /**
4940    * Indicates whether or not the control supports the input of multiple values.
4941    * (simple wrapper for this.supportsMultipleValues)
4942    * @function 
4943    * @name getSupportsMultipleValues
4944    * @memberOf gxe.control.InputBase#
4945    * @return {boolean} true if multi-valued input is supported
4946    */ 
4947   getSupportsMultipleValues: function() {return this.supportsMultipleValues;},
4948 
4949   /**
4950    * Makes an HTML "input" element of type "text" supporting entry of "other" code values.
4951    * This function is useful when the user requires the ability to enter a value outside
4952    * of a coded domain.
4953    * @function 
4954    * @name makeOtherInputText
4955    * @memberOf gxe.control.InputBase#
4956    * @param {Object} cfgOption the JSON configuration object associated with the input control
4957    * @return {Element} the HTML "input" element
4958    */ 
4959   makeOtherInputText: function(cfgOption) {
4960     var elOther = document.createElement("input");
4961     elOther.setAttribute("type","text");
4962     gxe.cfg.forEachHtmlAttribute(cfgOption,dojo.hitch(this,function(cfgAttribute) {
4963       var sName = cfgAttribute.name.toLowerCase();
4964       var sValue = cfgAttribute.name.toLowerCase();
4965       if ((sName == "maxlength") || (sName != "size")) {
4966         elOther.setAttribute(sName,cfgAttribute.value);
4967       }
4968     }));
4969     return elOther;
4970   },
4971 
4972   /** Override gxe.control.onHtmlChildrenCreated() */
4973   onHtmlChildrenCreated: function(domProcessor,domNode) {
4974     this.inherited(arguments);
4975     var sTip = gxe.cfg.getGxeAttributeValue(this.cfgObject,"tip");
4976     if (sTip == null) sTip = gxe.cfg.getGxeAttributeValue(this.xmlNode.cfgObject,"tip");
4977     if (sTip == null) {
4978       var sMinOccurs = gxe.cfg.getGxeAttributeValue(this.xmlNode.cfgObject,"minOccurs");
4979       if (sMinOccurs == "$parent") {
4980         var pe = this.xmlNode.parentElement;
4981         if (pe != null) sTip = gxe.cfg.getGxeAttributeValue(pe.cfgObject,"tip");
4982       }
4983     }
4984     /*
4985     if (sTip == null) {
4986       var sType = gxe.cfg.getGxeAttributeValue(this.xmlNode.cfgObject,"valueType");
4987       if (sType != null) sType = dojo.trim(sType);
4988       if ((sType == "date") || (sType == "xs:date") || (sType == "xsd:date")) {
4989       }
4990     }
4991     */
4992     if (sTip != null) this.htmlElement.title = sTip;
4993   },
4994 
4995 
4996   /**
4997    * An event fired when input has changed.
4998    * @event 
4999    * @name onInputChanged
5000    * @memberOf gxe.control.InputBase#
5001    * @param {Object} inputControl the input control that initiated the change
5002    */ 
5003   onInputChanged: function(inputControl) {}
5004 
5005 });
5006 
5007 /**
5008  * @class Supports the input of multiple values through a delimited "textarea" control.
5009  * <br/><br/>Typically associated with:<br/>gpt/gxe/core/ui/InputDelimitedTextArea.xml<br/>
5010  * @name gxe.control.InputDelimitedTextArea
5011  * @extends gxe.control.InputBase
5012  * @property {boolean} supportsMultipleValues true
5013  * @property {String} delimiter the delimiter (default="," configuration attribute g:delimiter)
5014  */
5015 dojo.provide("gxe.control.InputDelimitedTextArea");
5016 dojo.declare("gxe.control.InputDelimitedTextArea",gxe.control.InputBase,{
5017   delimiter: ",",
5018   supportsMultipleValues: true,
5019 
5020   /** Override gxe.control.Control.initialize() */
5021   initialize: function(context,cfgObject) {
5022     this.inherited(arguments);
5023     var s = gxe.cfg.getGxeAttributeValue(cfgObject,"delimiter");
5024     if ((s != null) && (s.length > 0)) this.delimiter = s;
5025   },
5026 
5027   /** Override gxe.control.InputBase.getInputValues() */
5028   getInputValues: function(bInFeedbackMode) {
5029     if ((this.delimiter == null) || (this.delimiter.length == 0)) this.delimiter = ",";
5030     var values = new Array();
5031     if (this.htmlElement != null) this._mergeTokens(values,this.htmlElement.value);
5032     return values;
5033   },
5034 
5035   /**
5036    * Tokenizes a supplied value and merges the result into a supplied array.
5037    * <br/>The supplied value will be split using this.delimiter plus characters: \r and \n 
5038    * @function 
5039    * @name _mergeTokens
5040    * @memberOf gxe.control.InputDelimitedTextArea#
5041    * @param {Array} values the values into which the tokens will be merged
5042    * @param {String} sValue the value that will be tokenized (using this.delimiter)
5043    */
5044   _mergeTokens: function(values,sValue) {
5045     if (sValue != null) {
5046       sValue = sValue.replace(/(\r\n|\r|\n|\n\r)/g,this.delimiter);
5047       var tokens = sValue.split(this.delimiter);
5048       if (tokens != null) {
5049         for (var i=0; i<tokens.length; i++) {
5050           var sToken = dojo.trim(tokens[i]);
5051           if (sToken.length > 0) values.push(sToken);
5052         }
5053       }
5054     }
5055   },
5056 
5057   /** Override gxe.control.Control.onHtmlElementCreated() */
5058   onHtmlElementCreated: function(domProcessor,domNode) {
5059     this.xmlNode.setInputControl(this);
5060 
5061     // TODO set a default value?
5062 
5063     if ((this.delimiter == null) && (this.delimiter.length == 0)) this.delimiter = ",";
5064     var domValues = new Array();
5065     if ((domProcessor != null) && (domNode != null)) {
5066       if (this.xmlNode.nodeInfo.isIsoWrappedMultiValueList) {
5067         var domParentMatches = domProcessor.findMatchingChildElements(
5068             domNode.parentNode.parentNode,this.xmlNode.parentElement.cfgObject);
5069         if (domParentMatches != null) {
5070           for (var i=0; i<domParentMatches.length; i++) {
5071             var domMatches = domProcessor.findMatchingChildElements(
5072                 domParentMatches[i],this.xmlNode.cfgObject);
5073             if (domMatches != null) {
5074               for (var j=0; j<domMatches.length; j++) {
5075                 var domMatch = domMatches[j];
5076                 var sValue = domProcessor.getNodeText(domMatch);
5077                 if (sValue != null) domValues.push(sValue);
5078               }
5079             }
5080           }
5081         }
5082       } else {
5083         var domMatches = domProcessor.findMatchingChildElements(
5084             domNode.parentNode,this.xmlNode.cfgObject);
5085         if (domMatches != null) {
5086           for (var i=0; i<domMatches.length; i++) {
5087             var domMatch = domMatches[i];
5088             var sValue = domProcessor.getNodeText(domMatch);
5089             if (sValue != null) domValues.push(sValue);
5090           }
5091         }
5092       }
5093     }   
5094     
5095     if (domValues.length > 0) {
5096       var values = new Array();
5097       for (var i=0; i<domValues.length; i++) {
5098         this._mergeTokens(values,domValues[i]);
5099       }
5100       if (values.length > 0) {
5101         var sValues = "";
5102         for (var i=0; i<values.length; i++) {
5103           if (sValues.length > 0) sValues += this.delimiter;
5104           sValues += values[i];
5105         }
5106         this.htmlElement.value = sValues;
5107       }
5108     }    
5109     
5110     dojo.connect(this.htmlElement,"onchange",this,"fireInputChanged");
5111     dojo.connect(this.htmlElement,"onkeyup",this,"fireInputChangedOnKeyUp");
5112   }
5113 });
5114 
5115 /**
5116  * @class Supports the input of multiple values through a collection of check boxes.
5117  * <br/><br/>Typically associated with:<br/>gpt/gxe/core/ui/InputSelectMany.xml<br/>
5118  * @name gxe.control.InputSelectMany
5119  * @extends gxe.control.InputBase
5120  * @property {boolean} supportsMultipleValues true
5121  */
5122 dojo.provide("gxe.control.InputSelectMany");
5123 dojo.declare("gxe.control.InputSelectMany",gxe.control.InputBase,{
5124   _checkBoxes: null,
5125   supportsMultipleValues: true,
5126 
5127   /**
5128    * Appends a checkbox option to the parent HTML DOM element.
5129    * @function 
5130    * @name _appendCheckBox
5131    * @memberOf gxe.control.InputSelectMany#
5132    * @param {String} sLabel the label
5133    * @param {String} sValue a value associated with the checkbox
5134    * @param {boolean} bSelected true if the box should be checked
5135    */
5136   _appendCheckBox: function(sLabel,sValue,bSelected) {
5137     var elListItem = document.createElement("li");
5138     this.htmlElement.appendChild(elListItem);
5139     
5140     var sCollectionName = this.gxeId+"Options";
5141     var sOptionId = this.context.generateUniqueId();
5142     var elOption = document.createElement("input");
5143     if (this._dataName != null) {
5144       if(dojo.isIE <= 8) {
5145         elOption = document.createElement("<input name=\""+sCollectionName+"\"/>");
5146       } else {
5147         elOption.setAttribute("name",sCollectionName);
5148       }
5149     }
5150     elOption.setAttribute("type","checkbox");
5151     elOption.setAttribute("id",sOptionId);
5152     elOption.setAttribute("value",sValue); 
5153     elListItem.appendChild(elOption);
5154     if (bSelected) elOption.setAttribute("checked","checked");
5155     this._checkBoxes.push(elOption);
5156     
5157     var elLabel = document.createElement("label");
5158     elLabel.setAttribute("for",sOptionId);
5159     elLabel.appendChild(document.createTextNode(sLabel));
5160     elListItem.appendChild(elLabel);
5161     return elListItem;
5162   },
5163 
5164   /** Override gxe.control.Control.focus() */
5165   focus: function(subjectXmlNode) {
5166     this.inherited(arguments);
5167     if ((this._checkBoxes != null) && (this._checkBoxes.length > 0)) {
5168       this._checkBoxes[0].focus();
5169     }
5170   },
5171 
5172   /** Override gxe.control.InputBase.getInputValues() */
5173   getInputValues: function(bInFeedbackMode) {
5174     var values = new Array();
5175     if (this._checkBoxes != null) {
5176       var n = this._checkBoxes.length;
5177       for (var i=0; i<n; i++) {
5178         var elCheckBox = this._checkBoxes[i];
5179         if (elCheckBox.checked) {
5180           var oValue = elCheckBox.value;
5181           var elOther = elCheckBox.gxeOtherInputText;
5182           if (elOther != null) {
5183             var sValue = dojo.trim(elOther.value);
5184             if (sValue != elOther.value) elOther.value = sValue;
5185             if (sValue.length > 0) oValue = sValue;
5186             else oValue = null;
5187           }
5188           if (oValue != null) values.push(oValue);
5189         }
5190       }
5191     }
5192     return values;
5193   },
5194 
5195   /** Override gxe.control.Control.onHtmlElementCreated() */
5196   onHtmlElementCreated: function(domProcessor,domNode) {
5197     this.xmlNode.setInputControl(this);
5198     this._checkBoxes = new Array();
5199     var cfgInput = this.cfgObject;
5200 
5201     var otherElements = new Array();
5202     var otherCheckBoxes = new Array();
5203     var knownValues = new Array();
5204     var bUseDomValuesForSelected = (domProcessor != null);
5205     var domValues = new Array();
5206     if ((domProcessor != null) && (domNode != null)) {
5207       if (this.xmlNode.nodeInfo.isIsoWrappedMultiValueList) {
5208         var domParentMatches = domProcessor.findMatchingChildElements(
5209             domNode.parentNode.parentNode,this.xmlNode.parentElement.cfgObject);
5210         if (domParentMatches != null) {
5211           for (var i=0; i<domParentMatches.length; i++) {
5212             var domMatches = domProcessor.findMatchingChildElements(
5213                 domParentMatches[i],this.xmlNode.cfgObject);
5214             if (domMatches != null) {
5215               for (var j=0; j<domMatches.length; j++) {
5216                 var domMatch = domMatches[j];
5217                 var sValue = domProcessor.getNodeText(domMatch);
5218                 if (sValue != null) domValues.push(sValue);
5219               }
5220             }
5221           }
5222         }
5223       } else {
5224         var domMatches = domProcessor.findMatchingChildElements(
5225             domNode.parentNode,this.xmlNode.cfgObject);
5226         if (domMatches != null) {
5227           for (var i=0; i<domMatches.length; i++) {
5228             var domMatch = domMatches[i];
5229             var sValue = domProcessor.getNodeText(domMatch);
5230             if (sValue != null) domValues.push(sValue);
5231           }
5232         }
5233       }
5234     }
5235 
5236     var cfgOptions = gxe.cfg.findChild(this.cfgObject,gxe.cfg.uriGxe,"options");
5237     if (cfgOptions != null) {
5238       gxe.cfg.forEachChild(cfgOptions,gxe.cfg.uriGxe,"option",dojo.hitch(this,function(cfgOption) {
5239         var sLabel = gxe.cfg.getGxeAttributeValue(cfgOption,"label");
5240         var sValue =  gxe.cfg.getGxeAttributeValue(cfgOption,"value");
5241         var sAlias =  gxe.cfg.getGxeAttributeValue(cfgOption,"alias");
5242         var sSelected = gxe.cfg.getGxeAttributeValue(cfgOption,"selected");
5243         var bSelected = false;
5244         if (!bUseDomValuesForSelected) {
5245           bSelected = (sSelected == "true");
5246         } else {
5247           knownValues.push(sValue);
5248           for (var i=0; i<domValues.length; i++) {
5249             if (sValue == domValues[i]) {
5250               bSelected = true;
5251               break;
5252             } else if (sAlias != null) {
5253               if (sAlias == domValues[i]) {
5254                 bSelected = true;
5255                 break;
5256               }
5257             }
5258           }
5259         }
5260         var elListItem = this._appendCheckBox(sLabel,sValue,bSelected);
5261         var elCheckBox = this._checkBoxes[this._checkBoxes.length - 1];
5262         var sOther = gxe.cfg.getGxeAttributeValue(cfgOption,"isOther");
5263         if ((sOther != null) && (sOther == "true")) {
5264           var elOther = this.makeOtherInputText(cfgOption);
5265           elCheckBox.gxeOtherInputText = elOther;
5266           elListItem.appendChild(elOther);
5267           if (bUseDomValuesForSelected) {
5268             otherElements.push(elOther);
5269             otherCheckBoxes.push(elCheckBox);
5270           }
5271           dojo.connect(elOther,"onchange",this,"fireInputChanged");
5272           dojo.connect(elOther,"onkeyup",this,"fireInputChangedOnKeyUp");
5273         }
5274         dojo.connect(elCheckBox,"onchange",this,"fireInputChanged");
5275       }));
5276     }
5277 
5278     if (bUseDomValuesForSelected && (domValues.length > 0) && (otherElements.length > 0)) {
5279       var unknownValues = new Array();
5280       for (var i=0; i<domValues.length; i++) {
5281         var domValue = domValues[i];
5282         var bKnown = false;
5283         for (var j=0; j<knownValues.length; j++) {
5284           if (domValue == knownValues[j]) {bKnown = true;break;}
5285         }
5286         if (!bKnown) unknownValues.push(domValue);
5287       }
5288       for (var i=0; i<unknownValues.length; i++) {
5289         if (otherElements.length > i) {
5290           otherElements[i].value = unknownValues[i];
5291           otherCheckBoxes[i].checked = true;
5292         }
5293       }
5294     }
5295   }
5296 
5297 });
5298 
5299 /**
5300  * @class Supports the input of a single value through a drop down list ("select").
5301  * <br/><br/>Typically associated with:<br/>gpt/gxe/core/ui/InputSelectOne.xml<br/>
5302  * @name gxe.control.InputSelectOne
5303  * @extends gxe.control.InputBase
5304  * @property {boolean} supportsMultipleValues false
5305  * @property {Element} _htmlOther reference to the "other" code input text box
5306  */
5307 dojo.provide("gxe.control.InputSelectOne");
5308 dojo.declare("gxe.control.InputSelectOne",gxe.control.InputBase,{
5309   _htmlOther: null,
5310   supportsMultipleValues: false,
5311 
5312   /** Override gxe.control.InputBase.getInputValue() */
5313   getInputValue: function(bInFeedbackMode) {
5314     if ((this.htmlElement != null) && (this.htmlElement.selectedIndex != null) &&
5315         (this.htmlElement.selectedIndex >= 0)) {
5316       var elOptions = this.htmlElement.options;
5317       var elOption = elOptions[this.htmlElement.selectedIndex];
5318       if ((this._htmlOther != null) && (elOption.value == this._htmlOther.gxeOptionValue)) {
5319         var sValue = dojo.trim(this._htmlOther.value);
5320         if (!bInFeedbackMode) {
5321           if (sValue != this._htmlOther.value) this._htmlOther.value = sValue;
5322         }
5323         if (sValue.length > 0) return sValue;
5324       } else {
5325         return elOption.value;
5326       }
5327     }
5328     return null;
5329   },
5330 
5331   /**
5332    * Catches "onchange" events for the HTML "select" element.
5333    * <br/>This method triggers the firing of the inputChanged() event.
5334    * <br/>If applicable, this method toggles the display of the 
5335    * "other" code input text box.
5336    * @function 
5337    * @name _onChange
5338    * @memberOf gxe.control.InputSelectOne#
5339    * @param {Event} e the underlying browser event
5340    */
5341   _onChange: function(e) {
5342     if (this._htmlOther != null) {
5343       var elOption = this.htmlElement.options[this.htmlElement.selectedIndex];
5344       if (elOption.value == this._htmlOther.gxeOptionValue) {
5345         this._htmlOther.style.display = "inline";
5346       } else {
5347         this._htmlOther.style.display = "none";
5348       }
5349     }
5350     this.fireInputChanged(e);
5351   },
5352 
5353   /** Override gxe.control.Control.onHtmlElementCreated() */
5354   onHtmlElementCreated: function(domProcessor,domNode) {
5355     this.xmlNode.setInputControl(this);
5356 
5357     var sDomValue = null;
5358     var bUseDomValueForSelected = (domProcessor != null);
5359     if ((domProcessor != null) && (domNode != null)) {
5360       sDomValue = domProcessor.getNodeText(domNode);
5361     }
5362     var elOptions = this.htmlElement.options;
5363     var bFoundSelected = false;
5364     var nIndex = -1;
5365     var cfgOptions = gxe.cfg.findChild(this.cfgObject,gxe.cfg.uriGxe,"options");
5366     
5367     if (cfgOptions != null) {
5368       gxe.cfg.forEachChild(cfgOptions,gxe.cfg.uriGxe,"option",dojo.hitch(this,function(cfgOption) {
5369         nIndex++;
5370         var sLabel = gxe.cfg.getGxeAttributeValue(cfgOption,"label");
5371         var sValue = gxe.cfg.getGxeAttributeValue(cfgOption,"value");
5372         var sAlias =  gxe.cfg.getGxeAttributeValue(cfgOption,"alias");
5373         var bSelected = false;
5374         
5375         if (!bFoundSelected) {
5376           if (!bUseDomValueForSelected) {
5377             var sSelected = gxe.cfg.getGxeAttributeValue(cfgOption,"selected");
5378             bSelected = (sSelected == "true");
5379           } else {
5380             bSelected = (sValue == sDomValue);
5381             if (!bSelected && (sAlias != null)) {
5382               bSelected = (sAlias == sDomValue);
5383             }
5384           }
5385           if (bSelected) bFoundSelected = true;
5386         }
5387         
5388         var elOption = new Option(sLabel,sValue,bSelected,bSelected);
5389         elOptions[elOptions.length] = elOption;
5390   
5391         if (this._htmlOther == null) {
5392           var sOther = gxe.cfg.getGxeAttributeValue(cfgOption,"isOther");
5393           if ((sOther != null) && (sOther == "true")) {
5394             this._htmlOther = this.makeOtherInputText(cfgOption);
5395             this._htmlOther.gxeOptionValue = sValue;
5396             this._htmlOther.gxeOptionIndex = nIndex;
5397             if (!bSelected) this._htmlOther.style.display = "none";
5398             this.htmlElement.parentNode.appendChild(this._htmlOther);
5399           }
5400         }
5401       }));
5402     }
5403     
5404     if (!bFoundSelected && (elOptions.length > 0)) {
5405       var nSelectedIndex = 0;
5406       if (bUseDomValueForSelected && (sDomValue != null) && (this._htmlOther != null)) {
5407         nSelectedIndex = this._htmlOther.gxeOptionIndex;
5408         this._htmlOther.value = sDomValue;
5409       }
5410       this.htmlElement.selectedIndex = nSelectedIndex;
5411       this._onChange();
5412     }
5413     dojo.connect(this.htmlElement,"onchange",this,"_onChange");
5414     if (this._htmlOther != null) {
5415       dojo.connect(this._htmlOther,"onchange",this,"fireInputChanged");
5416       dojo.connect(this._htmlOther,"onkeyup",this,"fireInputChangedOnKeyUp");
5417     }
5418   }
5419   
5420 });
5421 
5422 /**
5423  * @class Supports the input of a single value through a text box ("input" type="text").
5424  * <br/><br/>Typically associated with:<br/>gpt/gxe/core/ui/InputText.xml<br/>
5425  * @name gxe.control.InputText
5426  * @extends gxe.control.InputBase
5427  * @property {boolean} supportsMultipleValues false
5428  */
5429 dojo.provide("gxe.control.InputText");
5430 dojo.declare("gxe.control.InputText",gxe.control.InputBase,{
5431   supportsMultipleValues: false,
5432 
5433   /** Override gxe.control.InputBase.getInputValue() */
5434   getInputValue: function(bInFeedbackMode) {
5435     if ((this.htmlElement != null) && (this.htmlElement.value != null)) {
5436       var sValue = dojo.trim(this.htmlElement.value);
5437       if (!bInFeedbackMode) {
5438         if (sValue != this.htmlElement.value) this.htmlElement.value = sValue;
5439       }
5440       if (sValue.length > 0) return sValue;
5441     }
5442     return null;
5443   },
5444 
5445   /** Override gxe.control.Control.importHtmlAttributes() */
5446   importHtmlAttributes: function(cfgObject) {
5447     this.inherited(arguments);
5448     this.htmlTag = "input";
5449     this.htmlAttributes.set("type","text");
5450   },
5451 
5452   /** Override gxe.control.Control.onHtmlElementCreated() */
5453   onHtmlElementCreated: function(domProcessor,domNode) {
5454     this.xmlNode.setInputControl(this);
5455     var sValue = gxe.cfg.getGxeAttributeValue(this.cfgObject,"fixedValue");
5456     if (sValue == null) {
5457       sValue = gxe.cfg.getGxeAttributeValue(this.xmlNode.cfgObject,"fixedValue");
5458     }
5459     if (sValue == null) {
5460       if ((domProcessor != null) && (domNode != null)) {
5461         sValue = domProcessor.getNodeText(domNode);
5462       } else if (domProcessor == null) {
5463         sValue = gxe.cfg.getGxeAttributeValue(this.cfgObject,"value");
5464         if (sValue == null) sValue = gxe.cfg.getGxeAttributeValue(this.xmlNode.cfgObject,"value");
5465       }
5466     }
5467     if (sValue != null) {
5468       var sType = gxe.cfg.getGxeAttributeValue(this.xmlNode.cfgObject,"valueType");
5469       if (sType != null) sType = dojo.trim(sType);
5470       if (sType == "fgdc:date") sValue = sValue.replace(/-/g,"");
5471       this.htmlElement.value = sValue;
5472     }
5473     dojo.connect(this.htmlElement,"onchange",this,"fireInputChanged");
5474     dojo.connect(this.htmlElement,"onkeyup",this,"fireInputChangedOnKeyUp");
5475   }
5476   
5477 });
5478 
5479 /**
5480  * @class Supports the input of a single value through a "textarea" control.
5481  * <br/><br/>Typically associated with:<br/>gpt/gxe/core/ui/InputTextArea.xml<br/>
5482  * @name gxe.control.InputTextArea
5483  * @extends gxe.control.InputBase
5484  * @property {boolean} supportsMultipleValues false
5485  */
5486 dojo.provide("gxe.control.InputTextArea");
5487 dojo.declare("gxe.control.InputTextArea",gxe.control.InputBase,{
5488   supportsMultipleValues: false,
5489 
5490   /** Override gxe.control.InputBase.getInputValue() */
5491   getInputValue: function(bInFeedbackMode) {
5492     if ((this.htmlElement != null) && (this.htmlElement.value != null)) {
5493       var sValue = dojo.trim(this.htmlElement.value);
5494       if (!bInFeedbackMode) {
5495         if (sValue != this.htmlElement.value) this.htmlElement.value = sValue;
5496       }
5497       if (sValue.length > 0) return sValue;
5498     }
5499     return null;
5500   },
5501 
5502   /** Override gxe.control.Control.onHtmlElementCreated() */
5503   onHtmlElementCreated: function(domProcessor,domNode) {
5504     this.xmlNode.setInputControl(this);
5505     var sValue = gxe.cfg.getGxeAttributeValue(this.cfgObject,"fixedValue");
5506     if (sValue == null) {
5507       sValue = gxe.cfg.getGxeAttributeValue(this.xmlNode.cfgObject,"fixedValue");
5508     }
5509     if (sValue == null) {
5510       if ((domProcessor != null) && (domNode != null)) {
5511         sValue = domProcessor.getNodeText(domNode);
5512       } else if (domProcessor == null) {
5513         sValue = gxe.cfg.getGxeAttributeValue(this.cfgObject,"value");
5514         if (sValue == null) {
5515           sValue = gxe.cfg.getGxeAttributeValue(this.xmlNode.cfgObject,"value");
5516         }
5517       }
5518     }
5519     if (sValue != null) this.htmlElement.value = sValue;
5520     dojo.connect(this.htmlElement,"onchange",this,"fireInputChanged");
5521     dojo.connect(this.htmlElement,"onkeyup",this,"fireInputChangedOnKeyUp");
5522   }
5523   
5524 });
5525 
5526 /**
5527  * @class Provides a popup dialog for GEMET keyword selection.
5528  * <br/><br/>Requires class Gemet from library [wepabb]/catalog/js/v[latest]/gemet.js.
5529  * @name gxe.control.InputGemetKeyword
5530  * @extends gxe.control.InputText
5531  * @property {boolean} supportsMultipleValues false
5532  * @property {Element} _gemetTool reference to the anchor element taht launches the GEMET dialog
5533  */
5534 dojo.provide("gxe.control.InputGemetKeyword");
5535 dojo.declare("gxe.control.InputGemetKeyword",gxe.control.InputText,{
5536   supportsMultipleValues: false,
5537   _gemetTool: null,
5538   
5539   /**
5540    * Utility to enable/disable the anchor that launches the GEMET dialog.
5541    * <br/>The anchor is enabled when text has been entered into the associated input text box.
5542    * @function 
5543    * @name _enableDisable
5544    * @memberOf gxe.control.InputGemetKeyword#
5545    */
5546   _enableDisable: function() {
5547     var bOk = false;
5548     if (this._gemetTool != null) bOk = (dojo.trim(this.htmlElement.value).length > 0);
5549     if (bOk) this._gemetTool.style.display = "inline";
5550     else this._gemetTool.style.display = "none";
5551   },
5552   
5553   /** Override gxe.control.InputBase.onInputChanged() */
5554   onInputChanged: function() {this._enableDisable();},
5555 
5556   /** Override gxe.control.Control.onHtmlChildrenCreated() */
5557   onHtmlChildrenCreated: function(domProcessor,domNode) {
5558     this.inherited(arguments);
5559     var elLink = document.createElement("a");
5560     elLink.setAttribute("href","javascript:void(0);");
5561     elLink.className = "gxeInputTool";
5562     elLink.appendChild(document.createTextNode(this.context.getI18NString("gemet.find")));
5563     this.htmlElement.parentNode.appendChild(elLink);
5564     
5565     var gemet = new Gemet();
5566     gemet.lblHelp = this.context.getI18NString("gemet.dialogHelp");
5567     gemet.lblDialogTitle = this.context.getI18NString("gemet.dialogTitle");
5568     gemet.lblWordNotFound = this.context.getI18NString("gemet.wordNotFound");
5569     gemet.lblCancel = this.context.getI18NString("gemet.cancel");
5570     gemet.lblOk = this.context.getI18NString("gemet.ok");
5571     gemet.lblErrorKeywordEmpty = this.context.getI18NString("gemet.lblErrorKeywordEmpty");
5572     gemet.lblLoadingMessage = this.context.getI18NString("gemet.connectingMessage");
5573     gemet.proxyUrl = this.context.contextPath+"/catalog/download/proxy.jsp?{0}";
5574     gemet.imgLoading = this.context.contextPath+"/catalog/images/loading.gif";
5575     
5576     dojo.connect(elLink,"onclick",this,dojo.hitch(this,function(e) {
5577       var sValue = dojo.trim(this.htmlElement.value);
5578       if (sValue.length > 0) {
5579         gemet.findConcepts(sValue,null,dojo.hitch(this,function(sGemetText) {
5580           if ((typeof(sGemetText) != "undefined") && (sGemetText != null)) {
5581             sGemetText = dojo.trim(sGemetText);
5582             if (sGemetText.length > 0) {
5583               this.htmlElement.value = sGemetText;
5584               //this.htmlElement.disabled = true;
5585             }
5586           }
5587         })); 
5588       }
5589     }));
5590     
5591     this._gemetTool = elLink;
5592     this._enableDisable();
5593   }
5594   
5595 });
5596 
5597 /**
5598  * @class Provides an interactive map control for the definition of a bounding spatial envelope.
5599  * <br/><br/>Requires library [wepabb]/catalog/js/v[latest]/gpt.js.
5600  * @name gxe.control.Map
5601  * @extends gxe.control.Control
5602  */
5603 dojo.provide("gxe.control.Map");
5604 dojo.declare("gxe.control.Map",gxe.control.Control,{
5605   gptMap: null,
5606   gptMapToolbar: null,
5607   gptLocator: null,
5608   gptInpEnv: null,
5609   _wasMapInitialized: false,
5610   
5611   /**
5612    * Appends an image button to a map toolbar.
5613    * @function 
5614    * @name _appendImageButton
5615    * @memberOf gxe.control.Map#
5616    * @param {Element} elToolBar the parent HTML element for the toolbar
5617    * @param {String} sId the id for the new img element
5618    * @param {String} sSrc the src for the new img element
5619    * @param {String} sTip the tool tip for the new img element
5620    */
5621   _appendImageButton: function(elToolBar,sId,sSrc,sTip) {
5622     var el = document.createElement("img");
5623     el.id = sId;
5624     el.alt = sTip;
5625     el.title = sTip;
5626     el.src = sSrc;
5627     elToolBar.appendChild(el);
5628     return el;
5629   },
5630   
5631   /** Override gxe.control.Control.build() */
5632   build: function(htmlParentElement,domProcessor,domNode) {
5633     this.inherited(arguments);
5634     this.htmlElement.style.display = "none";
5635     
5636     var idPfx = this.gxeId+"_";
5637     var sImages = this.context.contextPath+"/catalog/images/";
5638     var elImg;
5639     
5640     var elUseMap = document.createElement("a");
5641     elUseMap.setAttribute("href","javascript:void(0);");
5642     elUseMap.className = "gxeUseMapButton";
5643     elUseMap.appendChild(document.createTextNode(
5644         this.context.getI18NString("map.useMap")));
5645     this.htmlElement.parentNode.insertBefore(elUseMap,this.htmlElement);
5646     
5647     var elToolBar = document.createElement("div");
5648     elToolBar.id = idPfx+"mapToolbar";
5649     elToolBar.className = "mapToolbar";
5650     this.htmlElement.appendChild(elToolBar);
5651     
5652     elImg = this._appendImageButton(elToolBar,idPfx+"mapButton-zoomToWorld",
5653         sImages+"btn-zoomToWorld-off.gif",
5654         this.context.getI18NString("map.zoomToWorld"));
5655     
5656     elImg = this._appendImageButton(elToolBar,idPfx+"mapButton-zoomToInputEnvelope",
5657         sImages+"btn-zoomToInputEnvelope-off.gif",
5658         this.context.getI18NString("map.zoomToInputEnvelope"));
5659     
5660     elImg = this._appendImageButton(elToolBar,idPfx+"mapTool-drawInputEnvelope",
5661         sImages+"btn-drawInputEnvelope-off.gif",
5662         this.context.getI18NString("map.drawInputEnvelope"));
5663     elImg.className = "firstTool";
5664     
5665     elImg = this._appendImageButton(elToolBar,idPfx+"mapTool-deactivate",
5666         sImages+"btn-deactivate-off.gif",
5667         this.context.getI18NString("map.deactivate"));
5668     
5669     var elLocatorInput = document.createElement("input");
5670     elLocatorInput.setAttribute("type","text");
5671     elLocatorInput.setAttribute("maxLength","1024");
5672     elLocatorInput.id = idPfx+"mapInput-locate";
5673     elLocatorInput.className = "locatorInput";
5674     dojo.connect(elLocatorInput,"onkeypress",this,"onLocatorKeyPress");
5675     elToolBar.appendChild(elLocatorInput);
5676     
5677     elImg = this._appendImageButton(elToolBar,idPfx+"mapButton-locate",
5678         sImages+"btn-locate-off.gif",
5679         this.context.getI18NString("map.locate"));
5680         
5681     var elCandidates = document.createElement("div");
5682     elCandidates.id = idPfx+"locatorCandidates";
5683     elCandidates.className = "locatorCandidates";
5684     this.htmlElement.appendChild(elCandidates);
5685     
5686     var elMapContainer = document.createElement("div");
5687     elMapContainer.className = "gxeMapContainer";
5688     this.htmlElement.appendChild(elMapContainer);
5689     
5690     var elMapCanvas = document.createElement("div");
5691     elMapCanvas.id = idPfx+"interactiveMap";
5692     elMapCanvas.className = "gxeMapCanvas";
5693     elMapContainer.appendChild(elMapCanvas);
5694     
5695     var config = this.context.gptMapConfig;
5696     config.mapElementId = idPfx+"interactiveMap";
5697     config.mapToolName = "drawInputEnvelope";
5698     config.mapToolbarId = idPfx+"mapToolbar";
5699     config.locatorInputId =  idPfx+"mapInput-locate";
5700     config.locatorCandidatesId = idPfx+"locatorCandidates";
5701     
5702     var el = this.htmlElement.parentNode;
5703     while (el != null) {
5704       var nl = dojo.query("[gxeMapPart='envelope_container']",el);
5705       if (nl.length == 1) {
5706         var elC = nl[0];
5707         var nlN = dojo.query("[gxeMapPart='envelope_north']",elC);
5708         var nlS = dojo.query("[gxeMapPart='envelope_south']",elC);
5709         var nlE = dojo.query("[gxeMapPart='envelope_east']",elC);
5710         var nlW = dojo.query("[gxeMapPart='envelope_west']",elC);
5711         if ((nlN.length == 1) && (nlS.length == 1) &&
5712             (nlE.length == 1) && (nlW.length == 1)) {
5713           config.inputEnvelopeXMinId = nlW[0].id;
5714           config.inputEnvelopeYMinId = nlS[0].id;
5715           config.inputEnvelopeXMaxId = nlE[0].id;
5716           config.inputEnvelopeYMaxId = nlN[0].id;
5717         }
5718         break;
5719       }
5720       el = el.parentNode;
5721     }
5722     
5723     dojo.connect(elUseMap,"onclick",this,"_useMap");
5724   },
5725   
5726   /**
5727    * Responds to a key press from the input text box associated with the locator (i.e. gazateer).
5728    * @function 
5729    * @name onLocatorKeyPress
5730    * @memberOf gxe.control.Map#
5731    * @param {Event} e the underlying browser event
5732    */
5733   onLocatorKeyPress: function(e) {
5734     if (!e) e = window.event;
5735     if (e) {
5736       var nKey = (e.keyCode) ? e.keyCode : e.which;
5737       if (nKey == 13) {
5738         if (this.gptLocator != null) this.gptLocator.locate();
5739         return false;
5740       }
5741     } 
5742     return true;
5743   },
5744   
5745   /**
5746    * Responds to a click of a map related button.
5747    * @function 
5748    * @name onMapButtonClicked
5749    * @memberOf gxe.control.Map#
5750    * @param {String} sButtonName the map button name
5751    */
5752   onMapButtonClicked: function(sButtonName) {
5753     if (sButtonName == "zoomToWorld") {
5754       if (this.gptMap != null) this.gptMap.zoomToWorld();
5755     } else if (sButtonName == "zoomToInputEnvelope") {
5756       if (this.gptInpEnv != null) this.gptInpEnv.zoomToInputEnvelope();
5757     } else if (sButtonName == "locate") {
5758       if (this.gptLocator != null) this.gptLocator.locate();
5759     }
5760   },
5761   
5762   /**
5763    * Responds following the load of the underlying map control.
5764    * @function 
5765    * @name onMapLoaded
5766    * @memberOf gxe.control.Map#
5767    */
5768   onMapLoaded: function() {
5769     if (this.gptInpEnv != null) this.gptInpEnv.highlightInputEnvelope();
5770   },
5771   
5772   /**
5773    * Repositions the underlying map control.
5774    * @function 
5775    * @name repositionMap
5776    * @memberOf gxe.control.Map#
5777    */
5778   repositionMap: function() {
5779     if (this.gptMap != null) this.gptMap.reposition();
5780   },
5781   
5782   /**
5783    * Handles a "Use Map" click.
5784    * @function 
5785    * @name _useMap
5786    * @memberOf gxe.control.Map#
5787    * @param {Event} e the underlying browser event
5788    */
5789   _useMap: function(e) {
5790     if (this._wasMapInitialized) {
5791       this.repositionMap();
5792       return;
5793     }
5794     
5795     this.htmlElement.style.display = "block";
5796     var config = this.context.gptMapConfig;
5797     
5798     this.gptMap = new GptMap();
5799     dojo.connect(this.gptMap,"onMapLoaded",this,"onMapLoaded");
5800   
5801     this.gptInpEnv = new GptInputEnvelope();
5802     this.gptInpEnv.initialize(config,this.gptMap);
5803     
5804     this.gptMap.initialize(config);
5805     this._wasMapInitialized = true;
5806     
5807     this.gptMapToolbar = new GptMapToolbar();
5808     dojo.connect(this.gptMapToolbar,"onMapButtonClicked",this,"onMapButtonClicked");
5809     dojo.connect(this.gptMapToolbar,"onDrawInputEnvelope",this.gptInpEnv,"onDrawInputEnvelope");
5810     this.gptMapToolbar.initialize(config,this.gptMap);
5811     
5812     this.gptLocator = new GptLocator();
5813     this.gptLocator.initialize(config,this.gptMap);
5814     
5815     dojo.connect(window,"onresize",this,"repositionMap");
5816     dojo.connect(window,"onscroll",this,"repositionMap");
5817   }
5818   
5819 });
5820 
5821 /**
5822  * @class Provides a popup dialog for the selection of keywords.
5823  * @name fgdc.control.KeywordSelector
5824  * @extends gxe.control.Control
5825  */
5826 dojo.provide("fgdc.control.KeywordSelector");
5827 dojo.declare("fgdc.control.KeywordSelector",gxe.control.Control,{
5828   
5829   /**
5830    * Appends a checkbox option to the popup dialog.
5831    * @function 
5832    * @name _appendCheckBox
5833    * @memberOf fgdc.control.KeywordSelector#
5834    * @param {Element} el the parent HTML element
5835    * @param {String} sLabel the label
5836    * @param {String} sValue a value associated with the checkbox
5837    * @param {boolean} bSelected true if the box should be checked
5838    */
5839   _appendCheckBox: function(el,sLabel,sValue,bSelected) {
5840     var sOptionId = this.context.generateUniqueId();
5841     var elListItem = document.createElement("li");
5842     el.appendChild(elListItem);
5843     var elOption = document.createElement("input");
5844     elOption.setAttribute("type","checkbox");
5845     elOption.setAttribute("id",sOptionId);
5846     elOption.setAttribute("value",sValue); 
5847     elListItem.appendChild(elOption);
5848     if (bSelected) elOption.setAttribute("checked","checked");
5849     
5850     var elLabel = document.createElement("label");
5851     elLabel.setAttribute("for",sOptionId);
5852     elLabel.appendChild(document.createTextNode(sLabel));
5853     elListItem.appendChild(elLabel);
5854     return elListItem;
5855   },
5856   
5857   /** Override gxe.control.Control.build() */
5858   build: function(htmlParentElement,domProcessor,domNode) {
5859     this.inherited(arguments);
5860     
5861     if (this.htmlElement != null) {
5862       dojo.connect(this.htmlElement,"onclick",this,dojo.hitch(this,function(e) {
5863         
5864         var delimitedTextArea = null;
5865         var aCurrentValues = null;
5866         var siblings = this.xmlNode.parentElement.children;
5867         for (var i=0;i<siblings.getLength();i++) {
5868           var sibling = siblings.getItem(i);
5869           if (sibling.getInputControl() != null) {
5870             if (sibling.getInputControl().getSupportsMultipleValues()) {
5871               var s = sibling.nodeInfo.localName;
5872               if (s.length > 3) {
5873                 s = s.substring(s.length-3);
5874                 if (s == "key") {
5875                   delimitedTextArea = sibling.getInputControl();
5876                   aCurrentValues = delimitedTextArea.getInputValues(true);
5877                   break;
5878                 }
5879               }
5880             }
5881           }
5882         }
5883 
5884         if (delimitedTextArea != null) {
5885           var elListDiv = document.createElement("div");
5886           dojo.style(elListDiv,{margin:"5px",padding:"5px",border:"1px solid #CCC"});
5887           var elList = document.createElement("ul");
5888           elList.className = "gxeSelectMany";
5889           elListDiv.appendChild(elList);
5890           var cfgOptions = gxe.cfg.findChild(this.cfgObject,gxe.cfg.uriGxe,"options");
5891           if (cfgOptions != null) {
5892             gxe.cfg.forEachChild(cfgOptions,gxe.cfg.uriGxe,"option",dojo.hitch(this,function(cfgOption) {
5893               var sLabel = gxe.cfg.getGxeAttributeValue(cfgOption,"label");
5894               var sValue =  gxe.cfg.getGxeAttributeValue(cfgOption,"value");
5895               var sAlias =  gxe.cfg.getGxeAttributeValue(cfgOption,"alias");
5896               var sSelected = gxe.cfg.getGxeAttributeValue(cfgOption,"selected");
5897               var bSelected = false;
5898               if (aCurrentValues != null) {
5899                 for (var iCur=0; iCur<aCurrentValues.length; iCur++) {
5900                   if (aCurrentValues[iCur] == sValue) {
5901                     bSelected = true;
5902                     break;
5903                   }
5904                 }
5905               }
5906               this._appendCheckBox(elList,sLabel,sValue,bSelected);
5907             }));
5908           }
5909                     
5910           var sTitle = this.htmlTextContent;
5911           if (sTitle == null) sTitle = "?Select";
5912           var dialog = new dijit.Dialog({
5913             title: sTitle,
5914             style: "display: none; border: 1px solid #000000; background: #FFFFFF;",
5915             autofocus: false
5916           });
5917           dojo.addClass(dialog.domNode,"tundra");
5918           dialog.domNode.appendChild(elListDiv);
5919           
5920           var elButtonDiv = document.createElement("div");
5921           dojo.style(elButtonDiv,{marginLeft:"auto",marginRight:"auto",width:"50%",padding:"5px"});
5922           dialog.domNode.appendChild(elButtonDiv);
5923           var elOk = document.createElement("button");
5924           elOk.appendChild(document.createTextNode(this.context.getI18NString("dialog.ok")));
5925           elButtonDiv.appendChild(elOk);
5926           var elCancel = document.createElement("button");
5927           elCancel.appendChild(document.createTextNode(this.context.getI18NString("dialog.cancel")));
5928           elButtonDiv.appendChild(elCancel);
5929           
5930           dojo.connect(elOk,"onclick",this,dojo.hitch(this,function(e) {
5931             var sCheckedValues = "";
5932             dojo.query("[type='checkbox']",dialog.domNode).forEach(dojo.hitch(this,function(item) {
5933               if (item.checked) {
5934                 if (sCheckedValues.length > 0) sCheckedValues += delimitedTextArea.delimiter;
5935                 sCheckedValues += item.value;
5936               }
5937             }));
5938             var sThesaurus = gxe.cfg.getGxeAttributeValue(this.cfgObject,"thesaurus");
5939             if (sThesaurus == null) sThesaurus = "";
5940             this.xmlNode.getInputControl().htmlElement.value = sThesaurus;
5941             this.xmlNode.getInputControl().fireInputChanged();
5942             delimitedTextArea.htmlElement.value = sCheckedValues;
5943             delimitedTextArea.fireInputChanged();
5944             dialog.hide();
5945             dialog.destroy();
5946           }));
5947           
5948           dojo.connect(elCancel,"onclick",this,dojo.hitch(this,function(e) {
5949             dialog.hide();
5950             dialog.destroy();
5951           }));
5952           dialog.show(); 
5953         }
5954       }));
5955     }
5956   }
5957   
5958 });
5959 
5960 
5961 
5962 
5963