// require XMLHttpRequest.js
// http://code.google.com/p/xmlhttprequest/


// namespace AjaxUpdate
var AjaxUpdate = {

  // custom events passed to addHandler
  Event: {
    Started: 0,
    Succeeded: 1,
    Canceled: 2
  },
  
  debug: function(){},
  
  debug2: function(){},
  
  // for security reasons, only application/x-www-form-urlencoded is allowed
  postContentType: 'application/x-www-form-urlencoded; charset=utf-8',

  // override as function if needed
  enabled: true,

  _enabled: function() {
    if (typeof AjaxUpdate.enabled === 'function')
      return AjaxUpdate.enabled();
    else
      return AjaxUpdate.enabled;
  },

  agentEnabled: function() {
    if (
      document.getElementById &&
      XMLHttpRequest &&
      document.createElement
      
    ) {
      return true;
    }
    return false;
  },

  // must override
  mapUri: function(url,id) {
    throw('missing function definition of AjaxUpdate.mapUri');
  },
  
  // will be changed on first update
  onLoad: function(handler) {
    AjaxUpdate.addEventListener(window,'load',handler);
  },

  currentUrl: function() {
    return document.location.href;
  }

};


// -----------------------------------------------------------------------------
// abstract class AjaxUpdate.CaseObject
//
// implementing a transitive AjaxUpdate.Case object parent relation
AjaxUpdate.CaseObject = function(id,elem) {
  this.id = id;
  this.elem = elem;
  this.__parent = null;
  this.__parentList = null;
  this.__eventCallbacks = {};
};

AjaxUpdate.CaseObject.__sharedEventCallbacks = {};

AjaxUpdate.CaseObject.addSharedEventCallback = function(event,handler) {
  if (!AjaxUpdate.CaseObject.__sharedEventCallbacks[event])
    AjaxUpdate.CaseObject.__sharedEventCallbacks[event] = [];
  var ref = AjaxUpdate.CaseObject.__sharedEventCallbacks[event].length;
  AjaxUpdate.CaseObject.__sharedEventCallbacks[event].push(handler);
  return ref;
};
AjaxUpdate.CaseObject.removeSharedEventCallback = function(event,ref) {
  if (AjaxUpdate.CaseObject.__sharedEventCallbacks[event] &&
      AjaxUpdate.CaseObject.__sharedEventCallbacks[event].length > ref)
    delete AjaxUpdate.CaseObject.__sharedEventCallbacks[event][ref];
};

AjaxUpdate.CaseObject.prototype = new Object;

// public bool isChildOf(string id)
AjaxUpdate.CaseObject.prototype.isChildOf = function(id) {
  var p = this.getParent();
  return (p != null && (p.id == id || p.isChildOf(id)));
};

// public AjaxUpdate.Case AjaxUpdate.CaseObject.getParent()
AjaxUpdate.CaseObject.prototype.getParent = function() {
  if (this.__parent == null)
    this.__parent = this.__getParent();
  return this.__parent || null;
};
AjaxUpdate.CaseObject.prototype.__getParent = function() {
  // find containig case object
  for (var n = this.elem.parentNode;
      n != null && n.tagName != 'BODY' && n.tagName != 'HTML';
      n = n.parentNode) {
    if (n.AjaxUpdate_caseInstance) // instanceof AjaxUpdate.Case
      return n.AjaxUpdate_caseInstance;
  }
  return 0; // no parent found
};

// handler as function(event,sender,object[,data])
AjaxUpdate.CaseObject.prototype.addEventCallback = function(event,handler) {
  if (!this.__eventCallbacks[event]) this.__eventCallbacks[event] = [];
  var ref = this.__eventCallbacks[event].length;
  this.__eventCallbacks[event].push(handler);
  return ref;
};
AjaxUpdate.CaseObject.prototype.removeEventCallback = function(event,ref) {
  if (this.__eventCallbacks[event] && this.__eventCallbacks[event].length > ref)
    delete this.__eventCallbacks[event][ref];
};
AjaxUpdate.CaseObject.prototype.__runEventCallbacks = function(event,sender,object,data) {
  var a;
  if ((a = this.__eventCallbacks[event])) {
    for (var i = 0; i < a.length; i++) {
      if (typeof a[i] === 'function')
        a[i].call(this,event,sender,object,data);
    }
  }
  if ((a = AjaxUpdate.CaseObject.__sharedEventCallbacks[event])) {
    for (var i = 0; i < a.length; i++) {
      if (typeof a[i] === 'function')
        a[i].call(this,event,sender,object,data);
    }
  }
};


// -----------------------------------------------------------------------------
// class AjaxUpdate.Case inherits AjaxUpdate.CaseObject
//
AjaxUpdate.Case = function(id,url) {
  this.base = AjaxUpdate.CaseObject;
  this.base(id,null);
  this.__currentUrl = url;
  this.__updateUrl = null;
  this.__req = null;
  this.__cancelUpdate = null;
  this.__tf = {};
  this.__validators = [];
};

// shared case registry
AjaxUpdate.Case.__Registry = {};

AjaxUpdate.Case.enable = function() {
  AjaxUpdate.onLoad(function(){AjaxUpdate.Case.__initCases()});
};

AjaxUpdate.Case.__initCases = function() {
  var a = [];
  for (var id in AjaxUpdate.Case.__Registry)
    with (AjaxUpdate.Case.__Registry[id]) {
      if (elem == null && __attachElem()) {
        a.push(AjaxUpdate.Case.__Registry[id]);
      }
    }
  for (var i = 0; i < a.length; i++)
    a[i].tryCreateTriggers();
  a = null;
};

AjaxUpdate.Case.create = function(id) {
  return (AjaxUpdate.Case.__Registry[id] = new AjaxUpdate.Case(id,AjaxUpdate.currentUrl()));
};

AjaxUpdate.Case.getInstance = function(id) {
  var elem = AjaxUpdate.getElem(id);
  if (elem && (elem.AjaxUpdate_caseInstance)) // instanceof AjaxUpdate.Case
    return elem.AjaxUpdate_caseInstance;
  return null;
};

AjaxUpdate.Case.prototype = new AjaxUpdate.CaseObject;

AjaxUpdate.Case.prototype.toString = function() {
  return 'AjaxUpdate.Case#'+this.id;
};

AjaxUpdate.Case.prototype.__attachElem = function() {
  if ((this.elem = AjaxUpdate.getElem(this.id))) {
    this.elem.AjaxUpdate_caseInstance = this;
    return true;
  }
  return false;
};

AjaxUpdate.Case.prototype.dispose = function() {
  if (this.elem) {
    for (var id in this.__tf) {
      this.__tf[id].dispose(); // instanceof AjaxUpdate.TriggerFactory
      delete this.__tf[id];
    }
    delete AjaxUpdate.Case.__Registry[this.id];
    this.elem.AjaxUpdate_caseInstance = null;
    this.elem = null;
  }
};

// overrides AjaxUpdate.CaseObject.prototype.__runEventCallbacks()
AjaxUpdate.Case.prototype.__runEventCallbacks = function(event,sender,object,data,node) {
  if (!node) {
    node = this.elem;
    AjaxUpdate.CaseObject.prototype.__runEventCallbacks.call(this,event,sender,object,data);
  }
  if (node.nodeType == 1) { // element node
    for (var i = 0; i < node.childNodes.length; i++) {
      var n = node.childNodes[i];
      (n.AjaxUpdate_caseInstance) ?
        n.AjaxUpdate_caseInstance.__runEventCallbacks(event,sender,object,data)
        : this.__runEventCallbacks(event,sender,object,data,n);
    }
  }
};

// public void addUpdateTrigger(string id, [string event])
AjaxUpdate.Case.prototype.addUpdateTrigger = function(id,event) {
  this.__tf[id] = new AjaxUpdate.TriggerFactory(this.id,id,event);
  return this;
};

// public void update(string url)
AjaxUpdate.Case.prototype.update = function(uri) {
  // start update directly w/o trigger
  this.__startUpdate(uri,null,null);
  return this;
};

// public bool AjaxUpdate.Case.handles(string triggerId)
AjaxUpdate.Case.prototype.handles = function(triggerId) {
  return (this.__tf[triggerId] != null); // instanceof AjaxUpdate.TriggerFactory
};

AjaxUpdate.Case.prototype.getCurrentUrl = function() {
  if (this.__currentUrl != null)
    return this.__currentUrl;
  return document.location.href;
};

AjaxUpdate.Case.prototype.startUpdate = function(trigger) {

  if (this._ignoreTrigger(trigger.id))
    return true;
    
  var url = trigger.getUpdateUri();
  var mapUrl = AjaxUpdate.mapUri(url,this.id);
  if (mapUrl == null) {
    throw 'AjaxUpdate.mapUri returned null: '+url;
  }
  
  if (!this._validate(trigger,url))
    return false;

  var postData = trigger.getUpdatePost();
  
  return this.__startUpdate(mapUrl,postData,trigger);
};

AjaxUpdate.Case.prototype.__startUpdate = function(url,postData,trigger) {
  // trigger may be null

  // remember to set as currentUrl on success
  this.__updateUrl = url;

  // ensure to run only one update at a time
  this.cancelUpdate();

  // call Started handlers
  this.__runEventCallbacks(AjaxUpdate.Event.Started,trigger,this);

  // perform request
  var c = this;
  this.__req = new XMLHttpRequest;
  this.__req.open(((postData != null)? 'POST' : 'GET'),url,true);
  this.__reqAbort = function() {
      return AjaxUpdate.Case.prototype.__cancelUpdate.call(c,trigger);
    };

    //alert('startUpdate: '+this+': '+url);

  this.__req.onreadystatechange = function() {
      //AjaxUpdate.debug(c,'onreadystatechange',this.readyState);
      if (this.readyState == XMLHttpRequest.DONE)	{
        c.__reqAbort = null;
        if (trigger) trigger.clear();
        var data = this.responseText;
        with (AjaxUpdate.Case.prototype) {
          freeChilds.call(c,trigger);
          __completeUpdate.call(c,data);
          __runEventCallbacks.call(c,AjaxUpdate.Event.Succeeded,trigger,c,data);
        }
      }
    };
  if (postData != null)
    this.__req.setRequestHeader('Content-Type',AjaxUpdate.postContentType);
  this.__req.send(postData);
  return true;
};

AjaxUpdate.Case.prototype.__completeUpdate = function(data) {

  this.elem.innerHTML = data;
	AjaxUpdate.debug(this,'__completeUpdate','data has been inserted ('+data.length+' bytes).');

  // evaluate obtained script code
  var url = this.__currentUrl = this.__updateUrl;
  AjaxUpdate.currentUrl = function() {return url};
	AjaxUpdate.onLoad = function(handler){handler()};
	AjaxUpdate.Case.__execScriptCode(data);
	AjaxUpdate.debug(this,'__completeUpdate','script code has been executed.');
	
	AjaxUpdate.Case.__initCases();
  this.__recreateChildTriggers();
  
  this.__updateUrl = null;
  this.__req = null;
};

AjaxUpdate.Case.prototype.__inProgress = function() {
  return (this.__req != null);
};

AjaxUpdate.Case.prototype.cancelUpdate = function() {
  return (null!=this.__reqAbort && this.__reqAbort());
};

AjaxUpdate.Case.prototype.__cancelUpdate = function(trigger) {
  if (null!=this.__req) {
    this.__req.abort();
    this.__req = null;
    this.__runEventCallbacks(AjaxUpdate.Event.Canceled,trigger,this);
    AjaxUpdate.debug(this,'__cancelUpdate','request aborted.');
    return true;
  }
  return false;
};

AjaxUpdate.Case.prototype.addValidator = function(handler) {
  this.__validators.push(handler);
  return this;
};
AjaxUpdate.Case.prototype._validate = function(trigger) {
  for (var i = 0; i < this.__validators.length; i++) {
    if (typeof this.__validators[i] === 'function')
      if (!this.__validators[i].call(this,trigger))
        return false;
  }
  return true;
};

AjaxUpdate.Case.prototype.tryCreateTriggers = function() {
	for (var id in this.__tf) this.__tf[id].tryCreate();
};

AjaxUpdate.Case.prototype.__recreateChildTriggers = function() {
  // try to re-create any triggers attached to child elements
  // and handled by cases not contained in this
  for (var id in AjaxUpdate.Case.__Registry) {
    var c = AjaxUpdate.Case.__Registry[id];
    if (!c.isChildOf(this.id)) {
      //AjaxUpdate.debug(this,'__recreateChildTriggers','not my child: '+c);
      c.tryCreateTriggers();
    }
  }
};

AjaxUpdate.Case.prototype._ignoreTrigger = function(id) {
  // ignore trigger if it's handled by a parent case
  for (var c = this.getParent(); c; c = c.getParent()) {
    if (c.handles(id))
      return true;
  }
  return false;
};

AjaxUpdate.Case.prototype.freeChilds = function(trigger,node) {
  if (!node) {
    node = this.elem;
  }
  if (node.nodeType == 1) { // element node
    if (node.AjaxUpdate_trigger) { // free attached triggers
      for (var id in node.AjaxUpdate_trigger) {
        if (node.AjaxUpdate_trigger[id]) { //  instanceof AjaxUpdate.Trigger
          node.AjaxUpdate_trigger[id].dispose();
        }
      }
      node.AjaxUpdate_trigger = null;
    }
    for (var i = 0; i < node.childNodes.length; i++) {
      var n = node.childNodes[i];
      if (n.AjaxUpdate_caseInstance) { // instanceof AjaxUpdate.Case
        with (n.AjaxUpdate_caseInstance) {
          // run freeChilds for AjaxUpdate.Case instance
          freeChilds(trigger);
          dispose();
        }
      } else {
        this.freeChilds(trigger,n);
      }
    }
  }
};

AjaxUpdate.Case.__execScriptCode = function(s) {
  s = s.replace(/<!--(.|\n)*?-->/g, '');
  var p1, p2, p3;
  while ((p1 = s.indexOf('<script')) >= 0 ) {
    s = s.substr(p1+7);
    if ((p2 = s.indexOf('>')) > 0) {
      var t = s.substr(0,p2);
      if (t.indexOf('text/javascript') > 0) {
        var m = t.match(/\s+src=['"]([^'"]+)["']/);
        if (m) { // load script source
          var src = m[1];
          AjaxUpdate.loadScript(src);
        } else { // eval inline script code
          if ((p3 = s.substr(p2+1).indexOf('</script>')) >= 0) {
            var c = s.substr(p2+1,p3);
            AjaxUpdate.execScript(c);
          }
        }
      }
    }
  }
};

// -----------------------------------------------------------------------------
// class AjaxUpdate.TriggerFactory inherits AjaxUpdate.CaseObject
//
// a potential trigger element attached to an instance of AjaxUpdate.Case
AjaxUpdate.TriggerFactory = function(caseId,elemId,event) {
  this.base = AjaxUpdate.CaseObject;
  this.base(elemId);
  this.__caseId = caseId;
  this.event = event;
  this.trigger = null;
};
AjaxUpdate.TriggerFactory.prototype = new AjaxUpdate.CaseObject;

// protected bool AjaxUpdate.TriggerFactory._findElem()
AjaxUpdate.TriggerFactory.prototype._findElem = function() {
  this.elem = AjaxUpdate.getElem(this.id);
  return (this.elem != null);
}

// public bool AjaxUpdate.TriggerFactory.isChildOf(string id)
// overrides AjaxUpdate.CaseObject.isChildOf
AjaxUpdate.TriggerFactory.prototype.isChildOf = function(id) {
  if (!this._findElem()) return false;
  return this.base.prototype.isChildOf(id);
};

// public void AjaxUpdate.TriggerFactory.tryCreate()
// try to create or re-create the trigger object
AjaxUpdate.TriggerFactory.prototype.tryCreate = function() {
  if (this._findElem()) { // having DOM node
    
    // skip if already existing
    if (this.elem.AjaxUpdate_trigger && this.elem.AjaxUpdate_trigger[this.__caseId])
      return true;
      
    var t = AjaxUpdate.Trigger.create(this.id,this.elem,this.event,this.__caseId);
    if (t) {
      // attach AjaxUpdate.Trigger instance to DOM node
      if (!this.elem.AjaxUpdate_trigger) this.elem.AjaxUpdate_trigger = {};
      this.elem.AjaxUpdate_trigger[this.__caseId] = t;

      // attach event listener
      t.attach();

      this.trigger = t;
      return true;
    }
  }
  return false;
};

AjaxUpdate.TriggerFactory.prototype.dispose = function() {
  if (this.trigger != null) this.trigger.dispose();
  this.trigger = null;
};

// -----------------------------------------------------------------------------
// abstract AjaxUpdate.Trigger
//
AjaxUpdate.Trigger = function(id,elem,event,caseId) {
  this.id = id;
  this.elem = elem;
  this.event = event;
  this.__caseId = caseId;
  this.__caseInstance = null;
  this.__eventHandler = null;
};

// AjaxUpdate.Trigger factory method
AjaxUpdate.Trigger.create = function(id,elem,event,caseId) {
  var t = null;
  if (elem) {
    switch (elem.tagName) {
      case 'A':
        t = new AjaxUpdate.LinkTrigger(id,elem,(event||'click'),caseId);
        break;
      case 'FORM':
        t = new AjaxUpdate.FormTrigger(id,elem,(event||'submit'),caseId);
        break;
      default:
        if (elem.AjaxUpdate_caseInstance)
          t = new AjaxUpdate.CaseTrigger(id,elem,null,caseId);
    }
  }
  return t;
}

AjaxUpdate.Trigger.prototype = new Object;

AjaxUpdate.Trigger.prototype.dispose = function() {
  if (this.elem) {
    this.detach();
    this.elem.AjaxUpdate_trigger[this.__caseId] = null;
    this.elem = null;
  }
}

AjaxUpdate.Trigger.prototype.clear = function() {
  // reset state when update is done
};

AjaxUpdate.Trigger.prototype.attach = function() {
  var inst = this;
  AjaxUpdate.debug2(this,'attach','(on '+this.event+') -> '+this.getCaseInstance());
  this.__eventHandler = AjaxUpdate.createEventCallback(function(){return !inst.handleEvent()});
  AjaxUpdate.addEventListener(this.elem,this.event,this.__eventHandler);
};

AjaxUpdate.Trigger.prototype.detach = function() {
  if (this.__eventHandler != null) {
    AjaxUpdate.debug2(this,'detach','(on '+this.event+') -> '+this.getCaseInstance());
    AjaxUpdate.removeEventListener(this.elem,this.event,this.__eventHandler);
    this.__eventHandler = null;
  }
}

AjaxUpdate.Trigger.prototype.getCaseInstance = function() {
  if (this.__caseInstance == null)
    this.__caseInstance = AjaxUpdate.Case.getInstance(this.__caseId);
  return this.__caseInstance;
};

// abstract public string getUpdateUri()
AjaxUpdate.Trigger.prototype.getUpdateUri = function() {
  throw('abstract function call: AjaxUpdate.Trigger.getUpdateUri');
};

//abstract public string getUpdateMethod()

AjaxUpdate.Trigger.prototype.getUpdatePost = function() {
  return null;
};

AjaxUpdate.Trigger.prototype.handleEvent = function() {
  if (AjaxUpdate._enabled() && AjaxUpdate.agentEnabled()) {
    var c = this.getCaseInstance();
    if (c) return AjaxUpdate.Case.prototype.startUpdate.call(c,this);
  }
  return false;
};


// -----------------------------------------------------------------------------
// class AjaxUpdate.LinkTrigger inherits AjaxUpdate.Trigger
//
// elem.tagName == 'A'
AjaxUpdate.LinkTrigger = function(id,elem,event,caseId) {
  this.base = AjaxUpdate.Trigger;
  this.base(id,elem,event,caseId);
};

AjaxUpdate.LinkTrigger.prototype = new AjaxUpdate.Trigger;

AjaxUpdate.LinkTrigger.prototype.toString = function() {
  return 'AjaxUpdate.LinkTrigger#'+this.id;
};

// overrides AjaxUpdate.Trigger.prototype.getUpdateUri()
AjaxUpdate.LinkTrigger.prototype.getUpdateUri = function() {
  return this.elem.href;
};

// -----------------------------------------------------------------------------
// class AjaxUpdate.FormTrigger inherits AjaxUpdate.Trigger
//
// elem.tagName == 'FORM'
AjaxUpdate.FormTrigger = function(id,elem,event,caseId) {
  this.base = AjaxUpdate.Trigger;
  this.base(id,elem,event,caseId);
  this.__formData = null;
};

AjaxUpdate.FormTrigger.prototype = new AjaxUpdate.Trigger;

AjaxUpdate.FormTrigger.prototype.toString = function() {
  return 'AjaxUpdate.FormTrigger#'+this.id;
};

AjaxUpdate.Trigger.prototype.clear = function() {
  this.__formData = null;
};

// overrides AjaxUpdate.Trigger.prototype.getUpdateUri()
AjaxUpdate.FormTrigger.prototype.getUpdateUri = function() {
  var uri = AjaxUpdate.getAbsUrl(this.getCaseInstance().getCurrentUrl(),(this.elem.action||''));
  return (this.__isPost())?
    uri : AjaxUpdate.appendUriQuery(uri,this.__getFormData());
};

// overrides AjaxUpdate.Trigger.prototype.getUpdatePost()
AjaxUpdate.FormTrigger.prototype.getUpdatePost = function() {
  return (this.__isPost())? this.__getFormData() : null;
};

AjaxUpdate.FormTrigger.prototype.__isPost = function() {
  return (this.elem.method)?
    (String(this.elem.method).toUpperCase() == 'POST') : false;
};

AjaxUpdate.FormTrigger.prototype.__getFormData = function() {
  if (this.__formData == null) {
  	this.__formData = AjaxUpdate.getFormData(this.elem);
    AjaxUpdate.debug2(this,'__getFormData', this.__formData);
  }
	return this.__formData;
};

// -----------------------------------------------------------------------------
// class AjaxUpdate.CaseTrigger inherits AjaxUpdate.Trigger
//
AjaxUpdate.CaseTrigger = function(id,elem,event,caseId) {
  this.base = AjaxUpdate.Trigger;
  this.base(id,elem,event,caseId);
  this.__startedHandler = null;
  this.__canceledHandler = null;
  this.__updateTrigger = null;
  
};

AjaxUpdate.CaseTrigger.prototype = new AjaxUpdate.Trigger;

AjaxUpdate.CaseTrigger.prototype.toString = function() {
  return 'AjaxUpdate.CaseTrigger#'+this.id;
};

// overrides AjaxUpdate.Trigger.prototype.getUpdateUri()
AjaxUpdate.CaseTrigger.prototype.getUpdateUri = function() {
  return (this.__updateTrigger != null)?
    this.__updateTrigger.getUpdateUri() : null;
};

// overrides AjaxUpdate.Trigger.prototype.getUpdatePost()
AjaxUpdate.CaseTrigger.prototype.getUpdatePost = function() {
  return (this.__updateTrigger != null)?
    this.__updateTrigger.getUpdatePost() : null;
};

// overrides AjaxUpdate.Trigger.prototype.attach()
AjaxUpdate.CaseTrigger.prototype.attach = function() {
  var inst = this;
  AjaxUpdate.debug2(this,'attach','(on update) -> '+this.getCaseInstance());

  //this.__startedHandler = null;
  //this.__canceledHandler = null;

  this.__startedHandler = this.elem.AjaxUpdate_caseInstance.addEventCallback(
    AjaxUpdate.Event.Started,
    function(event,trigger) {
      //AjaxUpdate.debug2(this,'onStarted','forwarding to: '+inst.getCaseInstance());
      //alert(this+'forwarding to: '+inst.getCaseInstance()');
      inst.__updateTrigger = trigger;
      AjaxUpdate.CaseTrigger.prototype.handleEvent.call(inst);
    });
    
  this.__canceledHandler = this.elem.AjaxUpdate_caseInstance.addEventCallback(
      AjaxUpdate.Event.Canceled,
      function(event,trigger) {
        inst.getCaseInstance().cancelUpdate();
    });
};
AjaxUpdate.CaseTrigger.prototype.detach = function() {

  if (null!=this.__startedHandler) {
    AjaxUpdate.debug2(this,'detach','(on update) -> '+this.getCaseInstance());
    this.elem.AjaxUpdate_caseInstance.removeEventCallback(
      AjaxUpdate.Event.Started,this.__startedHandler);
    this.__startedHandler = null;
  }

  if (null!=this.__canceledHandler) {
    this.elem.AjaxUpdate_caseInstance.removeEventCallback(
      AjaxUpdate.Event.Canceled,this.__canceledHandler);
    this.__canceledHandler = null;
  }

}


//
// ua interface
//
if (document.getElementById && document.createElement && document.body.appendChild) {

  AjaxUpdate.getElem = function(id) {return document.getElementById(id)};
  
  // add script node to document.body
  AjaxUpdate.execScript = function(code) {
    var a, elem = document.createElement('script');
    (a = document.createAttribute('type')).nodeValue = 'text/javascript';;
    elem.setAttributeNode(a);
    elem.text = '/*<![CDATA[*/'+code+'/*]]>*/';
    document.body.appendChild(elem);
  };

  // add script source node
  AjaxUpdate.loadScript = function(src) {
    var a, elem = document.createElement('script');
    (a = document.createAttribute('type')).nodeValue = 'text/javascript';;
    elem.setAttributeNode(a);
    (a = document.createAttribute('src')).nodeValue = src;
    elem.setAttributeNode(a);
    document.body.appendChild(elem);
  };

  // x-browser

  if (window.HTMLElement && window.addEventListener) { // w3c DOM Core Level 2

    AjaxUpdate.createEventCallback = function(handler) {
      return function(e) {
  			e.stopPropagation();
  			if (!handler(e)) e.preventDefault();
      };
    };

    AjaxUpdate.addEventListener = function(node,event,handler) {
      node.addEventListener(event,handler,false);
    };

    AjaxUpdate.removeEventListener = function(node,event,handler) {
      node.removeEventListener(event,handler,false);
    };

  } else if (window.ActiveXObject && window.attachEvent) { // MSIE

    AjaxUpdate.createEventCallback = function(handler) {
      return function() {
        var e = window.event;
  			e.cancelBubble = true;
  			e.returnValue = handler(e);
      };
    };

    AjaxUpdate.addEventListener = function(node,event,handler) {
      node.attachEvent('on'+event,handler);
    };

    AjaxUpdate.removeEventListener = function(node,event,handler) {
      node.detachEvent('on'+event,handler);
    };

  }

}

// helpers lib

// AjaxUpdate.urlEncode = function(s) {
//   return encodeURIComponent(s);
//   // escape encodes space as "%20", and treats "+" as a safe character
//   //return escape(s).replace(/\+/g,'%2B').replace(/%20/g,'+');
// };

AjaxUpdate.appendUriQuery = function(uri,query) {
  var p, f = '', q = '';
  if ((p = uri.lastIndexOf('#')) >= 0) {
    f = uri.substr(p);
    uri = uri.substr(0,p);
  }
  if ((p = uri.lastIndexOf('?')) >= 0) {
    q = uri.substr(p+1);
    uri = uri.substr(p);
  }
  if (q != '') q += '&';
  q += query;
  return uri+'?'+q+f;
};

AjaxUpdate.getAbsUrl = function(current,url) {
  if (url == '')
    return current;
  if (url.match(/^[a-z]+:\/\//))
    return url; // is abs already

  var m;
  if ((m = /^([a-z]+:\/\/(?:[^\/]+))([^\?#]*)/.exec(current))) {
    var left = m[1], path = m[2].replace(/[^\/]+$/,'');
    if (url.match(/^\//))
      return left+url;
    
    path = path || '/';
    url = url.replace(/^(\.\/)+/,'');
    while (url.match(/^\.\.\//)) {
      path = path.replace(/[^\/]+\/$/,'');
      url = url.substr(3);
    }
    return left+path+url;
  }
  return null;
}

AjaxUpdate.getFormData = function(form) {
  var n, data = '', add_data = function(n,v) {
      if (data != '') data += '&';
      data += encodeURIComponent(n)+'='+encodeURIComponent(v);
    };
	for (var i = 0; i < form.elements.length; i++) {
		if ((n = form.elements[i].name) != '') {
			var el = form.elements[i];
      switch (el.type) {
        case 'radio':
        case 'checkbox':
          if (el.checked)
            add_data(n,((el.value!='')? el.value : 'on'));
          break;
        case 'select-one':
          if (el.options)
            add_data(n,el.options[el.selectedIndex].value);
          break;
        case 'select-multiple':
          if (el.options)
            for (var j = 0; j < el.options.length; j++)
              if (el.options[j].selected)
                add_data(n,el.options[j].value);
          break;
        default:
          add_data(n,el.value);
          break;
      }
		}
	}
	return data;
};


