﻿/*
 *  XMLhttp and related Ajaxish stuff © 2006-2010, Horus Web Engineering Ltd
 *
 *  $Id: xmlhttp.js,v 1.75 2010-08-09 20:09:42 horus Exp $
 *
 *  some of this bears a distant historical debt to code from fckeditor:
 *    http://www.fckeditor.net/
 *
 *  licensed under the terms of the GNU Lesser General Public License:
 *    http://www.opensource.org/licenses/lgpl-license.php
 *
 *  needs horus.js, dom.js, status.js
 *  uses iterator.js if requested
 *
 */

horus.script.load('dom', 'status');


horus.xmlhttp=
  function ( name ) {
    if (this.constructor==horus.xmlhttp) {
      this.allocate();
      this.name=name ? name : 'horus';
      return;
    }

    return new horus.xmlhttp().request(name, arguments[1], arguments[2]);
  };


horus.xmlhttp.className='horus.xmlhttp';
horus.xmlhttp.implementation=0;
horus.xmlhttp.supportlevel=0;
horus.xmlhttp.minpool=8;
horus.xmlhttp.pool=[];


horus.xmlhttp.prototype.allocatenew=
  function () {
    switch (horus.xmlhttp.implementation) {

    case 1: this.xmlhttp=new XMLHttpRequest; break;
    case 2: this.xmlhttp=new ActiveXObject('Msxml2.XMLHTTP'); break;
    case 3: this.xmlhttp=new ActiveXObject('Microsoft.XMLHTTP'); break;

    default:
      try {
	this.xmlhttp=new XMLHttpRequest;
	horus.xmlhttp.implementation=1;
      } catch (err) {
	try {
	  this.xmlhttp=new ActiveXObject('Msxml2.XMLHTTP');
	  horus.xmlhttp.implementation=2;
	} catch (err) {
	  this.xmlhttp=new ActiveXObject('Microsoft.XMLHTTP');
	  horus.xmlhttp.implementation=3;
	}
      }

    }
  };


horus.xmlhttp.prototype.allocate=
  function () {
    var pool=horus.xmlhttp.pool;

    if (pool.length>=horus.xmlhttp.minpool) {
      for (var i=0;
	   i<pool.length && !(pool[i].readyState==undefined || pool[i].readyState==4);
	   i++);

      if (i<pool.length) {
	this.xmlhttp=pool[i];

	if (this.xmlhttp.readyState==undefined)
	  this.reallocate();
	else if (i<pool.length-1)
	  pool.push(pool.splice(i, 1));

      }
    }

    if (this.xmlhttp) {
      this.xmlhttp.onreadystatechange=null;
      this._arguments=null;
    } else {
      this.allocatenew();
      pool.push(this.xmlhttp);
    }
  };


horus.xmlhttp.prototype.reallocate=
  function () {
    var xmlhttp=this.xmlhttp;
    var pool=horus.xmlhttp.pool;
    for (var i=pool.length-1; i>=0 && pool[i]!=xmlhttp; i--);
    if (i>=0) pool.splice(i, 1);
    this.allocatenew();
    pool.push(this.xmlhttp);
    return this.xmlhttp;
  };


horus.xmlhttp.prototype.request=
  function ( url, method, callback ) {
    var args;

    if (url instanceof Array) {
      args=url[1];
      if (args && args instanceof Array) args=args.join('&');
      url=url[0];
    }

    if (args==null) args='';
    if (!method) method='GET';

    if (method=='GET') {
      url+=(url.search(/\?/)<0 ? '?' : '&')+'_hos='+Math.random();
      if (args!='') url+='&'+args;
    }

    var xmlhttp=this.xmlhttp;
    var async=callback!=null;

    xmlhttp.open(method, url, async);

    if (async) {
      var self=this;
      if (typeof callback=='boolean') callback=[ self, self.callback ];

      xmlhttp.onreadystatechange=
	function () {
	  if (xmlhttp.readyState==4) {
	    self.status=xmlhttp.status;
	    self.statusText=xmlhttp.statusText;
	    self.response=xmlhttp.responseXML;
	    horus.call(callback, self, self._arguments);
	  }
	};

    }

    if (method=='POST') {
      xmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
      xmlhttp.setRequestHeader('Content-length', args.length);
      xmlhttp.setRequestHeader('Connection', 'close');
      if (args!='') xmlhttp.send(args);
    } else
      xmlhttp.send(null);

    if (!async) {
      this.status=xmlhttp.status;
      this.statusText=xmlhttp.statusText;
      if (this.status!=200) return null;
      this.response=xmlhttp.responseXML;
      return this.response;
    }
  };


horus.xmlhttp.prototype.option=
  function ( name ) {
    return this.options ? this.options.get(name) : undefined;
  };


horus.xmlhttp.prototype.reference    = function () { return this.option('reference') };
horus.xmlhttp.prototype.internal     = function () { return this.option('internal') };
horus.xmlhttp.prototype.arguments    = function () { return this._arguments };
horus.xmlhttp.prototype.callback     = function () { this.ajaxStatus(false) };
horus.xmlhttp.prototype.state        = function () { return this.xmlhttp.readyState };
horus.xmlhttp.prototype.responseText = function () { return this.xmlhttp.responseText };
horus.xmlhttp.prototype.responseXML  = function () { return this.xmlhttp.responseXML };


horus.xmlhttp.prototype.selectNodes=
  function ( xpath, from, asIterator ) {
    if (typeof from=='boolean') {
      asIterator=from;
      from=this.response;
    } else if (!from)
      from=this.response;

    var nodes;

    switch (horus.xmlhttp.supportlevel) {

    case 1: nodes=this.selectNodes$internal(xpath, from);    break;
    case 2: nodes=this.selectNodes$resolver(xpath, from);    break;
    case 3: nodes=this.selectNodes$unsupported(xpath, from); break;

    default:
      try {
	nodes=this.selectNodes$internal(xpath, from);
	horus.xmlhttp.supportlevel=1;
      } catch (err) {
	try {
	  nodes=this.selectNodes$resolver(xpath, from);
	  horus.xmlhttp.supportlevel=2;
	} catch (err) {
	  nodes=this.selectNodes$unsupported(xpath, from);
	  horus.xmlhttp.supportlevel=3;
	}
      }

    }

    if (asIterator) nodes=new horus.iterator(nodes);
    return nodes;
  };


horus.xmlhttp.prototype.selectNodes$internal=
  function ( xpath, from ) {
    return from.selectNodes(xpath);
  };


horus.xmlhttp.prototype.selectNodes$resolver=
  function ( xpath, from ) {
    var doc=this.response;
    var elem=(doc.ownerDocument || doc).documentElement;
    var resolver=doc.createNSResolver(elem);
    var nodes=[];

    var result=doc.evaluate
      (xpath, from, resolver, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);

    if (result) {
      var node=result.iterateNext();

      while (node) {
	nodes[nodes.length]=node;
	node=result.iterateNext();
      }
    }

    return nodes;
  };


horus.xmlhttp.prototype.selectNodes$unsupported=
  function ( xpath, from ) {
    // hack in some support for browsers that don't do xpath
    var node=this.selectSingleNode$unsupported(xpath, from);
    var elemname=xpath.replace(/^.*\//, '');
    var nodes=[];

    while (node) {
      nodes[nodes.length]=node;

      for (node=node.nextSibling;
	   node && typeof node=='object' && node.tagName!=elemname;
	   node=node.nextSibling);

    }
  };


horus.xmlhttp.prototype.selectSingleNode=
  function ( xpath, from ) {
    if (!from) from=this.response;

    switch (horus.xmlhttp.supportlevel) {

    case 1: return this.selectSingleNode$internal(xpath, from);
    case 2: return this.selectSingleNode$resolver(xpath, from);
    case 3: return this.selectSingleNode$unsupported(xpath, from);

    default:
      var node;

      try {
	node=this.selectSingleNode$internal(xpath, from);
	horus.xmlhttp.supportlevel=1;
      } catch (err) {
	try {
	  node=this.selectSingleNode$resolver(xpath, from);
	  horus.xmlhttp.supportlevel=2;
	} catch (err) {
	  node=this.selectSingleNode$unsupported(xpath, from);
	  horus.xmlhttp.supportlevel=3;
	}
      }

      return node;
    }
  };


horus.xmlhttp.prototype.selectSingleNode$internal=
  function ( xpath, from ) {
    return from.selectSingleNode(xpath);
  };


horus.xmlhttp.prototype.selectSingleNode$resolver=
  function ( xpath, from ) {
    var doc=this.response;
    var elem=(doc.ownerDocument || doc).documentElement;
    var resolver=doc.createNSResolver(elem);

    var result=
      doc.evaluate(xpath, from, resolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null);

    return result && result.singleNodeValue ? result.singleNodeValue : null;
  };


horus.xmlhttp.prototype.selectSingleNode$unsupported=
  function ( xpath, from ) {
    // hack in some support for browsers that don't do xpath
    var path=xpath.split('/', true);
    if (path[0]=='') path.shift();
    if (from.tagName!=path[0]) return null;

    while (from && path.length>1) {
      path.shift();

      for (from=from.firstChild;
	   from && typeof from=='object' && from.tagName!=path[0];
	   from=from.nextSibling);

    }

    return from;
  };


horus.xmlhttp.prototype.getAttribute=
  function ( node, item, missing, datatype ) {
    if (node==null || typeof node=='string') {
      if (!node) node=this.name;
      node=this.selectSingleNode(node);
      if (node==null) return undefined;
    }

    var result=node.attributes.getNamedItem(item);
    if (horus.isNull(result, true)) return missing==undefined ? null : missing;
    result=result.value.trim();

    if (!datatype && missing!=null)
      switch (missing.constructor) {

      case Boolean:	datatype='Boolean';			break;
      case Number:	datatype='numeric';			break;
      case Array:	datatype='list';			break;
      case Date:	datatype='date';			break;
      case Function:	datatype='code';			break;

      }

    if (datatype)
      switch (datatype) {

      case 'Boolean':	result=horus.toBoolean(result);		break;
      case 'numeric':	result=Number(result);			break;
      case 'list':	result=result.split(',', true);		break;
      case 'date':	result=horus.toDate(result, missing);	break;
      case 'code':	result=horus.asObject(result, missing);	break;
      case 'eval':	result=horus.eval(result, missing);	break;

      }

    return result;
  };


horus.xmlhttp.prototype.getChildText=
  function ( node ) {
    var missing, substitute, submissing, result;
    if (typeof node=='string') node=this.selectSingleNode(node);

    if (arguments.length>2) {
      missing=arguments[1];
      substitute=arguments[2];
      submissing=arguments[3];
    } else if (arguments.length>1)
      if (horus.isString(arguments[1]))
	missing=arguments[1];
      else
	substitute=arguments[1];

    for (node=node.firstChild; node; node=node.nextSibling)
      if (node.nodeType==3)
	if (result==null)
	  result=node.data;
	else
	  result+=node.data;

    if (result==null)
      result=missing;
    else if (substitute && !substitute.isEmpty()) {
      if (submissing==null) submissing='';

      result=result.replace
	(horus.xmlhttp.$substitutepattern,
	 function ( m, t ) { return t in substitute ? substitute[t] : submissing });

    }

    return result;
  };


horus.xmlhttp.prototype.ajaxStatus=
  function ( options ) {
    var ok=false;
    var status;

    try {
      if (this.status!=200)
	status='horus.xmlhttp request error: '+this.statusText+' ('+this.status+')';
      else {
	status=this.getAttribute(this.name, 'status', '');
	ok=this.getAttribute(this.name, 'ok', true);
      }
    } catch ( err ) {
      status='protocol error ('+horus.toString(err)+')';
    }


    options=horus.options(options, this.options, 'clearstatus', ';');
    if (options.get('clearstatus')) horus.status.clear();

    if (status!='') {
      var show=options.get('showstatus', true);
      var get=options.get('getstatus', !show);

      if (show)
	horus.status.at({ reference: options.get('reference'), focus: !ok }, status);

      if (get) {
	ok=new Boolean(ok);
	ok.status=status;
      }
    }

    return ok;
  };


horus.xmlhttp.encode=
  function ( item ) {
    switch (horus.typeOf(item)) {

    case 'string':
      item='"'+item.replace(/\\/g, '\\\\').replace(/"/g, '\\"')+'"'; //"
      break;

    case 'number':
    case 'boolean':
      item=item.toString();
      break;

    case 'undefined':
      item='"'+horus.NUL+'"';
      break;

    case 'object':
      if (item==null)
	item='"'+horus.NUL+'"';
      else if (item instanceof Array) {
	for (var i=0; i<item.length; i++) item[i]=horus.xmlhttp.encode(item[i]);
	item='['+item.join(',')+']';
      } else {
	var to=[];

	for (var key in item)
	  if (item.hasOwnProperty(key))
	    to.push(horus.xmlhttp.encode(key)+':'+horus.xmlhttp.encode(item[key]));

	item='{'+to.join(',')+'}';
      }

      break;

    default:
      throw "can't encode a "+horus.typeOf(item);

    }

    return item;
  };


horus.ajax=
  function ( url, action, argv, callback, options ) {
    var name;

    if (url!=null)
      if (url instanceof Array) {
	name=url[0];
	url=url[1];
      } else if (typeof url=='string' && !/\./.test(url)) {
	name=url;
	url=null;
      }

    var xmlhttp=new horus.xmlhttp(name);

    if (url==null) {
      var location=document.location;

      url=
	location.protocol+'//'+location.host+
	location.pathname.replace(/^\/(local)?.*admin\//, '/$1action/');

    } else if (typeof url=='number' || /^[0-9]+$/.test(url))
      url=horus.ajax.action[url];

    xmlhttp.options=horus.options(options, horus.ajax.options, 'debug', ';');
    xmlhttp._arguments=argv;

    var upload=xmlhttp.options.get('upload');

    if (upload && typeof upload!='boolean') {
      var fields=upload.split(',', 'trim');
      upload=false;

      for (var i=0; i<fields.length; i++)
	if (argv[fields[i]]) {
	  upload=true;
	  break;
	}

    }

    if (upload)
      xmlhttp.upload(action, callback);
    else {
      var debug=xmlhttp.options.get('debug');
      if (debug) debug=[ 'horus.ajax: '+url+' - _action='+action ];
      var args=[ '_action='+encodeURIComponent(action) ];

      if (!(argv && 'action' in argv))
	args.push('action='+encodeURIComponent(action)); // deprecated

      if (argv) {
	var encode=xmlhttp.options.get('encode', false);
	var encodeall=encode && typeof encode=='boolean';

	if (encodeall)
	  encode={};
	else if (encode && typeof encode=='string')
	  encode=new horus.hash(encode);

	for (var tag in argv)
	  if (argv.hasOwnProperty(tag)) {
	    var item=argv[tag];

	    if (encodeall || encode && tag in encode) {
	      if (encodeall) encode[tag]=true;
	      item=horus.xmlhttp.encode(item);
	    } else
	      if (item==null)
		item=horus.NUL;
	      else if (item instanceof Array)
		item=item.join(',');

	    if (debug)
	      debug.push
		(tag+'='+(item.replace ? item.replace(/,([^ ])/g, ', $1') : item));

	    args.push(tag+'='+encodeURIComponent(item));
	  }

	if (encode) args.push('_encode='+horus.keys(encode).join(','));
      }

      if (debug) alert(debug.join('\n'));
      var synchronous=xmlhttp.options.get('synchronous');

      if (synchronous) {
	synchronous=callback;
	callback=null;
      }

      xmlhttp.request([ url, args ], 'POST', callback);

      if (!callback)
	if (synchronous)
	  horus.call(synchronous, xmlhttp, xmlhttp._arguments);
	else if (!xmlhttp.ajaxStatus())
	  return null;

    }

    return xmlhttp;
  };


horus.ajax.options={ debug: false, synchronous: false, clearstatus: true };


horus.script.autoload
  ('upload',      '.xmlhttp.prototype.upload',
   'contentdata', '.xmlhttp.prototype.contentdata');


// backward compatibility...
horus.xmlhttp.prototype.data=horus.xmlhttp.prototype.arguments; // deprecated

window.FCKXml=horus.xmlhttp;

horus.xmlhttp.prototype.GetHttpRequest=function () { return this.xmlhttp };

horus.xmlhttp.prototype.LoadUrl=
  function ( url, callback ) { return this.request(url, 'GET', callback) };

horus.xmlhttp.prototype.SelectNodes=horus.xmlhttp.prototype.selectNodes;
horus.xmlhttp.prototype.SelectSingleNode=horus.xmlhttp.prototype.selectSingleNode;
horus.xmlhttp.prototype.GetAttribute=horus.xmlhttp.prototype.getAttribute;
horus.xmlhttp.prototype.GetChildText=horus.xmlhttp.prototype.getChildText;


horus.script.loaded('xmlhttp');
