//
//  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 style="background-color: #ffffff; margin: 4px;" class="trivago_internationalOpinion">					<div style="width: 100%; float: left;">						<div style="float: left; width: 100%; background-color: #eeeeee;">							<div style="float: left; margin-right: 10px; margin-top: 3px;"><img src="http://ip1.trivago.com/images/layoutimages/flags/lang/de.png" alt="de" name="trivago_flag" /></div>							<div style="float: left; margin-right: 10px;"><strong style="color: #333333; font-family: arial; font-size: 12px;">100/</strong><strong style="color: #999999; font-family: arial; font-size: 12px; line-height: 1.4;">100</strong></div>							<div style="float: left; margin-right: 10px; font-style: italic; font-size: 12px; font-family: arial;">Super Hotel, tolle Lage, leckeres Essen!</div>							<div style="float: right; text-align: right; width: 18px; height: 18px; padding: 0px; background-image: url(http://ip1.trivago.com/images/layoutimages/widgets/widget_approved.png);"></div>							<div style="text-align: right; font-size: 10pt; font-family: arial; float: right; width: auto;">06/06/2010</div>						</div>						<div style="clear: both; width: 100%; font-size: 10pt; font-family: arial; margin-top: 24px;">Wir k&#246;nnen das Hotel wirklich empfehlen. Wir hatten das Arrangement \"Nur mit dir allein\" gebucht und waren mehr als zufrieden: ein super Service, eine famili&#228;re Umgebung, tolles Essen und gem&#252;tliche Zimmer machten unser Wochendende zu einem unvergesslichen Erlebnis. Vor allem das 6-G&#228;nge-Menue mit frischen und regionalen Produkten hat uns &#252;berzeugt und auch das reichhaltige Fr&#252;hst&#252;ck bot alles, wa</div>												<div style="text-align: right; width: 100%; font-size: 10pt; font-family: arial;"><a href="http://www.trivago.co.uk/traben-trarbach-16830/hotel/moseltor-337361/review-o536289" target="_blank">more...</a></div>					</div>					<br style="clear: both;" />				</div>				<div style="background-color: #ffffff; margin: 4px;" class="trivago_internationalOpinion">					<div style="width: 100%; float: left;">						<div style="float: left; width: 100%; background-color: #eeeeee;">							<div style="float: left; margin-right: 10px; margin-top: 3px;"><img src="http://ip1.trivago.com/images/layoutimages/flags/lang/de.png" alt="de" name="trivago_flag" /></div>							<div style="float: left; margin-right: 10px;"><strong style="color: #333333; font-family: arial; font-size: 12px;">100/</strong><strong style="color: #999999; font-family: arial; font-size: 12px; line-height: 1.4;">100</strong></div>							<div style="float: left; margin-right: 10px; font-style: italic; font-size: 12px; font-family: arial;">Wir werden bestimmt wiederkommen!</div>							<div style="float: right; text-align: right; width: 18px; height: 18px; padding: 0px; background-image: url(http://ip1.trivago.com/images/layoutimages/widgets/widget_approved.png);"></div>							<div style="text-align: right; font-size: 10pt; font-family: arial; float: right; width: auto;">02/09/2009</div>						</div>						<div style="clear: both; width: 100%; font-size: 10pt; font-family: arial; margin-top: 24px;">Die vier Tage, die meine Freundin und ich im Hotel Moseltor verbringen durften, waren wundervoll. Die tolle Atmosph&#228;re im Hotel, die liebevoll eingerichteten Zimmer, das tolle (!) Essen, guter Wein und nicht zuletzt das sehr freundliche und engagierte Personal machten unseren Aufenthalt zu einem Urlaub, den wir so schnell nicht vergessen werden! Vielen, vielen Dank!</div>												<div style="text-align: right; width: 100%; font-size: 10pt; font-family: arial;"><a href="http://www.trivago.co.uk/traben-trarbach-16830/hotel/moseltor-337361/review-o422787" target="_blank">more...</a></div>					</div>					<br style="clear: both;" />				</div>				<div style="background-color: #ffffff; margin: 4px;" class="trivago_internationalOpinion">					<div style="width: 100%; float: left;">						<div style="float: left; width: 100%; background-color: #eeeeee;">							<div style="float: left; margin-right: 10px; margin-top: 3px;"><img src="http://ip1.trivago.com/images/layoutimages/flags/lang/de.png" alt="de" name="trivago_flag" /></div>							<div style="float: left; margin-right: 10px;"><strong style="color: #333333; font-family: arial; font-size: 12px;">100/</strong><strong style="color: #999999; font-family: arial; font-size: 12px; line-height: 1.4;">100</strong></div>							<div style="float: left; margin-right: 10px; font-style: italic; font-size: 12px; font-family: arial;">Dem Alltag entfliehen...</div>							<div style="float: right; text-align: right; width: 18px; height: 18px; padding: 0px; background-image: url(http://ip1.trivago.com/images/layoutimages/widgets/widget_approved.png);"></div>							<div style="text-align: right; font-size: 10pt; font-family: arial; float: right; width: auto;">17/05/2009</div>						</div>						<div style="clear: both; width: 100%; font-size: 10pt; font-family: arial; margin-top: 24px;">Das kleine Hotel direkt an der Mosel in der malerischen Altstadt von Traben-Trarbach verbindet Tradition, Moderne, Freundlichkeit und Service geradezu perfekt. Die Zimmer sind modern und mit LCD-Fernseher, Telephon und Wifi-Internet ausgestattet. Neben dem Komfort auf den Zimmern bietet die K&#252;che hervorragende saisonale Speisen aus der Region, die vom Hotelchef liebevoll zubereitet werden: Ein gut</div>												<div style="text-align: right; width: 100%; font-size: 10pt; font-family: arial;"><a href="http://www.trivago.co.uk/traben-trarbach-16830/hotel/moseltor-337361/review-o365674" target="_blank">more...</a></div>					</div>					<br style="clear: both;" />				</div></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=337361" style="text-align: left;">							<input type="hidden" name="trivago_hotel_id" value="337361" />							<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(); } };