//
//  Copyright 2008, trivago GmbH
//  All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without modification,
//  are permitted provided that the following conditions are met:
//
//  -> Redistributions of source code must retain the above copyright notice, this
//     list of conditions and the following disclaimer.
//  -> Redistributions in binary form must reproduce the above copyright notice, this
//     list of conditions and the following disclaimer in the documentation and/or
//     other materials provided with the distribution.
//  -> Neither the name of the trivago GmbH nor the names of its contributors may
//     be used to endorse or promote products derived from this software without
//     specific prior written permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
//  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
//  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
//  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
//  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
//  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
//  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
//  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
//  POSSIBILITY OF SUCH DAMAGE.
//

//
//  This file represents the common functionality, respectively "the lib" of trivagos
//  JavaScript sources. It's named COM.class.js, but there's many source that is just
//  manipulating prototypes.
//  We have tried to write this as fast and save as possible. For example the logging
//  feature is often just overkill, so we decided not to take over a programmers main
//  task: thinking.
//
//  Actual this is just a beta. You can get the most actual version here:
//  http://www.trivago.com/javascript/base/COM.class.js
//
//  Note that it is NOT recommended to use for(key in array) constructs after binding
//  this source. You'll get strange results: every browser will iterate the prototype
//  methods defined here.
//

//
//  First of all we declare COM and add first basic function to extend prototypes
//  it's only temporary used until COM is redefined. We need that function only for
//  initializing some functions
//

var COM = new function()
{

  this.extendPrototype = function(t, s)
  {
    for(var property in s)
    {
      try
      {
        if(typeof t[property] == 'undefined')
        {
          t[property] = s[property];
        }
      }
      catch(e)
      {
        return null;
      }
    }
  }

};

//
//  you can't avoid such an object...
//

var Browser = {
     IE:   window.attachEvent && !window.opera,
    IE6:   /msie 6\.0/i.test(navigator.userAgent),
  Opera:   !!window.opera,
 WebKit:   navigator.userAgent.indexOf('AppleWebKit') != -1,
  Gecko:   navigator.userAgent.indexOf('Gecko') != -1 && navigator.userAgent.indexOf('KHTML') == -1,
  KHTML:   navigator.userAgent.indexOf('KHTML') != -1,
MacSafari: ((navigator.userAgent.indexOf('Safari') != -1) && (navigator.platform? navigator.platform: navigator.userAgent).toLowerCase().indexOf('mac') != -1),
 Safari:   navigator.userAgent.indexOf('Safari') != -1
};

//
//  First of all we declare our own object. We attach some functions to that object
//  with which we'll later attach Object.prototype. Note: it is recommended to use
//  all following extensions after adopting via COM.GET.Elem(), COM.GET.All(),
//  COM.GET.First(), COM.Element()...
//

var COMObject = function(){};

//
//  We start with some overhead: member(s) that are able to tell us the type of their
//  object. Because it's really uncomfortable to overload Elements (IEs <= v7 are not
//  able to fit with) every element is getting handled like all other objects - until
//  it has passed a COM routine.
//

COMObject.prototype.isElement = function() { return false };

//
//  Often it is necessary to distinguish objects more accurate than typeof(); does...
//  Example: typeof(new Array) and typeof(new Object) will result in the same string:
//  "object". Note that these and all following prototype definitions will not effect
//  elements in IE before they've been adopted...
//

COMObject.prototype.isBoolean = function() { return this.constructor == Boolean };
COMObject.prototype.isString  = function() { return this.constructor ==  String };
COMObject.prototype.isNumber  = function() { return this.constructor ==  Number };
COMObject.prototype.isArray   = function() { return this.constructor ==   Array };
COMObject.prototype.isObject  = function() { return this.constructor ==  Object };

//
//  Here is the alternative for "typeof". The con is that it can't get called on some
//  undefined value. Even if "null" has object character in JavaScript you should use
//  COM.GET.Type(); which is wrapping this by using try/catch.
//

COMObject.prototype.getType = function()
{
  return this.isArray()   ?   'array':
         this.isElement() ? 'element':
         this.isString()  ?  'string': typeof this;
};

//
//  This will return the first index of a value appearing in an array-like object. so
//  expect a number or string. in case of failure it'll return "null". an "arguments"
//  object will get handled like arrays do.
//

COMObject.prototype.getKeyOf = function(value)
{
  if(this.isArray() || (this.callee && (this.length || (this.length === 0))))
  {
    for(var i=0, n=this.length; i!=n; ++i)
    {
      if(this[i] == value)
      {
        return i;
      }
    }
  }

  for(var key in this)
  {
    if(this[key] == value)
    {
      return key;
    }
  }

  return null;
}

//
//  Can get used in for/in loops to iterate just the "new" properties.
//

COMObject.prototype.hasNative = function(property)
{
  return typeof this.constructor.prototype[property] != 'undefined';
}

//
//  A wrapper for .getKeyOf(); that allows simple statements in boolean context with-
//  out necessity to filter undefined, null or whatever. You should note that there's
//  a bit more to do like using .getKeyOf(); and so it's not faster.
//

COMObject.prototype.hasValue = function(value)
{
  return this.getKeyOf(value) !== null;
}

//
//  The easiest way to make copies of objects. Will also work with elements. The only
//  argument tells if the cloning has to be recursive. That means: childs of elements
//  will get cloned too.
//  You can define the clone depth by passing a negative value. For example: - 2 will
//  cause a result, where the 1st and the 2nd level are containing copies, but any of
//  the following will just hold references.
//  The value of this argument doesn't effect the cloning depth of elements - in this
//  case boolean context matters.
//

COMObject.prototype.clone = function(recursive)
{
  if(typeof this.cloneNode == 'function')
  {
    return this.cloneNode(recursive);
  }

  if(typeof this != 'object')
  {
    return this.valueOf();
  }

  var clone = new this.constructor(), property;

  for(property in this)
  {
    if(clone[property] !== this[property])
    {
      try
      {
        clone[property] = recursive && this[property] &&
                                       this[property].clone?
                                       this[property].clone(recursive +1):
                                       this[property]
      } catch(e) {}
    }
  }

  return clone;
}

//
//  You can pass a routine, a routine-reference or a string of Script. Every value of
//  the array (or the array-like object) will become passed as first argument for the
//  function - which will get called for each entry.
//  This function will log errors appearing during iteration, because it wouldn't be
//  very useful if the value that has caused it will also cause a full break. If you
//  want to suppress this behavior, pass a 2nd argument with a "true" value.
//

COMObject.prototype.forEach = function(routine, stop)
{
  var result  = [];
      routine = COM.GET.Function(routine);

  for(var i=0, n=this.length; i<n; ++i)
  {
    try
    {
      result.push(routine(this[i]));
    }
    catch(e)
    {
      if(stop)
      {
        throw e;
      }

      result.push(COM.Log(this+'.forEach(['+typeof routine+'])['+i+']: '+e));
    }
  }

  return result;
}

//
//  Nearly the same like .forEach(), calls the routine as member of the entry itself,
//  but: Here is possible to define arguments. The 3rd of .withEach() will become the
//  1st of the member routine...
//

COMObject.prototype.withEach = function(routine, stop)
{
  var result = [], args = [];

  for(var i=2,n=arguments.length; i<n; ++i)
  {
    args.push(arguments[i]);
  }

  for(var i=0, n=this.length; i<n; ++i)
  {
    try
    {
      result.push(this[i][routine].apply(this[i], args));
    }
    catch(e)
    {
      result.push(COM.Log(this+'.withEach('+routine+')['+i+']: '+e+' ('+this[i]+')'));

      if(stop)
      {
        throw e;
      }
    }
  }

  return result;
}

//
//  Creates search-parts of URLs by iterating over hashes/ objects members. booleans,
//  numbers and strings get included. other stuff will be ignored.
//

COMObject.prototype.toQuery = function()
{
  var result = '';

  for(var property in this)
  {
    ({'number':1,'string':1,'boolean':1})[typeof this[property]] &&
    (result += (result? '&': '')+ encodeURI(property) +'='+ (this[property].isBoolean()? new Number(this[property]): encodeURI(this[property])));
  }

  return result;
}

//
//  We extend Array.prototype with new functions.
//

COM.extendPrototype(Array.prototype, COMObject.prototype.clone());

//
//  We extend String.prototype with new functions.
//

COM.extendPrototype(String.prototype, COMObject.prototype.clone());

//
//  Standard string functionality. Removes all whitespace characters on the [r]ight,
//  the [l]eft or the [i]side of a string.
//

String.prototype.ltrim = function() { return this.replace(/^\s+/,  '') }
String.prototype.itrim = function() { return this.replace(/\s+/g, ' ') }
String.prototype.rtrim = function() { return this.replace(/\s+$/,  '') }

//
//  Removing leading and following whitespace(s) - with or without the inside ones.
//

String.prototype.gtrim = function() { return this.trim().itrim()  }
String.prototype.trim  = function() { return this.rtrim().ltrim() }

//
//  Standard string functionality. Extends a string to given [l]ength adding [s]tring
//  on the [l]eft or the [r]ight.
//

String.prototype.lpad = function(l, s)
{
  o = this; while(o.length < l) { o = s.concat(o); }
  return o.substr(o.length-l);
}

String.prototype.rpad = function(l, s)
{
  o = this; while(o.length < l) { o = o.concat(s); }
  return o.substr(0, l);
}

//
//  Creating a valid URL query by escaping a string, but not the ampersands and equal
//  signs in it. If there's more than one equal sign between two ampersands, just the
//  first will stay unescaped. Entities will confuse this function, /&+/g won't.
//

String.prototype.toQuery = function()
{
  var split = this.replace(/&+/g, '&').split('&');

  if(split.length < 2)
  {
    return this.valueOf();
  }

  for(var i=0, n=split.length; i!=n; ++i)
  {
    split[i] = escape((split[i] = split[i].split('=')).shift()) +'='+ escape(split[i].join('='));
  }

  return split.join('&');
}

//
//We extend Date.prototype with new functions.
//

COM.extendPrototype(Date.prototype, COMObject.prototype.clone());

//
//  Stolen from the nasty DHTML calendar, translated to real source and bound here...
//

Date.prototype.oldFullYear = Date.prototype.setFullYear;
Date.prototype.setFullYear = function(year)
{
  var date = new Date(this);
      date.oldFullYear(year);

  if(date.getMonth() != this.getMonth())
  {
    this.setDate(28);
  }

  this.oldFullYear(year);
  return true;
}

//
//  Stolen from the nasty DHTML calendar, translated to real source and bound here...
//

Date.prototype.setYearMonthDay = function()
{
  var date = new Date(arguments.length == 1? arguments[0] :
                                             arguments[0] +'-'+
                                             arguments[1] +'-'+
                                             arguments[2] );
  this.setDate(1);

  this.setFullYear( date.getFullYear());
  this.setMonth(    date.getMonth());
  this.setDate(     date.getDate());

  return true;
}

//
//  Stolen from the nasty DHTML calendar, translated to real source and bound here...
//

Date.prototype.getString = function(format)
{
  var month = this.getMonth(), day = this.getDate(), year = this.getFullYear(), hash = {};

  format || (format = '%y-%m%-%d');

  hash['%y'] = year;
  hash['%m'] = ++month <10? '0'+ month: month;
  hash['%d'] =     day <10? '0'+   day:   day;

  for(var i=0, a=format.match(/%./g), n=a.length; i<n; ++i)
  {
    if(hash[a[i]])
    {
      format = format.replace(new RegExp(a[i], 'g'), hash[a[i]]);
    }
  }

  return format;
}

//
//  Stolen from the nasty DHTML calendar, translated to real source and bound here...
//

Date.prototype.compareDate = function(date)
{
  return (this.getFullYear() == date.getFullYear() ) &&
         (this.getMonth()    == date.getMonth()    ) &&
         (this.getDate()     == date.getDate()     );
}

//
//  NOT stolen from the nasty DHTML calendar, but real source by nature...
//

Date.prototype.compareTime = function(date)
{
  return (this.getHours()   == date.getHours()   ) &&
         (this.getMinutes() == date.getMinutes() ) &&
         (this.getSeconds() == date.getSeconds() );
}

//
//  Stolen from the nasty DHTML calendar, translated to real source and bound here...
//

Date.prototype.getMonthDayCount = function(month, year)
{
  year  || (year  = this.getFullYear());
  month || (month = this.getMonth());

  return ((0 == (year %4  )) &&
         ((0 != (year %100)) ||
          (0 == (year %400)) )) && month == 1? 29: ([31,28,31,30,31,30,31,31,30,31,30,31])[month];
}

//
//  Defining COMElement to extend Element.prototype later. Note again: it is recommended
//  to use all following extensions after adopting via COM.GET.Elem(), COM.GET.All(),
//  COM.GET.First(), COM.Element()... 
//

var COMElement = function(){};
COMElement.prototype = COMObject.prototype.clone();

//
//  Redefining type.
//

COMElement.prototype.isElement = function() { return true  }
COMElement.prototype.isObject  = function() { return false }

//
//  Easy getters that are returning dimension and position values or undefined; they
//  don't really do anything. but they should get used - you'll see why...
//

COMElement.prototype.getWidth  = function() { return this.offsetWidth  }
COMElement.prototype.getHeight = function() { return this.offsetHeight }
COMElement.prototype.getLeft   = function() { return this.offsetLeft   }
COMElement.prototype.getTop    = function() { return this.offsetTop    }

//
//  Some comfort...
//

document.getCoords =
    self.getCoords =

COMElement.prototype.getCoords = function()
{
  return {x:this.getLeft(), y:this.getTop()};
}

//
//  More overhead - simple setter expecting a pixel-value. It's able to become a very
//  useful shortener for your source...
//

COMElement.prototype.setWidth  = function(w) { return this.setStyle('width',  parseInt(w) +'px') }
COMElement.prototype.setHeight = function(h) { return this.setStyle('height', parseInt(h) +'px') }
COMElement.prototype.setLeft   = function(x) { return this.setStyle('left',   parseInt(x) +'px') }
COMElement.prototype.setTop    = function(y) { return this.setStyle('top',    parseInt(y) +'px') }

//
//  Maybe you've heard from this, and yes, they work equivalent to members of window.
//  See COM.GET.Elem() if you're interested in what to do with the <body> tag.
//

COMElement.prototype.resizeBy = function(w, h) { return (this.setWidth( this.getWidth() +w) === null) || this.setHeight( this.getHeight() +h) }
COMElement.prototype.resizeTo = function(w, h) { return (this.setWidth(                  w) === null) || this.setHeight(                   h) }
COMElement.prototype.moveBy   = function(x, y) { return (this.setLeft(  this.getLeft()  +x) === null) || this.setTop(    this.getTop()    +y) }
COMElement.prototype.moveTo   = function(x, y) { return (this.setLeft(                   x) === null) || this.setTop(                      y) }

//
//  Get the next higher bodied element that's positioned absolute, relative or fixed.
//

COMElement.prototype.getContainer = function() { return this.offsetParent }

//
//  "Abs" abbreviates "Absolute"...
//

COMElement.prototype.getAbsLeft = function() { var node = this, value = 0; do value += node.offsetLeft; while((node = node.offsetParent) && (node != document.body)); return value }
COMElement.prototype.getAbsTop  = function() { var node = this, value = 0; do value += node.offsetTop;  while((node = node.offsetParent) && (node != document.body)); return value }

//
//  Detect scroll positions. NOT the window scroll positions!
//

COMElement.prototype.getXPos = function() { return this.scrollLeft }
COMElement.prototype.getYPos = function() { return this.scrollTop  }

//
//  Trying to get "computed style". In optimal case it's possible to get also not ex-
//  plicit defined properties - But I don't care. "float"/"opacity" will get returned
//  browser-independent.
//

COMElement.prototype.getStyle = function(property)
{
  switch(property)
  {
    case 'float':    return typeof this.style.cssFloat != 'undefined'? this.style.cssFloat: this.style.styleFloat;
    case 'opacity':  return this.getOpacity();
  }

  if(typeof self.getComputedStyle == 'function')
  {
    return self.getComputedStyle(this, null).getPropertyValue(property.replace(/([A-Z])/g, "-$1").toLowerCase());
  }

  return this.style[property];
}

//
//  Maybe this is just to ensure that "float" and "opacity" will get set in the right
//  way. But maybe there are properties we forget?
//

COMElement.prototype.setStyle = function(property, value)
{
  switch(property)
  {
    case 'float':    return this.style.cssFloat = this.style.styleFloat = value;
    case 'opacity':  return this.setOpacity(value);
  }

  return this.style[property] = value;
}

//
//  Use percentage values.
//

COMElement.prototype.getOpacity = function()
{
  var style = this.style;

  if(typeof(style.MozOpacity)   != 'undefined') { return style.MozOpacity   === ''? 100: parseFloat(style.MozOpacity)   *100; }
  if(typeof(style.opacity)      != 'undefined') { return style.opacity      === ''? 100: parseFloat(style.opacity)      *100; }
  if(typeof(style.KhtmlOpacity) != 'undefined') { return style.KhtmlOpacity === ''? 100: parseFloat(style.KhtmlOpacity) *100; }
  if(typeof(style.filter)       != 'undefined')
  {
    if(!(style = style.filter))
    {
      return 100;
    }

    var pos = style.indexOf('alpha');

    if((pos == -1) && ((pos = style.indexOf('Alpha')) == -1))
    {
      return 100;
    }

    if((pos = style.indexOf("opacity='")) == -1) {
    if((pos = style.indexOf('opacity="')) == -1) {
    if((pos = style.indexOf('opacity=' )) == -1) { return 100; } --pos; }}

    return parseInt(style.substr(pos +9));
  }

  return null;
}

//
//  Returns percentage values.
//

COMElement.prototype.setOpacity = function(value)
{
  var style = this.style;

  value = parseInt(value);
  value = value > 99? 100: value < 1? 0: value;

  style.opacity = style.KhtmlOpacity = style.MozOpacity = (value / 100).toString();
  style.filter  = 'alpha(opacity='+ value +')';

  return value;
}

//
//  To toggle style.visibility.
//

COMElement.prototype.show = function() { return this.style.visibility = 'visible' }
COMElement.prototype.hide = function() { return this.style.visibility =  'hidden' }
COMElement.prototype.swap = function()
{
  return this.style.visibility = this.getStyle('visibility') == 'hidden'? 'visible': 'hidden';
}

//
//  Will overload the element with the current value of (computed-) style.display and
//  set this value to 'none'. If it's still none, the defaultDisplayType will not get
//  set - so we ensure not to overwrite.
//

COMElement.prototype.disappear = function()
{
  if(this.getStyle('display') != 'none')
  {
    this.defaultDisplayType = this.getStyle('display');
  }

  return this.style.display = 'none';
}

//
//  If the defaultDisplayType is defined, we set it back. Otherwise the value is set
//  to 'block'. Nasty, but other methods are not fast enough.
//

COMElement.prototype.appear = function()
{
  return this.style.display = this.defaultDisplayType? this.defaultDisplayType: 'block';
}

//
//  Toggle (current-) style.display.
//

COMElement.prototype.toggle = function()
{
  return this.getStyle('display') == 'none'? this.appear(): this.disappear();
}

//
//
//

COMElement.prototype.setClass = function(name) { return this.className = name }
COMElement.prototype.getClass = function()     { return this.className }
COMElement.prototype.delClass = function(name) { return this.className = this.className.replace(new RegExp('\\b'+name+'\\b', 'g'), '').gtrim() }
COMElement.prototype.hasClass = function(name) { return this.className.match(new RegExp('\\b'+name+'\\b')) }
COMElement.prototype.addClass = function(name)
{
  if(this.className)
  {
    if(!this.hasClass(name))
    {
      this.className += ' '+ name;
    }
  }
  else
  {
    this.className = name;
  }

  return true;
}

//
//  Check if an element has any tasks for a specific event. Not really nesseccary but
//  the other event-related members will use this. Note that all handlers defined via
//  attributes will get translated, unshifted to the queue and removed from dom.
//  Furthermore: it doesn't matter if you set events to body-tag, document or window,
//  they'll all become entries in the same queue.
//
//  Important: The this. keyword, referring the element, will not work any more after
//             translating javascript-strings to functions! use arguments[1] instead.
//             the event object will become the first argument.
//             Maybe it would be useful to append something like the following pregex
//             to this method, but in this case "this" referring inline-defined elems
//             won't work any more:
//
//              .replace(/([^a-zA-Z0-9_$]*)\this\./, "$1arguments[1].")
//
//             See COM.GET.Function() for further discussion.
//             Any proposals how we should decide?
//

document.hasEvent =
    self.hasEvent =

COMElement.prototype.hasEvent = function(type)
{
  var holder = this, handle = this;

  switch(type.toLowerCase())
  {
    case 'domcontentloaded':
    case 'onreadystatechange':
    case 'readystatechange':
      holder = handle = (this.attachEvent) ? document           : self;
                 type = (this.attachEvent) ? 'readystatechange' : 'DOMContentLoaded';
      break;

    default:
	  if((this.tagName && (this.tagName == 'BODY')) || (this == self))
	  {
	    holder = COM.GET.Body();
	    handle = self;
	  }
      type = type.toLowerCase().replace(/^on/, '');
  }
  
  holder = COM.GET.Elem(holder);
  handle = COM.GET.Elem(handle);

  handle.events || (handle.events = COM.GET.Elem({}));

  if(!handle.events[type])
  {
    var routine = function(event) { return handle.runEvent(type, event); };
    handle.events[type] = COM.GET.Elem([]);

    try
    {
      handle.addEventListener(type, routine, false);
    }
    catch(e)
    {
      handle['do'+ type +'Event'] = routine;
      if (type == 'readystatechange')
      {
    	  handle['on'+ type +'Event'] = function() { if (handle.readyState === "complete") {handle['do'+ type +'Event'](self.event);} };
      }
      else
      {
    	  handle['on'+ type +'Event'] = function() { handle['do'+ type +'Event'](self.event); };
    	  
      }
      handle.attachEvent('on'+ type, handle['on'+ type +'Event']);
    }
  }

  if(holder&&holder.getAttribute&&holder.getAttribute('on'+ type))
  {
    handle.events[type].push(COM.GET.Function(holder.getAttribute('on'+ type)));

    holder.setAttribute('on'+ type, null);
    holder.removeAttribute('on'+ type);
  }

  return handle.events[type].length;
}

//
//  Here the tasks get added to the event- related queue. Specify the events type you
//  want to tune ('onclick', 'mouseover'), then pass a function or function reference
//  (or string, see discussion of .hasEvent() and COM.GET.Function()).
//  With the 3rd arg it's possible to avoid validation of your function - but that is
//  deprecated and not recommended.
//

document.addEvent =
    self.addEvent =

COMElement.prototype.addEvent = function(type, routine, force)
{
  var node;

  switch(type.toLowerCase())
  {
	case 'domcontentloaded':
    case 'onreadystatechange':
    case 'readystatechange':
      node = (this.attachEvent) ? document           : self;
      type = (this.attachEvent) ? 'readystatechange' : 'DOMContentLoaded';
      break;

    default:
      node = (this.tagName && (this.tagName == 'BODY'))? self: this;
      type = type.toLowerCase().replace(/^on/, '');
  }

  node = COM.GET.Elem(node);
  node.hasEvent(type);
  node.events[type].push(force? routine: COM.GET.Function(routine));
  return true;
}

//
//  Removing elements from the queue. Leaving out the 2nd arg will cause that it gets
//  flushed. Note that there's no way to get them back if you don't hold references -
//  or catch the spliced ones.
//

document.delEvent =
    self.delEvent =

COMElement.prototype.delEvent = function(type, routine, force)
{
  var node, i;

  switch(type.toLowerCase())
  {
  	case 'domcontentloaded':
    case 'onreadystatechange':
    case 'readystatechange':
      node = (this.attachEvent) ? document           : self;
      type = (this.attachEvent) ? 'readystatechange' : 'DOMContentLoaded';
      break;

    default:
      node =(this.tagName && (this.tagName == 'BODY'))? self: this;
      type = type.toLowerCase().replace(/^on/, '');
  }
  
  node = COM.GET.Elem(node);
  if(node.hasEvent(type))
  {
    if(!routine)
    {
      node.events[type] = COM.GET.Elem([]);
      return !0;
    }

    routine = String(force? routine: COM.GET.Function(routine));

    for(i=0; node.events[type] && i<node.events[type].length; ++i)
    {
      if(routine == String(node.events[type][i]))
      {
        return node.events[type].splice(i,1);
      }
    }
  } 

  return i? null: false;
}

//
//  This is called instead of the functions you have passed. Of course it is possible
//  to simulate this call, but you should also provide the 2nd arg, the event-object.
//

document.runEvent =
    self.runEvent =

COMElement.prototype.runEvent = function(type, task)
{
  var node, i=false;
  switch(type.toLowerCase())
  {
  	case 'domcontentloaded':
    case 'onreadystatechange':
    case 'readystatechange':
      node = (this.attachEvent) ? document           : self;
      type = (this.attachEvent) ? 'readystatechange' : 'DOMContentLoaded';
      break;

    default:
      node = (this.tagName && (this.tagName == 'BODY'))? self: this;
      type = type.toLowerCase().replace(/^on/, '');
  }

  node = COM.GET.Elem(node);
  if(node.hasEvent(type))
  {
	var q = node.events[type].clone();
    for(i=0; i<q.length; ++i)
    {
      try
      {
        q[i](task, COM.GET.EventTarget(task));
      }
      catch(e)
      {
        COM.Log(node+'.runEvent('+type+'); '+i+': '+e.message);
      }
    }
  }

  return i;
}

//
//  Some wrapped stuff...
//

document.resizeTo = self.resizeTo;
document.resizeBy = self.resizeBy;

document.moveTo   = self.moveTo;
document.moveBy   = self.moveBy;

document.scrollTo   = self.scrollTo;

//
//  Our window and document seems to be the same...
//

self.getWidth  = document.getWidth  = function() { var width  = document.body.clientWidth;  return typeof width  == 'undefined'? self.innerWidth:  width  }
self.getHeight = document.getHeight = function() { var height = document.body.clientHeight; return typeof height == 'undefined'? self.innerHeight: height }

self.getLeft = document.getLeft = function() { return self.screenX }
self.getTop  = document.getTop  = function() { return self.screenY }

self.getXPos = document.getXPos = function() { var x = (typeof document.documentElement == 'undefined')? self.pageXOffset : document.documentElement.scrollLeft; return typeof x == 'undefined'? self.scrollLeft: x }
self.getYPos = document.getYPos = function() { var y = (typeof document.documentElement == 'undefined')? self.pageYOffset : document.documentElement.scrollTop;  return typeof y == 'undefined'? self.scrollTop:  y }

self.setWidth  = document.setWidth  = function(w) { return self.resizeBy(w, 0) }
self.setHeight = document.setHeight = function(h) { return self.resizeBy(0, h) }

self.setLeft = document.setLeft = function(x) { return self.moveTo(x, 0) }
self.setTop  = document.setTop  = function(y) { return self.moveTo(0, y) }

//
//  Very long prelude, but now the flesh follows. We've decided to use the old object
//  notation because JSON doesn't allows us to define private variables.
//  Furthermore, "new function()" has the touch of a static class, not a singleton...
//  At this point COM.extendPrototype is deleted and all prototypes are extended.
//

COM = new function()
{

  //
  //  It is not usual to define privates at the top, but if you really want to under-
  //  stand what we're going to do, you should have a look at.
  //
  //  HEAD and BODY become references, LOAD is set after the windows load- event, LOG
  //  containes dynamic information for developers, SRC the urls of sources that have
  //  still been required, LYR is a stack of LAYER instances and DEBUG you will learn
  //  to love...
  //

  var HEAD = null, BODY = null, LOAD = false, LOG = [], SRC = {}, LYR = [];
  
  try
  {
    var DEBUG = self.opener &&
                self.opener.COM &&
                self.opener.COM.DEBUG &&
                self.opener.COM.DEBUG.Receiver? self.opener.COM.DEBUG.Receiver: null;
  }
  catch (e)
  {
    var DEBUG = null;
  }

  //
  //  Now it's time for some static getter.
  //

  this.GET = {

    //
    //  You should use this instead of document.getElementById(). It is an "adopting"
    //  routine, we've discussed before. You can pass an element reference or its id.
    //

    Elem: function(anything)
    {
      var name = 'COM.GET.Elem('+anything+'); ';

      if(!anything)
      {
        return COM.Log(name+'is null!');
      }

      var prototype, result;
      try
      {
	    if(typeof anything == 'object' || (typeof anything == 'function' && anything.constructor == Object))
	    {
	      prototype = typeof anything.nodeName == 'string'? COMElement.prototype: COMObject.prototype;
	      result    = anything;
	    }
	    else
	    {
	      prototype = COMElement.prototype;
	      result    = document.getElementById(anything);
	    }
      }
      catch(e)
      {
        COM.Log('COM.GET.Elem:'+e);
      }

      if(result)
      {
        var body = document.body == result;

        //if(window.attachEvent && !Browser.opera)
        //{
          for(var property in prototype)
          {
            try
            {
              if(typeof result[property] == 'undefined')
              {
                if( body &&
                    ( property == 'resizeTo' ||
                      property == 'moveTo' ||
                      property == 'resizeBy' ||
                      property == 'moveBy' ) )
                {
                  continue;
                }

                result[property] = prototype[property];
              }
            }
            catch(e)
            {
              COM.Log(name+property+': '+e);  
            }
          }
        //}

        try
        {
          if((result == document.body) && !BODY)
          {
            for(var a=[
              'moveTo', 'resizeTo', 'getWidth', 'getHeight', 'getLeft', 'getTop',    'getXPos', 'getYPos', 'getCoords', 
              'moveBy', 'resizeBy', 'setWidth', 'setHeight', 'setLeft', 'setTop', /*'setXPos', 'setYPos', 'setCoords', */
              'scrollTo'
            ], i=0; i!=a.length; ++i) { result[a[i]] = self[a[i]] }
          }
        }
        catch(e)
        {
          COM.Log(name+'BODY: '+e);  
        }

        return result;
      }

      COM.Log(name+'!exist')
      return new COMObject();
    },

    //
    //  Equivalent to document.getElementsByTagName, "adopting". Beside the tagName -
    //  which can be represented by a tag reference - you can pass a parent for focus
    //  and the max. number of entries in the result that are going to get adopted.
    //  The default's zero.
    //

    All: function(anything, parent, max)
    {
      parent = parent? COM.GET.Elem(parent): document;

      var result = parent.getElementsByTagName(anything.nodeName? anything.nodeName: anything);

      for(var i=0; (i!=max)&&(i!=result.length); ++i)
      {
        COM.GET.Elem(result[i]);
      }

      return result;
    },

    //
    //  Like .All(), but you'll get just the first reference. Adopting.
    //

    First: function(anything, parent)
    {
      try
      {
        return this.All(anything, parent, 1)[0];
      }
      catch(e)
      {
        return null;
      }
    },

    //
    //  Like .First(), when you pass 'head' or 'body'.
    //

    Head: function() { return HEAD? HEAD: HEAD = this.First('head'); },
    Body: function() { return BODY? BODY: BODY = this.First('body'); },

    //
    //  To catch the element which caused an event. Browser-independent. Note that it
    //  is not nesseccary to call it inside of event- handlers that have been defined
    //  by using this sources, try arguments[1].
    //

    EventTarget: function(task)
    {
      var target;

      if(task)
      {
        if(task == self)
        {
          return self;
        }

        if((typeof task.nodeName  == 'string')&&
           (typeof task.className == 'string'))
        {
          return task;
        }

        return typeof task.target == 'undefined'? task.srcElement?
                                                  task.srcElement: event.srcElement: task.target
      }
    },

    //
    //  Translates array-like objects (e.g. "arguments") to arrays.
    //  Arrays will stay arrays.
    //

    Array: function(object)
    {
      try
      {
        var result = [], i, n;

        for(i=0, n=object.length; i<n; ++i)
        {
          result.push(object[i]);
        }

        return result;
      }
      catch(e)
      {
        return COM.Log('COM.GET.Array(['+typeof object+']); '+e);
      }
    },

    //
    //  Translates strings into functions. Functions will stay functions. If you pass
    //  a string, the "this" keyword won't work anymore. That means, usually it won't
    //  refer what it has referred before, but maybe the object which calls it.
    //

    Function: function(routine)
    {
      try
      {
        return typeof routine == 'function' ? routine:
               typeof routine == 'string'   ? new Function('', routine): COM.Log('.Function(['+typeof routine+']): !allowed');
      }
      catch(e)
      {
        return COM.Log('COM.GET.Function(['+typeof routine+']); '+e);
      }
    },

    //
    //  Get whatever LOG contains. If you pass an arg that's true in boolean context,
    //  you'll get a reference to this array. Just a subject to developers.
    //

    Log: function(reference)
    {
      return reference? LOG: LOG.clone();
    }

    //
    //  Whenever there is anything not detecable failure-save or browser-independent enough, it
    //  should become a part of this subJect. But just, if more prototype tuning isn't a better
    //  solution.
    //

  }

  //
  //  Onscreens.
  //

  this.WIZARD = new function()
  {

    //
    //  Open an onscreen form. This may not really define any further layouts, but you can pass
    //  a COM.Element() compatible as 1st arg, as content. the second arg specifies the opacity
    //  of the layer in the middleground (zero is possible). if the third's boolean, a click on
    //  the layer will cause .WIZARD.Close(). Otherwise, if it's compatible to .GET.Function(),
    //  a click on the layer will cause it's execution.
    //  Call this function again to change the content if the WIZARD's still open.
    //

    this.Open = function(content, opac, click)
    {
      if(!Form)
      {
        BODY || COM.GET.Body();

        Form = COM.Element(['div',{'class':'COMElement COMWizardForm'}]);
        Cont = COM.Element(['div',{'class':'COMElement COMWizardCont'}]);

        Layer = new COM.Layer();
        Form.appendChild(Cont);
        Cont.addEvent('click', COM.Void);
      }

      if(click)
      {
        Form.addEvent('click', (typeof click == 'function')
                            || (typeof click == 'string')? COM.GET.Function(click): COM.WIZARD.Close);
      }
      else
      {
        Form.delEvent('click');
      }

      self.delEvent('resize', COM.WIZARD.Size);
      Layer.open(true, opac? opac: 50);
      COM.GET.Body().insertBefore(Form, COM.GET.Body().firstChild);

      if(content != Cont.firstChild)
      {
        while(Cont.firstChild)
        {
          Cont.removeChild(Cont.firstChild);
        }

        Cont.appendChild(COM.Element(content));
      }

      return COM.WIZARD.Size();
    }

    //
    //  Close the WIZARD. Note that the 3rd argument of .WIZARD.Open() won't take effect if you
    //  call this manually.
    //

    this.Close = function()
    {
      self.delEvent('resize', COM.WIZARD.Size);
      BODY.removeChild(Form);
      return Layer.close();
    }

    //
    //  Usually, there's no need to call this routine. It's in window.onresize queue as soon as
    //  you've called .WIZARD.Open().
    //

    this.Size = function()
    {
      if (!(Form && Cont))
      {
    	  return false;
      }
      
      self.delEvent('resize', COM.WIZARD.Size);

      var width  = self.getWidth(),
          height = self.getHeight(),
          xpos   = self.getXPos();

      Form.style.width  = width  +'px';
      Form.style.height = height +'px';

      Cont.style.width = Cont.firstChild.getWidth() +'px';
      var            y = Cont.firstChild.getHeight(), d;

      Cont.style.paddingTop = Cont.style.paddingBottom = ((d=height+xpos-y)>10? d/3: height/5) +'px';
      Layer && Layer.size();
      return self.addEvent('resize', COM.WIZARD.Size);
    }

    //
    //  Usually, there's no need to call this routine, but sometimes you probably need hided elements.
    //

    this.Hidden = function()
    {
      if (Layer)
      {
        return Layer.hidden();
      }
      return [];
    }    

    //
    //  Privates holding references.
    //

    var Form,
        Layer,
        Cont;

    //
    //  Especially if there are many elements in the root of the DOM, there's many stuff to do.
    //  So it's recommended to use a <div> or whatelse around the documents main nodes...
    //

  }

  //
  //  Used quite often to separate the pages content from the current focus. Not be visible all
  //  the times: can also get used just to simluate "onmouseout" for more complex structs, like
  //  dropdowns, or clickouts etc...
  //

  this.Layer = function()
  {
    //
    //  To show the element, you have to define if you still want the user to be able to scroll
    //  the main page. Optional you can specify an opacity value and z-index. default is 50,90.
    //  Last but not least: here you can also define special event handling, just like .WIZARD.
    //

    this.open = function(scroll, opacity, zIndex, event)
    {
      if(proc)
      {
        return COM.Log('.Layer['+ numb +'].open: double process: '+proc);
      }

      proc = 'open';

      if(!opac)
      {
        opac = COM.Element(['div',{'class':'COMElement COMLayerOpac'}]);
        temp = COM.Element(['div',{'class':'COMElement COMLayerTemp'}]);
      }
      else
      {
        var mySelf = this;
        self.delEvent('resize', function(){mySelf.size()}, 1);
      }

      if (!Browser.IE6)
      {
	    temp.style.position = 'relative';
	    temp.style.left = '0px';
	    temp.style.top  = '0px';
      }
      opac.setStyle('opacity', opacity?  opacity:  0);
      opac.setStyle('zIndex',   zIndex?   zIndex: 90);

      if(!(temp.parentNode && temp.parentNode.nodeType == 1))
      {
        BODY || COM.GET.Body();
        BODY.insertBefore(temp, BODY.firstChild);

        while(elem = BODY.firstChild.nextSibling)
        {
          BODY.removeChild(elem);
          temp.appendChild(elem);
        }

        BODY.appendChild(opac);

        if(!hide.length)
        {
          for(var i=0; i!=3; ++i)
          {
            hide.push(COM.GET.Array(COM.GET.All((['embed', 'object', 'select'])[i], temp, -1)));

            for(var j=0, n=hide[i].length; j<n; ++j)
            {
              try
              {
                hide[i][j] = [hide[i][j], hide[i][j].getStyle('visibility')];
                hide[i][j][0].setStyle('visibility', 'hidden');
              }
              catch(e)
              {}
            }
          }
        }
      }

      scfx = scroll;

      if(event)
      {
        exfx = (typeof event == 'function') || (typeof event == 'string')? COM.GET.Function(event): null;
        opac.addEvent('click', exfx? exfx: this.close, 1);
      }

      numb = LYR.push(this);

      return (proc = false) || this.size();
    }

    //
    //  Hide the Layer. Note that the 4th argument of .Layer.Open() will not take effect if you
    //  call this routine manually.
    //

    this.close = function()
    {
      if(proc)
      {
        return COM.Log('.Layer['+ numb +'].close: double process: '+proc);
      }

      proc = 'close';

      if (scfx)
      {
        var x = temp.getXPos(),
            y = temp.getYPos();
      }
      
      while(LYR.length > numb)
      {
        COM.Log('LYR.pop!!');
        LYR.pop().close();
      }
      LYR.pop();

      var mySelf = this;
      self.delEvent('resize', function(){mySelf.size()}, 1);

      for(var i=0, n=hide.length; i!=n; ++i)
      {
        for(var j=0, m=hide[i].length; j!=m; ++j)
        {
          try
          {
            hide[i][j][0].setStyle('visibility', hide[i][j][1]);
          }
          catch(e)
          {}
        }
      }

      hide = [];

      while(elem = temp.firstChild)
      {
        try
        {
          temp.removeChild(elem);
          BODY.appendChild(elem);
        }
        catch(e)
        {
          COM.Log('COM.Layer.close(); '+e);
          break;
        }
      }

      try
      {
        BODY.removeChild(opac);
        BODY.removeChild(temp);
      }
      catch(e)
      {
        ;
      }

      temp.style.width  = opac.style.width  = '';
      temp.style.height = opac.style.height = '';

      temp.style.overflow = '';

      opac.delEvent('click', this.close, 1);
      if (scfx)
      {
        self.scrollTo(x,y);
      }
      return !(proc = false);
    }

    //
    //  Just like WIZARD: will get done automatically.
    //

    this.size = function()
    {
      if(proc)
      {
        return COM.Log('.Layer['+ numb +'].size: double process'+proc);
      }

      proc = 'size';

      var mySelf = this;
      self.delEvent('resize', function(){mySelf.size()}, 1);

      var w = BODY.getWidth(),
          h = BODY.getHeight();

      if(scfx)
      {
        var x = BODY.getXPos(),
            y = BODY.getYPos();

        temp.style.width  = opac.style.width  = w.toString() +'px';
        temp.style.height = opac.style.height = h.toString() +'px';

        if(temp.style.overflow != 'hidden')
        {
          temp.style.position = 'absolute';
          temp.style.overflow = 'hidden';

          temp.style.left = '0px';
          opac.style.top  = '0px';

          temp.scrollLeft = x;
          temp.scrollTop  = y;
        }
      }
      else
      {
        temp.style.position =
        opac.style.position = 'absolute';

        opac.style.left = temp.style.left = '0px';
        opac.style.top  = temp.style.top  = '0px';

        (temp.getWidth()  >= w) || temp.setStyle('width',  w = w? w +'px': '100%'); opac.setStyle('width',  temp.getWidth()  +'px');
        (temp.getHeight() >= h) || temp.setStyle('height', h = h? h +'px': '100%'); opac.setStyle('height', temp.getHeight() +'px');
      }

      self.addEvent('resize', function(){mySelf.size()}, 1);
      return !(proc = false);
    }

    //
    //  Just like WIZARD: return hidden elements.
    //

    this.hidden = function()
    {
      return hide;
    }
    
    //
    //  Internals.
    //

    var opac = null,
        temp = null,
        numb = 0,
        proc = false,
        elem = null,
        hide = [],
        exfx = null,
        scfx = null;

    //
    //  IE isn't nice, but maybe there's a more simple way to do all this resizing stuff...
    //  Actually we decided to make it instantiateable, but: stack handling for more than 2
    //  simultaneous open ones is very bad.
    //

  }

  //
  //  Constructor for elements. The most useful part here. Most behavior - what to do
  //  which kinds and count of arguments to pass etc... is not defined yet - so it is
  //  still able to become more usefull...
  //

  this.Element = function(a)
  {
    try
    {
      if((a.nodeName && a.cloneNode) || a.isString())
      {
        return COM.GET.Elem(a);
      }

      var name = '.Element('+typeof a+'); ';

      if(!a.isArray())
      {
        return COM.Log(name+'arg?')
      }

      var l, e, k, h, s, p, x, y, z, v = [], c = document;

      if(a.length == 1)
      {
        return c.createTextNode(a[0]);
      }
      else
      {
        e = c.createElement(a[0]);
        
        if(document.body)
        {
          document.body.appendChild(e);  COM.GET.Elem(e);
          document.body.removeChild(e);
        }
        else
        {
          COM.GET.Elem(e);
        }

        if(a[1])
        {
          if(typeof(a[1]) == 'object')
          {
            for(h in a[1])
            {
              if(typeof h != 'string')
              {
                continue;
              }

              switch(h.toLowerCase())
              {
                case 'disabled':

                  e.disabled = a[1][h] && (a[1][h] != 'false');
                  break;

                case 'class':

                  e.setClass(a[1][h]);
                  break;

                case 'id':

                  e.setAttribute('id', e.id = a[1][h].toString());
                  break;

                case 'style':

                  s = a[1][h].split(';');

                  for(var i=0, m=s.length; i!=m; ++i)
                  {
                    try
                    {
                      s[i] && (p = s[i].split(':'));

                      if(p[0].match('-'))
                      {
                        x = p[0].split('-');
                        z = x[0];

                        for(y=1, n=x.length; y!=n; ++y)
                        {
                          z += x[y].substr(0,1).toUpperCase() + x[y].substring(1);
                        }

                        p[0] = z;
                      }

                      e.setStyle(p[0], p[1]);
                    }
                    catch(e)
                    {
                      COM.Log(name+'invalid style: '+e);
                    }
                  }

                  break;

                default:

                  if(h.match(/^on/))
                  {
                    a[1][h] && v.push([h.replace(/^on/, ''), a[1][h]]);
                  }
                  else
                  {
                    try
                    {
                      e.setAttribute(h, a[1][h]);
                    }
                    catch(e)
                    {
                      COM.Log(name+'.setAttribute: '+e)
                    }
                  }
              }
            }
          }
          else
          {
            COM.Log(name+'invalid attibutes/argument2 @'+i);
          }
        }

        if(a[2])
        {
          if(a[2].isString())
          {
            e.appendChild(c.createTextNode(a[2]));
          }
          else
          {
            if(a[2].isArray())
            {
              for(var i=0, n=a[2].length; i!=n; ++i)
              {
                try
                {
                  e.appendChild(COM.Element(a[2][i]));
                }
                catch(err)
                {
                  COM.Log(name+'.appendChild: '+err);
                }
              }
            }
            else
            {
              COM.Log(name+'invalid childs/argument3');
            }
          }
        }

        for(var i=0, n=v.length; i!=n; ++i)
        {
          e.addEvent(v[i][0], v[i][1]);
        }
      }

      return COM.GET.Elem(e);;
    }
    catch(err)
    {
      return COM.Log(name, err);
    }
  }

  //
  //  Binds JavaScript sources you need NOW.
  //

  this.Require = function(jsUrl)
  {
    var name = 'COM.Require('+jsUrl+'); ';

    if(SRC[jsUrl])
    {
      return COM.Log(name+SRC[jsUrl]);
    }

    COM.Execute((new COM.HTTP.Request('GET', jsUrl, {}, true)).start());
    return COM.Log(name+(SRC[jsUrl]='bound by require'));
  }

  //
  //  Bind JavaScript which has a callback, use it as preloader or simply bind css...
  //

  this.Include = function(url, isCss)
  {
    var name = 'COM.Include('+url+'); ';

    if(SRC[url])
    {
      return COM.Log(name+SRC[url]);
    }

    HEAD || COM.GET.Head();
// temporary Bugfix for IE: Problems with COM.Element at this point
//    HEAD.appendChild(COM.Element(isCss? ['link',   {'rel':'stylesheet','type':'text/css','href':url}]:
//                                        ['script', {'type':'text/javascript','src':url}]));
    if (isCss)
    {
      var node = document.createElement('link');
      node.href = url;
      node.rel = 'stylesheet';
      node.type = 'text/css';
    }
    else
    {
      var node = document.createElement('script');
      node.src = url;
      node.type = 'text/javascript';
  	}
    HEAD.appendChild(node);

    return COM.Log(name+(SRC[url]='bound by include'));
  }

  //
  //
  //

  this.Execute = function(source)
  {
    try
    {
      self.execScript(source);
    }
    catch(e)
    {
      try
      {
        var node = COM.Element(['script', {'type':'text/javascript'}]);
        node.appendChild(document.createTextNode(source));
        COM.GET.Head().appendChild(node);
      }
      catch(e)
      {
        if(typeof node.text != 'string')
        {
          return COM.Log('COM.Require([source]); '+e);
        }

        node.text = source;
      }
    }

    return true;
  }

  //
  //  Write yourself a note.
  //

  this.Log = function()
  {
    arguments[0].message || (arguments[0] = arguments[0].toString().replace(/^\./, 'COM.'));

    var text = [];

    for(var i=0, n=arguments.length; i!=n; ++i)
    {
      text.push(arguments[i] && arguments[i].message? arguments[i].message: arguments[i]);
    }

    LOG.push(text.join(': '));
    DEBUG && DEBUG(text);

    return 0;
  }

  //
  //
  //

}

//
//
//

COM.HTTP = new function()
{

  //
  //
  //

  this.Post = function(url, params, synchron) { var request = new COM.HTTP.Request('POST', url, params); return self.setTimeout(' COM.HTTP.Execute('+ request.getId() +'); ', 1); }
  this.Get  = function(url, params, synchron) { var request = new COM.HTTP.Request('GET',  url, params); return self.setTimeout(' COM.HTTP.Execute('+ request.getId() +'); ', 1); }

  //
  //
  //

  this.Request = function(method, url, params, synchron)
  {

    //
    //
    //
	params && (params = COM.GET.Elem(params));
    if(!(params && params.isObject()))
    {
      COM.Log('params will nicht', typeof params);
      params = COM.GET.Elem({});
    }

    for(var i=0, n=EVENTS.length; i!=n; ++i)
    {
      if(params['on'+ EVENTS[i]])
      {
        params['on'+ EVENTS[i]] = COM.GET.Function(params['on'+ EVENTS[i]]);
      }
    }

    method = (typeof method == 'string') && (method.toUpperCase() == 'POST')? 'POST': 'GET';

    //
    //
    //

    this.start = function()
    {
      xmlreqfx || (xmlreqfx = new COM.HTTP.Object());

      xmlreqfx.open(method, url, !synchron);

      if(method == 'POST')
      {
        xmlreqfx.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');

        if(params.vars)
        {
          xmlreqfx.setRequestHeader('Content-length', params.vars.length);
        }
        else
        {
          params.vars = null;

          xmlreqfx.setRequestHeader('Content-length', '0');
          xmlreqfx.setRequestHeader('Connection', 'close');
        }
      }

      if(!synchron)
      {
        var my = this;
        xmlreqfx.onreadystatechange = function() { my.handle(); }
        xmlreqfx.send(params.vars)
        return true;
      }
      else
      {
        xmlreqfx.send(params.vars);
        return this.setResponse();
      }
    }

    //
    //
    //

    this.handle = function()
    {
      if(xmlreqfx.readyState == 4)
      {
        this.setResponse();
      }
      else
      {
        if(params['on'+ EVENTS[xmlreqfx.readyState]])
        {
          params['on'+ EVENTS[xmlreqfx.readyState]]();
        }
      }
    }

    //
    //
    //

    this.setResponse = function()
    {
      response = params.xml? xmlreqfx.responseXML? xmlreqfx.responseXML.documentElement: null: xmlreqfx.responseText;

      if(params.onfinish)
      {
        params.onfinish(response);
      }

      return response;
    }

    //
    //
    //

    this.abort = function()
    {
      for(var i=0, n=EVENTS.length; i!=n; ++i)
      {
        (params['on'+ EVENTS[i]]) && (params['on'+ EVENTS[i]] = null);
      }
      return xmlreqfx && (xmlreqfx.abort() || 1);
    }

    //
    //
    //

    this.getId = function() { return identity; }

    //
    //
    //

    var identity = INDENT.push(this) -1,
        response = null,
        xmlreqfx = null;

    //
    //
    //

  }

  //
  //
  //

  var EVENTS = COM.GET.Elem(['begin','load','loading','interactive','finish','abort']),
      INDENT = COM.GET.Elem([]);

  //
  //
  //

  this.Execute = function(index) { INDENT[index].start(); }

  //
  //
  //

  this.Object = function()
  {
    try
    {
      return new XMLHttpRequest();
    }
    catch(e)
    {
      try { return new ActiveXObject("Msxml2.XMLHTTP");    } catch(e) {
      try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) { return COM.Log('.HTTP.Object', e); }}
    }

    return true
  }

  //
  //
  //

}

//
//
//

COM.HASH = new function()
{

  //
  //
  //
	
  this.Init = function()
  {
    if (!Browser.Safari && Browser.IE && !H)
    {
      H = COM.Element(['iframe', {'style':'display:none;','id':'COMHASHFrame'}]);
      COM.GET.Body().appendChild(H);
      var W = H.contentWindow.document;
      W.open();
      W.location.hash = '#'+document.location.hash.replace(/#/, '');
      W.close();
    }
    return true;
  }

  //
  //
  //
	
  this.Get = function(store)
  {
    if (!Browser.Safari)
    {
      H || this.Init();
      var hash = H ? H.contentWindow.document.location.hash.replace(/#/, '') : document.location.hash.replace(/#/, '');
      C = (store || typeof store == 'undefined') ? hash : C;
      return hash;
    }
    return null;
  }

  //
  //
  //

  this.Set = function(hash)
  {
    if (!Browser.Safari)
    {
      H || this.Init();
      hash && (hash = '#'+hash);
      C = document.location.hash = hash;
      if (H)
      {
    	var W = H.contentWindow.document;
        W.open();
        W.close();
        W.location.hash = hash;
      }
    }
    return true;
  }
	
  //
  //
  //

  this.Reg = function(f, r)
  {
    if (!Browser.Safari)
    {
      f && (F = COM.GET.Function(f));
      R = r;
      I || (F && (I = window.setInterval('COM.HASH.Check()', 200)));
    }
    return true;
  }

  //
  //
  //

  this.Check = function()
  { 
	var hash;
    if (!Browser.Safari && I && C != (hash = this.Get(false)))
    {
      if (hash != '')
      {
        C = document.location.hash = '#'+hash;
        (typeof F == 'function') && F();
        return true;
      }
      else if (R)
      {
    	window.clearInterval(I);
        document.location.href = document.location.href.substr(0, document.location.href.indexOf('#'));
        if (!Browser.IE)
        {
          document.location.reload();
        }
      }
    }
    return false;
  }
	
  //
  // private vars
  //

  var I = null,
      C = '',
      F = null,
      R = true,
      H = null;

}

//
//
//

COM.Menu = function(left, top, width, parent)
{
  var x =  left? (typeof  left == 'function'?  left: function() { return  left }): function() { if(m = $(ul.parentNode.parentNode)) return ul.setLeft(m.getWidth() - 15); },
      y =   top? (typeof   top == 'function'?   top: function() { return   top }): function() { if(m = $(ul.parentNode)) return ul.setTop(m.getTop() + 5); },
      w = width? (typeof width == 'function'? width: function() { return width }): null,
      
      m, ul = COM.Element(['ul',{'class':'COMElement COMMenuContainer'}]), j = 'var t=';  parent || (parent = ul);

  //
  //
  //

  function Entry(text, exec)
  {
    exec = exec.match(/^javascript\:/)? {'href':'javascript:COM.Void();', 'onclick':(m = exec.substring(11))}:
                                        {'href':exec,                     'onclick':function() { ul.disappear(1); }};

    return COM.Element(['li',{'style':'display:inline;', 'class':('COMElement COMMenu'+(m&&m.match(/^[\/]/)? 'ListEnd': 'SubList'))}, [['a', exec, text]]]); 
  }

  //
  //
  //

  ul.parade = function(start)
  {
    for(var i=0, n=ul.childNodes.length; i<n; ++i)
    {
      ul.childNodes[i].firstChild.setClass('COMElement COMMenu'+(i%2? 'Even': 'UnEven'));
    }

    return true;
  }

  //
  //
  //

  ul.getCount = function()
  {
    return ul.childNodes.length;
  }

  //
  //
  //

  ul.insert = function(text, exec)
  {
    var li = new Entry(text, exec);

    ul.firstChild? ul.insertBefore(li, ul.firstChild):
                   ul.appendChild(li);

    return ul.parade();
  }

  //
  //
  //

  ul.append = function(text, exec)
  {
    var i = ul.childNodes.length, li = new Entry(text, exec);
    li.firstChild.setClass('COMElement COMMenu'+(i%2? 'Even': 'UnEven'));
    return ul.appendChild(li);
  }

  //
  //
  //

  ul.remove = function(index)
  {
    return ul.removeChild(ul.childNodes[index]);
  }

  //
  //
  //

  ul.replace = function(index, text, exec)
  {
    m = new Entry(text, exec);
    m.firstChild.setClass(ul.childNodes[index].getClass());
    m = ul.replaceChild(m, ul.childNodes[index]);
    return m;
  }

  //
  //
  //

  ul.resize = function()
  {
    ul.moveTo(x(), y());
    w && ul.setWidth(w());
    return true;
  }

  //
  //
  //

  ul.disappear = function(recursive)
  {
    if($(ul.parentNode.firstChild).nodeName == 'A')
    {
      ul.parentNode.firstChild.delClass('COMMenuActive');
    }

    ul.setStyle('display', 'none');

    if(recursive && ul.parentNode.parentNode && $(ul.parentNode.parentNode).hasClass('COMMenuContainer'))
    {
      return ul.parentNode.parentNode.disappear();
    }

    return true;
  }

  //
  //
  //

  ul.appear = function(layer, handle)
  {
    ul.parentNode && ul.parentNode.parentNode &&
    ul.parentNode.parentNode.appear &&
    ul.parentNode.parentNode.appear();

    for(var i=0, n=ul.childNodes.length; i!=n; ++i)
    {
      (ul.childNodes[i].childNodes.length > 1) &&
       ul.childNodes[i].childNodes[1].disappear();
    }

    if($(ul.previousSibling) && ul.previousSibling.nodeName == 'A')
    {
      ul.previousSibling.addClass('COMMenuActive');
    }

    if(layer)
    {
      ul.layer || (ul.layer = new COM.Layer());
      ul.layer.open(false, 0, layer > 50? layer: 85, handle? handle: function() { ul.disappear(); ul.layer.close(); });

      try { COM.GET.Body().removeChild(ul); } catch(e) {}
            COM.GET.Body().appendChild(ul);
    }

    ul.setStyle('display', 'block');
    return ul.resize();
  }

  //
  //
  //

  ul.alternate = function(link, menu)
  {
    $(link.parentNode).appendChild(menu);

    link.delEvent('onclick');
    link.addEvent('onclick', function(e,n) { n.nextSibling.toggle(); });

    return true;
  }

  //
  //
  //

  return ul;

  //
  //
  //

}

//
//  We end like we've opened: with neccessary overhead.
//

COM.Void = function() {}

//
//  License: The BSD License
//  Authors: Mathias J. Hennig, Christina Rankers, Sascha T. Claus
//  Contact: mathias.hennig@trivago.com
//

///////////////////////////////////////////////////////////
//
//
//	Widget specific functions
//
//
///////////////////////////////////////////////////////////

//Form pre-validation
function trivagoCheckWidgetForm() {
	//Check headline
	if(COM.GET.Elem('trivago_add_opinion_form').elements['trivago_opinion_headline'].value == '') {
		alert("Please enter a title for your review.");
		COM.GET.Elem('trivago_add_opinion_form').elements['trivago_opinion_headline'].focus();
		
		return false;
	}
	
	//Check rating 
	var x = 0;
	for(var i = 1; i <= 10; i++) {
		if(COM.GET.Elem('trivago_rating_'+i).checked == true) {
			x++;
		}
	}
	
	if(x == 0) {
		alert('Please select a rating between 10 and 100.');
		
		return false;
	}
	
	//Check opinion
	if(COM.GET.Elem('trivago_add_opinion_form').elements['trivago_opinion_text'].value == '') {
		alert("Please write a brief review.");
		COM.GET.Elem('trivago_add_opinion_form').elements['trivago_opinion_text'].focus();
		
		return false;
	}
	
	//Check CAPTCHA
	if(COM.GET.Elem('trivago_add_opinion_form').elements['trivago_review_widget_captcha'].value == '') {
		alert("Please type in the number and letter combination from the image on the left.");
		COM.GET.Elem('trivago_add_opinion_form').elements['trivago_review_widget_captcha'].focus();
		
		return false;
	}
	
	//Check language
	if(COM.GET.Elem('trivago_add_opinion_form').elements['trivago_opinion_language'].value == '0') {
		alert("Please choose your language!");
		COM.GET.Elem('trivago_add_opinion_form').elements['trivago_opinion_language'].focus();
		
		return false;
	}
	
	//Check checkbox
	if(COM.GET.Elem('trivago_add_opinion_form').elements['trivago_affirm'].value == '') {
		alert("Please confirm that the review is based on your own experience.");
		COM.GET.Elem('trivago_add_opinion_form').elements['trivago_affirm'].focus();
		
		return false;
	}
	else
	{
		if(COM.GET.Elem('trivago_add_opinion_form').elements['trivago_affirm'].checked == false)
		{
			alert("Please confirm that the review is based on your own experience.");
			COM.GET.Elem('trivago_add_opinion_form').elements['trivago_affirm'].focus();
			
			return false;
		}
	}
	
	return true;
}//Get DOM elements based on the given CSS Selector - V 1.00.A Beta
//http://www.openjs.com/scripts/dom/css_selector/
function getElementsBySelector(all_selectors) {
	var selected = new Array();
	if(!document.getElementsByTagName) return selected;
	all_selectors = all_selectors.replace(/\s*([^\w])\s*/g,"$1");//Remove the 'beutification' spaces
	var selectors = all_selectors.split(",");
	// Grab all of the tagName elements within current context	
	var getElements = function(context,tag) {
		if (!tag) tag = '*';
		// Get elements matching tag, filter them for class selector
		var found = new Array;
		for (var a=0,len=context.length; con=context[a],a<len; a++) {
			var eles;
			if (tag == '*') eles = con.all ? con.all : con.getElementsByTagName("*");
			else eles = con.getElementsByTagName(tag);

			for(var b=0,leng=eles.length;b<leng; b++) found.push(eles[b]);
		}
		return found;
	}

	COMMA:
	for(var i=0,len1=selectors.length; selector=selectors[i],i<len1; i++) {
		var context = new Array(document);
		var inheriters = selector.split(" ");

		SPACE:
		for(var j=0,len2=inheriters.length; element=inheriters[j],j<len2;j++) {
			//This part is to make sure that it is not part of a CSS3 Selector
			var left_bracket = element.indexOf("[");
			var right_bracket = element.indexOf("]");
			var pos = element.indexOf("#");//ID
			if(pos+1 && !(pos>left_bracket&&pos<right_bracket)) {
				var parts = element.split("#");
				var tag = parts[0];
				var id = parts[1];
				var ele = document.getElementById(id);
				if(!ele || (tag && ele.nodeName.toLowerCase() != tag)) { //Specified element not found
					continue COMMA;
				}
				context = new Array(ele);
				continue SPACE;
			}

			pos = element.indexOf(".");//Class
			if(pos+1 && !(pos>left_bracket&&pos<right_bracket)) {
				var parts = element.split('.');
				var tag = parts[0];
				var class_name = parts[1];

				var found = getElements(context,tag);
				context = new Array;
 				for (var l=0,len=found.length; fnd=found[l],l<len; l++) {
 					if(fnd.className && fnd.className.match(new RegExp('(^|\s)'+class_name+'(\s|$)'))) context.push(fnd);
 				}
				continue SPACE;
			}

			if(element.indexOf('[')+1) {//If the char '[' appears, that means it needs CSS 3 parsing
				// Code to deal with attribute selectors
				if (element.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?['"]?([^\]'"]*)['"]?\]$/)) {
					var tag = RegExp.$1;
					var attr = RegExp.$2;
					var operator = RegExp.$3;
					var value = RegExp.$4;
				}
				var found = getElements(context,tag);
				context = new Array;
				for (var l=0,len=found.length; fnd=found[l],l<len; l++) {
 					if(operator=='=' && fnd.getAttribute(attr) != value) continue;
					if(operator=='~' && !fnd.getAttribute(attr).match(new RegExp('(^|\\s)'+value+'(\\s|$)'))) continue;
					if(operator=='|' && !fnd.getAttribute(attr).match(new RegExp('^'+value+'-?'))) continue;
					if(operator=='^' && fnd.getAttribute(attr).indexOf(value)!=0) continue;
					if(operator=='$' && fnd.getAttribute(attr).lastIndexOf(value)!=(fnd.getAttribute(attr).length-value.length)) continue;
					if(operator=='*' && !(fnd.getAttribute(attr).indexOf(value)+1)) continue;
					else if(!fnd.getAttribute(attr)) continue;
					context.push(fnd);
 				}

				continue SPACE;
			}

			//Tag selectors - no class or id specified.
			var found = getElements(context,element);
			context = found;
		}
		for (var o=0,len=context.length;o<len; o++) selected.push(context[o]);
	}
	return selected;
}
function trivago_changeFlag(language) { if(language == 0) { document.trivago_opinion_language_flag.style.display = 'none'; } else { document.trivago_opinion_language_flag.src = 'http://imgpe.trivago.com/images/layoutimages/flags/lang/' + language + '.png'; document.trivago_opinion_language_flag.style.display = 'inline'; } } 
function trivago_hideInternationalOpinions() { eles = getElementsBySelector('.trivago_internationalOpinion'); for(var i = eles.length - 1; ele = eles[i], i >= 0; i--) { ele.style.display = 'none'; } } 
function trivago_showInternationalOpinions() { eles = getElementsBySelector('.trivago_internationalOpinion'); for(var i = eles.length - 1; ele = eles[i], i >= 0; i--) { ele.style.display = 'block'; } } 
function trivago_toggleInternationalOpinions(checkbox) { if(checkbox.checked == true) { trivago_showInternationalOpinions(); } else { trivago_hideInternationalOpinions(); } } 
if(typeof currentOnload == 'undefined') { var currentOnload = Array(); if(window.onload) { currentOnload.push(window.onload); }}
var trivago_reviewwidget_onload = function() { var trivagoLinkContainer = COM.GET.Elem('trivago_backlink'); var trivagoLink = trivagoLinkContainer.getElementsByTagName('a')[0]; var regexp = /^http:\/\/[0-9a-z\.]+\/([0-9A-Z\%\-]*|[a-z\-\.]*)\-[0-9]+\/([a-z]+|[0-9A-Z\%]+)\/([a-z0-9A-Z\%\-]*|[0-9a-zA-Z\-_\.]*)\-[0-9]+\/([a-z]+|[0-9A-Z\%\-]+)$/; if(regexp.test(trivagoLink.href)) {
COM.GET.Elem('trivago_opinions_widget').innerHTML = '<div>					<div style="text-align: right; margin-right: 20px;"><img src="http://ip2.trivago.com/images/layoutimages/widgets/widget_trivagoproved_en.png" title="" alt="" /></div>					<div id="trivago_opinions_title" style="float: left; text-align: left; margin-left: 20px; margin-top: 4px; font-size: 18pt; font-family: arial; color: #333333; font-weight: bold; line-height: 1.5;">Read our guest\'s opinions</div>				</div><br style="clear: both;">				<div id="trivago_opinions_content" style="margin-left: 20px; margin-right: 20px; height: 250px; overflow: auto; text-align: left; border: 1px solid #999999; background-color: #ffffff;"></div>					<div style="text-align: left; margin-left: 16px; margin-top: 5px; font-size: 10pt; font-family: arial; color: #333333;"><input type="checkbox" name="trivago_opinions_internationalopinions" checked="checked" id="trivago_opinions_internationalopinions" onclick="trivago_toggleInternationalOpinions(this);" /><label for="trivago_opinions_internationalopinions">View international reviews</label></div>					<div style="text-align: left; margin-left: 20px; margin-top: 15px; font-size: 15pt; font-family: arial; color: #333333; font-weight: bold;">Did you enjoy your stay with us?</div>					<div style="text-align: left; margin-left: 20px; margin-top: 15px; line-height: 1.5; font-size: 10pt; font-family: arial; color: #333333;">Then tell us and other guests what you liked!</div>					<div id="trivago_opinions_form" style="margin-left: 20px;margin-right: 20px; margin-top: 10px;">						<form name="trivago_add_opinion_form" id="trivago_add_opinion_form" method="post" accept-charset="UTF-8" onsubmit="return trivagoCheckWidgetForm();" action="http://www.trivago.co.uk/uk/srv/hotelreviews/rpc/main_v1.rpc?item_id=765756" style="text-align: left;">							<input type="hidden" name="trivago_hotel_id" value="765756" />							<div><input type="text" id="trivago_opinion_headline" name="trivago_opinion_headline" value="" style="width: 100%; border: 1px solid #999999;" /></div>							<div style="margin-top: 10px;">								<div style="background-color: #ffffff; float: left; margin-left: 2px; margin-right: 2px;">									<strong style="color: #333333; font-family: arial; font-size: 12px;">10/</strong><strong style="color: #999999; font-family: arial; font-size: 12px;">100</strong>								</div>								<div style="float: left; text-align: center;">									<input type="radio" id="trivago_rating_1" style="border: 0; background-color: #eeeeee;" name="trivago_hotel_rating" value="1" />									<input type="radio" id="trivago_rating_2" style="border: 0; background-color: #eeeeee;" name="trivago_hotel_rating" value="2" />									<input type="radio" id="trivago_rating_3" style="border: 0; background-color: #eeeeee;" name="trivago_hotel_rating" value="3" />									<input type="radio" id="trivago_rating_4" style="border: 0; background-color: #eeeeee;" name="trivago_hotel_rating" value="4" />									<input type="radio" id="trivago_rating_5" style="border: 0; background-color: #eeeeee;" name="trivago_hotel_rating" value="5" />									<input type="radio" id="trivago_rating_6" style="border: 0; background-color: #eeeeee;" name="trivago_hotel_rating" value="6" />									<input type="radio" id="trivago_rating_7" style="border: 0; background-color: #eeeeee;" name="trivago_hotel_rating" value="7" />									<input type="radio" id="trivago_rating_8" style="border: 0; background-color: #eeeeee;" name="trivago_hotel_rating" value="8" />									<input type="radio" id="trivago_rating_9" style="border: 0; background-color: #eeeeee;" name="trivago_hotel_rating" value="9" />									<input type="radio" id="trivago_rating_10" style="border: 0; background-color: #eeeeee;" name="trivago_hotel_rating" value="10" />								</div>								<div style="background-color: #ffffff; margin-left: 2px; margin-right: 2px; text-align: center; float: left;">									<strong style="float: left; color: #333333; font-family: arial; font-size: 12px;">100/</strong><strong style="float: left; color: #999999; font-family: arial; font-size: 12px;">100</strong>								</div>								<br style="clear:both;" />								<div style="width: 340px;">									<div style="width: 98px; text-align: center; float: left; margin-right: 120px;">										<label for="trivago_rating_1" style="width: 98px; font-size: 10pt; font-family: arial; font-style: italic; font-weight: normal; float: left;">											bad										</label>									</div>									<div style="float: left; width: 120px; text-align: center; margin: 0px; padding: 0px;">										<label for="trivago_rating_10" style="float: left; width: 120px; font-size: 10pt; font-family: arial; margin: 0px; padding: 0px; font-style: italic; font-weight: normal;">											perfect										</label>									</div>								</div>								<br style="clear: both;" />							</div>							<div style="margin-top: 10px;">								<textarea name="trivago_opinion_text" id="trivago_opinion_text" style="width: 100%; height: 100px; border: 1px solid #999999;"></textarea>							</div>							<div style="float: left; margin-top: 4px;">								<img style="float: left; margin: 0px; padding: 0px;" src="http://www.trivago.co.uk/trivago_rpc.php?action=captcha" />								&nbsp;								<input type="text" style="width: 140px; border: 1px solid #999999; font-size: 29px;" id="trivago_review_widget_captcha" name="trivago_review_widget_captcha" />								&nbsp;								<select name="trivago_opinion_language" style="border: 1px solid #999999; vertical-align: top;" onchange="trivago_changeFlag(this.value);"><option value="0" selected="selected">- Language -</option><option value="de">de</option><option value="es">es</option><option value="fr">fr</option><option value="se">se</option><option value="pl">pl</option><option value="it">it</option><option value="en">en</option><option value="gr">gr</option><option value="tr">tr</option><option value="ru">ru</option><option value="rs">rs</option><option value="pt">pt</option><option value="bg">bg</option><option value="nl">nl</option><option value="fi">fi</option><option value="ro">ro</option><option value="jp">jp</option><option value="cn">cn</option></select>&nbsp;<img style="display: none; margin-top: 6px; vertical-align: top;" src="http://ip2.trivago.com/images/layoutimages/flags/lang/en.png" alt="en" id="trivago_opinion_language_flag" name="trivago_opinion_language_flag" />							</div>							<br style="clear: both;" />							<div style="width: 100%; float: left; margin-right: 4px; margin-top: 4px; margin-left: 0px; padding: 0px; font-size: 10pt; font-family: arial; color: #333333;">								<input style="background-color: #eeeeee; border: 0px;" type="checkbox" id="trivago_affirm" name="trivago_affirm" /><label for="trivago_affirm">I certify that the review is based on my own experience.</label>							</div>							<div style="text-align: right; float: right; margin: 0px;">								<input type="submit" name="submit" value="Submit" />							</div>							<br style="clear: both; />"						</form>					</div>					';
} else { COM.GET.Elem('trivago_opinions_widget').style.display = 'none'; alert('Your Hotel Widget could not be generated. Please contact your trivago Hotel Relations Manager.'); }}
currentOnload.push(trivago_reviewwidget_onload);
window.onload = function() { while(doOnload = currentOnload.pop()) { doOnload(); } };