OPTION_SEPARATOR = '-------------------';


/**
 * 
 * @param {String, Element} id
 * @return {ToolsObject}
 */
function x$(id) {  // the x is for extreme
  var to = new ToolsObject();
  return to.x$(id);
}
/**
 * @param e {event}
 * @returns {ToolObject}
 */
function xEvent(e) {
  var to = new ToolsObject();
  return to.setEvent(e);
}
/**
 * Do actions based on ajax response
 * @param data {array}
 */
function xAjaxAction(data) {
  switch(data[0]) {
    case 'set':
      x$(data[1]).setValue(data[2]);
      break;
    case 'eval':
      eval(data[1]);
      break;
  }
}

_xToolsGlobalVars = new Object();

function ToolsSearchObject(type,elemId,classNames) {
  this.type = type.toUpperCase();
  this.elemId = elemId;
  this.classes = new Array();
  if(classNames)
    this.classes = classNames.split(' ');
  //console.log(this.classes);
  
  this.findm = function(inArray) { // find from array of elements
    var elemArray = new Array();
    for(var i in inArray) {
      elemArray = this.appendArray(elemArray, this.find(x$(inArray[i])));
    }
    return elemArray;
  }
  /**
   * @param root {ToolObject} starting node
   * @returns {ToolOject}
   */
  this.findUp = function(root) { // root is a ToolObject
    if(this.match(root.element()))
      return root;
    else if(root.up().isDomTop())
      return false;
    else
      return this.findUp(root.up());
  }
  this.find = function(root) { // root is a ToolObject
    if(!root.element()) return new Array();
    var elemArray = new Array();
    if(this.match(root.element()))
      elemArray.push(root.element());
    var x = root.children().elements();
    if(x)
      elemArray = this.appendArray(elemArray, this.findm(x));
    return elemArray;
  }
  this.appendArray = function(orgArr,newArr) {
    for(var i in newArr) {
      orgArr.push(newArr[i]);
    }
    return orgArr;
  }
  this.match = function(el) {
    //console.log(el);
    //console.log('Type:"'+this.type+'"  "'+el.nodeName+'"');
    //console.log('Id:'+(this.elemId?this.elemId:'-')+'  '+(el.id?el.id:'-'));
    //console.log('Class:'+this.classes+'  '+(el.className?el.className:'-'));
    $res = true;
    if(this.type && el.nodeName != this.type) {
      //console.log('Type Failed');
      return false;
    } 
    if(this.elemId && el.id != this.elemId) {
      //console.log('ID Failed');
      return false;
    }
    if(this.classes.length > 0 && !this.classMatch(el.className)) {
      //console.log('Class Failed');
      return false;
    }
    //console.log('Matches');
    return true;
  }
  /**
   * 
   */
  this.classMatch = function(cls) {
    if(cls.indexOf(' ')!=-1) {
      var clss = cls.split(' ');
      var matches = 0;
      var matchedClasses = {};
      for(var i in clss) {
        for(var ii in this.classes) {
          if(clss[i]==this.classes[ii] && matchedClasses[clss[i]]==undefined) {
            matches++;
            matchedClasses[clss[i]] = 1;
          }
        }
      }
      if(matches == this.classes.length)
        return true;
    } else {
      for(var i in this.classes) {
        if(cls == this.classes[i]) {
          return true;
        }
      }
    }
    return false;
  }
}

_xToolsGlobalVars = new Object();

/**
 * @classDescription tools
 * @return {ToolsObject}
 * @constructor
 */
function ToolsObject() { 
  this.type = '';
  this.targetElem = false;
  this.targetElemIdRaw = false;
  this.elemArray = false;
  this.eventObj = false
  this.bResult = true;
  this._setValueFormat = false;
  this._getValueNumber = false;
  this._getValueURLEncoded = false;
  this._extraFormData = new Object();
  
//__________________________________________________________________________________________________
//                                                                                         Debugging
  this.log = function(str) {
    this.element().innerHTML = this.element().innerHTML + str + '\x0a';
  }
//__________________________________________________________________________________________________
//                                                                                           Setters
  this.x$ = function(id) {
    if(typeof(id)=='string') {
      if(id == "*document")
        this.targetElem=document;
      else {
        this.targetElem=document.getElementById(id);
        this.targetElemIdRaw=id;
      }
    } else if(typeof(id)=='object') {
      this.targetElem=id;
    }
    return this;
  };
  this.setEvent = function(e) {
    this.eventObj = e;
    return this;
  }
//__________________________________________________________________________________________________
//                                                                                       Global Vars
  // elem is optional
  this.setGlobal = function(name,value,elem) {
    if(elem!=undefined) {
      elem = x$(elem).identify();
      _xToolsGlobalVars = this._setObjectIfNotSet(_xToolsGlobalVars, elem);
      _xToolsGlobalVars[elem][name] = value;
    } else {
      _xToolsGlobalVars[name] = value;
    }
  }
  // elem is optional
  this.pushGlobal = function(name,value,elem) {
    if(elem!=undefined) {
      elem = x$(elem).identify();
      _xToolsGlobalVars = this._setObjectIfNotSet(_xToolsGlobalVars, elem);
      _xToolsGlobalVars[elem] = this._setArrayIfNotSet(_xToolsGlobalVars[elem], name);
      _xToolsGlobalVars[elem][name] = value;
    } else {
      this._setArrayIfNotSet(_xToolsGlobalVars, name);
      _xToolsGlobalVars[name].push(value);
    }
  }
  // elem is optional
  this.getGlobal = function(name,elem) {
    try {
      if(elem!=undefined) {
        elem = x$(elem).identify();
        return _xToolsGlobalVars[elem][name];
      }
      return _xToolsGlobalVars[name];
    } catch(e) { return false; }
  }
  this._setObjectIfNotSet = function(baseObj,newElem) {
    if(typeof baseObj[newElem] != 'object') {
      baseObj[newElem] = new Object();
    }
    return baseObj;
  }
  this._setArrayIfNotSet = function(baseObj,newElem) {
    if(typeof baseObj[newElem] != 'object') {
      baseObj[newElem] = new Array();
    }

    return baseObj; 
  }
//__________________________________________________________________________________________________
//                                                                                          Elements
  this.exists = function() {
    if(this.targetElem)
      return true;
    return false;
  }
  this.element = function() {
    return this.targetElem;
  }
  this.elements = function() {
    return this.elemArray;
  }
  this.identify = function() {
    if(typeof this.element().id == 'string')
      return this.element().id;
    else if(this.element() == document)
      return "*document";
    
    rand = this.getGlobal('randomId');
    rand++
    this.setGlobal('randomId',rand);
    this.element().id = Math.floor(Math.random()*1000)+'-'+rand;
    return this.element().id;
  }
  /**
   * Move up DOM tree 1 branch
   */
  this.up = function() {
    var newTarget = this.element()!=document && this.element()!=undefined ? this.element().parentNode : document;
    return x$(newTarget);
  }
  this.children = function(nodeName) {
    nodeName = nodeName == undefined ? false : nodeName.toUpperCase();
    if(this.elemArray) {
      var c = new Array();
      for(var i in this.elemArray) {
        var ec = this.elemArray[i].childNodes;
        for(var j in ec) {
          c.push(ec[j]); 
        }
      }
    } else {
      var c = this.targetElem.childNodes;
    }
    this.elemArray = [];
    for(var i in c) {
      try { cNodeName = c[i].nodeName }
      catch(e) { continue; }
      if(cNodeName == nodeName || !nodeName) {
        this.elemArray.push(c[i]);
      }
    }
    return this;
  };
  this.descendentsByName = function(nodeName) {
    nodeName = nodeName.toUpperCase();
    this.elemArray = this.targetElem.getElementsByTagName(nodeName);
    return this;
  }
  this.byClassName = function(c) {
    if(this.elemArray) {
      var searchElems = [];
      for(var i=0;i<this.elemArray.length;i++) {
        var elms = this.elemArray[i].childNodes;
        for (var ei in elms) {
          searchElems.push(elms[ei]);
        }
      }
    } else if(this.targetElem) {
      var searchElems = this.targetElem.childNodes;
    }
    this.elemArray = new Array;
    
    var regexPat = new RegExp("(^|\\s)" + c + "(\\s|$)");
    for(var i = 0; i < searchElems.length; i++) {
      if (regexPat.test(searchElems[i].className)) {
        this.elemArray.push(searchElems[i]);
      }
    }
    return this;
  }
  this._apply = function(fnt,data) {
    if(this.elemArray) {
      for(var i=0;i<this.elemArray.length;i++) {
        fnt(this.elemArray[i],data);
      }
    } else if(this.targetElem) {
      fnt(this.targetElem,data);
    } 
  }
  this._match = function(a,b) {
    if(typeof(b) == 'object' || typeof(b) == 'array') {
      for(var i in b) {
        if(a==b[i]) {
          return true;
        }
      } 
    } else {
      return a == b;
    }
    return false;
  }
  this.nodeName = function() {
    return this.element().nodeName.toLowerCase();
  }
  this._contains = function(a,b) {
    if(typeof(b) == 'object' || typeof(b) == 'array') {
      for(var i in b) {
        if(a.match(re = new RegExp("( |^)"+b[i]+"( |$)"))) {
          return true;
        }
      } 
    } else {
      return a.match(re = new RegExp("( |^)"+b+"( |$)"));
    }
    return false; 
  }
  this.hasClass = function(cls) {
    if(this.bResult == false) return false; 
    return this._contains(this.targetElem.className,cls);
  }
  // hide element[s]
  this.hide = function() {
    this._apply(this._eDisplay,false);
    return this;
  }
  
  // show element[s]
  this.show = function(display) {
    if (typeof(display) == 'undefined')
      display = true;
    this._apply(this._eDisplay,display);
    return this;
  }
  
  this._eDisplay = function(elem, display) {
    if (typeof(display) == 'boolean')
      display = display ? '' : 'none';
    elem.style.display=display; 
  }

  /**
   * set link for <a> tag[s]
   */
  this.setLink = function(link) {
    this._apply(this._eLink,link); 
    return this;
  }
  /**
   * get link for <a> tag[s]
   */
  this.getLink = function(link) {
    if(this.nodeName()=='a')
      return this.element().href;
    else
      return this.find('a').first().getLink();
  }
  this.appendLink = function(link) {
    this._apply(this._eAppendLink,link); 
    return this;
  }
  this.first = function() {
    this.targetElem = this.elemArray[0];
    return this;
  }
  this.setFormat = function(format) {
    this._setValueFormat = format;
    return this;
  }
  this.str_repeat = function(i, m) { for (var o = []; m > 0; o[--m] = i); return(o.join('')); }
  this._formatValue = function() {
    var i = 0, a, f = arguments[i++], o = [], m, p, c, x;
    while (f) {
      if (m = /^[^\x25]+/.exec(f)) o.push(m[0]);
      else if (m = /^\x25{2}/.exec(f)) o.push('%');
      else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) {
        if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) throw("Too few arguments.");
        if (/[^s]/.test(m[7]) && (typeof(a) != 'number'))
          throw("Expecting number but found " + typeof(a));
        switch (m[7]) {
          case 'b': a = a.toString(2); break;
          case 'c': a = String.fromCharCode(a); break;
          case 'd': a = parseInt(a); break;
          case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
          case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
          case 'o': a = a.toString(8); break;
          case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
          case 'u': a = Math.abs(a); break;
          case 'x': a = a.toString(16); break;
          case 'X': a = a.toString(16).toUpperCase(); break;
        }
        a = (/[def]/.test(m[7]) && m[2] && a > 0 ? '+' + a : a);
        c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
        x = m[5] - String(a).length;
        p = m[5] ? this.str_repeat(c, x) : '';
        o.push(m[4] ? a + p : p + a);
      }
      else throw ("Huh ?!");
      f = f.substring(m[0].length);
    }
    return o.join('');
  }
  this.setValue = function(value) {
    var elem = this.targetElem;
    if(this._setValueFormat)
      value = this._formatValue(this._setValueFormat,value);
    try {
      nodeName = elem.nodeName;
    } catch(e) {
      return false;
    }
    
    switch(elem.nodeName.toLowerCase()) {
     case 'input':
      switch(elem.type) {
        case 'checkbox':
          elem.checked = value!=0 && value!=false  ? true : false;
          break;
        default:
          elem.value = value;
          break;
      }
     break;  
     case 'select':
      this._setSelectValue(value);
     break;
     default:
       var content = this._seperateJS(value);
       elem.innerHTML = content[0];
       if(content[1]) {
        eval(content[1]);
      }
    }
    return this;
  }
  this._setSelectValue = function(value) {
    var elem = this.targetElem;
    for(var i = 0; i < elem.options.length; i++) {
    if( elem.options[i].value == value) {
         elem.selectedIndex = i;
      }
    }
  }
  this.setAttribute = function(attr, value) {
    this.targetElem.setAttribute(attr, value);
    return this;
  }
  this.getAttribute = function(attr) {
    return this.targetElem[attr];
  }
  this.number = function() {
    this._getValueNumber = true;
    return this;
  }
  this.URLEncoded = function() {
    this._getValueURLEncoded = true;
    return this;
  }
  this.textOnly = function() { // Strip Tags from GetValue
    this._getValueTextOnly = true;
    return this;
  }
  this.getValue = function(value) {
    if(this.targetElem==null) return false;
    if(!this.bResult) return false;
    var elem = this.targetElem;

/* @TODO add getValueData/Time functions  
    if((typeof elem == 'undefined' || elem == null) && ID(elem_raw+'year')) 
      return getValueDate(elem_raw);
    if((typeof elem == 'undefined' || elem == null) && ID(elem_raw+'hour')) 
      return getValueTime(elem_raw);
    if(typeof elem == 'undefined' || elem == null)
      return false;
*/
//    alert('tools.js line 262');    
    if(!elem.nodeName)
      return false;
      
    var result = '';
    switch(elem.nodeName.toLowerCase()) {
      case 'input':
        switch(elem.type) {
          case 'checkbox':
            return elem.checked ? elem.value : false;
          case 'radio':
            var frm = this.ancestorByType('form').element();
            var nme = elem.name;
            var elems = (eval('frm.'+nme));
            for(var i=0;i<elems.length;i++) {
              if(typeof elems[i] == 'object') {
                if(elems[i].checked) {
                  result = elems[i].value;
                  break;
                }
              }
            }
            break;
          default:
            result = elem.value;
        }
      break;  
      case 'select':
        if(elem.options.length==0) return false;
        if(elem.multiple) {//getAttribute('multiple')=='multiple') {
          selected = new Array(); 
            for (var i = 0; i < elem.options.length; i++) 
              if (elem.options[ i ].selected) 
                selected.push(elem.options[ i ].value);
          //alert(selected);
          result = selected;
       }
       return elem.options[elem.selectedIndex].value;
      break;   
      case 'span':
      case 'h1':
      case 'h2':
      case 'h3':
      case 'h4':
        result = elem.innerHTML;
        break;
      case 'textarea':
        result = elem.value;
      break;
  
    }
    if(elem.disabled)
      result = '';
    
    if(this._getValueNumber) {
      result = result.replace(/([^0-9\.])/g,'');
      result = parseFloat(result);
      if(isNaN(result))
        result = 0;
      return result;
    }
    if(this._getValueTextOnly) { // Strips Tags
      result = result.replace(/<([^>]*)>/g,'');
    }
    if(this._getValueURLEncoded) { // Strips Tags
      result = this.URLEncode(result);
    }
    return result;
  }
  /**
   * Splits str into HTML and Javascript
   * @param str {string} HTML that may include javascript
   * @returns {array} [HTML,Javascript]
   */
  this._seperateJS = function(str) {
    if(typeof(str) != 'string') str = ''+str;
    var scripts = str.split(new RegExp("<script[^>]*text/javascript[^>]>|</script>",'gi'));
    var scriptType = false;
    var FinalValue = '';
    var ScriptContent = '';
    
    var c=0;
    for(var i=0;i<scripts.length;i++) {
      // Firefox includes the seperator in the array produced from split
//      if(scripts[i].indexOf('<script')!=-1 || scripts[i].indexOf('</script>')!=-1)
//        continue;
      if(c%2==0)
        FinalValue += scripts[i];
      else
        ScriptContent += scripts[i];
      c++;
    }
    return [FinalValue, ScriptContent];
  }
  
  this._eLink = function(elem,link) {
    if(x$(elem).nodeName=='a')
      elem.href=link;
    else
      x$(elem).select('a').first().element().href=link;
  }
  this._eAppendLink = function(elem,link) {
    if(elem.href.indexOf('?')!=-1) {
      elem.href=elem.href+link.replace('?','&');  
    } else {
      elem.href=elem.href+link;
    }
  }
  // set focus to targetElem or first element in elemArray
  this.focus = function() {
    if (this.elemArray) {
      this.elemArray[0].focus();
    } else if (this.targetElem) {
      this.targetElem.focus();
    }
    return this;
  }
  this.elemValues = [];
  // returns single value or values in array (if elemArray is set)
  this.value = function() {
    this._apply(this._value);
    return this.elemArray ? this.elemValues : this.elemValues[0];
  }
  this._value = function(elem) {
    var v = false;
    if (elem.type == 'radio')
      v = elem.checked;
    else
      v = elem.value;
    this.elemValues.push(v);
  }
  this.ancestorByType = function(nodeName) {
    while(true) {
      try { var elemNodeName = this.targetElem.nodeName; }
      catch(e) { this.bResult = false; return this; }
      if(typeof elemNodeName == 'string' && elemNodeName.toLowerCase() == nodeName.toLowerCase()) {
        return this;
      } 
      if(this.targetElem == window)  {
        this.targetElem = window
        return this;
      }
      this.targetElem = this.targetElem.parentNode;
    }
    this.bResult = false; return this; 
  }
  this.id = function() {
    return this.targetElem.id;
  }
  this.getElement = function() {
    return this.targetElem;
  }
  this._getSearchSet = function(str) {
    var typeRegex = new RegExp("(^[^#\.]*)");
    var idRegex = new RegExp("(#.*)");
    var classRegex = new RegExp("(\..*)");
    var searchSet = new Array();
    var max=0;
    while(str.length > 0 || max++>5) {
      var elType = '';
      var elId = '';
      var elClass = '';
      if(str.match(typeRegex)) {
        var elType = str.match(typeRegex)[0];
        str = str.replace(typeRegex,'');
      } 
      if(str.match(idRegex)) {
        var elId = str.match(idRegex)[0];
        str = str.replace(idRegex,'');
      } 
      if(str.match(classRegex)) {
        var elClass = str.match(classRegex)[0];
        str = str.replace(classRegex,'');
      }
      searchSet.push(new ToolsSearchObject(elType,elId.substr(1),elClass.substr(1)));
    }
    return searchSet;
  }
  this.select = function(str) {
    var searchSet = this._getSearchSet(str);
    for(var i in searchSet) {
      if(!this.elemArray) {
        this.elemArray = searchSet[i].find(this);
      } else {
        this.elemArray = searchSet[i].findm(this.elemArray);
      }
    }
    return this;
  }
  this.selectUp = function(str) {
    var searchSet = this._getSearchSet(str);
    for(var i in searchSet) {
      return searchSet[i].findUp(this);
    }
    return this;
  }
  this.isDomTop = function() {
    return this.element() == document;
  }
//__________________________________________________________________________________________________
//                                                                                          Position
  this.getSize = function() {
    return {
      width:this._w(),
      height:this._h()
    };
  }
  /**
   * Moves and Resizes current object to position and dimensions of elem
   * 
   * @param elem {HTMLElement}
   * @param options {Object} defaults {'offsetX':0,'offsetY':0,'matchWidth':true,'matchHeight':true}
   */
  this.clonePosition = function(elem) {
    //console.log('clonePosition');
    options = arguments.length>1 ? arguments[1] : {'offsetX':0,'offsetY':0,'matchWidth':true,'matchHeight':true};
    if(typeof elem.targetElement == undefined) {
      var elem = x$(elem);      
      var elemRef = x$(elem).getPositionRef();
    }
    var pos = elem.getPosition();
    //console.log(pos);
    //console.log(this.element().offsetParent);
    var ref = this.getPositionRef().getPosition();
    var pos = this.pointSub(pos,ref);
    var size = elem.getSize();
    //console.log(this.getPositionRef().getStyle('position'));
    //console.log(this.getPositionRef().element());
    //console.log(ref);
    //console.log(pos);

    pos.x += (options.offsetX==undefined)?0:options.offsetX;
    pos.y += (options.offsetY==undefined)?0:options.offsetY;
    this.setPosition(pos);
    this.setSize(elem.getSize(),{setWidth:(options.matchWidth==undefined)?true:options.matchWidth,setHeight:(options.matchHeight==undefined)?true:options.matchHeight});
    this.setStyle('position','absolute');
    return this;
  }
  /**
   * Gets the global position of the current element
   */
  this.getPosition = function() {
    if(this.element() == null) return {x:0,y:0};
    var x = this._x();
    var y = this._y();
    x = isNaN(x) ? 0 : x;
    y = isNaN(y) ? 0 : y;
    var parent = this.getPositionRef();
    if(!parent.isDomTop() && parent.element()!=null && parent.element()!=false) {
      var pos = parent.getPosition();
      x+=pos.x;
      y+=pos.y;
      parent = parent.getPositionRef();
    }
    return {
      x:x,
      y:y
    };
  }
  /**
   * Set Position of element
   */
  this.setPosition = function(point) {
    this.setStyle('top',point.y);
    this.setStyle('left',point.x);
  }
  /**
   * Set size of element
   */
  this.setSize = function(size) {
    var options = arguments.length>1 ? arguments[1] : {setWidth:true,setHeight:true};
    if(options.setWidth==true || options.setWidth==undefined)
      this.setStyle('width',size.width);
    if(options.setHeight==true || options.setHeight==undefined)
      this.setStyle('height',size.height);
  }
  /**
   * Return left offset
   */
  this._x = function() {
    return this.element().offsetLeft;
  }
  /**
   * Return Top Offset
   */
  this._y = function() {
    return this.element().offsetTop;
  }
  /**
   * Return Width
   */
  this._w = function() {
    return this.element().offsetWidth;
  }
  /**
   * Return Height
   */
  this._h = function() {
    return this.element().offsetHeight;
  }
  this.isPositioned = function() {
    //console.log(this.element());
    //console.log(this.getStyle('position'));
    return this.getStyle('position');
  }
  this.getPositionRef = function() {
    //console.log('getRef');
    //console.log(this.element())
    if(this.element() == null || this.isDomTop()) return x$(document);
    var ref = x$(this.element().offsetParent);
    if(!ref.isPositioned())
      return ref.getPositionRef();
    
    return ref;
  }
  this.pointSub = function(p1,p2) {
    return {
      x: p1.x-p2.x,
      y: p1.y-p2.y
    }
  }
//__________________________________________________________________________________________________
//                                                                                               CSS
  this.setStyle = function(style,value) {
    value = value+'';
    var px = 'top,left,right,bottom,width,height';
    if(px.indexOf(style)!=-1 && value.indexOf(/%|px/)==-1)
      value+='px';
    this.targetElem.style[style] = value;
  }
  this.getStyle = function(style) {
    if(this.element() == null) return false;
    try {
      if(window.getComputedStyle)
        var value = window.getComputedStyle(this.element(),'').getPropertyValue(this.jsStyleToCssStyle(style));
      else if(document.defaultView && document.defaultView.getComputedStyle) {
        var value = document.defaultView.getComputedStyle(this.element(),'').getPropertyValue(this.jsStyleToCssStyle(style));
      }
      else if(this.element().currentStyle) {
        
        //alert(this.element().currentStyle['backgroundImage']);
        //alert(this.jsStyleToCssStyle(style));
        var value = this.element().currentStyle[this.cssStyleToJsStyle(style)];
      } else
        return false;
      return value;
    } catch(e) { }
  }
  this.jsStyleToCssStyle = function(style) {
    str = style.replace( /[A-Z]/g,
      function( match, char ) {
        return "-" + char.toLowerCase(); 
      }
    );
    return str;
  }
  this.cssStyleToJsStyle = function(style) {
    str = style.replace( /(-)([a-z])/g,
      function( match, dash, char ) {
        return char.toUpperCase(); 
      }
    );
    return str;
  }
  this.removeClassName = function(strClassName) {
    this.element().className = this.element().className.replace(new RegExp('(.*\\s|^)('+strClassName.replace('-','\\-')+')(\\s.*|$)','g'),'$1$3');
  }
  this.addClassName = function(strClassName) {
    this.element().className = this.element().className + ' ' + strClassName;
  }
//__________________________________________________________________________________________________
//                                                                                              Form
  /**
   * usage onfocus="x$(this).autoHideValue();"
   * 
   */
  this.autoHideValue = function() {
    if(!this.getGlobal('originalValue',this.element()))
      this.setGlobal('originalValue',this.getValue(),this.element());
    this.observe('blur',function() { if(x$(this).getValue()=='') x$(this).setValue(x$(this).getGlobal('originalValue',this)); });
    this.setValue('');
  }
//__________________________________________________________________________________________________
//                                                                                            Select
  this.clearOptions = function() {
    this.targetElem.options.length = 0;
    return this;
  }
  this.fillOptions = function(opts, currVal) {
    for(var i in opts) {
      var idx = this.targetElem.options.length;
      if(opts[i] == '-')
        opts[i] = OPTION_SEPARATOR;
      var opt = new Option(opts[i],i);
      if(opts[i] == OPTION_SEPARATOR )
        opt.disabled = true;
      if (opt.value == currVal) opt.selected = true;
      this.targetElem.options[idx] = opt;
    } 
    return this;
  }
  /**
   * Returns all available values from select box
   *
   * @returns {array} values for each option in select box
   */
  this.allOptionValues = function() {
    var values = new Array();
    var options = this.element().options;
    for(var i=0;i< options.length; i++) {
      values.push(this.element().options[i].value);
    }
    return values;
  }
  
//__________________________________________________________________________________________________
//                                                                                            Select
  ///**
  // * Clears options form current select box
  // */
  //this.clearOptions = function() {
  //  this.targetElem.options.length = 0;
  //  return this;
  //}
  ///**
  // * Add (opts) to select box
  // * @param opts {object} {key:value,...}
  // */
  //this.fillOptions = function(opts) {
  //  for(var i in opts) {
  //    this.targetElem.options[this.targetElem.options.length] = new Option(opts[i],i);
  //  } 
  //  return this;
  //}
  ///**
  // * Returns all available options from select box
  // */
  //this.allOptionValues = function() {
  //  try {
  //    //a/lert(x$(this.element()).selectUp('form').element());
  //    //var options = document.forms[x$(this.element()).selectUp('form').element().name][this.element().name].options;
  //    return x$(this.element().options).fromObject('value');
  //    //return x$(options).fromObject('value');
  //  } catch(e) {
  //    //alert(this.element().options[1].value);
  //    //alert(e);
  //    return [];
  //  }
  //}
//__________________________________________________________________________________________________
//                                                                                  Arrays / Objects

  /**
   * Find needle in current object
   * @param needle {*} search element
   * @returns {bool} true if found otherwise false (false on error also)
   */
  this.contains = function(needle) {
    if(this.element() instanceof Array && typeof this.element() != 'object')
      return false;
    
    for(var i in this.element())
      if(this.element()[i] == needle)
        return true;
    return false;
  }
  /**
   * Combine new Array (arr) with current array and return Combined Array
   * @param arr {array}
   * @return {array}
   */
  this.combine = function(arr) {
    if(this.element() instanceof Array && typeof this.element() != 'object')
      return {};
        
    if(this.element() instanceof Array) {
      for(var i in arr) {
        this.element().push(arr[i]);
      }
    } else {
      for(var i in arr) {
        this.element()[i] = arr[i];
      }
    }
    return this;
  }
  this.keys = function() {
    var keys = [];
    for(var i in this.element())
      keys.push(i);
    return keys;
  }
  /**
   * 
   */
  this.fromObject = function($f) {
    var values = [];
    for(var i in this.element())
      keys.push(i[$f]);
    return keys;
  }
  /**
   * Run fnt on each element
   */
  this.each = function(fnt) {
    if(this.element() instanceof Array)
      for(var i in this.element())
        fnt(this.element()[i], i);
    else if(this.elements())
      for(var i in this.elements())
        fnt(this.elements()[i], i);
    else
      for(var i in this.element())
        fnt(this.element()[i], i);
  }
//__________________________________________________________________________________________________
//                                                                                             Event
  this.addEventListener = function(event,fnt) {
    this._apply(this._eEventAdd,{event:event,fnt:fnt});
    return this;
  }
  this.runEvents = function(e, type) {
    //console.log('runEvents');
    var fnts = this.getGlobal('events_'+xEvent(e).eventType(),this.element());
    for(var i in fnts) {
      fnts[i](e);
    }
  }
  this._eEventAdd = function(elem, data) {
    var eventFnts = this.getGlobal('events_'+data.event,elem);
    switch(data.event) {
      default:
        if(typeof(eventFnts) != 'array')
          eventFnts = [data.fnt];
        else
          eventFnts.push(data.fnt);
        this.setGlobal('events_'+data.event,eventFnts,elem);
        elem['on'+data.event] = function(e) { xEvent(e).runEvents(e) };
        break;
    }   
  }

  /**
   * Attach events to element[s].
   * 
   * @alias ToolsObject.observe
   * @param {Object} event
   * @param {Object} fnt
   */
  this.observe = function(event,fnt) {
    this._apply(this._eEvent,{event:event,fnt:fnt});
    return this;
  };
  
  this._eEvent = function(elem, data) {
    //new CJL_RegisterEvent(elem,data.event,data.fnt);
    
    switch(data.event) {
      case 'click':
        elem.onclick = data.fnt;
        break; 
      case 'load':
        new CJL_RegisterEvent(elem,data.event,data.fnt);
        break; 
      case 'ctrl+click':
        x$(document).observe('keypress',this.keys);
        elem.onclick = data.fnt;
        break;
      case 'focus':
        elem.onfocus = data.fnt;
        break; 
      case 'blur':
        elem.onblur = data.fnt;
        break; 
      case 'mouseover':
        elem.onmouseover = data.fnt;
        break; 
      case 'mouseout':
        elem.onmouseout = data.fnt;
        break; 
      case 'keypress':
        elem.onkeypress = data.fnt;
        break; 
      case 'keyup':
        elem.onkeyup = data.fnt;
        break; 
      case 'keydown':
        elem.onkeydown = data.fnt;
        break;
      default:
        throw new Error('unrecognised event: ' + data.event);
        break; 
    }    
  }
  this.hasEvent = function(event,fnt) {
    switch(event) {
      case 'click':
        return this._match(this.targetElem.onclick,fnt); 
      case 'mouseover':
        return this._match(this.targetElem.onmouseover,fnt); 
      case 'mouseout':
        return this._match(this.targetElem.onmouseout,fnt);
      default:
        throw new Error('unrecognised event: ' + data.event);
        break; 
    }  
  }

  // returns key
  this.eventKeyCode = function() {
    e = this.eventObj;
    var key;
    if (window.event) key = window.event.keyCode;
    else if (e) key = e.which | e.keyCode;  
    return key;
  }
  // sets targetElem returns toolsObject
  this.eventElem = function() {
    e = this.eventObj;
    var elem;
    if (!e) var e = window.event;
    if (e.target) elem = e.target;
    else if (e.srcElement) elem = e.srcElement;
    if (elem.nodeType == 3) // defeat Safari bug
      elem = targ.parentNode;
    this.targetElem = elem;  
    return this;
  }  
  this.eventType = function() {
    e = this.eventObj;
    if (!e) var e = window.event; 
    return e.type;
  }
//__________________________________________________________________________________________________
//                                                                                              AJAX
  this.asynchronous = false;
  this._preventFormSubmit = false;
  this.setFormDataExtras = function(obj) {
    this._extraFormData = obj;
    return this;
  }
  this.preventFormSubmit = function() {
    this._preventFormSubmit = true;
  }
  this._getNewFormData = function() {
    var elem = this.targetElem;
    var msgElem = arguments.length > 3 ? arguments[3] : false;
    // TODO: make internal GetAncestor
    var form = this.ancestorByType(this._submitFormType).element();
    if(!form)
      return false;
    
    if(this._preventFormSubmit)
      if(this._submitFormType=='form')
        form.onsubmit = function() { return false; }
      
    var data = this._extraFormData;
      
    var inputs = form.getElementsByTagName('input');
    var selects = form.getElementsByTagName('select');
    var textareas = form.getElementsByTagName('textarea');
    
    try {  data['id'] = form.id; }
    catch(e) { /* do nothing */ }
  
    for(var i =0; i<inputs.length; i++) {
      if(inputs[i]) {
        if(inputs[i].name && typeof inputs[i] == 'object') {
          var name = inputs[i].name.replace(/\[\]$/, '');
          if(name != inputs[i].name) {
            try {
              data[name].push(x$(inputs[i]).getValue());
            } catch(e) {
              data[name] = new Array();
              data[name].push(x$(inputs[i]).getValue());
            }
          } else {
            data[name] = x$(inputs[i]).getValue();
          }
        }
      }
    }
    for(var i =0; i<selects.length; i++) {
      if(selects[i]) {
        if(selects[i].name && typeof selects[i] == 'object') {
          data[selects[i].name] = x$(selects[i]).getValue();
        }
      }
    }
    for(var i =0; i<textareas.length; i++) {
      if(textareas[i]) {
        if(textareas[i].name && typeof textareas[i] == 'object') {
          data[textareas[i].name] = x$(textareas[i]).getValue();
        }
      }
    }
    data['__AjaxMsgElem'] = msgElem;
    this._submitFormData = data;
  }
  this.getFormData = function(type) {
    this._submitFormType = type;
    this._getNewFormData();
    return this._submitFormData;
  }
  this.submitFieldset = function(AjaxClass,AjaxFnt) {
    return this._submitX('fieldset',arguments);
  }
  this.submitDiv = function(AjaxClass,AjaxFnt) {
    return this._submitX('div',arguments);
  }
  this.submitTr = function(AjaxClass,AjaxFnt) {
    return this._submitX('tr',arguments);
  }
  this.submitForm = function(AjaxClass,AjaxFnt) {
    return this._submitX('form',arguments);
  }
  this._submitX = function(type,args) {
    if(!this._ajaxCheck()) return false;
    this._submitFormType = type;
    this._getNewFormData();
    if(args.length>2)
      this._submitForm(args[0],args[1],args[2]);
    else
      this._submitForm(args[0],args[1]);
    return false;
  }
  this._submitForm = function(AjaxClass,AjaxFnt) { //, callback
    var msg = {targetClass:AjaxClass,
                targetMethod:AjaxFnt,
                targetMethodArguments:this._submitFormData,
                callback:(arguments.length>2 ? arguments[2] : false),
                // busyIndicator:<bool>,
                asynchronous:this.asynchronous};
    sendAjaxMessage(msg);
  }
  this.setAsynchronous = function() {
    this.asynchronous = true;
    return this;
  }
  this._ajaxCheck = function() {
    // @TODO: make this work
    return true;
  }
  this.sendValue = function(AjaxClass,AjaxFnt,data) {
    if(!this._ajaxCheck()) return false;
    sendAjaxMessage(AjaxClass,AjaxFnt, {'value':this.getValue(),'data':data});
  }
  this.send = function(AjaxClass,AjaxFnt,data) {
    if(!this._ajaxCheck()) return false;
    sendAjaxMessage(AjaxClass,AjaxFnt, data);
  }
  
  this.URLEncode = function(inputStr) {
    // The Javascript escape and unescape functions do not correspond
    // with what browsers actually do...
    var SAFECHARS = "0123456789" +          // Numeric
            "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +  // Alphabetic
            "abcdefghijklmnopqrstuvwxyz" +
            "-_.!~*'()";          // RFC2396 Mark characters
    var HEX = "0123456789ABCDEF";
  
    var plaintext = inputStr;
    var encoded = "";
    for (var i = 0; i < plaintext.length; i++ ) {
      var ch = plaintext.charAt(i);
        if (ch == " ") {
          encoded += "+";       // x-www-urlencoded, rather than %20
      } else if (SAFECHARS.indexOf(ch) != -1) {
          encoded += ch;
      } else {
          var charCode = ch.charCodeAt(0);
        if (charCode > 255) {
            alert( "Unicode Character '" 
                          + ch 
                          + "' cannot be encoded using standard URL encoding.\n" +
                    "(URL encoding only supports 8-bit characters.)\n" +
                "A space (+) will be substituted." );
          encoded += "+";
        } else {
          encoded += "%";
          encoded += HEX.charAt((charCode >> 4) & 0xF);
          encoded += HEX.charAt(charCode & 0xF);
        }
      }
    } // for
  
    return encoded;
  }
//__________________________________________________________________________________________________
//                                                                                            CREATE
  this.createAppend = function(type,id) {
    var xdiv = document.createElement(type.toUpperCase());
    xdiv.id=id;
    this.targetElem.appendChild(xdiv);
    this.targetElem = xdiv;
    return this;
  }
  this.createBefore = function(type,id) {
    var xdiv = document.createElement(type.toUpperCase());
    xdiv.id=id;
    this.targetElem.parentNode.insertBofore(this.targetElem._parent, xdiv);
    this.targetElem = xdiv;
    return this;
  }
  this.createAfter = function(type,id) {
    var xdiv = document.createElement(type.toUpperCase());
    xdiv.id=id;
    var nextSib = this.targetElem.nextSibling;
    
    if(nextSib) {
      this.targetElem.parentNode.insertBefore(xdiv,nextSib);
    }
    else  {
      this.targetElem.parentNode.appendChild(xdiv);
    }
    this.targetElem = xdiv;
    return this;
  }
//__________________________________________________________________________________________________
//                                                                                            REMOVE
  this.remove = function() {
    this.element().parentNode.removeChild(this.element());
  }
//__________________________________________________________________________________________________
//                                                                                             Debug
  /**
   * toString of object for debuging
   */
  this.toString = function() {
    var str = '';
    if(this.element() instanceof Array) {
      str = '[';
      for(var i=0; i<5; i++) {
        str += this.element()[i]+',';
      }
      str = str.substring(0, -1)+']';
    } else {
      str = '{';
      str += 'element():'+this.element().nodeName.toLowerCase()+'#'+this.element().id+'.'+this.element().className;
      str += '}';
    }
    return str;
  }
}


/********************************************************
 * Copyright (C) 2002-2003, CodeHouse.com. All rights reserved.
 * CodeHouse(TM) is a registered trademark.
 *
 * THIS SOURCE CODE MAY BE USED FREELY PROVIDED THAT
 * IT IS NOT MODIFIED OR DISTRIBUTED, AND IT IS USED
 * ON A PUBLICLY ACCESSIBLE INTERNET WEB SITE.
 * 
 * CodeHouse.com JavaScript Library Module: Register Event Class
 *
 * You can obtain this script at http://www.codehouse.com
 ********************************************************/
function CJL_RegisterEvent(elem, type, listener, useCapture, noAutoStart)
{
   var proto = arguments.callee.prototype;

   this.e = elem;
   this.type = type;
   this.cap = useCapture;
   this.l = listener;


   proto.start = function() {
      if( this.e.attachEvent ) {
         this.e.attachEvent("on" + this.type, this.l);
      } else if( this.e.addEventListener ) {
         this.e.addEventListener(this.type, this.l, this.cap);
      } else {
        eval('e.on'+type+' = listener');
      }
   }

   if( ! noAutoStart ) {
      this.start(elem, type, listener);
   }

   proto.stop = function() {
      if( this.e.detachEvent ) {
          this.e.detachEvent("on" + this.type, this.l);
      } else if( this.e.removeEventListener ) {
          this.e.removeEventListener(this.type, this.l, this.cap);
      } else {
        eval('e.on'+type+' = null');
      }
   }
}
