// global timeout for ajax calls
$.ajaxSetup({timeout: 10000});

// no indexOf support in IE
function contains(array, value) {
    for (var i=0; i<array.length; i++)  if (array[i]==value) return true;
    return false;
}

function removeChildren(element)
{
    while (element.firstChild) {
        element.removeChild(element.firstChild);
    }
}

function xmlEscape(str)
{
    if (str) {
        var escAmpRegEx = /&/g;
        var escLtRegEx = /</g;
        var escGtRegEx = />/g;
        var quotRegEx = /"/g;
        var aposRegEx = /'/g;
        var newlineRegEx = /\n/g;

        str = str.replace(escAmpRegEx, "&amp;");
        str = str.replace(escLtRegEx, "&lt;");
        str = str.replace(escGtRegEx, "&gt;");
        str = str.replace(quotRegEx, "&quot;");
        str = str.replace(aposRegEx, "&apos;");
        str = str.replace(newlineRegEx, "&#xA;");
    } else {
        str = "";
    }
    return str;
}

// used to be called super() but IE doesn't like that.
function superfn(o, args)
{
    if (args.callee.superclass) {
        args.callee.superclass.apply(o,args);
    }
}


/*----------------------------------------------------------------------*/

function Web2Field(args)
{
    this.saveXml = true;
    this.isLoading = false;
    this.validationUrl = null;
    this.children = [];
    for (var attr in args) {
        this[attr]=args[attr];
    }
    if (this.id!=null) {
        this.element = document.getElementById(this.id);
        if (this.element==null) {
            alert('cannot find '+this.id);
        }
    }
}

Web2Field.prototype.loadUrl = function Web2Field_loadUrl(url, onComplete /* opt */) 
{
    var me = this;
    this.onBusy();
    // use ajax instead of shortcuts to ensure busy is updated
    //$.get(url, function(data, status) {me.loadCallback(data, status);});
    $.ajax({type: "GET",
            url: url,
            //error: function(XMLHttpRequest, textStatus, errorThrown) {alert('Error saving: '+textStatus+' '+errorThrown);},
            success: function(data, textStatus) {me.loadXml(data.documentElement);},
            complete: function(){me.onIdle(); if (onComplete!=null) onComplete();}
    });
}

Web2Field.prototype.loadXml = function Web2Field_loadXml(node)
{
    if (node==null) {
        return;
    }

    if (typeof(node)!='object') {
        alert('Server returned error.');
        return;
    }

    if (node.nodeName=='error') {
        alert(node.firstChild.nodeValue);
        return;
    }

    if (node.nodeName=='redirect') {
        var url = node.getAttribute('url');
        window.location = url;
        return;
    }

    if (node.nodeName=='script') {
        var script = node.firstChild.nodeValue;
        eval(script);
        return;
    }

    this.isLoading = true; // distinguish onChange during loading from user changes
    this.clearWarning();
    this.setValue(node.getAttribute('value'));

    var warnings = $('>warning',node); // immediate children only //node.getElementsByTagName('warning');
    if (warnings.length>0) {
        var warning = warnings[0];
        node.removeChild(warning);
        this.setWarning(warning.firstChild.nodeValue);
    }

    var messages = $('>message',node);
    if (messages.length>0) {
        var message = messages[0];
        node.removeChild(message);
        alert(message.firstChild.nodeValue);
    }

    for (var i=0;i<node.childNodes.length;i++) {
        var child = node.childNodes[i];
        var tag = child.nodeName;

        if (tag=='field') {
            var name = child.getAttribute('name');
            var childObj = this.children[name];
            if (childObj!=null) {
                childObj.loadXml(child);
            } else {
                alert('child object '+name+' not found in '+this.id);
            }
        } else if (tag=='element') {
            var id = child.getAttribute('id');
            var html = child.firstChild.nodeValue;
            var element = document.getElementById(id);
            if (element!=null) {
                $(element).html(html);
            }
        } else if (tag=='redirect') {
            var url = child.getAttribute('url');
             window.location = url;
            return;
        }
    }
    this.isLoading = false;
}

Web2Field.prototype.setValue = function Web2Field_setValue(value)
{
    //alert('setting value of '+this.id+' to '+value);

    if (this.element!=null) {
        this.element.value = value;
    }
    this.onChange(value);
}

Web2Field.prototype.onChange = function Web2Field_onChange(value)
{
    // do nothing by default
}

Web2Field.prototype.validate = function Web2Field_validate()
{
    if (this.isLoading) return;  // don't revalidate if we are loading - prevent loop!
    this.clearWarning();
    var me = this;
    if (this.validationUrl!=null) {
        var xml = this.getXml();
        $.ajax({type: "POST",
                url: this.validationUrl,
                processData: false,
                data: xml,
                success: function(data, textStatus) {me.loadXml(data.documentElement);}
        });
    }
}

// create xml and post it to the specified URL
Web2Field.prototype.save = function Web2Field_save(url, onComplete)
{
    var me = this;
    var xml = this.getXml();
    this.onBusy();
    $.ajax({type: "POST",
            url: url,
            processData: false,
            data: xml,
            error: function(XMLHttpRequest, textStatus, errorThrown) {alert('Error saving: '+textStatus+" "+errorThrown);},
            success: function(data, textStatus) {me.loadXml(data.documentElement);me.onSaved()},
            complete: function(){me.onIdle(); if (onComplete!=null) onComplete();}
    });
}

Web2Field.prototype.onSaved = function Web2Field_onSaved()
{
    // do nothing by default
}

Web2Field.prototype.onUpdated = function Web2Field_onUpdated()
{
    // do nothing by default
}

Web2Field.prototype.onBusy = function Web2Field_onBusy()
{
    // do nothing by default
}

Web2Field.prototype.onIdle = function Web2Field_onIdle()
{
    // do nothing by default
}

Web2Field.prototype.getXml = function Web2Field_getXml(attrib)
{
    var xml = "";
    if (this.saveXml) {
        var xml = "<field";
        if (attrib==null) {
            attrib=[];
        }
        
        if (this.element && typeof(this.element.value)!='undefined') {
            attrib['value']=this.element.value;
        }
        
        for (var name in attrib) {
            value = attrib[name];
            xml+=' '+name+'="'+xmlEscape(value)+'"';
        }
        
        xml+='>';
        for (var name in this.children) {
            var child = this.children[name];
            xml+= child.getXml({name:name});
        }
        xml+="</field>";
    }
    return xml;
}

Web2Field.prototype.onLoad = function Web2Field_onLoad()
{
}

Web2Field.prototype.load = function Web2Field_load()
{
    this.onLoad();
    for (var name in this.children) {
        //alert('loading child '+name);
        this.children[name].load();
    }
}

Web2Field.prototype.setWarning = function Web2Field_setWarning(text)
{
    $(this.element).addClass('warning');
    var warnings = $('#'+this.id+'_warning');
    if (warnings.length>0) {
        var warning = warnings[0];
        $('.warningtext',warning).text(text);
        //if (textNodes[0]alert('n='+textNodes.length);
        //warning.firstChild.nodeValue = text;
        warnings.show();
    } else {
        alert('no warning field #'+this.id+'_warning');
    }
}

Web2Field.prototype.clearWarning = function Web2Field_clearWarning()
{
    $(this.element).removeClass('warning');
    $('#'+this.id+'_warning').hide();
}

/*----------------------------------------------------------------------*/
function Web2TextBox(args)
{
    superfn(this, arguments);
    if (this.element!=null) {
      var me = this;
      this.element.onchange = function(){me.onChange();};
    }
}

Web2TextBox.superclass = Web2Field;
Web2TextBox.prototype = new Web2Field();

/*----------------------------------------------------------------------*/
function Web2Html()
{
    superfn(this, arguments);
}

Web2Html.superclass = Web2Field;
Web2Html.prototype = new Web2Field();

Web2Html.prototype.setValue = function Web2Html_setValue(value)
{
    if (value=='') {
        $(this.element).hide();
    } else {
        $(this.element).html(value);
        $(this.element).show();
    }
}

/*----------------------------------------------------------------------*/

function Web2IntBox(args)
{
    superfn(this, arguments);
    var me = this;
    this.element.onkeyup= function(){this.value=this.value.replace(/\D/g,'');};
    this.element.onchange = function(){if (!this.disabled) {this.value=this.value.replace(/\D/g,'');} me.onChange();};
}
Web2IntBox.superclass = Web2TextBox;
Web2IntBox.prototype = new Web2TextBox();

/*----------------------------------------------------------------------*/

function Web2Date(args)
{
    superfn(this, arguments);
    $(this.element).datepicker({dateFormat: 'dd/mm/yy', firstDay:1}); // or use firstDay:1 for MTWTFSS
}
Web2Date.superclass = Web2TextBox;
Web2Date.prototype = new Web2TextBox();

/*----------------------------------------------------------------------*/
function Web2EmailAddress(args)
{
    superfn(this, arguments);
    // as of now, the longest tld's are 6 characters (.museum and .travel)
    this.re = /^([a-zA-Z0-9_\-\.]+@(([a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,6}))$/;
    if (this.element!=null) {
      var me = this;
      this.element.onchange = function(){me.checkValue();me.onChange();};
    }
}

Web2EmailAddress.superclass = Web2TextBox;
Web2EmailAddress.prototype = new Web2TextBox();

Web2EmailAddress.prototype.delayedCallback = function Web2EmailAddress_delayedCallback(callback)
{
    if (this.timer!=null) {
        cancelTimeout(this.timer);
    }
    alert(callback);
    this.timer = setTimeout(callback, this.delay);
}

Web2EmailAddress.prototype.checkValue = function Web2EmailAddress_checkValue()
{
    var value = this.element.value.replace(/^\s+|\s+$/g, ''); 
    this.element.value = value; // trimmed
    if (value!='' && !this.re.exec(value)) {
      this.setWarning('Not a valid email address.');
    } else {
      this.clearWarning();
    }
}

/*----------------------------------------------------------------------*/

function Web2TreeView(args)
{
    this.valueKey = 'id';
    this.closeAll = false;
    this.saveXml = false;  // don't save this
    this.closedNodes = [];
    superfn(this, arguments);
}

Web2TreeView.superclass = Web2Field;
Web2TreeView.prototype = new Web2Field();

Web2TreeView.prototype.reload = function Web2TreeView_reload(url)
{
    var me = this;
    if (url==null) url=this.url;
    if (url!=null) {
        $.ajax({type: "GET",
            url: url,
            error: function(XMLHttpRequest, textStatus, errorThrown) {alert('Error loading ['+me.url+']: '+textStatus+' '+errorThrown);},
            success: function(data, textStatus) {me.loadXml(data.documentElement);},
            complete: function() {me.onIdle();}
        }); 
    }
}

Web2TreeView.prototype.loadXml = function Web2TreeView_loadXml(node)
{
    if (node.childNodes.length>0) {
        this.closed = this.getClosedIdList();
        removeChildren(this.element);
        this.buildTree(this.element, node);
        $(this.element).treeview();
        this.closeAll = false;  // reset closeall
    }
}

Web2TreeView.prototype.getClosedIdList = function Web2TreeView_getClosedIdList() 
{
    var closed = [];
    var nodes = $("li.expandable", this.element);
    for (var i=0;i<nodes.length;i++) {
        closed.push(nodes[i].uid);
    }
    return closed;
}

/*
Web2TreeView.prototype.close = function Web2TreeView_closeAll()
{
    var nodes = $("li.collapsable", this.element);
    for (var i=0;i<nodes.length;i++) {
        closed.push(nodes[i].uid);
    }
}
*/

Web2TreeView.prototype.buildTree = function Web2TreeView_buildTree(ulNode, xmlNode)
{
    for (var i=0;i<xmlNode.childNodes.length;i++) {
        var child = xmlNode.childNodes[i];
        var name = child.getAttribute('name');
        var id = child.getAttribute('id');  // id may or may not == value
        var uid = (id!==null) ? id : name;
        var liNode = document.createElement('li');
        var spanNode = document.createElement('span');
        spanNode.appendChild(document.createTextNode(name));
        liNode.uid = uid;  // used for saving open state
        if (this.closeAll || (this.closed!=null && contains(this.closed, uid))) {
            liNode.className = "closed";
        }

        var value = child.getAttribute(this.valueKey);
        if (value!==null) {
          spanNode.onclick = onClickClosure(this, value);
          $(spanNode).addClass("link");
        }
        liNode.appendChild(spanNode);
        if (child.childNodes.length > 0) {
            var childUl = document.createElement('ul');
            this.buildTree(childUl, child);
            liNode.appendChild(childUl);
        }
        ulNode.appendChild(liNode);
    }
}

Web2TreeView.prototype.onLoad = function Web2TreeView_onLoad()
{
    this.reload();
}

Web2TreeView.prototype.onClick = function Web2TreeView_onClick(value)
{
    alert('clicked on '+value); 
}

/*----------------------------------------------------------------------*/
/* remember one of these is created as the prototype for any subclass */
function Web2DropDown(args)
{
    this.valueKey = 'id';
    superfn(this, arguments);
    if (this.id!=null) {
        //alert('id='+this.id);    
        //alert('element='+this.element);
        var me = this;
        if (this.element==null) {
            alert('no element for '+this.id);
        }
        this.element.onchange = function() {me.onChange();};

        $('<table class="web2static hidden"><tr><td id="'+this.id+'_single"></td></tr></table>').insertAfter(this.element);
    }
    
}

Web2DropDown.superclass = Web2Field;
Web2DropDown.prototype = new Web2Field();

Web2DropDown.prototype.reload = function Web2DropDown_reload()
{
    var me = this;
    me.onBusy();
    $.ajax({type: "GET",
            url: this.url,
            error: function(XMLHttpRequest, textStatus, errorThrown) {alert('Error loading ['+me.url+']: '+textStatus+' '+errorThrown);},
            success: function(data, textStatus) {me.loadXml(data.documentElement);},
            complete: function() {me.onIdle();}
    }); 
}

Web2DropDown.prototype.loadXml = function Web2DropDown_loadXml(node)
{
  if (node==null) return;
  var val = node.getAttribute('value');
  if (val==null) val=this.element.value;

  var warnings = $('>warning',node); // immediate children only //node.getElementsByTagName('warning');
  if (warnings.length>0) {
      var warning = warnings[0];
      node.removeChild(warning);
      this.setWarning(warning.firstChild.nodeValue);
  }

  var children = node.getElementsByTagName('*');
  if (children.length>0) {
      removeChildren(this.element);
  }
  for (var i=0;i<children.length;i++) {
    var child = children[i];
    var value = child.getAttribute(this.valueKey);
    var name = child.getAttribute('name');
    var optionNode = document.createElement('option');
    optionNode.appendChild(document.createTextNode(name));
    optionNode.value = value;
    this.element.appendChild(optionNode);
  }

  this.setValue(val);

  if (children.length==1) {
      $(this.element).hide();
      $(this.element.nextSibling).show();
      $('#'+this.id+'_single').html(children[0].getAttribute('name'));
  } else if (children.length>1) {
      $(this.element).show();
      $(this.element.nextSibling).hide();
  }
}

Web2DropDown.prototype.loadJSON = function Web2DropDown_loadJSON(json)
{
    removeChildren(this.element);
    for (i=0;i<json.length;i++) {
        option = json[i];
        optionNode = document.createElement('option');
        optionNode.appendChild(document.createTextNode(option.name));
        optionNode.value = option[this.valueKey];
        this.element.appendChild(optionNode);
    }
    this.setValue(this.element.value);

    if (json.length==1) {
        $(this.element).hide();
        $(this.element.nextSibling).show();
        $('#'+this.id+'_single').html(json[0].name);
    } else if (json.length>1) {
        $(this.element).show();
        $(this.element.nextSibling).hide();
    }
}

Web2DropDown.prototype.onLoad = function Web2DropDown_onLoad()
{
    if (this.url!=null) {
        this.reload();
    }
}

Web2DropDown.prototype.onChange = function Web2DropDown_onChange(value)
{
    //alert('changed to '+value);
}

/*----------------------------------------------------------------------*/
function Web2CheckBox(id)
{
    superfn(this, arguments);
    var me=this;
    this.element.onclick = function(){me.onChange();};
}
Web2CheckBox.superclass = Web2Field;
Web2CheckBox.prototype = new Web2Field();

Web2CheckBox.prototype.getXml = function Web2CheckBox_getXml(attrib)
{
    attrib['value']=this.element.checked ? '1' : '0';
    var xml = '<field';
    for (var name in attrib) {
        value = attrib[name];
        xml+=' '+name+'="'+xmlEscape(value)+'"';
    }
    xml+='/>';
    return xml;
}

Web2CheckBox.prototype.loadXml = function Web2CheckBox_loadXml(node)
{
    if (node==null) return;
    var val = node.getAttribute('value');
    this.element.checked = (val=="1");
    this.onChange();
}

/*----------------------------------------------------------------------*/
function Web2RadioGroup(id)
{
    superfn(this, arguments);
}
Web2RadioGroup.superclass = Web2Field;
Web2RadioGroup.prototype = new Web2Field();

Web2RadioGroup.prototype.loadXml = function Web2RadioGroup_loadXml(node)
{
    if (node==null) return;
    var val = node.getAttribute('value');
    var children = this.element.getElementsByTagName('input');
    for (var i=0;i<children.length;i++) {
        var child = children[i];
        if (child.getAttribute('value')==val) {
            child.checked = true;
        }
    }
}

Web2RadioGroup.prototype.getXml = function Web2RadioGroup_getXml(attrib)
{
    var children = this.element.getElementsByTagName('input');
    for (var i=0;i<children.length;i++) {
        var child = children[i];
        if (child.checked) {
            attrib['value'] = child.getAttribute('value');
        }
    }

    var xml = '<field';
    for (var name in attrib) {
        value = attrib[name];
        xml+=' '+name+'="'+xmlEscape(value)+'"';
    }
    xml+='/>';
    return xml;
}

/*----------------------------------------------------------------------*/

function Web2Index(id)
{
    superfn(this, arguments);
    this.saveXml = false;
}

Web2Index.superclass = Web2Field;
Web2Index.prototype = new Web2Field();

Web2Index.prototype.loadXml = function web2IndexLoadXml(xml)
{
    removeChildren(this.element);

    var items = xml.getElementsByTagName('item');
    for (var i=0;i<items.length;i++) {
        var child = items[i];
        var name = child.getAttribute('name');
        var value = child.getAttribute('value');
        var div = document.createElement('div');
        div.className = 'web2indexitem';
        var link = document.createElement('a');
        link.onclick = onClickClosure(this, value);
        var text = document.createTextNode(name);
        link.appendChild(text);
        div.appendChild(link);
        this.element.appendChild(div);
    }
}

function onClickClosure(obj, arg)
{
    return function() {obj.onClick(arg);};
}

/*----------------------------------------------------------------------*/

function Web2Button(id)
{
    superfn(this, arguments);
    this.saveXml = false;
    this.element.onclick = onClickClosure(this);
}

Web2Button.superclass = Web2Field;
Web2Button.prototype = new Web2Field();

Web2Button.prototype.onClick = function Web2Button_onClick()
{
    alert(this.id+' clicked');
    return false;
}

/*----------------------------------------------------------------------*/
/* remember one of these is created as the prototype for any subclass */
function Web2DropDownInfo(id)
{
    superfn(this, arguments);
    this.infoById = [];
    this.attrById = [];
    this.pointsById = [];

    //alert('element for '+this.id+' is '+this.element);
    if (this.id!=null) {
        this.infodiv = document.getElementById(this.id+'_info');
    }
}

Web2DropDownInfo.superclass = Web2DropDown;
Web2DropDownInfo.prototype = new Web2DropDown();

Web2DropDownInfo.prototype.onChange = function Web2DropDownInfo_onChange(value)
{
    var id = this.element.value;
    var info = this.infoById[id];
    $(this.infodiv).empty().append(info);
}

// non-recursive, builds flat list of entries and captures contents as info
Web2DropDownInfo.prototype.loadXml = function Web2DropDownInfo_loadXml(node)
{
    if (node.childNodes.length>0) {
      this.infoById = [];
      removeChildren(this.element);
      for (var i=0;i<node.childNodes.length;i++) {
        var child = node.childNodes[i];
        var name = child.getAttribute('name');
        var id = child.getAttribute('id');  // id may or may not == value
        var uid = (id!==null) ? id : name;

        if (child.childNodes.length>0) {
            var text = child.childNodes[0].nodeValue;  // get text node contents
            var infoNode = document.createElement('div');  
            $(infoNode).html(text);
            this.infoById[id] = infoNode;
        }

        if (child.childNodes.length>1) {
            this.pointsById[id] = child.childNodes[1];
        } 

        var optionNode = document.createElement('option');
        optionNode.setAttribute('value', id);
        optionNode.appendChild(document.createTextNode(name));
        this.element.appendChild(optionNode);
        
        // copy all attributes to the attr array
        var attributes = [];
        for (var j=0;j<child.attributes.length;j++) {
            var attr = child.attributes[j];
            attributes[attr.nodeName]=attr.nodeValue;
        }
        this.attrById[id] = attributes;
      }
    }
    this.setValue(node.getAttribute('value'));
}

/*---- Icon with a web link ----*/

function Web2IconLink(id)
{
    superfn(this, arguments);
    this.element.onclick = onClickClosure(this);
    this.address = null;
}

Web2IconLink.superclass = Web2Field;
Web2IconLink.prototype = new Web2Field();

Web2IconLink.prototype.loadXml = function Web2IconLink_loadXml(node)
{
    // note that setValue is not called by this loader
    var iconUrl = node.getAttribute('icon');
    this.element.firstChild.src = iconUrl;
    this.address = node.getAttribute('value');
    this.element.lastChild.nodeValue = node.getAttribute('text');
    superfn(this, arguments);
}

Web2IconLink.prototype.onClick = function Web2IconLink_onClick()
{
    if (this.address!=null) {
        document.location.href = this.address;
    }
}

/*---- Growable list of things ----*/

function Web2Collection()
{
    superfn(this, arguments);
    this.collection = [];
    this.contents = [];
    // dialog_id must be set in args
    var dialogDiv = document.getElementById(this.dialog.id+'_contents');
    //removeChildren(this.dialogDiv); // erase
    this.dialogUl = document.createElement('ul');
    dialogDiv.appendChild(this.dialogUl);
    this.itemsUl = document.getElementById(this.id+'_items');
    var me = this;
    var newButton = document.getElementById(this.id+'_add');
    newButton.onclick = function(){me.onNew();};
}

Web2Collection.superclass = Web2Field;
Web2Collection.prototype = new Web2Field();

Web2Collection.prototype.onNew = function Web2Collection_onNew()
{
    this.dialog.open();
}

Web2Collection.prototype.onDelete = function Web2Collection_onDelete()
{
    alert('that worked');  // not wired up yet.
}

/*
 <contents>
   <item id="n"/>
 </contents>
 <collection>
   <item id="n">html</item>
 </collection>
 */
Web2Collection.prototype.loadXml = function Web2Collection_loadXml(node)
{
    var me = this;
    var collectionList = node.getElementsByTagName('collection');
    if (collectionList.length>0) {
        this.collection.length = 0;
        removeChildren(this.dialogUl);
        var items = collectionList[0].getElementsByTagName('item');
        for (var i=0;i<items.length;i++) {
            var item = items[i];
            var id = item.getAttribute('id');
            var name = item.getAttribute('name');
            var html = item.childNodes[0].nodeValue;
            var itemNode = document.createElement('li');
            //var tickNode = document.createElement('input');
            //tickNode.setAttribute('type', 'checkbox');
            //tickNode.setAttribute('value', id);
            //itemNode.appendChild(tickNode);
            itemNode.className = 'item';
            //itemNode.onclick = onClickClosure(this, id);
            $(itemNode).html('<input type="checkbox" value="'+id+'"/>'+name);
            $('input',itemNode).change(function() {me.select(this.value, this.checked);});
            this.dialogUl.appendChild(itemNode);
            this.collection[id]={html:html, name:name, tick:itemNode.childNodes[0]};
        }
    }

    var itemList = node.getElementsByTagName('contents');
    if (itemList.length>0) {
        this.contents.length = 0;
        removeChildren(this.itemsUl);
        var items = itemList[0].getElementsByTagName('item');
        for (var i=0;i<items.length;i++) {
            var item = items[i];
            var id = item.getAttribute('id');
            this.collection[id].tick.checked = true;
            this.select(id, true);
        }
    }

}

Web2Collection.prototype.select = function Web2Collection_select(id, selected)
{
    var info = this.collection[id];
    if (info.selected!=selected) {
        info.selected = selected;
        if (selected) {
            info.item = document.createElement('li');
            $(info.item).html(info.html);
            this.itemsUl.appendChild(info.item);
        } else {
            $(info.item).remove();
        }
    }
}

Web2Collection.prototype.getXml = function Web2Collection_getXml(attrib)
{
    var xml = '<field';
    for (var name in attrib) {
        value = attrib[name];
        xml+=' '+name+'="'+xmlEscape(value)+'"';
    }
    xml+='>';
    xml+="<contents>";
    for (id in this.collection) {
        if (this.collection[id].selected) {
            xml+='<item id="'+id+'"/>'
        }
    }
    xml+="</contents>";
    xml+="</field>";
    return xml;
}


/*---- Popup dialog - modal ----*/

function Web2Dialog()
{
    superfn(this, arguments);
    this.saveXml = false; // no xml content
    this.cover = document.getElementById(this.id+'_cover');
}

Web2Dialog.superclass = Web2Field;
Web2Dialog.prototype = new Web2Field();

Web2Dialog.prototype.open = function Web2Dialog_open()
{
    //document.body.style.overflow = "hidden";
    {
        // not perfect - scrolling still reveals beneath.
        $(this.cover).height($('body').height());
        $(this.cover).width($('body').width());
    }
    $(this.cover).show();
    $(this.element).show();
}

Web2Dialog.prototype.close = function Web2Dialog_close()
{
    $(this.element).hide();
    $(this.cover).hide();
    //document.body.style.overflowY = "scroll";
}


/*---- Growable list of things ----*/

function Web2Table()
{
    superfn(this, arguments);
    this.rowCount=0;
    var me=this;
    $('._more_', this.element).click(function(){me.addRow();});
}

Web2Table.superclass = Web2Field;
Web2Table.prototype = new Web2Field();


Web2Table.prototype.loadXml = function Web2Table_loadXml(node)
{
    var me = this;
    $("tr._data_", this.element).remove();  // delete existing data
    this.rowCount=0;
    var children = node.getElementsByTagName('row');
    for (var i=0;i<children.length;i++) {
        this.addRow(children[i]);
    }
}

Web2Table.prototype.getXml = function Web2Table_getXml(attrib)
{
    var xml = '<field';
    for (var name in attrib) {
        value = attrib[name];
        xml+=' '+name+'="'+xmlEscape(value)+'"';
    }
    xml+=">";

    var row = this.element.firstChild.firstChild.nextSibling; // skip header
    for (var i=0;i<this.rowCount;i++) {
        var td = row.firstChild;
        rowXml = '<row';
        for (var j=0;j<this.fields.length;j++) {
            var name = this.fields[j].name;
            var input = td.firstChild;
            var value = input.value;
            rowXml+=' '+name+'="'+xmlEscape(value)+'"';
            td = td.nextSibling;
        }
        rowXml += '/>';
        xml+=rowXml;
        row=row.nextSibling;
    }
    xml+="</field>";
    return xml;
}



Web2Table.prototype.deleteRow = function Web2Table_deleteRow(tr)
{
    this.rowCount--;
    tr.parentNode.removeChild(tr);
}

Web2Table.prototype.addRow = function Web2Table_addRow(rowData)
{
    var me = this;
    var tr = document.createElement('tr');
    tr.className = '_data_';
    for (var j=0;j<this.fields.length;j++) {
        var td = document.createElement('td');
        var input = document.createElement('input');
        input.type = "text";
        input.size = this.fields[j].width;
        if (rowData) {
            input.value = rowData.getAttribute(this.fields[j].name);
        }
        td.appendChild(input);
        tr.appendChild(td);
    }
    if (this.rowCount>0) {
        var td = document.createElement('td');
        td.className = '_del_';
        var img = $('<img src="/images/close.gif">').appendTo(td).click(function(){me.deleteRow(tr);});
        tr.appendChild(td);
    }

    var tail = this.element.firstChild.lastChild;
    tail.parentNode.insertBefore(tr, tail);
    this.rowCount++;
}


/*---- Hierarchical menu ----*/

// REVISIT - this is a long way from working
// consider switching to jdmenu to get this running!

function Web2TreeMenu()
{
    this.allowParentSelect = true;
    this.value = 0;
    this.unwrap = 0;
    superfn(this, arguments);
    this.menuElement = document.getElementById(this.id+'_menu');
    this.menu = null;
}

Web2TreeMenu.superclass = Web2TreeView;
Web2TreeMenu.prototype = new Web2TreeView();

Web2TreeMenu.prototype.loadXml = function Web2TreeMenu_loadXml(node)
{
    // unwrap allows us to reuse some URLs.  I expect unwrap will only be 0 or 1
    var value = node.getAttribute('value');

    var warnings = $('>warning',node); // immediate children only //node.getElementsByTagName('warning');
    if (warnings.length>0) {
        var warning = warnings[0];
        node.removeChild(warning);
        this.setWarning(warning.firstChild.nodeValue);
    } else {
      this.clearWarning();
    }

    for (var i=0;i<this.unwrap;i++) {  
        node = node.firstChild; 
    }

    if (this.buildTree(this.menuElement, node)) {
        var me = this;
        $(this.element).mcDropdown(this.menuElement, {delim:' | ', lineHeight:50, allowParentSelect:this.allowParentSelect, select:function(value,name){me.changed(value,name);}});
        this.menu = $('#'+this.id).mcDropdown(); // second call required to get menu object
    }

    if (!value) value=this.value;  // fail over to current value
    if (value) this.setValue(value);
}


// changed is internal only - override onChange to get a callback
Web2TreeMenu.prototype.changed = function Web2TreeMenu_changed(value, name)
{
    this.value = value;
    this.onChange(value, name);
}

Web2TreeMenu.prototype.setValue = function Web2TreeMenu_setValue(value)
{
    this.value = value;
    if (this.menu) {
        this.menu.setValue(value);
    }
}

Web2TreeMenu.prototype.buildTree = function Web2TreeMenu_buildTree(ulNode, xmlNode)
{
    if (xmlNode) {
        //var menu = document.createElement('ul');
        //this.element.appendChild(menu);
        //menu.className = 'menu';
        //alert(xmlNode.childNodes.length);
        for (var i=0;i<xmlNode.childNodes.length;i++) {
            var child = xmlNode.childNodes[i];
            var name = child.getAttribute('name');
            //alert('name: '+name);
            var id = child.getAttribute('id');
            var uid = (id!==null) ? id : name;
            var li = document.createElement('li');
            li.setAttribute('rel', uid);
            //li.className = 'entry';
            li.appendChild(document.createTextNode(name));
            ulNode.appendChild(li);
            if (child.childNodes.length > 0) {
                var subUlNode = document.createElement('ul');
                li.appendChild(subUlNode);
                this.buildTree(subUlNode, child);
                //this.menuById[uid]=subMenu;
                //li.className = 'entry parent';
                //li.appendChild(subMenu);
            }
        }
        return i;
    }
}

Web2TreeMenu.prototype.getXml = function Web2TreeMenu_getXml(attrib)
{
    attrib['value']=this.value;
    var xml = '<field';
    for (var name in attrib) {
        value = attrib[name];
        xml+=' '+name+'="'+xmlEscape(value)+'"';
    }
    xml+=">";
    xml+="</field>";
    return xml;
}

function Web2CallOut()
{
    superfn(this, arguments);
    this.text = document.getElementById(this.id+'_text');
}
Web2CallOut.superclass = Web2Field;
Web2CallOut.prototype = new Web2Field();

Web2CallOut.prototype.setValue = function Web2CallOut_setValue(value)
{
    if (this.text) {
        if (value=='') {
            $(this.element).hide();
        } else {
            $(this.text).html(value);
            $(this.element).show();
        }
    }
}

/*----------------------------------------------------------------------*/

function Web2CheckList()
{
    this.splitChar = '|'
    superfn(this, arguments);
    this.inputs = $('input', this.element);
}
Web2CheckList.superclass = Web2Field;
Web2CheckList.prototype = new Web2Field();

Web2CheckList.prototype.setValue = function Web2CheckList_setValue(value)
{
    var values = value.split(this.splitChar);
    for (var i=0;i<this.inputs.length;i++) {
        var input = this.inputs[i];
        var checked = false;
        for (var j=0;j<values.length;j++) {
            if (input.value == values[j]) {
                checked = true;
            }
        }
        input.checked = checked;
    }
}

Web2CheckList.prototype.getValue = function Web2CheckList_getValue()
{
    var mask = 0;
    var values = [];
    for (i=0;i<this.inputs.length;i++) {
        var input = this.inputs[i];
        if (input.checked) {
            values.push(input.value);
        }
    }
    return values.join(this.splitChar);
}

Web2CheckList.prototype.getXml = function Web2CheckList_getXml(attrib)
{
    var mask = this.getValue();
    attrib['value'] = mask+''; // force string

    var xml = '<field';
    for (var name in attrib) {
        value = attrib[name];
        xml+=' '+name+'="'+xmlEscape(value)+'"';
    }
    xml+='/>';
    return xml;
}

/*----------------------------------------------------------------------*/

function Web2BitField()
{
    superfn(this, arguments);
    this.inputs = $('input', this.element);
}

Web2BitField.superclass = Web2CheckList;
Web2BitField.prototype = new Web2CheckList();

Web2BitField.prototype.setValue = function Web2BitField_setValue(value)
{
    for (i=0;i<this.inputs.length;i++) {
        var input = this.inputs[i];
        var mask = 1<<input.value;
        input.checked = (mask & value)>0;
    }
}

Web2BitField.prototype.getValue = function Web2BitField_getValue()
{
    var mask = 0;
    for (i=0;i<this.inputs.length;i++) {
        var input = this.inputs[i];
        if (input.checked) {
            mask |= 1<<input.value;
        }
    }
    return mask;
}

Web2BitField.prototype.getXml = function Web2BitField_getXml(attrib)
{
    var mask = this.getValue();
    attrib['value'] = mask+''; // force string

    var xml = '<field';
    for (var name in attrib) {
        value = attrib[name];
        xml+=' '+name+'="'+xmlEscape(value)+'"';
    }
    xml+='/>';
    return xml;
}

/*----------------------------------------------------------------------*/

function Web2FileList()
{
    superfn(this, arguments);
    this.list = $('#'+this.id+'_list');
    this.nextId = 1;
}

Web2FileList.superclass = Web2Field;
Web2FileList.prototype = new Web2Field();

// display input box to add a new file
Web2FileList.prototype.addFile = function Web2FileList_addFile(uid, fileName, size)
{
    var rowId = this.id+'_'+this.nextId;
    this.nextId++;
    var html = '<div id="'+rowId+'" class="file" uid="'+uid+'"><span class="filename"><a href="/file/load/'+uid+'"><img src="/file/icon/'+uid+'" alt="file"/>&nbsp;'+fileName+'</a></span><span class="filesize">'+size+' bytes</span><img class="filedel" src="/images/close.gif" alt="remove" onclick="if (confirm(\'Remove file '+fileName+'\')) $(\'#'+rowId+'\').remove();"/></div>';
    this.list.append(html);
}

Web2FileList.prototype.loadXml = function Web2FileList_loadXml(node)
{
    this.list.children().remove();
    var children = node.getElementsByTagName('file');
    for (var i=0; i<children.length;i++) {
        var child = children[i];
        var fileName = child.getAttribute('filename');
        var uid = child.getAttribute('id');
        var size = child.getAttribute('size');
        this.addFile(uid, fileName, size);
    }
}

Web2FileList.prototype.getXml = function Web2Table_getXml(attrib)
{
    var xml = '<field';
    for (var name in attrib) {
        value = attrib[name];
        xml+=' '+name+'="'+xmlEscape(value)+'"';
    }
    xml+=">";
    var list = document.getElementById(this.id+'_list');

    for (var i=0;i<list.childNodes.length;i++) {
        var uid = list.childNodes[i].getAttribute('uid');
        xml+='<file uid="'+uid+'"/>';
    }

    xml+='</field>';
    return xml;
}
