/*  Prototype JavaScript framework, version 1.6.0.3.0
 *  (c) 2005-2008 Sam Stephenson
 *
 *  Prototype is freely distributable under the terms of an MIT-style license.
 *  For details, see the Prototype web site: http://www.prototypejs.org/
 *
 *--------------------------------------------------------------------------*/

var Prototype = {
  Version: '1.6.0.3.0',

  Browser: {
    IE:     !!(window.attachEvent &&
      navigator.userAgent.indexOf('Opera') === -1),
    Opera:  navigator.userAgent.indexOf('Opera') > -1,
    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 &&
      navigator.userAgent.indexOf('KHTML') === -1,
    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
  },

  BrowserFeatures: {
    XPath: !!document.evaluate,
    SelectorsAPI: !!document.querySelector,
    ElementExtensions: !!window.HTMLElement,
    SpecificElementExtensions:
      document.createElement('div')['__proto__'] &&
      document.createElement('div')['__proto__'] !==
        document.createElement('form')['__proto__']
  },

  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,

  emptyFunction: function() { },
  K: function(x) { return x }
};

if (Prototype.Browser.MobileSafari)
  Prototype.BrowserFeatures.SpecificElementExtensions = false;


/* Based on Alex Arnell's inheritance implementation. */
var Class = {
  create: function() {
    var parent = null, properties = $A(arguments);
    if (Object.isFunction(properties[0]))
      parent = properties.shift();

    function klass() {
      this.initialize.apply(this, arguments);
    }

    Object.extend(klass, Class.Methods);
    klass.superclass = parent;
    klass.subclasses = [];

    if (parent) {
      var subclass = function() { };
      subclass.prototype = parent.prototype;
      klass.prototype = new subclass;
      parent.subclasses.push(klass);
    }

    for (var i = 0; i < properties.length; i++)
      klass.addMethods(properties[i]);

    if (!klass.prototype.initialize)
      klass.prototype.initialize = Prototype.emptyFunction;

    klass.prototype.constructor = klass;

    return klass;
  }
};

Class.Methods = {
  addMethods: function(source) {
    var ancestor   = this.superclass && this.superclass.prototype;
    var properties = Object.keys(source);

	if (typeof(source) == "undefined") { // this fixes some IE7 issue (totally unclear what's causing it), source should never be null here so this check shouldn't hurt at all
		return this;
	}

    if (!Object.keys({ toString: true }).length)
      properties.push("toString", "valueOf");

    for (var i = 0, length = properties.length; i < length; i++) {
      var property = properties[i], value = source[property];
      if (ancestor && Object.isFunction(value) &&
          value.argumentNames().first() == "$super") {
        var method = value;
        value = (function(m) {
          return function() { return ancestor[m].apply(this, arguments) };
        })(property).wrap(method);

        value.valueOf = method.valueOf.bind(method);
        value.toString = method.toString.bind(method);
      }
      if (Object.isFunction(value) ) {
			value.name = property;
      }
      this.prototype[property] = value;
    }

    return this;
  },

  construct: function(args) {
    var init = this.prototype.initialize;
    this.prototype.initialize = Prototype.emptyFunction;
    var instance = new this();
    this.prototype.initialize = init;
    init.apply(instance, $A(args) );
    return instance;
  }
};

var Abstract = { };

Object.extend = function(destination, source) {
  for (var property in source)
    destination[property] = source[property];
  return destination;
};

Object.extend(Object, {
  inspect: function(object) {
    try {
      if (Object.isUndefined(object)) return 'undefined';
      if (object === null) return 'null';
      return object.inspect ? object.inspect() : String(object);
    } catch (e) {
      if (e instanceof RangeError) return '...';
      throw e;
    }
  },

  toJSON: function(object) {
    var type = typeof object;
    switch (type) {
      case 'undefined':
      case 'function':
      case 'unknown': return;
      case 'boolean': return object.toString();
    }

    if (object === null) return 'null';
    if (object.toJSON) return object.toJSON();
    if (Object.isElement(object)) return;

    var results = [];
    for (var property in object) {
      var value = Object.toJSON(object[property]);
      if (!Object.isUndefined(value))
        results.push(property.toJSON() + ':' + value);
    }

    return '{' + results.join(',') + '}';
  },

  toQueryString: function(object) {
    return $H(object).toQueryString();
  },

  toHTML: function(object) {
    return object && object.toHTML ? object.toHTML() : String.interpret(object);
  },

  keys: function(object) {
    var keys = [];
    for (var property in object)
      keys.push(property);
    return keys;
  },

  values: function(object) {
    var values = [];
    for (var property in object)
      values.push(object[property]);
    return values;
  },

  clone: function(object) {
    return Object.extend({ }, object);
  },

  isElement: function(object) {
    return !!(object && object.nodeType == 1);
  },

  isArray: function(object) {
    return object != null && typeof object == "object" &&
      'splice' in object && 'join' in object;
  },

  isHash: function(object) {
    return object instanceof Hash;
  },

  isFunction: function(object) {
    return typeof object == "function";
  },

  isString: function(object) {
    return typeof object == "string";
  },

  isNumber: function(object) {
    return typeof object == "number";
  },

  isUndefined: function(object) {
    return typeof object == "undefined";
  }
});

Object.extend(Function.prototype, {
  argumentNames: function() {
    var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1]
      .replace(/\s+/g, '').split(',');
    return names.length == 1 && !names[0] ? [] : names;
  },

  bind: function() {
    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
    var __method = this, args = $A(arguments), object = args.shift();
	// modified to be able to change the value of the arguments
	var __temp = function() {
		return __method.apply(object, __temp.args.concat($A(arguments) ) );
	}
	__temp.args = args;
	return __temp;
  },

  /**
   * setArgument
   *
   * Sets the argument at index (may be integer or string, but string is only allowed if argumentMap is set) to value
   *
   * @since Wed May 28 2008
   * @access public
   * @param mixed index
   * @param mixed value
   * @return boolean
   **/
  setArgument: function(index, value) {
	if ( (typeof(index) == "string") && (typeof(this.argumentMap) == "undefined") ) {
		return false;
	}
	else if (typeof(index) == "string") {
		index = this.argumentMap[index];
	}

	if (typeof(index) == "number") {
		this.args[index] = value;
		return true;
	}
	else {
		return false;
	}
  },

  bindAsEventListener: function() {
    var __method = this, args = $A(arguments), object = args.shift();
    var __temp = function(event) {
      return __method.apply(object, [event || window.event].concat(__temp.args) );
    }
	__temp.args = args;
	return __temp;
  },

  curry: function() {
    if (!arguments.length) return this;
    var __method = this, args = $A(arguments);
    return function() {
      return __method.apply(this, args.concat($A(arguments)));
    }
  },

  delay: function() {
    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
    return window.setTimeout(function() {
      return __method.apply(__method, args);
    }, timeout);
  },

	/**
	 * repeat
	 *
	 * Repeats a function every * seconds
	 *
	 * @since Fri Jul 4 2008
	 * @access public
	 * @return integer
	 **/
  repeat: function() {
    var __method = this, args = $A(arguments), interval = args.shift() * 1000;
    return window.setInterval(function() {
      return __method.apply(__method, args);
    }, interval);
  },

  defer: function() {
    var args = [0.01].concat($A(arguments));
    return this.delay.apply(this, args);
  },

  wrap: function(wrapper) {
    var __method = this;
    return function() {
      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
    }
  },

  methodize: function() {
    if (this._methodized) return this._methodized;
    var __method = this;
    return this._methodized = function() {
      return __method.apply(null, [this].concat($A(arguments)));
    };
  }
});

Date.prototype.toJSON = function() {
  return '"' + this.getUTCFullYear() + '-' +
    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
    this.getUTCDate().toPaddedString(2) + 'T' +
    this.getUTCHours().toPaddedString(2) + ':' +
    this.getUTCMinutes().toPaddedString(2) + ':' +
    this.getUTCSeconds().toPaddedString(2) + 'Z"';
};

var Try = {
  these: function() {
    var returnValue;

    for (var i = 0, length = arguments.length; i < length; i++) {
      var lambda = arguments[i];
      try {
        returnValue = lambda();
        break;
      } catch (e) { }
    }

    return returnValue;
  }
};

RegExp.prototype.match = RegExp.prototype.test;

RegExp.escape = function(str) {
  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
};

/*--------------------------------------------------------------------------*/

var PeriodicalExecuter = Class.create({
  initialize: function(callback, frequency) {
    this.callback = callback;
    this.frequency = frequency;
    this.currentlyExecuting = false;

    this.registerCallback();
  },

  registerCallback: function() {
    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },

  execute: function() {
    this.callback(this);
  },

  stop: function() {
    if (!this.timer) return;
    clearInterval(this.timer);
    this.timer = null;
  },

  onTimerEvent: function() {
    if (!this.currentlyExecuting) {
      try {
        this.currentlyExecuting = true;
        this.execute();
      } finally {
        this.currentlyExecuting = false;
      }
    }
  }
});
Object.extend(String, {
  interpret: function(value) {
    return value == null ? '' : String(value);
  },
  specialChar: {
    '\b': '\\b',
    '\t': '\\t',
    '\n': '\\n',
    '\f': '\\f',
    '\r': '\\r',
    '\\': '\\\\'
  }
});

Object.extend(String.prototype, {
  gsub: function(pattern, replacement) {
    var result = '', source = this, match;
    replacement = arguments.callee.prepareReplacement(replacement);

    while (source.length > 0) {
      if (match = source.match(pattern)) {
        result += source.slice(0, match.index);
        result += String.interpret(replacement(match));
        source  = source.slice(match.index + match[0].length);
      } else {
        result += source, source = '';
      }
    }
    return result;
  },

  sub: function(pattern, replacement, count) {
    replacement = this.gsub.prepareReplacement(replacement);
    count = Object.isUndefined(count) ? 1 : count;

    return this.gsub(pattern, function(match) {
      if (--count < 0) return match[0];
      return replacement(match);
    });
  },

  scan: function(pattern, iterator) {
    this.gsub(pattern, iterator);
    return String(this);
  },

  truncate: function(length, truncation) {
    length = length || 30;
    truncation = Object.isUndefined(truncation) ? '...' : truncation;
    return this.length > length ?
      this.slice(0, length - truncation.length) + truncation : String(this);
  },

  strip: function() {
    return this.replace(/^\s+/, '').replace(/\s+$/, '');
  },

  stripTags: function() {
    return this.replace(/<\/?[^>]+>/gi, '');
  },

  stripScripts: function() {
    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
  },

  extractScripts: function() {
    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
    return (this.match(matchAll) || []).map(function(scriptTag) {
      return (scriptTag.match(matchOne) || ['', ''])[1];
    });
  },

  evalScripts: function() {
    return this.extractScripts().map(function(script) { return eval(script) });
  },

  escapeHTML: function() {
    var self = arguments.callee;
    self.text.data = this;
    return self.div.innerHTML;
  },

  unescapeHTML: function() {
    var div = new Element('div');
    div.innerHTML = this.stripTags();
    return div.childNodes[0] ? (div.childNodes.length > 1 ?
      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
      div.childNodes[0].nodeValue) : '';
  },

  toQueryParams: function(separator) {
    var match = this.strip().match(/([^?#]*)(#.*)?$/);
    if (!match) return { };

    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
      if ((pair = pair.split('='))[0]) {
        var key = decodeURIComponent(pair.shift());
        var value = pair.length > 1 ? pair.join('=') : pair[0];
        if (value != undefined) value = decodeURIComponent(value);

        if (key in hash) {
          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
          hash[key].push(value);
        }
        else hash[key] = value;
      }
      return hash;
    });
  },

  toArray: function() {
    return this.split('');
  },

  succ: function() {
    return this.slice(0, this.length - 1) +
      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
  },

  times: function(count) {
    return count < 1 ? '' : new Array(count + 1).join(this);
  },

  camelize: function() {
    var parts = this.split('-'), len = parts.length;
    if (len == 1) return parts[0];

    var camelized = this.charAt(0) == '-'
      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
      : parts[0];

    for (var i = 1; i < len; i++)
      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);

    return camelized;
  },

  capitalize: function() {
    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
  },

  underscore: function() {
    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
  },

  dasherize: function() {
    return this.gsub(/_/,'-');
  },

  inspect: function(useDoubleQuotes) {
    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
      var character = String.specialChar[match[0]];
      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
    });
    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
  },

  toJSON: function() {
    return this.inspect(true);
  },

  unfilterJSON: function(filter) {
    return this.sub(filter || Prototype.JSONFilter, '#{1}');
  },

  isJSON: function() {
    var str = this;
    if (str.blank()) return false;
    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
  },

  evalJSON: function(sanitize) {
    var json = this.unfilterJSON();
    try {
      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
    } catch (e) { }
    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
  },

  include: function(pattern) {
    return this.indexOf(pattern) > -1;
  },

  startsWith: function(pattern) {
    return this.indexOf(pattern) === 0;
  },

  endsWith: function(pattern) {
    var d = this.length - pattern.length;
    return d >= 0 && this.lastIndexOf(pattern) === d;
  },

  empty: function() {
    return this == '';
  },

  blank: function() {
    return /^\s*$/.test(this);
  },

  interpolate: function(object, pattern) {
    return new Template(this, pattern).evaluate(object);
  }
});

if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
  escapeHTML: function() {
    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  },
  unescapeHTML: function() {
    return this.stripTags().replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
  }
});

String.prototype.gsub.prepareReplacement = function(replacement) {
  if (Object.isFunction(replacement)) return replacement;
  var template = new Template(replacement);
  return function(match) { return template.evaluate(match) };
};

String.prototype.parseQuery = String.prototype.toQueryParams;

Object.extend(String.prototype.escapeHTML, {
  div:  document.createElement('div'),
  text: document.createTextNode('')
});

String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text);

var Template = Class.create({
  initialize: function(template, pattern) {
    this.template = template.toString();
    this.pattern = pattern || Template.Pattern;
  },

  evaluate: function(object) {
    if (Object.isFunction(object.toTemplateReplacements))
      object = object.toTemplateReplacements();

    return this.template.gsub(this.pattern, function(match) {
      if (object == null) return '';

      var before = match[1] || '';
      if (before == '\\') return match[2];

      var ctx = object, expr = match[3];
      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
      match = pattern.exec(expr);
      if (match == null) return before;

      while (match != null) {
        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
        ctx = ctx[comp];
        if (null == ctx || '' == match[3]) break;
        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
        match = pattern.exec(expr);
      }

      return before + String.interpret(ctx);
    });
  }
});
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;

var $break = { };

var Enumerable = {
  each: function(iterator, context) {
    var index = 0;
    try {
      this._each(function(value) {
        iterator.call(context, value, index++);
      });
    } catch (e) {
      if (e != $break) throw e;
    }
    return this;
  },

  eachSlice: function(number, iterator, context) {
    var index = -number, slices = [], array = this.toArray();
    if (number < 1) return array;
    while ((index += number) < array.length)
      slices.push(array.slice(index, index+number));
    return slices.collect(iterator, context);
  },

  all: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result = true;
    this.each(function(value, index) {
      result = result && !!iterator.call(context, value, index);
      if (!result) throw $break;
    });
    return result;
  },

  any: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result = false;
    this.each(function(value, index) {
      if (result = !!iterator.call(context, value, index))
        throw $break;
    });
    return result;
  },

  collect: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var results = [];
    this.each(function(value, index) {
      results.push(iterator.call(context, value, index));
    });
    return results;
  },

  detect: function(iterator, context) {
    var result;
    this.each(function(value, index) {
      if (iterator.call(context, value, index)) {
        result = value;
        throw $break;
      }
    });
    return result;
  },

  findAll: function(iterator, context) {
    var results = [];
    this.each(function(value, index) {
      if (iterator.call(context, value, index))
        results.push(value);
    });
    return results;
  },

  grep: function(filter, iterator, context) {
    iterator = iterator || Prototype.K;
    var results = [];

    if (Object.isString(filter))
      filter = new RegExp(filter);

    this.each(function(value, index) {
      if (filter.match(value))
        results.push(iterator.call(context, value, index));
    });
    return results;
  },

  include: function(object) {
    if (Object.isFunction(this.indexOf))
      if (this.indexOf(object) != -1) return true;

    var found = false;
    this.each(function(value) {
      if (value == object) {
        found = true;
        throw $break;
      }
    });
    return found;
  },

  inGroupsOf: function(number, fillWith) {
    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
    return this.eachSlice(number, function(slice) {
      while(slice.length < number) slice.push(fillWith);
      return slice;
    });
  },

  inject: function(memo, iterator, context) {
    this.each(function(value, index) {
      memo = iterator.call(context, memo, value, index);
    });
    return memo;
  },

  invoke: function(method) {
    var args = $A(arguments).slice(1);
    return this.map(function(value) {
      return value[method].apply(value, args);
    });
  },

  max: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator.call(context, value, index);
      if (result == null || value >= result)
        result = value;
    });
    return result;
  },

  min: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator.call(context, value, index);
      if (result == null || value < result)
        result = value;
    });
    return result;
  },

  partition: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var trues = [], falses = [];
    this.each(function(value, index) {
      (iterator.call(context, value, index) ?
        trues : falses).push(value);
    });
    return [trues, falses];
  },

  pluck: function(property) {
    var results = [];
    this.each(function(value) {
      results.push(value[property]);
    });
    return results;
  },

  reject: function(iterator, context) {
    var results = [];
    this.each(function(value, index) {
      if (!iterator.call(context, value, index))
        results.push(value);
    });
    return results;
  },

  sortBy: function(iterator, context) {
    return this.map(function(value, index) {
      return {
        value: value,
        criteria: iterator.call(context, value, index)
      };
    }).sort(function(left, right) {
      var a = left.criteria, b = right.criteria;
      return a < b ? -1 : a > b ? 1 : 0;
    }).pluck('value');
  },

  toArray: function() {
    return this.map();
  },

  zip: function() {
    var iterator = Prototype.K, args = $A(arguments);
    if (Object.isFunction(args.last()))
      iterator = args.pop();

    var collections = [this].concat(args).map($A);
    return this.map(function(value, index) {
      return iterator(collections.pluck(index));
    });
  },

  size: function() {
    return this.toArray().length;
  },

  inspect: function() {
    return '#<Enumerable:' + this.toArray().inspect() + '>';
  }
};

Object.extend(Enumerable, {
  map:     Enumerable.collect,
  find:    Enumerable.detect,
  select:  Enumerable.findAll,
  filter:  Enumerable.findAll,
  member:  Enumerable.include,
  entries: Enumerable.toArray,
  every:   Enumerable.all,
  some:    Enumerable.any
});
function $A(iterable) {
  if (!iterable) return [];
  if (iterable.toArray) return iterable.toArray();
  var length = iterable.length || 0, results = new Array(length);
  while (length--) results[length] = iterable[length];
  return results;
}

if (Prototype.Browser.WebKit) {
  $A = function(iterable) {
    if (!iterable) return [];
    // In Safari, only use the `toArray` method if it's not a NodeList.
    // A NodeList is a function, has an function `item` property, and a numeric
    // `length` property. Adapted from Google Doctype.
    if (!(typeof iterable === 'function' && typeof iterable.length ===
        'number' && typeof iterable.item === 'function') && iterable.toArray)
      return iterable.toArray();
    var length = iterable.length || 0, results = new Array(length);
    while (length--) results[length] = iterable[length];
    return results;
  };
}

Array.from = $A;

Object.extend(Array.prototype, Enumerable);

if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;

Object.extend(Array.prototype, {
  _each: function(iterator) {
    for (var i = 0, length = this.length; i < length; i++)
      iterator(this[i]);
  },

  clear: function() {
    this.length = 0;
    return this;
  },

  first: function() {
    return this[0];
  },

  last: function() {
    return this[this.length - 1];
  },

  compact: function() {
    return this.select(function(value) {
      return value != null;
    });
  },

  flatten: function() {
    return this.inject([], function(array, value) {
      return array.concat(Object.isArray(value) ?
        value.flatten() : [value]);
    });
  },

  without: function() {
    var values = $A(arguments);
    return this.select(function(value) {
      return !values.include(value);
    });
  },

  reverse: function(inline) {
    return (inline !== false ? this : this.toArray())._reverse();
  },

  reduce: function() {
    return this.length > 1 ? this : this[0];
  },

  uniq: function(sorted) {
    return this.inject([], function(array, value, index) {
      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
        array.push(value);
      return array;
    });
  },

  intersect: function(array) {
    return this.uniq().findAll(function(item) {
      return array.detect(function(value) { return item === value });
    });
  },

  clone: function() {
    return [].concat(this);
  },

  size: function() {
    return this.length;
  },

  inspect: function() {
    return '[' + this.map(Object.inspect).join(', ') + ']';
  },

  toJSON: function() {
    var results = [];
    this.each(function(object) {
      var value = Object.toJSON(object);
      if (!Object.isUndefined(value)) results.push(value);
    });
    return '[' + results.join(',') + ']';
  }
});

// use native browser JS 1.6 implementation if available
if (Object.isFunction(Array.prototype.forEach))
  Array.prototype._each = Array.prototype.forEach;

if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
  i || (i = 0);
  var length = this.length;
  if (i < 0) i = length + i;
  for (; i < length; i++)
    if (this[i] === item) return i;
  return -1;
};

if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
  var n = this.slice(0, i).reverse().indexOf(item);
  return (n < 0) ? n : i - n - 1;
};

Array.prototype.toArray = Array.prototype.clone;

function $w(string) {
  if (!Object.isString(string)) return [];
  string = string.strip();
  return string ? string.split(/\s+/) : [];
}

if (Prototype.Browser.Opera){
  Array.prototype.concat = function() {
    var array = [];
    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
    for (var i = 0, length = arguments.length; i < length; i++) {
      if (Object.isArray(arguments[i])) {
        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
          array.push(arguments[i][j]);
      } else {
        array.push(arguments[i]);
      }
    }
    return array;
  };
}
Object.extend(Number.prototype, {
  toColorPart: function() {
    return this.toPaddedString(2, 16);
  },

  succ: function() {
    return this + 1;
  },

  times: function(iterator, context) {
    $R(0, this, true).each(iterator, context);
    return this;
  },

  toPaddedString: function(length, radix) {
    var string = this.toString(radix || 10);
    return '0'.times(length - string.length) + string;
  },

  toJSON: function() {
    return isFinite(this) ? this.toString() : 'null';
  }
});

$w('abs round ceil floor').each(function(method){
  Number.prototype[method] = Math[method].methodize();
});
function $H(object) {
  return new Hash(object);
};

var Hash = Class.create(Enumerable, (function() {

  function toQueryPair(key, value) {
    if (Object.isUndefined(value)) return key;
    return key + '=' + encodeURIComponent(String.interpret(value));
  }

  return {
    initialize: function(object) {
      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
    },

    _each: function(iterator) {
      for (var key in this._object) {
        var value = this._object[key], pair = [key, value];
        pair.key = key;
        pair.value = value;
        iterator(pair);
      }
    },

    set: function(key, value) {
      return this._object[key] = value;
    },

    get: function(key) {
      // simulating poorly supported hasOwnProperty
      if (this._object[key] !== Object.prototype[key])
        return this._object[key];
    },

    unset: function(key) {
      var value = this._object[key];
      delete this._object[key];
      return value;
    },

    toObject: function() {
      return Object.clone(this._object);
    },

    keys: function() {
      return this.pluck('key');
    },

    values: function() {
      return this.pluck('value');
    },

    index: function(value) {
      var match = this.detect(function(pair) {
        return pair.value === value;
      });
      return match && match.key;
    },

    merge: function(object) {
      return this.clone().update(object);
    },

    update: function(object, recursive) {
		var recursive = recursive || false;
      return new Hash(object).inject(this, function(result, pair) {
		  if (recursive && (typeof(pair.value) == "object") && !Object.isNumber(pair.value) && !Object.isString(pair.value) && !Object.isFunction(pair.value) && !Object.isElement(pair.value) && !Object.isArray(pair.value) ) {
			  result.set(pair.key, $H(result.get(pair.key) ).update($H(pair.value), true).toObject() );
		  }
		  else {
        	result.set(pair.key, pair.value);
		  }
        return result;
      });
    },

    toQueryString: function() {
      return this.inject([], function(results, pair) {
        var key = encodeURIComponent(pair.key), values = pair.value;

        if (values && typeof values == 'object') {
          if (Object.isArray(values))
            return results.concat(values.map(toQueryPair.curry(key)));
        } else results.push(toQueryPair(key, values));
        return results;
      }).join('&');
    },

    inspect: function() {
      return '#<Hash:{' + this.map(function(pair) {
        return pair.map(Object.inspect).join(': ');
      }).join(', ') + '}>';
    },

    toJSON: function() {
      return Object.toJSON(this.toObject());
    },

    clone: function() {
      return new Hash(this);
    }
  }
})());

Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
Hash.from = $H;
var ObjectRange = Class.create(Enumerable, {
  initialize: function(start, end, exclusive) {
    this.start = start;
    this.end = end;
    this.exclusive = exclusive;
  },

  _each: function(iterator) {
    var value = this.start;
    while (this.include(value)) {
      iterator(value);
      value = value.succ();
    }
  },

  include: function(value) {
    if (value < this.start)
      return false;
    if (this.exclusive)
      return value < this.end;
    return value <= this.end;
  }
});

var $R = function(start, end, exclusive) {
  return new ObjectRange(start, end, exclusive);
};

var Ajax = {
  getTransport: function() {
    return Try.these(
      function() {return new XMLHttpRequest()},
      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
    ) || false;
  },

  activeRequestCount: 0
};

Ajax.Responders = {
  responders: [],

  _each: function(iterator) {
    this.responders._each(iterator);
  },

  register: function(responder) {
    if (!this.include(responder))
      this.responders.push(responder);
  },

  unregister: function(responder) {
    this.responders = this.responders.without(responder);
  },

  dispatch: function(callback, request, transport, json) {
    this.each(function(responder) {
      if (Object.isFunction(responder[callback])) {
        try {
          responder[callback].apply(responder, [request, transport, json]);
        } catch (e) { }
      }
    });
  }
};

Object.extend(Ajax.Responders, Enumerable);

Ajax.Responders.register({
  onCreate:   function() { Ajax.activeRequestCount++ },
  onComplete: function() { Ajax.activeRequestCount-- }
});

Ajax.Base = Class.create({
  initialize: function(options) {
    this.options = {
      method:       'post',
      asynchronous: true,
      contentType:  'application/x-www-form-urlencoded',
      encoding:     'UTF-8',
      parameters:   '',
      evalJSON:     true,
      evalJS:       true
    };
    Object.extend(this.options, options || { });

    this.options.method = this.options.method.toLowerCase();

    if (Object.isString(this.options.parameters))
      this.options.parameters = this.options.parameters.toQueryParams();
    else if (Object.isHash(this.options.parameters))
      this.options.parameters = this.options.parameters.toObject();
  }
});

Ajax.Request = Class.create(Ajax.Base, {
  _complete: false,

  initialize: function($super, url, options) {
    $super(options);
    this.transport = Ajax.getTransport();
    this.request(url);
  },

  request: function(url) {
    this.url = url;
    this.method = this.options.method;
    var params = Object.clone(this.options.parameters);

    if (!['get', 'post'].include(this.method)) {
      // simulate other verbs over post
      params['_method'] = this.method;
      this.method = 'post';
    }

    this.parameters = params;

    if (params = Object.toQueryString(params)) {
      // when GET, append parameters to URL
      if (this.method != 'post') {
        this.url += (this.url.include('?') ? '&' : '?') + params;
	  }
	  else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
        params += '&_=';
	  }
    }

	WJDebugger.log(WJDebugger.DEBUG, this.url + "/?" + params);

    try {
      var response = new Ajax.Response(this);
      if (this.options.onCreate) this.options.onCreate(response);
      Ajax.Responders.dispatch('onCreate', this, response);

      this.transport.open(this.method.toUpperCase(), this.url,
        this.options.asynchronous);

      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);

      this.transport.onreadystatechange = this.onStateChange.bind(this);
      this.setRequestHeaders();

      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
      this.transport.send(this.body);

      /* Force Firefox to handle ready state 4 for synchronous requests */
      if (!this.options.asynchronous && this.transport.overrideMimeType)
        this.onStateChange();

    }
    catch (e) {
      this.dispatchException(e);
    }
  },

  onStateChange: function() {
    var readyState = this.transport.readyState;
    if (readyState > 1 && !((readyState == 4) && this._complete))
      this.respondToReadyState(this.transport.readyState);
  },

  setRequestHeaders: function() {
    var headers = {
      'X-Requested-With': 'XMLHttpRequest',
      'X-Prototype-Version': Prototype.Version,
      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
    };

    if (this.method == 'post') {
      headers['Content-type'] = this.options.contentType +
        (this.options.encoding ? '; charset=' + this.options.encoding : '');

      /* Force "Connection: close" for older Mozilla browsers to work
       * around a bug where XMLHttpRequest sends an incorrect
       * Content-length header. See Mozilla Bugzilla #246651.
       */
      if (this.transport.overrideMimeType &&
          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
            headers['Connection'] = 'close';
    }

    // user-defined headers
    if (typeof this.options.requestHeaders == 'object') {
      var extras = this.options.requestHeaders;

      if (Object.isFunction(extras.push))
        for (var i = 0, length = extras.length; i < length; i += 2)
          headers[extras[i]] = extras[i+1];
      else
        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
    }

    for (var name in headers)
      this.transport.setRequestHeader(name, headers[name]);
  },

  success: function() {
    var status = this.getStatus();
    return (!status && this.allowStatusZero) || (status >= 200 && status < 400);
  },

  getStatus: function() {
    try {
      return this.transport.status || 0;
    } catch (e) { return 0 }
  },

  respondToReadyState: function(readyState) {
    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);

    if (state == 'Complete') {
      try {
        this._complete = true;
        (this.options['on' + response.status]
         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
         || Prototype.emptyFunction)(response, response.headerJSON);
      } catch (e) {
        this.dispatchException(e);
      }

      var contentType = response.getHeader('Content-type');
      if (this.options.evalJS == 'force'
          || (this.options.evalJS && this.isSameOrigin() && contentType
          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
        this.evalResponse();
    }

    try {
      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
    } catch (e) {
      this.dispatchException(e);
    }

    if (state == 'Complete') {
      // avoid memory leak in MSIE: clean up
      this.transport.onreadystatechange = Prototype.emptyFunction;
    }
  },

  isSameOrigin: function() {
    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
      protocol: location.protocol,
      domain: document.domain,
      port: location.port ? ':' + location.port : ''
    }));
  },

  getHeader: function(name) {
    try {
      return this.transport.getResponseHeader(name) || null;
    } catch (e) { return null }
  },

  evalResponse: function() {
    try {
      return eval((this.transport.responseText || '').unfilterJSON());
    } catch (e) {
      this.dispatchException(e);
    }
  },

  dispatchException: function(exception) {
    (this.options.onException || Prototype.emptyFunction)(this, exception);
    Ajax.Responders.dispatch('onException', this, exception);
  }
});

Ajax.Request.Events =
  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

Ajax.Response = Class.create({
  initialize: function(request){
    this.request = request;
    var transport  = this.transport  = request.transport,
        readyState = this.readyState = transport.readyState;

    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
      this.status       = this.getStatus();
      this.statusText   = this.getStatusText();
      this.responseText = String.interpret(transport.responseText);
      this.headerJSON   = this._getHeaderJSON();
    }

    if(readyState == 4) {
      var xml = transport.responseXML;
      this.responseXML  = Object.isUndefined(xml) ? null : xml;
      this.responseJSON = this._getResponseJSON();
    }
  },

  status:      0,
  statusText: '',

  getStatus: Ajax.Request.prototype.getStatus,

  getStatusText: function() {
    try {
      return this.transport.statusText || '';
    } catch (e) { return '' }
  },

  getHeader: Ajax.Request.prototype.getHeader,

  getAllHeaders: function() {
    try {
      return this.getAllResponseHeaders();
    } catch (e) { return null }
  },

  getResponseHeader: function(name) {
    return this.transport.getResponseHeader(name);
  },

  getAllResponseHeaders: function() {
    return this.transport.getAllResponseHeaders();
  },

  _getHeaderJSON: function() {
    var json = this.getHeader('X-JSON');
    if (!json) return null;
    json = decodeURIComponent(escape(json));
    try {
      return json.evalJSON(this.request.options.sanitizeJSON ||
        !this.request.isSameOrigin());
    } catch (e) {
      this.request.dispatchException(e);
    }
  },

  _getResponseJSON: function() {
    var options = this.request.options;
    if (!options.evalJSON || (options.evalJSON != 'force' &&
      !(this.getHeader('Content-type') || '').include('application/json')) ||
        this.responseText.blank())
          return null;
    try {
      return this.responseText.evalJSON(options.sanitizeJSON ||
        !this.request.isSameOrigin());
    } catch (e) {
      this.request.dispatchException(e);
    }
  }
});

Ajax.Updater = Class.create(Ajax.Request, {
  initialize: function($super, container, url, options) {
    this.container = {
      success: (container.success || container),
      failure: (container.failure || (container.success ? null : container))
    };

    options = Object.clone(options);
    var onComplete = options.onComplete;
    options.onComplete = (function(response, json) {
      this.updateContent(response.responseText);
      if (Object.isFunction(onComplete)) onComplete(response, json);
    }).bind(this);

    $super(url, options);
  },

  updateContent: function(responseText) {
    var receiver = this.container[this.success() ? 'success' : 'failure'],
        options = this.options;

    if (!options.evalScripts) responseText = responseText.stripScripts();

    if (receiver = $(receiver)) {
      if (options.insertion) {
        if (Object.isString(options.insertion)) {
          var insertion = { }; insertion[options.insertion] = responseText;
          receiver.insert(insertion);
        }
        else options.insertion(receiver, responseText);
      }
      else receiver.update(responseText);
    }
  }
});

Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
  initialize: function($super, container, url, options) {
    $super(options);
    this.onComplete = this.options.onComplete;

    this.frequency = (this.options.frequency || 2);
    this.decay = (this.options.decay || 1);

    this.updater = { };
    this.container = container;
    this.url = url;

    this.start();
  },

  start: function() {
    this.options.onComplete = this.updateComplete.bind(this);
    this.onTimerEvent();
  },

  stop: function() {
    this.updater.options.onComplete = undefined;
    clearTimeout(this.timer);
    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
  },

  updateComplete: function(response) {
    if (this.options.decay) {
      this.decay = (response.responseText == this.lastText ?
        this.decay * this.options.decay : 1);

      this.lastText = response.responseText;
    }
    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
  },

  onTimerEvent: function() {
    this.updater = new Ajax.Updater(this.container, this.url, this.options);
  }
});
function $(element) {
  if (arguments.length > 1) {
    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
      elements.push($(arguments[i]));
    return elements;
  }
  if (Object.isString(element)) {
	  var id = element;
      element = document.getElementById(element);
	  if (element) {
	  	if (element.id != id) {
		  	element = null;
	  	}
	  }
  }
  return Element.extend(element);
}

if (Prototype.BrowserFeatures.XPath) {
  document._getElementsByXPath = function(expression, parentElement) {
    var results = [];
    var query = document.evaluate(expression, $(parentElement) || document,
      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    for (var i = 0, length = query.snapshotLength; i < length; i++)
      results.push(Element.extend(query.snapshotItem(i)));
    return results;
  };
}

/*--------------------------------------------------------------------------*/

if (!window.Node) var Node = { };

if (!Node.ELEMENT_NODE) {
  // DOM level 2 ECMAScript Language Binding
  Object.extend(Node, {
    ELEMENT_NODE: 1,
    ATTRIBUTE_NODE: 2,
    TEXT_NODE: 3,
    CDATA_SECTION_NODE: 4,
    ENTITY_REFERENCE_NODE: 5,
    ENTITY_NODE: 6,
    PROCESSING_INSTRUCTION_NODE: 7,
    COMMENT_NODE: 8,
    DOCUMENT_NODE: 9,
    DOCUMENT_TYPE_NODE: 10,
    DOCUMENT_FRAGMENT_NODE: 11,
    NOTATION_NODE: 12
  });
}

(function() {
  var element = this.Element;
  this.Element = function(tagName, attributes) {
    attributes = attributes || { };
    tagName = tagName.toLowerCase();
    var cache = Element.cache;
    if (Prototype.Browser.IE && attributes.name) {
      tagName = '<' + tagName + ' name="' + attributes.name + '">';
      delete attributes.name;
      return Element.writeAttribute(document.createElement(tagName), attributes);
    }
    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
  };
  Object.extend(this.Element, element || { });
  if (element) this.Element.prototype = element.prototype;
}).call(window);

Element.cache = { };

Element.Methods = {
  visible: function(element) {
    return $(element).getStyle('display') != 'none';
  },

  toggle: function(element) {
    element = $(element);
    Element[Element.visible(element) ? 'hide' : 'show'](element);
    return element;
  },

  hide: function(element) {
    element = $(element);
    var originalDisplay = element.getStyle('display');
    if (originalDisplay && originalDisplay != 'none')
      element._originalDisplay = originalDisplay;
    element.style.display = 'none';
    return element;
  },

  show: function(element) {
    element = $(element);
    if (element._originalDisplay) {
      element.style.display = element._originalDisplay;
      element._originalDisplay = null;
 	} else element.style.display = '';
 	if (element.getStyle('display') == 'none')
 		element.style.display = element.getDefaultDisplay();
    return element;
  },

  getDefaultDisplay: function(element) {
    var container = element.ownerDocument.createElement('div');
    var tester = Element.extend(document.createElement(element.tagName) );
    container.appendChild(tester);
    return tester.getStyle('display');
  },

  remove: function(element) {
    element = $(element);
    element.parentNode.removeChild(element);
    return element;
  },

  swapWith: function(element, other) {
    element = $(element);
    other = $(other);
    if (element !== other) {
      var stub = element.ownerDocument.createElement('div');
      other = Element.replace(other, stub);
      element = Element.replace(element, other);
      stub = Element.replace(stub, element);
      stub = null; // prevent possible leaks
    }
    return element;
  },

  update: function(element, content) {
    element = $(element);
    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) return element.update().insert(content);
    content = Object.toHTML(content);
    element.innerHTML = content.stripScripts();
    content.evalScripts.bind(content).defer();
    return element;
  },

  replace: function(element, content) {
    element = $(element);
    if (content && content.toElement) content = content.toElement();
    else if (!Object.isElement(content)) {
      content = Object.toHTML(content);
      var range = element.ownerDocument.createRange();
      range.selectNode(element);
      content.evalScripts.bind(content).defer();
      content = range.createContextualFragment(content.stripScripts());
    }
    element.parentNode.replaceChild(content, element);
    return element;
  },

  insert: function(element, insertions) {
    element = $(element);

    if (Object.isString(insertions) || Object.isNumber(insertions) ||
        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
          insertions = {bottom:insertions};

    var content, insert, tagName, childNodes;

    for (var position in insertions) {
      content  = insertions[position];
      position = position.toLowerCase();
      insert = Element._insertionTranslations[position];

      if (content && content.toElement) content = content.toElement();
      if (Object.isElement(content)) {
        insert(element, content);
        continue;
      }

      content = Object.toHTML(content);

      tagName = ((position == 'before' || position == 'after')
        ? element.parentNode : element).tagName.toUpperCase();

      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());

      if (position == 'top' || position == 'after') childNodes.reverse();
      childNodes.each(insert.curry(element));

      content.evalScripts.bind(content).defer();
    }

    return element;
  },

  wrap: function(element, wrapper, attributes) {
    element = $(element);
    if (Object.isElement(wrapper))
      $(wrapper).writeAttribute(attributes || { });
    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
    else wrapper = new Element('div', wrapper);
    if (element.parentNode)
      element.parentNode.replaceChild(wrapper, element);
    wrapper.appendChild(element);
    return wrapper;
  },

  inspect: function(element) {
    element = $(element);
    var result = '<' + element.tagName.toLowerCase();
    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
      var property = pair.first(), attribute = pair.last();
      var value = (element[property] || '').toString();
      if (value) result += ' ' + attribute + '=' + value.inspect(true);
    });
    return result + '>';
  },

  recursivelyCollect: function(element, property) {
    element = $(element);
    var elements = [];
    while (element = element[property])
      if (element.nodeType == 1)
        elements.push(Element.extend(element));
    return elements;
  },

  ancestors: function(element) {
    return $(element).recursivelyCollect('parentNode');
  },

  descendants: function(element) {
    return $(element).select("*");
  },

  firstDescendant: function(element) {
    element = $(element).firstChild;
    while (element && element.nodeType != 1) element = element.nextSibling;
    return $(element);
  },

  immediateDescendants: function(element) {
    if (!(element = $(element).firstChild)) return [];
    while (element && element.nodeType != 1) element = element.nextSibling;
    if (element) return [element].concat($(element).nextSiblings());
    return [];
  },

  previousSiblings: function(element) {
    return $(element).recursivelyCollect('previousSibling');
  },

  nextSiblings: function(element) {
    return $(element).recursivelyCollect('nextSibling');
  },

  siblings: function(element) {
    element = $(element);
    return element.previousSiblings().reverse().concat(element.nextSiblings());
  },

  match: function(element, selector) {
    if (Object.isString(selector))
      selector = new Selector(selector);
    return selector.match($(element));
  },

  up: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(element.parentNode);
    var ancestors = element.ancestors();
    return Object.isNumber(expression) ? ancestors[expression] :
      Selector.findElement(ancestors, expression, index);
  },

  down: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return element.firstDescendant();
    return Object.isNumber(expression) ? element.descendants()[expression] :
      Element.select(element, expression)[index || 0];
  },

  previous: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
    var previousSiblings = element.previousSiblings();
    return Object.isNumber(expression) ? previousSiblings[expression] :
      Selector.findElement(previousSiblings, expression, index);
  },

  next: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
    var nextSiblings = element.nextSiblings();
    return Object.isNumber(expression) ? nextSiblings[expression] :
      Selector.findElement(nextSiblings, expression, index);
  },

  select: function() {
    var args = $A(arguments), element = $(args.shift());
    return Selector.findChildElements(element, args);
  },

  adjacent: function() {
    var args = $A(arguments), element = $(args.shift());
    return Selector.findChildElements(element.parentNode, args).without(element);
  },

  identify: function(element) {
    element = $(element);
    var id = element.readAttribute('id'), self = arguments.callee;
    if (id) return id;
    do { id = 'anonymous_element_' + self.counter++ } while ($(id));
    element.writeAttribute('id', id);
    return id;
  },

  readAttribute: function(element, name) {
    element = $(element);
    if (Prototype.Browser.IE) {
      var t = Element._attributeTranslations.read;
      if (t.values[name]) return t.values[name](element, name);
      if (t.names[name]) name = t.names[name];
      if (name.include(':')) {
        return (!element.attributes || !element.attributes[name]) ? null :
         element.attributes[name].value;
      }
    }
    return element.getAttribute(name);
  },

  writeAttribute: function(element, name, value) {
    element = $(element);
    var attributes = { }, t = Element._attributeTranslations.write;

    if (typeof name == 'object') attributes = name;
    else attributes[name] = Object.isUndefined(value) ? true : value;

    for (var attr in attributes) {
      name = t.names[attr] || attr;
      value = attributes[attr];
      if (t.values[attr]) name = t.values[attr](element, value);
      if (value === false || value === null)
        element.removeAttribute(name);
      else if (value === true)
        element.setAttribute(name, name);
      else element.setAttribute(name, value);
    }
    return element;
  },

  getHeight: function(element) {
    return $(element).getDimensions().height;
  },

  getWidth: function(element) {
    return $(element).getDimensions().width;
  },

  classNames: function(element) {
    return new Element.ClassNames(element);
  },

  hasClassName: function(element, className) {
    if (!(element = $(element))) return;
    var elementClassName = element.className;
    return (elementClassName.length > 0 && (elementClassName == className ||
      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
  },

  addClassName: function(element, className) {
    if (!(element = $(element))) return;
    if (!element.hasClassName(className))
      element.className += (element.className ? ' ' : '') + className;
    return element;
  },

  removeClassName: function(element, className) {
    if (!(element = $(element))) return;
    element.className = element.className.replace(
      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
    return element;
  },

  toggleClassName: function(element, className) {
    if (!(element = $(element))) return;
    return element[element.hasClassName(className) ?
      'removeClassName' : 'addClassName'](className);
  },

  // removes whitespace-only text node children
  cleanWhitespace: function(element) {
    element = $(element);
    var node = element.firstChild;
    while (node) {
      var nextNode = node.nextSibling;
      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
        element.removeChild(node);
      node = nextNode;
    }
    return element;
  },

  empty: function(element) {
    return $(element).innerHTML.blank();
  },

  descendantOf: function(element, ancestor) {
    element = $(element), ancestor = $(ancestor);

    if (element.compareDocumentPosition)
      return (element.compareDocumentPosition(ancestor) & 8) === 8;

    if (ancestor.contains)
      return ancestor.contains(element) && ancestor !== element;

    while (element = element.parentNode)
      if (element == ancestor) return true;

    return false;
  },

  scrollTo: function(element) {
    element = $(element);
    var pos = element.cumulativeOffset();
    window.scrollTo(pos[0], pos[1]);
    return element;
  },

  getStyle: function(element, style) {
    element = $(element);
    style = style == 'float' ? 'cssFloat' : style.camelize();
    var value = element.style[style];
    if (!value || value == 'auto') {
      var css = document.defaultView.getComputedStyle(element, null);
      value = css ? css[style] : null;
    }
    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
    return value == 'auto' ? null : value;
  },

  getOpacity: function(element) {
    return $(element).getStyle('opacity');
  },

  setStyle: function(element, styles) {
    element = $(element);
    var elementStyle = element.style, match;
    if (Object.isString(styles)) {
      element.style.cssText += ';' + styles;
      return styles.include('opacity') ?
        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
    }
    for (var property in styles)
      if (property == 'opacity') element.setOpacity(styles[property]);
      else
        elementStyle[(property == 'float' || property == 'cssFloat') ?
          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
            property] = styles[property];

    return element;
  },

  setOpacity: function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1 || value === '') ? '' :
      (value < 0.00001) ? 0 : value;
    return element;
  },

  getDimensions: function(element) {
    element = $(element);
    var display = element.getStyle('display');
    if (display != 'none' && display != null) // Safari bug
      return {width: element.offsetWidth, height: element.offsetHeight};

    // All *Width and *Height properties give 0 on elements with display none,
    // so enable the element temporarily
    var els = element.style;
    var originalVisibility = els.visibility;
    var originalPosition = els.position;
    var originalDisplay = els.display;
	var originalLeft = els.left;
	var originalTop = els.top;
    els.visibility = 'hidden';
    els.position = 'absolute';
	els.left = '-10000px';
	els.top = '-10000px';
    els.display = 'block';
    var originalWidth = element.clientWidth;
    var originalHeight = element.clientHeight;
    els.display = originalDisplay;
	els.left = originalLeft;
	els.top = originalTop;
    els.position = originalPosition;
    els.visibility = originalVisibility;
    return {width: originalWidth, height: originalHeight};
  },

  makePositioned: function(element) {
    element = $(element);
    var pos = Element.getStyle(element, 'position');
    if (pos == 'static' || !pos) {
      element._madePositioned = true;
      element.style.position = 'relative';
      // Opera returns the offset relative to the positioning context, when an
      // element is position relative but top and left have not been defined
      if (Prototype.Browser.Opera) {
        element.style.top = 0;
        element.style.left = 0;
      }
    }
    return element;
  },

  undoPositioned: function(element) {
    element = $(element);
    if (element._madePositioned) {
      element._madePositioned = undefined;
      element.style.position =
        element.style.top =
        element.style.left =
        element.style.bottom =
        element.style.right = '';
    }
    return element;
  },

  makeClipping: function(element) {
    element = $(element);
    if (element._overflow) return element;
    element._overflow = Element.getStyle(element, 'overflow') || 'auto';
    if (element._overflow !== 'hidden')
      element.style.overflow = 'hidden';
    return element;
  },

  undoClipping: function(element) {
    element = $(element);
    if (!element._overflow) return element;
    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
    element._overflow = null;
    return element;
  },

  cumulativeOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  positionedOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
      if (element) {
        if (element.tagName.toUpperCase() == 'BODY') break;
        var p = Element.getStyle(element, 'position');
        if (p !== 'static') break;
      }
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  absolutize: function(element) {
    element = $(element);
    if (element.getStyle('position') == 'absolute') return element;
    // Position.prepare(); // To be done manually by Scripty when it needs it.

    var offsets = element.positionedOffset();
    var top     = offsets[1];
    var left    = offsets[0];
    var width   = element.clientWidth;
    var height  = element.clientHeight;

    element._originalLeft   = left - parseFloat(element.style.left  || 0);
    element._originalTop    = top  - parseFloat(element.style.top || 0);
    element._originalWidth  = element.style.width;
    element._originalHeight = element.style.height;

    element.style.position = 'absolute';
    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.width  = width + 'px';
    element.style.height = height + 'px';
    return element;
  },

  relativize: function(element) {
    element = $(element);
    if (element.getStyle('position') == 'relative') return element;

	var size = {width: element.getWidth(), height: element.getHeight()};
	var location = element.cumulativeOffset();

    element.style.position = 'relative';
	var newloc = element.cumulativeOffset();

    element.style.left  = location.left - (newloc.left - (parseFloat(element.style.left) || 0) ) + "px";
    element.style.top   = location.top - (newloc.top - (parseFloat(element.style.top) || 0) ) + "px";
    element.style.width  = size.width + "px";
    element.style.height = size.height + "px";

    return element;
  },

  cumulativeScrollOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.scrollTop  || 0;
      valueL += element.scrollLeft || 0;
      element = element.parentNode;
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  getOffsetParent: function(element) {
    if (element.offsetParent) return $(element.offsetParent);
    if (element == document.body) return $(element);

    while ((element = element.parentNode) && element != document.body)
      if (Element.getStyle(element, 'position') != 'static')
        return $(element);

    return $(document.body);
  },

  viewportOffset: function(forElement) {
    var valueT = 0, valueL = 0;

    var element = forElement;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;

      // Safari fix
      if (element.offsetParent == document.body &&
        Element.getStyle(element, 'position') == 'absolute') break;

    } while (element = element.offsetParent);

    element = forElement;
    do {
      if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) {
        valueT -= element.scrollTop  || 0;
        valueL -= element.scrollLeft || 0;
      }
    } while (element = element.parentNode);

    return Element._returnOffset(valueL, valueT);
  },

  clonePosition: function(element, source) {
    var options = Object.extend({
      setLeft:    true,
      setTop:     true,
      setWidth:   true,
      setHeight:  true,
      offsetTop:  0,
      offsetLeft: 0
    }, arguments[2] || { });

    // find page position of source
    source = $(source);
    var p = source.viewportOffset();

    // find coordinate system to use
    element = $(element);
    var delta = [0, 0];
    var parent = null;
    // delta [0,0] will do fine with position: fixed elements,
    // position:absolute needs offsetParent deltas
    if (Element.getStyle(element, 'position') == 'absolute') {
      parent = element.getOffsetParent();
      delta = parent.viewportOffset();
    }

    // correct by body offsets (fixes Safari)
    if (parent == document.body) {
      delta[0] -= document.body.offsetLeft;
      delta[1] -= document.body.offsetTop;
    }

    // set position
    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
    return element;
  },

  getTextContent: function(element) {
    element = $(element);
    if (element.textContent) {
      return element.textContent;
    }
    else {
      return element.innerHTML.stripTags().replace(/\r\n/g, "");
    }
  }
};

Element.Methods.identify.counter = 1;

Object.extend(Element.Methods, {
  getElementsBySelector: Element.Methods.select,
  childElements: Element.Methods.immediateDescendants
});

Element._attributeTranslations = {
  write: {
    names: {
      className: 'class',
      htmlFor:   'for'
    },
    values: { }
  }
};

if (Prototype.Browser.Opera) {
  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
    function(proceed, element, style) {
      switch (style) {
        case 'left': case 'top': case 'right': case 'bottom':
          if (proceed(element, 'position') === 'static') return null;
        case 'height': case 'width':
          // returns '0px' for hidden elements; we want it to return null
          if (!Element.visible(element)) return null;

          // returns the border-box dimensions rather than the content-box
          // dimensions, so we subtract padding and borders from the value
          var dim = parseInt(proceed(element, style), 10);

          if (dim !== element['offset' + style.capitalize()])
            return dim + 'px';

          var properties;
          if (style === 'height') {
            properties = ['border-top-width', 'padding-top',
             'padding-bottom', 'border-bottom-width'];
          }
          else {
            properties = ['border-left-width', 'padding-left',
             'padding-right', 'border-right-width'];
          }
          return properties.inject(dim, function(memo, property) {
            var val = proceed(element, property);
            return val === null ? memo : memo - parseInt(val, 10);
          }) + 'px';
        default: return proceed(element, style);
      }
    }
  );

  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
    function(proceed, element, attribute) {
      if (attribute === 'title') return element.title;
      return proceed(element, attribute);
    }
  );
}

else if (Prototype.Browser.IE) {
  // IE doesn't report offsets correctly for static elements, so we change them
  // to "relative" to get the values, then change them back.
  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
    function(proceed, element) {
      element = $(element);
      // IE throws an error if element is not in document
      try { element.offsetParent }
      catch(e) { return $(document.body) }
      var position = element.getStyle('position');
      if (position !== 'static') return proceed(element);
      element.setStyle({ position: 'relative' });
      var value = proceed(element);
      element.setStyle({ position: position });
      return value;
    }
  );

  $w('positionedOffset viewportOffset').each(function(method) {
    Element.Methods[method] = Element.Methods[method].wrap(
      function(proceed, element) {
        element = $(element);
        try { element.offsetParent }
        catch(e) { return Element._returnOffset(0,0) }
        var position = element.getStyle('position');
        if (position !== 'static') return proceed(element);
        // Trigger hasLayout on the offset parent so that IE6 reports
        // accurate offsetTop and offsetLeft values for position: fixed.
        var offsetParent = element.getOffsetParent();
        if (offsetParent && offsetParent.getStyle('position') === 'fixed')
          offsetParent.setStyle({ zoom: 1 });
        element.setStyle({ position: 'relative' });
        var value = proceed(element);
        element.setStyle({ position: position });
        return value;
      }
    );
  });

  Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(
    function(proceed, element) {
      try { element.offsetParent }
      catch(e) { return Element._returnOffset(0,0) }
      return proceed(element);
    }
  );

  Element.Methods.getStyle = function(element, style) {
    element = $(element);
    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
    var value = element.style[style];
    if (!value && element.currentStyle) value = element.currentStyle[style];

    if (style == 'opacity') {
      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
        if (value[1]) return parseFloat(value[1]) / 100;
      return 1.0;
    }

    if (value == 'auto') {
      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
        return element['offset' + style.capitalize()] + 'px';
      return null;
    }
    return value;
  };

  Element.Methods.setOpacity = function(element, value) {
    function stripAlpha(filter){
      return filter.replace(/alpha\([^\)]*\)/gi,'');
    }
    element = $(element);
    var currentStyle = element.currentStyle;
    if ((currentStyle && !currentStyle.hasLayout) ||
      (!currentStyle && element.style.zoom == 'normal'))
        element.style.zoom = 1;

    var filter = element.getStyle('filter'), style = element.style;
    if (value == 1 || value === '') {
      (filter = stripAlpha(filter)) ?
        style.filter = filter : style.removeAttribute('filter');
      return element;
    } else if (value < 0.00001) value = 0;
    style.filter = stripAlpha(filter) +
      'alpha(opacity=' + (value * 100) + ')';
    return element;
  };

  Element._attributeTranslations = {
    read: {
      names: {
        'class': 'className',
        'for':   'htmlFor'
      },
      values: {
        _getAttr: function(element, attribute) {
          return element.getAttribute(attribute, 2);
        },
        _getAttrNode: function(element, attribute) {
          var node = element.getAttributeNode(attribute);
          return node ? node.value : "";
        },
        _getEv: function(element, attribute) {
          attribute = element.getAttribute(attribute);
          return attribute ? attribute.toString().slice(23, -2) : null;
        },
        _flag: function(element, attribute) {
          return $(element).hasAttribute(attribute) ? attribute : null;
        },
        style: function(element) {
          return element.style.cssText.toLowerCase();
        },
        title: function(element) {
          return element.title;
        }
      }
    }
  };

  Element._attributeTranslations.write = {
    names: Object.extend({
      cellpadding: 'cellPadding',
      cellspacing: 'cellSpacing'
    }, Element._attributeTranslations.read.names),
    values: {
      checked: function(element, value) {
        element.checked = !!value;
      },

      style: function(element, value) {
        element.style.cssText = value ? value : '';
      }
    }
  };

  Element._attributeTranslations.has = {};

  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
      'encType maxLength readOnly longDesc frameBorder').each(function(attr) {
    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
  });

  (function(v) {
    Object.extend(v, {
      href:        v._getAttr,
      src:         v._getAttr,
      type:        v._getAttr,
      action:      v._getAttrNode,
      disabled:    v._flag,
      checked:     v._flag,
      readonly:    v._flag,
      multiple:    v._flag,
      onload:      v._getEv,
      onunload:    v._getEv,
      onclick:     v._getEv,
      ondblclick:  v._getEv,
      onmousedown: v._getEv,
      onmouseup:   v._getEv,
      onmouseover: v._getEv,
      onmousemove: v._getEv,
      onmouseout:  v._getEv,
      onfocus:     v._getEv,
      onblur:      v._getEv,
      onkeypress:  v._getEv,
      onkeydown:   v._getEv,
      onkeyup:     v._getEv,
      onsubmit:    v._getEv,
      onreset:     v._getEv,
      onselect:    v._getEv,
      onchange:    v._getEv
    });
  })(Element._attributeTranslations.read.values);
}

else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
  Element.Methods.setOpacity = function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1) ? 0.999999 :
      (value === '') ? '' : (value < 0.00001) ? 0 : value;
    return element;
  };
}

else if (Prototype.Browser.WebKit) {
  Element.Methods.setOpacity = function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1 || value === '') ? '' :
      (value < 0.00001) ? 0 : value;

    if (value == 1)
      if(element.tagName.toUpperCase() == 'IMG' && element.width) {
        element.width++; element.width--;
      } else try {
        var n = document.createTextNode(' ');
        element.appendChild(n);
        element.removeChild(n);
      } catch (e) { }

    return element;
  };

  // Safari returns margins on body which is incorrect if the child is absolutely
  // positioned.  For performance reasons, redefine Element#cumulativeOffset for
  // KHTML/WebKit only.
  Element.Methods.cumulativeOffset = function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      if (element.offsetParent == document.body)
        if (Element.getStyle(element, 'position') == 'absolute') break;

      element = element.offsetParent;
    } while (element);

    return Element._returnOffset(valueL, valueT);
  };
}

if (Prototype.Browser.IE || Prototype.Browser.Opera) {
  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
  Element.Methods.update = function(element, content) {
    element = $(element);

    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) return element.update().insert(content);

    content = Object.toHTML(content);
    var tagName = element.tagName.toUpperCase();

    if (tagName in Element._insertionTranslations.tags) {
      $A(element.childNodes).each(function(node) { element.removeChild(node) });
      Element._getContentFromAnonymousElement(tagName, content.stripScripts())
        .each(function(node) { element.appendChild(node) });
    }
    else element.innerHTML = content.stripScripts();

    content.evalScripts.bind(content).defer();
    return element;
  };
}

if ('outerHTML' in document.createElement('div')) {
  Element.Methods.replace = function(element, content) {
    element = $(element);

    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) {
      element.parentNode.replaceChild(content, element);
      return element;
    }

    content = Object.toHTML(content);
    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();

    if (Element._insertionTranslations.tags[tagName]) {
      var nextSibling = element.next();
      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
      parent.removeChild(element);
      if (nextSibling)
        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
      else
        fragments.each(function(node) { parent.appendChild(node) });
    }
    else element.outerHTML = content.stripScripts();

    content.evalScripts.bind(content).defer();
    return element;
  };
}

Element._returnOffset = function(l, t) {
  var result = [l, t];
  result.left = l;
  result.top = t;
  return result;
};

Element._getContentFromAnonymousElement = function(tagName, html) {
  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
  if (t) {
    div.innerHTML = t[0] + html + t[1];
    t[2].times(function() { div = div.firstChild });
  } else div.innerHTML = html;
  return $A(div.childNodes);
};

Element._insertionTranslations = {
  before: function(element, node) {
    element.parentNode.insertBefore(node, element);
  },
  top: function(element, node) {
    element.insertBefore(node, element.firstChild);
  },
  bottom: function(element, node) {
    element.appendChild(node);
  },
  after: function(element, node) {
    element.parentNode.insertBefore(node, element.nextSibling);
  },
  tags: {
    TABLE:  ['<table>',                '</table>',                   1],
    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
    SELECT: ['<select>',               '</select>',                  1]
  }
};

(function() {
  Object.extend(this.tags, {
    THEAD: this.tags.TBODY,
    TFOOT: this.tags.TBODY,
    TH:    this.tags.TD
  });
}).call(Element._insertionTranslations);

Element.Methods.Simulated = {
  hasAttribute: function(element, attribute) {
    attribute = Element._attributeTranslations.has[attribute] || attribute;
    var node = $(element).getAttributeNode(attribute);
    return !!(node && node.specified);
  }
};

Element.Methods.ByTag = { };

Object.extend(Element, Element.Methods);

if (!Prototype.BrowserFeatures.ElementExtensions &&
    document.createElement('div')['__proto__']) {
  window.HTMLElement = { };
  window.HTMLElement.prototype = document.createElement('div')['__proto__'];
  Prototype.BrowserFeatures.ElementExtensions = true;
}

Element.extend = (function() {
  if (Prototype.BrowserFeatures.SpecificElementExtensions)
    return Prototype.K;

  var Methods = { }, ByTag = Element.Methods.ByTag;

  var extend = Object.extend(function(element) {
    if (!element || element._extendedByPrototype ||
        element.nodeType != 1 || element == window) return element;

    var methods = Object.clone(Methods),
      tagName = element.tagName.toUpperCase(), property, value;

    // extend methods for specific tags
    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);

    for (property in methods) {
      value = methods[property];
      if (Object.isFunction(value) && !(property in element))
        element[property] = value.methodize();
    }

    element._extendedByPrototype = Prototype.emptyFunction;
    return element;

  }, {
    refresh: function() {
      // extend methods for all tags (Safari doesn't need this)
      if (!Prototype.BrowserFeatures.ElementExtensions) {
        Object.extend(Methods, Element.Methods);
        Object.extend(Methods, Element.Methods.Simulated);
      }
    }
  });

  extend.refresh();
  return extend;
})();

Element.hasAttribute = function(element, attribute) {
  if (element.hasAttribute) return element.hasAttribute(attribute);
  return Element.Methods.Simulated.hasAttribute(element, attribute);
};

Element.addMethods = function(methods) {
  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;

  if (!methods) {
    Object.extend(Form, Form.Methods);
    Object.extend(Form.Element, Form.Element.Methods);
    Object.extend(Element.Methods.ByTag, {
      "FORM":     Object.clone(Form.Methods),
      "INPUT":    Object.clone(Form.Element.Methods),
      "SELECT":   Object.clone(Form.Element.Methods),
      "TEXTAREA": Object.clone(Form.Element.Methods)
    });
  }

  if (arguments.length == 2) {
    var tagName = methods;
    methods = arguments[1];
  }

  if (!tagName) Object.extend(Element.Methods, methods || { });
  else {
    if (Object.isArray(tagName)) tagName.each(extend);
    else extend(tagName);
  }

  function extend(tagName) {
    tagName = tagName.toUpperCase();
    if (!Element.Methods.ByTag[tagName])
      Element.Methods.ByTag[tagName] = { };
    Object.extend(Element.Methods.ByTag[tagName], methods);
  }

  function copy(methods, destination, onlyIfAbsent) {
    onlyIfAbsent = onlyIfAbsent || false;
    for (var property in methods) {
      var value = methods[property];
      if (!Object.isFunction(value)) continue;
      if (!onlyIfAbsent || !(property in destination))
        destination[property] = value.methodize();
    }
  }

  function findDOMClass(tagName) {
    var klass;
    var trans = {
      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
      "FrameSet", "IFRAME": "IFrame"
    };
    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
    if (window[klass]) return window[klass];
    klass = 'HTML' + tagName + 'Element';
    if (window[klass]) return window[klass];
    klass = 'HTML' + tagName.capitalize() + 'Element';
    if (window[klass]) return window[klass];

    window[klass] = { };
    window[klass].prototype = document.createElement(tagName)['__proto__'];
    return window[klass];
  }

  if (F.ElementExtensions) {
    copy(Element.Methods, HTMLElement.prototype);
    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
  }

  if (F.SpecificElementExtensions) {
    for (var tag in Element.Methods.ByTag) {
      var klass = findDOMClass(tag);
      if (Object.isUndefined(klass)) continue;
      copy(T[tag], klass.prototype);
    }
  }

  Object.extend(Element, Element.Methods);
  delete Element.ByTag;

  if (Element.extend.refresh) Element.extend.refresh();
  Element.cache = { };
};

document.viewport = {
  getDimensions: function() {
    var dimensions = { }, B = Prototype.Browser;
    $w('width height').each(function(d) {
      var D = d.capitalize();
      if (B.WebKit && !document.evaluate) {
        // Safari <3.0 needs self.innerWidth/Height
        dimensions[d] = self['inner' + D];
      } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) {
        // Opera <9.5 needs document.body.clientWidth/Height
        dimensions[d] = document.body['client' + D]
      } else {
        dimensions[d] = document.documentElement['client' + D];
      }
    });
    return dimensions;
  },

  getWidth: function() {
    return this.getDimensions().width;
  },

  getHeight: function() {
    return this.getDimensions().height;
  },

  getScrollOffsets: function() {
    return Element._returnOffset(
      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
  }
};
/* Portions of the Selector class are derived from Jack Slocum's DomQuery,
 * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
 * license.  Please see http://www.yui-ext.com/ for more information. */

var Selector = Class.create({
  initialize: function(expression) {
    this.expression = expression.strip();

    if (this.shouldUseSelectorsAPI()) {
      this.mode = 'selectorsAPI';
    } else if (this.shouldUseXPath()) {
      this.mode = 'xpath';
      this.compileXPathMatcher();
    } else {
      this.mode = "normal";
      this.compileMatcher();
    }

  },

  shouldUseXPath: function() {
    if (!Prototype.BrowserFeatures.XPath) return false;

    var e = this.expression;

    // Safari 3 chokes on :*-of-type and :empty
    if (Prototype.Browser.WebKit &&
     (e.include("-of-type") || e.include(":empty")))
      return false;

    // XPath can't do namespaced attributes, nor can it read
    // the "checked" property from DOM nodes
    if ((/(\[[\w-]*?:|:checked)/).test(e))
      return false;

    return true;
  },

  shouldUseSelectorsAPI: function() {
    if (!Prototype.BrowserFeatures.SelectorsAPI) return false;

    if (!Selector._div) Selector._div = new Element('div');

    // Make sure the browser treats the selector as valid. Test on an
    // isolated element to minimize cost of this check.
    try {
      Selector._div.querySelector(this.expression);
    } catch(e) {
      return false;
    }

    return true;
  },

  compileMatcher: function() {
    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
        c = Selector.criteria, le, p, m;

    if (Selector._cache[e]) {
      this.matcher = Selector._cache[e];
      return;
    }

    this.matcher = ["this.matcher = function(root) {",
                    "var r = root, h = Selector.handlers, c = false, n;"];

    while (e && le != e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        p = ps[i];
        if (m = e.match(p)) {
          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
            new Template(c[i]).evaluate(m));
          e = e.replace(m[0], '');
          break;
        }
      }
    }

    this.matcher.push("return h.unique(n);\n}");
    eval(this.matcher.join('\n'));
    Selector._cache[this.expression] = this.matcher;
  },

  compileXPathMatcher: function() {
    var e = this.expression, ps = Selector.patterns,
        x = Selector.xpath, le, m;

    if (Selector._cache[e]) {
      this.xpath = Selector._cache[e]; return;
    }

    this.matcher = ['.//*'];
    while (e && le != e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        if (m = e.match(ps[i])) {
          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
            new Template(x[i]).evaluate(m));
          e = e.replace(m[0], '');
          break;
        }
      }
    }

    this.xpath = this.matcher.join('');
    Selector._cache[this.expression] = this.xpath;
  },

  findElements: function(root) {
    root = root || document;
    var e = this.expression, results;

    switch (this.mode) {
      case 'selectorsAPI':
        // querySelectorAll queries document-wide, then filters to descendants
        // of the context element. That's not what we want.
        // Add an explicit context to the selector if necessary.
        if (root !== document) {
          var oldId = root.id, id = $(root).identify();
          e = "#" + id + " " + e;
        }

        results = $A(root.querySelectorAll(e)).map(Element.extend);
        root.id = oldId;

        return results;
      case 'xpath':
        return document._getElementsByXPath(this.xpath, root);
      default:
       return this.matcher(root);
    }
  },

  match: function(element) {
    this.tokens = [];

    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
    var le, p, m;

    while (e && le !== e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        p = ps[i];
        if (m = e.match(p)) {
          // use the Selector.assertions methods unless the selector
          // is too complex.
          if (as[i]) {
            this.tokens.push([i, Object.clone(m)]);
            e = e.replace(m[0], '');
          } else {
            // reluctantly do a document-wide search
            // and look for a match in the array
            return this.findElements(document).include(element);
          }
        }
      }
    }

    var match = true, name, matches;
    for (var i = 0, token; token = this.tokens[i]; i++) {
      name = token[0], matches = token[1];
      if (!Selector.assertions[name](element, matches)) {
        match = false; break;
      }
    }

    return match;
  },

  toString: function() {
    return this.expression;
  },

  inspect: function() {
    return "#<Selector:" + this.expression.inspect() + ">";
  }
});

Object.extend(Selector, {
  _cache: { },

  xpath: {
    descendant:   "//*",
    child:        "/*",
    adjacent:     "/following-sibling::*[1]",
    laterSibling: '/following-sibling::*',
    tagName:      function(m) {
      if (m[1] == '*') return '';
      return "[local-name()='" + m[1].toLowerCase() +
             "' or local-name()='" + m[1].toUpperCase() + "']";
    },
    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
    id:           "[@id='#{1}']",
    attrPresence: function(m) {
      m[1] = m[1].toLowerCase();
      return new Template("[@#{1}]").evaluate(m);
    },
    attr: function(m) {
      m[1] = m[1].toLowerCase();
      m[3] = m[5] || m[6];
      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
    },
    pseudo: function(m) {
      var h = Selector.xpath.pseudos[m[1]];
      if (!h) return '';
      if (Object.isFunction(h)) return h(m);
      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
    },
    operators: {
      '=':  "[@#{1}='#{3}']",
      '!=': "[@#{1}!='#{3}']",
      '^=': "[starts-with(@#{1}, '#{3}')]",
      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
      '*=': "[contains(@#{1}, '#{3}')]",
      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
    },
    pseudos: {
      'first-child': '[not(preceding-sibling::*)]',
      'last-child':  '[not(following-sibling::*)]',
      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
      'empty':       "[count(*) = 0 and (count(text()) = 0)]",
      'checked':     "[@checked]",
      'disabled':    "[(@disabled) and (@type!='hidden')]",
      'enabled':     "[not(@disabled) and (@type!='hidden')]",
      'not': function(m) {
        var e = m[6], p = Selector.patterns,
            x = Selector.xpath, le, v;

        var exclusion = [];
        while (e && le != e && (/\S/).test(e)) {
          le = e;
          for (var i in p) {
            if (m = e.match(p[i])) {
              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
              e = e.replace(m[0], '');
              break;
            }
          }
        }
        return "[not(" + exclusion.join(" and ") + ")]";
      },
      'nth-child':      function(m) {
        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
      },
      'nth-last-child': function(m) {
        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
      },
      'nth-of-type':    function(m) {
        return Selector.xpath.pseudos.nth("position() ", m);
      },
      'nth-last-of-type': function(m) {
        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
      },
      'first-of-type':  function(m) {
        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
      },
      'last-of-type':   function(m) {
        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
      },
      'only-of-type':   function(m) {
        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
      },
      nth: function(fragment, m) {
        var mm, formula = m[6], predicate;
        if (formula == 'even') formula = '2n+0';
        if (formula == 'odd')  formula = '2n+1';
        if (mm = formula.match(/^(\d+)$/)) // digit only
          return '[' + fragment + "= " + mm[1] + ']';
        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
          if (mm[1] == "-") mm[1] = -1;
          var a = mm[1] ? Number(mm[1]) : 1;
          var b = mm[2] ? Number(mm[2]) : 0;
          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
          "((#{fragment} - #{b}) div #{a} >= 0)]";
          return new Template(predicate).evaluate({
            fragment: fragment, a: a, b: b });
        }
      }
    }
  },

  criteria: {
    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',
    className:    'n = h.className(n, r, "#{1}", c);    c = false;',
    id:           'n = h.id(n, r, "#{1}", c);           c = false;',
    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
    attr: function(m) {
      m[3] = (m[5] || m[6]);
      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
    },
    pseudo: function(m) {
      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
    },
    descendant:   'c = "descendant";',
    child:        'c = "child";',
    adjacent:     'c = "adjacent";',
    laterSibling: 'c = "laterSibling";'
  },

  patterns: {
    // combinators must be listed first
    // (and descendant needs to be last combinator)
    laterSibling: /^\s*~\s*/,
    child:        /^\s*>\s*/,
    adjacent:     /^\s*\+\s*/,
    descendant:   /^\s/,

    // selectors follow
    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
    id:           /^#([\w\-\*]+)(\b|$)/,
    className:    /^\.([\w\-\*]+)(\b|$)/,
    pseudo:
/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
    attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/,
    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
  },

  // for Selector.match and Element#match
  assertions: {
    tagName: function(element, matches) {
      return matches[1].toUpperCase() == element.tagName.toUpperCase();
    },

    className: function(element, matches) {
      return Element.hasClassName(element, matches[1]);
    },

    id: function(element, matches) {
      return element.id === matches[1];
    },

    attrPresence: function(element, matches) {
      return Element.hasAttribute(element, matches[1]);
    },

    attr: function(element, matches) {
      var nodeValue = Element.readAttribute(element, matches[1]);
      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
    }
  },

  handlers: {
    // UTILITY FUNCTIONS
    // joins two collections
    concat: function(a, b) {
      for (var i = 0, node; node = b[i]; i++)
        a.push(node);
      return a;
    },

    // marks an array of nodes for counting
    mark: function(nodes) {
      var _true = Prototype.emptyFunction;
      for (var i = 0, node; node = nodes[i]; i++)
        node._countedByPrototype = _true;
      return nodes;
    },

    unmark: function(nodes) {
      for (var i = 0, node; node = nodes[i]; i++)
        node._countedByPrototype = undefined;
      return nodes;
    },

    // mark each child node with its position (for nth calls)
    // "ofType" flag indicates whether we're indexing for nth-of-type
    // rather than nth-child
    index: function(parentNode, reverse, ofType) {
      parentNode._countedByPrototype = Prototype.emptyFunction;
      if (reverse) {
        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
          var node = nodes[i];
          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
        }
      } else {
        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
      }
    },

    // filters out duplicates and extends all nodes
    unique: function(nodes) {
      if (nodes.length == 0) return nodes;
      var results = [], n;
      for (var i = 0, l = nodes.length; i < l; i++)
        if (!(n = nodes[i])._countedByPrototype) {
          n._countedByPrototype = Prototype.emptyFunction;
          results.push(Element.extend(n));
        }
      return Selector.handlers.unmark(results);
    },

    // COMBINATOR FUNCTIONS
    descendant: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        h.concat(results, node.getElementsByTagName('*'));
      return results;
    },

    child: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        for (var j = 0, child; child = node.childNodes[j]; j++)
          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
      }
      return results;
    },

    adjacent: function(nodes) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        var next = this.nextElementSibling(node);
        if (next) results.push(next);
      }
      return results;
    },

    laterSibling: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        h.concat(results, Element.nextSiblings(node));
      return results;
    },

    nextElementSibling: function(node) {
      while (node = node.nextSibling)
        if (node.nodeType == 1) return node;
      return null;
    },

    previousElementSibling: function(node) {
      while (node = node.previousSibling)
        if (node.nodeType == 1) return node;
      return null;
    },

    // TOKEN FUNCTIONS
    tagName: function(nodes, root, tagName, combinator) {
      var uTagName = tagName.toUpperCase();
      var results = [], h = Selector.handlers;
      if (nodes) {
        if (combinator) {
          // fastlane for ordinary descendant combinators
          if (combinator == "descendant") {
            for (var i = 0, node; node = nodes[i]; i++)
              h.concat(results, node.getElementsByTagName(tagName));
            return results;
          } else nodes = this[combinator](nodes);
          if (tagName == "*") return nodes;
        }
        for (var i = 0, node; node = nodes[i]; i++)
          if (node.tagName.toUpperCase() === uTagName) results.push(node);
        return results;
      } else return root.getElementsByTagName(tagName);
    },

    id: function(nodes, root, id, combinator) {
      var targetNode = $(id), h = Selector.handlers;
      if (!targetNode) return [];
      if (!nodes && root == document) return [targetNode];
      if (nodes) {
        if (combinator) {
          if (combinator == 'child') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (targetNode.parentNode == node) return [targetNode];
          } else if (combinator == 'descendant') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (Element.descendantOf(targetNode, node)) return [targetNode];
          } else if (combinator == 'adjacent') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (Selector.handlers.previousElementSibling(targetNode) == node)
                return [targetNode];
          } else nodes = h[combinator](nodes);
        }
        for (var i = 0, node; node = nodes[i]; i++)
          if (node == targetNode) return [targetNode];
        return [];
      }
      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
    },

    className: function(nodes, root, className, combinator) {
      if (nodes && combinator) nodes = this[combinator](nodes);
      return Selector.handlers.byClassName(nodes, root, className);
    },

    byClassName: function(nodes, root, className) {
      if (!nodes) nodes = Selector.handlers.descendant([root]);
      var needle = ' ' + className + ' ';
      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
        nodeClassName = node.className;
        if (nodeClassName.length == 0) continue;
        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
          results.push(node);
      }
      return results;
    },

    attrPresence: function(nodes, root, attr, combinator) {
      if (!nodes) nodes = root.getElementsByTagName("*");
      if (nodes && combinator) nodes = this[combinator](nodes);
      var results = [];
      for (var i = 0, node; node = nodes[i]; i++)
        if (Element.hasAttribute(node, attr)) results.push(node);
      return results;
    },

    attr: function(nodes, root, attr, value, operator, combinator) {
      if (!nodes) nodes = root.getElementsByTagName("*");
      if (nodes && combinator) nodes = this[combinator](nodes);
      var handler = Selector.operators[operator], results = [];
      for (var i = 0, node; node = nodes[i]; i++) {
        var nodeValue = Element.readAttribute(node, attr);
        if (nodeValue === null) continue;
        if (handler(nodeValue, value)) results.push(node);
      }
      return results;
    },

    pseudo: function(nodes, name, value, root, combinator) {
      if (nodes && combinator) nodes = this[combinator](nodes);
      if (!nodes) nodes = root.getElementsByTagName("*");
      return Selector.pseudos[name](nodes, value, root);
    }
  },

  pseudos: {
    'first-child': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        if (Selector.handlers.previousElementSibling(node)) continue;
          results.push(node);
      }
      return results;
    },
    'last-child': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        if (Selector.handlers.nextElementSibling(node)) continue;
          results.push(node);
      }
      return results;
    },
    'only-child': function(nodes, value, root) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
          results.push(node);
      return results;
    },
    'nth-child':        function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root);
    },
    'nth-last-child':   function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, true);
    },
    'nth-of-type':      function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, false, true);
    },
    'nth-last-of-type': function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, true, true);
    },
    'first-of-type':    function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, "1", root, false, true);
    },
    'last-of-type':     function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, "1", root, true, true);
    },
    'only-of-type':     function(nodes, formula, root) {
      var p = Selector.pseudos;
      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
    },

    // handles the an+b logic
    getIndices: function(a, b, total) {
      if (a == 0) return b > 0 ? [b] : [];
      return $R(1, total).inject([], function(memo, i) {
        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
        return memo;
      });
    },

    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
    nth: function(nodes, formula, root, reverse, ofType) {
      if (nodes.length == 0) return [];
      if (formula == 'even') formula = '2n+0';
      if (formula == 'odd')  formula = '2n+1';
      var h = Selector.handlers, results = [], indexed = [], m;
      h.mark(nodes);
      for (var i = 0, node; node = nodes[i]; i++) {
        if (!node.parentNode._countedByPrototype) {
          h.index(node.parentNode, reverse, ofType);
          indexed.push(node.parentNode);
        }
      }
      if (formula.match(/^\d+$/)) { // just a number
        formula = Number(formula);
        for (var i = 0, node; node = nodes[i]; i++)
          if (node.nodeIndex == formula) results.push(node);
      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
        if (m[1] == "-") m[1] = -1;
        var a = m[1] ? Number(m[1]) : 1;
        var b = m[2] ? Number(m[2]) : 0;
        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
          for (var j = 0; j < l; j++)
            if (node.nodeIndex == indices[j]) results.push(node);
        }
      }
      h.unmark(nodes);
      h.unmark(indexed);
      return results;
    },

    'empty': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        // IE treats comments as element nodes
        if (node.tagName == '!' || node.firstChild) continue;
        results.push(node);
      }
      return results;
    },

    'not': function(nodes, selector, root) {
      var h = Selector.handlers, selectorType, m;
      var exclusions = new Selector(selector).findElements(root);
      h.mark(exclusions);
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!node._countedByPrototype) results.push(node);
      h.unmark(exclusions);
      return results;
    },

    'enabled': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!node.disabled && (!node.type || node.type !== 'hidden'))
          results.push(node);
      return results;
    },

    'disabled': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (node.disabled) results.push(node);
      return results;
    },

    'checked': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (node.checked) results.push(node);
      return results;
    }
  },

  operators: {
    '=':  function(nv, v) { return nv == v; },
    '!=': function(nv, v) { return nv != v; },
    '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },
    '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },
    '*=': function(nv, v) { return nv == v || nv && nv.include(v); },
    '$=': function(nv, v) { return nv.endsWith(v); },
    '*=': function(nv, v) { return nv.include(v); },
    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
    '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() +
     '-').include('-' + (v || "").toUpperCase() + '-'); }
  },

  split: function(expression) {
    var expressions = [];
    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
      expressions.push(m[1].strip());
    });
    return expressions;
  },

  matchElements: function(elements, expression) {
    var matches = $$(expression), h = Selector.handlers;
    h.mark(matches);
    for (var i = 0, results = [], element; element = elements[i]; i++)
      if (element._countedByPrototype) results.push(element);
    h.unmark(matches);
    return results;
  },

  findElement: function(elements, expression, index) {
    if (Object.isNumber(expression)) {
      index = expression; expression = false;
    }
    return Selector.matchElements(elements, expression || '*')[index || 0];
  },

  findChildElements: function(element, expressions) {
    expressions = Selector.split(expressions.join(','));
    var results = [], h = Selector.handlers;
    for (var i = 0, l = expressions.length, selector; i < l; i++) {
      selector = new Selector(expressions[i].strip());
      h.concat(results, selector.findElements(element));
    }
    return (l > 1) ? h.unique(results) : results;
  }
});

if (Prototype.Browser.IE) {
  Object.extend(Selector.handlers, {
    // IE returns comment nodes on getElementsByTagName("*").
    // Filter them out.
    concat: function(a, b) {
      for (var i = 0, node; node = b[i]; i++)
        if (node.tagName !== "!") a.push(node);
      return a;
    },

    // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
    unmark: function(nodes) {
      for (var i = 0, node; node = nodes[i]; i++)
        node.removeAttribute('_countedByPrototype');
      return nodes;
    }
  });
}

function $$() {
  return Selector.findChildElements(document, $A(arguments));
}
var Form = {
  reset: function(form) {
    $(form).reset();
    return form;
  },

  serializeElements: function(elements, options) {
    if (typeof options != 'object') options = { hash: !!options };
    else if (Object.isUndefined(options.hash)) options.hash = true;
    var key, value, submitted = false, submit = options.submit;

    var data = elements.inject({ }, function(result, element) {
      if (!element.disabled && element.name) {
        key = element.name; value = $(element).getValue();
        if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&
            submit !== false && (!submit || key == submit) && (submitted = true)))) {
          if (key in result) {
            // a key is already present; construct an array of values
            if (!Object.isArray(result[key])) result[key] = [result[key]];
            result[key].push(value);
          }
          else result[key] = value;
        }
      }
      return result;
    });

    return options.hash ? data : Object.toQueryString(data);
  }
};

Form.Methods = {
  serialize: function(form, options) {
    return Form.serializeElements(Form.getElements(form), options);
  },

  getElements: function(form) {
    return $A($(form).getElementsByTagName('*')).inject([],
      function(elements, child) {
        if (Form.Element.Serializers[child.tagName.toLowerCase()])
          elements.push(Element.extend(child));
        return elements;
      }
    );
  },

  getInputs: function(form, typeName, name) {
    form = $(form);
    var inputs = form.getElementsByTagName('input');

    if (!typeName && !name) return $A(inputs).map(Element.extend);

    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
      var input = inputs[i];
      if ((typeName && input.type != typeName) || (name && input.name != name))
        continue;
      matchingInputs.push(Element.extend(input));
    }

    return matchingInputs;
  },

  disable: function(form) {
    form = $(form);
    Form.getElements(form).invoke('disable');
    return form;
  },

  enable: function(form) {
    form = $(form);
    Form.getElements(form).invoke('enable');
    return form;
  },

  findFirstElement: function(form) {
    var elements = $(form).getElements().findAll(function(element) {
      return 'hidden' != element.type && !element.disabled;
    });
    var firstByIndex = elements.findAll(function(element) {
      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
    }).sortBy(function(element) { return element.tabIndex }).first();

    return firstByIndex ? firstByIndex : elements.find(function(element) {
      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
    });
  },

  focusFirstElement: function(form) {
    form = $(form);
    form.findFirstElement().activate();
    return form;
  },

  request: function(form, options) {
    form = $(form), options = Object.clone(options || { });

    var params = options.parameters, action = form.readAttribute('action') || '';
    if (action.blank()) action = window.location.href;
    options.parameters = form.serialize(true);

    if (params) {
      if (Object.isString(params)) params = params.toQueryParams();
      Object.extend(options.parameters, params);
    }

    if (form.hasAttribute('method') && !options.method)
      options.method = form.method;

    return new Ajax.Request(action, options);
  }
};

/*--------------------------------------------------------------------------*/

Form.Element = {
  focus: function(element) {
    $(element).focus();
    return element;
  },

  select: function(element) {
    $(element).select();
    return element;
  }
};

Form.Element.Methods = {
  serialize: function(element) {
    element = $(element);
    if (!element.disabled && element.name) {
      var value = element.getValue();
      if (value != undefined) {
        var pair = { };
        pair[element.name] = value;
        return Object.toQueryString(pair);
      }
    }
    return '';
  },

  getValue: function(element) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    return Form.Element.Serializers[method](element);
  },

  setValue: function(element, value) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    Form.Element.Serializers[method](element, value);
    return element;
  },

  clear: function(element) {
    $(element).value = '';
    return element;
  },

  present: function(element) {
    return $(element).value != '';
  },

  activate: function(element) {
    element = $(element);
    try {
      element.focus();
      if (element.select && (element.tagName.toLowerCase() != 'input' ||
          !['button', 'reset', 'submit'].include(element.type)))
        element.select();
    } catch (e) { }
    return element;
  },

  disable: function(element) {
    element = $(element);
    element.disabled = true;
    return element;
  },

  enable: function(element) {
    element = $(element);
    element.disabled = false;
    return element;
  }
};

/*--------------------------------------------------------------------------*/

var Field = Form.Element;
var $F = Form.Element.Methods.getValue;

/*--------------------------------------------------------------------------*/

Form.Element.Serializers = {
  input: function(element, value) {
    switch (element.type.toLowerCase()) {
      case 'checkbox':
      case 'radio':
        return Form.Element.Serializers.inputSelector(element, value);
      default:
        return Form.Element.Serializers.textarea(element, value);
    }
  },

  inputSelector: function(element, value) {
    if (Object.isUndefined(value)) return element.checked ? element.value : null;
    else element.checked = !!value;
  },

  textarea: function(element, value) {
    if (Object.isUndefined(value)) return element.value;
    else element.value = value;
  },

  select: function(element, value) {
    if (Object.isUndefined(value))
      return this[element.type == 'select-one' ?
        'selectOne' : 'selectMany'](element);
    else {
      var opt, currentValue, single = !Object.isArray(value);
      for (var i = 0, length = element.length; i < length; i++) {
        opt = element.options[i];
        currentValue = this.optionValue(opt);
        if (single) {
          if (currentValue == value) {
            opt.selected = true;
            return;
          }
        }
        else opt.selected = value.include(currentValue);
      }
    }
  },

  selectOne: function(element) {
    var index = element.selectedIndex;
    return index >= 0 ? this.optionValue(element.options[index]) : null;
  },

  selectMany: function(element) {
    var values, length = element.length;
    if (!length) return null;

    for (var i = 0, values = []; i < length; i++) {
      var opt = element.options[i];
      if (opt.selected) values.push(this.optionValue(opt));
    }
    return values;
  },

  optionValue: function(opt) {
    // extend element because hasAttribute may not be native
    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
  }
};

/*--------------------------------------------------------------------------*/

Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
  initialize: function($super, element, frequency, callback) {
    $super(callback, frequency);
    this.element   = $(element);
    this.lastValue = this.getValue();
  },

  execute: function() {
    var value = this.getValue();
    if (Object.isString(this.lastValue) && Object.isString(value) ?
        this.lastValue != value : String(this.lastValue) != String(value)) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  }
});

Form.Element.Observer = Class.create(Abstract.TimedObserver, {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.Observer = Class.create(Abstract.TimedObserver, {
  getValue: function() {
    return Form.serialize(this.element);
  }
});

/*--------------------------------------------------------------------------*/

Abstract.EventObserver = Class.create({
  initialize: function(element, callback) {
    this.element  = $(element);
    this.callback = callback;

    this.lastValue = this.getValue();
    if (this.element.tagName.toLowerCase() == 'form')
      this.registerFormCallbacks();
    else
      this.registerCallback(this.element);
  },

  onElementEvent: function() {
    var value = this.getValue();
    if (this.lastValue != value) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  },

  registerFormCallbacks: function() {
    Form.getElements(this.element).each(this.registerCallback, this);
  },

  registerCallback: function(element) {
    if (element.type) {
      switch (element.type.toLowerCase()) {
        case 'checkbox':
        case 'radio':
          Event.observe(element, 'click', this.onElementEvent.bind(this));
          break;
        default:
          Event.observe(element, 'change', this.onElementEvent.bind(this));
          break;
      }
    }
  }
});

Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.EventObserver = Class.create(Abstract.EventObserver, {
  getValue: function() {
    return Form.serialize(this.element);
  }
});
if (!window.Event) var Event = { };

Object.extend(Event, {
  KEY_BACKSPACE: 8,
  KEY_TAB:       9,
  KEY_RETURN:   13,
  KEY_ESC:      27,
  KEY_LEFT:     37,
  KEY_UP:       38,
  KEY_RIGHT:    39,
  KEY_DOWN:     40,
  KEY_DELETE:   46,
  KEY_HOME:     36,
  KEY_END:      35,
  KEY_PAGEUP:   33,
  KEY_PAGEDOWN: 34,
  KEY_INSERT:   45,

  cache: { },

  relatedTarget: function(event) {
    var element;
    switch(event.type) {
      case 'mouseover': element = event.fromElement; break;
      case 'mouseout':  element = event.toElement;   break;
      default: return null;
    }
    return Element.extend(element);
  }
});

Event.Methods = (function() {
  var isButton;

  if (Prototype.Browser.IE) {
    var buttonMap = { 0: 1, 1: 4, 2: 2 };
    isButton = function(event, code) {
      return event.button == buttonMap[code];
    };

  } else if (Prototype.Browser.WebKit) {
    isButton = function(event, code) {
      switch (code) {
        case 0: return event.which == 1 && !event.metaKey;
        case 1: return event.which == 1 && event.metaKey;
        default: return false;
      }
    };

  } else {
    isButton = function(event, code) {
      return event.which ? (event.which === code + 1) : (event.button === code);
    };
  }

  return {
    isLeftClick:   function(event) { return isButton(event, 0) },
    isMiddleClick: function(event) { return isButton(event, 1) },
    isRightClick:  function(event) { return isButton(event, 2) },

    element: function(event) {
      event = Event.extend(event);

      var node          = event.target,
          type          = event.type,
          currentTarget = event.currentTarget;

      if (currentTarget && currentTarget.tagName) {
        // Firefox screws up the "click" event when moving between radio buttons
        // via arrow keys. It also screws up the "load" and "error" events on images,
        // reporting the document as the target instead of the original image.
        if (type === 'load' || type === 'error' ||
          (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
            && currentTarget.type === 'radio'))
              node = currentTarget;
      }
      if (node.nodeType == Node.TEXT_NODE) node = node.parentNode;
      return Element.extend(node);
    },

    findElement: function(event, expression) {
      var element = Event.element(event);
      if (!expression) return element;
      var elements = [element].concat(element.ancestors());
      return Selector.findElement(elements, expression, 0);
    },

    pointer: function(event) {
      var docElement = document.documentElement,
      body = document.body || { scrollLeft: 0, scrollTop: 0 };
      return {
        x: event.pageX || (event.clientX +
          (docElement.scrollLeft || body.scrollLeft) -
          (docElement.clientLeft || 0)),
        y: event.pageY || (event.clientY +
          (docElement.scrollTop || body.scrollTop) -
          (docElement.clientTop || 0))
      };
    },

    pointerX: function(event) { return Event.pointer(event).x },
    pointerY: function(event) { return Event.pointer(event).y },

	getLayerX: function(event) { return event.layerX ? event.layerX : event.offsetX; },
	getLayerY: function(event) { return event.layerY ? event.layerY : event.offsetY; },


    stop: function(event) {
      Event.extend(event);
      event.preventDefault();
      event.stopPropagation();
      event.stopped = true;
    }
  };
})();

Event.extend = (function() {
  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
    m[name] = Event.Methods[name].methodize();
    return m;
  });

  if (Prototype.Browser.IE) {
    Object.extend(methods, {
      stopPropagation: function() { this.cancelBubble = true },
      preventDefault:  function() { this.returnValue = false },
      inspect: function() { return "[object Event]" }
    });

    return function(event) {
      if (!event) return false;
      if (event._extendedByPrototype) return event;

      event._extendedByPrototype = Prototype.emptyFunction;
      var pointer = Event.pointer(event);
      Object.extend(event, {
        target: event.srcElement,
        relatedTarget: Event.relatedTarget(event),
        pageX:  pointer.x,
        pageY:  pointer.y
      });
      return Object.extend(event, methods);
    };

  } else {
    Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__'];
    Object.extend(Event.prototype, methods);
    return Prototype.K;
  }
})();

Object.extend(Event, (function() {
  var cache = Event.cache;

  function getEventID(element) {
    if (element._prototypeEventID) return element._prototypeEventID[0];
    arguments.callee.id = arguments.callee.id || 1;
    return element._prototypeEventID = [++arguments.callee.id];
  }

  function getDOMEventName(eventName) {
    if (eventName && eventName.include(':')) return "dataavailable";
    return eventName;
  }

  function getCacheForID(id) {
    return cache[id] = cache[id] || { };
  }

  function getWrappersForEventName(id, eventName) {
    var c = getCacheForID(id);
    return c[eventName] = c[eventName] || [];
  }

  function createWrapper(element, eventName, handler) {
    var id = getEventID(element);
    var c = getWrappersForEventName(id, eventName);
    if (c.pluck("handler").include(handler)) return false;

    var wrapper = function(event) {
      if (!Event || !Event.extend ||
        (event.eventName && event.eventName != eventName))
          return false;

      Event.extend(event);
      handler.call(element, event);
    };

    wrapper.handler = handler;
    c.push(wrapper);
    return wrapper;
  }

  function findWrapper(id, eventName, handler) {
    var c = getWrappersForEventName(id, eventName);
    return c.find(function(wrapper) { return wrapper.handler == handler });
  }

  function destroyWrapper(id, eventName, handler) {
    var c = getCacheForID(id);
    if (!c[eventName]) return false;
    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
  }

  function destroyCache() {
    for (var id in cache)
      for (var eventName in cache[id])
        cache[id][eventName] = null;
  }


  // Internet Explorer needs to remove event handlers on page unload
  // in order to avoid memory leaks.
  if (window.attachEvent) {
    window.attachEvent("onunload", destroyCache);
  }

  // Safari has a dummy event handler on page unload so that it won't
  // use its bfcache. Safari <= 3.1 has an issue with restoring the "document"
  // object when page is returned to via the back button using its bfcache.
  if (Prototype.Browser.WebKit) {
    window.addEventListener('unload', Prototype.emptyFunction, false);
  }

  return {
    observe: function(element, eventName, handler) {
      element = $(element);
      var name = getDOMEventName(eventName);

      var wrapper = createWrapper(element, eventName, handler);
      if (!wrapper) return element;

      if (element.addEventListener) {
        element.addEventListener(name, wrapper, false);
      } else {
        element.attachEvent("on" + name, wrapper);
      }

      return element;
    },

    stopObserving: function(element, eventName, handler) {
      element = $(element);
      var id = getEventID(element), name = getDOMEventName(eventName);

      if (!handler && eventName) {
        getWrappersForEventName(id, eventName).each(function(wrapper) {
          element.stopObserving(eventName, wrapper.handler);
        });
        return element;

      } else if (!eventName) {
        Object.keys(getCacheForID(id)).each(function(eventName) {
          element.stopObserving(eventName);
        });
        return element;
      }

      var wrapper = findWrapper(id, eventName, handler);
      if (!wrapper) return element;

      if (element.removeEventListener) {
        element.removeEventListener(name, wrapper, false);
      } else {
        element.detachEvent("on" + name, wrapper);
      }

      destroyWrapper(id, eventName, handler);

      return element;
    },

    fire: function(element, eventName, memo) {
      element = $(element);
      if (element == document && document.createEvent && !element.dispatchEvent)
        element = document.documentElement;

      var event;
      if (document.createEvent) {
        event = document.createEvent("HTMLEvents");
        event.initEvent("dataavailable", true, true);
      } else {
        event = document.createEventObject();
        event.eventType = "ondataavailable";
      }

      event.eventName = eventName;
      event.memo = memo || { };

      if (document.createEvent) {
        element.dispatchEvent(event);
      } else {
        element.fireEvent(event.eventType, event);
      }

      return Event.extend(event);
    }
  };
})());

Object.extend(Event, Event.Methods);

Element.addMethods({
  fire:          Event.fire,
  observe:       Event.observe,
  stopObserving: Event.stopObserving
});

Object.extend(document, {
  fire:          Element.Methods.fire.methodize(),
  observe:       Element.Methods.observe.methodize(),
  stopObserving: Element.Methods.stopObserving.methodize(),
  loaded:        false
});

(function() {
  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
     Matthias Miller, Dean Edwards and John Resig. */

  var timer;

  function fireContentLoadedEvent() {
    if (document.loaded) return;
    if (timer) window.clearInterval(timer);
    document.fire("dom:loaded");
    document.loaded = true;
  }

  if (document.addEventListener) {
    if (Prototype.Browser.WebKit) {
      timer = window.setInterval(function() {
        if (/loaded|complete/.test(document.readyState))
          fireContentLoadedEvent();
      }, 0);

      Event.observe(window, "load", fireContentLoadedEvent);

    } else {
      document.addEventListener("DOMContentLoaded",
        fireContentLoadedEvent, false);
    }

  } else {
    document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
    $("__onDOMContentLoaded").onreadystatechange = function() {
      if (this.readyState == "complete") {
        this.onreadystatechange = null;
        fireContentLoadedEvent();
      }
    };
  }
})();
/*------------------------------- DEPRECATED -------------------------------*/

Hash.toQueryString = Object.toQueryString;

var Toggle = { display: Element.toggle };

Element.Methods.childOf = Element.Methods.descendantOf;

var Insertion = {
  Before: function(element, content) {
    return Element.insert(element, {before:content});
  },

  Top: function(element, content) {
    return Element.insert(element, {top:content});
  },

  Bottom: function(element, content) {
    return Element.insert(element, {bottom:content});
  },

  After: function(element, content) {
    return Element.insert(element, {after:content});
  }
};

var $continue = new Error('"throw $continue" is deprecated, use "return" instead');

// This should be moved to script.aculo.us; notice the deprecated methods
// further below, that map to the newer Element methods.
var Position = {
  // set to true if needed, warning: firefox performance problems
  // NOT neeeded for page scrolling, only if draggable contained in
  // scrollable elements
  includeScrollOffsets: false,

  // must be called before calling withinIncludingScrolloffset, every time the
  // page is scrolled
  prepare: function() {
    this.deltaX =  window.pageXOffset
                || document.documentElement.scrollLeft
                || document.body.scrollLeft
                || 0;
    this.deltaY =  window.pageYOffset
                || document.documentElement.scrollTop
                || document.body.scrollTop
                || 0;
  },

  // caches x/y coordinate pair to use with overlap
  within: function(element, x, y) {
    if (this.includeScrollOffsets)
      return this.withinIncludingScrolloffsets(element, x, y);
    this.xcomp = x;
    this.ycomp = y;
    this.offset = Element.cumulativeOffset(element);

    return (y >= this.offset[1] &&
            y <  this.offset[1] + element.offsetHeight &&
            x >= this.offset[0] &&
            x <  this.offset[0] + element.offsetWidth);
  },

  withinIncludingScrolloffsets: function(element, x, y) {
    var offsetcache = Element.cumulativeScrollOffset(element);

    this.xcomp = x + offsetcache[0] - this.deltaX;
    this.ycomp = y + offsetcache[1] - this.deltaY;
    this.offset = Element.cumulativeOffset(element);

    return (this.ycomp >= this.offset[1] &&
            this.ycomp <  this.offset[1] + element.offsetHeight &&
            this.xcomp >= this.offset[0] &&
            this.xcomp <  this.offset[0] + element.offsetWidth);
  },

  // within must be called directly before
  overlap: function(mode, element) {
    if (!mode) return 0;
    if (mode == 'vertical')
      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
        element.offsetHeight;
    if (mode == 'horizontal')
      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
        element.offsetWidth;
  },

  // Deprecation layer -- use newer Element methods now (1.5.2).

  cumulativeOffset: Element.Methods.cumulativeOffset,

  positionedOffset: Element.Methods.positionedOffset,

  absolutize: function(element) {
    Position.prepare();
    return Element.absolutize(element);
  },

  relativize: function(element) {
    Position.prepare();
    return Element.relativize(element);
  },

  realOffset: Element.Methods.cumulativeScrollOffset,

  offsetParent: Element.Methods.getOffsetParent,

  page: Element.Methods.viewportOffset,

  clone: function(source, target, options) {
    options = options || { };
    return Element.clonePosition(target, source, options);
  }
};

/*--------------------------------------------------------------------------*/

if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
  function iter(name) {
    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
  }

  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
  function(element, className) {
    className = className.toString().strip();
    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
  } : function(element, className) {
    className = className.toString().strip();
    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
    if (!classNames && !className) return elements;

    var nodes = $(element).getElementsByTagName('*');
    className = ' ' + className + ' ';

    for (var i = 0, child, cn; child = nodes[i]; i++) {
      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
          (classNames && classNames.all(function(name) {
            return !name.toString().blank() && cn.include(' ' + name + ' ');
          }))))
        elements.push(Element.extend(child));
    }
    return elements;
  };

  return function(className, parentElement) {
    return $(parentElement || document.body).getElementsByClassName(className);
  };
}(Element.Methods);

/*--------------------------------------------------------------------------*/

Element.ClassNames = Class.create();
Element.ClassNames.prototype = {
  initialize: function(element) {
    this.element = $(element);
  },

  _each: function(iterator) {
    this.element.className.split(/\s+/).select(function(name) {
      return name.length > 0;
    })._each(iterator);
  },

  set: function(className) {
    this.element.className = className;
  },

  add: function(classNameToAdd) {
    if (this.include(classNameToAdd)) return;
    this.set($A(this).concat(classNameToAdd).join(' '));
  },

  remove: function(classNameToRemove) {
    if (!this.include(classNameToRemove)) return;
    this.set($A(this).without(classNameToRemove).join(' '));
  },

  toString: function() {
    return $A(this).join(' ');
  }
};

Object.extend(Element.ClassNames.prototype, Enumerable);

/*--------------------------------------------------------------------------*/

/**
 * WJSpin
 *
 * Javascript class that can perform ajax calls
 *
 * Changelog
 * ---------
 *
 * Ron Rademaker Mon Jan 05 2009
 * -----------------------------
 * - Added multi support
 * - Added root - prevents requests from having wrong callbacks
 *
 * @since Mon Jun 02 2008
 * @author Ron Rademaker
 **/

var WJSpin = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new WJSpin
	 *
	 * @since Mon Jun 02 2008
	 * @access public
	 * @return void
	 **/
	initialize: function() {
		this.root = true;
		this.htmlelement = null;
		this.callbackfunctions = null;
		this.errorcallback = null;
		this.multi = false;
		this.spins = [];
	},

	/**
	 * content
	 *
	 * Performs an ajax request to update, get or insert content
	 *
	 * @since Mon Jun 02 2008
	 * @access public
	 * @param WJUrl url
	 * @param Array callback
	 * @param Array errorcallback
	 * @return void
	 * @todo refactor
	 **/
	content: function(url, callback, errorcallback, method, rootSpin) {
		this._saveLastCall(arguments);
		var method = method || "post";
		if (this.root && this.multi) {
			if (!Object.isArray(callback) ) {
				throw new Error("Callback should be an array of elements and functions");
			}
			this.spins.push({url: url, callback: callback, errorcallback: errorcallback, method: method});
			return false;
		}
		else if (this.root) {
			var spin = new WJSpin();
			spin.root = false;
			return spin.content(url, callback, errorcallback, method, this);
		}
		else {
			if (!Object.isArray(callback) ) {
				throw new Error("Callback should be an array of elements and functions");
			}

			this._fillCallback(callback);
			if (typeof(errorcallback) == "function") {
				this.errorcallback = {"default": errorcallback};
			}
			else {
				this.errorcallback = errorcallback;
			}
			rootSpin.lastRequest = new Ajax.Request(url.getUrl(), {method: method, parameters: url.getParameters(), onSuccess: this.ajaxResponse.bind(this), onFailure: this.ajaxError.bind(this)});
			return true;
		}
	},

	/**
	 * run
	 *
	 * Performs all set requests when multi, returns the number of performed requests
	 *
	 * @since Mon Jan 05 2009
	 * @access public
	 * @return integer
	 * @todo refactor
	 **/
	run: function() {
		if (this.multi) {
			if (this.spins.length == 0) {
				return 0;
			}
			var method = -1;
			for (var i = 0; i < this.spins.length; i++) {
				if (method == -1) {
					method = this.spins[i].method;
				}
				else if (method != this.spins[i].method) {
					WJDebugger.log(WJDebugger.WARNING, "Multispin with multiple request methods, using " + method);
				}
			}
			var url = -1;
			for (var i = 0; i < this.spins.length; i++) {
				if (url == -1) {
					url = this.spins[i].url.getUrl();
				}
				else if (url != this.spins[i].url.getUrl() ) {
					WJDebugger.log(WJDebugger.WARNING, "Multispin with multiple urls, using " + url);
				}
			}
			var parameters = {"requests": this.spins.length, "wmtrigger[]": ["multispin"]};
			this.errorcallback = new Array();
			for (var i = 0; i < this.spins.length; i++) {
				var spinpars = this.spins[i].url.getParameters();
				for (var key in spinpars) {
					if (key.match(/\[.*\]/) ) {
						parameters[key.replace(/([^\[]*)(\[.*)/, "$1[" + i + "]$2")] = spinpars[key];
					}
					else {
						parameters[key + "[" + i + "]"] = spinpars[key];
					}
				}
				this._fillCallback(this.spins[i].callback, i);
				errorcallback = this.spins[i].errorcallback;
				if (typeof(errorcallback) == "function") {
					this.errorcallback[i] = {"default": errorcallback};
				}
				else {
					this.errorcallback[i] = errorcallback;
				}
			}
			this.lastRequest = new Ajax.Request(url, {method: method, parameters: parameters, onSuccess: this.ajaxResponse.bind(this), onFailure: this.ajaxError.bind(this)});
			this.spins = new Array();
			return parameters.requests;
		}
		return 0;
	},

	/**
	 * saveLastCall
	 *
	 * Saves the function object that can be used to reproduce the last call
	 *
	 * @since Wed Feb 11 2009
	 * @access protected
	 * @param arguments args
	 * @return void
	 * @todo Find out how many times the last call was called again, to avoid infinitive loops
	 **/
	_saveLastCall: function(args) {
		var bindArgs = $A(args);
		bindArgs[0] = bindArgs[0].clone();
		this._lastCall = this.content.bind(new WJSpin() );
		this._lastCall.args = bindArgs;
	},

	/**
	 * setMulti
	 *
	 * Switches this spin to or from multi (ie. wraps multiple ajax calls in one to improve performance)
	 *
	 * @since Mon Jan 05 2009
	 * @access public
	 * @param boolean multi
	 * @return void
	 **/
	setMulti: function(multi) {
		this.multi = multi;
	},

	/**
	 * ajaxError
	 *
	 * Handles an unsuccesful ajax response
	 *
	 * @since Mon Jun 02 2008
	 * @access public
	 * @param Ajax.Request response
	 * @return void
	 * @todo refactor
	 **/
	ajaxError: function(response) {
		var called = false;
		if (this.multi) {
			// not good, call all error handlers
			for (var i = 0; i < this.errorcallback.length; i++) {
				if (typeof(this.errorcallback[i][response.status]) == "function") {
					this.errorcallback[i][response.status](response);
					called = true;
				}
				else if (typeof(this.errorcallback[i]["default"]) == "function") {
					this.errorcallback[i]["default"](response);
					called = true;
				}
			}
		}
		else if (this.errorcallback) {
			if (typeof(this.errorcallback[response.status]) == "function") {
				this.errorcallback[response.status](response, this._lastCall);
				called = true;
			}
			else if (typeof(this.errorcallback["default"]) == "function") {
				this.errorcallback["default"](response, this._lastCall);
				called = true;
			}
		}
		if (!called) {
			if (typeof(WJSpin.fallbackErrorCallback) == "function") {
				WJSpin.fallbackErrorCallback(response, this._lastCall);
			}
			else {
				WJDebugger.log(WJDebugger.WARNING, "Unhandled error in WJSpin", response);
			}
		}
	},

	/**
	 * fallbackErrorCallback
	 *
	 * Function called when no error callback is set and an error occurs
	 *
	 * @since Wed Jan 07 2009
	 * @access public
	 * @param response response
	 * @return void
	 **/
	fallbackErrorCallback: function(response) {
		WJDebugger.log(WJDebugger.ERROR, "Unhandled error in WJSpin", response);
	},

	/**
	 * ajaxResponse
	 *
	 * Handles a succesful ajax request
	 *
	 * @since Mon Jun 02 2008
	 * @access public
	 * @param Ajax.Response response
	 * @return void
	 * @todo refactor
	 **/
	ajaxResponse: function(response) {
		if (this.multi) {
			if (response.getHeader("content-type").indexOf("xml") != -1) {
				this._multiResponse(response.responseXML);
			}
			else {
				WJDebugger.log(WJDebugger.ERROR, "Multispin not support for non-xml responses: " + response.getHeader("content-type") );
			}
		}
		else if (response.getHeader("content-type").indexOf("xml") != -1) {
			this._updateHtmlElementsWithPlain(response.responseText);
			
			var xmldoc = Try.these(
				function() {
					var xmldoc = new ActiveXObject("Microsoft.XMLDOM");
					xmldoc.async = "false";
					xmldoc.loadXML(response.responseText);
					return xmldoc;
				},
				function () {
					var parser = new DOMParser();
					var xmldoc = parser.parseFromString(response.responseText, "text/xml");
					return xmldoc;
				}
			) || response.responseText;
			
			this._callCallbacks(xmldoc);
		}
		else if (response.getHeader("content-type") == "application/json") {
			this._callCallbacks(response.responseJSON);
		}
		else {
			this._updateHtmlElementsWithPlain(response.responseText);
			this._callCallbacks(response.responseText);
		}
	},

	/**
	 * _multiResponse
	 *
	 * Handles a spin multi response
	 *
	 * @since Tue Jan 06 2009
	 * @access protected
	 * @param Ajax.Response response
	 * @return void
	 * @todo refactor
	 **/
	_multiResponse: function(response) {
		var responses = response.getElementsByTagName("request");
		for (var i = 0; i < responses.length; i++) {
			var id = responses[i].getAttribute("id");
			var status = responses[i].getAttribute("status");
			var contenttype = responses[i].getAttribute("contentType");

			if ( (status >= 200) && (status < 400) ) {
				if (contenttype.indexOf("xml") != -1) {
					var reqResp = false;
					for (var j = 0; (j < responses[i].childNodes.length) && (!reqResp); j++) {
						if (responses[i].childNodes[j].nodeType == 1) {
							reqResp = responses[i].childNodes[j];
						}
					}
					if (document.importNode) {
						this._updateHtmlElementsWithXML(reqResp.cloneNode(true), id);
					}
					else {
						this._updateHtmlElementsWithPlain(reqResp.xml, id);
					}

					this._callCallbacks(reqResp.cloneNode(true), id);
				}
				else if (contenttype == "application/json") {
					this._callCallbacks(responses[i].textContent, id);
				}
				else {
					this._updateHtmlElementsWithPlain(responses[i].getTextContent(), id);
					this._callCallbacks(responses[i].textContent, id);
				}
			}
			else {
				if (this.errorcallback[parseInt(id)]) {
					if (typeof(this.errorcallback[parseInt(id)][status]) == "function") {
						this.errorcallback[id][status](responses[i]);
					}
					else if (typeof(this.errorcallback[parseInt(id)]["default"]) == "function") {
						this.errorcallback[id]["default"](responses[i]);
					}
				}
			}
		}
	},

	/**
	 * _callCallbacks
	 *
	 * Calls all callback functions with the passed response (can be json, xml or plain text)
	 *
	 * @since Mon Jun 02 2008
	 * @access protected
	 * @param mixed response
	 * @param integer index
	 * @return void
	 **/
	_callCallbacks: function(response, index) {
		var callbacks = this.callback;
		if (this.multi) {
			callbacks = callbacks[index] || [];
		}
		for (var i = 0; i < callbacks.length; i++) {
			callbacks[i](response, this._lastCall);
		}
	},

	/**
	 * _updateHtmlElementsWithXML
	 *
	 * Updates the HTML with the contents in response (xml)
	 *
	 * @since Mon Jun 02 2008
	 * @access protected
	 * @param domnode response
	 * @param integer index
	 * @return void
	 **/
	_updateHtmlElementsWithXML: function(response, index) {
		var htmlelements = this.htmlelements;
		if (this.multi) {
			htmlelements = this.htmlelements[index];
		}
		for (var i = 0; i < htmlelements.length; i++) {
			htmlelements[i] = $(htmlelements[i]);
			var stub = new Element("div");
			if (response.documentElement) {
				stub.appendChild(htmlelements[i].ownerDocument.importNode(response.documentElement, true) );
			}
			else {
				stub.appendChild(htmlelements[i].ownerDocument.importNode(response, true) );
			}
			stub.innerHTML += "";
			var element = stub.firstDescendant();

			if (htmlelements[i].ajaxUpdateType == "replaceElement") {
				if (htmlelements[i].preserveId) {
					element.setAttribute("id", this.htmlelements[i].id);
				}
				htmlelements[i].replace(element);
			}
			else if (htmlelements[i].ajaxUpdateType == "prependChild") {
				htmlelements[i].insertBefore(element);
			}
			else if (htmlelements[i].ajaxUpdateType == "appendChild") {
				htmlelements[i].appendChild(element);
			}
			else {
				htmlelements[i].innerHTML = "";
				htmlelements[i].appendChild(element);
			}
			stub = null; // avoid possible memory leaks in IE
		}
	},

	/**
	 * _updateHtmlElementsWithPlain
	 *
	 * Updates the HTML with the contents in response (plain text)
	 *
	 * @since Mon Jun 02 2008
	 * @access protected
	 * @param text response
	 * @param integer index
	 * @return void
	 **/
	_updateHtmlElementsWithPlain: function(response, index) {
		for (el in WJSpin.closedElements) {
			response = response.replace(WJSpin.closedElements[el], "<$1$2/>");
		}
		var htmlelements = this.htmlelements;
		if (this.multi) {
			htmlelements = this.htmlelements[index];
		}
		for (var i = 0; i < htmlelements.length; i++) {
			if (htmlelements[i].ajaxUpdateType == "replaceElement") {
				var parent = htmlelements[i].parentNode;
				var id = false;
				if (htmlelements[i].preserveId) {
					var id = htmlelements[i].id;
				}

				htmlelements[i].replace(response);
				if (id) {
					htmlelements[i].id = id;
				}
				parent.innerHTML = parent.innerHTML;
			}
			else if (htmlelements[i].ajaxUpdateType == "prependChild") {
				htmlelements[i].innerHTML = response + htmlelements[i].innerHTML;
			}
			else if (this.htmlelements[i].ajaxUpdateType == "appendChild") {
				htmlelements[i].innerHTML = htmlelements[i].innerHTML + response;
			}
			else {
				htmlelements[i].innerHTML = response;
			}

			try {
				htmlelements[i].innerHTML.evalScripts();
			}
			catch (error) {
				// Otherwise Internet Exploder doesn't continue with execution of other javascript
			}
		}
	},

	/**
	 * update
	 *
	 * Performs an ajax request to do a cms function (the way windmill works allows you to update / get / insert content along with the cms actions)
	 *
	 * @since Mon Jun 02 2008
	 * @access public
	 * @param WJUrl url
	 * @param string module
	 * @param string func
	 * @param string data
	 * @param Array callback
	 * @param Array errorcallback
	 * @return void
	 **/
	update: function(url, module, func, data, callback, errorcallback) {
		var parameters = {};
		Object.extend(parameters, url.getParameters() || { });
		var _url = new WJUrl(parameters, url.getUrl() );
		_url.addParameter("__cms_" + module, "true");
		_url.addParameter("__function_" + module, func);
		for (var key in data) {
			_url.addParameter(key, data[key]);
		}

		return this.content(_url, callback, errorcallback);
	},

	/**
	 * _fillCallback
	 *
	 * Fills the callback and htmlelements array from the callback array
	 *
	 * @since Mon Jun 02 2008
	 * @access public
	 * @param Array callback
	 * @param integer index
	 * @return void
	 **/
	_fillCallback: function(callback, index) {
		if (!this.callback) {
			this.callback = new Array();
		}
		if (!this.htmlelements) {
			this.htmlelements = new Array();
		}
		if (index != undefined) {
			this.callback[index] = new Array();
			this.htmlelements[index] = new Array();
		}

		for (var i = 0; i < callback.length; i++) {
			if (Object.isFunction(callback[i]) ) {
				if (index != undefined) {
					this.callback[index].push(callback[i]);
				}
				else {
					this.callback.push(callback[i]);
				}
			}
			else if (Object.isElement(callback[i]) ) {
				if (index != undefined) {
					this.htmlelements[index].push(callback[i]);
				}
				else {
					this.htmlelements.push(callback[i]);
				}
			}
		}
	}
});

/**
 * The elements that should be closed when _updateHtmlElementsWithPlain is used
 *
 * @since Mon Oct 13 2008
 **/
WJSpin.closedElements = {};
["area", "base", "basefont", "br", "col", "frame", "hr", "img", "input", "isindex", "link", "meta", "param"].each(function(tag) {
	WJSpin.closedElements[tag] = new RegExp("<(" + tag + ")([^>]*)></\\1>", "gi");
});
/**
 * WJUrl
 *
 * Javascript class that represents a (windmill) URL
 *
 * @since Tue Jun 03 2008
 * @author Ron Rademaker
 **/

var WJUrl = Class.create();
WJUrl.prototype = {
	/**
	 * initialize
	 *
	 * Creates a new WJUrl
	 *
	 * @since Tue Jun 03 2008
	 * @access public
	 * @param object parameters
	 * @param string url
	 * @return void
	 **/
	initialize: function(parameters, url) {
		this.url = url || "/index.php";
		this.parameters = parameters || {};
	},

	/**
	 * addParameter
	 *
	 * Adds a parameter to the parameters object, returns true if added, false if overwritten
	 *
	 * @since Tue Jun 03 2008
	 * @access public
	 * @param string key
	 * @param string value
	 * @return boolean
	 **/
	addParameter: function(key, value) {
		if (this.parameters[key]) {
			this.parameters[key] = value;
			return false;
		}
		else {
			this.parameters[key] = value;
			return true;
		}
	},

	/**
	 * deleteParameter
	 *
	 * @since Tue Jun 03 2008
	 * @access public
	 * @param string key
	 * @return string
	 **/
	deleteParameter: function(key) {
		var value = this.parameters[key];
		delete this.parameters[key];
		return value;
	},

	/**
	 * getParameter
	 *
	 * Retrieves the value of a parameter
	 *
	 * @since Tue Jun 03 2008
	 * @access public
	 * @param string key
	 * @return string
	 **/
	getParameter: function(key) {
		return this.parameters[key];
	},

	/**
	 * setCt
	 *
	 * Sets the content template
	 *
	 * @since Tue Jun 03 2008
	 * @access public
	 * @param string ct
	 * @return void
	 **/
	setCt: function(ct) {
		this.addParameter("ct", ct);
	},

	/**
	 * setDt
	 *
	 * Sets the design template
	 *
	 * @since Tue Jun 03 2008
	 * @access public
	 * @param string dt
	 * @return void
	 **/
	setDt: function(dt) {
		this.addParameter("dt", dt);
	},

	/**
	 * setUft
	 *
	 * Sets the user interface function templates
	 *
	 * @since Tue Jun 03 2008
	 * @access public
	 * @param Array uft
	 * @return void
	 **/
	setUft: function(uft) {
		this.addParameter("uft", uft);
	},

	/**
	 * getParameters
	 *
	 * Retrieves all parameters
	 *
	 * @since Tue Jun 03 2008
	 * @access public
	 * @return object
	 **/
	getParameters: function() {
		return this.parameters;
	},

	/**
	 * getUrl
	 *
	 * Gets the URL of this WJUrl
	 *
	 * @since Tue Jun 03 2008
	 * @access public
	 * @return string
	 **/
	getUrl: function() {
		return this.url;
	},

	/**
	 * clone
	 *
	 * Copies all info to a new WJUrl and returns that
	 *
	 * @since Thu Feb 12 2009
	 * @access public
	 * @return WJUrl
	 **/
	clone: function() {
		return new WJUrl(Object.clone(this.parameters), this.url);
	}
}
/**
 * WJDebugger
 *
 * Javascript class that handles errors and notices
 *
 * @since Mon Aug 04 2008
 * @author Giso Stallenberg
 **/
var WJDebugger = {
	SILENT: 0,
	ERROR: 1,
	WARNING: 2,
	NOTICE: 3,
	INFO: 4,
	DEBUG: 5
};

WJDebugger.verbosity = WJDebugger.SILENT; // default verbosity (do not display any info)

WJDebugger.log = function(level) {
	if (isNaN(level) || level > WJDebugger.verbosity) {
		return;
	}
	var args = $A(arguments);
	args.shift();
	if (typeof(console) != "undefined" && typeof(console.error) == "function" && typeof(console.warn) == "function" && typeof(console.log) == "function") {
		switch (level) {
			case WJDebugger.ERROR:
				console.error.apply(console, args);
				break;
			case WJDebugger.WARNING:
				console.warn.apply(console, args);
				break;
			default:
				console.log.apply(console, args);
				break;
		}
	}
	else {
		// TODO: handle FF firebug extension missing
		// alert(args);
	}
}

WJDebugger.stack = function(level) {
	if (isNaN(level) || level > WJDebugger.verbosity) {
		return;
	}
	try {
		throw new Error("stack");
	}
	catch(e) {
      var stack = (e.stack || '').match(arguments.callee.regex).reject(function(path) {
        return /(prototype|eprototype([a-z][a-z]_[A-Z][A-Z])|unittest|update_helper)\.js/.test(path);
      }).join("\n");

		WJDebugger.log(level, stack);
		throw e;
	}
}
WJDebugger.stack.regex = new RegExp("@" + window.location.protocol + ".*?\\d+\\n", "g");
/**
 * WJUrl
 *
 * Javascript class for easy access to cookies
 *
 * @since Tue Oct 07 2008
 * @author Ron Rademaker
 **/
WJCookie = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new WJCookie
	 *
	 * @since Tue Oct 07 2008
	 * @access public
	 * @param integer timeout
	 * @return void
	 **/
	initialize: function(timeout) {
		this._timeout = timeout || (25 * 60 * 60 * 365); // default time to a year
		this._seperator = "; ";
		this._replacement = "~~~~";  // this will cause problems when trying to save four ~ in a row
	},

	/**
	 * get
	 *
	 * Gets the value of key if available
	 *
	 * @since Tue Oct 07 2008
	 * @access public
	 * @param string key
	 * @param boolean json
	 * @return mixed
	 **/
	get: function(key, json) {
		if (json == null) {
			json = true;
		}
		var cookies = document.cookie.split(this._seperator);

		for (var i = 0; i < cookies.length; i++) {
			var cookie = cookies[i].split("=");
			if (cookie[0] == key) {
				if (json) {
					// unescapes the cookie value if evalJSON gives an error
					try {
						return cookie.slice(1).join("=").replace(this._replacement, this._seperator).evalJSON(true);
					}
					catch (e) {
						var value = unescape(cookie.slice(1).join("=").replace(this._replacement, this._seperator) );
						return value.evalJSON(true);
					}
				}
				else {
					return cookie.slice(1).join("=").replace(this._replacement, this._seperator);
				}
			}
		}
	},

	/**
	 * set
	 *
	 * Sets the value of key to value
	 *
	 * @since Tue Oct 07 2008
	 * @access public
	 * @param string key
	 * @param mixed value
	 * @param boolean json
	 * @return void
	 **/
	set: function(key, value, json) {
		if (json == null) {
			json = true;
		}
		var tostore = json ? Object.toJSON(value) : value;
		document.cookie = key + "=" + tostore.replace(this._seperator, this._replacement) + this._seperator + this._getExpires() + this._seperator + "path=/";
	},

	/**
	 * _getExpires
	 *
	 * Gets the expires value for the current date and time
	 *
	 * @since Tue Oct 07 2008
	 * @access protected
	 * @return string
	 **/
	_getExpires: function() {
		var date  = new Date();
		date.setTime(date.getTime() + (this._timeout * 1000) );
		return "expires=" + date.toGMTString();
	}
});

Element.addMethods();
/**
 * prototype.historyManager
 * 
 * Observes back/forward button usage and saves states
 * for registered modules into the hash. This allows to
 * bookmark specific states for an application.
 * Based on HistoryManager for Mootools by Harald Kirschner at http://digitarald.de/project/history-manager/
 * @version		1.0
 * 
 * @see			Events, Options
 * 
 * @license		MIT License
 * @author		Alfredo Artiles
 * @copyright	2008 Author
 */
 

/**
 * Extends Array with 2 helpers: isSimilar(array) and complement(array)
 * 
 */
Object.extend(Array.prototype, {

	/**
	 * isSimilar - Returns true for similar arrays, type-insensitive
	 * 
	 * @example
	 *  [1].isSimilar(['1']) == true
	 *  [1, 2].isSimilar([1, false]) == false
	 *  
	 * @return	{Boolean}
	 * @param	{Object} Array
	 */
	isSimilar: function(array) {
		return ($A(this).toJSON() == $A(array).toJSON());
	},

	/**
	 * complement - Fills up empty array values from another array, length is the same
	 * 
	 * @example
	 *  [1, null].complement([3, 4]) == [1, 4]
	 *	[undefined, '1'].complement([2, 3, 4]) == [2, '1']

	 * @return	{Array} this
	 * @param	{Object} Array
	 */
	complement: function(array) {
		for (var i = 0, j = this.length; i < j; i++) this[i] = (this[i] != undefined)?this[i]:(array[i] || null);  //$pick(this[i], array[i] || null);
		return this;
	}
});
 
var ProtoHistoryManager = Class.create({
	/**
	 * Default options - Can be overridden with setOptions
	 * 
	 * observeDelay: Duration for checking the state, default 100ms
	 * stateSeparator: Seperator for module-state join, default ';'
	 * iframeSrc: Scr for IE6/7 iframe, must exist on server!
	 * onStart: Fires on start
	 * onRegister: Fires on register
	 * onUnregister: Fires on unregister
	 * onUpdate: Fires when state changes from ...
	 * onStateChange: ... module changes
	 * onObserverChange: ... history change
	 */
	options: {
		observeDelay: 100,
		stateSeparator: ';',
		iframeSrc: 'blank.html',
		onStart: Prototype.emptyFunction,
		onRegister: Prototype.emptyFunction,
		onUnregister: Prototype.emptyFunction,
		onStart: Prototype.emptyFunction,
		onUpdate: Prototype.emptyFunction,
		onStateChange: Prototype.emptyFunction,
		onObserverChange: Prototype.emptyFunction
	},

	/**
	 * Default options for register
	 * 
	 * defaults: Default values array, initially empty.
	 * regexpParams: When regexp is a String, this is the second argument for new RegExp.
	 * skipDefaultMatch: default true; When true onGenerate is not called when current values are similar to the default values.
	 */
	dataOptions: {
		skipDefaultMatch: true,
		defaults: [],
		regexpParams: ''
	},

	/**
	 * Constructur - Class.initialize
	 * 
	 * Options:
	 *  - observeDelay: duration in ms, default 100 - BackBuddy observe the hash for changes periodical
	 *  - stateSeparator: char, default ';' - Separator for multiple module-states in the hash
	 *  - iframeSrc: string, default 'blank.html' - File for the iframe (IE6/7), must exist on the server!
	 *  - Events: onStart, onRegister, onStart, onUpdate, onStateChange, onObserverChange
	 * 
	 * @return	this
	 * 
	 * @param	{Object} options
	 */
	initialize: function(options) {
		if (this.modules) return this;
		this.setOptions(options);
		this.modules = $H({});
		this.count = history.length;
		this.states = [];
		this.states[this.count] = this.getHash();
		this.state = null;
		return this;
	},

	setOptions: function(options){
		Object.extend(this, this.options);
		Object.extend(this, options);
		return this;
	},
	
	/**
	 * Start - Check hash and start observer
	 * 
	 * Call start after registering ALL modules. This start the observer,
	 * reads the state from the hash and calls onMatch for effected modules.
	 * 
	 * @return	this
	 * 
	 */
	start: function() {
		new PeriodicalExecuter(this.observe.bind(this), this.options.observeDelay/1000);
		this.started = true;
		this.observe();
		this.update();
		this.onStart.apply(this, [this.state]);
		return this;
	},

	/**
	 * Registers a module
	 * 
	 * @return	{Object} Object with shortcuts for setValues, setValue, generate and unregister
	 * 
	 * @param	{String} Module key
	 * @param	{RegExp}/{String} Regular expression that matches the string updated from onGenerate
	 * @param	{Function} Will be called when the regexp matches, with the new values as argument.
	 * @param	{Function} Should return the string for the state string, values are first argument
	 * @param	{Array} default values, the input values given to onMatch and onGenerate will be complemented with these
	 * @param	{Object} (optional) options
	 */
	register: function(key, defaults, onMatch, onGenerate, regexp, options) {
		if (!this.modules) this.initialize();
		var data = Object.extend(this.dataOptions, options || {});
		Object.extend(data, {
			defaults: defaults,
			onMatch: onMatch,
			onGenerate: onGenerate,
			regexp: regexp
		});
		data.regexp = data.regexp || key + '-([\\w_-]*)';
		if (typeof data.regexp == 'string') data.regexp = new RegExp(data.regexp, data.regexpParams);
		data.onGenerate = data.onGenerate || function(values) { return key + '-' + values[0]; };

		data.values = data.defaults.clone();
		this.modules.set(key, data);
		//Event.fire(this, 'onUnregister', [key, data]);
		this.onUnregister.apply(this, [key, data]);
		return {
			setValues: function(values) {
				return this.setValues(key, values);
			}.bind(this),
			setValue: function(index, value) {
				return this.setValue(key, index, value);
			}.bind(this),
			generate: function(values) {
				return this.generate(key, values);
			}.bind(this),
			unregister: function() {
				return this.unregister(key);
			}.bind(this)
		};
	},

	/**
	 * unregister - Removes an module from the
	 * 
	 * @param	{String} Module key
	 */
	unregister: function(key) {
		//Event.fire(this, 'onRegister', [key]);
		this.onRegister.apply(this, [key]);
		this.modules.unset(key);
	},

	/**
	 * setValues - Set all values new, updates new state
	 * 
	 * @param	{String} Module key
	 * @param	{Object} Complete values
	 */
	setValues: function(key, values) {
		var data = this.modules.get(key);
		if (!data || data.values.isSimilar(values)) return this;
		data.values = values;
		this.update();
		return this;
	},

	/**
	 * setValue - Set one value, updates new state
	 * 
	 * @param	{String} Module key
	 * @param	{Number} Value index
	 * @param	{Object} Value
	 */
	setValue: function(key, index, value) {
		var data = this.modules.get(key);
		if (!data) return this;
		data.values[index] = value;
		this.update();
		return this;
	},

	/**
	 * generate - Generates a hash from the given
	 * 
	 * @param	{String} Module key
	 * @param	{Number} Value index
	 * @param	{Object} Value
	 */
	generate: function(key, values) {
		var data = this.modules.get(key);
		var current = data.values.clone();
		data.values = values;
		var state = this.generateState();
		data.values = current;
		return '#' + state;
	},

	observe: function() {
		if (this.timeout) return;
		var state = this.getState();
		
		if (this.state == state) return;
		if ((Prototype.Browser.IE || Prototype.Browser.WebKit) && (this.state !== null)) this.setState(state, true);
		else this.state = state;
		this.modules.each(function(data) {
			var bits = state.match(data.value.regexp);
			if (bits) {
				bits.splice(0, 1);
				bits.complement(data.value.defaults);
				if (!bits.isSimilar(data.value.defaults)) data.value.values = bits;
			} else data.value.values = data.value.defaults.clone();
			data.value.onMatch(data.value.values, data.value.defaults);
		});
		//Event.fire(this, 'onStateChange', [state]).
		//Event.fire(this, 'onObserverChange', [state]);
		this.onStateChange.apply(this, [state]);
		this.onObserverChange.apply(this, [state]);
	},

	generateState: function() {
		var state = [];
		this.modules.each(function(data, key) {
			if (data.value.skipDefaultMatch && data.value.values.isSimilar(data.value.defaults)) return;
			state.push(data.value.onGenerate(data.value.values));
		});
		return state.join(this.options.stateSeparator);
	},

	update: function() {
		if (!this.started) return this;
		var state = this.generateState();
		if ((!this.state && !state) || (this.state == state)) return this;
		this.setState(state);
		this.onStateChange.apply(this, [state]);
		this.onUpdate.apply(this, [state]);
		//Event.fire(this, 'onStateChange', [state]).
		//Event.fire(this, 'onUpdate', [state]);
		return this;
	},

	observeTimeout: function() {
		if (this.timeout) this.timeout = clearInterval(this.timeout);
		else this.timeout = this.observeTimeout.bind(this).delay(200/1000);
	},

	getHash: function() {
		var href = top.location.href;
		var pos = href.indexOf('#') + 1;
		return (pos) ? href.substr(pos) : '';
	},

	getState: function() {
		var state = this.getHash();
		
		if (this.iframe) {
			var doc = this.iframe.contentWindow.document;
			if (doc && doc.body.id == 'state') {
				var istate = doc.body.innerText;
				if (this.state == state) return istate;
				this.istateOld = true;
			} else return this.istate;
		}
		if (Prototype.Browser.WebKit && history.length != this.count) {
			this.count = history.length;
			return (this.states[this.count - 1] != undefined)?this.states[this.count - 1] != undefined:state;
		}
		return state;
	},

	setState: function(state, fix) {
	
		state = state!=undefined?state:'';

		if (Prototype.Browser.WebKit) {
			if (!this.form) {
				this.form = new Element('form', {'method': 'get'});
				document.body.appendChild(this.form);;
			}	
			this.count = history.length;
			this.states[this.count] = state;
			this.observeTimeout();
			this.form.setProperty('action', '#' + state).submit();
		} else {
			top.location.hash = state || '#';
		}
		
		if (Prototype.Browser.IE && (!fix || this.istateOld)) {
			if (!this.iframe) {
				this.iframe = new Element('iframe', {
					'src': this.options.iframeSrc,
					'styles': 'display: none;',
					'width': '1',
					'height': '1'
				});
				document.body.appendChild(this.iframe);

				this.istate = this.state;
			}
			try {
				var doc = this.iframe.contentWindow.document;
				doc.open();
				doc.write('<html><body id="state">' + state + '</body></html>');
				doc.close();
				this.istateOld = false;
			} catch(e) {};
		}
		this.state = state;
	},

	extend: Object.extend
});



/**
 * SWFLoader is the javascript loader class for flash embedding
 * Launches the expressinstall when the user doesn't have the required flash version installed
 *
 * This class also handles communication between the Flash movie and the JavaScript class
 *
 * Changelog
 * ---------
 *
 * Niels Nijens - Mon Nov 10 2008
 * --------------------------------
 * - Added Debug interface for the new Debug class
 *
 * Niels Nijens - Thu Nov 06 2008
 * --------------------------------
 * - Added deeplinking
 *
 * Niels Nijens - Mon May 19 2008
 * --------------------------------
 * - Fixed version retrieval for Flash Player 10 beta
 *
 * Niels Nijens - Tue Apr 01 2008
 * --------------------------------
 * - Changed required version from 9.0.47 to 9.0.115 due to some flash functionalities not working
 *
 * Niels Nijens - Mon Mar 10 2008
 * --------------------------------
 * - Changed required version from 9.0.28 to 9.0.47 due to components bug
 *
 * Niels Nijens - Fri Nov 16 2007
 * --------------------------------
 * - Changed SWFCall(); so it will run registered callback functions
 *
 * Niels Nijens - Mon Oct 22 2007
 * --------------------------------
 * - Added unload function
 * - Added timeout function to unload SWFObjects
 *
 * Niels Nijens - Mon Oct 22 2007
 * --------------------------------
 * - Made load() and loadSWFObject() the same, loadSWFObject() missed the expressinstall functionality
 * - Fixed bgcolor error
 *
 * Niels Nijens - Mon Oct 15 2007
 * --------------------------------
 * - Made SWFError(); able to call from Flash
 * - Added arguments to function calls from Flash (thanks to Giso)
 *
 * Niels Nijens - Fri Sep 21 2007
 * --------------------------------
 * - Added addFlashConfigVars(); for WMFlashConfig
 *
 * Niels Nijens - Mon Sep 17 2007
 * --------------------------------
 * - Added size check for the expressinstall
 * - Added addAlternateContentCallback();
 *
 * Niels Nijens - Fri Sep 14 2007
 * --------------------------------
 * - Added workaround (onunload) for IE video streaming bug in Flash Player
 * - Added default alternate content
 *
 * To do
 * ---------
 * -
 *
 * @since Thu Sep 13 2007
 * @author Niels Nijens (niels@connectholland.nl)
 **/
var SWFLoader = Class.create();
SWFLoader.prototype = {

	/**
	 * initialize
	 *
	 * Initialize a new SWFLoader
	 *
	 * @since initial
	 * @return void
	 **/
	initialize: function() {
		this.swfobjects = {};
		this.deeplinkListener = null;
		this.requiredVersion = {"major" : 9, "minor" : 0, "rev" : 115};
		this.expressInstallVersion = {"major" : 6, "minor" : 0, "rev" : 65};
		this.expressInstallSize = {"width" : 215, "height" : 138};
		window.SWFCall = this.SWFCall.bind(this);
		window.SWFError = this.SWFError.bind(this);
		window.SWFLogStart = this.SWFLogStart.bind(this);
		window.SWFLogEnd = this.SWFLogEnd.bind(this);
		window.SWFDebug = this.SWFDebug.bind(this);
		window.SWFDeeplink = this.SWFDeeplink.bind(this);
		this.initUnload();
	},

	/**
	 * load
	 *
	 * Adds a SWF file to the document when flash is available
	 * Otherwise adds alternate content to the element
	 *
	 * @since initial
	 * @param string element
	 * @param string swfname
	 * @param string swffile
	 * @param integer width
	 * @param integer height
	 * @param string bgcolor - transparent also works
	 * @param boolean wmode
	 * @param object swfvars
	 * @return void
	 **/
	load: function(element, swfname, swffile, width, height, bgcolor, swfvars) {
		swfobject = this.getSWFObject(element, swfname, swffile, width, height, bgcolor, swfvars);
		this.loadSWFObject(element, swfobject);
	},

	/**
	 * loadSWFObject
	 *
	 * Adds a SWF file to the document when flash is available
	 * Otherwise adds alternate content to the element
	 * (This function should be used for extends of SWFObject, for example MPlayerObject)
	 *
	 * @since initial
	 * @param string element
	 * @param object swfobject
	 * @return void
	 **/
	loadSWFObject: function(element, swfobject) {
		swfName = swfobject.getAttribute("swfname");
		swfWidth = swfobject.getAttribute("width");
		swfHeight = swfobject.getAttribute("height");
		
		this.checkDeeplinking(swfobject);
		
		if (this.checkPlayerVersion() ) {
			this.addSWFObject.bind(this).defer(element, swfobject);
		}
		else if (this.checkExpressInstallVersion() && this.checkExpressInstallSize(swfWidth, swfHeight) ) {
			installObject = this.getSWFObject(element, swfName, "/lib/swfloader/expressinstall.swf", swfWidth, swfHeight, "transparent", {"SWFContainer" : element, "MMredirectURL" : escape(window.location), "MMdoctitle" : document.title});
			this.addSWFObject.bind(this).defer(element, installObject);
		}
		else {
			this.addAlternateContent(element, swfWidth, swfHeight);
		}
	},

	/**
	 * checkPlayerVersion
	 *
	 * Returns true if the required flash version is available
	 *
	 * @since initial
	 * @return boolean
	 **/
	checkPlayerVersion: function() {
		installedVersion = this.getPlayerVersion();
		if (this.getVersionInt(installedVersion) >= this.getVersionInt(this.requiredVersion) ) {
			return true;
		}
		return false;
	},

	/**
	 * checkPlayerVersion
	 *
	 * Returns true if the flash version required for the express install is available
	 *
	 * @since initial
	 * @return boolean
	 **/
	checkExpressInstallVersion: function() {
		installedVersion = this.getPlayerVersion();
		if (this.getVersionInt(installedVersion) >= this.getVersionInt(this.expressInstallVersion) ) {
			return true;
		}
		return false;
	},

	/**
	 * checkExpressInstallSize
	 *
	 * Returns true if width and height are equal or greater than the required size for the express install
	 *
	 * @since initial
	 * @param integer width
	 * @param integer height
	 * @return boolean
	 **/
	checkExpressInstallSize: function(width, height) {
		if (width >= this.expressInstallSize.width && height >= this.expressInstallSize.height) {
			return true;
		}
		return false;
	},

	/**
	 * getPlayerVersion
	 *
	 * Returns the installed version of flash
	 *
	 * @since initial
	 * @return object
	 **/
	getPlayerVersion: function() {
		if (!this.installedVersion) {
			this.installedVersion = {"major" : 0, "minor" : 0, "rev" : 0};
			if(navigator.plugins && navigator.mimeTypes.length) {
				version = this.getPlayerVersionFF();
			} else {
				version = this.getPlayerVersionIE();
			}
			if (version) {
				var i = 0;
				for (property in this.installedVersion) {
					this.installedVersion[property] = version[i] != null ? parseInt(version[i]) : 0;
					i++;
				}
			}
		}
		return this.installedVersion;
	},

	/**
	 * getVersionInt
	 *
	 * Returns an Integer of the version
	 *
	 * @since Fri Sep 14 2007
	 * @param object version
	 * @return integer
	 **/
	getVersionInt: function(version) {
		return (version.major * Math.pow(1000, 2) ) + (version.minor * 1000) + version.rev;
	},

	/**
	 * getPlayerVersionFF
	 *
	 * Returns the Flash version for FireFox and other browsers
	 * Returns false if Flash isn't available
	 *
	 * @since initial
	 * @return mixed
	 **/
	getPlayerVersionFF: function() {
		var flashplayer = navigator.plugins["Shockwave Flash"];
		if(flashplayer && flashplayer.description) {
			var version = flashplayer.description.replace(/([a-zA-Z]|\s)+/, "").replace(/(\s+r|\s+b[0-9]+)/, ".").split(".");
			if (version[2] == "") {
				version[2] = "0";
			}
			
			return version;
		}
		return false;
	},

	/**
	 * getPlayerVersionIE
	 *
	 * Returns the Flash version for Internet Exploder browsers
	 * Returns false if Flash isn't available
	 *
	 * @since initial
	 * @return mixed
	 **/
	getPlayerVersionIE: function() {
		try {
			flashplayer = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
			flashplayer.AllowScriptAccess = "always";
			return flashplayer.GetVariable("$version").split(" ")[1].split(",");
		}
		catch(e) {
			return false;
		}
	},

	/**
	 * getSWFObject
	 *
	 * Adds a SWF to the swfobjects list and writes the SWFObject to the document
	 *
	 * @since Mon Oct 22 2007 (previously known as addSWF() )
	 * @param string element
	 * @param string swfname
	 * @param string swffile
	 * @param integer width
	 * @param integer height
	 * @param string bgcolor
	 * @param boolean wmode
	 * @param object swfvars
	 * @return SWFObject
	 **/
	getSWFObject: function(element, swfname, swffile, width, height, bgcolor, swfvars) {
		swfobject = new SWFObject(swfname, swffile, width, height, bgcolor.replace(/#/g, ""), swfvars);
		
		return swfobject;
	},

	/**
	 * addSWFObject
	 *
	 * Adds a SWF to the swfobjects list and writes the SWFObject to the document
	 *
	 * @since initial
	 * @param string element
	 * @param object swfobject
	 * @return void
	 **/
	addSWFObject: function(element, swfobject) {
		swfname = swfobject.getAttribute("swfname");
		this.swfobjects[swfname] = {};
		this.swfobjects[swfname]["element"] = $(element);
		this.swfobjects[swfname]["object"] = swfobject;
		this.swfobjects[swfname]["object"].write(element);
	},

	/**
	 * addAlternateContent
	 *
	 * Adds alternate content to the document where the Flash should have been
	 *
	 * @since initial
	 * @param string element
	 * @param integer width
	 * @param integer height
	 * @param string bgcolor
	 * @return void
	 **/
	addAlternateContent: function(element, width, height, bgcolor) {
		if ($(element) ) {
			Element.setStyle(element, {"width" : width, "height" : height, "text-align" : "center"});
			$(element).innerHTML = "<a href='http://www.adobe.com/go/flashplayer' target='_blank'><img src='/lib/swfloader/images/getflashplayer.gif' border='0'/></a>";
			return true;
		}
		return false;
	},

	/**
	 * addAlternateContentCallback
	 *
	 * Callback function for when the autoupdate fails or has been canceled by the user
	 *
	 * @since Mon Sep 17 2007
	 * @param object swf
	 * @return void
	 **/
	addAlternateContentCallback: function(element, width, height) {
		this.addAlternateContent(element, width, height);
	},
	
	/**
	 * unload
	 *
	 * Unloads a SWFObject from the document
	 *
	 * @since Wed Oct 31 2007
	 * @param string swfname
	 * @return void
	 **/
	unload: function(swfname) {
		this.processUnload.bind(this).defer(swfname);
	},
	
	/**
	 * processUnload
	 *
	 * Unloads a SWFObject from the document
	 *
	 * @since Wed Oct 31 2007
	 * @param string swfname
	 * @return void
	 **/
	processUnload: function(swfname) {
		divElement = this.getDivByName(swfname);
		if (divElement) {
			divElement.removeChild(divElement.getElementsByTagName("embed")[0]);
			this.swfobjects[swfname] = undefined;
		}
	},
	
	/**
	 * getSWFObjectByName
	 *
	 * Returns the SWFObject with name swfname if it exists
	 * Otherwise returns false
	 *
	 * @since initial
	 * @param string swfname
	 * @return mixed
	 **/
	getSWFObjectByName: function(swfname) {
		if (this.swfobjects[swfname]) {
			return this.swfobjects[swfname]["object"];
		}
		else {
			return false;
		}
	},

	/**
	 * getDivByName
	 *
	 * Returns the div element with name swfname if it exists
	 * Otherwise returns false
	 *
	 * @since initial
	 * @param string swfname
	 * @return mixed
	 **/
	getDivByName: function(swfname) {
		if (this.swfobjects[swfname]) {
			return this.swfobjects[swfname]["element"];
		}
		else {
			return false;
		}
	},

	/**
	 * SWFCall
	 *
	 * Javascript interface for calls from SWFs
	 *
	 * @since Thu Oct 11 2007
	 * @return mixed
	 **/
	SWFCall: function() {
		flashVars = $A(arguments);
		
		swfname = flashVars.shift();
		methodName = flashVars.shift();
		methodVars = flashVars;
		
		swfobject = this.getSWFObjectByName(swfname);
		if (swfobject) {
			callbackFunction = swfobject.getCallbackByMethod(methodName);
			if (typeof(callbackFunction["callback"]) == "function") {
				return callbackFunction["callback"].apply(swfobject, methodVars);
			}
			else if (typeof(swfobject[methodName]) == "function") {
				return swfobject[methodName].apply(swfobject, methodVars);
			}
			else {
				this.SWFError("Method " + methodName + " doesn't exist in object " + swfname + ".");
			}
		}
		else {
			this.SWFError("Object with name " + swfname + " doesn't exist.");
		}
	},
	
	/**
	 * SWFLogStart
	 *
	 * Starts time log
	 * (For developers only)
	 *
	 * @since Thu Mar 20 2007
	 * @param object debugInfo
	 * @return void
	 **/
	SWFLogStart: function(debugInfo) {
		if (console) {
			console.log("loading (" + debugInfo.id + "): " + debugInfo.url);
			console.time("response time (" + debugInfo.id + ")");
		}
	},
	
	/**
	 * SWFLogEnd
	 *
	 * Ends time log
	 * (For developers only)
	 *
	 * @since Thu Mar 20 2007
	 * @param object debugInfo
	 * @return void
	 **/
	SWFLogEnd: function(debugInfo) {
		if (console) {
			console.timeEnd("response time (" + debugInfo.id + ")");
			console.log(debugInfo);
		}
	},
	
	/**
	 * SWFError
	 *
	 * Prints an error in the console
	 * (For developers only)
	 *
	 * @since Thu Oct 11 2007
	 * @param string message
	 * @param boolean log
	 * @return void
	 **/
	SWFError: function(message, log) {
		if ( $("consolewindow") ) {
			if (!log) {
				message = "ERROR: " + message;
			}
			
			$("consolewindow").innerHTML += message + "<br/>";
			
			return;
		}
		if (console) {
			if (log) {
				console.log(message);
			}
			else {
				console.error("ERROR: " + message);
			}
			
			return;
		}
	},
	
	/**
	 * SWFDebug
	 *
	 * Displays Debug messages from the SWF
	 *
	 * @since Mon Nov 10 2008
	 * @return void
	 **/
	SWFDebug: function(level) {
		var args = $A(arguments);
		args.shift();
		if (typeof(console) != "undefined" && typeof(console.error) == "function" && typeof(console.warn) == "function" && typeof(console.log) == "function") {
			switch (level) {
				case 1:
					console.error.apply(console, args);
					break;
				case 2:
					console.warn.apply(console, args);
					break;
				default:
					console.log.apply(console, args);
					break;
			}
		}
		else {
			throw Error(args);
		}
	},
	
	/**
	 * checkDeeplink
	 *
	 * Checks if deeplinking should be enabled for swfobject
	 *
	 * @since Thu Nov 06 2008
	 * @param SWFObject swfobject
	 * @return void
	 **/
	checkDeeplinking: function(swfobject) {
		var swfvars = swfobject.getVariables();
		if (swfvars["deeplinking"] == true) {
			swfobject.deeplinking = true;
			if (this.deeplinkListener == null) {
				this.deeplinkListener = new PeriodicalExecuter(this.broadcastDeeplink.bind(this), 0.05);
			}
		}
	},
	
	/**
	 * SWFDeeplink
	 *
	 * Deeplink functionality for flash websites
	 *
	 * @since Thu Nov 06 2008
	 * @param string link
	 * @param string title
	 * @return void
	 **/
	SWFDeeplink: function(link, title) {
		document.location.hash = "#" + link;
		if (title != "") {
			document.title = title;
		}
	},
	
	/**
	 * broadcastDeeplink
	 *
	 * Deeplink listener. Broadcasts the deeplink to the SWFObject's that have deeplinking enabled
	 *
	 * @since Thu Nov 06 2008
	 * @param string link
	 * @param string title
	 * @return void
	 **/
	broadcastDeeplink: function() {
		var deeplink = document.location.hash.replace(/#/g, "");
		if (deeplink != "" && this.broadcastedDeeplink != deeplink) {
			this.broadcastedDeeplink = deeplink;
			
			for (var swfname in this.swfobjects) {
				if (this.swfobjects[swfname]["object"].deeplinking) {
					this.swfobjects[swfname]["object"].setDeeplink(deeplink);
				}
			}
		}
	},
	
	/**
	 * initUnload
	 *
	 * Adds an onunload event to the document
	 * This is needed for an IE video streaming bug in Flash Player
	 *
	 * See: http://blog.deconcept.com/2006/07/28/swfobject-143-released/
	 *
	 * @since Fri Sep 14 2007
	 * @return void
	 **/
	initUnload: function() {
		if (!window.opera && document.all) {
			__flash_unloadHandler = function(){};
			__flash_savedUnloadHandler = function(){};
			
			//window.attachEvent("onunload", this.cleanupSWFObjects);
		}
	},

	/**
	 * cleanupSWFObjects
	 *
	 * Cleans up all the object elements
	 *
	 * @since Fri Sep 14 2007
	 * @return void
	 **/
	cleanupSWFObjects: function() {
		if (window.opera || !document.all) {
			return;
		}

		var objects = document.getElementsByTagName("object");
		for (i = 0; i < objects.length; i++) {
			objects[i].style.display = 'none';
			for (var x in objects[i]) {
				if (typeof objects[i][x] == 'function') {
					objects[i][x] = function() { };
				}
			}
		}
	}
}

/**
 * SWFObject
 *
 * Changelog
 * ---------
 *
 * Niels Nijens - Fri Nov 16 2007
 * -------------------------------
 * - Added registerCallback(); and getCallbackByMethod(); to override function calls from flash
 *
 * Niels Nijens - Mon Nov 05 2007
 * -------------------------------
 * - Added getContainer(); to get the SWFObject's parent div
 *
 * Niels Nijens - Mon Oct 22 2007
 * -------------------------------
 * - Added methodExists(); for flash function calls
 *
 * Niels Nijens - Tue Sep 18 2007
 * -------------------------------
 * - Added getColor and getWmode to combine them into one variable bgcolor
 *
 * @since Thu Sep 13 2007
 * @author Niels Nijens (niels@connectholland.nl)
 **/
var SWFObject = Class.create();
SWFObject.prototype = {

	/**
	 * initialize
	 *
	 * Initialize a new SWFObject
	 *
	 * @since initial
	 * @param string swfname
	 * @param string swffile
	 * @param integer width
	 * @param integer height
	 * @param string bgcolor
	 * @param boolean wmode
	 * @param object swfvars
	 * @return void
	 **/
	initialize: function(swfname, swffile, width, height, bgcolor, swfvars) {
		this.deeplinking = false;
		this.registeredCallbacks = {};
		
		this.initAttributes({"swffile" : swffile, "swfname" : swfname, "width" : width, "height" : height});
		this.initParams({"quality" : "high", "menu" : "false", "scale" : "noscale", "AllowScriptAccess" : "always", "bgcolor" : this.getColor(bgcolor), "wmode" : this.getWMode(bgcolor)});
		this.initVariables(swfvars);
		this.addFlashConfigVars();
	},

	/**
	 * initAttributes
	 *
	 * Creates and Fills the attributes object with variables
	 * (Is used for basic variables like filepath and the name of the SWF)
	 *
	 * @since initial
	 * @param object attributes
	 * @return void
	 **/
	initAttributes: function(attributes) {
		this.attributes = {};

		for(property in attributes) {
			if (attributes[property]) {
				this.setAttribute(property, attributes[property]);
			}
		}
	},

	/**
	 * initParams
	 *
	 * Creates and Fills the params object with variables
	 * (Is used for basic variables like bgcolor and wmode)
	 *
	 * @since initial
	 * @param object params
	 * @return void
	 **/
	initParams: function(params) {
		this.params = {};

		for(property in params) {
			if (params[property]) {
				this.addParam(property, params[property]);
			}
		}
	},

	/**
	 * initVariables
	 *
	 * Creates and Fills the variables object with variables
	 * (Is used for variables to be send to flash)
	 *
	 * @since initial
	 * @param object variables
	 * @return void
	 **/
	initVariables: function(variables) {
		this.variables = {};

		for(property in variables) {
			if (variables[property]) {
				this.addVariable(property, variables[property]);
			}
		}
	},

	/**
	 * addFlashConfigVars
	 *
	 * Adds the variables required for WMFlashConfig
	 *
	 * @since Fri Sep 21 2007
	 * @return void
	 **/
	addFlashConfigVars: function() {
		this.addVariable("swfname", this.getAttribute("swffile").substr(this.getAttribute("swffile").lastIndexOf("/") + 1, this.getAttribute("swffile").lastIndexOf(".swf") - (this.getAttribute("swffile").lastIndexOf("/") + 1) ) );
		this.addVariable("swfpath", this.getAttribute("swffile").substr(0, this.getAttribute("swffile").lastIndexOf("/") + 1) );
	},

	/**
	 * getColor
	 *
	 * Returns the bgcolor
	 *
	 * @since Tue Sep 18 2007
	 * @param string bgcolor
	 * @return mixed
	 **/
	getColor: function(bgcolor) {
		if (bgcolor == "transparent") {
			return false;
		}
		return bgcolor;
	},

	/**
	 * getWMode
	 *
	 * Returns the wmode
	 *
	 * @since Tue Sep 18 2007
	 * @param string bgcolor
	 * @return mixed
	 **/
	getWMode: function(bgcolor) {
		if (bgcolor != "transparent") {
			return false;
		}
		return bgcolor;
	},

	/**
	 * setAttribute
	 *
	 * Adds a variable with name and value to the attributes object
	 *
	 * @since initial
	 * @param string name
	 * @param mixed value
	 * @return void
	 **/
	setAttribute: function(name, value){
		this.attributes[name] = value;
	},

	/**
	 * getAttribute
	 *
	 * Returns the value of an attribute by name
	 *
	 * @since initial
	 * @param string name
	 * @return mixed
	 **/
	getAttribute: function(name) {
		return this.attributes[name];
	},

	/**
	 * addParam
	 *
	 * Adds a variable with name and value to the params object
	 *
	 * @since initial
	 * @param string name
	 * @param mixed value
	 * @return void
	 **/
	addParam: function(name, value) {
		this.params[name] = value;
	},

	/**
	 * getParams
	 *
	 * Returns the params object
	 *
	 * @since initial
	 * @return object
	 **/
	getParams: function() {
		return this.params;
	},

	/**
	 * addVariable
	 *
	 * Adds a variable with name and value to the variables object
	 *
	 * @since initial
	 * @param string name
	 * @param mixed value
	 * @return void
	 **/
	addVariable: function(name, value){
		this.variables[name] = value;
	},

	/**
	 * getVariables
	 *
	 * Returns the variables object
	 *
	 * @since initial
	 * @return object
	 **/
	getVariables: function() {
		return this.variables;
	},

	/**
	 * getVariablePairs
	 *
	 * Returns the variables object as an array with strings
	 *
	 * @since initial
	 * @return array
	 **/
	getVariablePairs: function(){
		variables = this.getVariables();
		variablePairs = new Array();
		for(property in variables){
			variablePairs.push(property + "=" + variables[property]);
		}
		return variablePairs;
	},

	/**
	 * getSWFHTML
	 *
	 * Returns the HTML for the SWF
	 *
	 * @since initial
	 * @return string
	 **/
	getSWFHTML: function() {
		if (navigator.plugins && navigator.mimeTypes && navigator.mimeTypes.length) {
			this.addVariable("MMplayerType", "PlugIn");
			SWFNode = this.getSWFHTMLEmbed();
		} else {
			this.addVariable("MMplayerType", "ActiveX");
			SWFNode = this.getSWFHTMLObject();
		}
		return SWFNode;
	},

	/**
	 * getSWFHTMLEmbed
	 *
	 * Returns the HTML <embed> for the SWF
	 * (FireFox browsers)
	 *
	 * @since initial
	 * @return string
	 **/
	getSWFHTMLEmbed: function() {
		SWFNode = "<embed type='application/x-shockwave-flash' src='" + this.getAttribute("swffile") + "'";
		SWFNode += " width='" + this.getAttribute("width") + "' height='" + this.getAttribute("height") + "'";
		SWFNode += " id='" + this.getAttribute("swfname") + "' name='" + this.getAttribute("swfname") + "' ";
		SWFNode += this.getParamHTML(true);
		SWFNode += this.getVariableHTML(true);
		SWFNode += "/>";

		return SWFNode;
	},

	/**
	 * getSWFHTMLObject
	 *
	 * Returns the HTML <object> for the SWF
	 * (Internet Exploder browsers)
	 *
	 * @since initial
	 * @return string
	 **/
	getSWFHTMLObject: function() {
		SWFNode = "<object id='" + this.getAttribute("swfname") + "' classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'";
		SWFNode += " width='" + this.getAttribute("width") + "' height='" + this.getAttribute("height") + "'>";
		SWFNode += "<param name='movie' value='" + this.getAttribute("swffile") + "'/>";
		SWFNode += this.getParamHTML(false);
		SWFNode += this.getVariableHTML(false);
		SWFNode += "</object>";

		return SWFNode;
	},

	/**
	 * getParamHTML
	 *
	 * Returns the params as HTML string
	 *
	 * @since initial
	 * @param boolean embed
	 * @return string
	 **/
	getParamHTML: function(embed) {
		paramHTML = "";

		params = this.getParams();
		for(var property in params) {
			if (embed) {
				paramHTML += property + "='" + params[property] + "' ";
			}
			else {
				paramHTML += '<param name="'+ property +'" value="'+ params[property] +'" />';
			}
		}

		return paramHTML;
	},

	/**
	 * getVariableHTML
	 *
	 * Returns the variables as HTML string
	 *
	 * @since initial
	 * @param boolean embed
	 * @return string
	 **/
	getVariableHTML: function(embed) {
		variableHTML = "";

		variablestring = this.getVariablePairs().join("&");
		if (variablestring.length > 0) {
			if (embed) {
				variableHTML += "flashvars='" + variablestring + "'";
			}
			else {
				variableHTML += "<param name='flashvars' value='" + variablestring + "'/>";
			}
		}

		return variableHTML;
	},

	/**
	 * write
	 *
	 * Writes the SWF HTML to the document
	 * Returns true on success
	 *
	 * @since initial
	 * @return boolean
	 **/
	write: function(element) {
		if ($(element) ) {
			$(element).innerHTML = this.getSWFHTML();
			return true;
		}
		return false;
	},

	/**
	 * methodExists
	 *
	 * Returns if the method exists on the flash element
	 *
	 * @since Mon Oct 22 2007
	 * @param string methodName
	 * @return boolean
	 **/
	methodExists: function(methodName) {
		element = $(this.getAttribute("swfname") );
		if (element) {
			if (typeof(element[methodName]) == "function") {
				return true;
			}
		}
		return false;
	},
	
	/**
	 * registerCallback
	 *
	 * Registers a callback function to override the default function
	 *
	 * @since Fri Nov 16 2007
	 * @param string methodName
	 * @param function callbackFunction
	 * @return void
	 **/
	registerCallback: function(methodName, callbackFunction) {
		this.registeredCallbacks[methodName] = {};
		this.registeredCallbacks[methodName]["callback"] = callbackFunction;
	},
	
	/**
	 * getCallbackByMethod
	 *
	 * Returns the registered callback function
	 *
	 * @since Fri Nov 16 2007
	 * @param string methodName
	 * @return object
	 **/
	getCallbackByMethod: function(methodName) {
		if (this.registeredCallbacks[methodName] != undefined) {
			return this.registeredCallbacks[methodName];
		}
		return false;
	},
	
	/**
	 * getContainer
	 *
	 * Returns the parent (container) div of the SWFObject
	 *
	 * @since Mon Nov 05 2007
	 * @return mixed
	 **/
	getContainer: function() {
		return swfloader.getDivByName(this.getAttribute("swfname") );
	},
	
	/**
	 * setDeeplink
	 *
	 * Sets the deeplink
	 *
	 * @since Thu Nov 06 2008
	 * @param string link
	 * @return void
	 **/
	setDeeplink: function(link) {
		if (this.methodExists("setDeeplink") ) {
			$(this.getAttribute("swfname") ).setDeeplink(link);
		}
		else {
			this.setDeeplink.bind(this).delay(0.05, link);
		}
	}
}

var swfloader = new SWFLoader();

/*  Copyright Mihai Bazon, 2002-2005  |  www.bazon.net/mishoo
 * -----------------------------------------------------------
 *
 * The DHTML Calendar, version 1.0 "It is happening again"
 *
 * Details and latest version at:
 * www.dynarch.com/projects/calendar
 *
 * This script is developed by Dynarch.com.  Visit us at www.dynarch.com.
 *
 * This script is distributed under the GNU Lesser General Public License.
 * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
 */

// $Id: calendar.js,v 1.51 2005/03/07 16:44:31 mishoo Exp $

/** The Calendar object constructor. */
Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) {
	// member variables
	this.activeDiv = null;
	this.currentDateEl = null;
	this.getDateStatus = null;
	this.getDateToolTip = null;
	this.getDateText = null;
	this.timeout = null;
	this.onSelected = onSelected || null;
	this.onClose = onClose || null;
	this.dragging = false;
	this.hidden = false;
	this.minYear = 1970;
	this.maxYear = 2050;
	this.dateFormat = Calendar._TT["DEF_DATE_FORMAT"];
	this.ttDateFormat = Calendar._TT["TT_DATE_FORMAT"];
	this.isPopup = true;
	this.weekNumbers = true;
	this.firstDayOfWeek = typeof firstDayOfWeek == "number" ? firstDayOfWeek : Calendar._FD; // 0 for Sunday, 1 for Monday, etc.
	this.showsOtherMonths = false;
	this.dateStr = dateStr;
	this.ar_days = null;
	this.showsDate = true;
	this.showsTime = false;
	this.time24 = true;
	this.yearStep = 2;
	this.hiliteToday = true;
	this.multiple = null;
	// HTML elements
	this.table = null;
	this.element = null;
	this.tbody = null;
	this.firstdayname = null;
	// Combo boxes
	this.monthsCombo = null;
	this.yearsCombo = null;
	this.hilitedMonth = null;
	this.activeMonth = null;
	this.hilitedYear = null;
	this.activeYear = null;
	// Information
	this.dateClicked = false;

	// one-time initializations
	if (typeof Calendar._SDN == "undefined") {
		// table of short day names
		if (typeof Calendar._SDN_len == "undefined")
			Calendar._SDN_len = 3;
		var ar = new Array();
		for (var i = 8; i > 0;) {
			ar[--i] = Calendar._DN[i].substr(0, Calendar._SDN_len);
		}
		Calendar._SDN = ar;
		// table of short month names
		if (typeof Calendar._SMN_len == "undefined")
			Calendar._SMN_len = 3;
		ar = new Array();
		for (var i = 12; i > 0;) {
			ar[--i] = Calendar._MN[i].substr(0, Calendar._SMN_len);
		}
		Calendar._SMN = ar;
	}
};

// ** constants

/// "static", needed for event handlers.
Calendar._C = null;

/// detect a special case of "web browser"
Calendar.is_ie = ( /msie/i.test(navigator.userAgent) &&
		   !/opera/i.test(navigator.userAgent) );

Calendar.is_ie5 = ( Calendar.is_ie && /msie 5\.0/i.test(navigator.userAgent) );

/// detect Opera browser
Calendar.is_opera = /opera/i.test(navigator.userAgent);

/// detect KHTML-based browsers
Calendar.is_khtml = /Konqueror|Safari|KHTML/i.test(navigator.userAgent);

// BEGIN: UTILITY FUNCTIONS; beware that these might be moved into a separate
//		library, at some point.

Calendar.getAbsolutePos = function(el) {
	var SL = 0, ST = 0;
	var is_div = /^div$/i.test(el.tagName);
	if (is_div && el.scrollLeft)
		SL = el.scrollLeft;
	if (is_div && el.scrollTop)
		ST = el.scrollTop;
	var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST };
	if (el.offsetParent) {
		var tmp = this.getAbsolutePos(el.offsetParent);
		r.x += tmp.x;
		r.y += tmp.y;
	}
	return r;
};

Calendar.isRelated = function (el, evt) {
	var related = evt.relatedTarget;
	if (!related) {
		var type = evt.type;
		if (type == "mouseover") {
			related = evt.fromElement;
		} else if (type == "mouseout") {
			related = evt.toElement;
		}
	}
	while (related) {
		if (related == el) {
			return true;
		}
		related = related.parentNode;
	}
	return false;
};

Calendar.removeClass = function(el, className) {
	if (!(el && el.className)) {
		return;
	}
	var cls = el.className.split(" ");
	var ar = new Array();
	for (var i = cls.length; i > 0;) {
		if (cls[--i] != className) {
			ar[ar.length] = cls[i];
		}
	}
	el.className = ar.join(" ");
};

Calendar.addClass = function(el, className) {
	Calendar.removeClass(el, className);
	el.className += " " + className;
};

// FIXME: the following 2 functions totally suck, are useless and should be replaced immediately.
Calendar.getElement = function(ev) {
	var f = Calendar.is_ie ? window.event.srcElement : ev.currentTarget;
	while (f.nodeType != 1 || /^div$/i.test(f.tagName))
		f = f.parentNode;
	return f;
};

Calendar.getTargetElement = function(ev) {
	var f = Calendar.is_ie ? window.event.srcElement : ev.target;
	while (f.nodeType != 1)
		f = f.parentNode;
	return f;
};

Calendar.stopEvent = function(ev) {
	ev || (ev = window.event);
	if (Calendar.is_ie) {
		ev.cancelBubble = true;
		ev.returnValue = false;
	} else {
		ev.preventDefault();
		ev.stopPropagation();
	}
	return false;
};

Calendar.addEvent = function(el, evname, func) {
	if (el.attachEvent) { // IE
		el.attachEvent("on" + evname, func);
	} else if (el.addEventListener) { // Gecko / W3C
		el.addEventListener(evname, func, true);
	} else {
		el["on" + evname] = func;
	}
};

Calendar.removeEvent = function(el, evname, func) {
	if (el.detachEvent) { // IE
		el.detachEvent("on" + evname, func);
	} else if (el.removeEventListener) { // Gecko / W3C
		el.removeEventListener(evname, func, true);
	} else {
		el["on" + evname] = null;
	}
};

Calendar.createElement = function(type, parent) {
	var el = null;
	if (document.createElementNS) {
		// use the XHTML namespace; IE won't normally get here unless
		// _they_ "fix" the DOM2 implementation.
		el = document.createElementNS("http://www.w3.org/1999/xhtml", type);
	} else {
		el = document.createElement(type);
	}
	if (typeof parent != "undefined") {
		parent.appendChild(el);
	}
	return el;
};

// END: UTILITY FUNCTIONS

// BEGIN: CALENDAR STATIC FUNCTIONS

/** Internal -- adds a set of events to make some element behave like a button. */
Calendar._add_evs = function(el) {
	with (Calendar) {
		addEvent(el, "mouseover", dayMouseOver);
		addEvent(el, "mousedown", dayMouseDown);
		addEvent(el, "mouseout", dayMouseOut);
		if (is_ie) {
			addEvent(el, "dblclick", dayMouseDblClick);
			el.setAttribute("unselectable", true);
		}
	}
};

Calendar.findMonth = function(el) {
	if (typeof el.month != "undefined") {
		return el;
	} else if (typeof el.parentNode.month != "undefined") {
		return el.parentNode;
	}
	return null;
};

Calendar.findYear = function(el) {
	if (typeof el.year != "undefined") {
		return el;
	} else if (typeof el.parentNode.year != "undefined") {
		return el.parentNode;
	}
	return null;
};

Calendar.showMonthsCombo = function () {
	var cal = Calendar._C;
	if (!cal) {
		return false;
	}
	var cal = cal;
	var cd = cal.activeDiv;
	var mc = cal.monthsCombo;
	if (cal.hilitedMonth) {
		Calendar.removeClass(cal.hilitedMonth, "hilite");
	}
	if (cal.activeMonth) {
		Calendar.removeClass(cal.activeMonth, "active");
	}
	var mon = cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];
	Calendar.addClass(mon, "active");
	cal.activeMonth = mon;
	var s = mc.style;
	s.display = "block";
	if (cd.navtype < 0)
		s.left = cd.offsetLeft + "px";
	else {
		var mcw = mc.offsetWidth;
		if (typeof mcw == "undefined")
			// Konqueror brain-dead techniques
			mcw = 50;
		s.left = (cd.offsetLeft + cd.offsetWidth - mcw) + "px";
	}
	s.top = (cd.offsetTop + cd.offsetHeight) + "px";
};

Calendar.showYearsCombo = function (fwd) {
	var cal = Calendar._C;
	if (!cal) {
		return false;
	}
	var cal = cal;
	var cd = cal.activeDiv;
	var yc = cal.yearsCombo;
	if (cal.hilitedYear) {
		Calendar.removeClass(cal.hilitedYear, "hilite");
	}
	if (cal.activeYear) {
		Calendar.removeClass(cal.activeYear, "active");
	}
	cal.activeYear = null;
	var Y = cal.date.getFullYear() + (fwd ? 1 : -1);
	var yr = yc.firstChild;
	var show = false;
	for (var i = 12; i > 0; --i) {
		if (Y >= cal.minYear && Y <= cal.maxYear) {
			yr.innerHTML = Y;
			yr.year = Y;
			yr.style.display = "block";
			show = true;
		} else {
			yr.style.display = "none";
		}
		yr = yr.nextSibling;
		Y += fwd ? cal.yearStep : -cal.yearStep;
	}
	if (show) {
		var s = yc.style;
		s.display = "block";
		if (cd.navtype < 0)
			s.left = cd.offsetLeft + "px";
		else {
			var ycw = yc.offsetWidth;
			if (typeof ycw == "undefined")
				// Konqueror brain-dead techniques
				ycw = 50;
			s.left = (cd.offsetLeft + cd.offsetWidth - ycw) + "px";
		}
		s.top = (cd.offsetTop + cd.offsetHeight) + "px";
	}
};

// event handlers

Calendar.tableMouseUp = function(ev) {
	var cal = Calendar._C;
	if (!cal) {
		return false;
	}
	if (cal.timeout) {
		clearTimeout(cal.timeout);
	}
	var el = cal.activeDiv;
	if (!el) {
		return false;
	}
	var target = Calendar.getTargetElement(ev);
	ev || (ev = window.event);
	Calendar.removeClass(el, "active");
	if (target == el || target.parentNode == el) {
		Calendar.cellClick(el, ev);
	}
	var mon = Calendar.findMonth(target);
	var date = null;
	if (mon) {
		date = new Date(cal.date);
		if (mon.month != date.getMonth()) {
			date.setMonth(mon.month);
			cal.setDate(date);
			cal.dateClicked = false;
			cal.callHandler();
		}
	} else {
		var year = Calendar.findYear(target);
		if (year) {
			date = new Date(cal.date);
			if (year.year != date.getFullYear()) {
				date.setFullYear(year.year);
				cal.setDate(date);
				cal.dateClicked = false;
				cal.callHandler();
			}
		}
	}
	with (Calendar) {
		removeEvent(document, "mouseup", tableMouseUp);
		removeEvent(document, "mouseover", tableMouseOver);
		removeEvent(document, "mousemove", tableMouseOver);
		cal._hideCombos();
		_C = null;
		return stopEvent(ev);
	}
};

Calendar.tableMouseOver = function (ev) {
	var cal = Calendar._C;
	if (!cal) {
		return;
	}
	var el = cal.activeDiv;
	var target = Calendar.getTargetElement(ev);
	if (target == el || target.parentNode == el) {
		Calendar.addClass(el, "hilite active");
		Calendar.addClass(el.parentNode, "rowhilite");
	} else {
		if (typeof el.navtype == "undefined" || (el.navtype != 50 && (el.navtype == 0 || Math.abs(el.navtype) > 2)))
			Calendar.removeClass(el, "active");
		Calendar.removeClass(el, "hilite");
		Calendar.removeClass(el.parentNode, "rowhilite");
	}
	ev || (ev = window.event);
	if (el.navtype == 50 && target != el) {
		var pos = Calendar.getAbsolutePos(el);
		var w = el.offsetWidth;
		var x = ev.clientX;
		var dx;
		var decrease = true;
		if (x > pos.x + w) {
			dx = x - pos.x - w;
			decrease = false;
		} else
			dx = pos.x - x;

		if (dx < 0) dx = 0;
		var range = el._range;
		var current = el._current;
		var count = Math.floor(dx / 10) % range.length;
		for (var i = range.length; --i >= 0;)
			if (range[i] == current)
				break;
		while (count-- > 0)
			if (decrease) {
				if (--i < 0)
					i = range.length - 1;
			} else if ( ++i >= range.length )
				i = 0;
		var newval = range[i];
		el.innerHTML = newval;

		cal.onUpdateTime();
	}
	var mon = Calendar.findMonth(target);
	if (mon) {
		if (mon.month != cal.date.getMonth()) {
			if (cal.hilitedMonth) {
				Calendar.removeClass(cal.hilitedMonth, "hilite");
			}
			Calendar.addClass(mon, "hilite");
			cal.hilitedMonth = mon;
		} else if (cal.hilitedMonth) {
			Calendar.removeClass(cal.hilitedMonth, "hilite");
		}
	} else {
		if (cal.hilitedMonth) {
			Calendar.removeClass(cal.hilitedMonth, "hilite");
		}
		var year = Calendar.findYear(target);
		if (year) {
			if (year.year != cal.date.getFullYear()) {
				if (cal.hilitedYear) {
					Calendar.removeClass(cal.hilitedYear, "hilite");
				}
				Calendar.addClass(year, "hilite");
				cal.hilitedYear = year;
			} else if (cal.hilitedYear) {
				Calendar.removeClass(cal.hilitedYear, "hilite");
			}
		} else if (cal.hilitedYear) {
			Calendar.removeClass(cal.hilitedYear, "hilite");
		}
	}
	return Calendar.stopEvent(ev);
};

Calendar.tableMouseDown = function (ev) {
	if (Calendar.getTargetElement(ev) == Calendar.getElement(ev)) {
		return Calendar.stopEvent(ev);
	}
};

Calendar.calDragIt = function (ev) {
	var cal = Calendar._C;
	if (!(cal && cal.dragging)) {
		return false;
	}
	var posX;
	var posY;
	if (Calendar.is_ie) {
		posY = window.event.clientY + document.body.scrollTop;
		posX = window.event.clientX + document.body.scrollLeft;
	} else {
		posX = ev.pageX;
		posY = ev.pageY;
	}
	cal.hideShowCovered();
	var st = cal.element.style;
	st.left = (posX - cal.xOffs) + "px";
	st.top = (posY - cal.yOffs) + "px";
	return Calendar.stopEvent(ev);
};

Calendar.calDragEnd = function (ev) {
	var cal = Calendar._C;
	if (!cal) {
		return false;
	}
	cal.dragging = false;
	with (Calendar) {
		removeEvent(document, "mousemove", calDragIt);
		removeEvent(document, "mouseup", calDragEnd);
		tableMouseUp(ev);
	}
	cal.hideShowCovered();
};

Calendar.dayMouseDown = function(ev) {
	var el = Calendar.getElement(ev);
	if (el.disabled) {
		return false;
	}
	var cal = el.calendar;
	cal.activeDiv = el;
	Calendar._C = cal;
	if (el.navtype != 300) with (Calendar) {
		if (el.navtype == 50) {
			el._current = el.innerHTML;
			addEvent(document, "mousemove", tableMouseOver);
		} else
			addEvent(document, Calendar.is_ie5 ? "mousemove" : "mouseover", tableMouseOver);
		addClass(el, "hilite active");
		addEvent(document, "mouseup", tableMouseUp);
	} else if (cal.isPopup) {
		cal._dragStart(ev);
	}
	if (el.navtype == -1 || el.navtype == 1) {
		if (cal.timeout) clearTimeout(cal.timeout);
		cal.timeout = setTimeout("Calendar.showMonthsCombo()", 250);
	} else if (el.navtype == -2 || el.navtype == 2) {
		if (cal.timeout) clearTimeout(cal.timeout);
		cal.timeout = setTimeout((el.navtype > 0) ? "Calendar.showYearsCombo(true)" : "Calendar.showYearsCombo(false)", 250);
	} else {
		cal.timeout = null;
	}
	return Calendar.stopEvent(ev);
};

Calendar.dayMouseDblClick = function(ev) {
	Calendar.cellClick(Calendar.getElement(ev), ev || window.event);
	if (Calendar.is_ie) {
		document.selection.empty();
	}
};

Calendar.dayMouseOver = function(ev) {
	var el = Calendar.getElement(ev);
	if (Calendar.isRelated(el, ev) || Calendar._C || el.disabled) {
		return false;
	}
	if (el.ttip) {
		if (el.ttip.substr(0, 1) == "_") {
			el.ttip = el.caldate.print(el.calendar.ttDateFormat) + el.ttip.substr(1);
		}
		el.calendar.tooltips.innerHTML = el.ttip;
	}
	if (el.navtype != 300) {
		Calendar.addClass(el, "hilite");
		if (el.caldate) {
			Calendar.addClass(el.parentNode, "rowhilite");
		}
	}
	return Calendar.stopEvent(ev);
};

Calendar.dayMouseOut = function(ev) {
	with (Calendar) {
		var el = getElement(ev);
		if (isRelated(el, ev) || _C || el.disabled)
			return false;
		removeClass(el, "hilite");
		if (el.caldate)
			removeClass(el.parentNode, "rowhilite");
		if (el.calendar) {
			if (this.showsTime && !this.showsDate) {
				el.calendar.tooltips.innerHTML = _TT["SEL_TIME"];
			}
			else {
				el.calendar.tooltips.innerHTML = _TT["SEL_DATE"];
				
			}
		}
		return stopEvent(ev);
	}
};

/**
 *  A generic "click" handler :) handles all types of buttons defined in this
 *  calendar.
 */
Calendar.cellClick = function(el, ev) {
	var cal = el.calendar;
	var closing = false;
	var newdate = false;
	var date = null;
	if (typeof el.navtype == "undefined") {
		if (cal.currentDateEl) {
			Calendar.removeClass(cal.currentDateEl, "selected");
			Calendar.addClass(el, "selected");
			closing = (cal.currentDateEl == el);
			if (!closing) {
				cal.currentDateEl = el;
			}
		}
		cal.date.setDateOnly(el.caldate);
		date = cal.date;
		var other_month = !(cal.dateClicked = !el.otherMonth);
		if (!other_month && !cal.currentDateEl)
			cal._toggleMultipleDate(new Date(date));
		else
			newdate = !el.disabled;
		// a date was clicked
		if (other_month)
			cal._init(cal.firstDayOfWeek, date);
	} else {
		if (el.navtype == 200) {
			Calendar.removeClass(el, "hilite");
			cal.callCloseHandler();
			return;
		}
		date = new Date(cal.date);
		if (el.navtype == 0)
			date.setDateOnly(new Date()); // TODAY
		// unless "today" was clicked, we assume no date was clicked so
		// the selected handler will know not to close the calenar when
		// in single-click mode.
		// cal.dateClicked = (el.navtype == 0);
		cal.dateClicked = false;
		var year = date.getFullYear();
		var mon = date.getMonth();
		function setMonth(m) {
			var day = date.getDate();
			var max = date.getMonthDays(m);
			if (day > max) {
				date.setDate(max);
			}
			date.setMonth(m);
		};
		switch (el.navtype) {
			case 400:
			Calendar.removeClass(el, "hilite");
			var text = Calendar._TT["ABOUT"];
			if (typeof text != "undefined") {
				text += cal.showsTime ? Calendar._TT["ABOUT_TIME"] : "";
			} else {
				// FIXME: this should be removed as soon as lang files get updated!
				text = "Help and about box text is not translated into this language.\n" +
					"If you know this language and you feel generous please update\n" +
					"the corresponding file in \"lang\" subdir to match calendar-en.js\n" +
					"and send it back to <mihai_bazon@yahoo.com> to get it into the distribution  ;-)\n\n" +
					"Thank you!\n" +
					"http://dynarch.com/mishoo/calendar.epl\n";
			}
			alert(text);
			return;
			case -2:
			if (year > cal.minYear) {
				date.setFullYear(year - 1);
			}
			break;
			case -1:
			if (mon > 0) {
				setMonth(mon - 1);
			} else if (year-- > cal.minYear) {
				date.setFullYear(year);
				setMonth(11);
			}
			break;
			case 1:
			if (mon < 11) {
				setMonth(mon + 1);
			} else if (year < cal.maxYear) {
				date.setFullYear(year + 1);
				setMonth(0);
			}
			break;
			case 2:
			if (year < cal.maxYear) {
				date.setFullYear(year + 1);
			}
			break;
			case 100:
			cal.setFirstDayOfWeek(el.fdow);
			return;
			case 50:
			var range = el._range;
			var current = el.innerHTML;
			for (var i = range.length; --i >= 0;)
				if (range[i] == current)
					break;
			if (ev && ev.shiftKey) {
				if (--i < 0)
					i = range.length - 1;
			} else if ( ++i >= range.length )
				i = 0;
			var newval = range[i];
			el.innerHTML = newval;
			cal.onUpdateTime();
			return;
			case 0:
			// TODAY will bring us here
			if ((typeof cal.getDateStatus == "function") &&
				cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) {
				return false;
			}
			break;
		}
		if (!date.equalsTo(cal.date)) {
			cal.setDate(date);
			newdate = true;
		} else if (el.navtype == 0)
			newdate = closing = true;
	}
	if (newdate) {
		ev && cal.callHandler();
	}
	if (closing) {
		Calendar.removeClass(el, "hilite");
		ev && cal.callCloseHandler();
	}
};

// END: CALENDAR STATIC FUNCTIONS

// BEGIN: CALENDAR OBJECT FUNCTIONS

/**
 *  This function creates the calendar inside the given parent.  If _par is
 *  null than it creates a popup calendar inside the BODY element.  If _par is
 *  an element, be it BODY, then it creates a non-popup calendar (still
 *  hidden).  Some properties need to be set before calling this function.
 */
Calendar.prototype.create = function (_par) {
	var parent = null;
	if (! _par) {
		// default parent is the document body, in which case we create
		// a popup calendar.
		parent = document.getElementsByTagName("body")[0];
		this.isPopup = true;
	} else {
		parent = _par;
		this.isPopup = false;
	}
	this.date = this.dateStr ? new Date(this.dateStr) : new Date();

	var table = Calendar.createElement("table");
	this.table = table;
	table.cellSpacing = 0;
	table.cellPadding = 0;
	table.calendar = this;
	Calendar.addEvent(table, "mousedown", Calendar.tableMouseDown);

	var div = Calendar.createElement("div");
	this.element = div;
	div.className = "calendar";
	if (this.isPopup) {
		div.style.position = "absolute";
		div.style.display = "none";
	}
	div.appendChild(table);

	var thead = Calendar.createElement("thead", table);
	var cell = null;
	var row = null;

	var cal = this;
	var hh = function (text, cs, navtype) {
		cell = Calendar.createElement("td", row);
		cell.colSpan = cs;
		cell.className = "button";
		if (navtype != 0 && Math.abs(navtype) <= 2)
			cell.className += " nav";
		Calendar._add_evs(cell);
		cell.calendar = cal;
		cell.navtype = navtype;
		cell.innerHTML = "<div unselectable='on'>" + text + "</div>";
		return cell;
	};

	row = Calendar.createElement("tr", thead);
	var title_length = 6;
	(this.isPopup) && --title_length;
	(this.weekNumbers) && ++title_length;

	hh("?", 1, 400).ttip = Calendar._TT["INFO"];
	this.title = hh("", title_length, 300);
	this.title.className = "title";
	if (this.isPopup) {
		this.title.ttip = Calendar._TT["DRAG_TO_MOVE"];
		this.title.style.cursor = "move";
		hh("&#x00d7;", 1, 200).ttip = Calendar._TT["CLOSE"];
	}

	
	row = Calendar.createElement("tr", thead);
	row.className = "headrow";

	if (this.showsDate) { // Re-added showsDate - Fri Apr 06 2007
		this._nav_py = hh("&#x00ab;", 1, -2);
		this._nav_py.ttip = Calendar._TT["PREV_YEAR"];
		
		this._nav_pm = hh("&#x2039;", 1, -1);
		this._nav_pm.ttip = Calendar._TT["PREV_MONTH"];
		
		this._nav_now = hh(Calendar._TT["TODAY"], this.weekNumbers ? 4 : 3, 0);
		this._nav_now.ttip = Calendar._TT["GO_TODAY"];
		
		this._nav_nm = hh("&#x203a;", 1, 1);
		this._nav_nm.ttip = Calendar._TT["NEXT_MONTH"];
		
		this._nav_ny = hh("&#x00bb;", 1, 2);
		this._nav_ny.ttip = Calendar._TT["NEXT_YEAR"];
		
		// day names
		row = Calendar.createElement("tr", thead);
		row.className = "daynames";
		if (this.weekNumbers) {
			cell = Calendar.createElement("td", row);
			cell.className = "name wn";
			cell.innerHTML = Calendar._TT["WK"];
		}
		for (var i = 7; i > 0; --i) {
			cell = Calendar.createElement("td", row);
			if (!i) {
				cell.navtype = 100;
				cell.calendar = this;
				Calendar._add_evs(cell);
			}
		}
		this.firstdayname = (this.weekNumbers) ? row.firstChild.nextSibling : row.firstChild;
		this._displayWeekdays();
	} // Re-added showsDate - Fri Apr 06 2007

	var tbody = Calendar.createElement("tbody", table);
	this.tbody = tbody;

	if (this.showsDate) { // Re-added showsDate - Fri Apr 06 2007
		for (i = 6; i > 0; --i) {
			row = Calendar.createElement("tr", tbody);
			if (this.weekNumbers) {
				cell = Calendar.createElement("td", row);
			}
			for (var j = 7; j > 0; --j) {
				cell = Calendar.createElement("td", row);
				cell.calendar = this;
				Calendar._add_evs(cell);
			}
		}
	} // Re-added showsDate - Fri Apr 06 2007

	if (this.showsTime) {
		row = Calendar.createElement("tr", tbody);
		row.className = "time";

		cell = Calendar.createElement("td", row);
		cell.className = "time";
		cell.colSpan = 2;
		cell.innerHTML = Calendar._TT["TIME"] || "&nbsp;";

		cell = Calendar.createElement("td", row);
		cell.className = "time";
		cell.colSpan = this.weekNumbers ? 4 : 3;

		(function(){
			function makeTimePart(className, init, range_start, range_end) {
				var part = Calendar.createElement("span", cell);
				part.className = className;
				part.innerHTML = init;
				part.calendar = cal;
				part.ttip = Calendar._TT["TIME_PART"];
				part.navtype = 50;
				part._range = [];
				if (typeof range_start != "number")
					part._range = range_start;
				else {
					for (var i = range_start; i <= range_end; ++i) {
						var txt;
						if (i < 10 && range_end >= 10) txt = '0' + i;
						else txt = '' + i;
						part._range[part._range.length] = txt;
					}
				}
				Calendar._add_evs(part);
				return part;
			};
			var hrs = cal.date.getHours();
			var mins = cal.date.getMinutes();
			var t12 = !cal.time24;
			var pm = (hrs > 12);
			if (t12 && pm) hrs -= 12;
			var H = makeTimePart("hour", hrs, t12 ? 1 : 0, t12 ? 12 : 23);
			var span = Calendar.createElement("span", cell);
			span.innerHTML = ":";
			span.className = "colon";
			var M = makeTimePart("minute", mins, 0, 59);
			var AP = null;
			cell = Calendar.createElement("td", row);
			cell.className = "time";
			cell.colSpan = 2;
			if (t12)
				AP = makeTimePart("ampm", pm ? "pm" : "am", ["am", "pm"]);
			else
				cell.innerHTML = "&nbsp;";

			cal.onSetTime = function() {
				var pm, hrs = this.date.getHours(),
					mins = this.date.getMinutes();
				if (t12) {
					pm = (hrs >= 12);
					if (pm) hrs -= 12;
					if (hrs == 0) hrs = 12;
					AP.innerHTML = pm ? "pm" : "am";
				}
				H.innerHTML = (hrs < 10) ? ("0" + hrs) : hrs;
				M.innerHTML = (mins < 10) ? ("0" + mins) : mins;
			};

			cal.onUpdateTime = function() {
				var date = this.date;
				var h = parseInt(H.innerHTML, 10);
				if (t12) {
					if (/pm/i.test(AP.innerHTML) && h < 12)
						h += 12;
					else if (/am/i.test(AP.innerHTML) && h == 12)
						h = 0;
				}
				var d = date.getDate();
				var m = date.getMonth();
				var y = date.getFullYear();
				date.setHours(h);
				date.setMinutes(parseInt(M.innerHTML, 10));
				date.setFullYear(y);
				date.setMonth(m);
				date.setDate(d);
				this.dateClicked = false;
				this.callHandler();
			};
		})();
	} else {
		this.onSetTime = this.onUpdateTime = function() {};
	}

	var tfoot = Calendar.createElement("tfoot", table);

	row = Calendar.createElement("tr", tfoot);
	row.className = "footrow";

	if (this.showsDate) { // Re-added showsDate - Fri Apr 06 2007
		cell = hh(Calendar._TT["SEL_DATE"], this.weekNumbers ? 8 : 7, 300);
	}
	else {
		cell = hh(Calendar._TT["SEL_TIME"], this.weekNumbers ? 8 : 7, 300);
	}
	cell.className = "ttip";
	if (this.isPopup) {
		cell.ttip = Calendar._TT["DRAG_TO_MOVE"];
		cell.style.cursor = "move";
	}
	this.tooltips = cell;

	div = Calendar.createElement("div", this.element);
	this.monthsCombo = div;
	div.className = "combo";
	for (i = 0; i < Calendar._MN.length; ++i) {
		var mn = Calendar.createElement("div");
		mn.className = Calendar.is_ie ? "label-IEfix" : "label";
		mn.month = i;
		mn.innerHTML = Calendar._SMN[i];
		div.appendChild(mn);
	}

	div = Calendar.createElement("div", this.element);
	this.yearsCombo = div;
	div.className = "combo";
	for (i = 12; i > 0; --i) {
		var yr = Calendar.createElement("div");
		yr.className = Calendar.is_ie ? "label-IEfix" : "label";
		div.appendChild(yr);
	}

	this._init(this.firstDayOfWeek, this.date);
	parent.appendChild(this.element);
};

/** keyboard navigation, only for popup calendars */
Calendar._keyEvent = function(ev) {
	var cal = window._dynarch_popupCalendar;
	if (!cal || cal.multiple)
		return false;
	(Calendar.is_ie) && (ev = window.event);
	var act = (Calendar.is_ie || ev.type == "keypress"),
		K = ev.keyCode;
	if (ev.ctrlKey) {
		switch (K) {
			case 37: // KEY left
			act && Calendar.cellClick(cal._nav_pm);
			break;
			case 38: // KEY up
			act && Calendar.cellClick(cal._nav_py);
			break;
			case 39: // KEY right
			act && Calendar.cellClick(cal._nav_nm);
			break;
			case 40: // KEY down
			act && Calendar.cellClick(cal._nav_ny);
			break;
			default:
			return false;
		}
	} else switch (K) {
		case 32: // KEY space (now)
		Calendar.cellClick(cal._nav_now);
		break;
		case 27: // KEY esc
		act && cal.callCloseHandler();
		break;
		case 37: // KEY left
		case 38: // KEY up
		case 39: // KEY right
		case 40: // KEY down
		if (act) {
			var prev, x, y, ne, el, step;
			prev = K == 37 || K == 38;
			step = (K == 37 || K == 39) ? 1 : 7;
			function setVars() {
				el = cal.currentDateEl;
				var p = el.pos;
				x = p & 15;
				y = p >> 4;
				ne = cal.ar_days[y][x];
			};setVars();
			function prevMonth() {
				var date = new Date(cal.date);
				date.setDate(date.getDate() - step);
				cal.setDate(date);
			};
			function nextMonth() {
				var date = new Date(cal.date);
				date.setDate(date.getDate() + step);
				cal.setDate(date);
			};
			while (1) {
				switch (K) {
					case 37: // KEY left
					if (--x >= 0)
						ne = cal.ar_days[y][x];
					else {
						x = 6;
						K = 38;
						continue;
					}
					break;
					case 38: // KEY up
					if (--y >= 0)
						ne = cal.ar_days[y][x];
					else {
						prevMonth();
						setVars();
					}
					break;
					case 39: // KEY right
					if (++x < 7)
						ne = cal.ar_days[y][x];
					else {
						x = 0;
						K = 40;
						continue;
					}
					break;
					case 40: // KEY down
					if (++y < cal.ar_days.length)
						ne = cal.ar_days[y][x];
					else {
						nextMonth();
						setVars();
					}
					break;
				}
				break;
			}
			if (ne) {
				if (!ne.disabled)
					Calendar.cellClick(ne);
				else if (prev)
					prevMonth();
				else
					nextMonth();
			}
		}
		break;
		case 13: // KEY enter
		if (act)
			Calendar.cellClick(cal.currentDateEl, ev);
		break;
		default:
		return false;
	}
	return Calendar.stopEvent(ev);
};

/**
 *  (RE)Initializes the calendar to the given date and firstDayOfWeek
 */
Calendar.prototype._init = function (firstDayOfWeek, date) {
	if(this.showsDate) { // Re-added showsDate - Fri Apr 06 2007
		var today = new Date(),
			TY = today.getFullYear(),
			TM = today.getMonth(),
			TD = today.getDate();
		this.table.style.visibility = "hidden";
		var year = date.getFullYear();
		if (year < this.minYear) {
			year = this.minYear;
			date.setFullYear(year);
		} else if (year > this.maxYear) {
			year = this.maxYear;
			date.setFullYear(year);
		}
		this.firstDayOfWeek = firstDayOfWeek;
		this.date = new Date(date);
		var month = date.getMonth();
		var mday = date.getDate();
		var no_days = date.getMonthDays();
		
		// calendar voodoo for computing the first day that would actually be
		// displayed in the calendar, even if it's from the previous month.
		// WARNING: this is magic. ;-)
		date.setDate(1);
		var day1 = (date.getDay() - this.firstDayOfWeek) % 7;
		if (day1 < 0)
			day1 += 7;
		date.setDate(-day1);
		date.setDate(date.getDate() + 1);
		
		var row = this.tbody.firstChild;
		var MN = Calendar._SMN[month];
		var ar_days = this.ar_days = new Array();
		var weekend = Calendar._TT["WEEKEND"];
		var dates = this.multiple ? (this.datesCells = {}) : null;
		for (var i = 0; i < 6; ++i, row = row.nextSibling) {
			var cell = row.firstChild;
			if (this.weekNumbers) {
				cell.className = "day wn";
				cell.innerHTML = date.getWeekNumber();
				cell = cell.nextSibling;
			}
			row.className = "daysrow";
			var hasdays = false, iday, dpos = ar_days[i] = [];
			for (var j = 0; j < 7; ++j, cell = cell.nextSibling, date.setDate(iday + 1)) {
				iday = date.getDate();
				var wday = date.getDay();
				cell.className = "day";
				cell.pos = i << 4 | j;
				dpos[j] = cell;
				var current_month = (date.getMonth() == month);
				if (!current_month) {
					if (this.showsOtherMonths) {
						cell.className += " othermonth";
						cell.otherMonth = true;
					} else {
						cell.className = "emptycell";
						cell.innerHTML = "&nbsp;";
						cell.disabled = true;
						continue;
					}
				} else {
					cell.otherMonth = false;
					hasdays = true;
				}
				cell.disabled = false;
				cell.innerHTML = this.getDateText ? this.getDateText(date, iday) : iday;
				if (dates)
					dates[date.print("%Y%m%d")] = cell;
				if (this.getDateStatus) {
					var status = this.getDateStatus(date, year, month, iday);
					if (this.getDateToolTip) {
						var toolTip = this.getDateToolTip(date, year, month, iday);
						if (toolTip)
							cell.title = toolTip;
					}
					if (status === true) {
						cell.className += " disabled";
						cell.disabled = true;
					} else {
						if (/disabled/i.test(status))
							cell.disabled = true;
						cell.className += " " + status;
					}
				}
				if (!cell.disabled) {
					cell.caldate = new Date(date);
					cell.ttip = "_";
					if (!this.multiple && current_month
						&& iday == mday && this.hiliteToday) {
						cell.className += " selected";
						this.currentDateEl = cell;
					}
					if (date.getFullYear() == TY &&
						date.getMonth() == TM &&
						iday == TD) {
						cell.className += " today";
						cell.ttip += Calendar._TT["PART_TODAY"];
					}
					if (weekend.indexOf(wday.toString()) != -1)
						cell.className += cell.otherMonth ? " oweekend" : " weekend";
				}
			}
			if (!(hasdays || this.showsOtherMonths))
				row.className = "emptyrow";
		}
		this.title.innerHTML = Calendar._MN[month] + ", " + year;
		this.onSetTime();
		this.table.style.visibility = "visible";
		this._initMultipleDates();
	} // Re-added showsDate - Fri Apr 06 2007
	// PROFILE
	// this.tooltips.innerHTML = "Generated in " + ((new Date()) - today) + " ms";
};

Calendar.prototype._initMultipleDates = function() {
	if (this.multiple) {
		for (var i in this.multiple) {
			var cell = this.datesCells[i];
			var d = this.multiple[i];
			if (!d)
				continue;
			if (cell)
				cell.className += " selected";
		}
	}
};

Calendar.prototype._toggleMultipleDate = function(date) {
	if (this.multiple) {
		var ds = date.print("%Y%m%d");
		var cell = this.datesCells[ds];
		if (cell) {
			var d = this.multiple[ds];
			if (!d) {
				Calendar.addClass(cell, "selected");
				this.multiple[ds] = date;
			} else {
				Calendar.removeClass(cell, "selected");
				delete this.multiple[ds];
			}
		}
	}
};

Calendar.prototype.setDateToolTipHandler = function (unaryFunction) {
	this.getDateToolTip = unaryFunction;
};

/**
 *  Calls _init function above for going to a certain date (but only if the
 *  date is different than the currently selected one).
 */
Calendar.prototype.setDate = function (date) {
	if (!date.equalsTo(this.date)) {
		this._init(this.firstDayOfWeek, date);
	}
};

/**
 *  Refreshes the calendar.  Useful if the "disabledHandler" function is
 *  dynamic, meaning that the list of disabled date can change at runtime.
 *  Just * call this function if you think that the list of disabled dates
 *  should * change.
 */
Calendar.prototype.refresh = function () {
	this._init(this.firstDayOfWeek, this.date);
};

/** Modifies the "firstDayOfWeek" parameter (pass 0 for Synday, 1 for Monday, etc.). */
Calendar.prototype.setFirstDayOfWeek = function (firstDayOfWeek) {
	this._init(firstDayOfWeek, this.date);
	this._displayWeekdays();
};

/**
 *  Allows customization of what dates are enabled.  The "unaryFunction"
 *  parameter must be a function object that receives the date (as a JS Date
 *  object) and returns a boolean value.  If the returned value is true then
 *  the passed date will be marked as disabled.
 */
Calendar.prototype.setDateStatusHandler = Calendar.prototype.setDisabledHandler = function (unaryFunction) {
	this.getDateStatus = unaryFunction;
};

/** Customization of allowed year range for the calendar. */
Calendar.prototype.setRange = function (a, z) {
	this.minYear = a;
	this.maxYear = z;
};

/** Calls the first user handler (selectedHandler). */
Calendar.prototype.callHandler = function () {
	if (this.onSelected) {
		this.onSelected(this, this.date.print(this.dateFormat));
	}
};

/** Calls the second user handler (closeHandler). */
Calendar.prototype.callCloseHandler = function () {
	if (this.onClose) {
		this.onClose(this);
	}
	this.hideShowCovered();
};

/** Removes the calendar object from the DOM tree and destroys it. */
Calendar.prototype.destroy = function () {
	var el = this.element.parentNode;
	el.removeChild(this.element);
	Calendar._C = null;
	window._dynarch_popupCalendar = null;
};

/**
 *  Moves the calendar element to a different section in the DOM tree (changes
 *  its parent).
 */
Calendar.prototype.reparent = function (new_parent) {
	var el = this.element;
	el.parentNode.removeChild(el);
	new_parent.appendChild(el);
};

// This gets called when the user presses a mouse button anywhere in the
// document, if the calendar is shown.  If the click was outside the open
// calendar this function closes it.
Calendar._checkCalendar = function(ev) {
	var calendar = window._dynarch_popupCalendar;
	if (!calendar) {
		return false;
	}
	var el = Calendar.is_ie ? Calendar.getElement(ev) : Calendar.getTargetElement(ev);
	for (; el != null && el != calendar.element; el = el.parentNode);
	if (el == null) {
		// calls closeHandler which should hide the calendar.
		window._dynarch_popupCalendar.callCloseHandler();
		return Calendar.stopEvent(ev);
	}
};

/** Shows the calendar. */
Calendar.prototype.show = function () {
	var rows = this.table.getElementsByTagName("tr");
	for (var i = rows.length; i > 0;) {
		var row = rows[--i];
		Calendar.removeClass(row, "rowhilite");
		var cells = row.getElementsByTagName("td");
		for (var j = cells.length; j > 0;) {
			var cell = cells[--j];
			Calendar.removeClass(cell, "hilite");
			Calendar.removeClass(cell, "active");
		}
	}
	this.element.style.display = "block";
	this.hidden = false;
	if (this.isPopup) {
		window._dynarch_popupCalendar = this;
		Calendar.addEvent(document, "keydown", Calendar._keyEvent);
		Calendar.addEvent(document, "keypress", Calendar._keyEvent);
		Calendar.addEvent(document, "mousedown", Calendar._checkCalendar);
	}
	this.hideShowCovered();
};

/**
 *  Hides the calendar.  Also removes any "hilite" from the class of any TD
 *  element.
 */
Calendar.prototype.hide = function () {
	if (this.isPopup) {
		Calendar.removeEvent(document, "keydown", Calendar._keyEvent);
		Calendar.removeEvent(document, "keypress", Calendar._keyEvent);
		Calendar.removeEvent(document, "mousedown", Calendar._checkCalendar);
	}
	this.element.style.display = "none";
	this.hidden = true;
	this.hideShowCovered();
};

/**
 *  Shows the calendar at a given absolute position (beware that, depending on
 *  the calendar element style -- position property -- this might be relative
 *  to the parent's containing rectangle).
 */
Calendar.prototype.showAt = function (x, y) {
	var s = this.element.style;
	s.left = x + "px";
	s.top = y + "px";
	this.show();
};

/** Shows the calendar near a given element. */
Calendar.prototype.showAtElement = function (el, opts) {
	var self = this;
	var p = Calendar.getAbsolutePos(el);
	if (!opts || typeof opts != "string") {
		this.showAt(p.x, p.y + el.offsetHeight);
		return true;
	}
	function fixPosition(box) {
		if (box.x < 0)
			box.x = 0;
		if (box.y < 0)
			box.y = 0;
		var cp = document.createElement("div");
		var s = cp.style;
		s.position = "absolute";
		s.right = s.bottom = s.width = s.height = "0px";
		document.body.appendChild(cp);
		var br = Calendar.getAbsolutePos(cp);
		document.body.removeChild(cp);
		if (Calendar.is_ie) {
			br.y += document.body.scrollTop;
			br.x += document.body.scrollLeft;
		} else {
			br.y += window.scrollY;
			br.x += window.scrollX;
		}
		var tmp = box.x + box.width - br.x;
		if (tmp > 0) box.x -= tmp;
		tmp = box.y + box.height - br.y;
		if (tmp > 0) box.y -= tmp;
	};
	this.element.style.display = "block";
	Calendar.continuation_for_the_fucking_khtml_browser = function() {
		var w = self.element.offsetWidth;
		var h = self.element.offsetHeight;
		self.element.style.display = "none";
		var valign = opts.substr(0, 1);
		var halign = "l";
		if (opts.length > 1) {
			halign = opts.substr(1, 1);
		}
		// vertical alignment
		switch (valign) {
			case "T": p.y -= h; break;
			case "B": p.y += el.offsetHeight; break;
			case "C": p.y += (el.offsetHeight - h) / 2; break;
			case "t": p.y += el.offsetHeight - h; break;
			case "b": break; // already there
		}
		// horizontal alignment
		switch (halign) {
			case "L": p.x -= w; break;
			case "R": p.x += el.offsetWidth; break;
			case "C": p.x += (el.offsetWidth - w) / 2; break;
			case "l": p.x += el.offsetWidth - w; break;
			case "r": break; // already there
		}
		p.width = w;
		p.height = h + 40;
		self.monthsCombo.style.display = "none";
		fixPosition(p);
		self.showAt(p.x, p.y);
	};
	if (Calendar.is_khtml)
		setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10);
	else
		Calendar.continuation_for_the_fucking_khtml_browser();
};

/** Customizes the date format. */
Calendar.prototype.setDateFormat = function (str) {
	this.dateFormat = str;
};

/** Customizes the tooltip date format. */
Calendar.prototype.setTtDateFormat = function (str) {
	this.ttDateFormat = str;
};

/**
 *  Tries to identify the date represented in a string.  If successful it also
 *  calls this.setDate which moves the calendar to the given date.
 */
Calendar.prototype.parseDate = function(str, fmt) {
	if (!fmt)
		fmt = this.dateFormat;
	this.setDate(Date.parseDate(str, fmt));
};

Calendar.prototype.hideShowCovered = function () {
	if (!Calendar.is_ie && !Calendar.is_opera)
		return;
	function getVisib(obj){
		var value = obj.style.visibility;
		if (!value) {
			if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C
				if (!Calendar.is_khtml)
					value = document.defaultView.
						getComputedStyle(obj, "").getPropertyValue("visibility");
				else
					value = '';
			} else if (obj.currentStyle) { // IE
				value = obj.currentStyle.visibility;
			} else
				value = '';
		}
		return value;
	};

	var tags = new Array("applet", "iframe", "select");
	var el = this.element;

	var p = Calendar.getAbsolutePos(el);
	var EX1 = p.x;
	var EX2 = el.offsetWidth + EX1;
	var EY1 = p.y;
	var EY2 = el.offsetHeight + EY1;

	for (var k = tags.length; k > 0; ) {
		var ar = document.getElementsByTagName(tags[--k]);
		var cc = null;

		for (var i = ar.length; i > 0;) {
			cc = ar[--i];

			p = Calendar.getAbsolutePos(cc);
			var CX1 = p.x;
			var CX2 = cc.offsetWidth + CX1;
			var CY1 = p.y;
			var CY2 = cc.offsetHeight + CY1;

			if (this.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) {
				if (!cc.__msh_save_visibility) {
					cc.__msh_save_visibility = getVisib(cc);
				}
				cc.style.visibility = cc.__msh_save_visibility;
			} else {
				if (!cc.__msh_save_visibility) {
					cc.__msh_save_visibility = getVisib(cc);
				}
				cc.style.visibility = "hidden";
			}
		}
	}
};

/** Internal function; it displays the bar with the names of the weekday. */
Calendar.prototype._displayWeekdays = function () {
	var fdow = this.firstDayOfWeek;
	var cell = this.firstdayname;
	var weekend = Calendar._TT["WEEKEND"];
	for (var i = 0; i < 7; ++i) {
		cell.className = "day name";
		var realday = (i + fdow) % 7;
		if (i) {
			cell.ttip = Calendar._TT["DAY_FIRST"].replace("%s", Calendar._DN[realday]);
			cell.navtype = 100;
			cell.calendar = this;
			cell.fdow = realday;
			Calendar._add_evs(cell);
		}
		if (weekend.indexOf(realday.toString()) != -1) {
			Calendar.addClass(cell, "weekend");
		}
		cell.innerHTML = Calendar._SDN[(i + fdow) % 7];
		cell = cell.nextSibling;
	}
};

/** Internal function.  Hides all combo boxes that might be displayed. */
Calendar.prototype._hideCombos = function () {
	this.monthsCombo.style.display = "none";
	this.yearsCombo.style.display = "none";
};

/** Internal function.  Starts dragging the element. */
Calendar.prototype._dragStart = function (ev) {
	if (this.dragging) {
		return;
	}
	this.dragging = true;
	var posX;
	var posY;
	if (Calendar.is_ie) {
		posY = window.event.clientY + document.body.scrollTop;
		posX = window.event.clientX + document.body.scrollLeft;
	} else {
		posY = ev.clientY + window.scrollY;
		posX = ev.clientX + window.scrollX;
	}
	var st = this.element.style;
	this.xOffs = posX - parseInt(st.left);
	this.yOffs = posY - parseInt(st.top);
	with (Calendar) {
		addEvent(document, "mousemove", calDragIt);
		addEvent(document, "mouseup", calDragEnd);
	}
};

// BEGIN: DATE OBJECT PATCHES

/** Adds the number of days array to the Date object. */
Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31);

/** Constants used for time computations */
Date.SECOND = 1000 /* milliseconds */;
Date.MINUTE = 60 * Date.SECOND;
Date.HOUR   = 60 * Date.MINUTE;
Date.DAY	= 24 * Date.HOUR;
Date.WEEK   =  7 * Date.DAY;

Date.parseDate = function(str, fmt) {
	var today = new Date();
	var y = 0;
	var m = -1;
	var d = 0;
	var a = str.split(/\W+/);
	var b = fmt.match(/%./g);
	var i = 0, j = 0;
	var hr = 0;
	var min = 0;
	for (i = 0; i < a.length; ++i) {
		if (!a[i])
			continue;
		switch (b[i]) {
			case "%d":
			case "%e":
			d = parseInt(a[i], 10);
			break;

			case "%m":
			m = parseInt(a[i], 10) - 1;
			break;

			case "%Y":
			case "%y":
			y = parseInt(a[i], 10);
			(y < 100) && (y += (y > 29) ? 1900 : 2000);
			break;

			case "%b":
			case "%B":
			for (j = 0; j < 12; ++j) {
				if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; }
			}
			break;

			case "%H":
			case "%I":
			case "%k":
			case "%l":
			hr = parseInt(a[i], 10);
			break;

			case "%P":
			case "%p":
			if (/pm/i.test(a[i]) && hr < 12)
				hr += 12;
			else if (/am/i.test(a[i]) && hr >= 12)
				hr -= 12;
			break;

			case "%M":
			min = parseInt(a[i], 10);
			break;
		}
	}
	if (isNaN(y)) y = today.getFullYear();
	if (isNaN(m)) m = today.getMonth();
	if (isNaN(d)) d = today.getDate();
	if (isNaN(hr)) hr = today.getHours();
	if (isNaN(min)) min = today.getMinutes();
	if (y != 0 && m != -1 && d != 0)
		return new Date(y, m, d, hr, min, 0);
	y = 0; m = -1; d = 0;
	for (i = 0; i < a.length; ++i) {
		if (a[i].search(/[a-zA-Z]+/) != -1) {
			var t = -1;
			for (j = 0; j < 12; ++j) {
				if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { t = j; break; }
			}
			if (t != -1) {
				if (m != -1) {
					d = m+1;
				}
				m = t;
			}
		} else if (parseInt(a[i], 10) <= 12 && m == -1) {
			m = a[i]-1;
		} else if (parseInt(a[i], 10) > 31 && y == 0) {
			y = parseInt(a[i], 10);
			(y < 100) && (y += (y > 29) ? 1900 : 2000);
		} else if (d == 0) {
			d = a[i];
		}
	}
	if (y == 0)
		y = today.getFullYear();
	if (m != -1 && d != 0)
		return new Date(y, m, d, hr, min, 0);
	return today;
};

/** Returns the number of days in the current month */
Date.prototype.getMonthDays = function(month) {
	var year = this.getFullYear();
	if (typeof month == "undefined") {
		month = this.getMonth();
	}
	if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) {
		return 29;
	} else {
		return Date._MD[month];
	}
};

/** Returns the number of day in the year. */
Date.prototype.getDayOfYear = function() {
	var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
	var then = new Date(this.getFullYear(), 0, 0, 0, 0, 0);
	var time = now - then;
	return Math.floor(time / Date.DAY);
};

/** Returns the number of the week in year, as defined in ISO 8601. */
Date.prototype.getWeekNumber = function() {
	var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
	var DoW = d.getDay();
	d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu
	var ms = d.valueOf(); // GMT
	d.setMonth(0);
	d.setDate(4); // Thu in Week 1
	return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1;
};

/** Checks date and time equality */
Date.prototype.equalsTo = function(date) {
	return ((this.getFullYear() == date.getFullYear()) &&
		(this.getMonth() == date.getMonth()) &&
		(this.getDate() == date.getDate()) &&
		(this.getHours() == date.getHours()) &&
		(this.getMinutes() == date.getMinutes()));
};

/** Set only the year, month, date parts (keep existing time) */
Date.prototype.setDateOnly = function(date) {
	var tmp = new Date(date);
	this.setDate(1);
	this.setFullYear(tmp.getFullYear());
	this.setMonth(tmp.getMonth());
	this.setDate(tmp.getDate());
};

/** Prints the date in a string according to the given format. */
Date.prototype.print = function (str) {
	var m = this.getMonth();
	var d = this.getDate();
	var y = this.getFullYear();
	var wn = this.getWeekNumber();
	var w = this.getDay();
	var s = {};
	var hr = this.getHours();
	var pm = (hr >= 12);
	var ir = (pm) ? (hr - 12) : hr;
	var dy = this.getDayOfYear();
	if (ir == 0)
		ir = 12;
	var min = this.getMinutes();
	var sec = this.getSeconds();
	s["%a"] = Calendar._SDN[w]; // abbreviated weekday name [FIXME: I18N]
	s["%A"] = Calendar._DN[w]; // full weekday name
	s["%b"] = Calendar._SMN[m]; // abbreviated month name [FIXME: I18N]
	s["%B"] = Calendar._MN[m]; // full month name
	// FIXME: %c : preferred date and time representation for the current locale
	s["%C"] = 1 + Math.floor(y / 100); // the century number
	s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31)
	s["%e"] = d; // the day of the month (range 1 to 31)
	// FIXME: %D : american date style: %m/%d/%y
	// FIXME: %E, %F, %G, %g, %h (man strftime)
	s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format)
	s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format)
	s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366)
	s["%k"] = hr;		// hour, range 0 to 23 (24h format)
	s["%l"] = ir;		// hour, range 1 to 12 (12h format)
	s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12
	s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59
	s["%n"] = "\n";		// a newline character
	s["%p"] = pm ? "PM" : "AM";
	s["%P"] = pm ? "pm" : "am";
	// FIXME: %r : the time in am/pm notation %I:%M:%S %p
	// FIXME: %R : the time in 24-hour notation %H:%M
	s["%s"] = Math.floor(this.getTime() / 1000);
	s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59
	s["%t"] = "\t";		// a tab character
	// FIXME: %T : the time in 24-hour notation (%H:%M:%S)
	s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn;
	s["%u"] = w + 1;	// the day of the week (range 1 to 7, 1 = MON)
	s["%w"] = w;		// the day of the week (range 0 to 6, 0 = SUN)
	// FIXME: %x : preferred date representation for the current locale without the time
	// FIXME: %X : preferred time representation for the current locale without the date
	s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99)
	s["%Y"] = y;		// year with the century
	s["%%"] = "%";		// a literal '%' character

	var re = /%./g;
	if (!Calendar.is_ie5 && !Calendar.is_khtml)
		return str.replace(re, function (par) { return s[par] || par; });

	var a = str.match(re);
	for (var i = 0; i < a.length; i++) {
		var tmp = s[a[i]];
		if (tmp) {
			re = new RegExp(a[i], 'g');
			str = str.replace(re, tmp);
		}
	}

	return str;
};

/*Date.prototype.__msh_oldSetFullYear = Date.prototype.setFullYear;
Date.prototype.setFullYear = function(y) {
	var d = new Date(this);
	d.__msh_oldSetFullYear(y);
	if (d.getMonth() != this.getMonth())
		this.setDate(28);
	this.__msh_oldSetFullYear(y);
};*/

// END: DATE OBJECT PATCHES


// global object that remembers the calendar
window._dynarch_popupCalendar = null;

// ** I18N

// Calendar EN language
// Author: Mihai Bazon, <mihai_bazon@yahoo.com>
// Encoding: any
// Distributed under the same terms as the calendar itself.

// For translators: please use UTF-8 if possible.  We strongly believe that
// Unicode is the answer to a real internationalized world.  Also please
// include your contact information in the header, as can be seen above.

// full day names
Calendar._DN = new Array
("Zondag",
 "Maandag",
 "Dinsdag",
 "Woensdag",
 "Donderdag",
 "Vrijdag",
 "Zaterdag",
 "Zondag");

// Please note that the following array of short day names (and the same goes
// for short month names, _SMN) isn't absolutely necessary.  We give it here
// for exemplification on how one can customize the short day names, but if
// they are simply the first N letters of the full name you can simply say:
//
//   Calendar._SDN_len = N; // short day name length
//   Calendar._SMN_len = N; // short month name length
//
// If N = 3 then this is not needed either since we assume a value of 3 if not
// present, to be compatible with translation files that were written before
// this feature.

// short day names
Calendar._SDN = new Array
("Zo",
 "Ma",
 "Di",
 "Wo",
 "Do",
 "Vri",
 "Za",
 "Zo");

Calendar._SDN_len = 2;

// First day of the week. "0" means display Sunday first, "1" means display
// Monday first, etc.
Calendar._FD = 0;

// full month names
Calendar._MN = new Array
("Januari",
 "Februari",
 "Maart",
 "April",
 "Mei",
 "Juni",
 "Juli",
 "Augustus",
 "September",
 "Oktober",
 "November",
 "December");

// short month names
Calendar._SMN = new Array
("Jan",
 "Feb",
 "Mar",
 "Apr",
 "Mei",
 "Jun",
 "Jul",
 "Aug",
 "Sep",
 "Okt",
 "Nov",
 "Dec");

// tooltips
Calendar._TT = {};
Calendar._TT["INFO"] = "Info";

Calendar._TT["ABOUT"] =
"Datum selectie:\n" +
"- Gebruik de \xab \xbb knoppen om een jaar te selecteren\n" +
"- Gebruik de " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " knoppen om een maand te selecteren\n" +
"- Houd de muis ingedrukt op de genoemde knoppen voor een snellere selectie.";
Calendar._TT["ABOUT_TIME"] = "\n\n" +
"Tijd selectie:\n" +
"- Klik op een willekeurig onderdeel van het tijd gedeelte om het te verhogen\n" +
"- of Shift-klik om het te verlagen\n" +
"- of klik en sleep voor een snellere selectie.";

Calendar._TT["PREV_YEAR"] = "Vorig jaar (ingedrukt voor menu)";
Calendar._TT["PREV_MONTH"] = "Vorige maand (ingedrukt voor menu)";
Calendar._TT["GO_TODAY"] = "Ga naar Vandaag";
Calendar._TT["NEXT_MONTH"] = "Volgende maand (ingedrukt voor menu)";
Calendar._TT["NEXT_YEAR"] = "Volgend jaar (ingedrukt voor menu)";
Calendar._TT["SEL_DATE"] = "Selecteer datum";
Calendar._TT["SEL_TIME"] = "Selecteer tijd";
Calendar._TT["DRAG_TO_MOVE"] = "Klik en sleep om te verplaatsen";
Calendar._TT["PART_TODAY"] = " (vandaag)";

// the following is to inform that "%s" is to be the first day of week
// %s will be replaced with the day name.
Calendar._TT["DAY_FIRST"] = "Toon %s eerst";

// This may be locale-dependent.  It specifies the week-end days, as an array
// of comma-separated numbers.  The numbers are from 0 to 6: 0 means Sunday, 1
// means Monday, etc.
Calendar._TT["WEEKEND"] = "0,6";

Calendar._TT["CLOSE"] = "Sluiten";
Calendar._TT["TODAY"] = "(vandaag)";
Calendar._TT["TIME_PART"] = "(Shift-)Klik of sleep om de waarde te veranderen";

// date formats
Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y";
Calendar._TT["TT_DATE_FORMAT"] = "%a, %e %b %Y";

Calendar._TT["WK"] = "wk";
Calendar._TT["TIME"] = "Tijd:";

/*  Copyright Mihai Bazon, 2002, 2003  |  http://dynarch.com/mishoo/
 * ---------------------------------------------------------------------------
 *
 * The DHTML Calendar
 *
 * Details and latest version at:
 * http://dynarch.com/mishoo/calendar.epl
 *
 * This script is distributed under the GNU Lesser General Public License.
 * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
 *
 * This file defines helper functions for setting up the calendar.  They are
 * intended to help non-programmers get a working calendar on their site
 * quickly.  This script should not be seen as part of the calendar.  It just
 * shows you what one can do with the calendar, while in the same time
 * providing a quick and simple method for setting it up.  If you need
 * exhaustive customization of the calendar creation process feel free to
 * modify this code to suit your needs (this is recommended and much better
 * than modifying calendar.js itself).
 */

// $Id: calendar-setup.js,v 1.25 2005/03/07 09:51:33 mishoo Exp $

/**
 *  This function "patches" an input field (or other element) to use a calendar
 *  widget for date selection.
 *
 *  The "params" is a single object that can have the following properties:
 *
 *    prop. name   | description
 *  -------------------------------------------------------------------------------------------------
 *   inputField    | the ID of an input field to store the date
 *   displayArea   | the ID of a DIV or other element to show the date
 *   button        | ID of a button or other element that will trigger the calendar
 *   eventName     | event that will trigger the calendar, without the "on" prefix (default: "click")
 *   ifFormat      | date format that will be stored in the input field
 *   daFormat      | the date format that will be used to display the date in displayArea
 *   singleClick   | (true/false) wether the calendar is in single click mode or not (default: true)
 *   firstDay      | numeric: 0 to 6.  "0" means display Sunday first, "1" means display Monday first, etc.
 *   align         | alignment (default: "Br"); if you don't know what's this see the calendar documentation
 *   range         | array with 2 elements.  Default: [1900, 2999] -- the range of years available
 *   weekNumbers   | (true/false) if it's true (default) the calendar will display week numbers
 *   flat          | null or element ID; if not null the calendar will be a flat calendar having the parent with the given ID
 *   flatCallback  | function that receives a JS Date object and returns an URL to point the browser to (for flat calendar)
 *   disableFunc   | function that receives a JS Date object and should return true if that date has to be disabled in the calendar
 *   onSelect      | function that gets called when a date is selected.  You don't _have_ to supply this (the default is generally okay)
 *   onClose       | function that gets called when the calendar is closed.  [default]
 *   onUpdate      | function that gets called after the date is updated in the input field.  Receives a reference to the calendar.
 *   date          | the date that the calendar will be initially displayed to
 *   showsDate     | default: true; if true the calendar will include a date selector
 *   showsTime     | default: false; if true the calendar will include a time selector
 *   timeFormat    | the time format; can be "12" or "24", default is "12"
 *   electric      | if true (default) then given fields/date areas are updated for each move; otherwise they're updated only on close
 *   step          | configures the step of the years in drop-down boxes; default: 2
 *   position      | configures the calendar absolute position; default: null
 *   cache         | if "true" (but default: "false") it will reuse the same calendar object, where possible
 *   showOthers    | if "true" (but default: "false") it will show days from other months too
 *
 *  None of them is required, they all have default values.  However, if you
 *  pass none of "inputField", "displayArea" or "button" you'll get a warning
 *  saying "nothing to setup".
 */
Calendar.setup = function (params) {
	function param_default(pname, def) { if (typeof params[pname] == "undefined") { params[pname] = def; } };

	param_default("inputField",     null);
	param_default("displayArea",    null);
	param_default("button",         null);
	param_default("eventName",      "click");
	param_default("ifFormat",       "%Y/%m/%d");
	param_default("daFormat",       "%Y/%m/%d");
	param_default("singleClick",    true);
	param_default("disableFunc",    null);
	param_default("dateStatusFunc", params["disableFunc"]);	// takes precedence if both are defined
	param_default("dateText",       null);
	param_default("firstDay",       null);
	param_default("align",          "Br");
	param_default("range",          [1900, 2999]);
	param_default("weekNumbers",    true);
	param_default("flat",           null);
	param_default("flatCallback",   null);
	param_default("onSelect",       null);
	param_default("onClose",        null);
	param_default("onUpdate",       null);
	param_default("date",           null);
	param_default("showsDate",      true);
	param_default("showsTime",      false);
	param_default("timeFormat",     "24");
	param_default("electric",       true);
	param_default("step",           2);
	param_default("position",       null);
	param_default("cache",          false);
	param_default("showOthers",     false);
	param_default("multiple",       null);

	var tmp = ["inputField", "displayArea", "button"];
	for (var i in tmp) {
		if (typeof params[tmp[i]] == "string") {
			params[tmp[i]] = document.getElementById(params[tmp[i]]);
		}
	}
	if (!(params.flat || params.multiple || params.inputField || params.displayArea || params.button)) {
		alert("Calendar.setup:\n  Nothing to setup (no fields found).  Please check your code");
		return false;
	}

	function onSelect(cal) {
		var p = cal.params;
		var update = (cal.dateClicked || p.electric);
		if (update && p.inputField) {
			p.inputField.value = cal.date.print(p.ifFormat);
			if (typeof p.inputField.onchange == "function")
				p.inputField.onchange();
		}
		if (update && p.displayArea)
			p.displayArea.innerHTML = cal.date.print(p.daFormat);
		if (update && typeof p.onUpdate == "function")
			p.onUpdate(cal);
		if (update && p.flat) {
			if (typeof p.flatCallback == "function")
				p.flatCallback(cal);
		}
		if (update && p.singleClick && cal.dateClicked)
			cal.callCloseHandler();
	};

	if (params.flat != null) {
		if (typeof params.flat == "string")
			params.flat = document.getElementById(params.flat);
		if (!params.flat) {
			//alert("Calendar.setup:\n  Flat specified but can't find parent.");
			return false;
		}
		var cal = new Calendar(params.firstDay, params.date, params.onSelect || onSelect);
		cal.showsOtherMonths = params.showOthers;
		cal.showsDate = params.showsDate;
		cal.showsTime = params.showsTime;
		cal.time24 = (params.timeFormat == "24");
		cal.params = params;
		cal.weekNumbers = params.weekNumbers;
		cal.setRange(params.range[0], params.range[1]);
		cal.setDateStatusHandler(params.dateStatusFunc);
		cal.getDateText = params.dateText;
		if (params.ifFormat) {
			cal.setDateFormat(params.ifFormat);
		}
		if (params.inputField && typeof params.inputField.value == "string") {
			cal.parseDate(params.inputField.value);
		}
		cal.create(params.flat);
		cal.show();
		return false;
	}

	var triggerEl = params.button || params.displayArea || params.inputField;
	triggerEl["on" + params.eventName] = function() {
		var dateEl = params.inputField || params.displayArea;
		var dateFmt = params.inputField ? params.ifFormat : params.daFormat;
		var mustCreate = false;
		var cal = window.calendar;
		if (dateEl)
			params.date = Date.parseDate(dateEl.value || dateEl.innerHTML, dateFmt);
		if (!(cal && params.cache)) {
			window.calendar = cal = new Calendar(params.firstDay,
							     params.date,
							     params.onSelect || onSelect,
							     params.onClose || function(cal) { cal.hide(); });
			cal.showsDate = params.showsDate;
			cal.showsTime = params.showsTime;
			cal.time24 = (params.timeFormat == "24");
			cal.weekNumbers = params.weekNumbers;
			mustCreate = true;
		} else {
			if (params.date)
				cal.setDate(params.date);
			cal.hide();
		}
		if (params.multiple) {
			cal.multiple = {};
			for (var i = params.multiple.length; --i >= 0;) {
				var d = params.multiple[i];
				var ds = d.print("%Y%m%d");
				cal.multiple[ds] = d;
			}
		}
		cal.showsOtherMonths = params.showOthers;
		cal.yearStep = params.step;
		cal.setRange(params.range[0], params.range[1]);
		cal.params = params;
		cal.setDateStatusHandler(params.dateStatusFunc);
		cal.getDateText = params.dateText;
		cal.setDateFormat(dateFmt);
		if (mustCreate)
			cal.create();
		cal.refresh();
		if (!params.position)
			cal.showAtElement(params.button || params.displayArea || params.inputField, params.align);
		else
			cal.showAt(params.position[0], params.position[1]);
		return false;
	};

	return cal;
};

/**
 * WJGuiSettings
 *
 * Mixes existing WJGuiSettings with default values
 *
 * @since Fri Oct 3 2008
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.WJGui.Javascript
 **/
var WJGuiSettings = Object.extend({
	windowBaseTitle: "Windmill CMS"
}, WJGuiSettings || {});
/**
 * class WJWindow
 *
 * The base window class
 *
 * @since Fri Jun 27 2008
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Aeroplane
 **/
var WJWindow = Class.create({
	/**
	 * Properties of Window
	 *
	 * string _title
	 * string _type
	 * DOMElement _content
	 * mixed _contenttype
	 * Function _callbackFunction
	 * boolean _visible
	 * integer _x
	 * integer _y
	 * integer _z
	 * integer _w
	 * integer _h
	 **/
	DEFAULT_PARENT: document.body,

	/**
	 * initialize
	 *
	 * Creates a new WJWindow
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param Function callback
	 * @param DOMElement parent (default: document.body)
	 * @return WJWindow
	 **/
	initialize: function(callback, parent, translate) {
		this._loading = false;
		this._basetitle = this._title = "";
		this._theme = "default";
		this._parent = parent || WJWindow.DEFAULT_PARENT || document.body;
		this._listeners = new Hash();
		this.translate = translate || this.translate;
		
		this._createWindow();
		this._addDefaultListeners();

		this._addCloseButton();

		this.setCallback(callback);
		this.setBaseTitle(WJGuiSettings.windowBaseTitle);
	},

	/**
	 * _createWindow
	 *
	 * Creates a new window DOMElement
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @return void
	 **/
	_createWindow: function() {
		var classname = this._getBaseClassname();
		this._windowElement = new Element("div");
		this._windowElement.addClassName(classname);
		this._windowElement.setStyle({"display": "none"});
		this._createWindowRows(["title", "main", "buttons", "bottom"], classname);
		this._windowElementId = this._windowElement.identify();
		this._parent.insert(this._windowElement);
		this._absolutizeTopLeft();
		this.hide();
		this._outerElement = this._windowElement;
		this.setTheme();
	},

	/**
	 * insertWindowRowBefore
	 *
	 * Inserts a new row before given rowname with name newrowname
	 *
	 * @since Tue Sep 23 2008
	 * @access public
	 * @param string rowname
	 * @param string newrowname
	 * @return DOMElement
	 **/
	insertWindowRowBefore: function(rowname, newrowname) {
		if (rowname === "title") {
			return;
		}
		var classname = this._getBaseClassname();
		var row = this._windowElement.select("." + classname + "_" + rowname)
		row = row.first();
		var newrowhtml = this._createRow(newrowname, classname, " " + classname + "_body");
		var div = new Element("div");
		div.update(newrowhtml);
		var toprow = row.parentNode;
		var newrow = toprow.insertBefore(div.firstChild, row );
		newrow = Element.extend(newrow);
		newrow = newrow.select("." + classname + "_content");
		this._contentElements[newrowname] = newrow.first();
		return newrow;
	},

	/**
	 * replaceWindowRow
	 *
	 * Replaces windowrow old with new, keeps references to old alive (they'll return the new rows)
	 * Returns the removed row
	 *
	 * @since Thu Feb 12 2009
	 * @access public
	 * @param string oldrow
	 * @param string newrow
	 * @return htmlelement
	 **/
	replaceWindowRow: function(oldrow, newrow) {
		var inserted = this.insertWindowRowBefore(oldrow, newrow);
		var toremove = this.getContentElement(oldrow);
		this._contentElements[oldrow] = this._contentElements[newrow];
		return toremove.remove();
	},

	/**
	 * _addCloseButton
	 *
	 * Adds a button to close the window
	 *
	 * @since Mon Jul 7 2008
	 * @access protected
	 * @return void
	 **/
	_addCloseButton: function() {
		var title = this.getContentElement("title");
		var titlediv = new Element("div", {"onclick": "this.parentNode.getWJWindowObject().fireClose(this)", "title": this.translate("CLOSE_WINDOW") } );
		titlediv.addClassName(this._getBaseClassname() + "_closebutton");
 		title.insert(titlediv);
	},

	/**
	 * fireClose
	 *
	 * Fires the close event from the given element
	 *
	 * @since Thu Oct 16 2008
	 * @access public
	 * @param Element element
	 * @return void
	 **/
	fireClose: function(element) {
		element = $(element);

		/* Observe once function */
		var func = function() {
			this.destroy();
			Event.stopObserving(document, "wjgui:close", arguments.callee.observerFunction);
		}
		var bound = func.bindAsEventListener(this);
		func.observerFunction = bound;
		/* End observe once function */

		Event.observe(document, "wjgui:close", bound);
		element.fire("wjgui:close");
		Event.stopObserving.defer(document, "wjgui:close", bound);
	},

	/**
	 * _addDefaultListeners
	 *
	 * Adds custom event listeners to the window
	 *
	 * @since Mon Jul 7 2008
	 * @access protected
	 * @return void
	 **/
	_addDefaultListeners: function(element) {
		this.addListener("true", this.windowResult.bindAsEventListener(this, true) );
		this.addListener("false", this.windowResult.bindAsEventListener(this, false) );
		this.addListener("close", this.windowResult.bindAsEventListener(this, false) );
		this.addListener("save", this.windowResult.bindAsEventListener(this) );
		this.addListener("delete", this.windowResult.bindAsEventListener(this) );
		this.addListener("cancel", this.windowResult.bindAsEventListener(this) );
		this._addDefaultKeyListener();
	},

	/**
	 * _addDefaultKeyListener
	 *
	 * Adds a listener for key's like return and esc
	 *
	 * @since Fri Sep 5 2008
	 * @access protected
	 * @return void
	 **/
	_addDefaultKeyListener: function() {
		// here for extending purposes only
		var element = element || this._windowElement;
		Event.observe(element, "keydown", this.keyHandle.bindAsEventListener(this) );
	},

	/**
	 * keyHandle
	 *
	 * Handles pressing enter or esc
	 *
	 * @since Fri Sep 5 2008
	 * @access public
	 * @param Event event
	 * @return void
	 **/
	keyHandle: function(event) {
		var element = event.element();
		if (Object.isElement(element.up(".wjgui_window") ) ) {
			switch (event.keyCode) {
				case Event.KEY_RETURN:
					if (this.isVisible() ) {
						element.fire("wjgui:true");
					}
					break;
				case Event.KEY_ESC:
					if (this.isVisible() ) {
						element.fire("wjgui:close");
					}
					break;
				default:
					return;
			}
		}
	},

	/**
	 * addListener
	 *
	 * Adds a listener for a custom event that calls the given callback or the default callback of this window
	 *
	 * @since Tue Aug 12 2008
	 * @access
	 * @param
	 * @return WJWindow
	 **/
	addListener: function(eventName, callback, element) {
		WJDebugger.log(WJDebugger.INFO, "Adding listener in WJWindow", eventName, callback);
		var callback = callback || this.windowResult.bindAsEventListener(this);
		var element = element || this._windowElement;

		Event.observe(element, "wjgui:" + eventName, callback);
		this._setListener(eventName, {"element": element, "callback": callback} );
		return this;
	},

	/**
	 * _setListener
	 *
	 * Registers a listener function
	 *
	 * @since Wed Jul 9 2008
	 * @access public
	 * @param string key
	 * @param Object elementAndCallback
	 * @return void
	 **/
	_setListener: function(key, elementAndCallback) {
		this._listeners.set(key, elementAndCallback);
	},

	/**
	 * removeListener
	 *
	 * Removes the listener set for key
	 *
	 * @since Tue Aug 12 2008
	 * @access
	 * @param
	 * @return WJWindow
	 **/
	removeListener: function(key) {
		var listener = this.getListener(key);
		Event.stopObserving(listener.element, "wjgui:" + key, listener.callback);
		this._listeners.unset(key);
		return this;
	},

	/**
	 * removeListeners
	 *
	 * Removes all listeners
	 *
	 * @since Tue Aug 12 2008
	 * @access public
	 * @return WJWindow
	 **/
	removeListeners: function() {
		this._listeners.each(function(info) {
			this.removeListener(info.key);
		}.bind(this) );
		return this;
	},

	/**
	 * windowResult
	 *
	 * Handles the window result event
	 *
	 * @since Wed Jul 9 2008
	 * @access protected
	 * @param Event event
	 * @return void
	 **/
	windowResult: function(event) {
		this._callback.apply(this, arguments);
	},

	/**
	 * getListeners
	 *
	 * Returns the listeners hash
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @return Hash
	 **/
	getListeners: function() {
		return this._listeners;
	},

	/**
	 * getListener
	 *
	 * Returns the listener info set for key
	 *
	 * @since Tue Aug 12 2008
	 * @access
	 * @param
	 * @return
	 **/
	getListener: function(key) {
		return this._listeners.get(key);
	},

	/**
	 * _getBaseClassname
	 *
	 * Returns the base classname used for windows
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @return string
	 **/
	_getBaseClassname: function() {
		return "wjgui_window";
	},

	/**
	 * _getWindowRowTemplate
	 *
	 * Returns a template that can be used to create rows in windows
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @return Template
	 **/
	_getWindowRowTemplate: function() {
		return new Template("<div class='#{classprefix}_#{rowname} #{classprefix}_row#{body}'><div class='#{classprefix}_left #{classprefix}_column'><div class='#{classprefix}_right #{classprefix}_column'><div class='#{classprefix}_center #{classprefix}_column'><div class='#{classprefix}_content'>&#160;</div></div></div></div></div>");
	},

	/**
	 * _createWindowRows
	 *
	 * Creates rows with names in the rows argument, appends them to windowElement and prefixes all classes with classprefix
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @param Array rows
	 * @param string classprefix
	 * @param Element windowElement
	 * @return array
	 **/
	_createWindowRows: function(rows, classprefix, windowElement) {
		var windowElement = windowElement || this._windowElement;
		rows.each(function(windowElement, classprefix, rowname, index) {
			var body = " " + classprefix + "_body";
			if (index == 0 || index == (rows.length - 1) ) {
				body = "";
			}
			windowElement.innerHTML += this._createRow(rowname, classprefix, body);
		}.bind(this, windowElement, classprefix));
		this._saveRows(rows, classprefix, windowElement);
		this._addWindowObjectGetters();
	},

	/**
	 * _addWindowObjectGetters
	 *
	 * Adds a getWJWindowObject getter to all content elements and the main window element
	 *
	 * @since Thu Oct 16 2008
	 * @access protected
	 * @return Array
	 **/
	_addWindowObjectGetters: function() {
		var test = [$H(this._contentElements).values(), this._windowElement].flatten();
		test.each(function(el) {
			el.getWJWindowObject = function() { return this; }.bind(this);
		}, this);
		
		return test;
	},

	/**
	 * _saveRows
	 *
	 * Saves all rows in this._contentElements
	 *
	 * @since Tue Sep 23 2008
	 * @access protected
	 * @param Array rows
	 * @param string classprefix
	 * @param Element windowElement
	 * @return WJWindow
	 **/
	_saveRows: function(rows, classprefix, windowElement) {
		var windowElement = windowElement || this._windowElement;
		this._contentElements = {};
		rows.each(function(windowElements, rowname, index) {
			this._contentElements[rowname] = windowElements[index];
		}.bind(this, windowElement.select("." + classprefix + "_content") ) );
		return this;
	},

	/**
	 * _createRow
	 *
	 * Creates the HTML of a row
	 *
	 * @since Tue Sep 23 2008
	 * @access protected
	 * @param string rowname
	 * @param string classprefix
	 * @param string body
	 * @return string
	 **/
	_createRow: function(rowname, classprefix, body) {
		var row = this._getWindowRowTemplate();
		return row.evaluate({"rowname": rowname, "classprefix": classprefix, "body": body});
	},

	/**
	 * getContentElement
	 *
	 * Returns the content element identified by rowname
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @param string rowname
	 * @return DOMElement
	 **/
	getContentElement: function(rowname) {
		return this._contentElements[rowname];
	},

	/**
	 * evalContentElement
	 *
	 * Evaluates the script parts in the content element identified by rowname
	 *
	 * @since Wed Jul 30 2008
	 * @access public
	 * @param string rowname
	 * @return WJWindow
	 **/
	evalContentElement: function(rowname) {
		var element = this.getContentElement(rowname);
		element.innerHTML.evalScripts();
		return this;
	},

	/**
	 * _absolutizeTopLeft
	 *
	 * Puts the window in the top left corner of the viewport
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @param Element element
	 * @return void
	 **/
	_absolutizeTopLeft: function(element) {
		var element = element || this._windowElement;
		element.absolutize();
		element.setStyle({height: "", width: ""});
		this.setX(0, element);
		this.setY(0, element);
	},

	/**
	 * _checkMaxHeight
	 *
	 * Checks the window wo be inside the viewport
	 *
	 * @since Mon Jul 7 2008
	 * @access protected
	 * @param DOMElement element
	 * @return void
	 **/
	_checkMaxHeight: function(element) {
		var element = element || this.getContentElement("main");
		if (this.getY() + this.getHeight() > document.viewport.getHeight() ) {
			this.setHeight(document.viewport.getHeight() - this.getY(), element, false);
		}
		else {
			element.setStyle({"maxHeight": ""});
		}
	},

	/**
	 * show
	 *
	 * Shows the window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return WJWindow
	 **/
	show: function(element) {
		var element = element || this._outerElement || this._windowElement;
		element.style.display = "block";
		try { element.focus() } catch(e) {} // TODO think of something better to get focus in browsers and a window in IE6
		return this;
	},

	/**
	 * hide
	 *
	 * Hides the window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return WJWindow
	 **/
	hide: function(element) {
		var element = element || this._outerElement || this._windowElement;
		element.style.display = "none";
		return this;
	},

	/**
	 * destroy
	 *
	 * Destroys the window
	 *
	 * @since Mon Jul 28 2008
	 * @access public
	 * @return WJWindow
	 **/
	destroy: function(element) {
		var element = element || this._outerElement || this._windowElement;
		if (element.parentNode) {
			element.remove();
		}
		return this;
	},

	/**
	 * _callback
	 *
	 * Does the callback that this window should do
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @return mixed
	 **/
	_callback: function() {
		if (Object.isFunction(this._callbackFunction) ) {
			var args = $A(arguments);
			args.unshift(this);
			return this._callbackFunction.apply(this._callbackFunction,  args);
		}
	},

	/**
	 * _close
	 *
	 * Closes the window
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @return mixed
	 **/
	_close: function(event) {
		this.hide();
	},

	/**
	 * setBaseTitle
	 *
	 * Changes the base title
	 *
	 * @since Wed Sep 10 2008
	 * @access public
	 * @param string title
	 * @return WJWindow
	 **/
	setBaseTitle: function(title) {
		this._basetitle = title;
		this.setTitle(this.getTitle() );
		return this;
	},

	/**
	 * setTitle
	 *
	 * Changes the title
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return WJWindow
	 **/
	setTitle: function(title) {
		this._title = title;
		var headers = this.getContentElement("title").getElementsByTagName("h1");
		if (headers.length < 1) {
			this.getContentElement("title").innerHTML = "<h1>&#160;</h1>" + this.getContentElement("title").innerHTML;
			return this.setTitle(this._title);
		}
		headers[0].innerHTML = this._getComposedTitle();
		return this;
	},

	/**
	 * _getComposedTitle
	 *
	 * Creates a nice looking title
	 *
	 * @since Wed Sep 10 2008
	 * @access protected
	 * @return string
	 **/
	_getComposedTitle: function() {
		return this._title + ( (this._basetitle != "" &&  this._basetitle != this._title) ? ( (this._title != "") ? " - " : "") + this._basetitle : "");
	},

	/**
	 * setContent
	 *
	 * Changes the content
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param mixed content
	 * @return WJWindow
	 **/
	setContent: function(content) {
		if (Object.isString(content) ) {
			this.getContentElement("main").innerHTML = content;
		}
		if (Object.isElement(content) ) {
			this.getContentElement("main").appendChild(content);
		}
		this._content = content;
		return this;
	},

	/**
	 * addButton
	 *
	 * Adds a button to the window
	 *
	 * @since Tue Aug 12 2008
	 * @access public
	 * @param string caption
	 * @param mixed callback
	 * @param boolean defaultButton
	 * @return DOMElement
	 **/
	addButton: function(caption, eventHandler, defaultButton) {
		WJDebugger.log(WJDebugger.INFO, "Adding button to window", caption, eventHandler, this);
		var button = WJButton.create(caption, eventHandler, defaultButton, this.getContentElement("buttons") );
		this._checkMaxHeight(); // this function is likely to change the height of the bottom row
		return button;
	},

	/**
	 * addStatusbar
	 *
	 * Adds a statusbar to the window
	 *
	 * @since Mon Feb 02 2009
	 * @access public
	 * @return void
	 **/
	addStatusbar: function() {
		WJDebugger.log(WJDebugger.INFO, "Adding statusbar to window", this);
		this._statusbar = new Element("div");
		this._statusbar.addClassName("wjgui_statusbar");
		this.getContentElement("buttons").insert(this._statusbar);
		this._checkMaxHeight();
	},

	/**
	 * setStatusbar
	 *
	 * Sets the content of the statusbar
	 *
	 * @since Mon Feb 02 2009
	 * @access public
	 * @param string content
	 * @param integer fade
	 * @return void
	 **/
	setStatusbar: function(content, fade) {
		var fade = fade || -1;
		if (this._statusbar) {
			this._statusbar.update(content);
			if (fade > 0) {
				this.hideStatusbar.bind(this).delay(fade);
			}
		}
	},

	/**
	 * hideStatusbar
	 *
	 * Hides the contents of the statusbar, and then clears its contents
	 *
	 * @since Mon Feb 02 2009
	 * @access public
	 * @return void
	 **/
	hideStatusbar: function() {
		Effect.Fade(this._statusbar, {duration: 3.0});
		this.clearAndShowStatusbar.bind(this).delay(3.5);
	},

	/**
	 * clearAndShowStatusbar
	 *
	 * Clears and shows the statusbar
	 *
	 * @since Mon Feb 02 2009
	 * @access public
	 * @return void
	 **/
	clearAndShowStatusbar: function() {
		this._statusbar.update("");
		this._statusbar.show();
	},

	/**
	 * setCallback
	 *
	 * Changes the callback function
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param Function callback
	 * @return WJWindow
	 **/
	setCallback: function(callback) {
		if (Object.isFunction(callback) ) {
			this._callbackFunction = callback;
		}
		return this;
	},

	/**
	 * setX
	 *
	 * Changes the x position of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param integer x
	 * @return WJWindow
	 **/
	setX: function(x, element) {
		this._x = x || 0;
		var element = element || this._windowElement;
		element.style.left = x + "px";
		return this;
	},

	/**
	 * setY
	 *
	 * Changes the y position of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param integer y
	 * @return WJWindow
	 **/
	setY: function(y, element) {
		this._y = y || 0;
		var element = element || this._windowElement;
		element.style.top = y + "px";
		this._checkMaxHeight(element);
		return this;
	},

	/**
	 * setZ
	 *
	 * Changes the z-index of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param integer z
	 * @return WJWindow
	 **/
	setZ: function(z, element) {
		this._z = z || 10000;
		var element = element || this._windowElement;
		element.style.zIndex = z;
		return this;
	},

	/**
	 * setWidth
	 *
	 * Changes the width of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param integer width
	 * @return WJWindow
	 **/
	setWidth: function(width, element) {
		this._width = width;
		var element = element || this._windowElement;
		element.setStyle({"width": width + "px"});
		element.fire("wjgui:resize");
		return this;
	},

	/**
	 * setHeight
	 *
	 * Changes the height of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param integer height
	 * @return WJWindow
	 **/
	setHeight: function(height, element, checkHeight) {
		this._height = height;
		var element = element || this.getContentElement("main");

		var wasVisible = this.isVisible();
		var origX = this.getX();
		var origY = this.getY();
		if (!wasVisible) {
			this.setX(-10000);
			this.setY(-10000);
			this.show();
		}

		var otherRowsHeight = 0;
		for (var key in this._contentElements) {
			if (key != "main") {
				otherRowsHeight += this._contentElements[key].getHeight();
			}
		}

		var height = (Object.isNumber(height) == false) ? height : (height - otherRowsHeight) + "px";
		element.setStyle({"height": height});

		if (!wasVisible) {
			this.hide();
			this.setX(origX);
			this.setY(origY);
		}

		if (checkHeight != false) {
			this._checkMaxHeight(element);
		}
		if (!checkHeight) {
			element.fire("wjgui:resize");
		}
		return this;
	},

	/**
	 * getBaseTitle
	 *
	 * Returns the title of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return string
	 **/
	getBaseTitle: function() {
		return this._basetitle;
	},

	/**
	 * getTitle
	 *
	 * Returns the title of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return string
	 **/
	getTitle: function() {
		return this._title;
	},

	/**
	 * getType
	 *
	 * Tells what window type this window is
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return string
	 **/
	getType: function() {
		return this._type;
	},

	/**
	 * getContent
	 *
	 * Returns the content of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return mixed
	 **/
	getContent: function() {
		return this._content;
	},

	/**
	 * getContenttype
	 *
	 * Returns the type of the content
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return mixed
	 **/
	getContenttype: function() {

	},

	/**
	 * getCallback
	 *
	 * Returns the callback function
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return Function
	 **/
	getCallback: function() {
		return this._callbackFunction;
	},

	/**
	 * isVisible
	 *
	 * Tells if this window can be seen
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return boolean
	 **/
	isVisible: function() {
		var xInLow = (this.getX() < 0 && (this.getX() + this.getWidth() ) > 0);
		var xInHigh = (this.getX() >= 0 && this.getX() < document.viewport.getWidth() );
		var yInLow = (this.getY() < 0 && (this.getY() + this.getHeight() ) > 0);
		var yInHigh = (this.getY() >= 0 && this.getY() < document.viewport.getHeight() );
		if ( (xInLow || xInHigh) && (yInLow || yInHigh) && this._windowElement.visible() ) {
			return true;
		}
		return false;
	},

	/**
	 * getX
	 *
	 * Tells the x position of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return integer
	 **/
	getX: function(element) {
		var element = element || this._windowElement;
		return parseInt(element.style.left);
	},

	/**
	 * getY
	 *
	 * Tells the y position of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return integer
	 **/
	getY: function(element) {
		var element = element || this._windowElement;
		return parseInt(element.style.top);
	},

	/**
	 * getZ
	 *
	 * Tells the zIndex of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return integer
	 **/
	getZ: function() {
		var element = element || this._windowElement;
		return element.style.zIndex;
	},

	/**
	 * getWidth
	 *
	 * Tells the width of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return integer
	 **/
	getWidth: function(element) {
		var element = element || this._windowElement;
		return element.getWidth();
	},

	/**
	 * getHeight
	 *
	 * Tells the height of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return integer
	 **/
	getHeight: function(element) {
		var element = element || this._windowElement;
		return element.getHeight();
	},

	/**
	 * getContentHeight
	 *
	 * Tells what's the height of the content element
	 *
	 * @since Tue Sep 16 2008
	 * @access public
	 * @return integer
	 **/
	getContentHeight: function() {
		return this.getContentElement("main").getHeight();
	},

	/**
	 * getContentWidth
	 *
	 * Tells what's the width of the content element
	 *
	 * @since Tue Sep 16 2008
	 * @access public
	 * @return integer
	 **/
	getContentWidth: function() {
		return this.getContentElement("main").getWidth();
	},

	/**
	 * getWindowElement
	 *
	 * Returns the windowElement
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @return DOMElement
	 **/
	getWindowElement: function() {
		return this._windowElement;
	},

	/**
	 * center
	 *
	 * centers the given element
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @param DOMElement element
	 * @return WJWindow
	 **/
	center: function(element) {
		var element = element || this._windowElement;
		var remainsX = document.viewport.getWidth() - this.getWidth(element);
		var remainsY = document.viewport.getHeight() - this.getHeight(element);

		this.setX(remainsX / 2, element).setY(remainsY / 2, element);
		return this;
	},

	/**
	 * keepCentered
	 *
	 * Makes sure the window stays centered
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @param DOMElement element
	 * @return void
	 **/
	keepCentered: function(element) {
		if (!this._centerObserver) {
			var element = element || this._windowElement;
			this._centerObserver = this.center.bind(this, element);
			Event.observe(window, "resize", this._centerObserver);
		}
	},

	/**
	 * stopCentered
	 *
	 * Stops centering the window
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @return void
	 **/
	stopCentered: function() {
		Event.stopObserving(window, "resize", this._centerObserver);
		this._centerObserver = null;
	},

	/**
	 * maximize
	 *
	 * Maximizes the window
	 *
	 * @since Fri Aug 8 2008
	 * @access public
	 * @return WJWindow
	 **/
	maximize: function(paddingTop, paddingRight, paddingBottom, paddingLeft, noScroll) {
		var paddingTop = paddingTop || 0;
		var paddingRight = paddingRight || paddingTop;
		var paddingBottom = paddingBottom || paddingTop;
		var paddingLeft = paddingLeft || paddingTop;
		var noScroll = noScroll || false;

		this.setX(paddingLeft).setY(paddingTop);

		if (noScroll) {
			this.setWidth(0).setHeight(0);
		}

		this.setWidth(document.viewport.getWidth() - (paddingLeft + paddingRight) ).setHeight(document.viewport.getHeight() - (paddingTop + paddingBottom) );
		return this;
	},

	/**
	 * keepMaximized
	 *
	 * Makes sure the window stays maximized
	 *
	 * @since Tue Sep 9 2008
	 * @access public
	 * @return void
	 **/
	keepMaximized: function() {
		if (!this._maximizedObserver) {
			var paddingTop = this.getY();
			var paddingLeft = this.getX();
			var paddingRight = document.viewport.getWidth() - this.getWidth() - paddingLeft;
			var paddingBottom = document.viewport.getHeight() - this.getHeight() - paddingTop;

			this._maximizedObserver = this.maximize.bind(this, paddingTop, paddingRight, paddingBottom, paddingLeft, true);
			Event.observe(window, "resize", this._maximizedObserver);
		}
	},

	/**
	 * stopMaximized
	 *
	 * Stops maximizing the window
	 *
	 * @since Tue Sep 9 2008
	 * @access public
	 * @return void
	 **/
	stopMaximized: function() {
		Event.stopObserving(window, "resize", this._maximizedObserver);
		this._maximizedObserver = null;
	},

	/**
	 * setLoading
	 *
	 * Mark this window as loading (or not)
	 *
	 * @since Wed Sep 3 2008
	 * @access public
	 * @param boolean loading
	 * @param function loadCallback;
	 * @return WJWindow
	 **/
	setLoading: function(loading, loadCallback) {
		var loadCallback = loadCallback || false;
		if (loading && !this.getLoading() ) {
			this.getWindowElement().addClassName("wjgui_window_loading");
			if (!loadCallback) {
				this.getContentElement("main").setStyle({"visibility": "hidden"});
			}
			else {
				loadCallback(this, loading);
			}
		}
		else if (!loading && this.getLoading() ) {
			this.getWindowElement().removeClassName("wjgui_window_loading");
			if (!loadCallback) {
				this.getContentElement("main").setStyle({"visibility": "visible"});
			}
			else {
				loadCallback(this, loading);
			}
		}
		this._loading = loading;
		return this;
	},

	/**
	 * getLoading
	 *
	 * Tells if this window is marked as loading
	 *
	 * @since Wed Sep 3 2008
	 * @access public
	 * @return boolean
	 **/
	getLoading: function() {
		return this._loading;
	},

	/**
	 * setTheme
	 *
	 * Sets the theme for the window
	 *
	 * @since Fri Dec 5 2008
	 * @access public
	 * @param string theme
	 * @return void
	 **/
	setTheme: function(theme) {
		if (theme == this.getTheme() ) {
			return;
		}
		this._removeOldTheme();
		this._setTheme(theme);
		this._addNewTheme();
	},

	/**
	 * _setTheme
	 *
	 * Sets the value of the _theme property
	 *
	 * @since Fri Dec 5 2008
	 * @access protected
	 * @param string theme
	 * @return void
	 **/
	_setTheme: function(theme) {
		this._theme = theme || "default";
	},

	/**
	 * _removeOldTheme
	 *
	 * Removes the previously set theme class from the window
	 *
	 * @since Fri Dec 5 2008
	 * @access
	 * @param
	 * @return
	 **/
	_removeOldTheme: function() {
		this.getWindowElement().removeClassName(this._getBaseClassname() + "theme_" + this.getTheme() );
	},

	/**
	 * _addNewTheme
	 *
	 * Adds the new theme class to the window
	 *
	 * @since Fri Dec 5 2008
	 * @access protected
	 * @return void
	 **/
	_addNewTheme: function() {
		this.getWindowElement().addClassName(this._getBaseClassname() + "theme_" + this.getTheme() );
	},

	/**
	 * getTheme
	 *
	 * Returns the value of the _theme property
	 *
	 * @since Fri Dec 5 2008
	 * @access public
	 * @return string
	 **/
	getTheme: function() {
		return this._theme;
	},
	
	/**
	 * translate
	 *
	 * Translates strings
	 *
	 * @since Thu Jun 18 2009
	 * @access public
	 * @param string key
	 * @return string
	 **/
	translate: function(key) {
		if (this._translations[key] ) {
			return this._translations[key];
		}
		return key.charAt(0).toUpperCase() + key.replace("_", " ").substr(1).toLowerCase();
	},
	
	/**
	 * The translations table
	 *
	 * @since Thu Jun 18 2009
	 * @access protected
	 * @var Object
	 **/
	_translations: {OK: "OK", CANCEL: "Cancel", YES: "Yes", NO: "No", CLOSE_WINDOW: "Close window"}
});


WJWindow._messagedialog = function(type, message, callback, show, translate, allowHTML) {
	var win = new WJWindow(callback, null, translate);
	var mwin = new window[type](win);
	mwin.setMessage(message, allowHTML);
	if (show) {
		mwin.show();
	}
	return mwin;
};
WJWindow.alert = function(message, callback, translate, allowHTML) {
	return WJWindow._messagedialog("WJWindowAlert", message, callback, true, translate, allowHTML);
};
WJWindow.notice = function(message, callback, translate, allowHTML) {
	return WJWindow._messagedialog("WJWindowNotice", message, callback, true, translate, allowHTML);
};
WJWindow.confirm = function(message, callback, translate, allowHTML) {
	return WJWindow._messagedialog("WJWindowConfirm", message, callback, true, translate, allowHTML);
};
WJWindow.booleanConfirm = function(message, callback, translate, allowHTML) {
	return WJWindow._messagedialog("WJWindowBooleanConfirm", message, callback, true, translate, allowHTML);
};
WJWindow.prompt = function(message, callback, translate, allowHTML) {
	return WJWindow._messagedialog("WJWindowPrompt", message, callback, true, translate, allowHTML);
};

/**
 * WJWindowModal
 *
 * The base class to decorate a window with modal properties
 *
 * @since Fri Jun 27 2008
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Aeroplane
 **/
var WJWindowModal = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new instanceof WJWindowModal
	 *
	 * @since Fri Jul 4 2008
	 * @access public
	 * @param WJWindow wjwindow
	 * @return WJWindowModal
	 **/
	initialize: function(toDecorate) {
		this._decorate(toDecorate);
		this._createModalLayer();
		this._maxblink = 6;
		this._addBlinkListener();
		this._rebindListeners();
		this._rebindWindowObjectGetters();
		this.setZ(2147483647);
		this.show(); // Remove this
	},

	/**
	 * _decorate
	 *
	 * Decorates the given object
	 *
	 * @since Tue Jul 8 2008
	 * @access protected
	 * @param WJWindow toDecorate
	 * @return void
	 **/
	_decorate: function(toDecorate) {
		this._decorated = toDecorate;

		for (property in this._decorated) {
			// Add all methods not defined in this
			if (!Object.isFunction(this[property]) ) {
				this[property] = this._decorated[property];
			}
		}
	},

	/**
	 * _rebindListeners
	 *
	 * Stops the listeners observing and rebinds 'this' to the listeners
	 *
	 * @since Mon Jul 7 2008
	 * @access protected
	 * @return void
	 **/
	_rebindListeners: function() {
		this.removeListeners();
		this._addDefaultListeners();
	},

	/**
	 * _createModalLayer
	 *
	 * Creates a layer to simulate modality
	 *
	 * @since Mon Jul 7 2008
	 * @access protected
	 * @return void
	 **/
	_createModalLayer: function() {
		var windowElement = this._decorated.getWindowElement();
		
		this._modalLayer = new WJModalLayer();
		this._modalLayer.getLayer().insert(windowElement);
		this._outerElement = this._modalLayer.getLayer();
	},

	/**
	 * show
	 *
	 * Shows the window
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @return void
	 **/
	show: function(element) {
		var element = element || this._outerElement || this._modalLayer.getLayer();
		element.style.display = "block";
		this._setBodyOverflow("hidden");
		this._decorated.show();
	},

	/**
	 * hide
	 *
	 * Hides the window
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @return void
	 **/
	hide: function(element) {
		var element = element || this._outerElement || this._modalLayer.getLayer();
		element.style.display = "none";
		this._setBodyOverflow();
		this._decorated.hide();
	},

	/**
	 * _setBodyOverflow
	 *
	 * Set's the overflow property of body (and html for IE7)
	 *
	 * @since Mon Jul 7 2008
	 * @access protected
	 * @param string value
	 * @return void
	 **/
	_setBodyOverflow: function(value) {
		var value = value || "";
		$(document.body).setStyle({overflow: value});
		$(document.getElementsByTagName("html")[0]).setStyle({overflow: value});
	},

	/**
	 *
	 *
	 *
	 *
	 * @since Thu Jul 10 2008
	 * @access
	 * @param
	 * @return
	 **/
	_addBlinkListener: function() {
		Event.observe(this._modalLayer.getLayer(), "click", this.blink.bindAsEventListener(this) );
	},

	/**
	 *
	 *
	 *
	 *
	 * @since Thu Jul 10 2008
	 * @access
	 * @param
	 * @return
	 **/
	blink: function(event, doBlink, count) {
		var count = count || 0;
		if (Event.element(event).className == this._getBaseClassname() + "_modality") {
			if (doBlink) {
				this.getWindowElement().addClassName(this._getBaseClassname() + "_blink");
				doBlink = false;
			}
			else {
				this.getWindowElement().removeClassName(this._getBaseClassname() + "_blink");
				doBlink = true;
			}
			if (count < this._maxblink) {
				count++
				this.blink.bind(this, event, doBlink, count).delay(0.05);
			}
		}
	},

	/**
	 * _rebindWindowObjectGetters
	 *
	 * Adds a getWJWindowObject getter to all content elements and the main window element and binds it to this not the decorated window
	 *
	 * @since Fri Feb 13 2009
	 * @access protected
	 * @return void
	 **/
	_rebindWindowObjectGetters: function() {
		var els = this._decorated._addWindowObjectGetters();
		els.each(function(el) {
			el.getWJWindowObject = el.getWJWindowObject.bind(this);
		}, this);
		return els;
	}
});

/**
 * WJWindowMessageDialog
 *
 * A class handling modal alert messages
 *
 * @since Fri Jun 27 2008
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Aeroplane
 **/
var WJWindowMessageDialog = Class.create(WJWindowModal, {
	/**
	 * initialize
	 *
	 * Creates a new WJWindowMessageDialog
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @param Class $super
	 * @param Function callback
	 * @param string type
	 * @return WJWindowMessageDialog
	 **/
	initialize: function($super, toDecorate) {
		$super(toDecorate);
		this.center();
		this.keepCentered();
		this._drawDefaultContent();
		this._buttons = null;
		this._addButtons();
	},

	/**
	 * _drawDefaultContent
	 *
	 * Draws the default alert content
	 *
	 * @since Tue Jul 8 2008
	 * @access protected
	 * @return void
	 **/
	_drawDefaultContent: function() {
		var content = this.getContentElement("main");
		var replaces = this._getTemplateValues();
		var template = this._getTemplate();
		content.innerHTML = template.evaluate(replaces);
		this._contentElements["message"] = content.down("." + replaces.classprefix+"_message");
	},

	/**
	 *
	 *
	 *
	 *
	 * @since Wed Jul 9 2008
	 * @access
	 * @param
	 * @return
	 **/
	_getTemplateValues: function() {
		return {"classprefix": this._getBaseClassname(), "windowtype": this._type};
	},

	/**
	 * _addButtons
	 *
	 * Adds the right buttons
	 *
	 * @since Wed Jul 9 2008
	 * @access protected
	 * @return void
	 **/
	_addButtons: function() {
		if (this._buttons == null) {
			this._buttons = new Hash();
			this._buttons.set("ok", WJButton.create(this._decorated.translate("OK"), "true", true, this.getContentElement("buttons") ) ).focus();
		}
		return this._buttons;
	},

	/**
	 * _getMainTemplate
	 *
	 *
	 *
	 * @since Tue Jul 8 2008
	 * @access protected
	 * @return Template
	 **/
	_getTemplate: function() {
		return new Template("<div class='#{classprefix}_messageicon #{classprefix}_messageicon_#{windowtype}'>&#160;</div><div class='#{classprefix}_message'>&#160;</div>");
	},

	/**
	 * setMessage
	 *
	 * Sets the message for this window
	 *
	 * @since Tue Jul 8 2008
	 * @access public
	 * @param string message
	 * @return void
	 **/
	setMessage: function(message, allowHTML) {
		var allowHTML = !!allowHTML;
		if (!allowHTML) {
			message = message.stripTags();
		}
		this.message = message.stripScripts().split("\n").join("<br/>");
		this.getContentElement("message").innerHTML = this.message;
	},

	/**
	 * _callback
	 *
	 * Does the callback that this window should do (and hides the message)
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @return mixed
	 **/
	_callback: function() {
		this.hide();
		return this._decorated._callback.apply(this._decorated, $A(arguments) );
	}
});

/**
 * WJWindowAlert
 *
 * A class handling modal alert messages
 *
 * @since Fri Jun 27 2008
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Aeroplane
 **/
var WJWindowAlert = Class.create(WJWindowMessageDialog, {
	/**
	 * initialize
	 *
	 * Creates a new WJWindowAlert
	 *
	 * @since Wed Jul 9 2008
	 * @access public
	 * @param WJWindow toDecorate
	 * @return WJWindowAlert
	 **/
	initialize: function($super, toDecorate) {
		if (!this._type) {
			this._type = "alert";
		}
		$super(toDecorate);
		this.getListener("close").callback.setArgument(0, true); // closing an alert means ok
		this.removeListener("false");// an alert can only be true
		this.removeListener("save");
		this.removeListener("cancel");
	}
});

/**
 * WJWindowConfirm
 *
 * A class handling modal confirm messages
 *
 * @since Fri Jun 27 2008
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Aeroplane
 **/
var WJWindowConfirm = Class.create(WJWindowMessageDialog, {
	/**
	 * initialize
	 *
	 * Creates a new WJWindowConfirm
	 *
	 * @since Wed Jul 9 2008
	 * @access public
	 * @param WJWindow toDecorate
	 * @return WJWindowAlert
	 **/
	initialize: function($super, toDecorate) {
		if (typeof(this._type) == "undefined") {
			this._type = "confirm";
		}
		$super(toDecorate);
	},
	
	/**
	 * _addButtons
	 *
	 * Adds the right buttons
	 *
	 * @since Wed Jul 9 2008
	 * @access protected
	 * @return void
	 **/
	_addButtons: function($super) {
		this._buttons = $super();
		this._buttons.set("cancel", WJButton.create(this._decorated.translate("CANCEL"), "false", false, this.getContentElement("buttons") ) );
		return this._buttons;
	}
});

/**
 * WJButton
 *
 * A class handling the creation of buttons
 *
 * @since Wed Jul 09 2008
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Aeroplane
 **/
var WJButton = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new WJButton
	 *
	 * @since Wed Jul 9 2008
	 * @access public
	 * @param string caption
	 * @param Function|string event
	 * @param string type
	 * @return WJButton
	 **/
	initialize: function(caption, eventHandler, defaultButton, parentElement) {
		WJDebugger.log(WJDebugger.INFO, "Create new button", caption, eventHandler, defaultButton, parentElement);
		this._caption = caption;
		this._eventHandler = eventHandler;
		this._defaultButton = (defaultButton === true) ? true : false;
		this._parentElement = $(parentElement);
		
		this._buildEventHandler();
		this._createButton();
		this._addButtonMethods();
		this._addObserver();
		
		if (Object.isElement(this._parentElement) ) {
			this._parentElement.appendChild(this.getButton() );
		}
		this.updateCaption(this._caption);
	},

	/**
	 * _addButtonMethods
	 *
	 * Adds methods to the element to easy update the caption and enable or disable the button
	 *
	 * @since Thu Jul 10 2008
	 * @access protected
	 * @return void
	 **/
	_addButtonMethods: function() {
		this.getButton().updateCaption = this.updateCaption.bind(this);
		this.getButton().setCaption = this.getButton().updateCaption;
		this.getButton().getCaption = this.getCaption.bind(this);
		this.getButton().enable = this.enable.bind(this);
		this.getButton().disable = this.disable.bind(this);
	},

	/**
	 * updateCaption
	 *
	 * A method that updates the caption content of the button (and resizes the button if needed)
	 *
	 * @since Thu Jul 10 2008
	 * @access public
	 * @param string caption
	 * @return Element
	 **/
	updateCaption: function(caption) {
		this._caption = caption.stripTags().stripScripts();
		return this._setCaption.bind(this).defer();
	},

	/**
	 * setCaption
	 *
	 * Changes the caption of this button
	 *
	 * @since Mon Feb 16 2009
	 * @access public
	 * @param string caption
	 * @return Element
	 **/
	setCaption: function(caption) {
		this.updateCaption(caption);
	},

	/**
	 * _setCaption
	 *
	 * Performs the real setting of the caption (it used to be called with a defer from initialize, now it's deferred always (@see updateCaption), to avoid it being updated later by old calls)
	 *
	 * @since Fri Feb 13 2009
	 * @access protected
	 * @return void
	 **/
	_setCaption: function() {
		var contentElement = this.getContentElement();
		contentElement.update(this._caption);
		this.setWidth(this._getNewButtonWidth(this._caption, contentElement) );
		return this.getButton();
	},

	/**
	 * enable
	 *
	 * Enables the button
	 *
	 * @since Mon Nov 10 2008
	 * @access public
	 * @return this
	 **/
	enable: function() {
		this.getButton().removeAttribute("disabled");
		this.getButton().removeClassName("wjgui_button_disabled");
		return this;
	},

	/**
	 * disable
	 *
	 * Disables the button
	 *
	 * @since Mon Nov 10 2008
	 * @access public
	 * @return this
	 **/
	disable: function() {
		this.getButton().disabled = "disabled";
		this.getButton().addClassName("wjgui_button_disabled");
		return this;
	},

	/**
	 * getContentElement
	 *
	 * Returns the container of the content
	 *
	 * @since Tue Aug 19 2008
	 * @access public
	 * @return Element
	 **/
	getContentElement: function() {
		return this.getButton().down("." + this._getBaseClassName() + "_content");
	},

	/**
	 * getNewButtonWidth
	 *
	 * Returns the width needed to fit the content on the button
	 *
	 * @since Tue Aug 19 2008
	 * @access protected
	 * @param string text
	 * @param Element element
	 * @return integer
	 **/
	_getNewButtonWidth: function(text, element) {
		var fontfamily = element.getStyle("fontFamily");
		var fontsize = element.getStyle("fontSize");
		var fontweight = element.getStyle("fontWeight");
		WJButton.measureElement.setStyle({"fontFamily": fontfamily, "fontSize": fontsize, "fontWeight": fontweight}).update(text);
		var width = document.body.appendChild(WJButton.measureElement).getWidth();
		WJButton.measureElement.remove();
		width += this.getWidth() - element.getWidth();
		return (width > 100 || this.getButton().up("div.autowidth", 0) ) ? width : 100;
	},

	/**
	 * getWidth
	 *
	 * Tells the width of the button
	 *
	 * @since Mon Aug 18 2008
	 * @access public
	 * @return integer
	 **/
	getWidth: function() {
		return this.getButton().getWidth();
	},

	/**
	 * setWidth
	 *
	 * Sets the width of the button
	 *
	 * @since Mon Aug 18 2008
	 * @access public
	 * @param integer width
	 * @return void
	 **/
	setWidth: function(width) {
		this.getButton().setStyle({"width": width + "px"});
	},
	
	/**
	 * _buildEventHandler
	 *
	 * Creates a function to use as click event handler
	 *
	 * @since Wed Jul 9 2008
	 * @access protected
	 * @return void
	 **/
	_buildEventHandler: function() {
		if (!Object.isFunction(this._eventHandler) ) {
			if (this._eventHandler.indexOf(":") == -1) {
				this._eventHandler = this._getEventPrefix() + ":" + this._eventHandler;
				WJDebugger.log(WJDebugger.INFO, "Build event handler for this, create event " + this._eventHandler);
				var func = function(event, eventHandler) {
					WJDebugger.log(WJDebugger.INFO, "Fire event in WJButton", event, eventHandler);
					Event.element(event).fire(eventHandler);
				}.bindAsEventListener(this.getButton(), this._eventHandler);
				this._eventHandler = func;
			}
		}
	},
	
	/**
	 * _getEventPrefix
	 *
	 * Returns the namespacing prefix for the event to fire
	 *
	 * @since Wed Jul 9 2008
	 * @access protected
	 * @return string
	 **/
	_getEventPrefix: function() {
		return "wjgui";
	},
	
	/**
	 * _createButton
	 *
	 * Creates the button element
	 *
	 * @since Wed Jul 9 2008
	 * @access protected
	 * @return void
	 **/
	_createButton: function() {
		this._buttonElement = new Element("button");
		var template = this._getTemplate();
		var replaces = {};
		replaces.classprefix = this._getBaseClassName();

		this._buttonElement.addClassName(replaces.classprefix + " " + replaces.classprefix + "_" + ((this._defaultButton) ? "" : "no") + "default");

		this._buttonElement.innerHTML = template.evaluate(replaces);
	},

	/**
	 * _getBaseClassName
	 *
	 * Returns the base className for buttons
	 *
	 * @since Wed Jul 9 2008
	 * @access protected
	 * @return string
	 **/
	_getBaseClassName: function() {
		return "wjgui_button";
	},

	/**
	 * _getTemplate
	 *
	 * Returns a template element on which the button innerHTML is based
	 *
	 * @since Wed Jul 9 2008
	 * @access protected
	 * @return Template
	 **/
	_getTemplate: function() {
		return new Template("<div class='#{classprefix}_left #{classprefix}_column'><div class='#{classprefix}_right #{classprefix}_column'><div class='#{classprefix}_center #{classprefix}_column'><div class='#{classprefix}_content'>&#160;</div></div></div></div>");
	},

	/**
	 * getButton
	 *
	 * Returns the created button
	 *
	 * @since Wed Jul 9 2008
	 * @access public
	 * @return DOMElement
	 **/
	getButton: function() {
		return this._buttonElement;
	},
	
	/**
	 * _addObserver
	 *
	 * Adds an observer function to the button's click event
	 *
	 * @since Wed Jul 9 2008
	 * @access protected
	 * @return void
	 **/
	_addObserver: function() {
		WJDebugger.log(WJDebugger.INFO, "_addObserver in WJButton", this._eventHandler);
		WJDebugger.log(WJDebugger.DEBUG, "WJButton observer code", this._eventHandler.toString() );
		Event.observe(this.getButton(), "click", this._eventHandler);
	},

	/**
	 * setObserver
	 *
	 * Sets the current click observer for this button to eventHandler
	 *
	 * @since Tue Sep 16 2008
	 * @access public
	 * @param function eventHandler
	 * @return void
	 **/
	setObserver: function(eventHandler) {
		Event.stopObserving(this.getButton(), "click", this._eventHandler);
		this._eventHandler = eventHandler;
		this._addObserver();
	},

	/**
	 * getCaption
	 *
	 * Returns the value of teh caption
	 *
	 * @since Tue Feb 10 2009
	 * @access public
	 * @return string
	 **/
	getCaption: function() {
		return this._caption;
	},

	/**
	 * recalculateWidth
	 *
	 * Recalculates the width by updating the caption with the current caption
	 *
	 * @since Tue Feb 10 2009
	 * @access public
	 * @return void
	 **/
	recalculateWidth: function() {
		this.updateCaption(this.getCaption() );
	}
});

/**
 * an element to measure string length in context
 *
 * @since Tue Aug 19 2008
 * @access public
 **/
WJButton.measureElement = new Element("div", {"style": "position: absolute; left: -1000px;"});

/**
 * create
 *
 * Static method to create a single button
 *
 * @since Wed Jul 9 2008
 * @access public
 * @param string caption
 * @param Function|string eventHandler
 * @param boolean defaultButton
 * @param DOMElement element
 * @return DOMElement
 **/
WJButton.create = function(caption, eventHandler, defaultButton, parentElement) {
	var button = new WJButton(caption, eventHandler, defaultButton, parentElement);
	return button.getButton();
}

/**
 * toWJButtonStyle
 *
 * Styles a given button as a WJButton (NOTE: does not create a WJButton instance and does not attach methods and so on)
 *
 * @since Fri Mar 20 2009
 * @access public
 * @param Element element
 * @param boolean defaultButton
 * @return Element
 **/
WJButton.toWJButtonStyle = function(element, defaultButton) {
	if (element.tagName.toLowerCase() == "button") {
		var element = $(element);
		var caption = element.getTextContent();
		var template = WJButton.prototype._getTemplate();
		var replaces = {classprefix: WJButton.prototype._getBaseClassName()};

		element.className = replaces.classprefix + " " + replaces.classprefix + "_" + ((defaultButton) ? "" : "no") + "default";
		element.update(template.evaluate(replaces) );
		element.down("." + replaces.classprefix + "_content").update(caption);
	}
	return element;
}



/**
 * WJModalLayer is a class to create a modal layer on the page to disallow any user interaction other that on things on top op the layer
 **/
var WJModalLayer = Class.create({
	/**
	 * initialize
	 *
	 * Creates and applies a WJModalLayer
	 *
	 * @since Tue Jan 06 2009
	 * @access public
	 * @param Element parent
	 * @param Element modalLayer
	 * @return void
	 **/
	initialize: function(parent, modalLayer) {
		var parent = parent || document.body;
		this._modalLayer = (modalLayer || new Element("div") );
		Element.extend(this._modalLayer).addClassName("wjgui_window_modality");
		parent.appendChild(this._modalLayer);
		this._removed = false;
		this._absolutizeTopLeft();
		this._fillViewport();

		Event.observe(window, "resize", this._fillViewport.bind(this, this._modalLayer) ); // on purpose, no need to bind as event listener
	},

	/**
	 * _absolutizeTopLeft
	 *
	 * Puts the window in the top left corner of the viewport
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @param Element element
	 * @return void
	 **/
	_absolutizeTopLeft: function(element) {
		var element = this._modalLayer;
		element.absolutize();
		element.setStyle({left: 0, top: 0, zIndex: 2147483647});
	},

	/**
	 * _fillViewport
	 *
	 * Stretches the given element to fill the viewport
	 *
	 * @since Mon Jul 7 2008
	 * @access protected
	 * @param DOMElement element
	 * @return void
	 **/
	_fillViewport: function() {
		var element = $(this._modalLayer);
		if ($(element.parentNode) && $(element.parentNode).getHeight) {
			element.setStyle( {width: $(element.parentNode).getWidth() + "px", height: $(element.parentNode).getHeight() + "px"} );
		}
		else {
			element.setStyle( {width: document.viewport.getWidth() + "px", height: document.viewport.getHeight() + "px"} );
		}
	},

	/**
	 * getLayer
	 *
	 * Gets the layer element
	 *
	 * @since Tue Jan 06 2009
	 * @access public
	 * @return htmlelement
	 **/
	getLayer: function() {
		return this._modalLayer;
	},

	/**
	 * destroy
	 *
	 * Destroys this modal layer
	 *
	 * @since Tue Jan 06 2009
	 * @access public
	 * @return void
	 **/
	destroy: function() {
		if (!this._removed) {
			this._modalLayer.remove();
			this._removed = true;
		}
	}
});

/**
 * WJWindowBooleanConfirm
 *
 * A class handling modal confirm messages with Yes/No buttons
 *
 * @since Thu Jul 10 2008
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Aeroplane
 **/
var WJWindowBooleanConfirm = Class.create(WJWindowConfirm, {
	/**
	 * initialize
	 *
	 * Creates a new WJWindowBooleanConfirm
	 *
	 * @since Thu Jul 10 2008
	 * @access public
	 * @param WJWindow toDecorate
	 * @return WJWindowAlert
	 **/
	initialize: function($super, toDecorate) {
		this._type = "booleanconfirm";
		$super(toDecorate);
	},

	/**
	 * _addButtons
	 *
	 * Adds the right buttons
	 *
	 * @since Thu Jul 10 2008
	 * @access protected
	 * @return void
	 **/
	_addButtons: function($super) {
		this._buttons = $super();
		this._buttons.get("ok").updateCaption(this._decorated.translate("YES") );
		this._buttons.get("cancel").updateCaption(this._decorated.translate("NO") );
		return this._buttons;
	}
});

/**
 * WJWindowGooglemaps
 *
 * A class handling google maps info windows
 *
 * @since Fri Jan 30 2009
 * @revision $Revision$
 * @author Reyo Stallenberg
 * @package Windmill.Javascript.WJGui
 **/
var WJWindowGooglemaps = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new instanceof WJWindowModal
	 *
	 * @since Fri Jul 4 2008
	 * @access public
	 * @param WJWindow wjwindow
	 * @return WJWindowModal
	 **/
	initialize: function(toDecorate, kmlname, sitename) {
		this._decorate(toDecorate);
		this._addClassNames(kmlname, sitename);
		if (typeof(kmlname) != "undefined") {
			this.setTheme(kmlname);
		}
		this._addPushpinWindowConnector();
		this._saveStyleSettings();
	},

	/**
	 * addPushpinWindowConnector
	 *
	 * Adds a wrapper for the connector between the pushpin and the infowindow
	 *
	 * @since Mon Feb 2 2009
	 * @access protected
	 * @return void
	 **/
	_addPushpinWindowConnector: function() {
		this._pushpinwindowconnector = new Element("div", {"style": "position: absolute;"});
		this._pushpinwindowconnector.addClassName("pushpinwindowconnector");
		this.getWindowElement().insert(new Element("div", {"style": "position: relative; overflow: visible; height: 0px; width: 0px;"} ).insert(this._pushpinwindowconnector ) );
	},

	/**
	 * getPushpinWindowConnectorElement
	 *
	 * Gets the pushpinwindowconnector element
	 *
	 * @since Wed Feb 18 2009
	 * @access public
	 * @return DomNode
	 **/
	getPushpinWindowConnectorElement: function() {
		return this._pushpinwindowconnector;
	},

	/**
	 * _saveStyleSettings
	 *
	 * Saves some settings from the style
	 *
	 * @since Wed Feb 18 2009
	 * @access protected
	 * @return void
	 **/
	_saveStyleSettings: function() {
		var wasVisible = this.isVisible();
		var origX = this.getX();
		var origY = this.getY();
		if (!wasVisible) {
			this.setX(-10000);
			this.setY(-10000);
			this.show();
		}

		this._stylesettings = new Hash();
		var top = (this._pushpinwindowconnector.getStyle("top") != null) ? parseInt(this._pushpinwindowconnector.getStyle("top") ) : 0;
		this._stylesettings.set("pushpinwindowconnector-top", top);
		this._stylesettings.set("pushpinwindowconnector-left", parseInt(this._pushpinwindowconnector.getStyle("left") ) );
		this._stylesettings.set("pushpinwindowconnector-height", this._pushpinwindowconnector.getHeight() );

		if (!wasVisible) {
			this.hide();
			this.setX(origX);
			this.setY(origY);
		}
	},

	/**
	 * getStyleSetting
	 *
	 * Returns a saved style setting
	 *
	 * @since Wed Feb 18 2009
	 * @access
	 * @param
	 * @return mixed
	 **/
	getStyleSetting: function(setting) {
		return this._stylesettings.get(setting);
	},

	/**
	 * _addClassNames
	 *
	 * Adds classnames to the window element
	 *
	 * @since Mon Feb 02 2009
	 * @access protected
	 * @param string kmlname
	 * @param string sitename
	 * @return void
	 **/
	_addClassNames: function(kmlname, sitename) {
		var classname = this._getBaseClassname() + "_googlemaps";
		var element = this.getWindowElement()
		element.addClassName(classname);
		if (typeof(sitename) != "undefined") {
			element.addClassName(classname + "_" + sitename);
		}
	},

	/**
	 * _decorate
	 *
	 * Decorates the given object
	 *
	 * @since Fri Jan 30 2009
	 * @access protected
	 * @param WJWindow toDecorate
	 * @return void
	 **/
	_decorate: function(toDecorate) {
		this._decorated = toDecorate;

		for (property in this._decorated) {
			// Add all methods not defined in this
			if (!Object.isFunction(this[property]) ) {
				this[property] = this._decorated[property];
			}
		}
	},

	/**
	 * fireClose
	 *
	 * Fires the close event from the given element
	 *
	 * @since Fri Jan 30 2009
	 * @access public
	 * @param Element element
	 * @return void
	 **/
	fireClose: function(element) {
		this.hide();
	},

	/**
	 * getHeight
	 *
	 * Tells the height of this window, including the pushpinwindowconnector
	 *
	 * @since Fri Jan 30 2009
	 * @access public
	 * @return integer
	 **/
	getFullHeight: function() {
		return this.getHeight() + (this._stylesettings.get("pushpinwindowconnector-height") + this._stylesettings.get("pushpinwindowconnector-top"));
	}
});

/**
 * WJWindowNotice
 *
 * A class handling modal notice (friendly alert) messages
 *
 * @since Mon Jul 07 2008
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Aeroplane
 **/
var WJWindowNotice = Class.create(WJWindowAlert, {
	/**
	 * initialize
	 *
	 * Creates a new WJWindowNotice
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @param Class $super
	 * @param Function callback
	 * @param string type
	 * @return WJWindowNotice
	 **/
	initialize: function($super, toDecorate) {
		this._type = "notice";
		$super(toDecorate);
	}
});

/**
 * WJWindowPrompt
 *
 * A class handling modal prompt messages
 *
 * @since Fri Jun 27 2008
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Aeroplane
 **/
var WJWindowPrompt = Class.create(WJWindowMessageDialog, {
	/**
	 * initialize
	 *
	 * Creates a new WJWindowPrompt
	 *
	 * @since Wed Jul 9 2008
	 * @access public
	 * @param WJWindow toDecorate
	 * @return WJWindowAlert
	 **/
	initialize: function($super, toDecorate) {
		this._type = "prompt";
		$super(toDecorate);
		this.getInput().focus();
	},
	
	/**
	 * _getMainTemplate
	 *
	 * Adds the input element to the template
	 *
	 * @since Tue Jul 8 2008
	 * @access protected
	 * @return Template
	 **/
	_getTemplate: function($super) {
		var template = $super();
		template.template += "<input/>";
		return template;
	},
	
	/**
	 * _addButtons
	 *
	 * Adds the right buttons
	 *
	 * @since Wed Jul 9 2008
	 * @access protected
	 * @return void
	 **/
	_addButtons: function($super) {
		$super();
		WJButton.create(this._decorated.translate("CANCEL"), "false", false, this.getContentElement("buttons") );
	},

	/**
	 * windowResult
	 *
	 * Handles the window result event (in other words calls the callback with the result)
	 *
	 * @since Wed Jul 9 2008
	 * @access protected
	 * @param Event event
	 * @param mixed result
	 * @return void
	 **/
	windowResult: function(event, result) {
		if (result) {
			this._callback(event, this.getInput().value );
		}
		else {
			this._callback(event, "");
		}
	},

	/**
	 * getInput
	 *
	 * Returns the prompt input element
	 *
	 * @since Thu Jul 10 2008
	 * @access public
	 * @return DOMElement
	 **/
	getInput: function() {
		if (!this._input) {
			this._input = this.getWindowElement().getElementsByTagName("input")[0];
		}
		return this._input;
	}
});

/**
 * WJWrappedPane
 *
 * A class used to create wrapped panes
 *
 * @since Mon Feb 9 2009
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Aeroplane
 **/
var WJWrappedPane = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new WJWrappedPane
	 *
	 * @since Mon Feb 9 2009
	 * @access public
	 * @param Element element
	 * @return WJWrappedPane
	 **/
	initialize: function(element) {
		this.element = $(element);
		WJDebugger.log(WJDebugger.INFO, "Create new wrapped pane", element);
		$(this.element.parentNode).insertBefore(this._createWrapper(), this.element).down(".wjgui_wrappedpane_main").down(".wjgui_wrappedpane_content").update("").appendChild(this.element);
	},

	/**
	 * _createWrapper
	 *
	 * Returns a div that can be used as wrapper
	 *
	 * @since Mon Feb 9 2009
	 * @access protected
	 * @return Element
	 **/
	_createWrapper: function() {
		var template = new Template("<div class='wjgui_wrappedpane_#{rowname}'><div class='wjgui_wrappedpane_left wjgui_wrappedpane_column'><div class='wjgui_wrappedpane_right wjgui_wrappedpane_column'><div class='wjgui_wrappedpane_center wjgui_wrappedpane_column'><div class='wjgui_wrappedpane_content'>&#160;</div></div></div></div></div>");
		var content = ["top", "main", "bottom"].inject("", function(content, rowname) {
			return content + template.evaluate({rowname: rowname});
		});
		
		var wrappedpane =  new Element("div").update(content);
		wrappedpane.addClassName("wjgui_wrappedpane");
		return wrappedpane;

	}
});

/**
 * WMNewsletter is the JavaScript class to manage the newsletter editing
 *
 * Changelog
 * ---------
 *
 * Ron Rademaker Thu Jul 24 2008
 * -----------------------------
 * - Replaced ajaxEngine with WJSpin
 *
 * Niels Nijens Tue May 08 2007
 * -----------------------------
 * - Bugfix for special characters, made changes in formatText();
 *
 * Niels Nijens Tue May 08 2007
 * -----------------------------
 * - Added sendPreview(); and sendPreviewFeedback();
 *
 * Niels Nijens Tue May 08 2007
 * -----------------------------
 * - Added Wmconfirm as replacement for the standard JavaScript confirm boxes
 *
 * Niels Nijens Tue May 08 2007
 * -----------------------------
 * - Fixed IE bug (gah!) with editor preview frame
 * - Fixed IE bug by setting focus to the first input field of the editor form
 * - Renamed class to WMNewsletter, as it isn't only used to view a preview
 * - Added showPreview(); to view the newsletter preview in a seperate window
 *
 * Niels Nijens Fri May 04 2007
 * -----------------------------
 * - Rewritten _removeHTMLEditors(); to _setHTMLEditors(); to make it both add and remove TinyMCE instances
 * - Changed use of interval _setInterval() to applyWithInterval();
 * - Removed depreciated functions _setInterval(); and _clearInterval();
 * - Added confirm when clicking "Wijzig" while editor is open
 *
 * Niels Nijens Thu May 03 2007
 * -----------------------------
 * - Rewritten updateFrameSize(); to make sure it works
 * - Added updateEditor(); and getScrollTop(); to make sure the editor popup always appears on the screen
 *
 * Niels Nijens Tue May 01 2007
 * -----------------------------
 * - Added deleteArticle();
 * - Added formatText(); to fix a bug with &gt; and &lt;
 * - Added getDocumentHeight(); and updateFrameSize(); to make the previewFrame the same size as the preview (removing the scrollbar)
 *
 * Niels Nijens Fri Apr 27 2007
 * -----------------------------
 * - Made one function for all AJAX requests
 * - Removed depreciated function requestPreview();
 *
 * Todo
 * ---------
 * - Add a better window for confirm()'s with yes/no/cancel
 *
 * @since Fri Apr 13 2007
 * @author Niels Nijens (niels@connectholland.nl)
 * @package Windmill.Modules.Emill.Wmnewsletter
 **/
var WMNewsletter = Class.create();
WMNewsletter.prototype = {
	
	/**
	 * initialize
	 *
	 * Returns an instance of WMNewsletter
	 *
	 * @since Fri Apr 13 2007
	 * @access public
	 * @param Integer id
	 * @return WMNewsletter
	 **/
	initialize: function(id, ldap) {
		this.id = id;
		this.ldap = ldap;
		this.intervals = {};
		this.editorActive = false;
		this.processingRequest = false;
		
		this.initFrame();
		this.registerAjax();
		this.sendRequest("preview", {id: this.id});
	},
	
	/**
	 * initFrame
	 *
	 * Prepares the previewframe for content
	 *
	 * @since Fri Apr 13 2007
	 * @access public
	 * @return void
	 **/
	initFrame: function() {
		this.previewFrame = $("previewframe");
		this.previewFrame.setAttribute("src", "/var/loading.html");
		this.editorFrame = $("editorframe");
		this.editorFrame.setAttribute("src", "/var/loading.html");
	},
	
	/**
	 * registerAjax
	 *
	 * Registers the Objects and elements used for AJAX
	 *
	 * @since Fri Apr 13 2007
	 * @access public
	 * @return void
	 **/
	registerAjax: function() {
		this.spin = new WJSpin();
	},
	
	/**
	 * sendRequest
	 *
	 * Sends all AJAX requests from this class
	 *
	 * @since Fri Apr 27 2007
	 * @access public
	 * @param String mode
	 * @param String variables
	 * @return void
	 **/
	sendRequest: function(mode, variables) {
		if (!this.processingRequest) {
			this.processingRequest = true;

			variables.mode = mode;

			var url = new WJUrl(variables);
			url.setCt("editor");
			url.setDt("editor");
			url.addParameter("disableuft[]", new Array("emill", "ajaxerrors", "window") );

			this.spin.content(url, [this.ajaxUpdate.bind(this)], SpinErrorHandling);
		}
	},
	
	/**
	 * ajaxUpdate
	 *
	 * Handles the response document
	 *
	 * @since Fri Apr 13 2007
	 * @access public
	 * @param document ajaxResponse
	 * @return void
	 **/
	ajaxUpdate: function(ajaxResponse) {
		ajaxResponse = ajaxResponse.documentElement;
		var responses = ajaxResponse.getElementsByTagName("response");
		for (var i = 0; i < responses.length; i++) {
			this.handleResponse(responses[i], responses[i].getAttribute("mode") );
		}
	},
	
	/**
	 * handleResponse
	 *
	 * 
	 *
	 * @since Fri Apr 13 2007
	 * @access public
	 * @param DomNode xml
	 * @param String mode
	 * @return void
	 **/
	handleResponse: function(xml, mode) {
		this.processingRequest = false;
		if (xml.getAttribute("type") == "object") {
			switch (mode) {
				case "sendpreview":
					this.sendPreviewFeedback(xml);
					break;
				case "preview":
					this.populatePreviewFrame(xml);
					break;
				case "editor":
					this.populateEditor(xml);
					break;
				case "save":
					this.saveFeedback(xml);
					break;
			}
			if (xml.getAttribute("articleid") != null) {
				this.articleid = xml.getAttribute("articleid");
			}
		}
		else if (xml.getAttribute("type") == "element") {
			var addToElem = null;
			switch (xml.getAttribute("id") ) {
				case "editorform":
					addToElem = $("editorformcontainer");
					break;
				case "newarticle":
					addToElem = $("newarticleselect");
					break;
				case "sjabloonselect":
					addToElem = $("sjabloonselect");
					break;
			}
			
			if (addToElem) {
				try {
					addToElem.innerHTML = "";
					$A(xml.childNodes).each(addToElem.appendChild.bind(addToElem) );
					addToElem.innerHTML = addToElem.innerHTML;
				}
				catch (error) {
					var xmlContent = "";
					for (var i = 0; i < xml.childNodes.length; i++) {
						xmlContent += xml.childNodes[i].xml;
					}
					
					addToElem.innerHTML = xmlContent;
				}
			}
		}
	},
	
	/**
	 * sendPreviewFeedback
	 *
	 * Gives feedback to the user that the e-mail is sent
	 *
	 * @since Tue Jun 05 2007
	 * @access public
	 * @param DomNode xml
	 * @return void
	 **/
	sendPreviewFeedback: function(xml) {
		Wmconfirm.ask("Er is een voorbeeld nieuwsbrief naar uw e-mail adres"+ "<br/>(" + this.getNodeContent(xml) + ")" + " gestuurd.",  new Array(new Array("Ok", "") ) );
	},
	
	/**
	 * populatePreviewFrame
	 *
	 * Adds the content from xml to the previewframe
	 *
	 * @since Fri Apr 13 2007
	 * @access public
	 * @param DomNode xml
	 * @return void
	 **/
	populatePreviewFrame: function(xml) {
		if (!this.editorActive) {
			Element.setStyle("showpreview", {"display": "block"});
			Element.setStyle("sendpreview", {"display": "block"});
			
			if ($("newarticleselect").getElementsByTagName("div")[0].childNodes.length > 0) {
				Element.setStyle("newarticleselect", {"display": "block"});
			}
		}
		this.updateFrameInterval = this.updateFrameSize.bind(this).repeat(1);
		this.updateFrame("previewframe", xml);
	},
	
	/**
	 * populateEditor
	 *
	 * Adds the content from xml to the editorframe
	 *
	 * @since Mon Apr 16 2007
	 * @access public
	 * @param DomNode xml
	 * @return void
	 **/
	populateEditor: function(xml) {
		this.editorActive = true;
		sjabloonSelect = $("sjabloonselect");
		if (sjabloonSelect.innerHTML != "") {
			Element.setStyle("sjabloonselect", {"display": "block"});
		}
		else {
			Element.setStyle("sjabloonselect", {"display": "none"});
		}
		
		Element.setStyle("showpreview", {"display": "none"});
		Element.setStyle("newarticleselect", {"display": "none"});
		this.updateEditor();
		this.updateFrame("editorframe", xml);
		this._setHTMLEditors(true);
		
		this.editorInit = true;
		this.updateHistory = {};
		this.editorInterval = this._editorPreviewUpdate.bind(this).repeat(this, 0.1);
		if (this.updateId) {
			this.updateId = null;
			this.sendRequest("preview", {id: this.id});
		}
	},
	
	/**
	 * saveFeedback
	 *
	 * Removes the editorForm and reloads the newsletter preview
	 *
	 * @since Fri Apr 20 2007
	 * @access public
	 * @param DomNode xml
	 * @return void
	 * @todo change this function to get a better transition
	 **/
	saveFeedback: function(xml) {
		this._cleanUpEditor(true);
		
		if (this.updateId) {
			this.updateArticle(this.updateId);
		}
		this.sendRequest("preview", {id: this.id});
	},
	
	/**
	 * _editorPreviewUpdate
	 *
	 * Updates the editorframe with the content from the editorform
	 *
	 * @since Tue Apr 17 2007
	 * @access protected
	 * @return void
	 **/
	_editorPreviewUpdate: function() {
		editorForm = $("editorform");
		for (var i = 0; i < editorForm.length; i++) {
			if (editorForm.elements[i].name != "submit") {
				doc = this.getContentDocument("editorframe");
				if (doc.getElementById(editorForm.elements[i].name) ) {
					content = this._formatPreviewContent(editorForm.elements[i].name, editorForm.elements[i].getAttribute("objtype"), editorForm.elements[i].value);
					
					if (this.updateHistory[editorForm.elements[i].name] != content) {
						if (!this.editorInit) {
							doc.getElementById(editorForm.elements[i].name).innerHTML = content;
						}
						this.updateHistory[editorForm.elements[i].name] = content;
					}
				}
			}
		}
		this.editorInit = false;
	},
	
	/**
	 * _formatPreviewContent
	 *
	 * Returns the formatted content for the specified objtype
	 *
	 * @since Fri Apr 20 2007
	 * @access protected
	 * @param String name
	 * @param String objtype
	 * @param String content
	 * @return String content
	 **/
	_formatPreviewContent: function(name, objtype, content) {
		switch (objtype) {
			case "link":
				content = this._formatPreviewLink(content);
				break;
			case "email":
				content = this._formatPreviewEmail(content);
				break;
			case "text":
				content = this._formatPreviewText(name);
				break;
		}
		return content;
	},
	
	/**
	 * _formatPreviewLink
	 *
	 * Returns a formatted link
	 *
	 * @since Fri Apr 20 2007
	 * @access protected
	 * @param String link
	 * @return String
	 **/
	_formatPreviewLink: function(link) {
		if (content.indexOf("http://") != -1) {
			content = content.substr(7);
		}
		return "<a href='http://" + link + "'>" + link + "</a>";
	},
	
	/**
	 * _formatPreviewEmail
	 *
	 * Returns a formatted e-mail link
	 *
	 * @since Fri Apr 20 2007
	 * @access protected
	 * @param String email
	 * @return String
	 **/
	_formatPreviewEmail: function(email) {
		return "<a href='mailto:" + email + "'>" + email + "</a>";
	},
	
	/**
	 * _formatPreviewText
	 *
	 * Returns the formatted text of tinyMCE instance id
	 *
	 * @since Fri Apr 20 2007
	 * @access protected
	 * @param String text
	 * @return String text
	 **/
	_formatPreviewText: function(id) {
		htmleditor = tinyMCE.getInstanceById(id);
		text = htmleditor.getHTML();
		/*if (text.indexOf("<p>") == -1) {
			text = "<p>" + text + "</p>";
		}*/
		return text;
	},
	
	/**
	 * newArticle
	 *
	 * Requests the editor form / preview of a new article with AJAX
	 *
	 * @since Fri Apr 27 2007
	 * @access public
	 * @param String layout
	 * @return void
	 **/
	newArticle: function(layout) {
		this.sendRequest("editor", {id: this.id, __cms_Wmnewsletter: "true", __function_Wmnewsletter: "newarticle", layout: layout});
	},
	
	/**
	 * updateArticle
	 *
	 * Requests the editor form / preview with AJAX
	 *
	 * @since Mon Apr 16 2007
	 * @access public
	 * @param Integer id
	 * @param Boolean saveConfirm
	 * @return void
	 **/
	updateArticle: function(id, saveConfirm) {
		if (this.editorActive && saveConfirm == null) {
			Wmconfirm.ask("Wilt u de gemaakte wijzigingen opslaan?", new Array(new Array("Ja", "newsletter.updateArticle(" + id + ", true)"), new Array("Nee", "newsletter.updateArticle(" + id + ", false)"), new Array("Annuleren", "") ) );
		}
		else {
			if (saveConfirm) {
				this.updateId = id;
				this.save();
			}
			
			this._cleanUpEditor();
			if (id) {
				this.articleid = id;
			}
			this.sendRequest("editor", {id: this.articleid});
		}
	},
	
	/**
	 * updateImage
	 *
	 * Updates the image in the preview with the selected image
	 *
	 * @since Thu Apr 26 2007 (previously known as reloadArticle)
	 * @access public
	 * @return void
	 **/
	updateImage: function(url, tagname) {
		doc = this.getContentDocument("editorframe");
		if (doc.getElementById(tagname) ) {
			doc.getElementById(tagname).src = url;
		}
		
		if ( $("editorform")[tagname] ) {
			$("editorform")[tagname].value = url;
		}
	},
	
	/**
	 * updateLayout
	 *
	 * Updates the layout of the article
	 *
	 * @since Thu Apr 26 2007
	 * @access public
	 * @param String layout
	 * @return void
	 **/
	updateLayout: function(layout) {
		this._cleanUpEditor();
		
		variables = {id: this.articleid, __cms_Wmnewsletter: "true", __function_Wmnewsletter: "updatelayout", layout: layout};
		variables = this.prepareSave(variables);
		
		this.sendRequest("editor", variables);
	},
	
	/**
	 * moveArticle
	 *
	 * Moves an article up or down
	 *
	 * @since Thu Apr 26 2007
	 * @access public
	 * @param Integer id
	 * @param String action
	 * @return void
	 **/
	moveArticle: function(id, action) {
		if (id) {
			this.articleid = id;
		}
		
		this.sendRequest("preview", {__cms_Wmnewsletter: "true", __function_Wmnewsletter: "move" + action, id: this.id, part_id: this.articleid});
	},
	
	/**
	 * deleteArticle
	 *
	 * Deletes an article
	 *
	 * @since Tue May 01 2007
	 * @access public
	 * @param Integer id
	 * @param Boolean deleteConfirm
	 * @return void
	 **/
	deleteArticle: function(id, deleteConfirm) { //Deze actie kan niet ongedaan gemaakt worden.\n\n
		if (deleteConfirm == null) {
			Wmconfirm.ask("Weet u zeker dat u deze alinea wilt verwijderen?", new Array(new Array("Ja", "newsletter.deleteArticle(" + id + ", true)"), new Array("Nee", "newsletter.deleteArticle(" + id + ", false)") ) );
		}
		else if (deleteConfirm) {
			this.sendRequest("preview", {id: this.id, __cms_Wmnewsletter: "true", __function_Wmnewsletter: "deletearticle", part_id: id});
		}
	},
	
	/**
	 * save
	 *
	 * Saves a Newsletterpart with AJAX
	 *
	 * @since Tue Apr 17 2007
	 * @access public
	 * @return void
	 * @todo add feedback "Saving..."
	 **/
	save: function() {
		this._cleanUpEditor();
		
		variables = {__cms_Wmnewsletter: "true", __function_Wmnewsletter: "updatearticle", id: this.articleid};
		variables = this.prepareSave(variables);
		
		this.sendRequest("save", variables);
	},
	
	/**
	 * prepareSave
	 *
	 * Add the form element key / value pairs to variables
	 *
	 * @since Tue Apr 17 2007
	 * @access public
	 * @param object variables
	 * @return void
	 **/
	prepareSave: function(variables) {
		editorForm = $("editorform");
		for (var i = 0; i < editorForm.length; i++) {
			if (editorForm.elements[i].type == "textarea") {
				tinyMCE.triggerSave();
			}
			if (editorForm.elements[i].name != "submit") {
				variables[editorForm.elements[i].name] = this.formatText(editorForm.elements[i].value);
			}
		}
		return variables;
	},
	
	/**
	 * cancelSave
	 *
	 * Returns the user to the preview
	 *
	 * @since Thu May 03 2007
	 * @access public
	 * @return void
	 **/
	cancelSave: function() {
		this._cleanUpEditor(true);
		Element.setStyle("showpreview", {"display": "block"});
		if ($("newarticleselect").select("div")[0].innerHTML != "") {
			Element.setStyle("newarticleselect", {"display": "block"});
		}
	},
	
	/**
	 * showPreview
	 *
	 * Opens a new window with a preview of the newsletter (without editor functionality)
	 *
	 * @since Tue May 08 2007
	 * @access public
	 * @return void
	 **/
	showPreview: function() {
		window.open("/?ct=preview&dt=preview&uft[]=&id=" + this.id);
	},
	
	/**
	 * showPreviewPDF
	 *
	 * Opens a new window with a preview of the mailing
	 *
	 * @since Mon Oct 20 2008
	 * @access public
	 * @return void
	 **/
	showPreviewPDF: function() {
		window.open("/var/scripts/generatepdf.php?concept=1&id=" + this.id);
	},
	
	/**
	 * sendPreview
	 *
	 * Sends a preview to the user by e-mail
	 *
	 * @since Tue Jun 05 2007
	 * @access public
	 * @param DomElement emailField
	 * @return void
	 **/
	sendPreview: function(emailField) {
		if (this.ldap && emailField != undefined) {
			if ( $("emailpopup") ) {
				Element.setStyle("emailpopup", {display: "none"});
			}
			this.sendRequest("sendpreview", {id: this.id, additionalemails: emailField.value});
		}
		else if (this.ldap) {
			if ( $("emailpopup") ) {
				$("emailpopup").innerHTML = "<h2>Verstuur voorbeeld</h2><p>U kunt hier (indien gewenst) meerdere extra mail adressen toevoegen. </p><input id='additionalemails' type='text'/><input type='button' onclick='newsletter.sendPreview( $(\"additionalemails\") );' value='Verstuur'/>";
				Element.setStyle("emailpopup", {display: "block", visibility: "visible"});
			}
		}
		else {
			this.sendRequest("sendpreview", {id: this.id});
		}
	},
	
	/**
	 * getNodeContent
	 *
	 * Returns the contents of node
	 *
	 * @since Tue Apr 17 2007
	 * @access public
	 * @param DomNode node
	 * @return String
	 **/
	getNodeContent: function(node) {
		if (node.textContent) {
			return node.textContent;
		}
		else {
			return node.text;
		}
	},
	
	/**
	 * formatText
	 *
	 * Formats the string for saving
	 *
	 * @since Tue Apr 17 2007
	 * @access public
	 * @param String text
	 * @return String text
	 **/
	formatText: function(text) {
		/*text = text.replace(/&amp;/gi, "%26");
		text = text.replace(/&gt;/gi, "%3E");
		text = text.replace(/&lt;/gi, "%3C");
		text = text.replace(/#/gi, "%23");
		text = encodeURIComponent(text);*/
		return text;
	},
	
	/**
	 * updateFrame
	 *
	 * Writes the html to the Iframe specified by id
	 *
	 * @since Tue Apr 17 2007
	 * @access public
	 * @param String id
	 * @param documentElement xml
	 * @return void
	 **/
	updateFrame: function(id, xml) {
		var html = this.getNodeContent(xml.getElementsByTagName("preview")[0]);
		var iframedoc = this.getContentDocument(id);
		
		try {
			iframedoc.open();
			iframedoc.write(html);
			iframedoc.close();
			this.getBody(id).innerHTML = html;
		} catch (e) {
			this.getBody(id).createTextRange().pasteHTML(html);
		}
	},
	
	/**
	 * updateFrameSize
	 *
	 * Makes the previewframe the same size as the preview document, removing the scrollbar
	 *
	 * @since Tue May 01 2007
	 * @access public
	 * @param String id
	 * @return void
	 **/
	updateFrameSize: function(id) {
		if ($("previewframe") ) {
			defaultHeight = parseInt(Element.getHeight("previewframe") );
			documentHeight = parseInt(this.getDocumentHeight("previewframe") );
			
			Element.setStyle("previewframe", {"height": (documentHeight + 30) + "px" });
			if (defaultHeight == documentHeight || documentHeight < (defaultHeight + 100) ) {
				clearInterval(this.updateFrameInterval);
			}
		}
		else {
			clearInterval(this.updateFrameInterval);
		}
	},
	
	/**
	 * updateEditor
	 *
	 * 
	 *
	 * @since Thu May 03 2007
	 * @access public
	 * @return void
	 **/
	updateEditor: function() {
		if (this.getScrollTop() > 170) {
			position = this.getScrollTop() - 170;
		}
		else {
			position = 32;
		}
		
		Element.setStyle("editor", {"top": position + "px"});
		Element.setStyle("editorformcontainer", {"display": "block"});
		Element.setStyle("editor", {"display": "block"});
		editorForm = $("editorform");
		if (editorForm) {
			editorForm.elements[0].focus();
		}
	},
	
	/**
	 * getScrollTop
	 *
	 * Returns the amount scrolled down
	 *
	 * @since Thu May 03 2007
	 * @access public
	 * @return Integer
	 **/
	getScrollTop: function() {
		if (document.body.scrollTop != 0) {
			return document.body.scrollTop;
		}
		if (document.documentElement.scrollTop != 0) {
			return document.documentElement.scrollTop;
		}
		return 0;
	},
	
	/**
	 * getContentDocument
	 *
	 * Returns the contentDocument of the Iframe document with id
	 *
	 * @since Tue Apr 17 2007
	 * @access public
	 * @param String id
	 * @return DomDocument
	 **/
	getContentDocument: function(id) {
		iframe = $(id);
		if (iframe.contentDocument) { // Gecko
			return iframe.contentDocument;
		}
		else if (iframe.contentWindow.document) { // IE 6, 7
			return iframe.contentWindow.document;
		}
		else { // IE 5 (?)
			return window.frames[id].contentWindow.document;
		}
	},
	
	/**
	 * getBody
	 *
	 * Returns the body of the Iframe document with id
	 *
	 * @since Tue Apr 17 2007
	 * @access public
	 * @param String id
	 * @return Domnode
	 **/
	getBody: function(id) {
		iframe = $(id);
		if (iframe.contentWindow.document) { // Gecko
			return iframe.contentWindow.document.body;
		}
	},
	
	/**
	 * getDocumentHeight
	 *
	 * Returns height of the body of the Iframe document
	 *
	 * @since Tue May 01 2007
	 * @access public
	 * @param String id
	 * @return Integer
	 **/
	getDocumentHeight: function(id) {
		doc = this.getContentDocument(id);
		if (doc.documentElement && doc.documentElement.clientHeight > 0) {
			return doc.documentElement.clientHeight;
		}
		else {
			return doc.body.scrollHeight; // other Explorers
		}
	},
	
	_lockEditor: function() {
		
	},
	
	/**
	 * _cleanUpEditor
	 *
	 * Cleans the editor popup, so no errors will come up
	 *
	 * @since Thu May 03 2007
	 * @access protected
	 * @param Boolean full
	 * @return void
	 **/
	_cleanUpEditor: function(full) {
		this.editorActive = false;
		Element.setStyle("editorformcontainer", {"display": "none"});
		clearInterval(this.editorInterval);
		this._setHTMLEditors();
		
		if (full) {
			editorForm = $("editorformcontainer");
			editorForm.innerHTML = "";
			Element.setStyle("editor", {"display": "none"});
		}
	},
	
	/**
	 * _setHTMLEditors
	 *
	 * Adds or removes all tinyMCE instances from the editorform
	 *
	 * @since Fri May 04 2007 (previously known as _removeHTMLEditors)
	 * @access protected
	 * @param Boolean add
	 * @return void
	 **/
	_setHTMLEditors: function(add) {
		editorForm = $("editorform");
		if (editorForm) {
			for (var i = 0; i < editorForm.length; i++) {
				if (editorForm.elements[i].type == "textarea") {
					if (add) {
						tinyMCE.execCommand('mceAddControl', false, editorForm.elements[i].name);
					}
					else {
						tinyMCE.execCommand('mceRemoveControl', false, editorForm.elements[i].name);
					}
				}
			}
		}
	}
}

/**
 * CompanySearch
 *
 * Changelog
 * ---------
 *
 * Ron Rademaker Wed Jul 30 2008
 * -----------------------------
 * - Replaced ajaxEngine with WJSpin
 *
 * Niels Nijens - Tue Apr 22 2008
 * --------------------------------
 * - 
 *
 * To do
 * ---------
 * -
 *
 * @since Tue Apr 22 2008
 * @author Niels Nijens (niels@connectholland.nl)
 **/
var CompanySearch = Class.create({
	/**
	 * initialize
	 *
	 * Initialize a new CompanySearch
	 *
	 * @since initial
	 * @param string formname
	 * @param string searchfield
	 * @param string selectbutton
	 * @param string resultdiv
	 * @return void
	 **/
	initialize: function(formname, searchfield, selectbutton, resultdiv, resultid) {
		this.formname = formname;
		this.searchfield = searchfield;
		this.selectbutton = selectbutton;
		this.resultid = resultid;
		this.resultdiv = resultdiv;
		
		if ( $(this.formname) ) {
			Event.observe(window, "load", this.onLoaded.bindAsEventListener(this) );
		}
		if ( $(searchfield) ) {
			Event.observe( $(searchfield), "keyup", this.search.bindAsEventListener(this) );
			Event.observe( $(searchfield), "click", this.search.bindAsEventListener(this) );
		}
		if ( $(selectbutton) ) {
			Event.observe( $(selectbutton), "click", this.addCompany.bindAsEventListener(this) );
		}
		
	},
	
	// Thu
	onLoaded: function(event) {
		formElements = $(this.formname).elements;
		for (i = 0; i < formElements.length; i++) {
			if ( formElements[i].type != "hidden" && formElements[i].name != $(this.searchfield).name && ( $(this.selectbutton) && formElements[i].name != $(this.selectbutton).name) ) {
				Event.observe(formElements[i], "focus", this.closeSelector.bindAsEventListener(this) );
			}
		}
	},
	
	
	search: function(event) {
		value = $(this.searchfield).value;
		if (value.length > 0) {
			this.sendRequest("searchcompany", null, {"bedrijfsnaam" : $(this.searchfield).value} );
		}
		else {
			this.closeSelector(event);
		}
	},
	
	
	searchAll: function(event) {
		this.sendRequest("searchcompany", null, {"bedrijfsnaam": ""} );
	},
	
	
	sendRequest: function(mode, params, searchparams) {
		variables = {ct: "companyxml", dt: "companyxml", mode: mode, "disableuft[]": "ajaxerrors"};
		
		if (params != null) {
			for (var key in params) {
				variables[key] = params[key];
			}
		}
		
		if (searchparams != null) {
			for (name in searchparams) {
				variables["query[" + name + "]"] = searchparams[name];
			}
		}
		
		var spin = new WJSpin();
		var url = new WJUrl(variables);
		var callbacks = [this.ajaxUpdate.bind(this)];
		if (mode == "searchcompany") {
			callbacks.push($(this.resultdiv) );
			spin.content(url, callbacks, SpinErrorHandling);
		}
		else if (mode == "fillcompany") {
			emillNavigator.showLoader();
			spin.update(url, "Wmcompanymanagement", "addcontact", {contact_id: document.contactform["id"].value, id: this.selectedCompany}, callbacks, SpinErrorHandling);
		}
		else if (mode == "deletecompany") {
			emillNavigator.showLoader();
			spin.update(url, "Wmcompanymanagement", "deletecontact", {contact_id: document.contactform["id"].value, id: this.selectedCompany}, callbacks, SpinErrorHandling);
		}
	},
	
	ajaxUpdate: function(response) {
		if (response.documentElement) {
			switch (response.documentElement.getAttribute("mode") ) {
				case "searchcompany":
					if ( $(this.resultdiv) && response.documentElement.getAttribute("searchcount") > 0) {
						$(this.resultdiv).show();
					}
					break;
				case "fillcompany":
				case "deletecompany":
					emillNavigator.refresh();
					break;
			}
		}
		if (response.documentElement.getAttribute("mode") == null) {
			emillNavigator.refresh();
		}
	},
	
	select: function(id, value) {
		this.selectedCompany = id;
		$(this.searchfield).value = value;
		this.closeSelector(null);
		$(this.searchfield).focus();
	},
	
	
	closeSelector: function(event) {
		if ( $(this.resultdiv) ) {
			$(this.resultdiv).hide();
		}
	},

	addCompany: function() {
		if (this.selectedCompany) {
			this.sendRequest("fillcompany");
		}
		else {
			WJWindow.notice("Zoek en selecteer eerst een bedrijf.\nLet op: U kunt alleen reeds bekende bedrijven toevoegen aan een contactpersoon.", function() {} );
		}
		this.selectedCompany = null;
	},
	
	deleteCompany: function(companyid) {
		this.selectedCompany = companyid;
		this.sendRequest("deletecompany");
		this.selectedCompany = null;
	}
});

/**
 * FormSender
 *
 * Sends the contents of a form with AJAX
 *
 * Changelog
 * ---------
 *
 *  Ron Rademaker Fri Aug 01 2008
 *  -----------------------------
 *  - Replaces
 *
 * Niels Nijens - Fri Jun 13 2008
 * --------------------------------
 * - Ported from License to Test
 *
 * @since Fri Jun 13 2008
 * @author Niels Nijens (niels@connectholland.nl)
 **/
var FormSender = Class.create();
FormSender.prototype = {

	/**
	 * initialize
	 *
	 * Creates a new FormSender object
	 *
	 * @since initial
	 * @access public
	 * @return FormSender
	 **/
	initialize: function() {
	},

	/**
	 * send
	 *
	 * Sends the contents
	 *
	 * @since initial
	 * @access public
	 * @param string formname
	 * @param string ct
	 * @param string dt
	 * @param string responseElement
	 * @param boolean query
	 * @param array excludequery
	 * @return void
	 **/
	send: function(formname, ct, dt, responseElement, query, excludequery) {
		if (excludequery.length > 0) {
			excludequery = excludequery.join(",");
		}
		
		formVariables = this.getFormVariables(formname, query, excludequery);
		
		if (ct) {
			formVariables.ct = ct;
		}
		if (dt) {
			formVariables.dt = dt;
		}
		
		this.sendRequest(formname, formVariables, responseElement);
	},

	/**
	 * sendRequest
	 *
	 * Creates and sends the AJAX request
	 *
	 * @since initial
	 * @access public
	 * @param string formname
	 * @param array formVariables
	 * @param string responseElement
	 * @return void
	 **/
	sendRequest: function(formname, formVariables, responseElement) {
		if ( $(responseElement) ) {
			element = responseElement;
		}
		else {
			element = formname;
		}

		var spin = new WJSpin();
		var url = new WJUrl(formVariables);
		spin.content(url, [$(element)], SpinErrorHandling);
	},

	/**
	 * getFormVariables
	 *
	 * Returns an array with all the variables in a form
	 *
	 * @since initial
	 * @access public
	 * @param string formname
	 * @param boolean query
	 * @param array excludequery
	 * @return array inputVariables
	 **/
	getFormVariables: function(formname, query, excludequery) {
		var variables = $(formname).serialize(true);

		if (query) {
			variables["query[]"] = new Array();
		}

		for (var key in variables) {
			if (query && excludequery.search(key) == -1) {
				variables["query[]"].push(variables[key]);
			}
		}
		
		return variables;
	}
}

var formSender = new FormSender();


function showKenmerken(show) {
	if (parseInt(show) == 1) {
		document.getElementById("kenmerken").style.display = 'block';
	}
	else {
		document.getElementById("kenmerken").style.display = 'none';
	}
}

function toggleSelectAll() {
	var x;
	var toggle = null;
	
	directoryForm = document.getElementById('directoryform');
	for (x in directoryForm.elements) {
		if (directoryForm.elements[x].type == "checkbox" && toggle == null) {
			toggle = !directoryForm.elements[x].checked;
		}
		if (directoryForm.elements[x].type == "checkbox") {
			directoryForm.elements[x].checked = toggle;
		}
	}
}

function deleteFiles(){
	var x;
	var checked = false;
	
	directoryForm = document.getElementById('directoryform');
	for (x in directoryForm.elements) {
		if (directoryForm.elements[x].checked == true) {
			checked = true;
			break;
		}
	}
	if (checked == false) {
		alert("U heeft geen bestanden geselecteerd.\nSelecteer eerst bestanden.");
	}
	else {
		if (confirm("Weet u zeker dat u de geselecteerde bestanden wilt verwijderen?\n\nKlik op OK als u de bestanden wilt verwijderen.") ) {
			document.getElementById("cmsfunction").value = "deletefiles";
			directoryForm.submit();
		}
		else {
			directoryForm.reset();
		}
	}
}

function insertURL(url) {
	window.opener.InsertDialogField.value = url;
	top.close();
}

function gotoPage(count, total, link) {
	var offsetnew = (count * document.getElementById("paginanummer").value) - count;
	if (offsetnew < total && offsetnew >= 0) {
		emillNavigator.extendNavigate({offset: offsetnew});
	}
}

function showPopup(id) {
	document.getElementById('meerinfopopup').style.visibility = "visible";
	var spin = new WJSpin();
	var url = new WJUrl({ct: "ajaxtekst", dt: "helptekst", page: "help" + id});
	spin.content(url, [$("meerinfopopup")], SpinErrorHandling);
	return true;
}

/**
 * updateTimeField
 * 
 * Merges the data from the uren en minuten fields together and inserts it into the id field.
 *
 * @since Tue Apr 14 2009
 * @param string id
 * @return void
 **/
function updateTimeField(id) {
	var hourSelect = $("uren_" + id).selectedIndex;
	var hour = $("uren_" + id).options[hourSelect].text;
	var minSelect = $("minuten_" + id).selectedIndex;
	var min = $("minuten_" + id).options[minSelect].text;
	
	$(id).value = hour + ":" + min;
}
/**
 * PhotoSnapObject is the PhotoSnap SWF to Javascript communication Object
 *
 * @since Tue May 19 2009
 * @author Niels Nijens (niels@connectholland.nl)
 **/
var PhotoSnapObject = Class.create(SWFObject, {
	
	/**
	 * initialize
	 *
	 * Initialize a new ScanControlObject
	 *
	 * @since initial
	 * @param string swfname
	 * @param string swffile
	 * @param integer width
	 * @param integer height
	 * @param string bgcolor
	 * @param boolean wmode
	 * @param object swfvars
	 * @return void
	 **/
	initialize: function($super, swfname, swffile, width, height, bgcolor, swfvars) {
		$super(swfname, swffile, width, height, bgcolor, swfvars);
		
		this.contact = swfvars.contact;
		this.setButtons();
	},
	
	/**
	 * snap
	 *
	 * Creates a Bitmap of the current Video
	 *
	 * @since initial
	 * @return void
	 **/
	snap: function() {
		if (this.methodExists("snap") ) {
			$(this.getAttribute("swfname") ).snap();
			
			this.setButtons(true);
		}
		else {
			this.snap.bind(this).defer();
		}
	},
	
	/**
	 * clear
	 *
	 * Clears the Bitmap
	 *
	 * @since initial
	 * @return void
	 **/
	clear: function() {
		if (this.methodExists("clear") ) {
			$(this.getAttribute("swfname") ).clear();
			this.setButtons();
		}
		else {
			this.clear.bind(this).defer();
		}
	},
	
	/**
	 * save
	 *
	 * Saves the Bitmap
	 *
	 * @since initial
	 * @return void
	 **/
	save: function() {
		if (this.methodExists("save") ) {
			var photo = $(this.getAttribute("swfname") ).save();
			
			emillNavigator.navigateTo( {ct: "contacten", mode: "requested", id: this.contact, nav_active: emillNavigator.lastVariables["nav_active"], __cms_Wmcontactmanagement: "true", __function_Wmcontactmanagement: "addphoto", photo: photo} );
		}
	},
	
	/**
	 * setButtons
	 *
	 * Sets the needed WJButton's
	 *
	 * @since initial
	 * @param boolean snap
	 * @return void
	 **/
	setButtons: function(snap) {
		emillNavigator.clearButtons();
		if (!snap) {
			snap = false;
		}
		
		if (snap) {
			var button = new WJButton("Foto toevoegen", this.save.bind(this), true, emillWindow.getContentElement("buttons") );
			button = new WJButton("Opnieuw", this.clear.bind(this), false, emillWindow.getContentElement("buttons") );
		}
		else {
			button = new WJButton("Foto maken", this.snap.bind(this), !snap, emillWindow.getContentElement("buttons") );
		}
		
		button = new WJButton("Annuleren", emillNavigator.navigateTo.bind(emillNavigator, {ct: "contacten", mode: "requested", id: this.contact, nav_active: emillNavigator.lastVariables["nav_active"]} ), false, emillWindow.getContentElement("buttons") );
	}
});
/**
 * ScanControlObject is the ScanControl SWF to Javascript communication Object
 *
 * @since Mon Apr 27 2009
 * @author Niels Nijens (niels@connectholland.nl)
 **/
var ScanControlObject = Class.create(SWFObject, {
	
	/**
	 * initialize
	 *
	 * Initialize a new ScanControlObject
	 *
	 * @since initial
	 * @param string swfname
	 * @param string swffile
	 * @param integer width
	 * @param integer height
	 * @param string bgcolor
	 * @param boolean wmode
	 * @param object swfvars
	 * @return void
	 **/
	initialize: function($super, swfname, swffile, width, height, bgcolor, swfvars) {
		$super(swfname, swffile, width, height, bgcolor, swfvars);
		
		this.spin = new WJSpin();
		this.searchQueue = new Array();
		this.searchLock = false;
		this.activity = swfvars.activity;
		
		var button = new WJButton("Annuleren", emillNavigator.navigateTo.bind(emillNavigator, {ct: "activity", nav_active: emillNavigator.lastVariables["nav_active"]} ) );
	},
	
	/**
	 * data
	 *
	 * Called when data is received from the SWF
	 *
	 * @since initial
	 * @param string data
	 * @return void
	 **/
	data: function(data) {
		if ( $("passnumber") ) {
			$("passnumber").value = data;
		}
		
		if (!this.searchLock) {
			this.searchLock = true;
			this.searchNumber = data;
			
			this.requestContact(data);
		}
		else {
			this.searchQueue.push(data);
		}
	},
	
	/**
	 * error
	 *
	 * Called when an error is received from the SWF
	 *
	 * @since Thu May 28 2009
	 * @param string error
	 * @return void
	 **/
	error: function(error) {
		console.log("Scannen mislukt. Probeer opnieuw.");
	},
	
	/**
	 * searchPassnumber
	 *
	 * Searches a contact by passnumber
	 *
	 * @since initial
	 * @return void
	 **/
	searchPassnumber: function() {
		if ( $("passnumber") ) {
			this.data( $("passnumber").value);
		}
	},
	
	/**
	 * requestContact
	 *
	 * Requests a contact by passnumber
	 *
	 * @since initial
	 * @param string passnumber
	 * @return void
	 **/
	requestContact: function(passnumber) {
		$(document.body).setStyle({"cursor": "wait"});
		var url = this.getAcceptURL();
		
		this.spin.content(url, [ $("scandata"), this.onCheckContact.bind(this)], SpinErrorHandling);
	},
	
	/**
	 * getAcceptURL
	 *
	 * Sends a AJAX request to accept the contact with passnumber
	 *
	 * @since Mon May 18 2009
	 * @param boolean force
	 * @return WJUrl
	 **/
	getAcceptURL: function(force) {
		var url = new WJUrl();
		url.setCt("participationxml");
		url.setDt("emill");
		url.addParameter("mode", "scan");
		url.addParameter("__cms_Wmparticipant", "true");
		url.addParameter("__function_Wmparticipant", "add");
		url.addParameter("activiteit", this.activity);
		url.addParameter("passnumber", this.searchNumber);
		if (force) {
			url.addParameter("force", "true");
		}
		url.addParameter("disableuft[]", new Array("emill", "ajaxerrors", "window") );
		
		return url;
	},
	
	/**
	 * onCheckContact
	 *
	 * Checks if the contact was denied or accepted
	 *
	 * @since Mon May 18 2009
	 * @return void
	 **/
	onCheckContact: function() {
		var sheets = $("scandata").getElementsByClassName("pos_sheet");
		if (sheets.length > 0 && sheets[0].hasClassName("denied") ) {
			emillNavigator.clearButtons();
			
			var button = new WJButton("Ok", this.forceAcceptContact.bind(this), true, emillWindow.getContentElement("buttons") );
			button = new WJButton("Weigeren", this.denyContact.bind(this), false, emillWindow.getContentElement("buttons") );
			button = new WJButton("Annuleren", emillNavigator.navigateTo.bind(emillNavigator, {ct: "participation", "query[activiteit]": this.activity, nav_active: emillNavigator.lastVariables["nav_active"]} ), false, emillWindow.getContentElement("buttons") );
		}
		else {
			this.onAcceptContact.bind(this).delay(4);
		}
		$(document.body).setStyle({"cursor": "auto"});
	},
	
	/**
	 * forceAcceptContact
	 *
	 * Forces contact to be accepted
	 *
	 * @since Tue May 19 2009
	 * @return void
	 **/
	forceAcceptContact: function() {
		var url = this.getAcceptURL(true);
		
		this.spin.content(url, [ $("scandata"), this.onAcceptContact.bind(this)], SpinErrorHandling);
	},
	
	/**
	 * onAcceptContact
	 *
	 * Reset the buttons
	 *
	 * @since Fri May 01 2009
	 * @return void
	 **/
	onAcceptContact: function() {
		this.resetButtons();
		
		if (this.searchQueue.length > 0) {
			var number = this.searchQueue.pop();
			
			this.searchNumber = number;
			this.requestContact(number);
		}
		else {
			this.searchLock = false;
			$("scandata").innerHTML = "";
		}
	},
	
	/**
	 * denyContact
	 *
	 * Resets the scandata container
	 *
	 * @since Fri May 01 2009
	 * @return void
	 **/
	denyContact: function() {
		this.onAcceptContact();
	},
	
	/**
	 * resetButtons
	 *
	 * Resets the button container
	 *
	 * @since Fri May 01 2009
	 * @return void
	 **/
	resetButtons: function() {
		emillNavigator.clearButtons();
		button = new WJButton("Annuleren", emillNavigator.navigateTo.bind(emillNavigator, {ct: "participation", "query[activiteit]": this.activity, nav_active: emillNavigator.lastVariables["nav_active"]} ), false, emillWindow.getContentElement("buttons") );
	}
});

tinyMCEConf = {
	mode : "specific_textareas",
	language : "nl",
	theme : "advanced",
	textarea_trigger : "htmlarea",
	entity_encoding : "raw",
	cleanup_on_startup : true,
	force_br_newlines : true,
	force_p_newlines : false,
	convert_newlines_to_brs : false,
	relative_urls : true,
	remove_script_host : false,
	theme_advanced_toolbar_location : "top",
	theme_advanced_toolbar_align : "left",
	theme_advanced_buttons1 : "bold,underline,italic,|,anchor,link,unlink,|,undo,redo,|,charmap,removeformat,cleanup,|,code",
	theme_advanced_buttons2 : "",
	theme_advanced_buttons3 : "",
	invalid_elements : "font,span,div,strike,sub,sup,blockquote,hr",
	safari_warning : false
}
tinyMCE.init(tinyMCEConf);

var WJBlockLoader = Class.create();
/**
 * WJBlockLoader is the class to load block information in with ajax and pass it to a blockscheme
 *
 * @since Fri Aug 08 2008
 * @author Ron Rademaker
 **/
WJBlockLoader.prototype = {
	/**
	 * initialize
	 *
	 * Creates a new WJBlockLoader
	 *
	 * @since Fri Aug 08 2008
	 * @access public
	 * @param WJBlockscheme scheme
	 * @param WJBlockSaver saver
	 * @return void
	 **/
	initialize: function(scheme, saver) {
		this.scheme = scheme;
		this.blockSaver = saver;
		this.spin = new WJSpin();
		this.url = new WJUrl({ct: "wmdynamic", module: "Wmevent", type: "xml", mode: "blocks"});
	},

	/**
	 * loadBlocks
	 *
	 * Loads in blocks for event id at date date and adds them to the scheme
	 *
	 * @since Fri Aug 08 2008
	 * @access public
	 * @param integer event_id
	 * @return void
	 **/
	loadBlocks: function(event_id, event) {
		var date = $("dateselector").getValue();
		this.url.addParameter("id", event_id);
		this.url.addParameter("date", date);
		this.scheme.clearBlocks();

		this.spin.content(this.url, [this.addBlocks.bind(this)], SpinErrorHandling);
	},

	/**
	 * addBlocks
	 *
	 * Shows the blocks in the blockscheme
	 *
	 * @since Fri Aug 08 2008
	 * @access public
	 * @param document response
	 * @return void
	 **/
	addBlocks: function(response) {
		var blocks = response.getElementsByTagName("blockinfo");
		
		for (var i = 0; i < blocks.length; i++) {
			this.scheme.addBlock({col: blocks[i].getAttribute("start") - (7 * 4), row: this.blockSaver.getLocationRow(blocks[i].getAttribute("location_id") )}, {col: blocks[i].getAttribute("end") - (7 * 4), row: this.blockSaver.getLocationRow(blocks[i].getAttribute("location_id") )}, {id: blocks[i].getAttribute("id")});
		}
	}
};

var WJBlockSaver = Class.create();
/**
 * WJBlockSaver is the class to handle the saving of new and updated blocks as well as removing old blocks
 *
 * @since Thu Aug 07 2008
 * @author Ron Rademaker
 **/

WJBlockSaver.prototype = {
	/**
	 * initialize
	 *
	 * Creates a new WJBlockSaver
	 *
	 * @since Thu Aug 07 2008
	 * @access public
	 * @param integer event_id
	 * @param object blockSize
	 * @return void
	 **/
	initialize: function(event_id, blockSize, curdate) {
		this.event_id = event_id;
		this.blockSize = blockSize;
		this.locations = new Array(); // row - {location, html element} mapping
		this.curdate = curdate;
		this.saving = false;
		this.spin = new WJSpin();
		this.url = new WJUrl({ct: "wmdynamic", module: "Wmevent", type: "xml"});
	},

	/**
	 * registerLocation
	 *
	 * Registers a location at the next free row
	 *
	 * @since Fri Aug 08 2008
	 * @access public
	 * @param integer location_id
	 * @param string html_id
	 * @return void
	 **/
	registerLocation: function(location_id, html_id) {
		this.locations.push({location: location_id, htmlelement: $(html_id)});
	},

	/**
	 * addLocationClickEvent
	 *
	 * Adds a click event to the registered location html elements
	 *
	 * @since Thu Aug 21 2008
	 * @access public
	 * @param function locclick
	 * @return void
	 **/
	addLocationClickEvent: function(locclick) {
		for (var i = 0; i < this.locations.length; i++) {
			var loc = this.locations[i];
			loc.date = function() { return this.curdate; }.bind(this);
			var func = locclick.curry(loc);
			this.locations[i].htmlelement.observe("click", func);
		}
	},

	/**
	 * saveBlocks
	 *
	 * Saves updated and new blocks
	 *
	 * @since Thu Aug 07 2008
	 * @access public
	 * @param Array blocks
	 * @return void
	 **/
	saveBlocks: function(blocks) {
		for (var i = 0; i < blocks.length; i++) {
			this._saveBlock(blocks[i]);
		}
	},

	/**
	 * _saveBlock
	 *
	 * Saves the passed block
	 *
	 * @since Fri Aug 08 2008
	 * @access protected
	 * @param htmlelement block
	 * @return void
	 **/
	_saveBlock: function(block) { 
		if (block.blockData == null) {
			block.blockData = {};
		}
		this._setBlockTimesAndLocation(block);
		var cmsfunction = block.blockData.id ? "updateblock" : "addblock";
		this.saving = true;
		this.spin.update(this.url, "Wmevent", cmsfunction, block.blockData, [this.saveCompleted.bind(this, block)], SpinErrorHandling);
	},

	/**
	 * saveCompleted
	 *
	 * Callback to update the blockData of block after a save (ie. set the id)
	 *
	 * @since Fri Aug 08 2008
	 * @access public
	 * @param htmlelement block
	 * @param document response
	 * @return void
	 **/
	saveCompleted: function(block, response) {
		this.saving = false;
		var idNodes = response.getElementsByTagName("id");
		var active = $$(".pos_dateselectoractive").first();
		if (active) {
			active.addClassName("pos_dateselectorhasblocks").addClassName("stl_dateselectorhasblocks");
		}
		
		if (idNodes.length > 0) {
			var idNode = idNodes[0];
			block.blockData.id = idNode.textContent;
		}
	},

	/**
	 * setDate
	 *
	 * Changes the current date
	 *
	 * @since Fri Aug 08 2008
	 * @access public
	 * @return void
	 **/
	setDate: function() {
		this.curdate = $("dateselector").getValue();
	},

	/**
	 * getLocationRow
	 *
	 * Gets the row where the passed location is on
	 *
	 * @since Fri Aug 08 2008
	 * @access public
	 * @param integer location_id
	 * @return integer
	 **/
	getLocationRow: function(location_id) {
		for (var i = 0; i < this.locations.length; i++) {
			if (this.locations[i].location == location_id) {
				return (i+1);
			}
		}
	},

	/**
	 * _setBlockTimesAndLocation
	 *
	 * Sets the times and location of a block
	 *
	 * @since Fri Aug 08 2008
	 * @access public
	 * @param htmlelement block
	 * @return void
	 **/
	_setBlockTimesAndLocation: function(block) {
		var time = {start: (7 * 60) + ( (block.offsetLeft / this.blockSize.width) * 15), end: (7 * 60) + ( ( (block.offsetLeft + block.getWidth() )/ this.blockSize.width) * 15)}; // start and end in minutes since midnight
		var location_id = this.locations[(block.offsetTop / this.blockSize.height)].location;
		block.blockData.start = Math.floor(time.start / 60) + ":" + ( ( (time.start % 60) == 0) ? "00" : (time.start % 60) );
		block.blockData.end = Math.floor(time.end / 60) + ":" + ( ( (time.end % 60) == 0) ? "00" : (time.end % 60) );
		block.blockData.location_id = location_id;
		block.blockData.event_id = this.event_id;
		block.blockData.date = this.curdate;
	},

	/**
	 * saveMovedBlock
	 *
	 * Saves the changed location of a block
	 *
	 * @since Thu Aug 07 2008
	 * @access public
	 * @param draggable draggable
	 * @return void
	 **/
	saveMovedBlock: function(draggable) {
		this._saveBlock(draggable.element);
	},

	/**
	 * editBlockProperties
	 *
	 * Shows a popup with properties of this div and makes it possible to update that information
	 *
	 * @since Thu Aug 07 2008
	 * @access public
	 * @param event e
	 * @return void
	 **/
	editBlockProperties: function(e) {
		if (!this.saving) {
			var blockWindow = new WJWindow(this.blockWindowEventListener.bind(this) );
			var width = 500;
			var height = 450;
			blockWindow.setTitle("Details Blok");
			
			var x = e.pointerX() - (width / 2);
			if (x < 0) {
				x = 0;
			}
			else if ( (x + width) > document.viewport.getWidth() ) {
				x = document.viewport.getWidth() - (width + 10);
			}
			blockWindow.setX(x);
			blockWindow.setY(e.pointerY() - (height / 2) );
			blockWindow.setWidth(width);
			blockWindow.setHeight(height);
			blockWindow.setZ(100000);
			blockWindow.show();

			this.url.setDt("blockform");
			this.url.addParameter("block_id", e.target.parentNode.blockData.id);
			this.url.addParameter("mode", "updateBlock");
			this.url.addParameter("uft[]", "event");
			this.spin.content(this.url, [blockWindow.getContentElement("main"), function() {blockWindow.getContentElement("main").select(".pos_calendar").invoke("hide"); }, this.addButtons.bind(this, blockWindow)]);
			this.url.deleteParameter("dt");
			this.url.deleteParameter("block_id");
			this.url.deleteParameter("mode");
			this.url.deleteParameter("uft[]");
		
		}
	},

	/**
	 * registerLoader
	 *
	 * Registers the blockloader (so the blocksaver can issue a reload)
	 *
	 * @since Wed Aug 13 2008
	 * @access public
	 * @param WJBlockLoader blockloader
	 * @return void
	 **/
	registerLoader: function(blockloader) {
		this.blockloader = blockloader;
	},

	/**
	 * addButtons
	 *
	 * Adds the save button to the blockWindow
	 *
	 * @since Tue Aug 12 2008
	 * @access public
	 * @param WJWindow blockWindow
	 * @return void
	 **/
	addButtons: function(blockWindow) {
		blockWindow.addButton("Opslaan", "save", true);
		blockWindow.addButton("Verwijderen", "delete");
		blockWindow.addButton("Annuleren", "close");
	},

	/**
	 * blockWindowEventListener
	 *
	 * Closes the edit block properties window
	 *
	 * @since Tue Aug 12 2008
	 * @access public
	 * @param WJWindow window
	 * @param event e
	 * @return void
	 **/
	blockWindowEventListener: function(window, e) {
		switch (e.eventName) {
			case "wjgui:close":
				window.destroy();
				break;
			case "wjgui:save":
				this.spin.update(this.url, "Wmevent", "updateBlockDetails", $(document.forms.blockform).serialize(true), [this.blockloader.loadBlocks.bind(this.blockloader, this.event_id, this.curdate)]);
				window.destroy();
				break;
			case "wjgui:delete":	
				this.spin.update(this.url, "Wmevent", "deleteBlock", {block_id: $(document.forms.blockform).serialize(true).block_id}, [this.blockloader.loadBlocks.bind(this.blockloader, this.event_id, this.curdate)]);
				window.destroy();
				break;
		}
	}
};

/**
 * WJBlockscheme class for blockscheme interaction
 *
 * @since Wed Aug 06 2008 (start of complete rebuild)
 * @author Ron Rademaker
 **/
var WJBlockscheme = Class.create();
WJBlockscheme.prototype = {
	/**
	 * initialize
	 *
	 * Creates a new WJBlockscheme for the div identified by id
	 *
	 * @since Wed Aug 06 2008
	 * @access public
	 * @param string id
	 * @param object options
	 * @return void
	 **/
	initialize: function(id, options) {
		if (!$(id) ) {
			throw new Error("Unable to create a WJBlockscheme for " + id + ", no such element");
		}
		this.id = id;
		this._setDefaultOptions();
		for (var option in options) {
			this[option] = options[option];
		}
		this._draw();
	},

	/**
	 * addBlock
	 *
	 * Draws a new block from cell start to cell end, piggybacks data on the created block
	 *
	 * @since Thu Aug 07 2008
	 * @access public
	 * @param object start
	 * @param object end
	 * @param object data
	 * @return void
	 **/
	addBlock: function(start, end, data) {
		this.blocks = new Array(this._createBlock({x: (start.col * this.blockSize.width), y: ( (start.row - 1) * this.blockSize.height)}, {width: ( (end.col - start.col) * this.blockSize.width), height: ( (end.row - start.row + 1) * this.blockSize.height)}) );
		this.blocks[0].blockData = data;
		this._startBlocksDraggable();
	},

	/**
	 * clearBlocks
	 *
	 * Removes all current blocks
	 *
	 * @since Fri Aug 08 2008
	 * @access public
	 * @return void
	 **/
	clearBlocks: function() {
		var mainclass;
		if (this.blockClass.indexOf(" ") > -1) {
			mainclass = this.blockClass.substr(0, this.blockClass.indexOf(" ") );
		}
		else {
			mainclass = this.blockClass;
		}
		if (mainclass != "") { 
			$$("." + mainclass).invoke("remove");
		}
		
		var active = $$(".pos_dateselectoractive").first();
		if (active) {
			active.removeClassName("pos_dateselectorhasblocks").removeClassName("stl_dateselectorhasblocks");
		}
	},

	/**
	 * getBlocks
	 *
	 * Retrieves all htmlelements for blocks
	 *
	 * @since Thu Aug 07 2008
	 * @access public
	 * @return void
	 **/
	getBlocks: function() {
		return $(this.id).select("." + this.blockClass);
	},

	/**
	 * _setDefaultOptions
	 *
	 * Sets the default options for a WJBlockscheme, override by passing an option in an object as a second 
	 *
	 * @since Thu Aug 07 2008
	 * @access protected
	 * @return void
	 **/
	_setDefaultOptions: function() {
		this.blockSize = {"width": 10, "height": 10};
		this.constraint = "none";
		this.dimensions = {"top": 0, "left": 0, "width": $(this.id).getWidth(), "height": $(this.id).getHeight()};
		this.borders = {"color": "#000000", "top": true, "bottom": false, "left": true, "right": false};
		this.gridImage = "/images/grid.gif";
		this.readonly = false;
		this.zIndeces = {"eventOff": -1, "scheme": 10, "block": 100, "eventOn": 1000};
		this.blockClass = "wjblockschemeblock";
	},

	/**
	 * _draw
	 *
	 * Draws the blockschem component
	 *
	 * @since Thu Aug 07 2008
	 * @access protected
	 * @return void
	 **/
	_draw: function() {
		this._drawBlockScheme();
		if (!this.readonly) {
			this._drawEventCatcher();
			this._createEventListeners();
			this._initEvents();
		}
	},

	/**
	 * _createEventListeners
	 *
	 * Creates some event listeners, these are created here and not on the fly so they can be found again later. This allows stopping the observation of the events
	 *
	 * @since Thu Aug 07 2008
	 * @access protected
	 * @return void
	 **/
	_createEventListeners: function() {
		this.resizeBlockEventListener = this.resizeBlock.bindAsEventListener(this);
		this.endResizeBlockListener = this.endResizeBlock.bindAsEventListener(this);
	},

	/**
	 * _drawBlockScheme
	 *
	 * Draws the blockscheme div, ie. sets the right CSS on the existing element
	 *
	 * @since Thu Aug 07 2008
	 * @access protected
	 * @return void
	 **/
	_drawBlockScheme: function() {
		$(this.id).setStyle({
			"width": this.dimensions.width + "px",
			"height": this.dimensions.height + "px",
			"borderTop": this.borders.top ? "1px solid " + this.borders.color : "none",
			"borderLeft": this.borders.left ? "1px solid " + this.borders.color : "none",
			"borderRight": this.borders.right ? "1px solid " + this.borders.color : "none",
			"borderBottom": this.borders.bottom ? "1px solid " + this.borders.color : "none",
			"backgroundImage": "url(\"" + this.gridImage + "\")",
			"zIndex": this.zIndeces.scheme});

		//$(this.id).relativize();
	},

	/**
	 * _drawEventCatcher
	 *
	 * Adds a div to the dom that is to be used to catch events on the block scheme
	 *
	 * @since Thu Aug 07 2008
	 * @access protected
	 * @return void
	 **/
	_drawEventCatcher: function() {
		this.eventcatcher = $(this.id).parentNode.appendChild(new Element("div") );
		this.eventcatcher.setStyle({
			"position": "absolute",
			"width": this.dimensions.width + "px",
			"height": this.dimensions.height + "px",
			"top": $(this.id).offsetTop + "px",
			"left": $(this.id).offsetLeft + "px",
			"zIndex": this.zIndeces.eventOff});
	},

	/**
	 * _initEvents
	 *
	 * Adds the event to the blockscheme to make it editable
	 *
	 * @since Thu Aug 07 2008
	 * @access protected
	 * @return void
	 **/
	_initEvents: function() {
		$(this.id).observe("mousedown", this.blockSchemeMouseDown.bindAsEventListener(this) );
	},

	/**
	 * blockSchemeMouseDown
	 *
	 * Starts the right proces on a mousedown event (depends on the target)
	 *
	 * @since Thu Aug 07 2008
	 * @access public
	 * @param event e
	 * @return void
	 **/
	blockSchemeMouseDown: function(e) {
		if (e.target.id == this.id) {
			this._readyEventcatcher();
			this._startNewBlock(e);
		}
		else if (e.target.hasClassName(this.blockClass + "_resizer") ) {
			this._readyEventcatcher();
			this._startBlockResize(e.target);
		}
	},

	/**
	 * _startBlockResize
	 *
	 * Starts the resizing of a block
	 *
	 * @since Thu Aug 07 2008
	 * @access protected
	 * @param htmlelement resizer
	 * @return void
	 **/
	_startBlockResize: function(resizer) {
		this.blockElement = resizer.parentNode;
		if (resizer.hasClassName(this.blockClass + "_resizer_left") ) {
			this.startCoords = {x: this.blockElement.offsetLeft + this.blockElement.getWidth() - this.blockSize.width, y: this.blockElement.offsetTop};
		}
		else {
			this.startCoords = {x: resizer.parentNode.offsetLeft, y: resizer.parentNode.offsetTop};
		}
	},

	/**
	 * _readyEventcatcher
	 *
	 * Let the event catcher start catching all needed events
	 *
	 * @since Thu Aug 07 2008
	 * @access public
	 * @return void
	 **/
	_readyEventcatcher: function() {
		this.eventcatcher.setStyle({"zIndex": this.zIndeces.eventOn});
		this.eventcatcher.observe("mousemove", this.resizeBlockEventListener);
		document.observe("mouseup", this.endResizeBlockListener);
	},

	/**
	 * _startNewBlock
	 *
	 * Creates a new block and hangs it correctly in the scheme
	 *
	 * @since Thu Aug 07 2008
	 * @access protected
	 * @param event e
	 * @return void
	 **/
	_startNewBlock: function(e) {
		this.startCoords = this._getBlockCoords(e);
		this.blockElement = this._createBlock(this.startCoords, {width: this.blockSize.width, height: this.blockSize.height});
	},

	/**
	 * _createBlock
	 *
	 * Creates a block in the scheme at coords with dimensions
	 *
	 * @since Thu Aug 07 2008
	 * @access public
	 * @param object coords
	 * @param object dimensions
	 * @return htmlelement
	 **/
	_createBlock: function(coords, dimensions) { 
		var block = $(this.id).appendChild(new Element("div") );
		block.addClassName(this.blockClass);
		block.setStyle({
			"left": coords.x + "px",
			"top": coords.y + "px",
			"position": "absolute",
			"zIndex": this.zIndeces.block,
			"width": dimensions.width + "px",
			"height": dimensions.height + "px"});
		var leftResizer = block.appendChild(new Element("div") );
		leftResizer.setStyle({
			"float": "left",
			"width": "2px",
			"height": "100%",
			"cursor": "w-resize"});
		var blockContent = block.appendChild(new Element("div") );
		var rightResizer = block.appendChild(new Element("div") );
		rightResizer.setStyle({
			"float": "right",
			"width": "2px",
			"height": "100%",
			"cursor": "w-resize"});
		blockContent.setStyle({
			"float": "left",
			"width": (dimensions.width - leftResizer.getWidth() - rightResizer.getWidth() ) + "px",
			"height": "100%"});
		blockContent.addClassName(this.blockClass + "_blockmover");
		leftResizer.addClassName(this.blockClass + "_resizer");
		leftResizer.addClassName(this.blockClass + "_resizer_left");
		rightResizer.addClassName(this.blockClass + "_resizer");
		rightResizer.addClassName(this.blockClass + "_resizer_right");
		if (typeof(this.editBlockProperties) == "function") {
			blockContent.observe("click", this.editBlockProperties);
		}
		return block;
	},

	/**
	 * _getBlockCoords
	 *
	 * Gets the top left x and y for the block the cursor was in at e
	 *
	 * @since Thu Aug 07 2008
	 * @access protected
	 * @param event e
	 * @return object
	 **/
	_getBlockCoords: function(e) { 
		var layerX = e.getLayerX();
		var layerY = e.getLayerY();
		var x = layerX - (layerX % this.blockSize.width);
		var y = layerY - (layerY % this.blockSize.height);
		return {x: x, y: y};
	},

	/**
	 * resizeBlock
	 *
	 * Resizes the currently be added or modified block to reflect the mouse position in e
	 *
	 * @since Thu Aug 07 2008
	 * @access public
	 * @param event e
	 * @return void
	 **/
	resizeBlock: function(e) {
		var coords = this._getBlockCoords(e);
		var origCoords = Object.clone(this.startCoords);
		if (origCoords.x <= coords.x) {
			coords.x += this.blockSize.width;
		}
		else {
			origCoords.x += this.blockSize.width;
		}
		if (origCoords.y <= coords.y) {
			coords.y += this.blockSize.height;
		}
		else {
			origCoords.y += this.blockSize.height;
		}

		var moveCoords = {"left": Math.min(coords.x, origCoords.x), "top": Math.min(coords.y, origCoords.y), "right": Math.max(coords.x, origCoords.x), "bottom": Math.max(coords.y, origCoords.y)};
		this.blockElement.setStyle({
			"left": moveCoords.left + "px",
			"top": moveCoords.top + "px",
			"width": (moveCoords.right - moveCoords.left) + "px",
			"height": (moveCoords.bottom - moveCoords.top) + "px"});
	},

	/**
	 * endResizeBlock
	 *
	 * Ends the resizing of a block. Passes it to a callback to handle the resize
	 *
	 * @since Thu Aug 07 2008
	 * @access public
	 * @param event e
	 * @return void
	 **/
	endResizeBlock: function(e) {
		this._endEventCatcher();
		if (this.constraint == "vertical") {
			this._createVerticalBlocks();
		}
		else if (this.constraint == "horizontal") {
			this._createHorizontalBlocks();
		}
		else {
			this.blocks = new Array(this._createBlock({x: this.blockElement.offsetLeft, y: this.blockElement.offsetTop}, {width: this.blockElement.getWidth(), height: this.blockElement.getHeight()}) );
		}

		if (typeof(this.endResizeCallback) == "function") {
			this.endResizeCallback(this.blocks, this);
		}
		this._startBlocksDraggable();
	},

	/**
	 * _startBlocksDraggable
	 *
	 * Makes all blocks in this.blocks draggable
	 *
	 * @since Thu Aug 07 2008
	 * @access public
	 * @return void
	 **/
	_startBlocksDraggable: function() {
		if (this.readonly) {
			return;
		}
		var draggableOptions = {snap: this.snapBlockmove.bind(this), handle: this.blockClass + "_blockmover"};
		if (typeof(this.endMoveCallback) == "function") {
			draggableOptions.onEnd = this.endMoveCallback;
		}

		for (var i = 0; i < this.blocks.length; i++) {
			new Draggable(this.blocks[i], draggableOptions);
		}
	},

	/**
	 * snapBlockmove
	 *
	 * Snaps x and y to the nearest blockcorner
	 *
	 * @since Thu Aug 07 2008
	 * @access public
	 * @param integer x
	 * @param integer y
	 * @param element draggable
	 * @return void
	 **/
	snapBlockmove: function(x, y, draggable) {
		var xdiff = x % this.blockSize.width;
		var ydiff = y % this.blockSize.height;
		// snap to blockcorner
		var snapped = [xdiff > (this.blockSize.width / 2) ? x + this.blockSize.width - xdiff : x - xdiff, ydiff > (this.blockSize.height / 2) ? y + this.blockSize.height - ydiff : y - ydiff];
		// contain within blockscheme
		return [Math.min(Math.max(this.dimensions.left, snapped[0]), (this.dimensions.width - draggable.element.getWidth() ) ), Math.min(Math.max(this.dimensions.top, snapped[1]), (this.dimensions.height - draggable.element.getHeight() ) )];
	},

	/**
	 * _createHorizontalBlocks
	 *
	 * Breaks up one block into one or more blocks of height 1
	 *
	 * @since Thu Aug 07 2008
	 * @access protected
	 * @return void
	 **/
	_createHorizontalBlocks: function() {
		this.blocks = new Array();
		if (this.blockElement.getHeight() == this.blockSize.height) {
			this.blocks.push(this._createBlock({x: this.blockElement.offsetLeft, y: this.blockElement.offsetTop}, {width: this.blockElement.getWidth(), height: this.blockElement.getHeight()}) );
		}
		else {
			var blockCoords = {top: this.blockElement.offsetTop, left: this.blockElement.offsetLeft, width: this.blockElement.getWidth(), height: this.blockElement.getHeight()};
			var cury = blockCoords.top;
			do {
				this.blocks.push(this._createBlock({x: blockCoords.left, y: cury}, {width: blockCoords.width, height: this.blockSize.height}) );
				cury += this.blockSize.height;
			}
			while (cury < (blockCoords.top + blockCoords.height) );
		}
		this.blocks[this.blocks.length - 1].blockData = this.blockElement.blockData;
		this.blockElement.parentNode.removeChild(this.blockElement);
	},

	/**
	 * _createVerticalBlocks
	 *
	 * Breaks up one block into one or more blocks of width 1
	 *
	 * @since Thu Aug 07 2008
	 * @access protected
	 * @return void
	 **/
	_createVerticalBlocks: function() {
		this.blocks = new Array();
		if (this.blockElement.getWidth() == this.blockSize.width) {
			this.blocks.push(this._createBlock({x: this.blockElement.offsetLeft, y: this.blockElement.offsetTop}, {width: this.blockElement.getWidth(), height: this.blockElement.getHeight()}) );
		}
		else {
			var blockCoords = {top: this.blockElement.offsetTop, left: this.blockElement.offsetLeft, width: this.blockElement.getWidth(), height: this.blockElement.getHeight()};
			var curx = blockCoords.left;
			do {
				this.blocks.push(this._createBlock({x: curx, y: blockCoords.top}, {width: this.blockSize.width, height: blockCoords.height}) );
				curx += this.blockSize.width;
			}
			while (curx < (blockCoords.left + blockCoords.width) );
		}
		this.blockElement.parentNode.removeChild(this.blockElement);
	},

	/**
	 * _endEventCatcher
	 *
	 * Ends the event catching of the event catcher
	 *
	 * @since Thu Aug 07 2008
	 * @access public
	 * @return void
	 **/
	_endEventCatcher: function() {
		this.eventcatcher.stopObserving("mousemove", this.resizeBlockEventListener);
		document.stopObserving("mouseup", this.endResizeBlockListener);
		this.eventcatcher.setStyle({"zIndex": this.zIndeces.eventOff});
	}
};

var WJEmillNavigator = Class.create();
/**
 * WJEmillNavigator is the class to handle navigation in the emill window
 *
 * @since Wed Jul 30 2008
 * @author Ron Rademaker
 **/

WJEmillNavigator.prototype = {
	/**
	 * initialize
	 *
	 * Creates a new WJEmillNavigator
	 *
	 * @since Wed Jul 30 2008
	 * @access public
	 * @param WJWindow emillWindow
	 * @param object start
	 * @return void
	 **/
	initialize: function(emillWindow, start) {
		this.spin = new WJSpin();
		this.window = emillWindow;
		this.loader = null;
		this.navigating = false;

		this.historyKey = "emill";
		this.historyManager = new ProtoHistoryManager();
		this.history = this.historyManager.register(
			this.historyKey,
			[start],
			function(values) {
				this.navigateTo(values[0]);
			}.bind(this),
			function(values) {
				return this.historyKey + "-" + $H(values[0]).toQueryString();
			}.bind(this),
			this.historyKey + '-(.+)');
	
		this.historyManager.start();
	},

	/**
	 * navigateTo
	 *
	 * Loads the  URL with the passed variables into window
	 *
	 * @since Wed Jul 30 2008
	 * @access public
	 * @param object or string variables
	 * @return void
	 **/
	navigateTo: function(variables) {
		if (this.navigating) {
			this.navigateTo.bind(this, variables).delay(0.2);
		}
		else {
			this.navigating = true;
			var url = new WJUrl({dt: "emill"});
			if (typeof(variables) == "string") {
				variables = variables.toQueryParams();
			}

			for (var key in variables) {
				if (key == "nav_active") {
					this.handleNavigation(variables[key]);
					this.setActiveColor(variables[key]);
					this.setTitle(variables[key]);
				}
				else {
					url.addParameter(key, variables[key]);
				}
			}
			
			if (!variables["nav_active"]) {
				variables["nav_active"] = this.lastNavActive;
			}
			
			this.clearButtons();

			this.lastVariables = variables;

			this.history.setValue(0, this.lastVariables);
			url.addParameter("nocache", Math.random() );

			this.showLoader();
			this.spin.content(url, [this.window.getContentElement("main"), this.checkStatus.bind(this), this.hideLoader.bind(this)], SpinErrorHandling);
		}
	},
	
	/**
	 * checkStatus
	 *
	 * Checks if the content isn't the frame, meaning the user has logged out
	 *
	 * @since Thu May 28 2009
	 * @access public
	 * @return void
	 **/
	checkStatus: function() {
		var content = this.window.getContentElement("main").getElementsByTagName("title");
		if (content.length > 0) {
			document.location.href = document.location.href.substr(0, document.location.href.indexOf("#") );
		}
	},

	/**
	 * clearButtons
	 *
	 * Clears all current buttons
	 * 
	 * @since Tue Mar 17 2009
	 * @access public
	 * @return void
	 **/
	clearButtons: function() {
		this.window.getContentElement("buttons").innerHTML = "&#160;";
	},

	/**
	 * refresh
	 *
	 * Refreshes the page
	 *
	 * @since Fri Nov 21 2008
	 * @access public
	 * @return void
	 **/
	refresh: function() {
		return this.navigateTo(this.lastVariables);
	},

	/**
	 * showLoader
	 *
	 * Shows a div showing the user something is happening
	 *
	 * @since Thu Aug 21 2008
	 * @access public
	 * @return void
	 **/
	showLoader: function() {
		if(/MSIE (5|6)/.test(navigator.userAgent) ) {
			var selects = $(document.body).select("select");
			for (var i = 0; i < selects.length; i++) {
				selects[i].setStyle({display: "none"});
			}
		}
		
		if (this.loader == null) {
			this.loader = document.body.appendChild(new Element("div", {"class": "pos_ajaxloader stl_ajaxloader"}) );
			this.loader.innerHTML = "<img src='/images/loader.gif'/><p>Pagina wordt geladen</p>";
		}
		this.loader.show();
	},
	
	/**
	 * hideLoader
	 *
	 * Hides the div showing the user something is happening
	 *
	 * @since Thu Aug 21 2008
	 * @access public
	 * @return void
	 **/
	hideLoader: function() {
		this.navigating = false;
		if (this.loader != null) {
			if ($(this.loader) ) {
				this.loader.hide();
			}
		}
	},

	/**
	 * extendNavigate
	 *
	 * Extends the last navigation with the variables in variables
	 *
	 * @since Thu Jul 31 2008
	 * @access public
	 * @param object variables
	 * @param array excludevariables
	 * @return void
	 **/
	extendNavigate: function(variables, excludevariables) {
		if (!Object.isArray(excludevariables) ) {
			excludevariables = new Array();
		}
		
		var newVariables = Object.clone(this.lastVariables);
		for (var key in newVariables) {
			if (key.indexOf("__cms_") != -1 || key.indexOf("__function_") != -1 || excludevariables.indexOf(key) != -1) {
				delete newVariables[key];
			}
		}
		
		for (var key in variables) {
			newVariables[key] = variables[key];
		}
		
		this.navigateTo(newVariables);
	},

	/**
	 * handleNavigation
	 *
	 * Sets the menu to navigation active nav_active
	 *
	 * @since Wed Jul 30 2008
	 * @access public
	 * @param integer nav_active
	 * @return void
	 **/
	handleNavigation: function(nav_active) {
		if (!$("topmenu") ) {
			return; // no menu, nothing to do about the menu
		}
		$A($("topmenu").getElementsByTagName("li") ).each(function(s) { $(s).removeClassName("pos_active stl_active"); });

		$A($("subsubmenu").getElementsByTagName("div") ).each(function(s) { $(s).removeClassName("pos_active stl_active"); });
		
		if ($("item" + nav_active) ) {
			$("item" + nav_active).addClassName("pos_active stl_active");
			this.lastNavActiveElement = $("item" + nav_active);
		}
		
		var menuid = "menu" + (Math.floor(nav_active / 100) * 100);
		var submenuid = "tabs" + (Math.floor(nav_active / 10) * 10);
		
		if ($(menuid) ) {
			$(menuid).addClassName("pos_active stl_active");
			
			if ($(submenuid) ) {
				$(submenuid).addClassName("pos_active stl_active");
				$(submenuid).style.left = ( ( (document.viewport.getWidth() - 946 ) / 2) + 8 ) + "px";
			}
		}
		if ($("menu" + nav_active) ) {
			$("menu" + nav_active).addClassName("pos_active stl_active");
		}
		if ($("tab" + nav_active) ) {
			$("tab" + nav_active).addClassName("pos_active stl_active");
		}

		this.lastNavActive = nav_active;
	},
	
	/**
	 * showOverview
	 *
	 * Returns to the main page of the active menu item
	 *
	 * @since Fri Nov 21 2008
	 * @access public
	 * @param array removeVariables
	 * @return void
	 **/
	showOverview: function(removeVariables) {
		if (!Object.isArray(removeVariables) ) {
			var removeVariables = new Array();
		}
		
		var newVariables = Object.clone(this.lastVariables);
		delete newVariables["mode"];
		if (this.lastNavActiveElement) {
			var extra = this.lastNavActiveElement.select("a").first().href.match(/\{[^\}]*\}/)
			var extra =	unescape(this.lastNavActiveElement.select("a").first().href).replace(/"/g, '').match(/\{([^\}]*)\}/)[1].split(/[,:]/);
			for (i = 0; i < extra.length; i += 2) {
				var value = extra[i + 1].replace(/^\s*/, "").replace(/\s*$/, "");
				newVariables[extra[i] ] = value;
			}
		}
		for (var key in newVariables) {
			if (key.indexOf("__cms_") != -1 || key.indexOf("__function_") != -1 || removeVariables.indexOf(key) != -1) {
				delete newVariables[key];
			}
		}
		this.navigateTo(newVariables);
	},
	
	/**
	 * setTitle
	 *
	 * Sets the menu to navigation active nav_active
	 *
	 * @since Wed Jul 30 2008
	 * @access public
	 * @param integer nav_active
	 * @return void
	 **/
	setTitle: function(nav_active) {
		if ($("item" + nav_active) ) {
			emillWindow.setTitle($("item" + nav_active).getTextContent() );
		}
		else if ($("menu" + nav_active) ) {
			emillWindow.setTitle($("menu" + nav_active).getAttribute("title") );
		}
	},
	
	/**
	 * setActiveColor
	 *
	 * Sets the style to the body
	 *
	 * @since Thu Aug 7 2008
	 * @access public
	 * @param integer nav_active
	 * @return void
	 **/
	setActiveColor: function(nav_active) {
		var classNameInfo = $H({200: "coloremail", 300: "colorrelaties", 400: "colormarketing", 500: "colordirectmail", 600: "colorhospitality", 800: "coloractivity", 700: "colorlocation"});
		var menuid = (Math.floor(nav_active / 100) * 100);
		
		classNameInfo.each(function(info) {
			var color = info.value;
			if (info.key == menuid) {
				$("body").addClassName("pos_" + color).addClassName("stl_" + color);
			}
			else {
				$("body").removeClassName("pos_" + color).removeClassName("stl_" + color);
			}
		});
	},

	/**
	 * iframeAjaxResponse
	 *
	 * Handles an 'ajax' response posted to a temporary iframe
	 *
	 * @since Wed Jan 21 2009
	 * @access public
	 * @param event e
	 * @return void
	 **/
	iframeAjaxResponse: function(e) {
		var elems = this._postedForm.elements;
		var getVars = {};
		for (var i = 0; i < elems.length; i++) {
			if ( (elems[i].tagName.toUpperCase() == "INPUT") && (elems[i].type.toUpperCase() == "HIDDEN") ) {
				getVars[elems[i].name] = elems[i].value;
			}
		}
		this.navigateTo(getVars);
	},

	/**
	 * navigateForm
	 *
	 * Navigate by submitting a form, optionally overwrites / adds everything in the form with the info in variables
	 *
	 * @since Wed Jul 30 2008
	 * @access public
	 * @param HTMLElement form
	 * @param object variables
	 * @return void
	 **/
	navigateForm: function(form, variables) {
		if (form.enctype == "multipart/form-data") {
			var frame = $("uploadframe");
			if (!frame) {
				frame = new Element("iframe", {"style": "display: none;", "src": "about:blank", "id": "uploadframe", "name": "uploadframe"});
				document.body.insert(frame);
				frame.observe("load", this.iframeAjaxResponse.bindAsEventListener(this) );
			}
			form.target = frame.name;
			this._postedForm = form;
			form.submit();
		}
		else {
			var formVars = $(form).serialize(true);
			for (var key in variables) {
				formVars[key] = variables[key];
			}
			for (key in formVars) {
				if (Object.isString(formVars[key]) ) {
					formVars[key] = formVars[key].replace(/\"/g, "%22").replace(/\'/g, "%27");
				}
			}
			
			this.navigateTo(formVars);
		}
	}
}

/**
 * WJEvent
 *
 * Changelog
 * ---------
 *
 *
 * @since Fri Aug 06 2008
 * @author Vanja de Keizer (vanja@connectholland.nl)
 **/
var WJEvent = Class.create();
WJEvent.prototype = {
	
	/**
	 * initialize
	 *
	 * Initialize a new WmEvent
	 *
	 * @since initial
	 * @return void
	 **/
	initialize: function() {
	},
	
	/**
	 * showCalendar
	 *
	 * Sets the style display on block
	 *
	 * @access public
	 * @since Fri Aug 15 2008
	 * @param string id
	 * @return void
	 **/
	showCalendar: function(id) {
		if ( $(id).getStyle("display") == "block") {
			$(id).setStyle({display: "none"});
		}
		else {
			if (window.calendars) {
				window.calendars.invoke("hide");
			}
			$(id).setStyle({display: "block"});
		}
	},
	
	/**
	 * disabledIntroductions
	 *
	 * Sets the disabled attribute
	 *
	 * @access public
	 * @since Fri Aug 15 2008
	 * @param event
	 * @param element
	 * @param name
	 * @param value
	 * @return integer
	 **/
	disabledIntroductions: function(event, element, name, value) {
		var inputField = element.form[name];
		inputField.disabled = value;
	}
}

var wjevent = new WJEvent();

var WJInvitor = Class.create();

/**
 * WJInvitor is the class to handle assigning people to events, dates, locations and blocks
 *
 * @since Wed Aug 13 2008
 * @author Ron Rademaker
 **/
WJInvitor.prototype = {
	/**
	 * initialize
	 *
	 * Initializes this WJInvitor, sets some divs
	 *
	 * @since Wed Aug 13 2008
	 * @access public
	 * @param htmlelement founddiv
	 * @param htmlelement inviteediv
	 * @return void
	 **/
	initialize: function(founddiv, inviteediv) {
		this.inviteediv = $(inviteediv);
		this.founddiv = $(founddiv);

		//Droppables.add(inviteediv, {hoverclass: "pos_accept stl_accept", onDrop: this.dropContact.bind(this)});

		this.searchUrl = new WJUrl({module: "Wmcontactmanagement", mode: "xml"});
		this.searchUrl.setCt("wmdynamic");
		this.searchUrl.setDt("searchinvite");
		this.inviteUrl = new WJUrl({module: "Wminvitation", mode: "xml"});
		this.inviteUrl.setCt("wmdynamic");
		this.blockUrl = new WJUrl({module: "Wmevent", mode: "block"});
		this.blockUrl.setCt("wmdynamic");
	},

	/**
	 * setSearch
	 *
	 * Sets the form to use for searching, results are to be displayed in the founddiv
	 *
	 * @since Wed Aug 13 2008
	 * @access public
	 * @param form searchform
	 * @return void
	 **/
	setSearch: function(searchform) {
		this.searchform = $(searchform);
		this._activateFormListeners();
	},

	/**
	 * setSelection
	 *
	 * Sets the current selection to invite people for, combination of type and id. ie "event" 10, "block" 17, "date" 2008-08-14 or "location" 5 (numbers refer to database ids)
	 *
	 * @since Thu Aug 14 2008
	 * @access public
	 * @param string type
	 * @param string id
	 * @return void
	 **/
	setSelection: function(type, id) {
		if (type == "event") {
			this.event_id = id;
			this.blockUrl.addParameter("event_id", this.event_id);
			this.inviteUrl.addParameter("event_id", this.event_id);
			this.searchUrl.addParameter("event_id", this.event_id);
		}
		if (type == "date") {
			id = id + "," +  $("dateselector").getValue();
		}
		var scroll = (this.selection != null);
		$A(document.getElementsByClassName("pos_selected") ).each(function(s) { $(s).removeClassName("pos_selected stl_selected")}); // visually deselect selected blocks
		this.selection = {type: type, id: id};
		this._updateSelectionHeader();
		this._loadSelection();
		/*var anchor = $("invitationsform");
		if (anchor && scroll) {
			anchor.scrollIntoView();
		}*/
	},

	/**
	 * _loadSelection
	 *
	 * Loads the invites for the current selection
	 *
	 * @since Fri Aug 15 2008
	 * @access protected
	 * @return void
	 **/
	_loadSelection: function() {
		this.inviteUrl.addParameter("type", this.selection.type);
		this.inviteUrl.addParameter("id", this.selection.id);
		this.inviteUrl.setDt("invited");
		var spin = new WJSpin();
		spin.content(this.inviteUrl, [this.inviteediv, this.initDraggables.bind(this), this.initRemovables.bind(this)], SpinErrorHandling);
		this.inviteUrl.deleteParameter("type");
		this.inviteUrl.deleteParameter("id");
		this.inviteUrl.deleteParameter("dt");
	},

	/**
	 * initRemovables
	 *
	 * Starts event listeners on the remove buttons for the currently invited people
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @return void
	 **/
	initRemovables: function() {
		var removables = $A(this.inviteediv.getElementsByClassName("pos_remove") );
		for (var i = 0; i < removables.length; i++) {
			removables[i].observe("click", this.removeInvitee.bind(this, removables[i].up() ) );
		}
	},

	/**
	 * selectBlock
	 *
	 * Selects a block to invite people for
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @param event e
	 * @return void
	 **/
	selectBlock: function(e) {
		var block = e.target.up();
		this.setSelection("block", block.blockData.id);
		block.addClassName("pos_selected stl_selected");
	},

	/**
	 * _activateFormListeners 
	 *
	 * Starts some listeners to add ajax calls to load the search result
	 *
	 * @since Wed Aug 13 2008
	 * @access public
	 * @return void
	 **/
	_activateFormListeners: function() {
		$A(this.searchform.elements).each(function(s) {
			$(s).observe("keyup", this.searchInvitees.bind(this) );
		}.bind(this) );
		this.searchInvitees();

		this.lastQuery = this.searchform.serialize(true);
	},

	/**
	 * searchInvitees
	 *
	 * Searches for invitees if the search criteria have changed and someone is not typing real fast
	 *
	 * @since Wed Aug 13 2008
	 * @access public
	 * @return void
	 **/
	searchInvitees: function() {
		var query = this.searchform.serialize(true);

		if (Object.toQueryString(query) != Object.toQueryString(this.lastQuery) ) {
			this.lastQuery = query;
			if (this.lastDelay) {
				window.clearTimeout(this.lastDelay);
			}

			this.lastDelay = this.search.bind(this).delay(0.4);
		}
	},

	/**
	 * search
	 *
	 * Calls the ajax function to search the CRM
	 *
	 * @since Wed Aug 13 2008
	 * @access public
	 * @return void
	 **/
	search: function() {
		for (var key in this.lastQuery) {
			this.searchUrl.addParameter(key, this.lastQuery[key]);
		}

		var spin = new WJSpin();
		spin.content(this.searchUrl, [this.founddiv, this.initDraggables.bind(this)], SpinErrorHandling);
		
		for (var key in this.lastQuery) {
			this.searchUrl.deleteParameter(key);
		}
	},

	/**
	 * initDraggables
	 *
	 * Initializes the found contacts as draggables, droppable in the inviteediv 
	 *
	 * @since Thu Aug 14 2008
	 * @access public
	 * @return void
	 **/
	initDraggables: function() {
		var contacts = this.founddiv.getElementsByClassName("pos_contact");
		for (var i = 0; i < contacts.length; i++) {
			if (!contacts[i].hasClassName("pos_header") ) {
				$(contacts[i]).stopObserving("dblclick");
				$(contacts[i]).observe("dblclick", this.moveContact.bind(this) );
				new Draggable(contacts[i], {revert: this.dropContact.bind(this), snap: this.contactSnapper.bind(this)});
			}
		}
	},
	
	/**
	 * moveContact
	 *
	 * Moves a contact straight to the inviteediv (on double click)
	 *
	 * @since Mon Mar 02 2009
	 * @access public
	 * @param Event event
	 * @return void
	 **/
	moveContact: function(event) {
		var element = Event.element(event);
		if (!element.hasClassName("pos_contact") ) {
			element = element.up();
		}
		
		element.inDropper = true;
		this.dropContact(element);
	},

	/**
	 * contactSnapper
	 *
	 * Snaps the contact to the first free position in the inviteediv once the contact is above the inviteediv
	 *
	 * @since Thu Aug 14 2008
	 * @access public
	 * @param integer x
	 * @param integer y 
	 * @param element draggable
	 * @return void
	 **/
	contactSnapper: function(x, y, draggable) {
		var elemLocation = draggable.element.cumulativeOffset();
		var location = {x: elemLocation.left, y: elemLocation.top};
		var dropper = null;
		
		var undropdiv;
		var inviteediv = this.inviteediv.select(".pos_results").pop();
		if (!inviteediv) {
			inviteediv = this.inviteediv.appendChild(new Element("div") );
			inviteediv.addClassName("pos_results stl_results");
		}
		var invitLocation = inviteediv.cumulativeOffset();
		dropper = {x: invitLocation.left - draggable.element.getWidth(), y: invitLocation.top, width: inviteediv.getWidth() + draggable.element.getWidth(), height: inviteediv.getHeight()};
		var dropdiv = inviteediv;

		if ( (location.x > dropper.x) && (location.x < (dropper.x + dropper.width) ) &&
			 (location.y > dropper.y) && (location.y < (dropper.y + dropper.height) ) ) {
			draggable.element.inDropper = true;
			dropdiv.addClassName("pos_accept stl_accept");
		}
		else {
			draggable.element.inDropper = false;
			dropdiv.removeClassName("pos_accept stl_accept");
		}
		return [x, y];
	},

	/**
	 * dropContact
	 *
	 * Attaches a contact in the current inviteediv
	 *
	 * @since Thu Aug 14 2008
	 * @access public 
	 * @param element draggable
	 * @return void
	 **/
	dropContact: function(draggable) {
		if (draggable.inDropper) {
			var newdiv = this.inviteediv.select(".pos_results").pop();
			if (!newdiv) {
				newdiv = this.inviteediv.appendChild(new Element("div") );
				newdiv.addClassName("pos_results stl_results");
			}

			var contact = draggable.cloneNode(true);
			draggable.down(".invited").update("V");
			contact.setStyle({"position": "static", "left": "auto", "top": "auto"}); // reset draggable info
			newdiv.appendChild(contact);
			newdiv.removeClassName("pos_accept stl_accept");
			this._saveSelection();
		}
		return true;
	},

	/**
	 * removeInvitee
	 *
	 * Removes an invitee and saves the new selection
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @param htmlelement invitee
	 * @return void
	 **/
	removeInvitee: function(invitee) {
		invitee.remove();
		this._saveSelection();
	},

	/**
	 * setInviteForm
	 *
	 * Sets the html form where the selected contacts can be fetched
	 *
	 * @since Thu Aug 14 2008
	 * @access public
	 * @param form form
	 * @return void
	 **/
	setInviteForm: function(form) {
		this.inviteform = $(form);
	},

	/**
	 * clickLocation
	 *
	 * Selects a location
	 *
	 * @since Thu Aug 21 2008
	 * @access public
	 * @param object location
	 * @return void
	 **/
	clickLocation: function(location) {
		this.setSelection("location", location.location + "," + location.date() );
		this._updateSelectionHeader(location.htmlelement);
	},

	/**
	 * _updateSelectionHeader
	 *
	 * Updates the header that says what you're currently inviting people for
	 *
	 * @since Thu Aug 21 2008
	 * @access public
	 * @param htmlelement elem
	 * @return void
	 **/
	_updateSelectionHeader: function(elem) {
		var header = $("selectionHeader");
		switch (this.selection.type) {
			case "event":
				header.innerHTML = "Gehele Evenement";
				break;
			case "location":
				if (elem) {
					var pieces = this.selection.id.split(",");
					header.innerHTML = elem.innerHTML + " op " + pieces[1];
				}
				break;
			case "date":
				var pieces = this.selection.id.split(",");
				header.innerHTML = pieces[1];
				break;
			case "block":
				var spin = new WJSpin();
				this.blockUrl.addParameter("id", this.selection.id);
				spin.content(this.blockUrl, [this.updateBlockHeader.bind(this, header)]);
				this.blockUrl.deleteParameter("id");
				break;
		}
	},

	/**
	 * updateBlockHeader
	 *
	 * Updates the selection header with passed block info
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @param document response
	 * @return void
	 **/
	updateBlockHeader: function(header, response) {
		var locationNode = response.getElementsByTagName("name").item(0);
		var startNode = response.getElementsByTagName("start").item(0);
		if (locationNode.textContent ) {
			var location = locationNode.textContent;
			var start = startNode.textContent;
		}
		else {
			var location = locationNode.text;
			var start = startNode.text;
		}
		header.innerHTML = location + " op " + start;
	},

	/**
	 * _saveSelection
	 *
	 * Saves the currently selected contacts as invited contacts
	 *
	 * @since Thu Aug 14 2008
	 * @access protected
	 * @return void
	 **/
	_saveSelection: function() {
		var data = this.inviteform.serialize(true);
		data.inviteForType = this.selection.type;
		data.inviteForId = this.selection.id;
		var spin = new WJSpin();
		this.inviteUrl.addParameter("mode", "cmsonly");
		spin.update(this.inviteUrl, "Wminvitation", "saveInvites", data, [this._loadSelection.bind(this)], SpinErrorHandling);
		this.inviteUrl.addParameter("mode", "xml");
	}
};

/**
 * WJRegistrator is the class to register event attendance
 *
 * Note: WJRegistrator started as a copy of WJInvitor
 *
 * @since Thu Oct 02 2008
 * @author Ron Rademaker
 **/
var WJRegistrator = Class.create({
	/**
	 * initialize
	 *
	 * Initializes this WJRegistrator, sets some divs
	 *
	 * @since Thu Oct 02 2008
	 * @access public
	 * @param htmlelement inviteediv
	 * @param htmlelement attenddiv
	 * @return void
	 **/
	initialize: function(inviteediv, attenddiv) {
		this.inviteediv = $(inviteediv);
		this.attenddiv = $(attenddiv);

		//Droppables.add(inviteediv, {hoverclass: "pos_accept stl_accept", onDrop: this.dropContact.bind(this)});

		this.inviteeSpin = new WJSpin();
		this.attendeeSpin = new WJSpin();
		this.inviteeUrl = new WJUrl({module: "Wminvitation", ct: "wmdynamic", "mode": "invited", "dt": "invited"});
		this.attendeeUrl = new WJUrl({module: "Wminvitation", ct: "wmdynamic", "mode": "registered", "dt": "invited"});
	},

	/**
	 * setSelection
	 *
	 * Sets the current selection to invite people for, a block id
	 *
	 * @since Thu Oct 02 2008
	 * @access public
	 * @param string id
	 * @return void
	 **/
	setSelection: function(id) {
		$A(document.getElementsByClassName("pos_selected") ).each(function(s) { $(s).removeClassName("pos_selected stl_selected")}); // visually deselect selected blocks
		this.selection = {id: id};
		this._loadSelection();
	},

	/**
	 * _loadSelection
	 *
	 * Loads the invites for the current selection
	 *
	 * @since Thu Oct 02 2008
	 * @access protected
	 * @return void
	 **/
	_loadSelection: function() {
		this.loadInvitees();
		this.loadAttendees();
	},

	/**
	 * loadInvitees
	 *
	 * Loads the invitees for the current selection
	 *
	 * @since Fri Oct 03 2008
	 * @access public
	 * @return void
	 **/
	loadInvitees: function() { 
		this.inviteeUrl.addParameter("block_id", this.selection.id);
		this.inviteeSpin.content(this.inviteeUrl, [this.inviteediv, this.initDraggables.bind(this)]);
		this.inviteeUrl.deleteParameter("block_id");
	},

	/**
	 * loadAttendees
	 *
	 * Loads the attendees for the current selection
	 *
	 * @since Fri Oct 03 2008
	 * @access public
	 * @return void
	 **/
	loadAttendees: function() { 
		this.attendeeUrl.addParameter("block_id", this.selection.id);
		this.attendeeSpin.content(this.attendeeUrl, [this.attenddiv, this.initAttendees.bind(this)]);
		this.attendeeUrl.deleteParameter("block_id");
	},

	/**
	 * initAttendees
	 *
	 * Inits actions on attendees (remove and update showups)
	 *
	 * @since Fri Oct 03 2008
	 * @access public
	 * @return void
	 **/
	initAttendees: function() {
		$A(this.attenddiv.select("select") ).each(function(s) {
			$(s).observe("change", this._saveSelection.bind(this) );
		}.bind(this) );
		$A(this.attenddiv.select(".pos_remove") ).each(function(s) {
			$(s).observe("click", function(e, elem) {
				elem.up().remove();
				this._saveSelection();
			}.bindAsEventListener(this, s) );
		}.bind(this) );
	},

	/**
	 * selectBlock
	 *
	 * Selects a block to invite people for
	 *
	 * @since Thu Oct 02 2008
	 * @access public
	 * @param event e
	 * @return void
	 **/
	selectBlock: function(e) {
		var block = e.target.up();
		this.setSelection(block.blockData.id);
		block.addClassName("pos_selected stl_selected");
	},

	/**
	 * initDraggables
	 *
	 * Initializes the found contacts as draggables, droppable in the inviteediv 
	 *
	 * @since Thu Oct 02 2008
	 * @access public
	 * @return void
	 **/
	initDraggables: function() {
		var contacts = this.inviteediv.select(".pos_contact");
		for (var i = 0; i < contacts.length; i++) {
			new Draggable(contacts[i], {revert: this.dropContact.bind(this), snap: this.contactSnapper.bind(this)});
		}
	},

	/**
	 * contactSnapper
	 *
	 * Snaps the contact to the first free position in the attenddiv once the contact is above the attenddiv
	 *
	 * @since Thu Oct 02 2008
	 * @access public
	 * @param integer x
	 * @param integer y 
	 * @param element draggable
	 * @return void
	 **/
	contactSnapper: function(x, y, draggable) {
		var elemLocation = draggable.element.viewportOffset();
		var location = {x: elemLocation.left, y: elemLocation.top};
		var dropper = null;
		
		var undropdiv;
		var attenddiv = this.attenddiv.select(".pos_results").pop();
		if (!attenddiv) {
			attenddiv = this.attenddiv.appendChild(document.createElement("div") );
			attenddiv.addClassName("pos_results stl_results");
		}
		var invitLocation = inviteediv.viewportOffset();
		dropper = {x: invitLocation.left, y: invitLocation.top, width: inviteediv.getWidth(), height: inviteediv.getHeight()};
		var dropdiv = attenddiv;

		if ( (location.x > dropper.x) && (location.x < (dropper.x + dropper.width) ) &&
			 (location.y > dropper.y) && (location.y < (dropper.y + dropper.height) ) ) {
			draggable.element.inDropper = true;
			dropdiv.addClassName("pos_accept stl_accept");
		}
		else {
			draggable.element.inDropper = false;
			dropdiv.removeClassName("pos_accept stl_accept");
		}
		return [x, y];
	},

	/**
	 * dropContact
	 *
	 * Attaches a contact in the current inviteediv
	 *
	 * @since Thu Aug 14 2008
	 * @access public 
	 * @param element draggable
	 * @return void
	 **/
	dropContact: function(draggable) {
		if (draggable.inDropper) {
			var newdiv = this.attenddiv.select(".pos_results").pop();
			if (!newdiv) {
				newdiv = this.attenddiv.appendChild(document.createElement("div") );
				newdiv.addClassName("pos_results stl_results");
			}

			var contact = draggable.cloneNode(true);
			contact.setStyle({"position": "static", "left": "auto", "top": "auto"}); // reset draggable info
			draggable.remove();
			newdiv.appendChild(contact);
			newdiv.removeClassName("pos_accept stl_accept");
			this._saveSelection();
			this.initDraggables();
			return false;
		}
		else {
			return true;
		}
	},

	/**
	 * _saveSelection
	 *
	 * Saves the currently selected contacts as invited contacts
	 *
	 * @since Thu Aug 14 2008
	 * @access protected
	 * @return void
	 **/
	_saveSelection: function() {
		var data = $(document.selectedcontacts).serialize(true);
		data["block_id"] = this.selection.id;
		this.attendeeSpin.update(this.attendeeUrl, "Wminvitation", "saveRegistration", data, [this.attenddiv, this.initAttendees.bind(this), this.loadInvitees.bind(this)]);
	}
});


/**
 * WMConfirm is the replacement for the javascript confirm and alert
 * This is a partial copy of cms.js of Windmill CMS
 *
 * @since Fri May 25 2007
 * @author: Niels Nijens
 **/
var WMConfirm = Class.create();
WMConfirm.prototype = {
	
	/**
	 * initialize
	 *
	 * Initialize a new WMConfirm
	 *
	 * @access public
	 * @since initial
	 * @return void
	 **/
	initialize: function() {
		this.curX = 0;
		this.curY = 0;
	},

	/**
	 * warn
	 *
	 * Substitute for javascript standard alert function
	 *
	 * @since initial
	 * @access public
	 * @param String warning
	 * @return void
	 **/
	warn: function(warning) {
		this._addModalFrame();
		var warnDiv = this._createModalDiv("__warndiv");
		warnDiv.innerHTML = "<h1 id='__warndiv_dragbar'>E-mill</h1><p>" + warning + "</p><div class='pos_submitwrapper stl_submitwrapper'><div class='pos_submit stl_submit'><input type='submit' onclick='Wmconfirm.hideWarning(); return false;' value='OK'/></div></div>";
		document.body.appendChild(warnDiv);
		this.curwarn = new Draggable("__warndiv", {handle: "__warndiv_dragbar", zindex: 10000});
	},

	/**
	 * _createModalDiv
	 *
	 * Create a div suitable for modal showing
	 *
	 * @since initial
	 * @access private
	 * @param String id
	 * @return void
	 **/
	_createModalDiv: function(id) {
		var modalDiv = document.createElement("div");
		modalDiv.id = id;
		modalDiv.className = "pos_modaldiv stl_modaldiv";
		modalDiv.style.left = (document.viewport.getWidth() / 2) - 198 + "px";
		if (window.scrollY) {
			modalDiv.style.top = window.scrollY + 100 + "px";
		}
		else {
			modalDiv.style.top = document.documentElement.scrollTop + 100 + "px";
		}
		return modalDiv;
	},

	/**
	 * _addModalFrame
	 *
	 * Adds an iframe masking everything to create a modal popup
	 *
	 * @since initial
	 * @access private
	 * @return void
	 **/
	_addModalFrame: function() {
		var modalFrame = document.createElement("iframe");
		modalFrame.src = "/var/blank.html";
		modalFrame.id = "__modalFrame";
		modalFrame.className = "pos_modal stl_modal";
		modalFrame.style.width = document.viewport.getWidth() - 18 + "px"; // -18 = scrollbar
		modalFrame.style.height = document.body.clientHeight + "px";
		document.body.appendChild(modalFrame);
	},

	/**
	 * hideWarning
	 *
	 * Hides the current warning
	 *
	 * @since initial
	 * @access public
	 * @return void
	 **/
	hideWarning: function() {
		document.getElementById("__warndiv").parentNode.removeChild(document.getElementById("__warndiv") ); 
		document.getElementById("__modalFrame").parentNode.removeChild(document.getElementById("__modalFrame") );
	},
	
	/**
	 * hideAsk
	 *
	 * Hides the current confirm
	 *
	 * @since initial
	 * @access public
	 * @return void
	 **/
	hideAsk: function() {
		document.getElementById("__askdiv").parentNode.removeChild(document.getElementById("__askdiv") ); 
		document.getElementById("__modalFrame").parentNode.removeChild(document.getElementById("__modalFrame") );
	},

	/**
	 * ask
	 * 
	 * Replacement for javascript confirm
	 *
	 * @since initial
	 * @access public
	 * @param String question
	 * @param Array options
	 * @return void
	 **/
	ask: function(question, options) {
		this._addModalFrame();
		var askDiv = this._createModalDiv("__askdiv");
		askDiv.innerHTML = "<h1 id='__askdiv_dragbar'>E-mill</h1><p>" + question + "</p>";
		
		askDiv.innerHTML += "<div class='pos_submitwrapper stl_submitwrapper'>";

		for (var i = options.length - 1; i > -1; i--) {
			askDiv.innerHTML += "<div class='pos_submit stl_submit'><input type='submit' onclick='Wmconfirm.hideAsk(); " + options[i][1] + "; return false;' value='" + options[i][0] + "'/></div>";
		}

		askDiv.innerHTML += "</div>";
		document.body.appendChild(askDiv);
		this.curask = new Draggable("__askdiv", {handle: "__askdiv_dragbar", zindex: 10000});
	},

	/**
	 * _getScreenWidth
	 *
	 * Retrieves the horizontal viewport dimension
	 *
	 * @since initial
	 * @access private
	 * @return Integer
	 **/
	_getScreenWidth: function() {
		return document.viewport.getWidth();
	},
	
	/**
	 * clickObserver
	 *
	 * Registers x and y location of browser
	 *
	 * @since Fri May 04 2007
	 * @access public
	 * @param Event event
	 * @return void
	 **/
	clickObserver: function(event) {
		Wmconfirm.curX = event.clientX;
		if (window.scrollY) {
			Wmconfirm.curY = event.clientY + window.scrollY;
		}
		else if (document.documentElement && document.documentElement.scrollTop) {
			Wmconfirm.curY = event.clientY + document.documentElement.scrollTop;
		}
		else {
			Wmconfirm.curY = event.clientY; // Required because that random sequence of bits that sometimes acts like a browser doesn't support a scrollTop of 0 (really, it won't create scrollTop until you have scolled)
		}
	}
}

var Wmconfirm = new WMConfirm();
Event.observe(document, "click", Wmconfirm.clickObserver);

/**
 * WmGroupmanagement
 *
 * Changelog
 * ---------
 *
 * Niels Nijens - Tue Jun 24 2008
 * --------------------------------
 * - Moved functionality from Wmdoelgroep to this class
 *
 * Ron Rademaker Thu Jul 24 2008
 * -----------------------------
 * - Replaced ajaxEngine with WJSpin
 *
 * To do
 * ---------
 * -
 *
 * @since Tue Jun 17 2008
 * @author Niels Nijens (niels@connectholland.nl)
 **/
var WmGroupmanagement = Class.create();
WmGroupmanagement.prototype = {
	
	/**
	 * initialize
	 *
	 * Initialize a new WmGroupmanagement
	 *
	 * @since initial
	 * @return void
	 **/
	initialize: function() {
		
	},
	
	/**
	 * activateObservers
	 *
	 * Adds observers to the fieldname selects
	 *
	 * @since Fri Aug 13 2010
	 * @return void
	 **/
	activateObservers: function() {
		var columnSelects = $(document.body).select("select[name = 'column[]']");
		for (var i = 0; i < (columnSelects.length - 1); i++) {
			columnSelects[i].observe("change", this.checkInputElement.bind(this) );
			if ( $("select_" + columnSelects[i].options[columnSelects[i].selectedIndex].text.replace(/ /g, "_").toLowerCase() ) ) {
				this.disableComparisonOptions(columnSelects[i], true);
			}
		}
		
		var comparisonSelects = $(document.body).select("select[name = 'comparison[]']");
		for (var i = 0; i < (comparisonSelects.length - 1); i++) {
			comparisonSelects[i].observe("change", this.checkComparison.bind(this) );
		}
		
		var valueSelects = $(document.body).select("select[name = 'selectvalue[]']");
		for (var i = 0; i < (valueSelects.length - 1); i++) {
			this.fillSelectInput(valueSelects[i]);
			valueSelects[i].observe("change", this.onSelectChange.bind(this) );
		}
	},
	
	/**
	 * checkInputElement
	 *
	 * If a select field for a selected fieldname exists, the input field will be changed into the select element
	 *
	 * @since Fri Aug 13 2010
	 * @param Event event
	 * @return void
	 **/
	checkInputElement: function(event) {
		var newValueSelect = $("select_" + event.element().options[event.element().selectedIndex].text.replace(/ /g, "_").toLowerCase() );
		var valueInput = event.element().up().select("input[name = 'value[]']");
		var valueSelect = event.element().up().select("select[name = 'selectvalue[]']");
		
		if (newValueSelect) {
			if (valueInput.length > 0) {
				valueInput[0].type = "hidden";
			}
			if (valueSelect.length > 0) {
				Element.remove(valueSelect[0]);
			}
			
			valueSelect = newValueSelect.cloneNode(true);
			valueSelect.setAttribute("class", "valueinput");
			valueSelect.removeAttribute("id");
			valueSelect.observe("change", this.onSelectChange.bind(this) );
			event.element().up().insertBefore(valueSelect, event.element().up().select("input")[0]);
			
			this.disableComparisonOptions(valueSelect, true);
			
			if (event.element().up().select("select[name = 'comparison[]']")[0].value == "in") {
				valueSelect.multiple = true;
				if (valueSelect.options[0].text == "") {
					valueSelect.remove(0);
				}
			}
		}
		else if (valueSelect.length > 0) {
			Element.remove(valueSelect[0]);
			
			if (valueInput.length > 0) {
				valueInput = valueInput[0];
				valueInput.type = "text";
			}
			else {
				valueInput = new Element("input", {"class": "valueinput", "type": "text", "name": "value[]"});
				event.element().up().insertBefore(valueInput, event.element().up().select("input")[0]);
			}
			
			this.disableComparisonOptions(valueInput, false);
		}
	},
	
	/**
	 * disableComparisonOptions
	 *
	 * Disables comparison options for fields with options
	 *
	 * @since Mon Aug 16 2010
	 * @param Select columnSelect
	 * @param boolean disable
	 * @return void
	 **/
	disableComparisonOptions: function(columnSelect, disable) {
		var comparisonSelect = columnSelect.up().select("select[name = 'comparison[]']")[0];
		for (var i = 0; i < comparisonSelect.options.length; i++) {
			if (["beginswith", "endswith", "greaterthan", "lessthan"].indexOf(comparisonSelect.options[i].value) != -1) {
				comparisonSelect.options[i].disabled = disable;
			}
		}
	},
	
	/**
	 * checkComparison
	 *
	 * When the comparison for a field with options is a list, the select will be changed to a multiple select
	 *
	 * @since Mon Aug 16 2010
	 * @param Event event
	 * @return void
	 **/
	checkComparison: function(event) {
		var columnSelect = event.element().up().select("select[name = 'column[]']")[0];
		var valueSelect = event.element().up().select("select[name = 'selectvalue[]']")[0];
		if ( $("select_" + columnSelect.options[columnSelect.selectedIndex].text.replace(/ /g, "_").toLowerCase() ) ) {
			if (event.element().value == "in") {
				valueSelect.multiple = true;
				if (valueSelect.options[0].text == "") {
					valueSelect.remove(0);
				}
			}
			else {
				if (valueSelect.options[0].text != "") {
					valueSelect.add(new Option("", ""), valueSelect.options[0]);
				}
				valueSelect.multiple = false;
			}
			
			this.fillSelectInput(valueSelect);
		}
	},
	
	/**
	 * onSelectChange
	 *
	 * Sets the selected options as text in the hidden input field
	 *
	 * @since Tue Aug 17 2010
	 * @param Event event
	 * @return void
	 **/
	onSelectChange: function(event) {
		this.fillSelectInput(event.element() );
	},
	
	/**
	 * fillSelectInput
	 *
	 * Sets the selected options as text in the hidden input field
	 *
	 * @since Tue Aug 17 2010
	 * @param Select element
	 * @return void
	 **/
	fillSelectInput: function(element) {
		var valueInput = element.up().select("input[name = 'value[]']")[0];
		if (element.multiple) {
			var values = new Array();
			for (var i = 0; i < element.options.length; i++) {
				if (element.options[i].selected) {
					values.push(element.options[i].value);
				}
			}
			
			valueInput.value = values.valueOf();
		}
		else {
			valueInput.value = element.value;
		}
	},
	
	/**
	 * newCriteria
	 *
	 * Adds a criterion row to the document
	 *
	 * @since initial
	 * @return void
	 **/
	newCriteria: function() {
		var criteria = $("contactgroupcriteria").childElements();
		var previousCriterion = criteria[criteria.length - 1];
		var previousCriterionColumn = previousCriterion.select("select")[0];
		var previousCriterionValue = previousCriterion.select("input")[0];
		
		if ( $("newcriterion") && previousCriterionColumn.options[previousCriterionColumn.selectedIndex].text != "" && previousCriterionValue.value != "") {
			previousCriterion.select("input")[previousCriterion.select("input").length - 1].value = "-";
			previousCriterion.select("input")[previousCriterion.select("input").length - 1].onclick = function() {
				groupmanagement.deleteCriteria(this.parentNode);
			};
			
			var criterion = $("newcriterion").childElements()[0].cloneNode(true);
			criterion.select("select[name = 'column[]']")[0].observe("change", this.checkInputElement.bind(this) );
			criterion.select("select[name = 'comparison[]']")[0].observe("change", this.checkComparison.bind(this) );
			$("contactgroupcriteria").insert(criterion);
		}
	},
	
	/**
	 * deleteCriteria
	 *
	 * Removes a criterion row from the document
	 *
	 * @since initial
	 * @param DomNode criterion
	 * @return void
	 **/
	deleteCriteria: function(criterion) {
		criterion.parentNode.removeChild(criterion);
	},
	
	/**
	 * initNewsletterSelect
	 *
	 * 
	 *
	 * @since Tue Jun 24 2008
	 * @param string divid
	 * @param integer newsletterid
	 * @return void
	 **/
	initNewsletterSelect: function(divid, newsletterid) {
		this.formdiv = $(divid);
		this.newsletterid = newsletterid;
		this.requestNewsletterSelect();
	},
	
	
	requestNewsletterSelect: function(additionalParameters) {
		parameters = {newsletter_id: this.newsletterid};
		for (key in additionalParameters) {
			parameters[key] = additionalParameters[key];
		}
		
		var spin = new WJSpin();
		var url = new WJUrl(parameters);
		url.setCt("doelgroepxml");
		url.setDt("doelgroepxml");
		spin.content(url, [this.formdiv], SpinErrorHandling);
	},
	
	
	updateNewsletterSelect: function() {
		var variables = {__cms_Wmgroupmanagement: "true", __function_Wmgroupmanagement: "selectcontactgroups"};
		variables["contactgroups[]"] = new Array();
		checkboxes = $("contactgroup_select").getElementsByTagName("input");
		for (i = 0; i < checkboxes.length; i++) {
			if (checkboxes[i].checked) {
				variables["contactgroups[]"].push(checkboxes[i].value);
			}
		}
		
		this.requestNewsletterSelect(variables);
	}
}

var groupmanagement = new WmGroupmanagement();


var WMImagemanager = Class.create();

WMImagemanager.prototype = {
	
	/**
	 * initialize
	 *
	 * Returns an instance of WMNewsletterImage
	 *
	 * @since Tue Apr 23 2007
	 * @access public
	 * @param Integer id
	 * @return WMNewsletterImage
	 **/
	initialize: function(id) {
		this.id = id;
	},
	
	/**
	 * selectImage
	 *
	 * 
	 *
	 * @since Tue Apr 23 2007
	 * @access public
	 * @return void
	 **/
	selectImage: function(image) {
		var spin = new WJSpin();
		var url = new WJUrl({ct: "imagespin", image: image, id: this.id, "disableuft[]": ["emill", "ajaxerrors"]});
		spin.update(url, "Wmimagemanager", "selectimage", {}, [this.ajaxUpdate.bind(this)]);
	},
	
	/**
	 * ajaxUpdate
	 *
	 * 
	 *
	 * @since Tue Apr 23 2007
	 * @access public
	 * @param DomNode ajaxResponse
	 * @return void
	 **/
	ajaxUpdate: function(ajaxResponse) {
		this.handleResponse(ajaxResponse);
	},
	
	/**
	 * handleResponse
	 *
	 * 
	 *
	 * @since Tue Apr 23 2007
	 * @access public
	 * @param DomNode xml
	 * @return void
	 **/
	handleResponse: function(xml) {
		var selectedimages = xml.getElementsByTagName("selectedimage");
		var selectedtagnames = xml.getElementsByTagName("selectedtagname");
		if ( (selectedtagnames.length > 0) && (selectedimages.length > 0) ) {
			window.opener.newsletter.updateImage(this.getNodeContent(selectedimages.item(0) ), this.getNodeContent(selectedtagnames.item(0) ) );
		}
		window.close();
	},
	
	/**
	 * getNodeContent
	 *
	 * Returns the contents of node
	 *
	 * @since Thu Apr 26 2007
	 * @access public
	 * @param DomNode node
	 * @return String
	 **/
	getNodeContent: function(node) {
		if (node.textContent) {
			return node.textContent;
		}
		else {
			return node.text;
		}
	},
	
	showPreview: function(position, image) {
		if (position) {
			position = parseInt(position);
			elTop = 20 * position;
		}
		else {
			elTop = 0;
		}
		elTop += 190;
		
		previewImage = $("previewimage");
		previewImage.src = image;
		Element.setStyle("previewframe", {"top": elTop + "px", "display": "block"});
	},
	
	hidePreview: function() {
		Element.setStyle("previewframe", {"display": "none"});
	}
}

/**
 * WMMailScheduler is the JavaScript class to manage the scheduling of mailings
 *
 * Changelog
 * ---------
 *
 * Niels Nijens Fri May 11 2007
 * -----------------------------
 * - Continued development
 *
 * Ron Rademaker Thu Jul 24 2008
 * -----------------------------
 * - Replaced ajaxEngine with WJSpin
 *
 *
 * @since Thu May 10 2007
 * @author Niels Nijens (niels@connectholland.nl)
 * @package Windmill.Modules.Emill.Wmnewsletter
 **/
var WMMailScheduler = Class.create();
WMMailScheduler.prototype = {
	
	/**
	 * initialize
	 *
	 * Returns an instance of WMMailScheduler
	 *
	 * @since Thu May 10 2007
	 * @access public
	 * @param Integer id
	 * @return WMMailScheduler
	 **/
	initialize: function(id) {
		this.id = id;
		this.activeDate = null;
		this.processingRequest = false;
		
		this.initCalendar();
		this.requestTimetable(new Date() );
	},
	
	/**
	 * initCalendar
	 *
	 * Creates an instance calendar instance
	 *
	 * @since Thu May 10 2007
	 * @access public
	 * @return void
	 **/
	initCalendar: function() {
		date = new Date();
		year = date.getFullYear();
		
		Calendar.setup({
			range : new Array(year, year + 1),
			flat : "calendar",
			flatCallback : this.handleCallback,
			disableFunc : this.invalidDate,
			step : 1
		});
	},
	
	/**
	 * invalidDate
	 *
	 * Returns true if the date is not allowed
	 *
	 * @since Fri May 11 2007
	 * @access public
	 * @param Date date
	 * @return Boolean
	 **/
	invalidDate: function(date) {
		today = new Date();
		if (date.getFullYear() >= today.getFullYear() ) {
			if (date.getMonth() >= today.getMonth() ) {
				if (date.getMonth() == today.getMonth() && date.getDate() >= today.getDate() ) {
					return false;
				}
				else if (date.getMonth() > today.getMonth() ) {
					return false;
				}
			}
		}
		return true;
	},
	
	/**
	 * sendRequest
	 *
	 * Sends all AJAX requests from this class
	 *
	 * @since Thu May 10 2007
	 * @access public
	 * @param String mode
	 * @param String variables
	 * @return void
	 **/
	sendRequest: function(mode, variables) {
		if (!this.processingRequest) {
			this.processingRequest = true;

			var spin = new WJSpin();
			var url = new WJUrl(variables);
			url.setCt("scheduler");
			url.setDt("scheduler");
			url.addParameter("mode", mode);

			if (mode == "scheduler") {
				spin.content(url, [this.ajaxUpdate.bind(this)], SpinErrorHandling);
			}
			else if (mode == "timetable") {
				spin.content(url, [this.updateContent.bind(this, $("timetable") ), this.ajaxUpdate.bind(this)], SpinErrorHandling);
			}
		}
	},

	/**
	 * updateContent
	 *
	 * Updates the content of element
	 *
	 * @since Fri Jul 25 2008
	 * @access public
	 * @param htmlelements element
	 * @param document response
	 * @return void
	 **/
	updateContent: function(element, response) {
		var responseNodes = response.getElementsByTagName("response");
		for (var i = 0; i < responseNodes.length; i++) {
			if (responseNodes[i].getAttribute("type") == "element") {
				try {
					element.innerHTML = "";
					element.appendChild(responseNodes[i]);
					element.innerHTML = element.innerHTML;
				}
				catch (error) {
					var xmlContent = "";
					for (var j = 0; j < responseNodes[i].childNodes.length; j++) {
						xmlContent += responseNodes[i].childNodes[j].xml;
					}
					
					element.innerHTML = xmlContent;
				}
			}
		}
	},
	
	/**
	 * handleCallback
	 *
	 * Handles the callback when a date in the calendar is selected
	 *
	 * @since Thu May 10 2007
	 * @access public
	 * @param Calendar calendar
	 * @return void
	 **/
	handleCallback: function(calendar) {
		scheduler.requestTimetable.bind(scheduler, calendar.date).delay(0.01);
	},
	
	/**
	 * requestTimetable
	 *
	 * Sends an AJAX request to request the timetable of the selected day
	 *
	 * @since Thu May 10 2007
	 * @access public
	 * @param Date date
	 * @return void
	 **/
	requestTimetable: function(date) {
		if (!this.invalidDate(date) ) {
			this.setStatus("Ophalen van beschikbare tijden...", "loading", true);
			this.processingRequest = false;
			this.activeDate = date;
			this.sendRequest("timetable", {date: this.formatDate(date), "id": this.id});
		}
	},
	
	/**
	 * requestTimetable
	 *
	 * Returns a formatted date string
	 *
	 * @since Thu May 10 2007
	 * @access public
	 * @param Date date
	 * @return String
	 **/
	formatDate: function(date) {
		y = date.getFullYear();
		m = date.getMonth() + 1;
		d = date.getDate();
		
		return y + "-" + m + "-" + d;
	},
	
	/**
	 * ajaxUpdate
	 *
	 * Handles the ajax response
	 *
	 * @since Thu May 10 2007
	 * @access public
	 * @param DomNode ajaxResponse
	 * @return void
	 **/
	ajaxUpdate: function(ajaxResponse) {
		this.processingRequest = false;
		var responseNodes = ajaxResponse.getElementsByTagName("response");
		for (var i = 0; i < responseNodes.length; i++) {
			if (responseNodes[i].getAttribute("type") == "object") {
				switch (responseNodes[i].getAttribute("mode") ) {
					case "scheduler":
						this.handleSchedule(responseNodes[i]);
						break;
					case "timetable":
						this.handleTimetable(responseNodes[i]);
						break;
					case "unavailable":
						this.handleUnavailable(responseNodes[i]);
						break;
				}
			}
		}
	},
	
	handleSchedule: function(xml) {
		if (this.getNodeContent(xml) != null) {
			this.setStatus(this.getNodeContent(xml), "done", true);
		}
		else {
			this.setStatus("Error!");
		}
	},
	
	handleTimetable: function(xml) {
		if (xml.getAttribute("error") == 1) {
			this.setStatus(this.getNodeContent(xml), null, true);
		}
		else {
			this.setStatus("");
		}
	},
	
	handleUnavailable: function(xml) {
		if (this.getNodeContent(xml) != null) {
			this.setStatus(this.getNodeContent(xml) );
		}
		else {
			this.setStatus("Error!");
		}
	},
	
	/**
	 * setStatus
	 *
	 * Sets a status text in a div
	 *
	 * @since Mon May 14 2007
	 * @access public
	 * @param String text
	 * @param Boolean icon
	 * @param Boolean disabled
	 * @return void
	 **/
	setStatus: function(text, icon, disabled) {
		scheduleStatus = $("schedulestatus");
		scheduleStatus.innerHTML = "<p>" + text + "</p>";
		if (icon) {
			scheduleStatus.innerHTML = "<img src='/images/icons/" + icon + ".gif'/>" + scheduleStatus.innerHTML;
		}
		if (disabled) {
			this.setDisabled(true);
		} else {
			this.setDisabled(false);
		}

		if (text.match(/^Mailing\ succesvol\ ingepland/) ) {
			$("completedbutton").style.display = "block";
		}
	},
	
	/**
	 * setDisabled
	 *
	 * Sets the form fields disabled when true
	 *
	 * @since Mon May 14 2007
	 * @access public
	 * @param Boolean disabled
	 * @return void
	 **/
	setDisabled: function(disabled) {
		timeField = $("time");
		if (timeField) {
			timeField.disabled = disabled;
		}
		scheduleButton = $("schedulebutton");
		if (scheduleButton) {
			scheduleButton.disabled = disabled;
		}
	},
	
	/**
	 * save
	 *
	 * Schedules the mailing in the database
	 *
	 * @since Fri May 11 2007
	 * @access public
	 * @return void
	 **/
	save: function() {
		this.setStatus("Uw mailing wordt ingepland... Dit kan enkele minuten duren.", "loading", true);
		var tijd = $F("time");
		
		this.sendRequest("scheduler", {id: this.id, __cms_Wmscheduler: "true", __function_Wmscheduler: "schedulemailing", date: this.formatDate(this.activeDate) + " " + tijd});
	},
	
	/**
	 * getNodeContent
	 *
	 * Returns the contents of node
	 *
	 * @since Mon May 14 2007
	 * @access public
	 * @param DomNode node
	 * @return String
	 **/
	getNodeContent: function(node) {
		if (node.textContent) {
			return node.textContent;
		}
		else {
			return node.text;
		}
	}
}

/**
 * WmNote
 *
 * Changelog
 * ---------
 *
 * Ron Rademaker Fri Aug 01 2008
 * -----------------------------
 * - Replaced ajaxEngine with WJSpin
 *
 * Niels Nijens - Tue May 06 2008
 * --------------------------------
 * - 
 *
 * To do
 * ---------
 * -
 *
 * @since Tue May 06 2008
 * @author Niels Nijens (niels@connectholland.nl)
 **/
var WmNote = Class.create();
WmNote.prototype = {
	
	/**
	 * initialize
	 *
	 * Initialize a new WmNote
	 *
	 * @since initial
	 * @return void
	 **/
	initialize: function() {
		this.viewAll = false;
		
	},
	
	/**
	 * initViewer
	 *
	 * 
	 *
	 * @since initial
	 * @return void
	 **/
	initViewer: function() {
		this.createNoteViewer();
	},
	
	/**
	 * createNoteViewer
	 *
	 * 
	 *
	 * @since initial
	 * @return void
	 **/
	createNoteViewer: function() {
		if ( !$("noteviewer") ) {
			div = document.createElement("div");
			div.setAttribute("id", "noteviewer");
			div.setAttribute("class", "pos_noteviewer stl_noteviewer");
			div.setAttribute("style", "display: none;");
			
			$("notesheet").appendChild(div);
			
			this.draggable = new Draggable("noteviewer", {handle: "notedrag", zindex: 10000});
		}
	},
	
	/**
	 * cleanViewer
	 *
	 * 
	 *
	 * @since Thu May 08 2008
	 * @return void
	 **/
	cleanViewer: function() {
		if ( $("notecontent") ) {
			this.setHTMLEditors(false);
			$("notecontent").innerHTML = "";
		}
	},
	
	/**
	 * setHTMLEditors
	 *
	 * 
	 *
	 * @since Thu May 08 2008
	 * @param boolean add
	 * @return void
	 **/
	setHTMLEditors: function(add) {
		if ( $("noteform") ) {
			noteForm = $("noteform");
			for (var i = 0; i < noteForm.length; i++) {
				if (noteForm.elements[i].type == "textarea") {
					if (add) {
						tinyMCE.execCommand('mceAddControl', false, noteForm.elements[i].id);
					}
					else {
						tinyMCE.execCommand('mceRemoveControl', false, noteForm.elements[i].id);
					}
				}
			}
		}
	},
	
	/**
	 * ajaxUpdate
	 *
	 * 
	 *
	 * @since Thu May 08 2008
	 * @param DomNode response
	 * @return void
	 **/
	ajaxUpdate: function(response) {
		if (response.textContent == "true") {
			$("noteviewer").style.display = "block";
		}
		
		this.setHTMLEditors(true);
	},
	
	/**
	 * view
	 *
	 * 
	 *
	 * @since initial
	 * @param integer id
	 * @return void
	 **/
	view: function(id) {
		this.initViewer();
		this.cleanViewer();
		this.sendRequest("requested", id);
	},
	
	/**
	 * toggleList
	 *
	 * 
	 *
	 * @since initial
	 * @param integer id
	 * @return void
	 **/
	toggleList: function(contactid, button) {
		if (this.viewAll) {
			this.viewAll = false;
			button.updateCaption("alle notities");
		}
		else {
			this.viewAll = true;
			button.updateCaption("laatste notities");
		}
		
		this.initViewer();
		this.sendRequest(null, null, contactid);
	},
	
	/**
	 * add
	 *
	 * 
	 *
	 * @since initial
	 * @return void
	 **/
	add: function(contactid) {
		this.initViewer();
		this.cleanViewer();
		this.sendRequest("add", null, contactid);
	},
	
	/**
	 * update
	 *
	 * 
	 *
	 * @since initial
	 * @param integer id
	 * @return void
	 **/
	update: function(id) {
		this.initViewer();
		this.cleanViewer();
		this.sendRequest("update", id);
	},
	
	/**
	 * deleteNote
	 *
	 * 
	 *
	 * @since initial
	 * @param integer id
	 * @return void
	 **/
	deleteNote: function(contactid, id) {
		this.initViewer();
		
		if (confirm("Weet u zeker dat u deze notitie wilt verwijderen?") ) {
			params = new Array();
			params.push("__cms_Wmnote=true");
			params.push("__function_Wmnote=delete");
			params.push("objid=" + id);
			
			this.sendRequest(null, null, contactid, params);
		}
	},
	
	/**
	 * submit
	 *
	 * 
	 *
	 * @since initial
	 * @return void
	 **/
	submit: function() {
		this.sendRequest();
	},
	
	/**
	 * close
	 *
	 * 
	 *
	 * @since Thu May 08 2008
	 * @return void
	 **/
	close: function() {
		if ( $("noteviewer") ) {
			this.cleanViewer();
			$("noteviewer").style.display = "none";
		}
	},
	
	/**
	 * sendRequest
	 *
	 * 
	 *
	 * @since initial
	 * @param string mode
	 * @param integer id
	 * @param array params
	 * @param string form
	 * @return void
	 **/
	sendRequest: function(mode, id, contactid, params) {
		variables = {ct: "notitie", dt: "notitiexml"};
		if (mode) {
			variables.mode = mode;
		}
		if (contactid) {
			variables.contactid = contactid;
		}
		if (id) {
			variables.id = id;
		}
		if (!this.viewAll) {
			variables.count= 4;
		}
		if ( $("noteform") ) {
			noteForm = $("noteform");
			this.setHTMLEditors(false);
			for (var i = 0; i < noteForm.length; i++) {
				if (noteForm.elements[i].type == "textarea") {
					tinyMCE.triggerSave();
				}
				if (noteForm.elements[i].type != "button") {
					variables[noteForm.elements[i].name]  = escape(noteForm.elements[i].value);
				}
			}
		}
		
		var spin = new WJSpin();
		var url = new WJUrl(variables);
		spin.content(url, [$("noteviewer"), this.ajaxUpdate.bind(this)], SpinErrorHandling);
	}
}

var wmnote = new WmNote();

/**
 * StatData is a class to represent and provide easy access to sets of statistical data
 *
 * @since Tue Jun 12 2007
 * @author Ron Rademaker
 **/
var StatData = Class.create();

StatData.prototype = {
	/**
	 * initialize
	 *
	 * Initialize a new StatData
	 *
	 * @access public
	 * @since Tue Jun 12 2007
	 * @return void
	 **/
	initialize: function() {
		this.empty = true;
		this.fields = {};
		this.colors = {};
	},

	/**
	 * setValue
	 *
	 * Sets the value of field to value
	 *
	 * @access public
	 * @since Tue Jun 12 2007
	 * @param string field
	 * @param integer value
	 * @param string color
	 * @return void
	 **/
	setValue: function(field, value, color) {
		this.empty = false;
		this.fields[field] = value;
		this.colors[field] = color;
	},

	/**
	 * getValue
	 *
	 * Gets the value of field 
	 *
	 * @access public
	 * @since Tue Jun 12 2007
	 * @param string field
	 * @return integer
	 **/
	getValue: function(field) {
		return this.fields[field];
	},

	/**
	 * getTotal
	 *
	 * Gets the sum of all fields (no, not the movie ;) )
	 *
	 * @access public
	 * @since Tue Jun 12 2007
	 * @return integer
	 **/
	getTotal: function() {
		var total = 0;
		for (var i in this.fields) {
			total += this.fields[i];
		}
		return total;
	},

	/**
	 * getPortion
	 *
	 * Gets the fraction of scale for field (for example, use scale 100 to get percentage or 2PI to get the radial angle for a pie chart)
	 *
	 * @access public
	 * @since Tue Jun 12 2007
	 * @param string field
	 * @param float scale
	 * @return float
	 **/
	getPortion: function(field, scale) {
		var total = this.getTotal();
		var value = this.getValue(field);

		return scale * (value / total);		
	},

	/**
	 * getColor
	 *
	 * Gets a color for the field
	 *
	 * @access public
	 * @since Tue Jun 12 2007
	 * @param string field
	 * @return string
	 **/
	getColor: function(field) {
		return this.colors[field];
	},

	/**
	 * getFields
	 *
	 * Returns an array of all defined fields
	 *
	 * @access public
	 * @since Tue Jun 12 2007
	 * @return array
	 **/
	getFields: function() {
		var fields = new Array();
		for (var i in this.fields) {
			fields.push(i);
		}
		return fields;
	}
}

if(!window.CanvasRenderingContext2D){(function(){var I=Math,i=I.round,L=I.sin,M=I.cos,m=10,A=m/2,Q={init:function(a){var b=a||document;if(/MSIE/.test(navigator.userAgent)&&!window.opera){var c=this;b.attachEvent("onreadystatechange",function(){c.r(b)})}},r:function(a){if(a.readyState=="complete"){if(!a.namespaces["s"]){a.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml")}var b=a.createStyleSheet();b.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}g_vml_\\:*{behavior:url(#default#VML)}";
var c=a.getElementsByTagName("canvas");for(var d=0;d<c.length;d++){if(!c[d].getContext){this.initElement(c[d])}}}},q:function(a){var b=a.outerHTML,c=a.ownerDocument.createElement(b);if(b.slice(-2)!="/>"){var d="/"+a.tagName,e;while((e=a.nextSibling)&&e.tagName!=d){e.removeNode()}if(e){e.removeNode()}}a.parentNode.replaceChild(c,a);return c},initElement:function(a){a=this.q(a);a.getContext=function(){if(this.l){return this.l}return this.l=new K(this)};a.attachEvent("onpropertychange",V);a.attachEvent("onresize",
W);var b=a.attributes;if(b.width&&b.width.specified){a.style.width=b.width.nodeValue+"px"}else{a.width=a.clientWidth}if(b.height&&b.height.specified){a.style.height=b.height.nodeValue+"px"}else{a.height=a.clientHeight}return a}};function V(a){var b=a.srcElement;switch(a.propertyName){case "width":b.style.width=b.attributes.width.nodeValue+"px";b.getContext().clearRect();break;case "height":b.style.height=b.attributes.height.nodeValue+"px";b.getContext().clearRect();break}}function W(a){var b=a.srcElement;
if(b.firstChild){b.firstChild.style.width=b.clientWidth+"px";b.firstChild.style.height=b.clientHeight+"px"}}Q.init();var R=[];for(var E=0;E<16;E++){for(var F=0;F<16;F++){R[E*16+F]=E.toString(16)+F.toString(16)}}function J(){return[[1,0,0],[0,1,0],[0,0,1]]}function G(a,b){var c=J();for(var d=0;d<3;d++){for(var e=0;e<3;e++){var g=0;for(var h=0;h<3;h++){g+=a[d][h]*b[h][e]}c[d][e]=g}}return c}function N(a,b){b.fillStyle=a.fillStyle;b.lineCap=a.lineCap;b.lineJoin=a.lineJoin;b.lineWidth=a.lineWidth;b.miterLimit=
a.miterLimit;b.shadowBlur=a.shadowBlur;b.shadowColor=a.shadowColor;b.shadowOffsetX=a.shadowOffsetX;b.shadowOffsetY=a.shadowOffsetY;b.strokeStyle=a.strokeStyle;b.d=a.d;b.e=a.e}function O(a){var b,c=1;a=String(a);if(a.substring(0,3)=="rgb"){var d=a.indexOf("(",3),e=a.indexOf(")",d+1),g=a.substring(d+1,e).split(",");b="#";for(var h=0;h<3;h++){b+=R[Number(g[h])]}if(g.length==4&&a.substr(3,1)=="a"){c=g[3]}}else{b=a}return[b,c]}function S(a){switch(a){case "butt":return"flat";case "round":return"round";
case "square":default:return"square"}}function K(a){this.a=J();this.m=[];this.k=[];this.c=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=m*1;this.globalAlpha=1;this.canvas=a;var b=a.ownerDocument.createElement("div");b.style.width=a.clientWidth+"px";b.style.height=a.clientHeight+"px";b.style.overflow="hidden";b.style.position="absolute";a.appendChild(b);this.j=b;this.d=1;this.e=1}var j=K.prototype;j.clearRect=function(){this.j.innerHTML=
"";this.c=[]};j.beginPath=function(){this.c=[]};j.moveTo=function(a,b){this.c.push({type:"moveTo",x:a,y:b});this.f=a;this.g=b};j.lineTo=function(a,b){this.c.push({type:"lineTo",x:a,y:b});this.f=a;this.g=b};j.bezierCurveTo=function(a,b,c,d,e,g){this.c.push({type:"bezierCurveTo",cp1x:a,cp1y:b,cp2x:c,cp2y:d,x:e,y:g});this.f=e;this.g=g};j.quadraticCurveTo=function(a,b,c,d){var e=this.f+0.6666666666666666*(a-this.f),g=this.g+0.6666666666666666*(b-this.g),h=e+(c-this.f)/3,l=g+(d-this.g)/3;this.bezierCurveTo(e,
g,h,l,c,d)};j.arc=function(a,b,c,d,e,g){c*=m;var h=g?"at":"wa",l=a+M(d)*c-A,n=b+L(d)*c-A,o=a+M(e)*c-A,f=b+L(e)*c-A;if(l==o&&!g){l+=0.125}this.c.push({type:h,x:a,y:b,radius:c,xStart:l,yStart:n,xEnd:o,yEnd:f})};j.rect=function(a,b,c,d){this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath()};j.strokeRect=function(a,b,c,d){this.beginPath();this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath();this.stroke()};j.fillRect=function(a,
b,c,d){this.beginPath();this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath();this.fill()};j.createLinearGradient=function(a,b,c,d){var e=new H("gradient");return e};j.createRadialGradient=function(a,b,c,d,e,g){var h=new H("gradientradial");h.n=c;h.o=g;h.i.x=a;h.i.y=b;return h};j.drawImage=function(a,b){var c,d,e,g,h,l,n,o,f=a.runtimeStyle.width,k=a.runtimeStyle.height;a.runtimeStyle.width="auto";a.runtimeStyle.height="auto";var q=a.width,r=a.height;a.runtimeStyle.width=
f;a.runtimeStyle.height=k;if(arguments.length==3){c=arguments[1];d=arguments[2];h=(l=0);n=(e=q);o=(g=r)}else if(arguments.length==5){c=arguments[1];d=arguments[2];e=arguments[3];g=arguments[4];h=(l=0);n=q;o=r}else if(arguments.length==9){h=arguments[1];l=arguments[2];n=arguments[3];o=arguments[4];c=arguments[5];d=arguments[6];e=arguments[7];g=arguments[8]}else{throw"Invalid number of arguments";}var s=this.b(c,d),t=[],v=10,w=10;t.push(" <g_vml_:group",' coordsize="',m*v,",",m*w,'"',' coordorigin="0,0"',
' style="width:',v,";height:",w,";position:absolute;");if(this.a[0][0]!=1||this.a[0][1]){var x=[];x.push("M11='",this.a[0][0],"',","M12='",this.a[1][0],"',","M21='",this.a[0][1],"',","M22='",this.a[1][1],"',","Dx='",i(s.x/m),"',","Dy='",i(s.y/m),"'");var p=s,y=this.b(c+e,d),z=this.b(c,d+g),B=this.b(c+e,d+g);p.x=Math.max(p.x,y.x,z.x,B.x);p.y=Math.max(p.y,y.y,z.y,B.y);t.push("padding:0 ",i(p.x/m),"px ",i(p.y/m),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",x.join(""),", sizingmethod='clip');")}else{t.push("top:",
i(s.y/m),"px;left:",i(s.x/m),"px;")}t.push(' ">','<g_vml_:image src="',a.src,'"',' style="width:',m*e,";"," height:",m*g,';"',' cropleft="',h/q,'"',' croptop="',l/r,'"',' cropright="',(q-h-n)/q,'"',' cropbottom="',(r-l-o)/r,'"'," />","</g_vml_:group>");this.j.insertAdjacentHTML("BeforeEnd",t.join(""))};j.stroke=function(a){var b=[],c=O(a?this.fillStyle:this.strokeStyle),d=c[0],e=c[1]*this.globalAlpha,g=10,h=10;b.push("<g_vml_:shape",' fillcolor="',d,'"',' filled="',Boolean(a),'"',' style="position:absolute;width:',
g,";height:",h,';"',' coordorigin="0 0" coordsize="',m*g," ",m*h,'"',' stroked="',!a,'"',' strokeweight="',this.lineWidth,'"',' strokecolor="',d,'"',' path="');var l={x:null,y:null},n={x:null,y:null};for(var o=0;o<this.c.length;o++){var f=this.c[o];if(f.type=="moveTo"){b.push(" m ");var k=this.b(f.x,f.y);b.push(i(k.x),",",i(k.y))}else if(f.type=="lineTo"){b.push(" l ");var k=this.b(f.x,f.y);b.push(i(k.x),",",i(k.y))}else if(f.type=="close"){b.push(" x ")}else if(f.type=="bezierCurveTo"){b.push(" c ");
var k=this.b(f.x,f.y),q=this.b(f.cp1x,f.cp1y),r=this.b(f.cp2x,f.cp2y);b.push(i(q.x),",",i(q.y),",",i(r.x),",",i(r.y),",",i(k.x),",",i(k.y))}else if(f.type=="at"||f.type=="wa"){b.push(" ",f.type," ");var k=this.b(f.x,f.y),s=this.b(f.xStart,f.yStart),t=this.b(f.xEnd,f.yEnd);b.push(i(k.x-this.d*f.radius),",",i(k.y-this.e*f.radius)," ",i(k.x+this.d*f.radius),",",i(k.y+this.e*f.radius)," ",i(s.x),",",i(s.y)," ",i(t.x),",",i(t.y))}if(k){if(l.x==null||k.x<l.x){l.x=k.x}if(n.x==null||k.x>n.x){n.x=k.x}if(l.y==
null||k.y<l.y){l.y=k.y}if(n.y==null||k.y>n.y){n.y=k.y}}}b.push(' ">');if(typeof this.fillStyle=="object"){var v={x:"50%",y:"50%"},w=n.x-l.x,x=n.y-l.y,p=w>x?w:x;v.x=i(this.fillStyle.i.x/w*100+50)+"%";v.y=i(this.fillStyle.i.y/x*100+50)+"%";var y=[];if(this.fillStyle.p=="gradientradial"){var z=this.fillStyle.n/p*100,B=this.fillStyle.o/p*100-z}else{var z=0,B=100}var C={offset:null,color:null},D={offset:null,color:null};this.fillStyle.h.sort(function(T,U){return T.offset-U.offset});for(var o=0;o<this.fillStyle.h.length;o++){var u=
this.fillStyle.h[o];y.push(u.offset*B+z,"% ",u.color,",");if(u.offset>C.offset||C.offset==null){C.offset=u.offset;C.color=u.color}if(u.offset<D.offset||D.offset==null){D.offset=u.offset;D.color=u.color}}y.pop();b.push("<g_vml_:fill",' color="',D.color,'"',' color2="',C.color,'"',' type="',this.fillStyle.p,'"',' focusposition="',v.x,", ",v.y,'"',' colors="',y.join(""),'"',' opacity="',e,'" />')}else if(a){b.push('<g_vml_:fill color="',d,'" opacity="',e,'" />')}else{b.push("<g_vml_:stroke",' opacity="',
e,'"',' joinstyle="',this.lineJoin,'"',' miterlimit="',this.miterLimit,'"',' endcap="',S(this.lineCap),'"',' weight="',this.lineWidth,'px"',' color="',d,'" />')}b.push("</g_vml_:shape>");this.j.insertAdjacentHTML("beforeEnd",b.join(""));this.c=[]};j.fill=function(){this.stroke(true)};j.closePath=function(){this.c.push({type:"close"})};j.b=function(a,b){return{x:m*(a*this.a[0][0]+b*this.a[1][0]+this.a[2][0])-A,y:m*(a*this.a[0][1]+b*this.a[1][1]+this.a[2][1])-A}};j.save=function(){var a={};N(this,a);
this.k.push(a);this.m.push(this.a);this.a=G(J(),this.a)};j.restore=function(){N(this.k.pop(),this);this.a=this.m.pop()};j.translate=function(a,b){var c=[[1,0,0],[0,1,0],[a,b,1]];this.a=G(c,this.a)};j.rotate=function(a){var b=M(a),c=L(a),d=[[b,c,0],[-c,b,0],[0,0,1]];this.a=G(d,this.a)};j.scale=function(a,b){this.d*=a;this.e*=b;var c=[[a,0,0],[0,b,0],[0,0,1]];this.a=G(c,this.a)};j.clip=function(){};j.arcTo=function(){};j.createPattern=function(){return new P};function H(a){this.p=a;this.n=0;this.o=
0;this.h=[];this.i={x:0,y:0}}H.prototype.addColorStop=function(a,b){b=O(b);this.h.push({offset:1-a,color:b})};function P(){}G_vmlCanvasManager=Q;CanvasRenderingContext2D=K;CanvasGradient=H;CanvasPattern=P})()};

// Copyright 2006 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


// Known Issues:
//
// * Patterns are not implemented.
// * Radial gradient are not implemented. The VML version of these look very
//   different from the canvas one.
// * Clipping paths are not implemented.
// * Coordsize. The width and height attribute have higher priority than the
//   width and height style values which isn't correct.
// * Painting mode isn't implemented.
// * Canvas width/height should is using content-box by default. IE in
//   Quirks mode will draw the canvas using border-box. Either change your
//   doctype to HTML5
//   (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
//   or use Box Sizing Behavior from WebFX
//   (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
// * Optimize. There is always room for speed improvements.

// only add this code if we do not already have a canvas implementation
if (!window.CanvasRenderingContext2D) {

(function () {

  // alias some functions to make (compiled) code shorter
  var m = Math;
  var mr = m.round;
  var ms = m.sin;
  var mc = m.cos;

  // this is used for sub pixel precision
  var Z = 10;
  var Z2 = Z / 2;

  var G_vmlCanvasManager_ = {
    init: function (opt_doc) {
      var doc = opt_doc || document;
      if (/MSIE/.test(navigator.userAgent) && !window.opera) {
        var self = this;
        doc.attachEvent("onreadystatechange", function () {
          self.init_(doc);
        });
      }
    },

    init_: function (doc) {
      if (doc.readyState == "complete") {
        // create xmlns
        if (!doc.namespaces["g_vml_"]) {
          doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml");
        }

        // setup default css
        var ss = doc.createStyleSheet();
        ss.cssText = "canvas{display:inline-block;overflow:hidden;" +
            // default size is 300x150 in Gecko and Opera
            "text-align:left;width:300px;height:150px}" +
            "g_vml_\\:*{behavior:url(#default#VML)}";

        // find all canvas elements
        var els = doc.getElementsByTagName("canvas");
        for (var i = 0; i < els.length; i++) {
          if (!els[i].getContext) {
            this.initElement(els[i]);
          }
        }
      }
    },

    fixElement_: function (el) {
      // in IE before version 5.5 we would need to add HTML: to the tag name
      // but we do not care about IE before version 6
      var outerHTML = el.outerHTML;

      var newEl = el.ownerDocument.createElement(outerHTML);
      // if the tag is still open IE has created the children as siblings and
      // it has also created a tag with the name "/FOO"
      if (outerHTML.slice(-2) != "/>") {
        var tagName = "/" + el.tagName;
        var ns;
        // remove content
        while ((ns = el.nextSibling) && ns.tagName != tagName) {
          ns.removeNode();
        }
        // remove the incorrect closing tag
        if (ns) {
          ns.removeNode();
        }
      }
      el.parentNode.replaceChild(newEl, el);
      return newEl;
    },

    /**
     * Public initializes a canvas element so that it can be used as canvas
     * element from now on. This is called automatically before the page is
     * loaded but if you are creating elements using createElement you need to
     * make sure this is called on the element.
     * @param {HTMLElement} el The canvas element to initialize.
     * @return {HTMLElement} the element that was created.
     */
    initElement: function (el) {
      el = this.fixElement_(el);
      el.getContext = function () {
        if (this.context_) {
          return this.context_;
        }
        return this.context_ = new CanvasRenderingContext2D_(this);
      };

      // do not use inline function because that will leak memory
      el.attachEvent('onpropertychange', onPropertyChange);
      el.attachEvent('onresize', onResize);

      var attrs = el.attributes;
      if (attrs.width && attrs.width.specified) {
        // TODO: use runtimeStyle and coordsize
        // el.getContext().setWidth_(attrs.width.nodeValue);
        el.style.width = attrs.width.nodeValue + "px";
      } else {
        el.width = el.clientWidth;
      }
      if (attrs.height && attrs.height.specified) {
        // TODO: use runtimeStyle and coordsize
        // el.getContext().setHeight_(attrs.height.nodeValue);
        el.style.height = attrs.height.nodeValue + "px";
      } else {
        el.height = el.clientHeight;
      }
      //el.getContext().setCoordsize_()
      return el;
    }
  };

  function onPropertyChange(e) {
    var el = e.srcElement;

    switch (e.propertyName) {
      case 'width':
        el.style.width = el.attributes.width.nodeValue + "px";
        el.getContext().clearRect();
        break;
      case 'height':
        el.style.height = el.attributes.height.nodeValue + "px";
        el.getContext().clearRect();
        break;
    }
  }

  function onResize(e) {
    var el = e.srcElement;
    if (el.firstChild) {
      el.firstChild.style.width =  el.clientWidth + 'px';
      el.firstChild.style.height = el.clientHeight + 'px';
    }
  }

  G_vmlCanvasManager_.init();

  // precompute "00" to "FF"
  var dec2hex = [];
  for (var i = 0; i < 16; i++) {
    for (var j = 0; j < 16; j++) {
      dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
    }
  }

  function createMatrixIdentity() {
    return [
      [1, 0, 0],
      [0, 1, 0],
      [0, 0, 1]
    ];
  }

  function matrixMultiply(m1, m2) {
    var result = createMatrixIdentity();

    for (var x = 0; x < 3; x++) {
      for (var y = 0; y < 3; y++) {
        var sum = 0;

        for (var z = 0; z < 3; z++) {
          sum += m1[x][z] * m2[z][y];
        }

        result[x][y] = sum;
      }
    }
    return result;
  }

  function copyState(o1, o2) {
    o2.fillStyle     = o1.fillStyle;
    o2.lineCap       = o1.lineCap;
    o2.lineJoin      = o1.lineJoin;
    o2.lineWidth     = o1.lineWidth;
    o2.miterLimit    = o1.miterLimit;
    o2.shadowBlur    = o1.shadowBlur;
    o2.shadowColor   = o1.shadowColor;
    o2.shadowOffsetX = o1.shadowOffsetX;
    o2.shadowOffsetY = o1.shadowOffsetY;
    o2.strokeStyle   = o1.strokeStyle;
    o2.arcScaleX_    = o1.arcScaleX_;
    o2.arcScaleY_    = o1.arcScaleY_;
  }

  function processStyle(styleString) {
    var str, alpha = 1;

    styleString = String(styleString);
    if (styleString.substring(0, 3) == "rgb") {
      var start = styleString.indexOf("(", 3);
      var end = styleString.indexOf(")", start + 1);
      var guts = styleString.substring(start + 1, end).split(",");

      str = "#";
      for (var i = 0; i < 3; i++) {
        str += dec2hex[Number(guts[i])];
      }

      if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) {
        alpha = guts[3];
      }
    } else {
      str = styleString;
    }

    return [str, alpha];
  }

  function processLineCap(lineCap) {
    switch (lineCap) {
      case "butt":
        return "flat";
      case "round":
        return "round";
      case "square":
      default:
        return "square";
    }
  }

  /**
   * This class implements CanvasRenderingContext2D interface as described by
   * the WHATWG.
   * @param {HTMLElement} surfaceElement The element that the 2D context should
   * be associated with
   */
   function CanvasRenderingContext2D_(surfaceElement) {
    this.m_ = createMatrixIdentity();

    this.mStack_ = [];
    this.aStack_ = [];
    this.currentPath_ = [];

    // Canvas context properties
    this.strokeStyle = "#000";
    this.fillStyle = "#000";

    this.lineWidth = 1;
    this.lineJoin = "miter";
    this.lineCap = "butt";
    this.miterLimit = Z * 1;
    this.globalAlpha = 1;
    this.canvas = surfaceElement;

    var el = surfaceElement.ownerDocument.createElement('div');
    el.style.width =  surfaceElement.clientWidth + 'px';
    el.style.height = surfaceElement.clientHeight + 'px';
    el.style.overflow = 'hidden';
    el.style.position = 'absolute';
    surfaceElement.appendChild(el);

    this.element_ = el;
    this.arcScaleX_ = 1;
    this.arcScaleY_ = 1;
  };

  var contextPrototype = CanvasRenderingContext2D_.prototype;
  contextPrototype.clearRect = function() {
    this.element_.innerHTML = "";
    this.currentPath_ = [];
  };

  contextPrototype.beginPath = function() {
    // TODO: Branch current matrix so that save/restore has no effect
    //       as per safari docs.

    this.currentPath_ = [];
  };

  contextPrototype.moveTo = function(aX, aY) {
    this.currentPath_.push({type: "moveTo", x: aX, y: aY});
    this.currentX_ = aX;
    this.currentY_ = aY;
  };

  contextPrototype.lineTo = function(aX, aY) {
    this.currentPath_.push({type: "lineTo", x: aX, y: aY});
    this.currentX_ = aX;
    this.currentY_ = aY;
  };

  contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
                                            aCP2x, aCP2y,
                                            aX, aY) {
    this.currentPath_.push({type: "bezierCurveTo",
                           cp1x: aCP1x,
                           cp1y: aCP1y,
                           cp2x: aCP2x,
                           cp2y: aCP2y,
                           x: aX,
                           y: aY});
    this.currentX_ = aX;
    this.currentY_ = aY;
  };

  contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
    // the following is lifted almost directly from
    // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
    var cp1x = this.currentX_ + 2.0 / 3.0 * (aCPx - this.currentX_);
    var cp1y = this.currentY_ + 2.0 / 3.0 * (aCPy - this.currentY_);
    var cp2x = cp1x + (aX - this.currentX_) / 3.0;
    var cp2y = cp1y + (aY - this.currentY_) / 3.0;
    this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, aX, aY);
  };

  contextPrototype.arc = function(aX, aY, aRadius,
                                  aStartAngle, aEndAngle, aClockwise) {
    aRadius *= Z;
    var arcType = aClockwise ? "at" : "wa";

    var xStart = aX + (mc(aStartAngle) * aRadius) - Z2;
    var yStart = aY + (ms(aStartAngle) * aRadius) - Z2;

    var xEnd = aX + (mc(aEndAngle) * aRadius) - Z2;
    var yEnd = aY + (ms(aEndAngle) * aRadius) - Z2;

    // IE won't render arches drawn counter clockwise if xStart == xEnd.
    if (xStart == xEnd && !aClockwise) {
      xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
                       // that can be represented in binary
    }

    this.currentPath_.push({type: arcType,
                           x: aX,
                           y: aY,
                           radius: aRadius,
                           xStart: xStart,
                           yStart: yStart,
                           xEnd: xEnd,
                           yEnd: yEnd});

  };

  contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
    this.moveTo(aX, aY);
    this.lineTo(aX + aWidth, aY);
    this.lineTo(aX + aWidth, aY + aHeight);
    this.lineTo(aX, aY + aHeight);
    this.closePath();
  };

  contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
    // Will destroy any existing path (same as FF behaviour)
    this.beginPath();
    this.moveTo(aX, aY);
    this.lineTo(aX + aWidth, aY);
    this.lineTo(aX + aWidth, aY + aHeight);
    this.lineTo(aX, aY + aHeight);
    this.closePath();
    this.stroke();
  };

  contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
    // Will destroy any existing path (same as FF behaviour)
    this.beginPath();
    this.moveTo(aX, aY);
    this.lineTo(aX + aWidth, aY);
    this.lineTo(aX + aWidth, aY + aHeight);
    this.lineTo(aX, aY + aHeight);
    this.closePath();
    this.fill();
  };

  contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
    var gradient = new CanvasGradient_("gradient");
    return gradient;
  };

  contextPrototype.createRadialGradient = function(aX0, aY0,
                                                   aR0, aX1,
                                                   aY1, aR1) {
    var gradient = new CanvasGradient_("gradientradial");
    gradient.radius1_ = aR0;
    gradient.radius2_ = aR1;
    gradient.focus_.x = aX0;
    gradient.focus_.y = aY0;
    return gradient;
  };

  contextPrototype.drawImage = function (image, var_args) {
    var dx, dy, dw, dh, sx, sy, sw, sh;

    // to find the original width we overide the width and height
    var oldRuntimeWidth = image.runtimeStyle.width;
    var oldRuntimeHeight = image.runtimeStyle.height;
    image.runtimeStyle.width = 'auto';
    image.runtimeStyle.height = 'auto';

    // get the original size
    var w = image.width;
    var h = image.height;

    // and remove overides
    image.runtimeStyle.width = oldRuntimeWidth;
    image.runtimeStyle.height = oldRuntimeHeight;

    if (arguments.length == 3) {
      dx = arguments[1];
      dy = arguments[2];
      sx = sy = 0;
      sw = dw = w;
      sh = dh = h;
    } else if (arguments.length == 5) {
      dx = arguments[1];
      dy = arguments[2];
      dw = arguments[3];
      dh = arguments[4];
      sx = sy = 0;
      sw = w;
      sh = h;
    } else if (arguments.length == 9) {
      sx = arguments[1];
      sy = arguments[2];
      sw = arguments[3];
      sh = arguments[4];
      dx = arguments[5];
      dy = arguments[6];
      dw = arguments[7];
      dh = arguments[8];
    } else {
      throw "Invalid number of arguments";
    }

    var d = this.getCoords_(dx, dy);

    var w2 = sw / 2;
    var h2 = sh / 2;

    var vmlStr = [];

    var W = 10;
    var H = 10;

    // For some reason that I've now forgotten, using divs didn't work
    vmlStr.push(' <g_vml_:group',
                ' coordsize="', Z * W, ',', Z * H, '"',
                ' coordorigin="0,0"' ,
                ' style="width:', W, ';height:', H, ';position:absolute;');

    // If filters are necessary (rotation exists), create them
    // filters are bog-slow, so only create them if abbsolutely necessary
    // The following check doesn't account for skews (which don't exist
    // in the canvas spec (yet) anyway.

    if (this.m_[0][0] != 1 || this.m_[0][1]) {
      var filter = [];

      // Note the 12/21 reversal
      filter.push("M11='", this.m_[0][0], "',",
                  "M12='", this.m_[1][0], "',",
                  "M21='", this.m_[0][1], "',",
                  "M22='", this.m_[1][1], "',",
                  "Dx='", mr(d.x / Z), "',",
                  "Dy='", mr(d.y / Z), "'");

      // Bounding box calculation (need to minimize displayed area so that
      // filters don't waste time on unused pixels.
      var max = d;
      var c2 = this.getCoords_(dx + dw, dy);
      var c3 = this.getCoords_(dx, dy + dh);
      var c4 = this.getCoords_(dx + dw, dy + dh);

      max.x = Math.max(max.x, c2.x, c3.x, c4.x);
      max.y = Math.max(max.y, c2.y, c3.y, c4.y);

      vmlStr.push("padding:0 ", mr(max.x / Z), "px ", mr(max.y / Z),
                  "px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",
                  filter.join(""), ", sizingmethod='clip');")
    } else {
      vmlStr.push("top:", mr(d.y / Z), "px;left:", mr(d.x / Z), "px;")
    }

    vmlStr.push(' ">' ,
                '<g_vml_:image src="', image.src, '"',
                ' style="width:', Z * dw, ';',
                ' height:', Z * dh, ';"',
                ' cropleft="', sx / w, '"',
                ' croptop="', sy / h, '"',
                ' cropright="', (w - sx - sw) / w, '"',
                ' cropbottom="', (h - sy - sh) / h, '"',
                ' />',
                '</g_vml_:group>');

    this.element_.insertAdjacentHTML("BeforeEnd",
                                    vmlStr.join(""));
  };

  contextPrototype.stroke = function(aFill) {
    var lineStr = [];
    var lineOpen = false;
    var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
    var color = a[0];
    var opacity = a[1] * this.globalAlpha;

    var W = 10;
    var H = 10;

    lineStr.push('<g_vml_:shape',
                 ' fillcolor="', color, '"',
                 ' filled="', Boolean(aFill), '"',
                 ' style="position:absolute;width:', W, ';height:', H, ';"',
                 ' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H, '"',
                 ' stroked="', !aFill, '"',
                 ' strokeweight="', this.lineWidth, '"',
                 ' strokecolor="', color, '"',
                 ' path="');

    var newSeq = false;
    var min = {x: null, y: null};
    var max = {x: null, y: null};

    for (var i = 0; i < this.currentPath_.length; i++) {
      var p = this.currentPath_[i];

      if (p.type == "moveTo") {
        lineStr.push(" m ");
        var c = this.getCoords_(p.x, p.y);
        lineStr.push(mr(c.x), ",", mr(c.y));
      } else if (p.type == "lineTo") {
        lineStr.push(" l ");
        var c = this.getCoords_(p.x, p.y);
        lineStr.push(mr(c.x), ",", mr(c.y));
      } else if (p.type == "close") {
        lineStr.push(" x ");
      } else if (p.type == "bezierCurveTo") {
        lineStr.push(" c ");
        var c = this.getCoords_(p.x, p.y);
        var c1 = this.getCoords_(p.cp1x, p.cp1y);
        var c2 = this.getCoords_(p.cp2x, p.cp2y);
        lineStr.push(mr(c1.x), ",", mr(c1.y), ",",
                     mr(c2.x), ",", mr(c2.y), ",",
                     mr(c.x), ",", mr(c.y));
      } else if (p.type == "at" || p.type == "wa") {
        lineStr.push(" ", p.type, " ");
        var c  = this.getCoords_(p.x, p.y);
        var cStart = this.getCoords_(p.xStart, p.yStart);
        var cEnd = this.getCoords_(p.xEnd, p.yEnd);

        lineStr.push(mr(c.x - this.arcScaleX_ * p.radius), ",",
                     mr(c.y - this.arcScaleY_ * p.radius), " ",
                     mr(c.x + this.arcScaleX_ * p.radius), ",",
                     mr(c.y + this.arcScaleY_ * p.radius), " ",
                     mr(cStart.x), ",", mr(cStart.y), " ",
                     mr(cEnd.x), ",", mr(cEnd.y));
      }


      // TODO: Following is broken for curves due to
      //       move to proper paths.

      // Figure out dimensions so we can do gradient fills
      // properly
      if(c) {
        if (min.x == null || c.x < min.x) {
          min.x = c.x;
        }
        if (max.x == null || c.x > max.x) {
          max.x = c.x;
        }
        if (min.y == null || c.y < min.y) {
          min.y = c.y;
        }
        if (max.y == null || c.y > max.y) {
          max.y = c.y;
        }
      }
    }
    lineStr.push(' ">');

    if (typeof this.fillStyle == "object") {
      var focus = {x: "50%", y: "50%"};
      var width = (max.x - min.x);
      var height = (max.y - min.y);
      var dimension = (width > height) ? width : height;

      focus.x = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%";
      focus.y = mr((this.fillStyle.focus_.y / height) * 100 + 50) + "%";

      var colors = [];

      // inside radius (%)
      if (this.fillStyle.type_ == "gradientradial") {
        var inside = (this.fillStyle.radius1_ / dimension * 100);

        // percentage that outside radius exceeds inside radius
        var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside;
      } else {
        var inside = 0;
        var expansion = 100;
      }

      var insidecolor = {offset: null, color: null};
      var outsidecolor = {offset: null, color: null};

      // We need to sort 'colors' by percentage, from 0 > 100 otherwise ie
      // won't interpret it correctly
      this.fillStyle.colors_.sort(function (cs1, cs2) {
        return cs1.offset - cs2.offset;
      });

      for (var i = 0; i < this.fillStyle.colors_.length; i++) {
        var fs = this.fillStyle.colors_[i];

        colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ",");

        if (fs.offset > insidecolor.offset || insidecolor.offset == null) {
          insidecolor.offset = fs.offset;
          insidecolor.color = fs.color;
        }

        if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) {
          outsidecolor.offset = fs.offset;
          outsidecolor.color = fs.color;
        }
      }
      colors.pop();

      lineStr.push('<g_vml_:fill',
                   ' color="', outsidecolor.color, '"',
                   ' color2="', insidecolor.color, '"',
                   ' type="', this.fillStyle.type_, '"',
                   ' focusposition="', focus.x, ', ', focus.y, '"',
                   ' colors="', colors.join(""), '"',
                   ' opacity="', opacity, '" />');
    } else if (aFill) {
      lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />');
    } else {
      lineStr.push(
        '<g_vml_:stroke',
        ' opacity="', opacity,'"',
        ' joinstyle="', this.lineJoin, '"',
        ' miterlimit="', this.miterLimit, '"',
        ' endcap="', processLineCap(this.lineCap) ,'"',
        ' weight="', this.lineWidth, 'px"',
        ' color="', color,'" />'
      );
    }

    lineStr.push("</g_vml_:shape>");

    this.element_.insertAdjacentHTML("beforeEnd", lineStr.join(""));

    this.currentPath_ = [];
  };

  contextPrototype.fill = function() {
    this.stroke(true);
  }

  contextPrototype.closePath = function() {
    this.currentPath_.push({type: "close"});
  };

  /**
   * @private
   */
  contextPrototype.getCoords_ = function(aX, aY) {
    return {
      x: Z * (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]) - Z2,
      y: Z * (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1]) - Z2
    }
  };

  contextPrototype.save = function() {
    var o = {};
    copyState(this, o);
    this.aStack_.push(o);
    this.mStack_.push(this.m_);
    this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
  };

  contextPrototype.restore = function() {
    copyState(this.aStack_.pop(), this);
    this.m_ = this.mStack_.pop();
  };

  contextPrototype.translate = function(aX, aY) {
    var m1 = [
      [1,  0,  0],
      [0,  1,  0],
      [aX, aY, 1]
    ];

    this.m_ = matrixMultiply(m1, this.m_);
  };

  contextPrototype.rotate = function(aRot) {
    var c = mc(aRot);
    var s = ms(aRot);

    var m1 = [
      [c,  s, 0],
      [-s, c, 0],
      [0,  0, 1]
    ];

    this.m_ = matrixMultiply(m1, this.m_);
  };

  contextPrototype.scale = function(aX, aY) {
    this.arcScaleX_ *= aX;
    this.arcScaleY_ *= aY;
    var m1 = [
      [aX, 0,  0],
      [0,  aY, 0],
      [0,  0,  1]
    ];

    this.m_ = matrixMultiply(m1, this.m_);
  };

  /******** STUBS ********/
  contextPrototype.clip = function() {
    // TODO: Implement
  };

  contextPrototype.arcTo = function() {
    // TODO: Implement
  };

  contextPrototype.createPattern = function() {
    return new CanvasPattern_;
  };

  // Gradient / Pattern Stubs
  function CanvasGradient_(aType) {
    this.type_ = aType;
    this.radius1_ = 0;
    this.radius2_ = 0;
    this.colors_ = [];
    this.focus_ = {x: 0, y: 0};
  }

  CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
    aColor = processStyle(aColor);
    this.colors_.push({offset: 1-aOffset, color: aColor});
  };

  function CanvasPattern_() {}

  // set up externs
  G_vmlCanvasManager = G_vmlCanvasManager_;
  CanvasRenderingContext2D = CanvasRenderingContext2D_;
  CanvasGradient = CanvasGradient_;
  CanvasPattern = CanvasPattern_;

})();

} // if

/**
 * Pie chart is a javascript class to create pie charts on canvas
 *
 * @since Tue Jun 12 2007
 * @author Ron Rademaker
 **/

/**
 * Only bc requirement (so now this works with both prototypejs 1.5 and 1.6)
 *
 * @since Fri Oct 17 2008
 **/
if (typeof(Function.prototype.applyWithTimeout) != "function" && typeof(Function.prototype.delay) == "function" ) {
	Function.prototype.applyWithTimeout = function() {
		var __method = this, args = $A(arguments), object = args.shift(), timeout = args.shift();
		__timoutmethod = function() {
			return __method.apply(object, args.concat($A(arguments)));
		}
		return setTimeout(__timoutmethod, timeout);
	}
}


var Piechart = Class.create();

Piechart.prototype = {
	/**
	 * initialize
	 *
	 * Initialize a new Piechart 
	 *
	 * @access public
	 * @param string canvasid
	 * @param string background
	 * @since Tue Jun 12 2007
	 * @return void
	 **/
	initialize: function(canvasid, background) {
		this.canvas = $(canvasid);
		this.context = this.canvas.getContext("2d");
		this.background = "white";
		if (background != null) {
			this.background = background;
		}
	},

	/**
	 * shade
	 *
	 * Draws a shade for the piechart
	 *
	 * @since Tue Jun 12 2007
	 * @since Tue Jun 12 2007
	 * @return void
	 **/
	shade: function(centeroffset, intensity) {
		if (/MSIE/.test(navigator.userAgent) ) { //unable to do object testing because createRadialGradient has a (broken) implementation
			// maybe provide alternative
		}
		else {
			var rad = this.context.createRadialGradient(this.getCanvasCenterX() + centeroffset, this.getCanvasCenterY() + centeroffset, Math.floor(this.getCanvasSize() / 2) - intensity, this.getCanvasCenterX() + centeroffset, this.getCanvasCenterY() + centeroffset, Math.floor(this.getCanvasSize() / 2) - centeroffset );
			rad.addColorStop(0, "black");
			rad.addColorStop(1, this.background);
			this.context.fillStyle = rad;
			this.context.fillRect(0, 0, this.canvas.clientWidth, this.canvas.clientHeight);
		}
	},

	/**
	 * draw
	 *
	 * Draws a piechart for data
	 *
	 * @access public
	 * @param object data
	 * @since Tue Jun 12 2007
	 * @return void
	 **/
	draw: function(data) {
		var fields = data.getFields();
		var curangle = -1 * (Math.PI / 2);

		var fielddata = new Array();
		for (var i = 0; i < fields.length; i++) {
			var angle = data.getPortion(fields[i], 2 * Math.PI);
			fielddata.push(new Array(angle, data.getColor(fields[i]) ) );
		}

		// three times to avoid black lines
		this.animateDraw(fielddata, curangle, curangle);
		this.animateDraw(fielddata, curangle, curangle);
		this.animateDraw(fielddata, curangle, curangle);
	},

	/**
	 * animateDraw
	 *
	 * Animates the drawing
	 *
	 * @since Tue Jun 26 2007
	 * @access public
	 * @param array data
	 * @param float startangle
	 * @param float curangle
	 * @return void
	 **/
	animateDraw: function(data, startangle, curangle) {
		if (data.length == 0) {
			return;
		}
		var curpiece = this._getCurPiece(data, startangle, curangle);
		var angle = curangle + 0.1;
		this.context.fillStyle = curpiece[1];
		this.context.beginPath();
		this.context.moveTo(this.getCanvasCenterX(), this.getCanvasCenterY() );
		this.context.arc(this.getCanvasCenterX(), this.getCanvasCenterY(), Math.floor(this.getCanvasSize() / 2) - 5, curangle, angle, false);
		this.context.fill();
		if (angle < (startangle + (2 * Math.PI) ) ) {
			this.animateDraw.applyWithTimeout(this, 5, data, startangle, angle);
		}
	},

	/**
	 * _getCurPiece
	 *
	 * Gets the datapiece at curangle when starting at startangle in data
	 *
	 * @since Tue Jun 26 2007
	 * @access public
	 * @param array data
	 * @param float startangle
	 * @param float curangle
	 * @return void
	 **/
	_getCurPiece: function(data, startangle, curangle) {
		for (var i = 0; i < data.length; i++) {
			startangle += data[i][0];
			if (startangle > curangle) {
				return data[i];
			}
		}
	},

	/**
	 * getCanvasCenterX
	 *
	 * Gets the x position of the center of the canvas
	 *
	 * @access public
	 * @since Tue Jun 12 2007
	 * @return integer
	 **/
	getCanvasCenterX: function() {
		var width = this.canvas.clientWidth;
		return Math.floor(width / 2);
	},

	/**
	 * getCanvasCenterY
	 *
	 * Gets the y position of the center of the canvas
	 *
	 * @access public
	 * @since Tue Jun 12 2007
	 * @return integer
	 **/
	getCanvasCenterY: function() {
		var width = this.canvas.clientHeight;
		return Math.floor(width / 2);
	},
	
	/**
	 * getCanvasSize
	 *
	 * Gets the size of the canvas (either width or height, whichever is smaller)
	 *
	 * @access public
	 * @since Tue Jun 12 2007
	 * @return integer
	 **/
	getCanvasSize: function() {
		return Math.min(this.canvas.clientWidth, this.canvas.clientHeight );
	}
}

// script.aculo.us scriptaculous.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008

// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// For details, see the script.aculo.us web site: http://script.aculo.us/

var Scriptaculous = {
  Version: '1.8.1',
  require: function(libraryName) {
    // inserting via DOM fails in Safari 2.0, so brute force approach
    document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
  },
  REQUIRED_PROTOTYPE: '1.6.0',
  load: function() {
    function convertVersionString(versionString){
      var r = versionString.split('.');
      return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
    }
 
    if((typeof Prototype=='undefined') || 
       (typeof Element == 'undefined') || 
       (typeof Element.Methods=='undefined') ||
       (convertVersionString(Prototype.Version) < 
        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
        Scriptaculous.REQUIRED_PROTOTYPE);
    
    $A(document.getElementsByTagName("script")).findAll( function(s) {
      return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
    }).each( function(s) {
      var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
      var includes = s.src.match(/\?.*load=([a-z,]*)/);
      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
       function(include) { Scriptaculous.require(path+include+'.js') });
    });
  }
}

Scriptaculous.load();
// script.aculo.us effects.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008

// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Contributors:
//  Justin Palmer (http://encytemedia.com/)
//  Mark Pilgrim (http://diveintomark.org/)
//  Martin Bialasinki
// 
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/ 

// converts rgb() and #xxx to #xxxxxx format,  
// returns self (or first argument) if not convertable  
String.prototype.parseColor = function() {  
  var color = '#';
  if (this.slice(0,4) == 'rgb(') {  
    var cols = this.slice(4,this.length-1).split(',');  
    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
  } else {  
    if (this.slice(0,1) == '#') {  
      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
      if (this.length==7) color = this.toLowerCase();  
    }  
  }  
  return (color.length==7 ? color : (arguments[0] || this));  
};

/*--------------------------------------------------------------------------*/

Element.collectTextNodes = function(element) {  
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue : 
      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
  }).flatten().join('');
};

Element.collectTextNodesIgnoreClass = function(element, className) {  
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue : 
      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 
        Element.collectTextNodesIgnoreClass(node, className) : ''));
  }).flatten().join('');
};

Element.setContentZoom = function(element, percent) {
  element = $(element);  
  element.setStyle({fontSize: (percent/100) + 'em'});   
  if (Prototype.Browser.WebKit) window.scrollBy(0,0);
  return element;
};

Element.getInlineOpacity = function(element){
  return $(element).style.opacity || '';
};

Element.forceRerendering = function(element) {
  try {
    element = $(element);
    var n = document.createTextNode(' ');
    element.appendChild(n);
    element.removeChild(n);
  } catch(e) { }
};

/*--------------------------------------------------------------------------*/

var Effect = {
  _elementDoesNotExistError: {
    name: 'ElementDoesNotExistError',
    message: 'The specified DOM element does not exist, but is required for this effect to operate'
  },
  Transitions: {
    linear: Prototype.K,
    sinoidal: function(pos) {
      return (-Math.cos(pos*Math.PI)/2) + 0.5;
    },
    reverse: function(pos) {
      return 1-pos;
    },
    flicker: function(pos) {
      var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
      return pos > 1 ? 1 : pos;
    },
    wobble: function(pos) {
      return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
    },
    pulse: function(pos, pulses) { 
      pulses = pulses || 5; 
      return (
        ((pos % (1/pulses)) * pulses).round() == 0 ? 
              ((pos * pulses * 2) - (pos * pulses * 2).floor()) : 
          1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
        );
    },
    spring: function(pos) { 
      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); 
    },
    none: function(pos) {
      return 0;
    },
    full: function(pos) {
      return 1;
    }
  },
  DefaultOptions: {
    duration:   1.0,   // seconds
    fps:        100,   // 100= assume 66fps max.
    sync:       false, // true for combining
    from:       0.0,
    to:         1.0,
    delay:      0.0,
    queue:      'parallel'
  },
  tagifyText: function(element) {
    var tagifyStyle = 'position:relative';
    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
    
    element = $(element);
    $A(element.childNodes).each( function(child) {
      if (child.nodeType==3) {
        child.nodeValue.toArray().each( function(character) {
          element.insertBefore(
            new Element('span', {style: tagifyStyle}).update(
              character == ' ' ? String.fromCharCode(160) : character), 
              child);
        });
        Element.remove(child);
      }
    });
  },
  multiple: function(element, effect) {
    var elements;
    if (((typeof element == 'object') || 
        Object.isFunction(element)) && 
       (element.length))
      elements = element;
    else
      elements = $(element).childNodes;
      
    var options = Object.extend({
      speed: 0.1,
      delay: 0.0
    }, arguments[2] || { });
    var masterDelay = options.delay;

    $A(elements).each( function(element, index) {
      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
    });
  },
  PAIRS: {
    'slide':  ['SlideDown','SlideUp'],
    'blind':  ['BlindDown','BlindUp'],
    'appear': ['Appear','Fade']
  },
  toggle: function(element, effect) {
    element = $(element);
    effect = (effect || 'appear').toLowerCase();
    var options = Object.extend({
      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
    }, arguments[2] || { });
    Effect[element.visible() ? 
      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
  }
};

Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;

/* ------------- core effects ------------- */

Effect.ScopedQueue = Class.create(Enumerable, {
  initialize: function() {
    this.effects  = [];
    this.interval = null;    
  },
  _each: function(iterator) {
    this.effects._each(iterator);
  },
  add: function(effect) {
    var timestamp = new Date().getTime();
    
    var position = Object.isString(effect.options.queue) ? 
      effect.options.queue : effect.options.queue.position;
    
    switch(position) {
      case 'front':
        // move unstarted effects after this effect  
        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
            e.startOn  += effect.finishOn;
            e.finishOn += effect.finishOn;
          });
        break;
      case 'with-last':
        timestamp = this.effects.pluck('startOn').max() || timestamp;
        break;
      case 'end':
        // start effect after last queued effect has finished
        timestamp = this.effects.pluck('finishOn').max() || timestamp;
        break;
    }
    
    effect.startOn  += timestamp;
    effect.finishOn += timestamp;

    if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
      this.effects.push(effect);
    
    if (!this.interval)
      this.interval = setInterval(this.loop.bind(this), 15);
  },
  remove: function(effect) {
    this.effects = this.effects.reject(function(e) { return e==effect });
    if (this.effects.length == 0) {
      clearInterval(this.interval);
      this.interval = null;
    }
  },
  loop: function() {
    var timePos = new Date().getTime();
    for(var i=0, len=this.effects.length;i<len;i++) 
      this.effects[i] && this.effects[i].loop(timePos);
  }
});

Effect.Queues = {
  instances: $H(),
  get: function(queueName) {
    if (!Object.isString(queueName)) return queueName;
    
    return this.instances.get(queueName) ||
      this.instances.set(queueName, new Effect.ScopedQueue());
  }
};
Effect.Queue = Effect.Queues.get('global');

Effect.Base = Class.create({
  position: null,
  start: function(options) {
    function codeForEvent(options,eventName){
      return (
        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
      );
    }
    if (options && options.transition === false) options.transition = Effect.Transitions.linear;
    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
    this.currentFrame = 0;
    this.state        = 'idle';
    this.startOn      = this.options.delay*1000;
    this.finishOn     = this.startOn+(this.options.duration*1000);
    this.fromToDelta  = this.options.to-this.options.from;
    this.totalTime    = this.finishOn-this.startOn;
    this.totalFrames  = this.options.fps*this.options.duration;
    
    eval('this.render = function(pos){ '+
      'if (this.state=="idle"){this.state="running";'+
      codeForEvent(this.options,'beforeSetup')+
      (this.setup ? 'this.setup();':'')+ 
      codeForEvent(this.options,'afterSetup')+
      '};if (this.state=="running"){'+
      'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
      'this.position=pos;'+
      codeForEvent(this.options,'beforeUpdate')+
      (this.update ? 'this.update(pos);':'')+
      codeForEvent(this.options,'afterUpdate')+
      '}}');
    
    this.event('beforeStart');
    if (!this.options.sync)
      Effect.Queues.get(Object.isString(this.options.queue) ? 
        'global' : this.options.queue.scope).add(this);
  },
  loop: function(timePos) {
    if (timePos >= this.startOn) {
      if (timePos >= this.finishOn) {
        this.render(1.0);
        this.cancel();
        this.event('beforeFinish');
        if (this.finish) this.finish(); 
        this.event('afterFinish');
        return;  
      }
      var pos   = (timePos - this.startOn) / this.totalTime,
          frame = (pos * this.totalFrames).round();
      if (frame > this.currentFrame) {
        this.render(pos);
        this.currentFrame = frame;
      }
    }
  },
  cancel: function() {
    if (!this.options.sync)
      Effect.Queues.get(Object.isString(this.options.queue) ? 
        'global' : this.options.queue.scope).remove(this);
    this.state = 'finished';
  },
  event: function(eventName) {
    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
    if (this.options[eventName]) this.options[eventName](this);
  },
  inspect: function() {
    var data = $H();
    for(property in this)
      if (!Object.isFunction(this[property])) data.set(property, this[property]);
    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
  }
});

Effect.Parallel = Class.create(Effect.Base, {
  initialize: function(effects) {
    this.effects = effects || [];
    this.start(arguments[1]);
  },
  update: function(position) {
    this.effects.invoke('render', position);
  },
  finish: function(position) {
    this.effects.each( function(effect) {
      effect.render(1.0);
      effect.cancel();
      effect.event('beforeFinish');
      if (effect.finish) effect.finish(position);
      effect.event('afterFinish');
    });
  }
});

Effect.Tween = Class.create(Effect.Base, {
  initialize: function(object, from, to) {
    object = Object.isString(object) ? $(object) : object;
    var args = $A(arguments), method = args.last(), 
      options = args.length == 5 ? args[3] : null;
    this.method = Object.isFunction(method) ? method.bind(object) :
      Object.isFunction(object[method]) ? object[method].bind(object) : 
      function(value) { object[method] = value };
    this.start(Object.extend({ from: from, to: to }, options || { }));
  },
  update: function(position) {
    this.method(position);
  }
});

Effect.Event = Class.create(Effect.Base, {
  initialize: function() {
    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
  },
  update: Prototype.emptyFunction
});

Effect.Opacity = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    // make this work on IE on elements without 'layout'
    if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
      this.element.setStyle({zoom: 1});
    var options = Object.extend({
      from: this.element.getOpacity() || 0.0,
      to:   1.0
    }, arguments[1] || { });
    this.start(options);
  },
  update: function(position) {
    this.element.setOpacity(position);
  }
});

Effect.Move = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      x:    0,
      y:    0,
      mode: 'relative'
    }, arguments[1] || { });
    this.start(options);
  },
  setup: function() {
    this.element.makePositioned();
    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
    if (this.options.mode == 'absolute') {
      this.options.x = this.options.x - this.originalLeft;
      this.options.y = this.options.y - this.originalTop;
    }
  },
  update: function(position) {
    this.element.setStyle({
      left: (this.options.x  * position + this.originalLeft).round() + 'px',
      top:  (this.options.y  * position + this.originalTop).round()  + 'px'
    });
  }
});

// for backwards compatibility
Effect.MoveBy = function(element, toTop, toLeft) {
  return new Effect.Move(element, 
    Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
};

Effect.Scale = Class.create(Effect.Base, {
  initialize: function(element, percent) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      scaleX: true,
      scaleY: true,
      scaleContent: true,
      scaleFromCenter: false,
      scaleMode: 'box',        // 'box' or 'contents' or { } with provided values
      scaleFrom: 100.0,
      scaleTo:   percent
    }, arguments[2] || { });
    this.start(options);
  },
  setup: function() {
    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
    this.elementPositioning = this.element.getStyle('position');
    
    this.originalStyle = { };
    ['top','left','width','height','fontSize'].each( function(k) {
      this.originalStyle[k] = this.element.style[k];
    }.bind(this));
      
    this.originalTop  = this.element.offsetTop;
    this.originalLeft = this.element.offsetLeft;
    
    var fontSize = this.element.getStyle('font-size') || '100%';
    ['em','px','%','pt'].each( function(fontSizeType) {
      if (fontSize.indexOf(fontSizeType)>0) {
        this.fontSize     = parseFloat(fontSize);
        this.fontSizeType = fontSizeType;
      }
    }.bind(this));
    
    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
    
    this.dims = null;
    if (this.options.scaleMode=='box')
      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
    if (/^content/.test(this.options.scaleMode))
      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
    if (!this.dims)
      this.dims = [this.options.scaleMode.originalHeight,
                   this.options.scaleMode.originalWidth];
  },
  update: function(position) {
    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
    if (this.options.scaleContent && this.fontSize)
      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
  },
  finish: function(position) {
    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
  },
  setDimensions: function(height, width) {
    var d = { };
    if (this.options.scaleX) d.width = width.round() + 'px';
    if (this.options.scaleY) d.height = height.round() + 'px';
    if (this.options.scaleFromCenter) {
      var topd  = (height - this.dims[0])/2;
      var leftd = (width  - this.dims[1])/2;
      if (this.elementPositioning == 'absolute') {
        if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
        if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
      } else {
        if (this.options.scaleY) d.top = -topd + 'px';
        if (this.options.scaleX) d.left = -leftd + 'px';
      }
    }
    this.element.setStyle(d);
  }
});

Effect.Highlight = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
    this.start(options);
  },
  setup: function() {
    // Prevent executing on elements not in the layout flow
    if (this.element.getStyle('display')=='none') { this.cancel(); return; }
    // Disable background image during the effect
    this.oldStyle = { };
    if (!this.options.keepBackgroundImage) {
      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
      this.element.setStyle({backgroundImage: 'none'});
    }
    if (!this.options.endcolor)
      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
    if (!this.options.restorecolor)
      this.options.restorecolor = this.element.getStyle('background-color');
    // init color calculations
    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
  },
  update: function(position) {
    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
      return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
  },
  finish: function() {
    this.element.setStyle(Object.extend(this.oldStyle, {
      backgroundColor: this.options.restorecolor
    }));
  }
});

Effect.ScrollTo = function(element) {
  var options = arguments[1] || { },
    scrollOffsets = document.viewport.getScrollOffsets(),
    elementOffsets = $(element).cumulativeOffset(),
    max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();  

  if (options.offset) elementOffsets[1] += options.offset;

  return new Effect.Tween(null,
    scrollOffsets.top,
    elementOffsets[1] > max ? max : elementOffsets[1],
    options,
    function(p){ scrollTo(scrollOffsets.left, p.round()) }
  );
};

/* ------------- combination effects ------------- */

Effect.Fade = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  var options = Object.extend({
    from: element.getOpacity() || 1.0,
    to:   0.0,
    afterFinishInternal: function(effect) { 
      if (effect.options.to!=0) return;
      effect.element.hide().setStyle({opacity: oldOpacity}); 
    }
  }, arguments[1] || { });
  return new Effect.Opacity(element,options);
};

Effect.Appear = function(element) {
  element = $(element);
  var options = Object.extend({
  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
  to:   1.0,
  // force Safari to render floated elements properly
  afterFinishInternal: function(effect) {
    effect.element.forceRerendering();
  },
  beforeSetup: function(effect) {
    effect.element.setOpacity(effect.options.from).show(); 
  }}, arguments[1] || { });
  return new Effect.Opacity(element,options);
};

Effect.Puff = function(element) {
  element = $(element);
  var oldStyle = { 
    opacity: element.getInlineOpacity(), 
    position: element.getStyle('position'),
    top:  element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height
  };
  return new Effect.Parallel(
   [ new Effect.Scale(element, 200, 
      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
     Object.extend({ duration: 1.0, 
      beforeSetupInternal: function(effect) {
        Position.absolutize(effect.effects[0].element)
      },
      afterFinishInternal: function(effect) {
         effect.effects[0].element.hide().setStyle(oldStyle); }
     }, arguments[1] || { })
   );
};

Effect.BlindUp = function(element) {
  element = $(element);
  element.makeClipping();
  return new Effect.Scale(element, 0,
    Object.extend({ scaleContent: false, 
      scaleX: false, 
      restoreAfterFinish: true,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping();
      } 
    }, arguments[1] || { })
  );
};

Effect.BlindDown = function(element) {
  element = $(element);
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({ 
    scaleContent: false, 
    scaleX: false,
    scaleFrom: 0,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
    },  
    afterFinishInternal: function(effect) {
      effect.element.undoClipping();
    }
  }, arguments[1] || { }));
};

Effect.SwitchOff = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  return new Effect.Appear(element, Object.extend({
    duration: 0.4,
    from: 0,
    transition: Effect.Transitions.flicker,
    afterFinishInternal: function(effect) {
      new Effect.Scale(effect.element, 1, { 
        duration: 0.3, scaleFromCenter: true,
        scaleX: false, scaleContent: false, restoreAfterFinish: true,
        beforeSetup: function(effect) { 
          effect.element.makePositioned().makeClipping();
        },
        afterFinishInternal: function(effect) {
          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
        }
      })
    }
  }, arguments[1] || { }));
};

Effect.DropOut = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left'),
    opacity: element.getInlineOpacity() };
  return new Effect.Parallel(
    [ new Effect.Move(element, {x: 0, y: 100, sync: true }), 
      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
    Object.extend(
      { duration: 0.5,
        beforeSetup: function(effect) {
          effect.effects[0].element.makePositioned(); 
        },
        afterFinishInternal: function(effect) {
          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
        } 
      }, arguments[1] || { }));
};

Effect.Shake = function(element) {
  element = $(element);
  var options = Object.extend({
    distance: 20,
    duration: 0.5
  }, arguments[1] || {});
  var distance = parseFloat(options.distance);
  var split = parseFloat(options.duration) / 10.0;
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left') };
    return new Effect.Move(element,
      { x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
        effect.element.undoPositioned().setStyle(oldStyle);
  }}) }}) }}) }}) }}) }});
};

Effect.SlideDown = function(element) {
  element = $(element).cleanWhitespace();
  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
  var oldInnerBottom = element.down().getStyle('bottom');
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({ 
    scaleContent: false, 
    scaleX: false, 
    scaleFrom: window.opera ? 0 : 1,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makePositioned();
      effect.element.down().makePositioned();
      if (window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
    },
    afterUpdateInternal: function(effect) {
      effect.element.down().setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' }); 
    },
    afterFinishInternal: function(effect) {
      effect.element.undoClipping().undoPositioned();
      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
    }, arguments[1] || { })
  );
};

Effect.SlideUp = function(element) {
  element = $(element).cleanWhitespace();
  var oldInnerBottom = element.down().getStyle('bottom');
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, window.opera ? 0 : 1,
   Object.extend({ scaleContent: false, 
    scaleX: false, 
    scaleMode: 'box',
    scaleFrom: 100,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makePositioned();
      effect.element.down().makePositioned();
      if (window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping().show();
    },  
    afterUpdateInternal: function(effect) {
      effect.element.down().setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' });
    },
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping().undoPositioned();
      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
    }
   }, arguments[1] || { })
  );
};

// Bug in opera makes the TD containing this element expand for a instance after finish 
Effect.Squish = function(element) {
  return new Effect.Scale(element, window.opera ? 1 : 0, { 
    restoreAfterFinish: true,
    beforeSetup: function(effect) {
      effect.element.makeClipping(); 
    },  
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping(); 
    }
  });
};

Effect.Grow = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.full
  }, arguments[1] || { });
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();    
  var initialMoveX, initialMoveY;
  var moveX, moveY;
  
  switch (options.direction) {
    case 'top-left':
      initialMoveX = initialMoveY = moveX = moveY = 0; 
      break;
    case 'top-right':
      initialMoveX = dims.width;
      initialMoveY = moveY = 0;
      moveX = -dims.width;
      break;
    case 'bottom-left':
      initialMoveX = moveX = 0;
      initialMoveY = dims.height;
      moveY = -dims.height;
      break;
    case 'bottom-right':
      initialMoveX = dims.width;
      initialMoveY = dims.height;
      moveX = -dims.width;
      moveY = -dims.height;
      break;
    case 'center':
      initialMoveX = dims.width / 2;
      initialMoveY = dims.height / 2;
      moveX = -dims.width / 2;
      moveY = -dims.height / 2;
      break;
  }
  
  return new Effect.Move(element, {
    x: initialMoveX,
    y: initialMoveY,
    duration: 0.01, 
    beforeSetup: function(effect) {
      effect.element.hide().makeClipping().makePositioned();
    },
    afterFinishInternal: function(effect) {
      new Effect.Parallel(
        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
          new Effect.Scale(effect.element, 100, {
            scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
        ], Object.extend({
             beforeSetup: function(effect) {
               effect.effects[0].element.setStyle({height: '0px'}).show(); 
             },
             afterFinishInternal: function(effect) {
               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); 
             }
           }, options)
      )
    }
  });
};

Effect.Shrink = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.none
  }, arguments[1] || { });
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();
  var moveX, moveY;
  
  switch (options.direction) {
    case 'top-left':
      moveX = moveY = 0;
      break;
    case 'top-right':
      moveX = dims.width;
      moveY = 0;
      break;
    case 'bottom-left':
      moveX = 0;
      moveY = dims.height;
      break;
    case 'bottom-right':
      moveX = dims.width;
      moveY = dims.height;
      break;
    case 'center':  
      moveX = dims.width / 2;
      moveY = dims.height / 2;
      break;
  }
  
  return new Effect.Parallel(
    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
    ], Object.extend({            
         beforeStartInternal: function(effect) {
           effect.effects[0].element.makePositioned().makeClipping(); 
         },
         afterFinishInternal: function(effect) {
           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
       }, options)
  );
};

Effect.Pulsate = function(element) {
  element = $(element);
  var options    = arguments[1] || { };
  var oldOpacity = element.getInlineOpacity();
  var transition = options.transition || Effect.Transitions.sinoidal;
  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
  reverser.bind(transition);
  return new Effect.Opacity(element, 
    Object.extend(Object.extend({  duration: 2.0, from: 0,
      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
    }, options), {transition: reverser}));
};

Effect.Fold = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height };
  element.makeClipping();
  return new Effect.Scale(element, 5, Object.extend({   
    scaleContent: false,
    scaleX: false,
    afterFinishInternal: function(effect) {
    new Effect.Scale(element, 1, { 
      scaleContent: false, 
      scaleY: false,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping().setStyle(oldStyle);
      } });
  }}, arguments[1] || { }));
};

Effect.Morph = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      style: { }
    }, arguments[1] || { });
    
    if (!Object.isString(options.style)) this.style = $H(options.style);
    else {
      if (options.style.include(':'))
        this.style = options.style.parseStyle();
      else {
        this.element.addClassName(options.style);
        this.style = $H(this.element.getStyles());
        this.element.removeClassName(options.style);
        var css = this.element.getStyles();
        this.style = this.style.reject(function(style) {
          return style.value == css[style.key];
        });
        options.afterFinishInternal = function(effect) {
          effect.element.addClassName(effect.options.style);
          effect.transforms.each(function(transform) {
            effect.element.style[transform.style] = '';
          });
        }
      }
    }
    this.start(options);
  },
  
  setup: function(){
    function parseColor(color){
      if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
      color = color.parseColor();
      return $R(0,2).map(function(i){
        return parseInt( color.slice(i*2+1,i*2+3), 16 ) 
      });
    }
    this.transforms = this.style.map(function(pair){
      var property = pair[0], value = pair[1], unit = null;

      if (value.parseColor('#zzzzzz') != '#zzzzzz') {
        value = value.parseColor();
        unit  = 'color';
      } else if (property == 'opacity') {
        value = parseFloat(value);
        if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
          this.element.setStyle({zoom: 1});
      } else if (Element.CSS_LENGTH.test(value)) {
          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
          value = parseFloat(components[1]);
          unit = (components.length == 3) ? components[2] : null;
      }

      var originalValue = this.element.getStyle(property);
      return { 
        style: property.camelize(), 
        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), 
        targetValue: unit=='color' ? parseColor(value) : value,
        unit: unit
      };
    }.bind(this)).reject(function(transform){
      return (
        (transform.originalValue == transform.targetValue) ||
        (
          transform.unit != 'color' &&
          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
        )
      )
    });
  },
  update: function(position) {
    var style = { }, transform, i = this.transforms.length;
    while(i--)
      style[(transform = this.transforms[i]).style] = 
        transform.unit=='color' ? '#'+
          (Math.round(transform.originalValue[0]+
            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
          (Math.round(transform.originalValue[1]+
            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
          (Math.round(transform.originalValue[2]+
            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
        (transform.originalValue +
          (transform.targetValue - transform.originalValue) * position).toFixed(3) + 
            (transform.unit === null ? '' : transform.unit);
    this.element.setStyle(style, true);
  }
});

Effect.Transform = Class.create({
  initialize: function(tracks){
    this.tracks  = [];
    this.options = arguments[1] || { };
    this.addTracks(tracks);
  },
  addTracks: function(tracks){
    tracks.each(function(track){
      track = $H(track);
      var data = track.values().first();
      this.tracks.push($H({
        ids:     track.keys().first(),
        effect:  Effect.Morph,
        options: { style: data }
      }));
    }.bind(this));
    return this;
  },
  play: function(){
    return new Effect.Parallel(
      this.tracks.map(function(track){
        var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
        var elements = [$(ids) || $$(ids)].flatten();
        return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
      }).flatten(),
      this.options
    );
  }
});

Element.CSS_PROPERTIES = $w(
  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 
  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
  'fontSize fontWeight height left letterSpacing lineHeight ' +
  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
  'right textIndent top width wordSpacing zIndex');
  
Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;

String.__parseStyleElement = document.createElement('div');
String.prototype.parseStyle = function(){
  var style, styleRules = $H();
  if (Prototype.Browser.WebKit)
    style = new Element('div',{style:this}).style;
  else {
    String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
    style = String.__parseStyleElement.childNodes[0].style;
  }
  
  Element.CSS_PROPERTIES.each(function(property){
    if (style[property]) styleRules.set(property, style[property]); 
  });
  
  if (Prototype.Browser.IE && this.include('opacity'))
    styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);

  return styleRules;
};

if (document.defaultView && document.defaultView.getComputedStyle) {
  Element.getStyles = function(element) {
    var css = document.defaultView.getComputedStyle($(element), null);
    return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
      styles[property] = css[property];
      return styles;
    });
  };
} else {
  Element.getStyles = function(element) {
    element = $(element);
    var css = element.currentStyle, styles;
    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
      results[property] = css[property];
      return results;
    });
    if (!styles.opacity) styles.opacity = element.getOpacity();
    return styles;
  };
};

Effect.Methods = {
  morph: function(element, style) {
    element = $(element);
    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
    return element;
  },
  visualEffect: function(element, effect, options) {
    element = $(element)
    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
    new Effect[klass](element, options);
    return element;
  },
  highlight: function(element, options) {
    element = $(element);
    new Effect.Highlight(element, options);
    return element;
  }
};

$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
  'pulsate shake puff squish switchOff dropOut').each(
  function(effect) { 
    Effect.Methods[effect] = function(element, options){
      element = $(element);
      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
      return element;
    }
  }
);

$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( 
  function(f) { Effect.Methods[f] = Element[f]; }
);

Element.addMethods(Effect.Methods);

// script.aculo.us builder.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008

// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

var Builder = {
  NODEMAP: {
    AREA: 'map',
    CAPTION: 'table',
    COL: 'table',
    COLGROUP: 'table',
    LEGEND: 'fieldset',
    OPTGROUP: 'select',
    OPTION: 'select',
    PARAM: 'object',
    TBODY: 'table',
    TD: 'table',
    TFOOT: 'table',
    TH: 'table',
    THEAD: 'table',
    TR: 'table'
  },
  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
  //       due to a Firefox bug
  node: function(elementName) {
    elementName = elementName.toUpperCase();
    
    // try innerHTML approach
    var parentTag = this.NODEMAP[elementName] || 'div';
    var parentElement = document.createElement(parentTag);
    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
    } catch(e) {}
    var element = parentElement.firstChild || null;
      
    // see if browser added wrapping tags
    if(element && (element.tagName.toUpperCase() != elementName))
      element = element.getElementsByTagName(elementName)[0];
    
    // fallback to createElement approach
    if(!element) element = document.createElement(elementName);
    
    // abort if nothing could be created
    if(!element) return;

    // attributes (or text)
    if(arguments[1])
      if(this._isStringOrNumber(arguments[1]) ||
        (arguments[1] instanceof Array) ||
        arguments[1].tagName) {
          this._children(element, arguments[1]);
        } else {
          var attrs = this._attributes(arguments[1]);
          if(attrs.length) {
            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
              parentElement.innerHTML = "<" +elementName + " " +
                attrs + "></" + elementName + ">";
            } catch(e) {}
            element = parentElement.firstChild || null;
            // workaround firefox 1.0.X bug
            if(!element) {
              element = document.createElement(elementName);
              for(attr in arguments[1]) 
                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
            }
            if(element.tagName.toUpperCase() != elementName)
              element = parentElement.getElementsByTagName(elementName)[0];
          }
        } 

    // text, or array of children
    if(arguments[2])
      this._children(element, arguments[2]);

     return element;
  },
  _text: function(text) {
     return document.createTextNode(text);
  },

  ATTR_MAP: {
    'className': 'class',
    'htmlFor': 'for'
  },

  _attributes: function(attributes) {
    var attrs = [];
    for(attribute in attributes)
      attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
          '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
    return attrs.join(" ");
  },
  _children: function(element, children) {
    if(children.tagName) {
      element.appendChild(children);
      return;
    }
    if(typeof children=='object') { // array can hold nodes and text
      children.flatten().each( function(e) {
        if(typeof e=='object')
          element.appendChild(e)
        else
          if(Builder._isStringOrNumber(e))
            element.appendChild(Builder._text(e));
      });
    } else
      if(Builder._isStringOrNumber(children))
        element.appendChild(Builder._text(children));
  },
  _isStringOrNumber: function(param) {
    return(typeof param=='string' || typeof param=='number');
  },
  build: function(html) {
    var element = this.node('div');
    $(element).update(html.strip());
    return element.down();
  },
  dump: function(scope) { 
    if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope 
  
    var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
      "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
      "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
      "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
      "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
      "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
  
    tags.each( function(tag){ 
      scope[tag] = function() { 
        return Builder.node.apply(Builder, [tag].concat($A(arguments)));  
      } 
    });
  }
}

// script.aculo.us controls.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008

// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//           (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
// Contributors:
//  Richard Livsey
//  Rahul Bhargava
//  Rob Wills
// 
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

// Autocompleter.Base handles all the autocompletion functionality 
// that's independent of the data source for autocompletion. This
// includes drawing the autocompletion menu, observing keyboard
// and mouse events, and similar.
//
// Specific autocompleters need to provide, at the very least, 
// a getUpdatedChoices function that will be invoked every time
// the text inside the monitored textbox changes. This method 
// should get the text for which to provide autocompletion by
// invoking this.getToken(), NOT by directly accessing
// this.element.value. This is to allow incremental tokenized
// autocompletion. Specific auto-completion logic (AJAX, etc)
// belongs in getUpdatedChoices.
//
// Tokenized incremental autocompletion is enabled automatically
// when an autocompleter is instantiated with the 'tokens' option
// in the options parameter, e.g.:
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
// will incrementally autocomplete with a comma as the token.
// Additionally, ',' in the above example can be replaced with
// a token array, e.g. { tokens: [',', '\n'] } which
// enables autocompletion on multiple tokens. This is most 
// useful when one of the tokens is \n (a newline), as it 
// allows smart autocompletion after linebreaks.

if(typeof Effect == 'undefined')
  throw("controls.js requires including script.aculo.us' effects.js library");

var Autocompleter = { }
Autocompleter.Base = Class.create({
  baseInitialize: function(element, update, options) {
    element          = $(element)
    this.element     = element; 
    this.update      = $(update);  
    this.hasFocus    = false; 
    this.changed     = false; 
    this.active      = false; 
    this.index       = 0;     
    this.entryCount  = 0;
    this.oldElementValue = this.element.value;

    if(this.setOptions)
      this.setOptions(options);
    else
      this.options = options || { };

    this.options.paramName    = this.options.paramName || this.element.name;
    this.options.tokens       = this.options.tokens || [];
    this.options.frequency    = this.options.frequency || 0.4;
    this.options.minChars     = this.options.minChars || 1;
    this.options.onShow       = this.options.onShow || 
      function(element, update){ 
        if(!update.style.position || update.style.position=='absolute') {
          update.style.position = 'absolute';
          Position.clone(element, update, {
            setHeight: false, 
            offsetTop: element.offsetHeight
          });
        }
        Effect.Appear(update,{duration:0.15});
      };
    this.options.onHide = this.options.onHide || 
      function(element, update){ new Effect.Fade(update,{duration:0.15}) };

    if(typeof(this.options.tokens) == 'string') 
      this.options.tokens = new Array(this.options.tokens);
    // Force carriage returns as token delimiters anyway
    if (!this.options.tokens.include('\n'))
      this.options.tokens.push('\n');

    this.observer = null;
    
    this.element.setAttribute('autocomplete','off');

    Element.hide(this.update);

    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
  },

  show: function() {
    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
    if(!this.iefix && 
      (Prototype.Browser.IE) &&
      (Element.getStyle(this.update, 'position')=='absolute')) {
      new Insertion.After(this.update, 
       '<iframe id="' + this.update.id + '_iefix" '+
       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
       'sr
