var BrowserDetect = {
	init: function () {
		this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
		this.version = this.searchVersion(navigator.userAgent)
			|| this.searchVersion(navigator.appVersion)
			|| "an unknown version";
		this.OS = this.searchString(this.dataOS) || "an unknown OS";
	},
	searchString: function (data) {
		for (var i=0;i<data.length;i++)	{
			var dataString = data[i].string;
			var dataProp = data[i].prop;
			this.versionSearchString = data[i].versionSearch || data[i].identity;
			if (dataString) {
				if (dataString.indexOf(data[i].subString) != -1)
					return data[i].identity;
			}
			else if (dataProp)
				return data[i].identity;
		}
	},
	searchVersion: function (dataString) {
		var index = dataString.indexOf(this.versionSearchString);
		if (index == -1) return;
		return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
	},
	dataBrowser: [
		{ 	string: navigator.userAgent,
			subString: "OmniWeb",
			versionSearch: "OmniWeb/",
			identity: "OmniWeb"
		},
		{
			string: navigator.userAgent,
			subString: "Chrome",
			identity: "Chrome"
		},
		{
			string: navigator.vendor,
			subString: "Apple",
			identity: "Safari"
		},
		{
			prop: window.opera,
			identity: "Opera"
		},
		{
			string: navigator.vendor,
			subString: "iCab",
			identity: "iCab"
		},
		{
			string: navigator.vendor,
			subString: "KDE",
			identity: "Konqueror"
		},
		{
			string: navigator.userAgent,
			subString: "Firefox",
			identity: "Firefox"
		},
		{
			string: navigator.vendor,
			subString: "Camino",
			identity: "Camino"
		},
		{		// for newer Netscapes (6+)
			string: navigator.userAgent,
			subString: "Netscape",
			identity: "Netscape"
		},
		{
			string: navigator.userAgent,
			subString: "MSIE",
			identity: "Explorer",
			versionSearch: "MSIE"
		},
		{
			string: navigator.userAgent,
			subString: "Gecko",
			identity: "Mozilla",
			versionSearch: "rv"
		},
		{ 		// for older Netscapes (4-)
			string: navigator.userAgent,
			subString: "Mozilla",
			identity: "Netscape",
			versionSearch: "Mozilla"
		}
	],
	dataOS : [
		{
			string: navigator.platform,
			subString: "Win",
			identity: "Windows"
		},
		{
			string: navigator.platform,
			subString: "Mac",
			identity: "Mac"
		},
		{
			string: navigator.platform,
			subString: "Linux",
			identity: "Linux"
		}
	]

};
BrowserDetect.init();

/*  Prototype JavaScript framework, version 1.5.1.1
 *  (c) 2005-2007 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.5.1.1',

  Browser: {
    IE:     !!(window.attachEvent && !window.opera),
    Opera:  !!window.opera,
    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1
  },

  BrowserFeatures: {
    XPath: !!document.evaluate,
    ElementExtensions: !!window.HTMLElement,
    SpecificElementExtensions:
      (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 }
};

var Class = {
  create: function() {
    return function() {
      this.initialize.apply(this, arguments);
    }
  }
};

var Abstract = new Object();

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 === undefined) return 'undefined';
      if (object === null) return 'null';
      return object.inspect ? object.inspect() : object.toString();
    } 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.ownerDocument === document) return;
    var results = [];
    for (var property in object) {
      var value = Object.toJSON(object[property]);
      if (value !== undefined)
        results.push(property.toJSON() + ': ' + value);
    }
    return '{' + results.join(', ') + '}';
  },

  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);
  }
});

Function.prototype.bind = function() {
  var __method = this, args = $A(arguments), object = args.shift();
  return function() {
    return __method.apply(object, args.concat($A(arguments)));
  }
};

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

Object.extend(Number.prototype, {
  toColorPart: function() {
    return this.toPaddedString(2, 16);
  },

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

  times: function(iterator) {
    $R(0, this, true).each(iterator);
    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';
  }
});

Date.prototype.toJSON = function() {
  return '"' + this.getFullYear() + '-' +
    (this.getMonth() + 1).toPaddedString(2) + '-' +
    this.getDate().toPaddedString(2) + 'T' +
    this.getHours().toPaddedString(2) + ':' +
    this.getMinutes().toPaddedString(2) + ':' +
    this.getSeconds().toPaddedString(2) + '"';
};

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;
  }
};

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

var PeriodicalExecuter = Class.create();
PeriodicalExecuter.prototype = {
  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);
  },

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

  onTimerEvent: function() {
    if (!this.currentlyExecuting) {
      try {
        this.currentlyExecuting = true;
        this.callback(this);
      } 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 = count === undefined ? 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 this;
  },

  truncate: function(length, truncation) {
    length = length || 30;
    truncation = truncation === undefined ? '...' : truncation;
    return this.length > length ?
      this.slice(0, length - truncation.length) + truncation : 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 = document.createElement('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 (hash[key].constructor != Array) 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) {
    var result = '';
    for (var i = 0; i < count; i++) result += this;
    return result;
  },

  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.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);
  }
});

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.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
  }
});

String.prototype.gsub.prepareReplacement = function(replacement) {
  if (typeof replacement == 'function') 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('')
});

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

var Template = Class.create();
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
Template.prototype = {
  initialize: function(template, pattern) {
    this.template = template.toString();
    this.pattern  = pattern || Template.Pattern;
  },

  evaluate: function(object) {
    return this.template.gsub(this.pattern, function(match) {
      var before = match[1];
      if (before == '\\') return match[2];
      return before + String.interpret(object[match[3]]);
    });
  }
};

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

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

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

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

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

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

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

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

  grep: function(pattern, iterator) {
    var results = [];
    this.each(function(value, index) {
      var stringValue = value.toString();
      if (stringValue.match(pattern))
        results.push((iterator || Prototype.K)(value, index));
    });
    return results;
  },

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

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

  inject: function(memo, iterator) {
    this.each(function(value, index) {
      memo = iterator(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) {
    var result;
    this.each(function(value, index) {
      value = (iterator || Prototype.K)(value, index);
      if (result == undefined || value >= result)
        result = value;
    });
    return result;
  },

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

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

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

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

  sortBy: function(iterator) {
    return this.map(function(value, index) {
      return {value: value, criteria: iterator(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 (typeof args.last() == 'function')
      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,
  member:  Enumerable.include,
  entries: Enumerable.toArray
});
var $A = Array.from = function(iterable) {
  if (!iterable) return [];
  if (iterable.toArray) {
    return iterable.toArray();
  } else {
    var results = [];
    for (var i = 0, length = iterable.length; i < length; i++)
      results.push(iterable[i]);
    return results;
  }
};

if (Prototype.Browser.WebKit) {
  $A = Array.from = function(iterable) {
    if (!iterable) return [];
    if (!(typeof iterable == 'function' && iterable == '[object NodeList]') &&
      iterable.toArray) {
      return iterable.toArray();
    } else {
      var results = [];
      for (var i = 0, length = iterable.length; i < length; i++)
        results.push(iterable[i]);
      return results;
    }
  };
}

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(value && value.constructor == Array ?
        value.flatten() : [value]);
    });
  },

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

  indexOf: function(object) {
    for (var i = 0, length = this.length; i < length; i++)
      if (this[i] == object) return i;
    return -1;
  },

  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;
    });
  },

  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 (value !== undefined) results.push(value);
    });
    return '[' + results.join(', ') + ']';
  }
});

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

function $w(string) {
  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 (arguments[i].constructor == Array) {
        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
          array.push(arguments[i][j]);
      } else {
        array.push(arguments[i]);
      }
    }
    return array;
  };
}
var Hash = function(object) {
  if (object instanceof Hash) this.merge(object);
  else Object.extend(this, object || {});
};

Object.extend(Hash, {
  toQueryString: function(obj) {
    var parts = [];
    parts.add = arguments.callee.addPair;

    this.prototype._each.call(obj, function(pair) {
      if (!pair.key) return;
      var value = pair.value;

      if (value && typeof value == 'object') {
        if (value.constructor == Array) value.each(function(value) {
          parts.add(pair.key, value);
        });
        return;
      }
      parts.add(pair.key, value);
    });

    return parts.join('&');
  },

  toJSON: function(object) {
    var results = [];
    this.prototype._each.call(object, function(pair) {
      var value = Object.toJSON(pair.value);
      if (value !== undefined) results.push(pair.key.toJSON() + ': ' + value);
    });
    return '{' + results.join(', ') + '}';
  }
});

Hash.toQueryString.addPair = function(key, value, prefix) {
  key = encodeURIComponent(key);
  if (value === undefined) this.push(key);
  else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value)));
};

Object.extend(Hash.prototype, Enumerable);
Object.extend(Hash.prototype, {
  _each: function(iterator) {
    for (var key in this) {
      var value = this[key];
      if (value && value == Hash.prototype[key]) continue;

      var pair = [key, value];
      pair.key = key;
      pair.value = value;
      iterator(pair);
    }
  },

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

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

  merge: function(hash) {
    return $H(hash).inject(this, function(mergedHash, pair) {
      mergedHash[pair.key] = pair.value;
      return mergedHash;
    });
  },

  remove: function() {
    var result;
    for(var i = 0, length = arguments.length; i < length; i++) {
      var value = this[arguments[i]];
      if (value !== undefined){
        if (result === undefined) result = value;
        else {
          if (result.constructor != Array) result = [result];
          result.push(value)
        }
      }
      delete this[arguments[i]];
    }
    return result;
  },

  toQueryString: function() {
    return Hash.toQueryString(this);
  },

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

  toJSON: function() {
    return Hash.toJSON(this);
  }
});

function $H(object) {
  if (object instanceof Hash) return object;
  return new Hash(object);
};

// Safari iterates over shadowed properties
if (function() {
  var i = 0, Test = function(value) { this.key = value };
  Test.prototype.key = 'foo';
  for (var property in new Test('bar')) i++;
  return i > 1;
}()) Hash.prototype._each = function(iterator) {
  var cache = [];
  for (var key in this) {
    var value = this[key];
    if ((value && value == Hash.prototype[key]) || cache.include(key)) continue;
    cache.push(key);
    var pair = [key, value];
    pair.key = key;
    pair.value = value;
    iterator(pair);
  }
};
ObjectRange = Class.create();
Object.extend(ObjectRange.prototype, Enumerable);
Object.extend(ObjectRange.prototype, {
  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 (typeof responder[callback] == 'function') {
        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 = function() {};
Ajax.Base.prototype = {
  setOptions: function(options) {
    this.options = {
      method:       'post',
      asynchronous: true,
      contentType:  'application/x-www-form-urlencoded',
      encoding:     'UTF-8',
      parameters:   ''
    };
    Object.extend(this.options, options || {});

    this.options.method = this.options.method.toLowerCase();
    if (typeof this.options.parameters == 'string')
      this.options.parameters = this.options.parameters.toQueryParams();
  }
};

Ajax.Request = Class.create();
Ajax.Request.Events =
  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
  _complete: false,

  initialize: function(url, options) {
    this.transport = Ajax.getTransport();
    this.setOptions(options);
    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 = Hash.toQueryString(params)) {
      // when GET, append parameters to URL
      if (this.method == 'get')
        this.url += (this.url.include('?') ? '&' : '?') + params;
      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
        params += '&_=';
    }

    try {
      if (this.options.onCreate) this.options.onCreate(this.transport);
      Ajax.Responders.dispatch('onCreate', this, this.transport);

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

      if (this.options.asynchronous)
        setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10);

      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 (typeof extras.push == 'function')
        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() {
    return !this.transport.status
        || (this.transport.status >= 200 && this.transport.status < 300);
  },

  respondToReadyState: function(readyState) {
    var state = Ajax.Request.Events[readyState];
    var transport = this.transport, json = this.evalJSON();

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

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

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

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

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

  evalJSON: function() {
    try {
      var json = this.getHeader('X-JSON');
      return json ? json.evalJSON() : 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.Updater = Class.create();

Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
  initialize: function(container, url, options) {
    this.container = {
      success: (container.success || container),
      failure: (container.failure || (container.success ? null : container))
    };

    this.transport = Ajax.getTransport();
    this.setOptions(options);

    var onComplete = this.options.onComplete || Prototype.emptyFunction;
    this.options.onComplete = (function(transport, param) {
      this.updateContent();
			setTimeout(function() { onComplete(transport, param); }, 15); //updateContent uses 10ms timer... fire onComplete after content would be executed
      
    }).bind(this);

    this.request(url);
  },

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

    if (!this.options.evalScripts) response = response.stripScripts();

	 //alert("updateContent");
    if (receiver = $(receiver)) {
		 if (this.options.insertion) {
			 //alert("uc 1");
			 new this.options.insertion(receiver, response);
			 //alert("uc 1b");
		 } else {
			 //alert("uc2");
			 receiver.update(response);
		 }
    }

    if (this.success()) {
      if (this.onComplete)
        setTimeout(this.onComplete.bind(this), 10);
    }
  }
});

Ajax.PeriodicalUpdater = Class.create();
Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
  initialize: function(container, url, options) {
    this.setOptions(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(request) {
    if (this.options.decay) {
      this.decay = (request.responseText == this.lastText ?
        this.decay * this.options.decay : 1);

      this.lastText = request.responseText;
    }
    this.timer = setTimeout(this.onTimerEvent.bind(this),
      this.decay * this.frequency * 1000);
  },

  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 (typeof element == 'string')
    element = document.getElementById(element);
  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(query.snapshotItem(i));
    return results;
  };

  document.getElementsByClassName = function(className, parentElement) {
    var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
    return document._getElementsByXPath(q, parentElement);
  };

} else document.getElementsByClassName = function(className, parentElement) {
  var children = ($(parentElement) || document.body).getElementsByTagName('*');
  var elements = [], child, pattern = new RegExp("(^|\\s)" + className + "(\\s|$)");
  for (var i = 0, length = children.length; i < length; i++) {
    child = children[i];
    var elementClassName = child.className;
    if (elementClassName.length == 0) continue;
    if (elementClassName == className || elementClassName.match(pattern))
      elements.push(Element.extend(child));
  }
  return elements;
};

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

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

Element.extend = function(element) {
  var F = Prototype.BrowserFeatures;
  if (!element || !element.tagName || element.nodeType == 3 ||
   element._extended || F.SpecificElementExtensions || element == window)
    return element;

  var methods = {}, tagName = element.tagName, cache = Element.extend.cache,
   T = Element.Methods.ByTag;

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

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

  for (var property in methods) {
    var value = methods[property];
    if (typeof value == 'function' && !(property in element))
      element[property] = cache.findOrStore(value);
  }

  element._extended = Prototype.emptyFunction;
  return element;
};

Element.extend.cache = {
  findOrStore: function(value) {
    return this[value] = this[value] || function() {
      return value.apply(null, [this].concat($A(arguments)));
    }
  }
};

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

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

  hide: function(element) {
    $(element).style.display = 'none';
    return element;

  },

  show: function(element) {
    $(element).style.display = '';
    return element;
  },

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

  update: function(element, html) {
    html = typeof html == 'undefined' ? '' : html.toString();
    $(element).innerHTML = html.stripScripts();
    setTimeout(function() {html.evalScripts()}, 10);
    return element;
  },

  replace: function(element, html) {
    element = $(element);
    html = typeof html == 'undefined' ? '' : html.toString();
    if (element.outerHTML) {
      element.outerHTML = html.stripScripts();
    } else {
      var range = element.ownerDocument.createRange();
      range.selectNodeContents(element);
      element.parentNode.replaceChild(
        range.createContextualFragment(html.stripScripts()), element);
    }
    setTimeout(function() {html.evalScripts()}, 10);
    return element;
  },

  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 $A($(element).getElementsByTagName('*')).each(Element.extend);
  },

  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 (typeof selector == 'string')
      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 expression ? Selector.findElement(ancestors, expression, index) :
      ancestors[index || 0];
  },

  down: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return element.firstDescendant();
    var descendants = element.descendants();
    return expression ? Selector.findElement(descendants, expression, index) :
      descendants[index || 0];
  },

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

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

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

  getElementsByClassName: function(element, className) {
    return document.getElementsByClassName(className, element);
  },

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

  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;
    if (elementClassName.length == 0) return false;
    if (elementClassName == className ||
        elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
      return true;
    return false;
  },

  addClassName: function(element, className) {
    if (!(element = $(element))) return;
    Element.classNames(element).add(className);
    return element;
  },

  removeClassName: function(element, className) {
    if (!(element = $(element))) return;
    Element.classNames(element).remove(className);
    return element;
  },

  toggleClassName: function(element, className) {
    if (!(element = $(element))) return;
    Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className);
    return element;
  },

  observe: function() {
    Event.observe.apply(Event, arguments);
    return $A(arguments).first();
  },

  stopObserving: function() {
    Event.stopObserving.apply(Event, arguments);
    return $A(arguments).first();
  },

  // 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);
    while (element = element.parentNode)
      if (element == ancestor) return true;
    return false;
  },

  scrollTo: function(element) {
    element = $(element);
    var pos = Position.cumulativeOffset(element);
    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) {
      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, camelized) {
    element = $(element);
    var elementStyle = element.style;

    for (var property in styles) {
      if (property == 'opacity') element.setOpacity(styles[property]);
      else
        elementStyle[(property == 'float' || property == 'cssFloat') ?
          (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') :
          (camelized ? property : property.camelize())] = 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;
    els.visibility = 'hidden';
    els.position = 'absolute';
    els.display = 'block';
    var originalWidth = element.clientWidth;
    var originalHeight = element.clientHeight;
    els.display = originalDisplay;
    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 (window.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.style.overflow || 'auto';
    if ((Element.getStyle(element, 'overflow') || 'visible') != '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;
  }
};

Object.extend(Element.Methods, {
  childOf: Element.Methods.descendantOf,
  childElements: Element.Methods.immediateDescendants
});

if (Prototype.Browser.Opera) {
  Element.Methods._getStyle = Element.Methods.getStyle;
  Element.Methods.getStyle = function(element, style) {
    switch(style) {
      case 'left':
      case 'top':
      case 'right':
      case 'bottom':
        if (Element._getStyle(element, 'position') == 'static') return null;
      default: return Element._getStyle(element, style);
    }
  };
}
else if (Prototype.Browser.IE) {
  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) {
    element = $(element);
    var filter = element.getStyle('filter'), style = element.style;
    if (value == 1 || value === '') {
      style.filter = filter.replace(/alpha\([^\)]*\)/gi,'');
      return element;
    } else if (value < 0.00001) value = 0;
    style.filter = filter.replace(/alpha\([^\)]*\)/gi, '') +
      'alpha(opacity=' + (value * 100) + ')';
    return element;
  };

  // IE is missing .innerHTML support for TABLE-related elements
  Element.Methods.update = function(element, html) {
    element = $(element);
    html = typeof html == 'undefined' ? '' : html.toString();
    var tagName = element.tagName.toUpperCase();
    if (['THEAD','TBODY','TR','TD'].include(tagName)) {
      var div = document.createElement('div');
      switch (tagName) {
        case 'THEAD':
        case 'TBODY':
          div.innerHTML = '<table><tbody>' +  html.stripScripts() + '</tbody></table>';
          depth = 2;
          break;
        case 'TR':
          div.innerHTML = '<table><tbody><tr>' +  html.stripScripts() + '</tr></tbody></table>';
          depth = 3;
          break;
        case 'TD':
          div.innerHTML = '<table><tbody><tr><td>' +  html.stripScripts() + '</td></tr></tbody></table>';
          depth = 4;
      }
      $A(element.childNodes).each(function(node) { element.removeChild(node) });
      depth.times(function() { div = div.firstChild });
      $A(div.childNodes).each(function(node) { element.appendChild(node) });
    } else {
      element.innerHTML = html.stripScripts();
    }
    setTimeout(function() { html.evalScripts() }, 10);
    return element;
  };
}
else if (Prototype.Browser.Gecko) {
  Element.Methods.setOpacity = function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1) ? 0.999999 :
      (value === '') ? '' : (value < 0.00001) ? 0 : value;
    return element;
  };
}

Element._attributeTranslations = {
  names: {
    colspan:   "colSpan",
    rowspan:   "rowSpan",
    valign:    "vAlign",
    datetime:  "dateTime",
    accesskey: "accessKey",
    tabindex:  "tabIndex",
    enctype:   "encType",
    maxlength: "maxLength",
    readonly:  "readOnly",
    longdesc:  "longDesc"
  },
  values: {
    _getAttr: function(element, attribute) {
      return element.getAttribute(attribute, 2);
    },
    _flag: function(element, attribute) {
      return $(element).hasAttribute(attribute) ? attribute : null;
    },
    style: function(element) {
      return element.style.cssText.toLowerCase();
    },
    title: function(element) {
      var node = element.getAttributeNode('title');
      return node.specified ? node.nodeValue : null;
    }
  }
};

(function() {
  Object.extend(this, {
    href: this._getAttr,
    src:  this._getAttr,
    type: this._getAttr,
    disabled: this._flag,
    checked:  this._flag,
    readonly: this._flag,
    multiple: this._flag
  });
}).call(Element._attributeTranslations.values);

Element.Methods.Simulated = {
  hasAttribute: function(element, attribute) {
    var t = Element._attributeTranslations, node;
    attribute = t.names[attribute] || attribute;
    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.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 (tagName.constructor == Array) 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;
    var cache = Element.extend.cache;
    for (var property in methods) {
      var value = methods[property];
      if (!onlyIfAbsent || !(property in destination))
        destination[property] = cache.findOrStore(value);
    }
  }

  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 (typeof klass == "undefined") continue;
      copy(T[tag], klass.prototype);
    }
  }

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

var Toggle = { display: Element.toggle };

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

Abstract.Insertion = function(adjacency) {
  this.adjacency = adjacency;
};

Abstract.Insertion.prototype = {
  initialize: function(element, content) {
    this.element = $(element);
    this.content = content.stripScripts();
	 //alert("initialize");

    if (this.adjacency && this.element.insertAdjacentHTML) {
		 //alert("initialize 1");
      try {
        this.element.insertAdjacentHTML(this.adjacency, this.content);
      } catch (e) {
			//alert("ex");
        var tagName = this.element.tagName.toUpperCase();
		  //alert(tagName);
        if (['TBODY', 'TR'].include(tagName)) {
			  //alert("try again");
          this.insertContent(this.contentFromAnonymousTable());
        } else if (['TABLE'].include(tagName)) {
			  //alert("try again table");
          this.insertContent(this.contentFromAnonymousTBody());
        } else {
          throw e;
        }
      }
    } else {
      this.range = this.element.ownerDocument.createRange();
      if (this.initializeRange) this.initializeRange();
      this.insertContent([this.range.createContextualFragment(this.content)]);
    }

    setTimeout(function() {content.evalScripts()}, 10);
  },

  contentFromAnonymousTable: function() {
    var div = document.createElement('div');
    div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
    return $A(div.childNodes[0].childNodes[0].childNodes);
  },
  
  contentFromAnonymousTBody: function() {
    var div = document.createElement('div');
    div.innerHTML = '<table>' + this.content + '</table>';
    return $A(div.childNodes[0].childNodes);
  }
};

var Insertion = new Object();

Insertion.Before = Class.create();
Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
  initializeRange: function() {
    this.range.setStartBefore(this.element);
  },

  insertContent: function(fragments) {
    fragments.each((function(fragment) {
      this.element.parentNode.insertBefore(fragment, this.element);
    }).bind(this));
  }
});

Insertion.Top = Class.create();
Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
  initializeRange: function() {
    this.range.selectNodeContents(this.element);
    this.range.collapse(true);
  },

  insertContent: function(fragments) {
    fragments.reverse(false).each((function(fragment) {
      this.element.insertBefore(fragment, this.element.firstChild);
    }).bind(this));
  }
});

Insertion.Bottom = Class.create();
Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
  initializeRange: function() {
	  //alert("init range");
    this.range.selectNodeContents(this.element);
    this.range.collapse(this.element);
	 //alert("init range end");
  },

  insertContent: function(fragments) {
	  //alert("insertContent");
	  //alert(fragments);
    fragments.each((function(fragment) {
			 //alert("b4");
      this.element.appendChild(fragment);
		//alert("af");
    }).bind(this));
  }
});

Insertion.After = Class.create();
Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
  initializeRange: function() {
    this.range.setStartAfter(this.element);
  },

  insertContent: function(fragments) {
    fragments.each((function(fragment) {
      this.element.parentNode.insertBefore(fragment,
        this.element.nextSibling);
    }).bind(this));
  }
});

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

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);
/* 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();

Selector.prototype = {
  initialize: function(expression) {
    this.expression = expression.strip();
    this.compileMatcher();
  },

  compileMatcher: function() {
    // Selectors with namespaced attributes can't use the XPath version
    if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression))
      return this.compileXPathMatcher();

    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(typeof c[i] == 'function' ? 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(typeof x[i] == 'function' ? 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;
    if (this.xpath) return document._getElementsByXPath(this.xpath, root);
    return this.matcher(root);
  },

  match: function(element) {
    return this.findElements(document).include(element);
  },

  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: "[@#{1}]",
    attr: function(m) {
      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 (typeof h === 'function') 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 or translate(text(), ' \t\r\n', '') = '')]",
      'checked':     "[@checked]",
      'disabled':    "[@disabled]",
      'enabled':     "[not(@disabled)]",
      'not': function(m) {
        var e = m[6], p = Selector.patterns,
            x = Selector.xpath, le, m, v;

        var exclusion = [];
        while (e && le != e && (/\S/).test(e)) {
          le = e;
          for (var i in p) {
            if (m = e.match(p[i])) {
              v = typeof x[i] == 'function' ? 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 = false;',
    attr: function(m) {
      m[3] = (m[5] || m[6]);
      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}");  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]+)\]/,
    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/
  },

  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) {
      for (var i = 0, node; node = nodes[i]; i++)
        node._counted = true;
      return nodes;
    },

    unmark: function(nodes) {
      for (var i = 0, node; node = nodes[i]; i++)
        node._counted = 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._counted = true;
      if (reverse) {
        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
          node = nodes[i];
          if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
        }
      } else {
        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
          if (node.nodeType == 1 && (!ofType || node._counted)) 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])._counted) {
          n._counted = true;
          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, children = [], 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) {
      tagName = 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() == tagName) results.push(node);
        return results;
      } else return root.getElementsByTagName(tagName);
    },

    id: function(nodes, root, id, combinator) {
      var targetNode = $(id), h = Selector.handlers;
      if (!nodes && root == document) return targetNode ? [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) {
      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) {
      if (!nodes) nodes = root.getElementsByTagName("*");
      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._counted) {
          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 && !node.innerHTML.match(/^\s*$/))) 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._counted) 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) 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.startsWith(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() + '-'); }
  },

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

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

  findChildElements: function(element, expressions) {
    var exprs = expressions.join(','), expressions = [];
    exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
      expressions.push(m[1].strip());
    });
    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;
  }
});

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

  serializeElements: function(elements, getHash) {
    var data = elements.inject({}, function(result, element) {
      if (!element.disabled && element.name) {
        var key = element.name, value = $(element).getValue();
        if (value != null) {
         	if (key in result) {
            if (result[key].constructor != Array) result[key] = [result[key]];
            result[key].push(value);
          }
          else result[key] = value;
        }
      }
      return result;
    });

    return getHash ? data : Hash.toQueryString(data);
  }
};

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

  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) {
    return $(form).getElements().find(function(element) {
      return element.type != 'hidden' && !element.disabled &&
        ['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;
    options.parameters = form.serialize(true);

    if (params) {
      if (typeof params == 'string') params = params.toQueryParams();
      Object.extend(options.parameters, params);
    }

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

    return new Ajax.Request(form.readAttribute('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 Hash.toQueryString(pair);
      }
    }
    return '';
  },

  getValue: function(element) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    return Form.Element.Serializers[method](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.blur();
    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) {
    switch (element.type.toLowerCase()) {
      case 'checkbox':
      case 'radio':
        return Form.Element.Serializers.inputSelector(element);
      default:
        return Form.Element.Serializers.textarea(element);
    }
  },

  inputSelector: function(element) {
    return element.checked ? element.value : null;
  },

  textarea: function(element) {
    return element.value;
  },

  select: function(element) {
    return this[element.type == 'select-one' ?
      'selectOne' : 'selectMany'](element);
  },

  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 = function() {};
Abstract.TimedObserver.prototype = {
  initialize: function(element, frequency, callback) {
    this.frequency = frequency;
    this.element   = $(element);
    this.callback  = callback;

    this.lastValue = this.getValue();
    this.registerCallback();
  },

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

  onTimerEvent: function() {
    var value = this.getValue();
    var changed = ('string' == typeof this.lastValue && 'string' == typeof value
      ? this.lastValue != value : String(this.lastValue) != String(value));
    if (changed) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  }
};

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

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

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

Abstract.EventObserver = function() {};
Abstract.EventObserver.prototype = {
  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.bind(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();
Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

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

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,

  element: function(event) {
    return $(event.target || event.srcElement);
  },

  isLeftClick: function(event) {
    return (((event.which) && (event.which == 1)) ||
            ((event.button) && (event.button == 1)));
  },

  pointerX: function(event) {
    return event.pageX || (event.clientX +
      (document.documentElement.scrollLeft || document.body.scrollLeft));
  },

  pointerY: function(event) {
    return event.pageY || (event.clientY +
      (document.documentElement.scrollTop || document.body.scrollTop));
  },

  stop: function(event) {
    if (event.preventDefault) {
      event.preventDefault();
      event.stopPropagation();
    } else {
      event.returnValue = false;
      event.cancelBubble = true;
    }
  },

  // find the first node with the given tagName, starting from the
  // node the event was triggered on; traverses the DOM upwards
  findElement: function(event, tagName) {
    var element = Event.element(event);
    while (element.parentNode && (!element.tagName ||
        (element.tagName.toUpperCase() != tagName.toUpperCase())))
      element = element.parentNode;
    return element;
  },

  observers: false,

  _observeAndCache: function(element, name, observer, useCapture) {
    if (!this.observers) this.observers = [];
    if (element.addEventListener) {
      this.observers.push([element, name, observer, useCapture]);
      element.addEventListener(name, observer, useCapture);
    } else if (element.attachEvent) {
      this.observers.push([element, name, observer, useCapture]);
      element.attachEvent('on' + name, observer);
    }
  },

  unloadCache: function() {
    if (!Event.observers) return;
    for (var i = 0, length = Event.observers.length; i < length; i++) {
      Event.stopObserving.apply(this, Event.observers[i]);
      Event.observers[i][0] = null;
    }
    Event.observers = false;
  },

  observe: function(element, name, observer, useCapture) {
    //element = $(element);
    useCapture = useCapture || false;

    if (name == 'keypress' &&
      (Prototype.Browser.WebKit || element.attachEvent))
      name = 'keydown';

    Event._observeAndCache(element, name, observer, useCapture);
  },

  stopObserving: function(element, name, observer, useCapture) {
    //element = $(element);
    useCapture = useCapture || false;

    if (name == 'keypress' &&
        (Prototype.Browser.WebKit || element.attachEvent))
      name = 'keydown';

    if (element.removeEventListener) {
      element.removeEventListener(name, observer, useCapture);
    } else if (element.detachEvent) {
      try {
        element.detachEvent('on' + name, observer);
      } catch (e) {}
    }
  }
});

/* prevent memory leaks in IE */
if (Prototype.Browser.IE)
  Event.observe(window, 'unload', Event.unloadCache, false);
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;
  },

  realOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.scrollTop  || 0;
      valueL += element.scrollLeft || 0;
      element = element.parentNode;
    } while (element);
    return [valueL, valueT];
  },

  cumulativeOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {        
      valueT += (element.offsetTop  || 0);
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
    } while (element);
    return [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=='BODY') break;
        var p = Element.getStyle(element, 'position');
        if (p == 'relative' || p == 'absolute') break;
      }
    } while (element);
    return [valueL, valueT];
  },

  offsetParent: 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;
  },

  // 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 = this.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 = this.realOffset(element);

    this.xcomp = x + offsetcache[0] - this.deltaX;
    this.ycomp = y + offsetcache[1] - this.deltaY;
    this.offset = this.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;
  },

  page: 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)
        if (Element.getStyle(element,'position')=='absolute') break;

    } while (element = element.offsetParent);

    element = forElement;
    do {
      if (!window.opera || element.tagName=='BODY') {
        valueT -= element.scrollTop  || 0;
        valueL -= element.scrollLeft || 0;
      }
    } while (element = element.parentNode);

    return [valueL, valueT];
  },

  clone: function(source, target) {
    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 = Position.page(source);

    // find coordinate system to use
    target = $(target);
    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(target,'position') == 'absolute') {
      parent = Position.offsetParent(target);
      delta = Position.page(parent);
    }

    // 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)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
    if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
    if(options.setWidth)  target.style.width = source.offsetWidth + 'px';
    if(options.setHeight) target.style.height = source.offsetHeight + 'px';
  },

  absolutize: function(element) {
    element = $(element);
    if (element.style.position == 'absolute') return;
    Position.prepare();

    var offsets = Position.positionedOffset(element);
    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';
  },

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

    element.style.position = 'relative';
    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);

    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.height = element._originalHeight;
    element.style.width  = element._originalWidth;
  }
};

/* Safari returns margins on body which is incorrect if the child is absolutely
positioned.  For performance reasons, redefine Position.cumulativeOffset for
 KHTML/WebKit only.*/
 
if (Prototype.Browser.WebKit) {
  Position.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 [valueL, valueT];
  };
}

Element.addMethods();
	/************************************************************************************************************
	(C) www.dhtmlgoodies.com, October 2005
	
	This is a script from www.dhtmlgoodies.com. You will find this and a lot of other scripts at our website.	
	
	Terms of use:
	You are free to use this script as long as the copyright message is kept intact. However, you may not
	redistribute, sell or repost it without our permission.
	
	Thank you!
	
	www.dhtmlgoodies.com
	Alf Magne Kalleland
	
	************************************************************************************************************/	

	var MSIE = navigator.userAgent.indexOf('MSIE')>=0?true:false;
	var navigatorVersion = navigator.appVersion.replace(/.*?MSIE (\d\.\d).*/g,'$1')/1;
	
	var form_widget_amount_slider_handle = '/images/color/slider_handle.gif';
	var slider_handle_image_obj = false;
	var sliderObjectArray = new Array();
	var slider_counter = 0;
	var slideInProgress = false;
	var handle_start_x;
	var event_start_x;
	var currentSliderIndex;
	
	function form_widget_cancel_event()
	{
		return false;		
	}
	
	function getImageSliderHeight(){
		if(!slider_handle_image_obj){
			slider_handle_image_obj = new Image();
			slider_handle_image_obj.src = form_widget_amount_slider_handle;
		}
		if(slider_handle_image_obj.width>0){
			return;
		}else{
			setTimeout('getImageSliderHeight()',50);
		}
	}
	
	function positionSliderImage(e,theIndex,inputObj)
	{
		if(this)inputObj = this;
		if(!theIndex)theIndex = inputObj.getAttribute('sliderIndex');
		var handleImg = document.getElementById('slider_handle' + theIndex);
		var ratio = sliderObjectArray[theIndex]['width'] / (sliderObjectArray[theIndex]['max']-sliderObjectArray[theIndex]['min']);
		var currentValue = sliderObjectArray[theIndex]['formTarget'].value-sliderObjectArray[theIndex]['min'];		
		handleImg.style.left = currentValue * ratio + 'px';			
		setColorByRGB();
	}
	
	function adjustFormValue(theIndex)
	{
		var handleImg = document.getElementById('slider_handle' + theIndex);	
		var ratio = sliderObjectArray[theIndex]['width'] / (sliderObjectArray[theIndex]['max']-sliderObjectArray[theIndex]['min']);
		var currentPos = handleImg.style.left.replace('px','');
		sliderObjectArray[theIndex]['formTarget'].value = Math.round(currentPos / ratio) + sliderObjectArray[theIndex]['min'];
		
	}
		
	function initMoveSlider(e)
	{
	
		if(document.all)e = event;	
		slideInProgress = true;
		event_start_x = e.clientX;
		handle_start_x = this.style.left.replace('px','');
		currentSliderIndex = this.id.replace(/[^\d]/g,'');
		return false;
	}
	
	function startMoveSlider(e)
	{
		if(document.all)e = event;	
		try {
			if(!slideInProgress)return;
		} catch(e) {
			return;
		}
		var leftPos = handle_start_x/1 + e.clientX/1 - event_start_x;
		if(leftPos<0)leftPos = 0;
		if(leftPos/1>sliderObjectArray[currentSliderIndex]['width'])leftPos = sliderObjectArray[currentSliderIndex]['width'];
		document.getElementById('slider_handle' + currentSliderIndex).style.left = leftPos + 'px';
		adjustFormValue(currentSliderIndex);
		if(sliderObjectArray[currentSliderIndex]['onchangeAction']){
			eval(sliderObjectArray[currentSliderIndex]['onchangeAction']);
		}
	}
	
	function stopMoveSlider()
	{
		slideInProgress = false;
	}
	
	
	function form_widget_amount_slider(targetElId,formTarget,width,min,max,onchangeAction)
	{
		if(!slider_handle_image_obj){
			getImageSliderHeight();		
		}
				
		slider_counter = slider_counter +1;
		sliderObjectArray[slider_counter] = new Array();
		sliderObjectArray[slider_counter] = {"width":width - 9,"min":min,"max":max,"formTarget":formTarget,"onchangeAction":onchangeAction};
		
		formTarget.setAttribute('sliderIndex',slider_counter);
		formTarget.onchange = positionSliderImage;
		var parentObj = document.createElement('DIV');
		parentObj.style.width = width + 'px';
		parentObj.style.height = '12px';	// The height of the image
		parentObj.style.position = 'relative';
		parentObj.id = 'slider_container' + slider_counter;
		document.getElementById(targetElId).appendChild(parentObj);
		
		var obj = document.createElement('DIV');
		obj.className = 'form_widget_amount_slider';
		obj.innerHTML = '<span></span>';
		obj.style.width = width + 'px';
		obj.id = 'slider_slider' + slider_counter;
		obj.style.position = 'absolute';
		obj.style.bottom = '0px';
		parentObj.appendChild(obj);
		
		var handleImg = document.createElement('IMG');
		handleImg.style.position = 'absolute';
		handleImg.style.left = '0px';
		handleImg.style.zIndex = 5;
		handleImg.src = slider_handle_image_obj.src;
		handleImg.id = 'slider_handle' + slider_counter;
		handleImg.onmousedown = initMoveSlider;
		if(document.body.onmouseup){
			if(document.body.onmouseup.toString().indexOf('stopMoveSlider')==-1){
				alert('You allready have an onmouseup event assigned to the body tag');
			}
		}else{
			document.body.onmouseup = stopMoveSlider;	
			document.body.onmousemove = startMoveSlider;	
		}
		handleImg.ondragstart = form_widget_cancel_event;
		parentObj.appendChild(handleImg);
		positionSliderImage(false,slider_counter);
	}
		

	
	var namedColors = new Array('AliceBlue','AntiqueWhite','Aqua','Aquamarine','Azure','Beige','Bisque','Black','BlanchedAlmond','Blue','BlueViolet','Brown',
	'BurlyWood','CadetBlue','Chartreuse','Chocolate','Coral','CornflowerBlue','Cornsilk','Crimson','Cyan','DarkBlue','DarkCyan','DarkGoldenRod','DarkGray',
	'DarkGreen','DarkKhaki','DarkMagenta','DarkOliveGreen','Darkorange','DarkOrchid','DarkRed','DarkSalmon','DarkSeaGreen','DarkSlateBlue','DarkSlateGray',
	'DarkTurquoise','DarkViolet','DeepPink','DeepSkyBlue','DimGray','DodgerBlue','Feldspar','FireBrick','FloralWhite','ForestGreen','Fuchsia','Gainsboro',
	'GhostWhite','Gold','GoldenRod','Gray','Green','GreenYellow','HoneyDew','HotPink','IndianRed','Indigo','Ivory','Khaki','Lavender','LavenderBlush',
	'LawnGreen','LemonChiffon','LightBlue','LightCoral','LightCyan','LightGoldenRodYellow','LightGrey','LightGreen','LightPink','LightSalmon','LightSeaGreen',
	'LightSkyBlue','LightSlateBlue','LightSlateGray','LightSteelBlue','LightYellow','Lime','LimeGreen','Linen','Magenta','Maroon','MediumAquaMarine',
	'MediumBlue','MediumOrchid','MediumPurple','MediumSeaGreen','MediumSlateBlue','MediumSpringGreen','MediumTurquoise','MediumVioletRed','MidnightBlue',
	'MintCream','MistyRose','Moccasin','NavajoWhite','Navy','OldLace','Olive','OliveDrab','Orange','OrangeRed','Orchid','PaleGoldenRod','PaleGreen',
	'PaleTurquoise','PaleVioletRed','PapayaWhip','PeachPuff','Peru','Pink','Plum','PowderBlue','Purple','Red','RosyBrown','RoyalBlue','SaddleBrown',
	'Salmon','SandyBrown','SeaGreen','SeaShell','Sienna','Silver','SkyBlue','SlateBlue','SlateGray','Snow','SpringGreen','SteelBlue','Tan','Teal','Thistle',
	'Tomato','Turquoise','Violet','VioletRed','Wheat','White','WhiteSmoke','Yellow','YellowGreen');
	
	 var namedColorRGB = new Array('#F0F8FF','#FAEBD7','#00FFFF','#7FFFD4','#F0FFFF','#F5F5DC','#FFE4C4','#000000','#FFEBCD','#0000FF','#8A2BE2','#A52A2A','#DEB887',
	'#5F9EA0','#7FFF00','#D2691E','#FF7F50','#6495ED','#FFF8DC','#DC143C','#00FFFF','#00008B','#008B8B','#B8860B','#A9A9A9','#006400','#BDB76B','#8B008B',
	'#556B2F','#FF8C00','#9932CC','#8B0000','#E9967A','#8FBC8F','#483D8B','#2F4F4F','#00CED1','#9400D3','#FF1493','#00BFFF','#696969','#1E90FF','#D19275',
	'#B22222','#FFFAF0','#228B22','#FF00FF','#DCDCDC','#F8F8FF','#FFD700','#DAA520','#808080','#008000','#ADFF2F','#F0FFF0','#FF69B4','#CD5C5C','#4B0082',
	'#FFFFF0','#F0E68C','#E6E6FA','#FFF0F5','#7CFC00','#FFFACD','#ADD8E6','#F08080','#E0FFFF','#FAFAD2','#D3D3D3','#90EE90','#FFB6C1','#FFA07A','#20B2AA',
	'#87CEFA','#8470FF','#778899','#B0C4DE','#FFFFE0','#00FF00','#32CD32','#FAF0E6','#FF00FF','#800000','#66CDAA','#0000CD','#BA55D3','#9370D8','#3CB371',
	'#7B68EE','#00FA9A','#48D1CC','#C71585','#191970','#F5FFFA','#FFE4E1','#FFE4B5','#FFDEAD','#000080','#FDF5E6','#808000','#6B8E23','#FFA500','#FF4500',
	'#DA70D6','#EEE8AA','#98FB98','#AFEEEE','#D87093','#FFEFD5','#FFDAB9','#CD853F','#FFC0CB','#DDA0DD','#B0E0E6','#800080','#FF0000','#BC8F8F','#4169E1',
	'#8B4513','#FA8072','#F4A460','#2E8B57','#FFF5EE','#A0522D','#C0C0C0','#87CEEB','#6A5ACD','#708090','#FFFAFA','#00FF7F','#4682B4','#D2B48C','#008080',
	'#D8BFD8','#FF6347','#40E0D0','#EE82EE','#D02090','#F5DEB3','#FFFFFF','#F5F5F5','#FFFF00','#9ACD32');	
	
	
	var color_picker_div = false;
	var color_picker_active_tab = false;
	var color_picker_form_field = false;
	var color_picker_active_input = false;
	function baseConverter (number,ob,nb) {
		number = number + "";
		number = number.toUpperCase();
		var list = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
		var dec = 0;
		for (var i = 0; i <=  number.length; i++) {
			dec += (list.indexOf(number.charAt(i))) * (Math.pow(ob , (number.length - i - 1)));
		}
		number = "";
		var magnitude = Math.floor((Math.log(dec))/(Math.log(nb)));
		for (var i = magnitude; i >= 0; i--) {
			var amount = Math.floor(dec/Math.pow(nb,i));
			number = number + list.charAt(amount); 
			dec -= amount*(Math.pow(nb,i));
		}
		if(number.length==0)number=0;
		return number;
	}
	
	function colorPickerGetTopPos(inputObj)
	{
		
	  var returnValue = inputObj.offsetTop;
	  while((inputObj = inputObj.offsetParent) != null){
	  	returnValue += inputObj.offsetTop;
	  }
	  return returnValue;
	}
	
	function colorPickerGetLeftPos(inputObj)
	{
	  var returnValue = inputObj.offsetLeft;
	  while((inputObj = inputObj.offsetParent) != null)returnValue += inputObj.offsetLeft;
	  return returnValue;
	}
	
	function cancelColorPickerEvent(){
		return false;
	}
	
	function showHideColorOptions(e,inputObj)
	{
		

		var thisObj = this;
		if(inputObj){
			var parentNode = inputObj.parentNode; 
			thisObj = inputObj;
		}else var parentNode = this.parentNode;
		var activeColorDiv = false;
		var subDiv = parentNode.getElementsByTagName('DIV')[0];
		counter=0;	
		var initZIndex = 10;	
		var contentDiv = document.getElementById('color_picker_content').getElementsByTagName('DIV')[0];
		do{			
			if(subDiv.tagName=='DIV' && subDiv.className!='colorPickerCloseButton'){
				if(subDiv==thisObj){
					thisObj.className='colorPickerTab_active';
					thisObj.style.zIndex = 50;
					var img = thisObj.getElementsByTagName('IMG')[0];
					img.src = "/images/color/tab_right_active.gif"
					img.src = img.src.replace(/inactive/,'active');							
					contentDiv.style.display='block';
					activeColorDiv = contentDiv;
				}else{
					subDiv.className = 'colorPickerTab_inactive';	
					var img = subDiv.getElementsByTagName('IMG')[0];
					img.src = "/images/color/tab_right_inactive.gif";
					if(activeColorDiv)
						subDiv.style.zIndex = initZIndex - counter;
					else
						subDiv.style.zIndex = counter;
					contentDiv.style.display='none';
				}
				counter++;
			}
			subDiv = subDiv.nextSibling;
			if(contentDiv.nextSibling)contentDiv = contentDiv.nextSibling;
		}while(subDiv);
		
		
		document.getElementById('colorPicker_statusBarTxt').innerHTML = '&nbsp;';


	}
	
	function createColorPickerTopRow(inputObj){
		var tabs = ['RGB','Named colors','Color slider'];
		var tabWidths = [37,90,70];
		var div = document.createElement('DIV');
		div.className='colorPicker_topRow';
	
		inputObj.appendChild(div);	
		var currentWidth = 0;
		for(var no=0;no<tabs.length;no++){			
			
			var tabDiv = document.createElement('DIV');
			tabDiv.onselectstart = cancelColorPickerEvent;
			tabDiv.ondragstart = cancelColorPickerEvent;
			if(no==0){
				suffix = 'active'; 
				color_picker_active_tab = this;
			}else suffix = 'inactive';
			
			tabDiv.id = 'colorPickerTab' + no;
			tabDiv.onclick = showHideColorOptions;
			if(no==0)tabDiv.style.zIndex = 50; else tabDiv.style.zIndex = 1 + (tabs.length-no);
			tabDiv.style.left = currentWidth + 'px';
			tabDiv.style.position = 'absolute';
			tabDiv.className='colorPickerTab_' + suffix;
			var tabSpan = document.createElement('SPAN');
			tabSpan.innerHTML = tabs[no];
			tabDiv.appendChild(tabSpan);
			var tabImg = document.createElement('IMG');
			tabImg.src = "/images/color/tab_right_" + suffix + ".gif";
			tabDiv.appendChild(tabImg);
			div.appendChild(tabDiv);
			if(navigatorVersion<6 && MSIE){	/* Lower IE version fix */
				tabSpan.style.position = 'relative';
				tabImg.style.position = 'relative';
				tabImg.style.left = '-3px';		
				tabDiv.style.cursor = 'hand';	
			}			
			currentWidth = currentWidth + tabWidths[no];
		
		}
		
		var closeButton = document.createElement('DIV');
		closeButton.className='colorPickerCloseButton';
		closeButton.innerHTML = 'x';
		closeButton.onclick = closeColorPicker;
		closeButton.onmouseover = toggleCloseButton;
		closeButton.onmouseout = toggleOffCloseButton;
		div.appendChild(closeButton);
		
	}
	
	function toggleCloseButton()
	{
		this.style.color='#FFF';
		this.style.backgroundColor = '#317082';	
	}
	function toggleOffCloseButton()
	{
		this.style.color='';
		this.style.backgroundColor = '';			
		
	}
	function closeColorPicker()
	{
		
		color_picker_div.style.display='none';
	}
	function createWebColors(inputObj){
		var webColorDiv = document.createElement('DIV');
		webColorDiv.style.paddingTop = '1px';
		inputObj.appendChild(webColorDiv);
		for(var r=15;r>=0;r-=3){
			for(var g=0;g<=15;g+=3){
				for(var b=0;b<=15;b+=3){
					var red = baseConverter(r,10,16) + '';
					var green = baseConverter(g,10,16) + '';
					var blue = baseConverter(b,10,16) + '';
					
					var color = '#' + red + red + green + green + blue + blue;
					var div = document.createElement('DIV');
					div.style.backgroundColor=color;
					div.innerHTML = '<span></span>';
					div.className='colorSquare';
					div.title = color;	
					div.onclick = chooseColor;
					div.setAttribute('rgbColor',color);
					div.onmouseover = colorPickerShowStatusBarText;
					div.onmouseout = colorPickerHideStatusBarText;
					webColorDiv.appendChild(div);
				}
			}
		}
	}
		
	function createNamedColors(inputObj){
		var namedColorDiv = document.createElement('DIV');
		namedColorDiv.style.paddingTop = '1px';
		namedColorDiv.style.display='none';
		inputObj.appendChild(namedColorDiv);
		for(var no=0;no<namedColors.length;no++){
			var color = namedColorRGB[no];
			var div = document.createElement('DIV');
			div.style.backgroundColor=color;
			div.innerHTML = '<span></span>';
			div.className='colorSquare';
			div.title = namedColors[no];	
			div.onclick = chooseColor;
			div.onmouseover = colorPickerShowStatusBarText;
			div.onmouseout = colorPickerHideStatusBarText;
			div.setAttribute('rgbColor',color);
			namedColorDiv.appendChild(div);				
		}	
	
	}
	
	function colorPickerHideStatusBarText()
	{
		document.getElementById('colorPicker_statusBarTxt').innerHTML = '&nbsp;';
	}
	
	function colorPickerShowStatusBarText()
	{
		var txt = this.getAttribute('rgbColor');
		if(this.title.indexOf('#')<0)txt = txt + " (" + this.title + ")";
		document.getElementById('colorPicker_statusBarTxt').innerHTML = txt;	
	}
	
	function createAllColorDiv(inputObj){
		var allColorDiv = document.createElement('DIV');
		allColorDiv.style.display='none';
		allColorDiv.className = 'js_color_picker_allColorDiv';
		allColorDiv.style.paddingLeft = '3px';
		allColorDiv.style.paddingTop = '5px';
		allColorDiv.style.paddingBottom = '5px';
		inputObj.appendChild(allColorDiv);	
		
		var labelDiv = document.createElement('DIV');
		labelDiv.className='colorSliderLabel';
		labelDiv.innerHTML = 'R';
		allColorDiv.appendChild(labelDiv);	
		
		var innerDiv = document.createElement('DIV');
		innerDiv.className = 'colorSlider';
		innerDiv.id = 'sliderRedColor';		
		allColorDiv.appendChild(innerDiv);		
		
		var innerDivInput = document.createElement('DIV');
		innerDivInput.className='colorInput';
		
		var input = document.createElement('INPUT');
		input.id = 'js_color_picker_red_color';
		input.maxlength = 3;
		input.style.width = '48px';
		input.style.fontSize = '11px';
		input.name = 'redColor';
		input.value = 0;
		
		innerDivInput.appendChild(input);
		allColorDiv.appendChild(innerDivInput);

		var labelDiv = document.createElement('DIV');
		labelDiv.className='colorSliderLabel';
		labelDiv.innerHTML = 'G';
		allColorDiv.appendChild(labelDiv);	
				
		var innerDiv = document.createElement('DIV');
		innerDiv.className = 'colorSlider';
		innerDiv.id = 'sliderGreenColor';		
		allColorDiv.appendChild(innerDiv);		
		
		var innerDivInput = document.createElement('DIV');
		innerDivInput.className='colorInput';
		
		var input = document.createElement('INPUT');
		input.id = 'js_color_picker_green_color';
		input.maxlength = 3;
		input.style.width = '48px';
		input.style.fontSize = '11px';
		input.name = 'GreenColor';
		input.value = 0;
		
		innerDivInput.appendChild(input);
		allColorDiv.appendChild(innerDivInput);
		
		var labelDiv = document.createElement('DIV');
		labelDiv.className='colorSliderLabel';
		labelDiv.innerHTML = 'B';
		allColorDiv.appendChild(labelDiv);			
		var innerDiv = document.createElement('DIV');
		innerDiv.className = 'colorSlider';
		innerDiv.id = 'sliderBlueColor';		
		allColorDiv.appendChild(innerDiv);		
		
		var innerDivInput = document.createElement('DIV');
		innerDivInput.className='colorInput';
		
		var input = document.createElement('INPUT');
		input.id = 'js_color_picker_blue_color';
		input.maxlength = 3;
		input.style.width = '48px';
		input.style.fontSize = '11px';
		input.name = 'BlueColor';
		input.value = 0;
		
		innerDivInput.appendChild(input);
		allColorDiv.appendChild(innerDivInput);

	
		var colorPreview = document.createElement('DIV');
		colorPreview.className='colorPreviewDiv';
		colorPreview.id = 'colorPreview';
		colorPreview.style.backgroundColor = '#000000';
		colorPreview.innerHTML = '<span></span>';	
		colorPreview.title = 'Click on me to assign color';	
		allColorDiv.appendChild(colorPreview);
		colorPreview.onclick = chooseColorSlider;
		
		var colorCodeDiv = document.createElement('DIV');
		colorCodeDiv.className='colorCodeDiv';		
		var input = document.createElement('INPUT');
		input.id = 'js_color_picker_color_code';
		
		colorCodeDiv.appendChild(input);
		input.maxLength = 7;
		input.style.fontSize = '11px';
		input.style.width = '48px';		
		input.value = '#000000';
		input.onchange = setPreviewColorFromTxt;
		input.onblur = setPreviewColorFromTxt;
		allColorDiv.appendChild(colorCodeDiv);
		
		var clearingDiv = document.createElement('DIV');
		clearingDiv.style.clear = 'both';
		allColorDiv.appendChild(clearingDiv);
		
		
		form_widget_amount_slider('sliderRedColor',document.getElementById('js_color_picker_red_color'),170,0,255,"setColorByRGB()");
		form_widget_amount_slider('sliderGreenColor',document.getElementById('js_color_picker_green_color'),170,0,255,"setColorByRGB()");
		form_widget_amount_slider('sliderBlueColor',document.getElementById('js_color_picker_blue_color'),170,0,255,"setColorByRGB()");
	}
	
	function setPreviewColorFromTxt()
	{
		if(this.value.match(/\#[0-9A-F]{6}/g)){
			document.getElementById('colorPreview').style.backgroundColor=this.value;
			var r = this.value.substr(1,2);
			var g = this.value.substr(3,2);
			var b = this.value.substr(5,2);
			document.getElementById('js_color_picker_red_color').value = baseConverter(r,16,10);
			document.getElementById('js_color_picker_green_color').value = baseConverter(g,16,10);
			document.getElementById('js_color_picker_blue_color').value = baseConverter(b,16,10);
			
			positionSliderImage(false,1,document.getElementById('js_color_picker_red_color'));
			positionSliderImage(false,2,document.getElementById('js_color_picker_green_color'));
			positionSliderImage(false,3,document.getElementById('js_color_picker_blue_color'));
		}
		
	}
	
	function chooseColor()
	{
		//color_picker_form_field.value = this.getAttribute('rgbColor');
		if(color_picker_callback_function!=null) {
			color_picker_callback_function(this.getAttribute('rgbColor'));
		}
		color_picker_div.style.display='none';
	}
	
	function createStatusBar(inputObj)
	{
		var div = document.createElement('DIV');
		div.className='colorPicker_statusBar';	
		
		var transSpan = document.createElement('SPAN');
		transSpan.style.border="solid 1px black";
		transSpan.style.fontSize = "10px";
		transSpan.style.cursor = "pointer";
		transSpan.style.display="none";
		transSpan.style.marginRight="4px";
		transSpan.style.paddingRight="4px";
		transSpan.style.paddingLeft="4px";
		transSpan.innerHTML = "Make Transparent";
		transSpan.id = 'colorPicker_tranparent';
		
		transSpan.onclick = chooseColor;
		transSpan.setAttribute('rgbColor',"Transparent");
					
		div.appendChild(transSpan);
		
		var innerSpan = document.createElement('SPAN');
		innerSpan.id = 'colorPicker_statusBarTxt';
		div.appendChild(innerSpan);
		inputObj.appendChild(div);
	}
	
	function chooseColorSlider()
	{
		//color_picker_form_field.value = document.getElementById('js_color_picker_color_code').value;
		if(color_picker_callback_function!=null) {
			color_picker_callback_function(document.getElementById('js_color_picker_color_code').value);
		}
		
		color_picker_div.style.display='none';		
	}
	
	
	function showColorPicker(inputObj,callback_function, allowTransparency, transparencyCaption)
	{
		if(allowTransparency==null) {
			allowTransparency = false;
		}
		if(transparencyCaption==null) {
			transparencyCaption = "Make Transparent";
		}
		if(!color_picker_div){
			color_picker_div = document.createElement('DIV');
			color_picker_div.id = 'dhtmlgoodies_colorPicker';
			color_picker_div.style.display='none';
			document.body.appendChild(color_picker_div);
			createColorPickerTopRow(color_picker_div);			
			var contentDiv = document.createElement('DIV');
			contentDiv.id = 'color_picker_content';
			color_picker_div.appendChild(contentDiv);			
			createWebColors(contentDiv);
			createNamedColors(contentDiv);
			createAllColorDiv(contentDiv);
			createStatusBar(color_picker_div);			
		}		
		if(allowTransparency) {
			var cpt = document.getElementById("colorPicker_tranparent");
			cpt.style.display="";
			cpt.innerHTML = transparencyCaption;
		} else {
			document.getElementById("colorPicker_tranparent").style.display="none";
		}
		if(color_picker_div.style.display=='none' || color_picker_active_input!=inputObj)color_picker_div.style.display='block'; else color_picker_div.style.display='none';		
		color_picker_div.style.left = colorPickerGetLeftPos(inputObj) + 'px';
		color_picker_div.style.top = colorPickerGetTopPos(inputObj) + inputObj.offsetHeight + 2 + 'px';
		//color_picker_form_field = formField;
		color_picker_active_input = inputObj;	
		color_picker_callback_function = callback_function;
	}

	function setColorByRGB()
	{
		var formObj = document.forms[0];	
		var r = document.getElementById('js_color_picker_red_color').value.replace(/[^\d]/,'');
		var g = document.getElementById('js_color_picker_green_color').value.replace(/[^\d]/,'');
		var b = document.getElementById('js_color_picker_blue_color').value.replace(/[^\d]/,'');		
		if(r/1>255)r=255;
		if(g/1>255)g=255;
		if(b/1>255)b=255;
		r = baseConverter(r,10,16) + '';
		g = baseConverter(g,10,16) + '';
		b = baseConverter(b,10,16) + '';
		if(r.length==1)r = '0' + r;
		if(g.length==1)g = '0' + g;
		if(b.length==1)b = '0' + b;

		document.getElementById('colorPreview').style.backgroundColor = '#' + r + g + b;
		document.getElementById('js_color_picker_color_code').value = '#' + r + g + b;		
	}	
// vim: tw=80 ts=4 sw=4 noet
// ----------------------------------------------------------------------------
// Project   : Extend - Prototype OOP extension
// URL       : <http://www.ivy.fr/js/extend>
// ----------------------------------------------------------------------------
// Author    : Sebastien Pierre                              <sebastien@ivy.fr>
// License   : Revised BSD License
// ----------------------------------------------------------------------------
// Creation  : 08-Sep-2006
// Last mod  : 17-Nov-2006
// ----------------------------------------------------------------------------

// The Extend object holds all the information required to implement the
// inheritance and other OO-goodness.
Extend = {
	VERSION:           1.1,
	CLASSDEF:          "CLASSDEF",
	DELETE:            "DELETE",
	// These are a list of methods of class instances that are reserved by the
	// OO layer (see the reparent method for more info)
	INSTANCE_RESERVED: {
		CLASSDEF:    true,
		getClass:    true,
		parentClass: true
	},

	// Sets up a class
	setupClass: function( _class, declaration )
	{
		// We create an empty prototype if the user did not provide one
		declaration        = declaration || {}
		_class.prototype   = declaration
		// We clone the given method definition, because they will be augmented
		// with the ones defined in the parent class
		_class.methods     = {} 
		for ( var key in declaration ) { 
      _class.methods[key] = declaration[key] 
    }
		_class.inherited   = {}
		_class.parentClass = undefined
		if ( declaration[Extend.CLASSDEF] )
		{ _class.className = declaration[Extend.CLASSDEF].name; 
      //attempt to make firebug show function names...
      //for(var key in _class.methods) {
     //   _class.methods[key].displayName = _class.className + "_" + key;
      //}
    
    }
		else
		{ _class.className = undefined }
  
  
  
		_class.subclasses  = _class.subclasses || []
		_class.constructor = Extend.Operations.constructor
		_class.reparent    = Extend.Operations.reparent
		_class.method      = Extend.Operations.method
		_class.update      = Extend.Operations.update
		if ( declaration[Extend.CLASSDEF] )
		{ _class.reparent(declaration[Extend.CLASSDEF].parent) }
		// We update the class methods with an `ofClass` method that returns the
		// class, so that instances will have a proper
		declaration.getClass    = function() {return _class}
		declaration.parentClass = function() {return this.getClass().parentClass}
		declaration.parentCall  = function() {
			var new_args = []
			for ( var i=1;i<arguments.length;i++ ) {new_args.push(arguments[i])}
			return this.parentClass().method(arguments[0]).apply(this, new_args)
		}
		declaration.setClass    = function(newClass) {
			return this.getClass().parentClass
		}
		
		// We reparent the subclasses if any
		for ( var i=0 ; i<_class.subclasses ; i++ ) {
			_class.subclasses[i].reparent(_class)
		}
		return _class
	},
	// These are operations that will be "mixed-in" with the new classes
	Operations: {
		constructor: function() {
			return this.prototype.initialize || function() {}
		},
		// Reparents this class
		reparent: function( newParentClass )
		{
			if ( this.parentClass )
			{
				var this_index = this.subclasses.indexOf(this)
				this.parentClass.subclasses.splice(this_index, 1)
				for ( var method_name in this.inherited ) {
					this.method(method_name, null, this.parentClass)
				}
			}
			this.parentClass   = newParentClass
			if ( !newParentClass ) return
			var parent_methods = newParentClass.prototype
			// We iterate on all the parent methods
			for ( parent_method_name in parent_methods ) {
				// If the method is a reserved one, we skip it
				if ( Extend.INSTANCE_RESERVED[parent_method_name] == true ) { continue }
				// If the method is not directly defined in the current class, we add it
				if ( this.methods[parent_method_name] == undefined )
				{
					this.method( parent_method_name,
						parent_methods[parent_method_name],
						newParentClass.inherited[parent_method_name] || newParentClass
					)
				}
			}
			newParentClass.subclasses.push(this)
		},
		update: function(newPrototype) {
			Extend.setupClass(this, newPrototype||this.prototype)
		},
		// Returns, sets or deletes a method in this class
		method: function( name, body, declaredIn ) {
			if ( body == undefined )
			{
				var method = this.prototype[name]
				if ( name == undefined ) throw new Error("Method not found: "+name)
				return method
			}
			else
			{
				declaredIn = declaredIn || this
				// If the method is declared in this class
				if ( declaredIn == this )
				{
					if ( body == Extend.DELETE ) {
						delete this.methods[name]
						delete this.inherited[name]
						delete this.prototype[name]
						// If the method is defined in the parent we set it
						if ( this.parentClass ) {
							var parent_method = this.parentClass.method(name)
							if ( parent_method ) {
								this.method(name, parent_method, this.parentClass.inherited[name] || this.parentClass)
							}
						}
					} else {
						this.methods[name]   = body
						this.prototype[name] = body
						delete this.inherited[name]
					}
				}
				// Or if its declared in another class
				else
				{
					if ( body == Extend.DELETE ) {
						delete this.inherited[name]
						delete this.methods[name]
						delete this.prototype[name]
						// If the method is defined in the parent we set it
						if ( this.parentClass ) {
							var parent_method = this.parentClass.method(name)
							if ( parent_method ) {
								this.method(name, parent_method, this.parentClass.inherited[name] || this.parentClass)
							}
						}
					}
					else {
						if ( this.methods[name] == undefined ) {
							this.inherited[name] = declaredIn
							this.prototype[name] = body
						}
					}
				}
				for ( var i=0 ; i<this.subclasses.length ; i++ )
				{
					this.subclasses[i].method(name,body,declaredIn)
				}
			}
		}
	}
}

// In case prototype is not available, we use this instead
try {
	Class = Class
} catch ( Error ) {
	Class = {create:function() {return function() {this.initialize.apply(this, arguments)}}}
}
Class._create = Class.create
Class.create  = function( declaration ) {
	var new_class = Extend.setupClass(Class._create(declaration), declaration)
	 // The following only works on FireFox
	/*
	new_class.watch("prototype", function(id,oldval,newval) {
		new_class.prototype = newval
		Extend.setupClass(new_class, newval)
		return newval
	})*/
	return new_class
}

// EOF

var useAlphaHack = false;
var useCanvas = false;
if((BrowserDetect.browser == "Explorer")&&(BrowserDetect.version < 7.0)&&(BrowserDetect.version >= 5.5)) {
	useAlphaHack = true;
}

if((BrowserDetect.browser == "Chrome")||(BrowserDetect.browser == "Safari")||(BrowserDetect.browser == "Opera")||(BrowserDetect.browser == "Firefox")) {
  useCanvas=true;
} else if((BrowserDetect.browser == "Explorer")&&(BrowserDetect.version > 8.0)) {
	useCanvas = true;
}



//list of objects with an .id attribute.. keeps track of order (list) can be accessed by id (byId)
var MapList = Class.create({
	CLASSDEF: {
		name: 'MapList'
	},
	
	initialize: function(parentObject) {
		this.byId = {};
		this.list = [];
		this.parentObject = parentObject;
	},
	
	first: function() {
	  if (this.isEmpty()) return null;
	  return (this.list[0]);
	},
	
	last: function() {
	  if (this.isEmpty()) return null;
	  return (this.list[this.list.size()-1]);
	},
	
	isEmpty: function() {
	  return (this.list.size() == 0);
	},
	
	add: function(obj) {
		this.byId[obj.id] = obj
		this.list.push(obj);
		obj.parentObject = this.parentObject;
		return obj;
	},
	
	copy: function(newParentObject) {
		var c = new MapList(newParentObject);
		for(var i=0; i < this.list.size(); i++) {
			c.add(this.list[i].copy());
		}
		return c;
	},
  
  clone: function() {
    var c = new MapList(this.parentObject);
		for(var i=0; i < this.list.size(); i++) {
			c.add(this.list[i]);
		}
		return c;
  },
	
	resort: function() { //this assumes the object has a compare(other) function which returns -1 if it is less than other
		this.list = this.qsort(this.list);
	},
	
	qsort: function(a) {
    if (a.length == 0) return [];

    var left = [];
    var right = [];
    var pivot = a[0];
    for (var i = 1; i < a.length; i++) {
        if (a[i].compare(pivot) < 0)
            left.push(a[i]);
        else
            right.push(a[i]);
    }
    return this.qsort(left).concat(pivot, this.qsort(right));
	},
	
	moveUp: function(id) {
		var idx = this.objectIndex(id);
		if(idx ==0) return false;
		return this.swapPos(idx, idx-1);
	},
	
	moveDown: function(id) {
		var idx = this.objectIndex(id);
		if(idx >= this.list.size() -1) return false;
		return this.swapPos(idx, idx+1);
	},
	
	swapPos: function(idx1, idx2) {
		var o1 = this.list[idx1];
		var o2 = this.list[idx2];
		
		var tmp = o1.pos;
		o1.pos = o2.pos;
		o2.pos = tmp;
		this.list[idx1] = o2;
		this.list[idx2] = o1;
    return true;
	},
	
	objectIndex: function(id) {
		for(var i=0; i < this.list.size(); i++) {
			if(this.list[i].id == id) {
				return i;
			}
		}
		return -1;
	},
	
	remove: function(id) {
		var idx = this.objectIndex(id);
		if(idx != -1) {
      var delObj = this.byId[id];
			this.list.splice(idx,1);
			delete this.byId[id];
      return delObj;
		}
    return null;
	},
	
	replace: function(newObj) {
		var idx = this.objectIndex(newObj.id);
		if(idx != -1) {
			this.list[idx] = newObj;
		} else {
			this.list.push(newObj);
		}
		this.byId[newObj.id] = newObj;
	}

});






//queue to load images
var PriorityQueue = Class.create({
    CLASSDEF: {
        name: 'PriorityQueue'
    },
	
	initialize: function() {
		this.imageQueue = {};
		this.imageQueueKeys = [];
		this.lastAdded = 0;
		this.currentQueue = null;
	},
	
	push: function(key, obj) {
		if(this.imageQueue[key] != null) {
			return;
		}
		this.imageQueue[key] = obj;
		var now = new Date().getTime();
		if((now - this.lastAdded > 100)||(this.currentQueue==null)) {
			//start a new queue..
			log("Starting new queue, time diff=" + (now - this.lastAdded));
			if(this.currentQueue!=null) {
				log("Adding existing queue");
				this.imageQueueKeys.push(this.currentQueue);
			}
			this.currentQueue = [];
		} else {
			log("time diff=" + (now - this.lastAdded));
		}
		this.lastAdded = now;
		this.currentQueue.push(key);
	},
	
	pop: function() {
		var keyFound = false;
		var data = null;
		while(!keyFound) {
			var q = this.getCurrentQueue();
			if(q != null) {
				var key = q.shift();
				var data = this.imageQueue[key];
				if(data != null) {
					this.imageQueue[key] = null;
					return data;
				}
			} else {
				return null;
			}
		}
	},
	
	getCurrentQueue: function() {
		if(this.currentQueue==null) {
			return null;
		}
		while(this.currentQueue.size() == 0) {
			if(this.imageQueueKeys.size() == 0) {
				log("Empty Queue");
				this.currentQueue=null;
				return null;
			}
			log("getting next queue");
			this.currentQueue = this.imageQueueKeys.pop();
		}
		return this.currentQueue;
	},
	
	removeFromImageQueue: function(key) {
		this.imageQueue[key] = null;
	}
});




var imageQueue = new PriorityQueue();
var imageQueueRunningCount = 0;


function queueImageLoading(img, src, w, h, callback) {
	var obj = {"img": img, "src":src, "w":w, "h":h, "callback":callback};
	imageQueue.push(src, obj);
	if(imageQueueRunningCount < 2) {
		imageQueueRunningCount ++;
		processImageQueue();
	}
}

function removeFromImageQueue(src) {
	imageQueue.removeFromImageQueue(src);
}

function processImageQueue() {
	var data = imageQueue.pop();
	if(data == null) {
		log("processImageQueue - Empty");
		imageQueueRunningCount --;
		return;
	}
	log("processImageQueue - loading " + data["src"]);
	backgroundLoadImage(data["src"], function() {
			setTransPng(data["img"], data["src"],data["w"], data["h"]);
			data["callback"]();
			processImageQueue();
	}, null);
}

function resetImageQueue() {
	log("Reset Image Queue");
	imageQueue = new PriorityQueue();
}


function backgroundLoadImage(url, callback, callbackData) {
	var cbimg = null;
	
	var cbimg = document.createElement("IMG");
	cbimg.style.display = "none";
	document.body.appendChild(cbimg);
		
	
	var callbackDone = false;
	
	var handler = function() {
		if(callbackDone) {
			log(cbimg.src + ":callbackDone");
			return;
		}
		log(cbimg.src + ":handle");
		callbackDone = true;
		if(!cbimg.complete) {
			log("Failed to load " + cbimg.src);
		}
		if(useAlphaHack) {
			window.setTimeout(function() {
					callback(callbackData);
					document.body.removeChild(cbimg);
			}, 100);
		} else {
			callback(callbackData);
			document.body.removeChild(cbimg);
		}
		
	};
	
	cbimg.onload = handler;
		
	cbimg.onerror = handler;   
	
	cbimg.onabort = handler;
		
	cbimg.src = url;
	if(BrowserDetect.browser == "Explorer") { //other browsers will call the callback..
		if(cbimg.complete) {
			handler();
		} 
	}
}


function setTransPng(img, src, w, h) {
  if(useAlphaHack) {
    if(w != null) {
      img.style.width = w + "px";
    }
    if(h != null) {
      img.style.height = h + "px";
    }
    if(img.tagName == "IMG") {
      img.src = "/ppr/images/trans.gif";
    } else {
      img.style.fontSize = "1px";
    }
    img.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '", sizingMethod="scale")';
  } else {
    img.src = src;
    if(w != null) {
      img.width = w;
    }
    if(h != null) {
      img.height = h;
    }
  }
}


var asyncImagesLoading = new Hash();


function setImageUrl(container, img, url, callback, attempt) {
	var cbimg = null;
	var elId = null;
	if(attempt == null) attempt = 1;
	if(img!= null) {
		log("loading image from " + url, true);
		elId = getElId(img);
		if(asyncImagesLoading[elId] != null) {
			asyncFinish(asyncImagesLoading[elId]);
			asyncImagesLoading[elId] = null;
		}
		cbimg = img; 
	} else {
		var cbimg = document.createElement("IMG");
		cbimg.style.display = "none";
		document.body.appendChild(cbimg);
		log("appended async img");
	}
	var callbackDone = false;
	var asyncKey = null;
	cbimg.onload = function() {
			log("async img loaded: " + cbimg.src + " callbackDone=" + callbackDone + " cbimg.complete=" + cbimg.complete);
			if(callbackDone) {
				return;
			}
			callbackDone = true;
			/*
			if(img!=null) {
				log("si");
				img.src = cbimg.src;
				log("sd");
			}
			*/
			if(callback!=null) {
				callback(cbimg.src);
			}
			asyncFinish(asyncKey);
			if(img==null) {
				document.body.removeChild(cbimg);
			}
		};
		
	cbimg.onerror = function() {
		log("error occured loading img:" + url + " attempt=" + attempt);
		asyncFinish(asyncKey);
		if(attempt < 3) {
		  setImageUrl(container, img, url, callback, attempt + 1);
		}
	};   
	
	cbimg.onabort = function() {
		log("abort occured loading img:" + url);
		asyncFinish(asyncKey);
	};
		
	cbimg.src = url;
	if((BrowserDetect.browser == "Explorer")||(BrowserDetect.browser == "Chrome")) {
		if(cbimg.complete) {
			log("async img complete: " + callbackDone);
			/*
			if(img!=null) {
				img.src = cbimg.src;
			}
			*/
			if(callbackDone) {
				return;
			}
			callbackDone = true;
			if(callback!=null) {
				callback(cbimg.src);
			}
			if(img==null) {
				document.body.removeChild(cbimg);
			}
		} else {
			asyncKey = asyncStart(container);
			
		}
	} else {
		//other browsers call onload properly...
		if(container != null) {
			asyncKey = asyncStart(container);
		}
	}
	if(img!= null) {
		asyncImagesLoading[elId] = asyncKey;
	}
	return asyncKey;
}

function setTransparentImage(container, img, url, callback) {
	var elId = getElId(img == null ? container : img);
	if(asyncImagesLoading[elId] != null) {
		asyncFinish(asyncImagesLoading[elId]);
		asyncImagesLoading[elId] = null;
	}
	log("loading transparent image from " + url);
	var k = setImageUrl(container, null, url, function(nUrl) {
    if(img != null) {
      if(useAlphaHack) {
        img.style.fontSize = "1px";
        img.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + nUrl + '", sizingMethod="scale")';
      } else {
        img.src = nUrl;
      }
    }
    if(callback!=null) {
      callback();
    }
	});
	asyncImagesLoading[elId] = k;
	return k;
}

function setBackgroundImage(container, div, url, callback) {
	var elId = getElId(div);
	if(asyncImagesLoading[elId] != null) {
		asyncFinish(asyncImagesLoading[elId]);
		asyncImagesLoading[elId] = null;
	}
	log("loading background image from " + url);
	var k = setImageUrl(container, null, url, function(nUrl) {
		log("setting background image to " + nUrl);
		div.style.backgroundImage = "url(" + nUrl + ")";
		if(callback!=null) {
			callback();
		}
	});
	asyncImagesLoading[elId] = k;
	return k;
}


var pwUtilsNextId = -10000000;
function getNextId() {
	return pwUtilsNextId--;
}

function getElId(el) {
	if((el.id != null)&&(el.id != "")) {
		return el.id;
	}
	el.id = "tmp_id_" + getNextId();
	return el.id;
}

//TAB MANAGMENT
var pwTabs = {};

function registerAppTab(tabsName, tabId, panelId, options) {
	if(pwTabs[tabsName] == null) {
		pwTabs[tabsName] = {};
	}
	var tabs = pwTabs[tabsName];
	tabs[tabId] = {tab: tabId, originalClass: $(tabId).className, panel: panelId, selected: false, options: options==null ? {} : options};
	
	Event.observe($(tabId), "click", function(event) { appTabClick(tabsName, tabId); });
}

function appTabClick(tabsName, tabId) {
	var tabs = pwTabs[tabsName];
	if(tabs[tabId].selected) {
		return;
	}
	var tab = tabs[tabId];
	if(tab.panel == null) {
		tab.panel = tab.options.callback(tabId, tab.options.callbackData);
	}
	if(tab.options.onClick != null) {
		tab.options.onClick(tab.options.onClickData);
	}
	for(var t in tabs) {
		var tab = tabs[t];
		if(tab.tab == tabId) {
			if(tab.originalClass != null) {
				$(tabId).className = tab.originalClass + " alt";
			} else {
				$(tabId).className = "alt";
			}
			log("selecting panel " + tab.panel );
			$(tab.panel).style.display = "";
			tab.selected = true;
		} else {
			if(tab.selected) {
				$(tab.tab).className = tab.originalClass;
				$(tab.panel).style.display = "none";
				tab.selected = false;
			}
		}
	}
	
}

function removeTab(tabsName, tabId) {
	var tabs = pwTabs[tabsName];
	var wasSelected = tabs[tabId].selected;
	delete tabs[tabId];
	return wasSelected;
}

function clearAppTabs(tabsName) {
	delete pwTabs[tabsName];
}


function addUrlParam(url, param) {
  if(url.indexOf("?") != -1) {
    return  url + "&" + param;
  } else {
    return  url + "?" + param;
  }
}

function addSelectOption(el, caption, val, selectedVal) {
  var opt = document.createElement("OPTION");
  opt.text = caption;
  opt.value = val;
  if(selectedVal == val) {
    opt.selected = true;
  }
  if(BrowserDetect.browser == "Explorer") {
    el.options.add(opt);
  } else {
    el.appendChild(opt);
  }
}

function selectOptionHtml(options, selectedOption, allowBlank) {
  var opts = [];
  if(allowBlank) {
    var sel = selectedOption == null ? ' selected="true"' : '';
    opts.push('<option value=""' + sel + '></option>');
  }
  for(var i=0; i < options.length; i++) {
    var opt = options[i];
    var v = null;
    var c = null;
    if(opt instanceof Array) {
      c = opt[0];
      v = opt.length > 1 ? opt[1] : opt[0];
    } else if(typeof opt == "string") {
      c = v = opt; 
    } else if(opt instanceof Object && opt.v && opt.c) {
      c = opt.c;
      v = opt.v;
      var subOpts = selectOptionHtml(opt.v, selectedOption);
      opts.push('<optgroup label="' + c + '">' + subOpts + '</optgroup>');
      continue;
    } else if(opt instanceof Object && opt.name && opt.id) {
      c = opt.name;
      v = opt.id;
    } else {
      c = v = opt;
    }
    var sel = v == selectedOption ? ' selected="true"' : '';
    opts.push('<option value="' + v + '"' + sel + '>' + c + '</option>');
  }
  return opts.join("\n");
}

//correctly identify arrays
function typeOf(value) {
    var s = typeof value;
    if (s === 'object') {
        if (value) {
            if (value instanceof Array) {
                s = 'array';
            }
        } else {
            s = 'null';
        }
    }
    return s;
}


function serializeObject(queryComponents, prefix, o, forFormFields) {
  for(var k in o) {
    if(k[0] != "_") { //we dont serialise "internal" entries
      var i = o[k];
      var type = typeOf(i);
      if(type == 'array') {
        serializeArray(queryComponents, prefix + "[" + k + "]", i, forFormFields);
      } else if(type == 'string' || type == 'number' || type == "boolean") {
        if(forFormFields) {
          queryComponents.push([prefix + "[" + k + "]", i]);
        } else {
          queryComponents.push(encodeURIComponent(prefix + "[" + k + "]") + "=" + encodeURIComponent(i));
        }
      } else if(type == 'undefined') {
        //drop it....
        log("serializeObject:" + k + " is undefined"); 
      } else if(type == 'function') {
        //drop it....
        log("serializeObject:" + k + " is a function"); 
      } else {
        serializeObject(queryComponents, prefix + "[" + k + "]", i, forFormFields);
      }
    }
  }
  return queryComponents;
}

function serializeArray(queryComponents, prefix, o, forFormFields) {
  for(var idx=0; idx < o.length; idx++) {
    var i = o[idx];
    if(typeof i == 'array') {
      serializeArray(queryComponents, prefix + "[]", i, forFormFields);
    } else if(typeof i == 'string' || typeof i == 'number' || typeof i == "boolean") {
      if(forFormFields) {
        queryComponents.push([prefix + "[]", i]);
      } else {
        queryComponents.push(encodeURIComponent(prefix + "[]") + "=" + encodeURIComponent(i));
      }
    } else if(typeof i == 'undefined') {
      //drop it....
      log("serializeObject: item" + idx + " is undefined"); 
    } else {
      serializeObject(queryComponents, prefix + "[]", i, forFormFields);
    }
  }
  return queryComponents;
}

function copyObject(o) {
  if(o==null) return null;
  var result = {};
  for(var k in o) {
    var i = o[k];
    if(typeof i == 'array') {
      result[k] = copyArray(i);
    } else if(typeof i == 'string' || typeof i == 'number' || typeof i == "boolean") {
      result[k] = i;
    } else if(typeof i == 'undefined') {
      //drop it....
      log("copyObject:" + k + " is undefined"); 
    } else if(typeof i == 'function') {
      //drop it....
    //  log("copyObject:" + k + " is function"); 
    } else {
      result[k] = copyObject(i);
    }
  }
  return result;
}

function copyArray(o) {
  var result = [];
  for(var idx=0; i < o.length; i++) {
    var i = o[idx];
    if(typeof i == 'array') {
      result.push(copyArray(i));
    } else if(typeof i == 'string' || typeof i == 'number' || typeof i == "boolean") {
      result.push(i);
    } else if(typeof i == 'undefined') {
      //drop it....
      log("copyArray: item" + idx + " is undefined"); 
    } else {
      result.push(copyObject(i));
    }
  }
  return result;
}

//MULTILANG STRING TABLE
var mlStringTable = {};
var debugMissingML = false;

function registerMLString(eng,ml) {
  mlStringTable[eng] = {ml:ml, sub: (ml.indexOf("%s") != -1), msub: ml.indexOf("%1s") != -1};
}

function ml(eng, params) {
  var ml = mlStringTable[eng];
  if(ml == null) {
    var subPos = eng.indexOf("%s");
    if(subPos != -1) {
      eng = subMl(eng, params);
    } else {
      subPos = eng.indexOf("%1s");
      if(subPos != -1) {
        eng = subMlm(eng, params);
      } 
    }
    
    if(debugMissingML) {
      return "[" + eng + "]";
    } else {
      return eng;
    }
  } else {
    if(ml.sub) {
      return subMl(ml.ml, params);
    } else if(ml.msub) {
      return subMlm(ml.ml, params);
    } else {
      return ml.ml;
    }
  }
}

function subMl(text, params) {
  return text.replace("%s", params);
}

function subMlm(text, params) {
  for(var i=0; i < params.length; i++) {
    text = text.replace("%" + (i + 1) + "s", params[i]);
  }
  return text;
}

function updateToolTip(el, newText) {
  var elem = $(el);
  var ttId = elem.getAttribute("tool_tip_element_id");
  if(ttId == null || $(ttId) == null) {
    new Tooltip(el, newText);
    return;
  }
  $(ttId).update('<b>' + newText + '</b>');
}

function processToolTips(container) {
  var ttips= Selector.findChildElements(container, $A([".hasToolTip"]));

  if (ttips  == null || ttips.length == null) return;
	for(var i=0; i < ttips.length; i++) {
	  new Tooltip(ttips[i]);  
	}
}
	
var Tooltip = Class.create();

Tooltip.prototype = {
	initialize: function(el, tip) {
		this.el = $(el);
		this.initialized = false;
		this.elMoved = true;
		var ttId = this.el.getAttribute("tool_tip_element_id");
		var int_tip = null;
		var updatingToolTip = false;
		if(ttId != null && $(ttId)) {
		  int_tip = $(ttId);
		  updatingToolTip = true;
		} else {
		  int_tip=document.createElement('b');
		  document.body.appendChild(int_tip);
		  ttId = getElId(int_tip);
		  this.el.setAttribute("tool_tip_element_id",ttId);
		}
		 
		if(tip != null) {
		  if(typeof tip == "string") {
		    int_tip.innerHTML='<b>' + tip + '</b>';
		  } else {
		    int_tip.innerHTML=tip.innerHTML;
		  }
		} else {
		  tipText = el.getAttribute("tooltip");
		  if(tipText != null) {
		    int_tip.innerHTML='<b>' + tipText + '</b>';
		  }
		}
		int_tip.className="tip";

		if(updatingToolTip) {
		   //do nothing so this object can get collected....
		   this.el = null;
		} else {
      this.tooltip=int_tip;
   
      // Event handlers
      this.showEvent = this.show.bindAsEventListener(this);
      this.hideEvent = this.hide.bindAsEventListener(this);
      this.updateEvent = this.update.bindAsEventListener(this);
      this.bodyMoveEvent = this.bodyMove.bindAsEventListener(this);
      Event.observe(this.el, "mouseover", this.showEvent );
      Event.observe(this.el, "mouseout", this.hideEvent );
    }
	},
	
	show: function(e) {
		this.xCord = Event.pointerX(e);
		this.yCord = Event.pointerY(e);
		if(!this.initialized) this.timeout = window.setTimeout(this.appear.bind(this), 250);
	},
	
	hide: function(e) {
		if(this.initialized) {
			Event.stopObserving(this.el, "mousemove", this.updateEvent);
			Event.stopObserving(document.body, "mousemove", this.bodyMoveEvent);
			this.tooltip.style.display="none";
		}
		this._clearTimeout(this.timeout);
		this.initialized = false;
	},
	
	bodyMove: function(e) {
	  if(this.elMoved) {
	    this.elMoved = false;
	  } else {
	    this.hide(e);
	  }
	},
	
	update: function(e){
		this.xCord = Event.pointerX(e);
		this.yCord = Event.pointerY(e);
		this.setup();
		this.elMoved = true;
	},
	
	appear: function() {
		Event.observe(this.el, "mousemove", this.updateEvent);
		Event.observe(document.body, "mousemove", this.bodyMoveEvent );
		this.setup();
		
		this.initialized = true;
		this.tooltip.style.display="block";
	},
	
	setup: function(){
		this.tooltip.style.left = this.xCord +20 + "px";
		this.tooltip.style.top = this.yCord + 20 + "px";
	},
		
	_clearTimeout: function(timer) {
		clearTimeout(timer);
		clearInterval(timer);
		return null;
	}
};


var Product_image = Class.create();

Product_image.prototype = {
  initialize: function(row_id, width){
  	  
    this.empty_width=width;
    this.row=$(row_id);
    this.row_li=$$("#"+row_id+">li");
	this.base_image=$$("#"+row_id+">li img");
    
	this.row_over=$$("#"+row_id+" .over");
	this.row_corners=$$("#"+row_id+" .corners");
    this.row_details=$$("#"+row_id+">li .details");
    this.details_height=0;
    this.change_height=false;
    
    this.width=this.row_li[0].offsetWidth;
    
    this.base_width=this.row.offsetWidth-1;
    this.per_row=Math.floor(this.base_width/this.empty_width);
    
    this.per_row=(this.base_width%this.empty_width)==0 ? this.per_row-1 : this.per_row;
    
    this.gaps=this.per_row;
     this.set_gaps();
    
	 if(this.gap_width<5){
      this.per_row=this.per_row-1;
      this.gaps=this.per_row;
      this.set_gaps();
    }
    
    this.set_width();
    this.set_overlay();
	
	for(q=0; q<this.base_image.length; q++){
		if(this.base_image[q].complete){
				this.test_image(q);
		}else{
			Event.observe(this.base_image[q], 'load', this.test_image.bind(this, q));
		}
	}
  },
  
  test_image: function(q){
	  if(this.base_image[q].width!=this.empty_width){
		  var margin=(this.empty_width-this.base_image[q].width)/2;
		  if(this.row_corners.length>0){
			  this.row_corners[q].style.width=this.base_image[q].width+"px";
			  this.row_corners[q].style.marginLeft=margin+"px";
		  }
		  this.base_image[q].style.marginLeft=margin+"px";
	  }
	  if(this.base_image[q].height!=this.empty_width){
		  var margin=(this.empty_width-this.base_image[q].height)/2;
		  if(this.row_corners.length>0){
			  this.row_corners[q].style.height=this.base_image[q].height+"px";
			  this.row_corners[q].style.marginTop=margin+"px";
		  }
		  this.base_image[q].style.marginTop=margin+"px";
	  }
  },
  set_width: function(){
    row_left=0;
    if(this.row_li.length%this.per_row!=0){
      row_left=this.per_row-(this.row_li.length%this.per_row);
    }
    
    for(x=0; x<(this.row_li.length+row_left); x++){
      offset=0;
      if(x>=this.row_li.length){
      }else{
			if(this.row_details.length>0){
				if(this.row_details[x].offsetHeight>this.details_height){
					this.details_height=this.row_details[x].offsetHeight;
				}
				if(this.row_details[x].offsetHeight!=this.details_height&&this.details_height!=0){
					this.change_height=true;
				}
			}
        
        int_row=this.row_li[x];
        if(this.row_li[x].offsetWidth>this.width){
          offset=this.row_li[x].offsetWidth-this.width;
        }
      }
	  	int_row.setStyle({
						   "margin-left" : this.gap_left-offset+"px",
						   "margin-right" : this.gap_right-offset+"px"
					   });
    }
    if(this.change_height==true) this.set_height();
  }, 
  set_height: function(){
	
    for(y=0; y<this.row_details.length; y++){
		this.row_details[y].style.width=this.row_li[0].offsetWidth+"px";
		this.row_details[y].style.height=this.details_height+"px";
    }
  },
  set_gaps: function(){
    this.difference=(this.base_width-(this.per_row*this.empty_width));
    this.gap_width=Math.floor(this.difference/this.gaps);
	this.gap_left=Math.ceil(this.gap_width/2);
	this.gap_right=Math.floor(this.gap_width/2);
  }, 
  set_overlay: function(){
	  var over_final=this.empty_width;
	  for(x=0; x<this.row_over.length; x++){
		 this.row_over[x].style.width=over_final+"px";
		 this.row_over[x].style.height=over_final+"px";
		 this.row_over[x].style.visibility="visible";
		 if(this.row_corners.length > x) {
       this.row_corners[x].style.width=over_final+"px";
       this.row_corners[x].style.height=over_final+"px";
       this.row_corners[x].style.visibility="visible";
     }
	  }
  }, 
  
  clean_up: function(){
	  alert('clean clean clean');
  }
}

var Rollover_engine=Class.create();

Rollover_engine.prototype={
  initialize: function(li_a, li_img, height){
    this.li_a=li_a;
    this.li_img=li_img;
    
    this.height=height;
    
    this.showEvent = this.show.bindAsEventListener(this);
    this.hideEvent = this.hide.bindAsEventListener(this);
    
    Event.observe(this.li_a, "mouseover", this.showEvent );
    Event.observe(this.li_a, "mouseout", this.hideEvent );
  }, 
  
  show: function(){
    new Effect.Morph(this.li_img, { style:"margin-top: -"+this.height+"px;", duration: 0.3 });
  },
  
  hide: function(){
    new Effect.Morph(this.li_img, { style:"margin-top: 0;", duration: 0.3 });
  }
}


var Popup = Class.create();

Popup.prototype = {
	initialize: function(el, img) {
		
		this.el=el;
		this.imgurl=img.value;
		this.init=false;
		
		this.showEvent = this.show.bindAsEventListener(this);
		this.hideEvent = this.hide.bindAsEventListener(this);
		this.updateEvent = this.update.bindAsEventListener(this);
		Event.observe(this.el, "mouseover", this.showEvent );
		Event.observe(this.el, "mouseout", this.hideEvent );
		 
	},
	
	show: function(e) {
		this.xCord = Event.pointerX(e);
		this.yCord = Event.pointerY(e);
		
		if(!this.init){
			this.img=document.createElement('img');
			this.img.src=this.imgurl;
			this.img.className="preview_image";
			document.body.appendChild(this.img);
			this.init=true;
		}
		
		this.timeout = window.setTimeout(this.appear.bind(this), 250);
	},
	
	hide: function(e) {
		Event.stopObserving(this.el, "mousemove", this.updateEvent);
		this.img.style.display="none";
		
		clearTimeout(this.timeout);
		clearInterval(this.timeout);
	},
	
	update: function(e){
		this.xCord = Event.pointerX(e);
		this.yCord = Event.pointerY(e);
		this.setup();
	},
	
	appear: function() {
		Event.observe(this.el, "mousemove", this.updateEvent);
		this.setup();
		this.img.style.display="block";
	},
	
	setup: function(){
		
		var scroll_x=window.pageXOffset ? window.pageXOffset : document.documentElement.scrollLeft;
		var scroll_y=window.pageYOffset ? window.pageYOffset : document.documentElement.scrollTop;
		
		var int_x=(this.xCord-scroll_x)+this.img.offsetWidth;
		var int_y=(this.yCord-scroll_y)+this.img.offsetHeight;
		
		var margin_x=(int_x>document.body.clientWidth) ? -(20+this.img.offsetWidth) : 20;
		var margin_y=(int_y>document.body.clientHeight) ? -(20+this.img.offsetHeight) : 20;
		
		this.img.style.left = this.xCord + margin_x + "px";
		this.img.style.top = this.yCord + margin_y + "px";
	},
		
	_clearTimeout: function(timer) {
		clearTimeout(timer);
		clearInterval(timer);
		return null;
	}
};

Event.observe(window, "load", function(){
	var my_prev=$$('input.preview_image');
	if (my_prev.length>0){
		var my_a=$$('a.preview_link');
		
		for(x=0; x<my_a.length; x++){
			new Popup(my_a[x], my_prev[x]);
		}
	}
});

function get_page_pos(elem){
	var x_pos = elem.offsetLeft;
	temp_el = elem.offsetParent;
	try {
		while (temp_el != null) {
			int_x=temp_el.offsetLeft;
			if(int_x) x_pos += temp_el.offsetLeft;
			temp_el = temp_el.offsetParent;
		}
	}
	catch(ex){}
	return x_pos;
	//return 40;
}

function get_page_ypos(elem){
	var y_pos = elem.offsetTop;
	temp_el = elem.offsetParent;
	try {
		while (temp_el != null) {
			int_y=temp_el.offsetTop;
			if(int_y) y_pos += temp_el.offsetTop;
			temp_el = temp_el.offsetParent;
		}
	}
	catch(ex){}
	return y_pos;
	//return 40;
}

var cartDetails = {
  refresh: true,
  cost: 0
};
var slidebox=new Array();
var open_extra=null;
Event.observe(window, "load", function() {
  if(pwUsingExtras == true) {
    var a=document.getElementById('extras');
    var b=a.getElementsByTagName('a');
    var cont=$$('.extra_container');

    for(x=0; x<b.length; x++){
      b[x].onclick=function(){
        var c=$$('#extras li');
        var my_a=$$('#extras li a');
        for(y=0; y<c.length; y++){ 
          c[y].className=c[y].className.replace(" alt", "");
          if(my_a[y]==this){
            this.parentNode.parentNode.className+=" alt";
            if(open_extra!=cont[y].id){
              manage_extras(cont[y].id);
            }else{ close_extras(c[y]); }
          }
        } ;
        if(this.id=="pw_c_popup_link"){
          pwCurSelectCurrency();
        }
        return false;
      };
    }
  }
  test_ul(); //moved to the end of index_portal for testing. (ie. so it works)
});

function manage_extras(element){
  var element=$(element);
  if ( window["load"+element.id] ){ window["load"+element.id](); }
  if(open_extra!=null){
    open_extra=$(open_extra);
    new Effect.SlideUp(open_extra, { transition: Effect.Transitions.easeInOutCubic, duration: 0.5, afterFinish: function() { if(open_extra!=null) { new Effect.SlideDown(element, { transition: Effect.Transitions.easeInOutCubic, duration: 0.5 } ); open_extra=element.id; } } } );
  }else{
    new Effect.SlideDown(element, { transition: Effect.Transitions.easeInOutCubic, duration: 0.5, afterFinish: function(){ open_extra=element.id; } } );
  }
}
function close_extras(my_li){
  new Effect.SlideUp(open_extra, { duration: 0.5,  afterFinish: function() { my_li.className=my_li.className.replace(" alt", ""); open_extra=null; }  } );
}

function loadcart(){
  if (cartDetails.refresh) {
    if($("top_cart_notice") != null) {
      $("top_cart_notice").innerHTML = "<h3 class='loading'>" + ml("Loading") + "</h3>";
      $("top_cart_notice").show();
    }
    new Ajax.Request("/ppr/shop/cart_info", {
      method: 'get', 
      onSuccess: function(transport, json) { 
        //$("top_cart_notice").hide();
        
        cartDetails.refresh = false;
        try {
          if(!json) {json = transport.responseText.evalJSON(); }
          var cart = json.cart;
        
          if($("cart_count") != null) {
            $("cart_count").innerHTML = json.cart.link_text ;
            //if (parseInt(json.cart.qty,10) != 1){
              //$("cart_count").innerHTML = json.cart.qty+ml(" items");
            //}else{
              //$("cart_count").innerHTML = ml("1 item");
            //}
          }
          if($('cart_cost') != null) {
            if (parseFloat(json.cart.price) > 0) {
            //$('cart_cost').innerHTML = pwCurFormatAmount(parseFloat(json.cart.price))+ " " + pwCurCur[1];
            pwCurArea(parseFloat(json.cart.price), null, null, true, null, null, null, null, 'cart_cost');
            } else {
            //$('cart_cost').innerHTML = pwCurFormatAmount(0) + " " + pwCurCur[1];
            pwCurArea(0, null, null, true, null, null, null, null, 'cart_cost');
            }
          }
          cartDetails.cost = parseFloat(json.cart.price);
          
          if($('hub_cart') != null) {
            if(cart.qty > 0) {
              $('hub_cart').className = "full_cart";  
            } else {
              $('hub_cart').className = "empty_cart";  
            }
          }
          if($('hub_cart_link') != null) {
            /*if(cart.qty > 0) {
              $('hub_cart_link').onclick=function() { return true; };
            } else {
              $('hub_cart_link').onclick=function() { return false; };
            }*/
          }
        
          if(($("top_cart_notice") != null)&&($("item_list") != null)&&($("cart_checkout_link")!= null)) {
            if(parseInt(json.cart.qty,10) > 0){
              Effect.Fade($("top_cart_notice"), { duration: 0.2 });
              $("item_list").innerHTML = "";
              $("cart_checkout_link").onclick = function(){  };
              Element.removeClassName("cart_checkout_link", "disabled");
            }else{
              //$("cart_loading_text").innerHTML = "You have no items in your cart.";
                Effect.Fade($("top_cart_notice"), { duration: 0.2, afterFinish: function(){ $("top_cart_notice").innerHTML = "<h3 class='empty'>" + ml("You have no items in your cart.") + "</h3>"; Effect.Appear($("top_cart_notice"), { duration: 0.2 }); } });
                //$("top_cart_notice").show();
            }
          }
          if($('cart_link') != null) {
            $('cart_link').childElements()[0].innerHTML = ml("cart: ")+json.cart.link_text;
          }
        
          if($("item_list") != null) {
            json.products.each(function(item){
                var li = null;
                if(item.link != null) {
                  li = '<li id="item'+item.id+'" style="display:none"><div><a href="' + item.link + '"><img src="' +item.url+'" width="77" height="77"/></a></div></li>';
                } else {
                  li = "<li id='item"+item.id+"' style='display:none'><div><img src='"+item.url+"' width='77px', height='77px'></div></li>";
                }
                new Insertion.Bottom("item_list", li);
                test_ul();
                Effect.Appear("item"+item.id, { duration: 0.2 });
            });
          }
        } catch(ex) {
          log("Exception occured updating cart info:" + ex.message);
          log(ex);
        }
      }
    }) ;
  }else{ // we are just needing to update the cost incase the currency has changed
    if($('cart_cost') != null) {
      if (cartDetails.cost > 0) {
        //$('cart_cost').innerHTML = pwCurFormatAmount(cartDetails.cost)+ " " + pwCurCur[1];
        pwCurArea(cartDetails.cost, null, null, true, null, null, null, null, 'cart_cost');
      } else {
        //$('cart_cost').innerHTML = pwCurFormatAmount(0) + " " + pwCurCur[1];
        pwCurArea(0, null, null, true, null, null, null, null, 'cart_cost');
      }
    }
  }
}

function updateCart() {
  cartDetails.refresh = true;
  loadcart();
}

function createEle(name) { return $(document.createElement(name)); }



function test_ul(element){
  var my_div=document.getElementsByTagName('div');
  for(x=0; x<my_div.length; x++){
    if(my_div[x].className=="slidebox"){
      slidebox[slidebox.length]=new Sliderclass(my_div[x], x);
    }
  }
}

function Sliderclass(element, num){
  var amt=86;
  
  var num=num;
  var base=element;
  
  base.id="base_"+num;
  var bid=base.id;
  
  var my_window=$$('#'+bid+' div.viewer');
  var win_width=my_window[0].style.width;
  win_width=win_width.replace("px", "");
  
  var base_ul=$$('#'+bid+' ul.test_ul');
  base_ul=base_ul[0];
  var base_li=$$('#'+bid+' ul.test_ul li');
  
  var curr_move=false;
  
  var left=$$('#'+bid+' .right');
  left=left[0];
  var right=$$('#'+bid+' .left');
  right=right[0];
  
  var l_on=true;
  var r_on=true;
  
  slider_setup();
  
  function slider_setup(){
    if(base_ul != null) {
      base_ul.style.width=(base_li.length*amt)+"px";
      test_limits(0);
    }
  }
  
  if(!curr_move){
    left.onclick=function(){
      if(l_on){	
        base_margin=(Math.floor((base_ul.style.marginLeft).replace("px", "")/amt))*amt;
        var move_left=(base_ul.style.marginLeft==null)? - amt : base_margin-amt;
        test_limits(move_left);
        move_left=move_left+"px";
        new Effect.Morph(base_ul, { style: 'margin-left:'+move_left, duration: 0.2, afterFinish:function(){ } });
      }
      return false;
    };
    right.onclick=function(){
      if(r_on){
        base_margin=(Math.ceil((base_ul.style.marginLeft).replace("px", "")/amt))*amt;
        var move_right=(base_ul.style.marginLeft==null)? +amt : base_margin+amt;
        test_limits(move_right);
        move_right=move_right+"px";
        new Effect.Morph(base_ul, { style: 'margin-left:'+move_right, duration: 0.2, afterFinish:function(){ } });
      }
      return false;
    };
  }
  function test_limits(end){
    
    var l_curr=l_on;
    var r_curr=r_on;
    
    if(end>=0){ r_on=false; }else{ r_on=true; }
    
    ul_width=parseFloat(base_ul.style.width.replace("px", ""));
    final_bit=eval(ul_width+end-win_width);
    if(final_bit<=0){ l_on=false; }else{ l_on=true; }
    
    if(l_curr!=l_on){
      if(l_on){ 
        new Effect.Morph(left, { style: 'color: #ffffff;', duration: 0.3 });
        //left.style.color="#ffffff";
      }else{
        new Effect.Morph(left, { style: 'color: #404040;', duration: 0.3 });
        //left.style.color="#404040";
      }
    }
    if(r_curr!=r_on){
      if(r_on){
        new Effect.Morph(right, { style: 'color: #ffffff;', duration: 0.3 });
        //right.style.color="#ffffff";
      }else{
        new Effect.Morph(right, { style: 'color: #404040;', duration: 0.3 });
        //right.style.color="#404040";
      }
    }
  }
}

var ImageQueue=Class.create({
        num_items:0,
        cleared_items:0,
        items:Array(),
        interval:null,

        initialize:function(){},

        add:function(image){
			if(typeof(image)!="undefined"){
				image.style.visibility="hidden";
				this.items.push(image);
			}
			this.start();
        },

        start:function(){
			if(this.items.length>0) {
				this.interval=new PeriodicalExecuter(this.check_images.bind(this),	0.5);
			} else {
				
			}
        },

        check_images:function(){
        	var not_cleared=new Array();
			this.num_items=this.items.length;

			for(var i=0; i<this.num_items; i++){
				var el=this.items[i];
				if(el.complete){ 
					this.on_complete(el);
				}else{
					not_cleared.push(el);
				}
			}
			
			this.items=not_cleared;
			if(this.items.length<=0){
				this.interval.stop();
			}
        },

        on_complete:function(elm){
          //try {
            if(elm.nextSibling!=null && elm.nextSibling.className=="window"){
              var img=elm.nextSibling;
              img.parentNode.removeChild(img);
            }
            if(elm.parentNode != null) {
              //}else{
              var img=document.createElement('img');
              img.src="/images/trans.gif";
              img.className="window";
              elm.parentNode.insertBefore(img, elm.nextSibling);
              img.style.position="absolute";
          //}
              
              var my_x=get_page_pos(elm);
              var my_y=get_page_ypos(elm);
              var my_width=elm.offsetWidth;
              var my_height=elm.offsetHeight;
              
              var img_x=get_page_pos(img);
              var img_y=get_page_ypos(img);
              img.style.marginTop=eval(my_y-img_y)+"px";
              img.style.marginLeft=eval(my_x-img_x)+"px";
              
              img.style.width=my_width+"px";
              img.style.height=my_height+"px";
              
              elm.style.visibility="visible";
            }
          //} catch(e);
        }
}); 

var myqueue=new ImageQueue(); 

function protect(){
	var i_protect=$$('.protect');
	for(x=0; x<i_protect.length; x++){
		myqueue.add(i_protect[x]);
	}
}


var curTopMenuItem = null;

var mnu_z_ind=100;
var MnuDrop=Class.create({
    initialize: function(mnu){
      this.mnu=mnu;
      this.btn=mnu.parentNode;
      this.init=false;
      
      this.margin_top=parseFloat(this.mnu.getStyle("margin-top"));
      this.mnu.style.marginTop=eval(this.margin_top-10)+"px";
      
      this.show_event=this.show.bindAsEventListener(this);
      this.hide_event=this.hide.bindAsEventListener(this);
      Event.observe(this.btn, "mouseover", this.show_event);
      Event.observe(this.btn, "mouseout", this.hide_event);
    },
    show: function(){
      if((curTopMenuItem != null)&&(curTopMenuItem != this)) {
        curTopMenuItem.remove(); 
      }
      curTopMenuItem = this;
      this._clearTimeout(this.timeout);
      this.init=true;
      
      mnu_z_ind++;
      this.mnu.setStyle({
          'display': 'block',
          'z-index': mnu_z_ind
      });
      
      new Effect.Morph(this.mnu, {
          style: "margin-top: "+this.margin_top+"px; opacity: 1;",
          duration: .2
      });
    },
    hide:function(){
      if(this.init) this.timeout = window.setTimeout(this.disappear.bind(this), 500);
    }, 
    disappear: function(){
      this.init=false;
      if(curTopMenuItem == this) curTopMenuItem = null;
      new Effect.Morph(this.mnu, {
          style: "margin-top: "+eval(this.margin_top-10)+"px; opacity: 0;",
          duration: 0.2, 
          afterFinish: this.remove.bind(this)
      });
    },
    remove: function(){
      this._clearTimeout(this.timeout);
      this.mnu.style.display="none";
    },
    _clearTimeout: function(timer) {
    clearTimeout(timer);
    clearInterval(timer);
    return null;
  }
});

var A_B_Tip=Class.create({
		initialize: function(btn, tar){
		  if (btn == null || tar == null) return;
		  
			this.btn=btn;
			this.tar=tar;
			
			this.m_stop=parseFloat(this.tar.getStyle("margin-top"));
			this.m_start=this.m_stop-5;
			
			this.tar.setStyle({
					'margin-top': this.m_start+'px', 
					'opacity': 1
			});
			
			this.show_event=this.show.bindAsEventListener(this);
			this.hide_event=this.hide.bindAsEventListener(this);
			Event.observe(this.btn, "mouseover", this.show_event);
			Event.observe(this.btn, "mouseout", this.hide_event);
		},
		
		show:function(){
			this.timeout = window.setTimeout(this.appear.bind(this), 500);
		}, 
		appear: function(){
			this.tar.show();
			new Effect.Morph(this.tar, {
				style: "margin-top: "+this.m_stop+"px; opacity: 1;",
				duration: 0.2
			});
		}, 
		hide: function(){
			this._clearTimeout(this.timeout);
			new Effect.Morph(this.tar, {
				style: "margin-top: "+this.m_start+"px; opacity: 0;",
				duration: 0.2, 
				afterFinish: this.disappear.bind(this)
			});
		}, 
		disappear: function(){
			this.tar.hide();
		},
		 _clearTimeout: function(timer) {
			clearTimeout(timer);
			clearInterval(timer);
			return null;
		  }
});

var AdminBar=Class.create({
		initialize: function(status){
			this.admin_bar=$("admin_menu");
			if (!this.admin_bar) return;
			this.site_controls=$("site_controls");
			this.s_c_buttons=$$("#site_controls .s_c_tar");
			
			/* sets up button rollovers */
			for(x=0; x<this.s_c_buttons.length; x++){
				var int_c=this.s_c_buttons[x].id;
				if($(int_c).parentNode.className=="alt") this.curr=int_c;
				new A_B_Tip($(int_c), $$("#"+int_c+" .c_note")[0]);
			}
			
			if ($("base_simple")) {
			  new A_B_Tip($$("#base_simple>a")[0], $$("#base_simple .c_note")[0]);
			}
			if ($("base_store")) {
			  new A_B_Tip($$("#base_store>a")[0], $$("#base_store .c_note")[0]);
			}
			if ($("base_fulfillment")) {
			  new A_B_Tip($$("#base_fulfillment>a")[0], $$("#base_fulfillment .c_note")[0]);
			}
			if ($("base_view")) {
			  new A_B_Tip($$("#base_view>a")[0], $$("#base_view .c_note")[0]);
			}
			if ($("base_map")) {
			  new A_B_Tip($$("#base_map>a")[0], $$("#base_map .c_note")[0]);
			}
			
			/* sets up status rollover */
			var int_status=$$('.s_icon');
			if(int_status.length>0){
				this.status=int_status[0];
				new A_B_Tip(this.status, $$('.s_icon .s_note')[0]);
			}
			
			/*sets up clicks */
			if($('s_c_page')){
				if($('s_c_page').nodeName=="A"){
					this.page_menu=this.page.bindAsEventListener(this);
					Event.observe($('s_c_page'), "click", this.page_menu);
				}
			}
			if($('s_c_user') && $('s_c_user').nodeName=="xxx"){
				this.user_menu=this.user.bindAsEventListener(this);
				Event.observe($('s_c_user'), "click", this.user_menu);
			}
			if($('s_c_mlm')!=null && $('s_c_mlm').nodeName=="A"){
				this.mlm_menu=this.mlm.bindAsEventListener(this);
				Event.observe($('s_c_mlm'), "click", this.mlm_menu);
			}
			if($('s_c_map')!=null && $('s_c_map').nodeName=="A"){
				this.map_menu=this.map.bindAsEventListener(this);
				Event.observe($('s_c_map'), "click", this.map_menu);
			}
			this.curr=null;
			switch(status) {
			case 1:
			  this.curr=null;
			  break;
			case 2:
			  this.curr = 's_c_user';
			  break;
			case 3:
			  this.curr = 's_c_mlm';
			  break;
			case 4:
			  this.page();
			  break;
			};
		}, 
		
		user: function(){
			
			if(this.curr!="s_c_user"){
				this.clear_current("s_c_user");
				this.user_drop(1);
			}else{
				this.home_current();
			}
			return false;
		},
		user_drop: function(dir){
			c_menu=$("customer_menu");
			if(dir==1){
				new Effect.SlideDown(c_menu, { duration: .2});
			}else{
				new Effect.SlideUp(c_menu, { duration: .2});	
			}
		},
		
		map: function(){
			if(this.curr!="s_c_map"){
				this.clear_current("s_c_map");
				this.map_drop(1);
			}else{
				this.home_current();
			}
			return false;
		},
		map_drop: function(dir){
			c_map=$("site_map");
			if(dir==1){
				new Effect.SlideDown(c_map, { duration: .2});
			}else{
				new Effect.SlideUp(c_map, { duration: .2});	
			}
		},
		
		page: function(){
			
			if(this.curr!="s_c_page"){
				this.clear_current("s_c_page");
				if ($('container')) $('container').addClassName('p_o');
				$$('#container a').each(function(item) {
				    var href = item.href;
				    if (href.indexOf('?') == -1) {
				      item.href += '?abs=4';
				    } else {
				      item.href += '&abs=4';
				    }
				});
			}else{
			  $$('#container a').each(function(item) {
				    item.href = item.href.gsub(/[\&\?]abs=4/, '');
				});
				this.home_current();
			}
			return false;
		},
		
		mlm: function(){
			
			if(this.curr!="s_c_mlm"){
				this.clear_current("s_c_mlm");
			}else{
				this.home_current();
			}
		},
		
		clear_current: function(id){
			if(this.curr!=null){
				$(this.curr).up().removeClassName("alt");
			}
			if(this.curr=="s_c_user"){ this.user_drop(); }
			if(this.curr=="s_c_map"){ this.map_drop(); }
			if(this.curr=="s_c_page"){ if ($('container')) $('container').removeClassName('p_o'); }
			if(id!=null) $(id).up().addClassName("alt");
			this.curr=id;
		}, 
		
		home_current: function(){
			this.clear_current(null);
		}
});

Event.observe(window, 'load', 
	function(){ 
		//new AdminBar();
		var p_o=new PageOptionSetter();
	}
);

/* add options */
var PageOptionSetter=new Class.create({
	initialize: function(){
		var int_option=$$(".page_option");
		if(int_option.length>0){
			this.create_overlay();
			this.create_options();
		}
	},
	create_overlay: function(){
		this.overlay=document.createElement('div');
		Element.extend(this.overlay);
		this.overlay.id="option_overlay";
		this.overlay.innerHTML="<b>&nbsp;</b><div>&nbsp;</div><span id='option_overlay_tip'><span id='option_overlay_inner'>&nbsp;</span><b id='option_overlay_tail'>&nbsp;</b></span>";
		document.body.appendChild(this.overlay);
	},
	create_options: function(){
		var int_option=$$(".page_option");
		for(x=0; x<int_option.length; x++){
			new PageOption(int_option[x]);
		}
	}
});

var PageOption=new Class.create({
		initialize: function(el){
			this.el=el;	
			this.par=el.up();
			this.over=$("option_overlay");
			this.over_div=$$("#option_overlay div")[0];
			
			this.over_class=this.el.hasClassName("store") ? "store" : "";
			this.over_span=$("option_overlay_tip");
			
			this.el_a=this.el.childNodes;
			for(y=0; y<this.el_a.length; y++){
				var int_child=this.el_a[y].childNodes;
				if(int_child[int_child.length-1].nodeName=="B"){
					new PageOptionTip(this.el_a[y], this.par, int_child[int_child.length-1].innerHTML);
				}else{
					new PageOptionTip(this.el_a[y], this.par, null);
				}
			}
		}
});

/* displays tips */
var PageOptionTip=new Class.create({
		initialize: function(el, par, tip){
			this.el=el;
			Element.extend(this.el);
			this.par=par;
			this.par.addClassName('relative');
			
			this.tip=tip;
			this.over=$("option_overlay");
			this.over_div=$$("#option_overlay div")[0];
			this.over_b=$$("#option_overlay b")[0];
			this.over_span=$("option_overlay_tip");
			this.over_text=$("option_overlay_inner");
			
			this.over_class=this.el.hasClassName("fulfill") ? "fulfill" : "";
			
			this.show_option=this.show.bindAsEventListener(this);
			Event.observe(el, "mouseover", this.show_option);
			
			this.hide_option=this.hide.bindAsEventListener(this);
			Event.observe(el, "mouseout", this.hide_option);
		}, 
		show: function(){
			
			this.set_style(this.over, 1, 0); 
			this.set_style(this.over_div, 0, 0);
			this.set_style(this.over_b, 0, 4);
			
			this.over.addClassName(this.over_class);
			
			if(this.tip!=null){
				this.over_text.innerHTML=this.tip;
				this.over_span.setStyle({
						"visibility" : "visible"
				});
				this.set_pos();
			}else{
				this.over_span.setStyle({
						"visibility" : "hidden"
				});
			}
		},
		hide: function(){
			
			this.over.removeClassName(this.over_class);
			this.over.setStyle({		
					"display": "none"
			});
			
			this.over_text.innerHTML="";
			this.over_span.setStyle({
					"visibility" : "hidden"
			});
		},
		get_pos: function(){
			return Position.cumulativeOffset(this.par);
		},
		get_dims: function(){
			return this.par.getDimensions();
		}, 
		set_style: function(some_el, mode, offset){
			some_el.setStyle({
						"width" : eval(this.get_dims().width-offset)+"px",
						"height" : eval(this.get_dims().height-offset)+"px"
						});
			if(mode==1){
				some_el.setStyle({
						"display": "block",
						"top" : this.get_pos()[1]+"px",
						"left" : this.get_pos()[0]+"px"
						});
			}
		},
		/* sets the position of the tip */
		set_pos: function(){
			if(this.get_dims().width-260<0){
				this.over_span.setStyle({
							"bottom" : "100%",
							"margin-left" : eval((this.get_dims().width-260)/2)+"px",
							"top" : "auto"
							
					});
			}else{
				this.over_span.setStyle({
							"bottom" : "100%",
							"margin-left" : 0,
							"top" : "auto"
							
					});
			}
			
			var body=(document.compatMode && document.compatMode != "BackCompat")? document.documentElement : document.body
			var top=document.all? body.scrollTop : pageYOffset;
			if((Position.cumulativeOffset(this.over_span)[1]-top)<0){
				this.over_span.setStyle({
						"bottom" : "auto",
						"top" : eval(top - this.get_pos()[1] + 10) + "px"
				});
			}
		}
		
});

/*
================================
================================
================================
================================
add backend options */

var BackOptionSetter=new Class.create({
	initialize: function(){
		
		this.scroller=null;
		
		this.create_overlay();
		Event.observe(document.body, 'click', this.hide.bind(this));
	},
	create_overlay: function(){
		
		this.par=$('back_option').up();
		this.par.addClassName("relative");
		
		this.b_a=document.createElement('b');
		this.b_a.id="b_a";
		
		this.b_b=document.createElement('b');
		this.b_b.id="b_b";
		
		this.b_c=document.createElement('b');
		this.b_c.id="b_c";
		
		this.b_d=document.createElement('b');
		this.b_d.id="b_d";
		
		this.par.appendChild(this.b_a);
		this.par.appendChild(this.b_b);
		this.par.appendChild(this.b_c);
		this.par.appendChild(this.b_d);
		
		/* setup overlay */
		this.overlay=document.createElement('div');
		this.overlay.id="back_tip";
		this.overlay.innerHTML="<hr id='back_tip_scroll' /><div id='back_span'>"+$('back_option').innerHTML+"</div><b>&nbsp;</b>";
		
		this.size_comp=null;
		this.pos_comp=null;
		
		this.par.appendChild(this.overlay);
		
		/* set positions and style */
		this.set_style();
		this.size_tester();
		this.scroller=new Effect.ScrollTo($('back_tip'));
	},
	hide: function(){
		this.hide_el(this.b_a);
		this.hide_el(this.b_b);
		this.hide_el(this.b_c);
		this.hide_el(this.b_d);
		this.hide_el($('back_tip'));
		
		this._clearTimeout(this.timeout);
		this.scroller.cancel();
		
		this.cancel=1;
	},
	hide_el: function(el){
		el.setStyle({
				"display" : "none"
		});
	},
	set_style: function(){
		
		var x_edge_a=Position.cumulativeOffset(this.b_a)[0]+this.par.getDimensions().width
		var x_edge_b=Position.cumulativeOffset(this.b_b)[0]+2;
		this.offset=x_edge_b-x_edge_a;
		
		$('b_a').setStyle({
				"width" : eval(this.par.getDimensions().width+this.offset)+"px"
		});
		$('b_c').setStyle({
				"width" : eval(this.par.getDimensions().width+this.offset)+"px"
		});
		$('b_b').setStyle({
				"height" : eval(this.par.getDimensions().height+this.offset)+"px"
		});
		$('b_d').setStyle({
				"height" : eval(this.par.getDimensions().height+this.offset)+"px"
		});
		
	},
	
	size_tester: function(){
		
		if(this.size_comp==null){
			this.size_comp=this.par.getDimensions();
		}
		if(this.pos_comp==null){
			this.pos_comp=Position.cumulativeOffset(this.par)[1];
		}
		if(this.par.getDimensions()!=this.size_comp){
			this.set_style();
			this.size_comp=this.par.getDimensions();
		}
		if(this.pos_comp!=Position.cumulativeOffset(this.par)[1]){
			this.scroller.cancel();
			this.scroller=new Effect.ScrollTo($('back_tip'));
			this.pos_comp=Position.cumulativeOffset(this.par)[1];
		}
		this.timeout=window.setTimeout(this.size_tester.bind(this), 1000);
	},
	_clearTimeout: function(timer) {
		clearTimeout(timer);
		clearInterval(timer);
		return null;
	  }
});

var MapButton=new Class.create({
	initialize: function(el){
		this.drop=el;
		this.par=el.up();
		this.button=this.par.down();
		this.state=0;
		//this.list=$$(".page_list>ul>li")
		this.list=$$(".map_a");
		this.v_line=$$('#v_line li');
		this.h_line=$$('#h_line li');
		
		this.show_option=this.show.bindAsEventListener(this);
		Event.observe(this.button, "click", this.show_option);
	},
	show: function(){
		if(this.state==0){
			this.appear();
		}else{
			this.hide();
		}
	}, 
	appear: function(){
		this.drop.setStyle({ 
			"display" : "block",
			"opacity" : 0
		});
		new Effect.Appear(this.drop, { from: 0, to: 1, duration: .2 });
		this.par.setStyle({ "z-index" : "201" });
		this.par.addClassName('alt');
		this.clear(1);
		
		this.body_clear=this.body_func.bindAsEventListener(this);
		Event.observe($(document.body), "click", this.body_clear);
		this.state=1;
	},
	hide: function(){
		new Effect.Appear(this.drop, { from: 1, to: 0, duration: .2, afterFinish: this.hide_drop.bind(this)});
		this.par.setStyle({ "z-index" : "1" });
		this.par.removeClassName('alt');
		this.clear();
		this.state=0;
	},
	hide_drop: function(){
		this.drop.setStyle({
				"display" : "none"
		});
	},
	clear: function(a){
		if(a==1){
			for(y=0; y<this.list.length; y++){
				if(this.list[y]!=this.button){
					new Effect.Morph(this.list[y], { style: "opacity: .25", duration: .2 });
				}
			}
			for(y=0; y<this.h_line.length; y++){
				new Effect.Morph(this.h_line[y], { style: "opacity: .07", duration: .2 });
			}
			for(y=0; y<this.v_line.length; y++){
				new Effect.Morph(this.v_line[y], { style: "opacity: .07", duration: .2 });
			}
			
			$("cover").show();
		}else{
			for(y=0; y<this.list.length; y++){
				if(this.list[y]!=this.par){
					new Effect.Morph(this.list[y], { style: "opacity: 1", duration: .2 });
				}
			}
			for(y=0; y<this.h_line.length; y++){
				new Effect.Morph(this.h_line[y], { style: "opacity: 1", duration: .2 });
			}
			for(y=0; y<this.v_line.length; y++){
				new Effect.Morph(this.v_line[y], { style: "opacity: 1", duration: .2 });
			}
			$("cover").hide();
		}
	},
	body_func: function(e){
		if (e.target){
				int_test=e.target;
		}else if (e.srcElement){
			int_test= e.srcElement;
		}
		
		//var int_test=e.target;
		
		var test_cond=0;
		while(int_test!=document.body){
			if(int_test==this.par&&this.state==1){ test_cond=1; }
			int_test=int_test.up();
		}
		if(test_cond==0){
			this.hide();
			Event.stopObserving($(document.body), "click", this.body_clear)	
		}
	}
});

Event.observe(window, 'load', 
	function(){ 
		var drop=$$("#site_map .drop");
		for(x=0; x<drop.length; x++){
			new MapButton(drop[x]);
		}
	}
);



var asyncProgressKey = null;
var asyncProgressStartingText = null;
var asyncProgressOptions = null;

function startAsyncProgress(key, title, initialStartingText, options) {
  asyncProgressOptions = options;
  if(asyncProgressOptions == null) asyncProgressOptions = {};
  asyncProgressKey = key;
  if($("async_progress_dialog") == null) {
    alert("Unable to display progress: async_progress_dialog missing");
    return
  }
  $("async_progress_title").update(title);
  if(initialStartingText == null) {
    initialStartingText = "starting..";
  }
  asyncProgressStartingText = initialStartingText;
  $("async_progress_actions").hide();
  updateAsyncProgress(initialStartingText, 0, true, null, null);
  
  var actions = [];
  if(asyncProgressOptions.actions != null) {
    for(k in asyncProgressOptions.actions) {
      var action = asyncProgressOptions.actions[k];
      var clz = action.className == null ? '' : ' class="' + action.className + '"';
      actions.push('<input ' + clz + ' type="button" value="' + action.caption + '" onclick="callAsyncProgressAction(\'' + k + '\');"/>');
    }
  } else if(asyncProgressOptions.requireOk) {
    actions.push('<input type="button" value="OK" onclick="updateAsyncProgressOk();"/>');
  }
  $('async_progress_actions').update(actions.join(''));
  
  if (typeof(go_popup) == 'function') {
    go_popup("async_progress_dialog", true, true, null, true);
  } else {
    popup("async_progress_dialog");
  }
  
  if (asyncProgressOptions.showSpinner) {
    if ($("async_progress_spinner")) $("async_progress_spinner").show();
  }
}

function continueAsyncProgress(key, initialStartingText) {
  if(initialStartingText == null) {
    initialStartingText = asyncProgressStartingText;
  }
  asyncProgressKey = key;
  updateAsyncProgress(initialStartingText, 0, true, null, null);
}

function updateAsyncProgress(statusText, percent, doRefresh, warnings, errors, notes, metaData) {
  
  var totalWidth = parseInt($("async_progress_status_bar").offsetWidth);
	var barWidth = parseFloat(totalWidth * percent) / 100.0;
	$("async_progress_indicator").style.width = parseInt(barWidth) + "px";
	$("async_progress_message").update(statusText);
	
  if((notes == null)||(notes.length == 0)) {
    $("async_progress_notes").hide();
  } else {
    $("async_progress_notes").show();
    var html = "";
    for(var i=0; i < notes.length; i++) {
      html += "<li>" + notes[i] + "</li>";
    }
    $("async_progress_note_list").update(html);
  }
  
  if((warnings == null)||(warnings.length == 0)) {
    $("async_progress_warnings").hide();
  } else {
    $("async_progress_warnings").show();
    var html = "";
    for(var i=0; i < warnings.length; i++) {
      html += "<li>" + warnings[i] + "</li>";
    }
    $("async_progress_warning_list").update(html);
    $("async_progress_actions").show();
  }
  if((errors == null)||(errors.length == 0)) {
    $("async_progress_errors").hide()
  } else {
    $("async_progress_errors").show();
    var html = "";
    for(var i=0; i < errors.length; i++) {
      html += "<li>" + errors[i] + "</li>";
    }
    $("async_progress_error_list").update(html);
    $("async_progress_actions").show();
  }
  
	if(doRefresh) {
    if(asyncProgressKey != null) { //this lets us start the dialog before we have a key...
      window.setTimeout( function() {
        var ajax = new Ajax.Request("/shared/lookups/async_progress?key=" + encodeURIComponent(asyncProgressKey) + "&ts=" + new Date().getTime(), {asynchronous:true, evalScripts:true, method:'get', 
          onFailure: function() {
            updateAsyncProgress(caption, percent, doRefresh, warnings, errors); //retry....
          }
        });
      }, 500);
    }
	} else {
    if ($("async_progress_spinner")) $("async_progress_spinner").hide();
    if((asyncProgressOptions.requireOk != true)&&((asyncProgressOptions.actions == null)||(asyncProgressOptions.actions.length == 0))) {
      if(asyncProgressOptions.onFinish != null) { 
        if (typeof(go_popup) == 'function') {
          go_popup("async_progress_dialog", true, false);
        } else {
          closePopup("async_progress_dialog");
        }
        if(asyncProgressOptions.onFinish(warnings, errors, notes, metaData) == "show_errors") {
          $('async_progress_actions').update('<input type="button" value="OK" onclick="updateAsyncProgressOk();"/>');
          $("async_progress_actions").show();
          
          if (typeof(go_popup) == 'function') {
            go_popup("async_progress_dialog", true, true, null, true);
          } else {
            popup("async_progress_dialog");
          }
        }
      } else {
        window.setTimeout( function() {
          if (typeof(go_popup) == 'function') {
            go_popup("async_progress_dialog", true, false);
          } else {
            closePopup("async_progress_dialog");
          }
        }, 1000);
      }
    } else {
      if(asyncProgressOptions.onFinish != null) { 
        asyncProgressOptions.onFinish(warnings, errors, notes, metaData);
      }
      $("async_progress_actions").show();
      
      if (typeof(iframeMode) != 'undefined' && iframeMode) {
        var div = $("async_progress_dialog");
        parent.orderManager.notifyPopupSizeChange(div.clientWidth, div.clientHeight);
      }
    }
	}
}

function updateAsyncProgressOk() {
  if (typeof(go_popup) == 'function') {
    go_popup("async_progress_dialog", true, false);
  } else {
    closePopup("async_progress_dialog");
  }
  if(asyncProgressOptions.onOK != null) asyncProgressOptions.onOK();
  if(asyncProgressOptions.onFinish != null) { 
    asyncProgressOptions.onFinish(warnings, errors, notes, metaData);
  }
}

function callAsyncProgressAction(action) {
  if (typeof(go_popup) == 'function') {
    go_popup("async_progress_dialog", true, false);
  } else {
    closePopup("async_progress_dialog");
  }
  var actionData = asyncProgressOptions.actions[action];
  if(actionData != null) {
    actionData.callback();
  }
}

function cancelAsyncProgress() {
  if (typeof(go_popup) == 'function') {
    go_popup("async_progress_dialog", true, false);
  } else {
    closePopup("async_progress_dialog");
  }
}


function _pcRebindSessionLinks(target) {
  //log("rebinding secure links for " + target);
  for(var i=0; i < document.links.length; i++) {
    var link = document.links[i];
    if(link.href.indexOf(target) == 0) {
      log("rebinding secure link " + link.href);
      Event.observe(link, "click", _pcSecureLink);
    }
  }
}

function _pcSecureLink(e) {
  log("session change link clicked");
  var el = Event.element(e);
  while(el != null && el.tagName != "A") {
    el = el.parentNode;  
  }
  if(el == null) {
    alert("Unable to change domains correctly: cannot rewrite url");
    return;
  }
  
  var url = _pcGetSecureLink(el.href);
  window.location = url;
  log("changed location to " + url);
  el.href = url;
  Event.stop(e);
  return false;
}

function _pcGetSecureLink(url) {
  if(url.indexOf("?") == -1) {
    return url + "?_pc_session_id=" + pcSID + "&_pc_skey=" + pcSKey;
  } else {
    return url + "&_pc_session_id=" + pcSID + "&_pc_skey=" + pcSKey;
  }
}

function updateStatesFromEl(countryEl, stateControlId) {
	//alert("getting updateStatesFromEl..");
	updateStates(countryEl.value, stateControlId);
}

function updateStates(countryId, stateControlId) {
	//alert("getting states..");
	//alert(stateControlId);
	var sb = document.getElementById(stateControlId);
	//alert("got el");
	//alert(sb);
	if(sb==null) {
		alert("unable to get states element (" + stateControlId + ") to populate it");
		return;
	}
	//alert("1");
	sb.options.length = 0;
	//alert("2");
	sb.options[0] = new Option("Updating...","");
	
	//alert("asyncStart..");
	var aKey = asyncStart(stateControlId);
	
	//alert("calling..");
	var t2 = new Ajax.Request("/ppr/countries/get_states?id=" + countryId + "&ctl=" + stateControlId, {asynchronous:true, evalScripts:true, onComplete: function() { asyncFinish(aKey);},
			onException: function(t,e) {
				alert(e.name);
				alert(e.message);
	}});
}

function updatePostcode(countryId, postcodeControlId) {
  var pc = $(postcodeControlId);
  
  if (pc == null) {
    alert("Unable to get postcode control (" + postcodeControlId + ") to update");
    return;
  }
  
  var aKey = asyncStart(postcodeControlId);
  
  var t2 = new Ajax.Request("/ppr/countries/check_postcode_usage?id=" + countryId + "&ctl=" + postcodeControlId, {asynchronous:true, evalScripts:true, onComplete: function() { asyncFinish(aKey);},
			onException: function(t,e) {
				alert(e.name);
				alert(e.message);
	}});
}

function isNumeric(sText) {
  var validChars = "0123456789.";
  var isNumber=true;
  var c;
  
  
  for (var i = 0; i < sText.length && isNumber == true; i++) { 
    c = sText.charAt(i); 
    if (validChars.indexOf(c) == -1) {
      isNumber = false;
    }
  }
  return isNumber;
}

function getPriceValue(el, max, defaultValue, min) {
  if (!defaultValue) defaultValue = 0;
  var price = parseFloat(el.value);
  if (isNaN(price)) {
    el.value = defaultValue;
    price = defaultValue;
  }
  if (!isNumeric(el.value) || price != price.round()) {
    price = price.round();
    el.value = price;
  }
  if (max != null && price > max) {
    el.value = max;
    price = max;
  }
  if (min != null && price < min) {
    el.value = min;
    price = min;
  }
  while (el.value.length > 1 && el.value.charAt(0) == '0' && el.value.charAt(1) != '.') {
    el.value = el.value.substr(1, el.value.length-1);
  }
  return price;
}

function getIntegerValue(el, max, defaultValue, min) {
  if (!defaultValue) defaultValue = 0;
  var num = parseInt(el.value);
  if (isNaN(num)) {
    num = defaultValue;
  }
  if (!isNumeric(el.value) || num != parseInt(num)) {
    num = parseInt(num);
  }
  if (max != null && num > max) {
    num = max;
  }
  if (min != null && num < min) {
    num = min;
  }
  el.value = num;
  return num;
}

function inputSpanClicked(el, id) {
  try {
    $(id).focus();
  } catch(e) {}
}


var allRanOk = true;
var allRunErrorCount = 0;
function trackRuns(func) {
  try {
    func();
  } catch(e) {
    allRunErrorCount += 1;
    allRanOk = false;  
    if(allRunErrorCount == 1) {
      alert("An error has occured while loading this page which will stop if from being saved. Please report the error '" + e.message + "' along with the current url '" + window.location + "'"); 
    }
  }
}



var _pcCallbacks = {};
function dnRegisterCallback(code, callback) {
  _pcCallbacks[code] = callback;
}

function dnRunCallback(code, context){
  if(_pcCallbacks[code] != null) {
    return _pcCallbacks[code](context);
  }
  return null;
}

// Place your application-specific JavaScript functions and classes here

var useAlphaHack = false;
if((BrowserDetect.browser == "Explorer")&&(BrowserDetect.version < 7.0)&&(BrowserDetect.version >= 5.5)) {
	useAlphaHack = true;
}

function f_clientWidth() {
	return f_filterResults (
		window.innerWidth ? window.innerWidth : 0,
		document.documentElement ? document.documentElement.clientWidth : 0,
		document.body ? document.body.clientWidth : 0
	);
}
function f_clientHeight() {
	return f_filterResults (
		window.innerHeight ? window.innerHeight : 0,
		document.documentElement ? document.documentElement.clientHeight : 0,
		document.body ? document.body.clientHeight : 0
	);
}
function f_scrollLeft() {
	return f_filterResults (
		window.pageXOffset ? window.pageXOffset : 0,
		document.documentElement ? document.documentElement.scrollLeft : 0,
		document.body ? document.body.scrollLeft : 0
	);
}
function f_scrollTop() {
	return f_filterResults (
		window.pageYOffset ? window.pageYOffset : 0,
		document.documentElement ? document.documentElement.scrollTop : 0,
		document.body ? document.body.scrollTop : 0
	);
}
function f_filterResults(n_win, n_docel, n_body) {
	var n_result = n_win ? n_win : 0;
	if (n_docel && (!n_result || (n_result > n_docel)))
		n_result = n_docel;
	return n_body && (!n_result || (n_result > n_body)) ? n_body : n_result;
}

function windowSize() {
  var myWidth = 0, myHeight = 0;
  if( typeof( window.innerWidth ) == 'number' ) {
    //Non-IE
    myWidth = window.innerWidth;
    myHeight = window.innerHeight;
  } else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
    //IE 6+ in 'standards compliant mode'
    myWidth = document.documentElement.clientWidth;
    myHeight = document.documentElement.clientHeight;
  } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
    //IE 4 compatible
    myWidth = document.body.clientWidth;
    myHeight = document.body.clientHeight;
  }
  return {w: myWidth + f_scrollLeft(), h:myHeight + f_scrollTop()};
}

var hiddenSelects = [];

var currentPopupId = null;
var popupStack = [];

var iframeMode = false;
function popup(divId, mode, callback) {
	
	log("popup(" + divId + ")");
	var div = $(divId);
	if(div==null) {
		alert("unable to get element:" + divId);
		return;
	}
	
	var bgdiv = $('popupbackground');
	if(bgdiv==null) {
		alert("unable to get element:popupbackground");
		return;
	}
	
	var height = f_clientHeight();
	var width = f_clientWidth();
	
	var ws = windowSize();
	
	if(width < document.body.clientWidth) {
		width = document.body.clientWidth;
	}
	
	/*if (document.body.scrollHeight>document.body.offsetHeight){ 
		bgdiv.style.height=document.body.scrollHeight+"px"; 
	}else{
		bgdiv.style.height=document.body.offsetHeight+"px";
	}*/
	
	bgdiv.show();
	
	div.style.zIndex = 3001;
	
	if(mode==1) {
		
	} else {
		div.style.position="absolute";
		div.style.display="block";
		div.style.visibility="hidden";
		log("scrolltop =" +  f_scrollTop() + ", f_clientHeight()=" + f_clientHeight());
		if( ((((f_clientHeight() / 2) - (div.clientHeight/2))) + f_scrollTop()) <10 ) {
			div.style.top = "10px";
		} else {
			div.style.top = parseInt(((f_clientHeight() / 2) - (div.clientHeight/2)),10) + f_scrollTop() + "px";
			log("div.style.top=" + div.style.top);
		}
		
		div.style.left = parseInt(((f_clientWidth()  / 2) - (div.clientWidth/2)),10) + f_scrollLeft() + "px";
		div.style.visibility="visible";
	}
	
	if (iframeMode) {
	  div.className = 'ipopup';
	  div.style.top = '0';
	  div.style.left = '0';
	  window.scrollTo(0,0);
	  parent.orderManager.notifyPopupSizeChange(div.clientWidth, div.clientHeight);
	}
	
	if(currentPopupId != null) {
	  restoreHiddenSelects();
	  if(currentPopupCallback != null) {
	    currentPopupCallback("hide");  
	  }
		popupStack.push([currentPopupId, currentPopupCallback]);
		$(currentPopupId).style.display="none";
	}
	
	currentPopupId = divId;
	currentPopupCallback = callback;
	storeBackgroundSelects(div);
	
	return {t: div.style.top, l: div.style.left};
}

function storeBackgroundSelects(div) {
	var sels = document.getElementsByTagName("SELECT");
	for(var i=0;i < sels.length;i++) {
		if(!$(sels[i]).descendantOf(div)) {
			hiddenSelects.push(sels[i]);
			sels[i].style.visibility="hidden";
		}
	}
}

function restoreHiddenSelects() {
	while(hiddenSelects.size() > 0) {
		var el = hiddenSelects.pop();
		el.style.visibility = "visible";
	}
}

function swapPopup(oldDivId, newDivId, reposition) {
	var oldDiv = $(oldDivId);
	var newDiv = $(newDivId);
	
	newDiv.style.zIndex = oldDiv.style.zIndex;
	
	if (iframeMode) {
	  newDiv.className = 'ipopup';
	}
	
	if(reposition == true) {
		newDiv.style.visibility="hidden";
		newDiv.style.display="";
		repositionPopup(newDivId);
	} else {
		newDiv.style.top = oldDiv.style.top;
		newDiv.style.left = oldDiv.style.left;
		newDiv.style.width = oldDiv.style.width;
		newDiv.style.height = oldDiv.style.height;
	}
	newDiv.style.display="";
	newDiv.style.visibility="visible";
	oldDiv.style.display="none";
	
	restoreHiddenSelects();
	storeBackgroundSelects(newDiv);
	
	currentPopupId = newDivId;
	if(currentPopupCallback != null) {
    currentPopupCallback("swap");
  }
}

function closePopup(divId, mode, ignoreIframe) {
	
	if(divId == null) {
		divId = currentPopupId;
	}
	log("closePopup(" + divId + ")");
	var div = $(divId);
	if(div==null) {
		alert("unable to get popup element");
		return;
	}
	var bgdiv = $('popupbackground');
	if(bgdiv==null) {
		alert("unable to get background element");
		return;
	}
	
	if(mode != 1) {
		log(div);
		div.style.visibility="hidden";
		div.style.display="none";
		log(div);
	}
	
	if (iframeMode && !ignoreIframe) {
	  parent.orderManager.notifyPopupClose();
	}
	
	restoreHiddenSelects();
	
	if(popupStack.length > 0) {
	  var psData = popupStack.pop();
		currentPopupId = psData[0];
		currentPopupCallback = psData[1];
		$(currentPopupId).style.display="";
		storeBackgroundSelects($(currentPopupId)) ;
		if(currentPopupCallback != null) {
		  currentPopupCallback("restore");
		}
	} else {
		log(bgdiv);
		//bgdiv.className="popupclose";
		bgdiv.hide();
		log(bgdiv);
		currentPopupId = null;
		currentPopupCallback = null;
	}
	
}

function repositionPopup(divId) {
	var div = $(divId);
	if(div==null) {
		alert("unable to get popup element");
		return;
	}
	
	if (iframeMode) {
	  div.style.top = '0';
	  div.style.left = '0';
	  parent.orderManager.notifyPopupSizeChange(div.clientWidth, div.clientHeight);
	} else {
    if( ((((f_clientHeight() / 2) - (div.clientHeight/2))) + f_scrollTop()) <10 ) {
      div.style.top = "10px";
    } else {
      div.style.top = (((f_clientHeight() / 2) - (div.clientHeight/2))) + f_scrollTop() + "px";
    }
    
    div.style.left = (((f_clientWidth()  / 2) - (div.clientWidth/2))) + f_scrollLeft() + "px";
  }
  
	if(currentPopupCallback != null) {
    currentPopupCallback("reposition");
  }
}


function dynamicPopup(url) {
  var div = $('dynpopup_content');
  if(div == null) {
    alert("Unable to get dynamic popup");
  }
  div.update("Loading....");
  popup('dynpopup');
  var aKey = null;
  var t2 = new Ajax.Updater({success:"dynpopup_content"}, url, {asynchronous:true, evalScripts:true,
    onComplete: function D_onComplete() { 
      asyncFinish(aKey);
      repositionPopup("dynpopup");
    },
    onFailure: function D_onFailure() {
      closePopup("dynpopup");
      alert("An error occured processing request");
    }
  });
  aKey = asyncStart($("dynpopup"));
}


var msgBoxCallback = null;
var msgboxId = null;

function msgBox(title, message, icon, yes, no, cancel, callback) {
	msgBoxCallback = callback;
	msgboxId = getNextId();
	var html="";
	if(icon != null) { html += '<img src="' + icon + '" align="left"/>'; }
	html+="<div>";
	html+='<h3>' + title + '</h3>';
	
	html += '<span class="msgbox_text">' + message + '</span><br/><div class="submit msgbox_buttons">';
	
	if(yes != null) {
		html += '<b class="action"><input type="button" value="' + yes + '" onclick="msgBoxFinish(0);" class="button" id="d_msgbox_def_' + msgboxId + '"/></b>';
	}
	if(no != null) {
		html += '<b class="action"><input type="button" value="' + no + '" onclick="msgBoxFinish(1);" class="button"/></b>';
	}
	if(cancel != null) {
		html += '<b class="action cancel"><input type="button" value="' + cancel + '" onclick="msgBoxFinish(2);" class="button cancel"/></b>';
	}
	html += "</div>";
	html += "</div>";
	var div = document.createElement("DIV");
	div.className = "popup msgbox";
	div.style.display="none";
	div.id = "msgbox_" + msgboxId;
	div.innerHTML = html;
	
	document.body.appendChild(div);
	popup("msgbox_" + msgboxId);
	try {
	  $('d_msgbox_def_' + msgboxId).focus();
	} catch(e) {}
}

function msgBoxFinish(result) {
	closePopup("msgbox_" + msgboxId);
	var div = document.getElementById("msgbox_" + msgboxId);
	document.body.removeChild(div);
  if(msgBoxCallback != null) {
    msgBoxCallback(result);
  }
}


var promptCallback = null;
var promptId = null;

function promptNew(title, message, icon, ok, cancel, defaultValue, callback) {
	promptCallback = callback;
	promptId = getNextId();
	var html="";
	if(icon != null) {
		html += '<img src="' + icon + '" align="left"/>'; 
	}
	html+='<div>';
	html += '<h3>' + title + '</h3>';
	
	html += '<span class="msgbox_text">' + message + ' <input id="propt_val_' + promptId + '" value="' + defaultValue + '"/></span><br/><div class="submit msgbox_buttons">';
	if(cancel != null) {
		html += '<b class="action cancel"><input type="button" value="' + cancel + '" onclick="promptFinish(1);" class="button cancel"/></b>';
	}
	if(ok != null) {
		html += '<b class="action"><input type="button" value="' + ok + '" onclick="promptFinish(0);" class="button"/></b>';
	}
	
	html += "</div>";
	html += "</div>";
	var div = document.createElement("DIV");
	div.className = "popup msgbox";
	div.style.display="none";
	div.id = "prompt_" + promptId;
	div.innerHTML = html;
	
	document.body.appendChild(div);
	popup("prompt_" + promptId);
	$('propt_val_' + promptId).focus();
	var el = document.getElementById("propt_val_" + promptId);
	el.focus();
}

function promptFinish(result) {
	var val = null;
	closePopup("prompt_" + promptId);
	if(result == 0) {
		var el = document.getElementById("propt_val_" + promptId);
		val = el.value;
	}
	var div = document.getElementById("prompt_" + promptId);
	document.body.removeChild(div);
	promptCallback(result, val);
}

var nextId = 0;
function getNextId() {
	nextId --;
	return nextId;
}

function getElId(el) {
	if(el.id != null && el.id != "") {
		return el.id;
	}
	el.id = "tmp_id_" + getNextId();
	return el.id;
}

var asyncActions = new Hash();

function asyncStart(container) {
  var containerEl = $(container);
  var containerId = getElId(containerEl);
  
  log("asyncStart:" + containerId);
  
  //there can only be one async action per container...
  if(asyncActions[containerId] != null) {
    log("Stopping existing async action " + containerId);
    asyncFinish({id:containerId, v: asyncActions[containerId].v});
  }
  
  var divB = document.createElement("DIV");
  divB.style.display = "none";
  divB.className = "async_action_background";
  
  var div = document.createElement("DIV");
  div.style.display = "none";
  div.className = "async_action";
  
  div.innerHTML="<img src='/images/spinner_no_bg.gif' alt='[]' />";
  var oldPositioning = null;
  

  var dims = Element.getDimensions(container);
  var offset = Position.cumulativeOffset(containerEl);
  log(offset);
  
  divB.style.position = "absolute";
  divB.style.top = offset[1] + "px";
  divB.style.left = offset[0] + "px";
  divB.style.height = dims.height + "px";
  divB.style.width = dims.width + "px";
  //divB.style.display="none";
  divB.style.zIndex = 9999;
  document.body.appendChild(divB);
  divB.id = "aa_" + containerId + "_bg";
  
  div.style.position = "absolute";
  div.style.top = offset[1] + "px";
  div.style.left = offset[0] + "px";
  div.style.height = dims.height + "px";
  div.style.width = dims.width + "px";
  div.backgroundColor="#000000";
  //div.style.display="none";
  div.style.zIndex = 10000;
  document.body.appendChild(div);
  //Effect.Appear(div, { duration: 0.2 });
  
  div.id = "aa_" + containerId;
  
  $(divB).show();
  $(div).show();
  var v = getNextId();
  asyncActions[containerId] = {d:div, db:divB, c:container, p:oldPositioning, v:v};
  return {id: containerId, v:v};
}

function asyncFinish(key) {
  if(key == null) {
    return;
  }
  var containerId = key.id;
  
  var aa = asyncActions[containerId];
  if(aa == null) {
    log("asyncFinish ignored (" + containerId + " not in asyncActions)");
  } else {
    if(aa.v != key.v) {
      log("asyncFinish ignored (" + aa.v + " != " + key.v + ")");
    } else {
      delete asyncActions[containerId];
      try {
        // Check to make sure the object exists before removing it (because this is run twice and may not still exist)
        if($(aa.d.id)){
         // Effect.Fade(aa.d, { 
          //  duration: 0.2, 
          //  afterFinish: function asyncFinishAfterFinish(){document.body.removeChild(aa.d);}
         // });
         document.body.removeChild(aa.d);
        }
        if($(aa.db.id)){
          document.body.removeChild(aa.db);
        }
      } catch(e) {}
    }
  }
}

function setImageUrl(container, img, url, callback) {
	var cbimg = null;
	if(img!= null) {
		cbimg = img; 
	} else {
		var cbimg = document.createElement("IMG");
		cbimg.style.display = "none";
		document.body.appendChild(cbimg);
		log("appended async img");
	}
	var callbackDone = false;
	var asyncKey = null;
	cbimg.onload = function() {
			log("async img loaded: " + callbackDone);
			if(callbackDone) {
				return;
			}
			callbackDone = true;
			/*
			if(img!=null) {
				log("si");
				img.src = cbimg.src;
				log("sd");
			}
			*/
			if(callback!=null) {
				callback(cbimg.src);
			}
			asyncFinish(asyncKey);
			if(img==null) {
				document.body.removeChild(cbimg);
			}
		};
		
	cbimg.src = url;
	if(BrowserDetect.browser == "Explorer") {
		if(cbimg.complete) {
			log("async img complete: " + callbackDone);
			/*
			if(img!=null) {
				img.src = cbimg.src;
			}
			*/
			if(callbackDone) {
				return;
			}
			callbackDone = true;
			if(callback!=null) {
				callback(cbimg.src);
			}
			if(img==null) {
				document.body.removeChild(cbimg);
			}
		} else {
			asyncKey = asyncStart(container);
			
		}
	} else {
		//other browsers call onload properly...
		asyncKey = asyncStart(container);
	}
}

function setBackgroundImage(container, div, url) {
	setImageUrl(container, null, url, function(nUrl) {
		log("setting background image to " + nUrl);
		div.style.backgroundImage = "url(" + nUrl + ")";
	});
}


function setTransImage(el, src, w, h) {
	if(useAlphaHack) {
		el.src = "/ppr/images/trans.gif";
		el.style.fontSize = "1px";
		el.style.width = w + "px";
		el.style.height = h + "px";
		el.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '", sizingMethod="scale")';
	} else {
		el.src = src;
		el.width = w;
		el.height = h;
	}
}

function updateStatesFromEl(countryEl, stateControlId) {
	//alert("getting updateStatesFromEl..");
	updateStates(countryEl.value, stateControlId);
}

function updateStates(countryId, stateControlId, stateContainerId, filterStates) {
	//alert("getting states..");
	//alert(stateControlId);
	var sb = document.getElementById(stateControlId);
	//alert("got el");
	//alert(sb);
	if(sb==null) {
		alert("unable to get states element (" + stateControlId + ") to populate it");
		return;
	}
	//alert("1");
	sb.options.length = 0;
	//alert("2");
	sb.options[0] = new Option("Updating...","");
	
	//alert("asyncStart..");
	var aKey = asyncStart(stateControlId);
	
	var extraParams = '';
	if(stateContainerId != null) {
	  extraParams = '&cnt=' + stateContainerId;
	  if(filterStates) {
	    extraParams += '&fs=1';
	  }
	}
	//alert("calling..");
	var t2 = new Ajax.Request("/ppr/countries/get_states?id=" + countryId + "&ctl=" + stateControlId + extraParams , {asynchronous:true, evalScripts:true, onComplete: function() { asyncFinish(aKey);},
			onException: function(t,e) {
				alert(e.name);
				alert(e.message);
	}});
}

function updatePostcode(countryId, postcodeControlId) {
  var pc = $(postcodeControlId);
  
  if (pc == null) {
    alert("Unable to get postcode control (" + postcodeControlId + ") to update");
    return;
  }
  
  var aKey = asyncStart(postcodeControlId);
  
  var t2 = new Ajax.Request("/ppr/countries/check_postcode_usage?id=" + countryId + "&ctl=" + postcodeControlId, {asynchronous:true, evalScripts:true, onComplete: function() { asyncFinish(aKey);},
			onException: function(t,e) {
				alert(e.name);
				alert(e.message);
	}});
}

function toggle_faq(fid) {
	var el = $("a_" + fid);
	if(el.style.display=="none") {
		el.style.display="";
	} else {
		el.style.display="none";
	}
}

function showDesignerFaq() {
	myLightWindow.activateWindow({
			href: "/ppr/product_info/pop/tshirt_faq", 
			title: 'FAQs',
			width: 650,
			height: 550
		});
	
}

function showCv2Info() {
	myLightWindow.activateWindow({
			href: "/ppr/product_info/pop/cv2", 
			title: 'CV2 Numbers',
			width: 400,
			height: 400
		});
	
}
	
function showDesignerShipping() {
	myLightWindow.activateWindow({
			href: "/ppr/product_info/pop/shipping", 
			title: 'Shippping Information',
			width: 650,
			height: 550
		});
	
}


var ttCurrentPopup = null;
var ttTimer = null;

function ttMouseOver(el, popEl) {
	if(ttCurrentPopup==popEl) {
		ttClearTimeout();
		return;
	}
	if(ttCurrentPopup!=null) {
		ttCurrentPopup.style.display="none";
	}
	//position the new popup
	
	pos = Position.cumulativeOffset(el);
	popEl.style.left = (pos[0] + 10) + "px";
	popEl.style.top = (pos[1] + 10) + "px";
	popEl.style.display="block";
	
	ttClearTimeout();
	ttCurrentPopup = popEl;
	
}

function ttMouseLeave(el, popEl) {
	if(ttCurrentPopup==popEl) {
		ttSetCloseTimeout(ttCurrentPopup);
		return;
	}
}

function ttSetCloseTimeout(popEl) {
	ttClearTimeout();
	//log("ttSetCloseTimeout()");
	ttTimer = window.setTimeout( function () { 
			popEl.style.display="none";
			ttCurrentPopup = null;
	}, 1000);
}

function ttClearTimeout() {
	if(ttTimer != null) {
		window.clearTimeout(ttTimer);
		ttTimer = null;
	}
}

//quick version of $ that does not extend
function $q(element) {
  if (typeof element == 'string')
    element = document.getElementById(element);
  return element;
}

//quick version that does not extend
function firstChildElement(element) {
	var el = element.firstChild;
	while((el != null)&&(el.nodeType != 1)) {
		el = el.nextSibling;
	}
	return el;
}

//quick version that does not extend
function nextSiblingElement(element) {
	var el = element.nextSibling;
	while((el != null)&&(el.nodeType != 1)) {
		el = el.nextSibling;
	}
	return el;
}

//quick version that does not extend
function prevSiblingElement(element) {
	var el = element.previousSibling;
	while((el != null)&&(el.nodeType != 1)) {
		el = el.previousSibling;
	}
	return el;
}





function hashCopy(src) {
	var dst = {};
	for(k in src) {
		dst[k] = src[k];
	}
	return dst;
}

function hashSize(hash) {
	var cnt = 0;
	for(k in hash) {
		cnt++;
	}
	return cnt;
}

function hashEmpty(hash) {
	var cnt = 0;
	for(k in hash) {
		return false;
	}
	return true;
}

function hashClearEmpty(src) {
	var dst = {};
	for(k in src) {
		if((src[k]!=null)&&(src[k] != "")) {
			dst[k] = src[k];
		}
	}
	return dst;
}


var debug = null;

function log(msg, trace) {
	//if(BrowserDetect.browser == "Firefox") {
		try {
			console.debug(msg);
			if(trace) {
				console.trace();
			}
		} catch(e) {}
	//}
	if((debug!=null)&&(debug.style.display != 'none')) {
		debug.value = msg + "\n" + debug.value;
	}
	/* else if(debug==null) {
		alert("no debug: " + msg);
	} else {
		alert(debug.style.display);
	}
	*/
}

function imageRollover(){
	var a=document.getElementsByTagName('li');
	for(x=0; x<a.length; x++){
		if(a[x].className=="display"){
			var b=a[x].getElementsByTagName('a');
			b[0].onmouseover=function(){ 
				var c=this.getElementsByTagName('img');
				if(c.length==2){
					c[0].style.display="none";
					c[1].style.display="inline";
				}
			}
			b[0].onmouseout=function(){
				var c=this.getElementsByTagName('img');
				if(c.length==2){
					c[1].style.display="none";
					c[0].style.display="inline";
				}
			}
		}
	}
}


// monitors whether changes made to the current page
var page_dirty = false;

function setPageDirty() {
  page_dirty = true;
}

function isPageDirty() {
  return page_dirty;
}

function pageDirtyCheck() {
  if (isPageDirty()) {
    return confirm("Changes are not saved. Do you still want to leave this page?");
  }
}


// prevent double click submit button
function stopDoubleClick(timeout) {
  var f;
  var d = document.createElement('DIV');
  d.style.height = document.body.offsetHeight + 'px';
  d.style.width = document.body.offsetWidth + 'px';
  d.style.top = d.style.left = 0;
  d.className = "screen_guard";
  d.id = "screen_guard";
  d.style.display = "none";
  document.body.appendChild(d);
  
  for (var i=0; i<document.forms.length; i++) {
    document.forms[i].onkeypress = "return false;";
  }
  $(d).show();
  setTimeout("disableSubmit();", 10);
  if (timeout != 0) setTimeout("enableForms();", timeout);
  
  return true;
}

function disableSubmit() {
  for (var i=0; i<document.forms.length; i++) {
    for (var j=0; j<document.forms[i].elements.length; j++) {
      if (document.forms[i].elements[j].type == "submit") {
        document.forms[i].elements[j].disabled = "disabled";
      }
    }
  }
}

function enableForms() {
  var sg = $("screen_guard")
  if (sg != null) sg.remove();
  
  for (var i=0; i<document.forms.length; i++) {
    document.forms[i].onkeypress = "";
    for (var j=0; j<document.forms[i].elements.length; j++) {
      if (document.forms[i].elements[j].type == "submit") {
        document.forms[i].elements[j].disabled = "";
      }
    }
  }
}


var MarketBannerClass=Class.create({
	initialize: function(container){
	  
	  
		this.cell=container.getElementsBySelector("#m_wrapper .m_cell");
		this.button=container.getElementsBySelector("#m_controls a");
		this.curr=0;
		
		for(x=0; x<this.cell.length; x++){
			this.show_option=this.show.bindAsEventListener(this, x);
			Event.observe(this.button[x], "click", this.show_option);
		}
		
		this.starttime();
	}, 
	show: function(e, num){
		this.cleartime();
		this.clear();
		this.curr=num;
		this.cell[this.curr].setStyle({
				//"opacity" : 0,
				"display" : "block"
		});
		//new Effect.Appear(this.cell[this.curr], { from: 0, to: 1 });
		this.cell[this.curr].addClassName('alt');
		this.button[this.curr].addClassName('alt');
		this.starttime();
	},
	clear: function(){
		//new Effect.Fade(this.cell[this.curr]);
		this.cell[this.curr].removeClassName('alt');
		this.button[this.curr].removeClassName('alt');
	}, 
	job: function(){
		int_curr=this.curr+1;
		if(int_curr>=this.cell.length){
			int_curr=0;
		}
		this.show(1, int_curr);
		//this.starttime();
	},
	starttime: function(){
		if(this.cell.length>0){
			this.timeout = window.setTimeout(this.job.bind(this), 5000);
		}
	},
	cleartime: function(){
		clearTimeout(this.timeout);
	}
});


// script.aculo.us effects.js v1.7.1_beta2, Sat Apr 28 15:20:12 CEST 2007

// 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) { }
};

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

Array.prototype.call = function() {
  var args = arguments;
  this.each(function(f){ f.apply(this, args) });
}

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

var Effect = {
  _elementDoesNotExistError: {
    name: 'ElementDoesNotExistError',
    message: 'The specified DOM element does not exist, but is required for this effect to operate'
  },
  tagifyText: function(element) {
    if(typeof Builder == 'undefined')
      throw("Effect.tagifyText requires including script.aculo.us' builder.js library");
      
    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(
            Builder.node('span',{style: tagifyStyle},
              character == ' ' ? String.fromCharCode(160) : character), 
              child);
        });
        Element.remove(child);
      }
    });
  },
  multiple: function(element, effect) {
    var elements;
    if(((typeof element == 'object') || 
        (typeof element == 'function')) && 
       (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);
  }
};

var Effect2 = Effect; // deprecated

/* ------------- transitions ------------- */

Effect.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 (
      Math.round((pos % (1/pulses)) * pulses) == 0 ? 
            ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) : 
        1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2))
      );
  },
  none: function(pos) {
    return 0;
  },
  full: function(pos) {
    return 1;
  }
};

/* ------------- core effects ------------- */

Effect.ScopedQueue = Class.create();
Object.extend(Object.extend(Effect.ScopedQueue.prototype, 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 = (typeof effect.options.queue == 'string') ? 
      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(typeof queueName != 'string') return queueName;
    
    if(!this.instances[queueName])
      this.instances[queueName] = new Effect.ScopedQueue();
      
    return this.instances[queueName];
  }
}
Effect.Queue = Effect.Queues.get('global');

Effect.DefaultOptions = {
  transition: Effect.Transitions.sinoidal,
  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'
}

Effect.Base = function() {};
Effect.Base.prototype = {
  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.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(options,'beforeSetup')+
      (this.setup ? 'this.setup();':'')+ 
      codeForEvent(options,'afterSetup')+
      '};if(this.state=="running"){'+
      'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
      'this.position=pos;'+
      codeForEvent(options,'beforeUpdate')+
      (this.update ? 'this.update(pos);':'')+
      codeForEvent(options,'afterUpdate')+
      '}}');
    
    this.event('beforeStart');
    if(!this.options.sync)
      Effect.Queues.get(typeof this.options.queue == 'string' ? 
        '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 = Math.round(pos * this.totalFrames);
      if(frame > this.currentFrame) {
        this.render(pos);
        this.currentFrame = frame;
      }
    }
  },
  cancel: function() {
    if(!this.options.sync)
      Effect.Queues.get(typeof this.options.queue == 'string' ? 
        '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(typeof this[property] != 'function') data[property] = this[property];
    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
  }
}

Effect.Parallel = Class.create();
Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
  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.Event = Class.create();
Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), {
  initialize: function() {
    var options = Object.extend({
      duration: 0
    }, arguments[0] || {});
    this.start(options);
  },
  update: Prototype.emptyFunction
});

Effect.Opacity = Class.create();
Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
  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();
Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
  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() {
    // Bug in Opera: Opera returns the "real" position of a static element or
    // relative element that does not have top/left explicitly set.
    // ==> Always set top and left for position relative elements in your stylesheets 
    // (to 0 if you do not need them) 
    this.element.makePositioned();
    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
    if(this.options.mode == 'absolute') {
      // absolute movement, so we need to calc deltaX and deltaY
      this.options.x = this.options.x - this.originalLeft;
      this.options.y = this.options.y - this.originalTop;
    }
  },
  update: function(position) {
    this.element.setStyle({
      left: Math.round(this.options.x  * position + this.originalLeft) + 'px',
      top:  Math.round(this.options.y  * position + this.originalTop)  + '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();
Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
  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 = Math.round(width) + 'px';
    if(this.options.scaleY) d.height = Math.round(height) + '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();
Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
  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+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
  },
  finish: function() {
    this.element.setStyle(Object.extend(this.oldStyle, {
      backgroundColor: this.options.restorecolor
    }));
  }
});

Effect.ScrollTo = Class.create();
Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    this.start(arguments[1] || {});
  },
  setup: function() {
    Position.prepare();
    var offsets = Position.cumulativeOffset(this.element);
    if(this.options.offset) offsets[1] += this.options.offset;
    var max = window.innerHeight ? 
      window.height - window.innerHeight :
      document.body.scrollHeight - 
        (document.documentElement.clientHeight ? 
          document.documentElement.clientHeight : document.body.clientHeight);
    this.scrollStart = Position.deltaY;
    this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
  },
  update: function(position) {
    Position.prepare();
    window.scrollTo(Position.deltaX, 
      this.scrollStart + (position*this.delta));
  }
});

/* ------------- 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 oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left') };
    return new Effect.Move(element, 
      { x:  20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -20, y: 0, duration: 0.05, 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');
  return new Effect.Scale(element, window.opera ? 0 : 1,
   Object.extend({ scaleContent: false, 
    scaleX: false, 
    scaleMode: 'box',
    scaleFrom: 100,
    restoreAfterFinish: true,
    beforeStartInternal: 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().setStyle({bottom: oldInnerBottom});
      effect.element.down().undoPositioned();
    }
   }, 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();
Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    if(!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      style: {}
    }, arguments[1] || {});
    if (typeof options.style == 'string') {
      if(options.style.indexOf(':') == -1) {
        var cssText = '', selector = '.' + options.style;
        $A(document.styleSheets).reverse().each(function(styleSheet) {
          if (styleSheet.cssRules) cssRules = styleSheet.cssRules;
          else if (styleSheet.rules) cssRules = styleSheet.rules;
          $A(cssRules).reverse().each(function(rule) {
            if (selector == rule.selectorText) {
              cssText = rule.style.cssText;
              throw $break;
            }
          });
          if (cssText) throw $break;
        });
        this.style = cssText.parseStyle();
        options.afterFinishInternal = function(effect){
          effect.element.addClassName(effect.options.style);
          effect.transforms.each(function(transform) {
            if(transform.style != 'opacity')
              effect.element.style[transform.style] = '';
          });
        }
      } else this.style = options.style.parseStyle();
    } else this.style = $H(options.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 + Math.round(
          ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit;
    this.element.setStyle(style, true);
  }
});

Effect.Transform = Class.create();
Object.extend(Effect.Transform.prototype, {
  initialize: function(tracks){
    this.tracks  = [];
    this.options = arguments[1] || {};
    this.addTracks(tracks);
  },
  addTracks: function(tracks){
    tracks.each(function(track){
      var data = $H(track).values().first();
      this.tracks.push($H({
        ids:     $H(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 elements = [$(track.ids) || $$(track.ids)].flatten();
        return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.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.prototype.parseStyle = function(){
  var element = document.createElement('div');
  element.innerHTML = '<div style="' + this + '"></div>';
  var style = element.childNodes[0].style, styleRules = $H();
  
  Element.CSS_PROPERTIES.each(function(property){
    if(style[property]) styleRules[property] = style[property]; 
  });
  if(Prototype.Browser.IE && this.indexOf('opacity') > -1) {
    styleRules.opacity = this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1];
  }
  return styleRules;
};

Element.morph = function(element, style) {
  new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {}));
  return element;
};

['getInlineOpacity','forceRerendering','setContentZoom',
 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each( 
  function(f) { Element.Methods[f] = Element[f]; }
);

Element.Methods.visualEffect = function(element, effect, options) {
  s = effect.dasherize().camelize();
  effect_class = s.charAt(0).toUpperCase() + s.substring(1);
  new Effect[effect_class](element, options);
  return $(element);
};

Element.addMethods();

/** 
 * Easing Equations for Script.aculo.us
 * @author Brian Crescimanno <brian.crescimanno@gmail.com>
 * @version 0.8.1
 * @revised November 20, 2008
 * @copyright 2008 Brian Crescimanno, all rights reserved
 *
 * Released under terms of the BSD License
 * http://www.opensource.org/licenses/bsd-license.php
 *
 * The math for these equations was created by Robert Penner
 * http://www.robertpenner.com/profmx
 * 
 * -----------------------------------------------------------------------------------------
 * Do not remove any comments above this line, below comments may be removed to save space.
 *
 * An adaptation of Robert Penner's "easing equations" as seen in many Flash animations for 
 * Script.aculo.us 1.8.  One of my great pains in working with Script.aculo.us over other
 * libraries was the lack of these easing equations so I set about to port as many of the
 * equations as I could. 
 *
 * Imlements from Penner's equations:
 * 		Quadratic
 * 		Cubic
 * 		Quartic
 * 		Quintic
 * 		Sinusoidal
 *		Exponential
 *		Circular
 * 		Bounce (easeOut only)
 * 		Back
 *
 * Does not implement (yet)
 * 		Elastic		
 *		Bounce (easeIn, easeInOut)
 *
 * Ken Snyder provided a few reference implementations of Penner equations for 
 * Script.aculo.us; these reference implementations aided my work in porting a
 * (more) complete set.
 *
 */
 
 
/****** Quadratic ******/

Effect.Transitions.easeInQuad = function(pos){
	return Math.pow(pos, 2);
}

Effect.Transitions.easeOutQuad = function(pos){
	return -(Math.pow((pos-1), 2) -1);
}

Effect.Transitions.easeInOutQuad = function(pos){
	if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,2);
    return -0.5 * ((pos-=2)*pos - 2); 
}


/****** Cubic ******/

Effect.Transitions.easeInCubic = function(pos){
	return Math.pow(pos, 3);
}

Effect.Transitions.easeOutCubic = function(pos){
	return (Math.pow((pos-1), 3) +1);
}

Effect.Transitions.easeInOutCubic = function(pos){
	if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,3);
    return 0.5 * (Math.pow((pos-2),3) + 2); 	
}


/****** Quartic ******/

Effect.Transitions.easeInQuart = function(pos){
	return Math.pow(pos, 4);
}

Effect.Transitions.easeOutQuart = function(pos){
	return -(Math.pow((pos-1), 4) -1)
}

Effect.Transitions.easeInOutQuart = function(pos){
	if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,4);
    return -0.5 * ((pos-=2)*Math.pow(pos,3) - 2); 
}


/****** Quintic ******/

Effect.Transitions.easeInQuint = function(pos){
	return Math.pow(pos, 5);
}

Effect.Transitions.easeOutQuint = function(pos){
	return (Math.pow((pos-1), 5) +1);
}

Effect.Transitions.easeInOutQuint = function(pos){
	if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,5);
    return 0.5 * (Math.pow((pos-2),5) + 2); 	
}


/****** Sinusoidal ******/

Effect.Transitions.easeInSine = function(pos){
	return -Math.cos(pos * (Math.PI/2)) + 1;
}

Effect.Transitions.easeOutSine = function(pos){
	return Math.sin(pos * (Math.PI/2));
}

Effect.Transitions.easeInOutSine = function(pos){
	return (-.5 * (Math.cos(Math.PI*pos) -1));
}


/****** Exponential ******/

Effect.Transitions.easeInExpo = function(pos){
	return (pos==0) ? 0 : Math.pow(2, 10 * (pos - 1));
}

Effect.Transitions.easeOutExpo = function(pos){
	return (pos==1) ? 1 : -Math.pow(2, -10 * pos) + 1;
}

Effect.Transitions.easeInOutExpo = function(pos){
	if(pos==0) return 0;
	if(pos==1) return 1;
	if((pos/=0.5) < 1) return 0.5 * Math.pow(2,10 * (pos-1));
	return 0.5 * (-Math.pow(2, -10 * --pos) + 2);	
}


/****** Circular ******/

Effect.Transitions.easeInCirc = function(pos){
	return -(Math.sqrt(1 - (pos*pos)) - 1);
}

Effect.Transitions.easeOutCirc = function(pos){
	return Math.sqrt(1 - Math.pow((pos-1), 2))
}

Effect.Transitions.easeInOutCirc = function(pos){
	if((pos/=0.5) < 1) return -0.5 * (Math.sqrt(1 - pos*pos) - 1);
	return 0.5 * (Math.sqrt(1 - (pos-=2)*pos) + 1);	
}


/****** Bounce ******/

Effect.Transitions.easeInBounce = function(pos){
	return 1;
}

Effect.Transitions.easeOutBounce = function(pos){
	if ((pos) < (1/2.75)) {
		return (7.5625*pos*pos);
	} else if (pos < (2/2.75)) {
		return (7.5625*(pos-=(1.5/2.75))*pos + .75);
	} else if (pos < (2.5/2.75)) {
		return (7.5625*(pos-=(2.25/2.75))*pos + .9375);
	} else {
		return (7.5625*(pos-=(2.625/2.75))*pos + .984375);
	}
}

Effect.Transitions.easeInOutBounce = function(pos){
	return 1;
}

/****** Back ******/

Effect.Transitions.easeInBack = function(pos){
	var s = 1.70158;	
	return (pos)*pos*((s+1)*pos - s);
}

Effect.Transitions.easeOutBack = function(pos){
	var s = 1.70158;	
	return (pos=pos-1)*pos*((s+1)*pos + s) + 1;
}

Effect.Transitions.easeInOutBack = function(pos){
	var s = 1.70158;	
	if((pos/=0.5) < 1) return 0.5*(pos*pos*(((s*=(1.525))+1)*pos -s));
	return 0.5*((pos-=2)*pos*(((s*=(1.525))+1)*pos +s) +2);
}

/****** Elastic ******/

Effect.Transitions.easeInElastic = function(pos){
	return 1;
}

Effect.Transitions.easeOutElastic = function(pos){
	return 1;
}

Effect.Transitions.easeInOutElastic = function(pos){
	return 1;
}



// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//           (c) 2005, 2006 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
//           (c) 2005, 2006 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 = function() {};
Autocompleter.Base.prototype = {
  baseInitialize: function(element, update, options) {
    this.element     = $(element); 
    this.update      = $(update);  
    this.hasFocus    = false; 
    this.changed     = false; 
    this.active      = false; 
    this.index       = 0;     
    this.entryCount  = 0;

    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);

    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, "keypress", this.onKeyPress.bindAsEventListener(this));
  },

  show: function() {
    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
    if(!this.iefix && 
      (navigator.appVersion.indexOf('MSIE')>0) &&
      (navigator.userAgent.indexOf('Opera')<0) &&
      (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);" ' +
       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
      this.iefix = $(this.update.id+'_iefix');
    }
    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
  },
  
  fixIEOverlapping: function() {
    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
    this.iefix.style.zIndex = 1;
    this.update.style.zIndex = 2;
    Element.show(this.iefix);
  },

  hide: function() {
    this.stopIndicator();
    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
    if(this.iefix) Element.hide(this.iefix);
  },

  startIndicator: function() {
    if(this.options.indicator) Element.show(this.options.indicator);
  },

  stopIndicator: function() {
    if(this.options.indicator) Element.hide(this.options.indicator);
  },

  onKeyPress: function(event) {
    if(this.active)
      switch(event.keyCode) {
       case Event.KEY_TAB:
       case Event.KEY_RETURN:
         this.selectEntry();
         Event.stop(event);
       case Event.KEY_ESC:
         this.hide();
         this.active = false;
         Event.stop(event);
         return;
       case Event.KEY_LEFT:
       case Event.KEY_RIGHT:
         return;
       case Event.KEY_UP:
         this.markPrevious();
         this.render();
         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
         return;
       case Event.KEY_DOWN:
         this.markNext();
         this.render();
         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
         return;
      }
     else 
       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
         (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;

    this.changed = true;
    this.hasFocus = true;

    if(this.observer) clearTimeout(this.observer);
      this.observer = 
        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
  },

  activate: function() {
    this.changed = false;
    this.hasFocus = true;
    this.getUpdatedChoices();
  },

  onHover: function(event) {
    var element = Event.findElement(event, 'LI');
    if(this.index != element.autocompleteIndex) 
    {
        this.index = element.autocompleteIndex;
        this.render();
    }
    Event.stop(event);
  },
  
  onClick: function(event) {
    var element = Event.findElement(event, 'LI');
    this.index = element.autocompleteIndex;
    this.selectEntry();
    this.hide();
  },
  
  onBlur: function(event) {
    // needed to make click events working
    setTimeout(this.hide.bind(this), 250);
    this.hasFocus = false;
    this.active = false;     
  }, 
  
  render: function() {
    if(this.entryCount > 0) {
      for (var i = 0; i < this.entryCount; i++)
        this.index==i ? 
          Element.addClassName(this.getEntry(i),"selected") : 
          Element.removeClassName(this.getEntry(i),"selected");
        
      if(this.hasFocus) { 
        this.show();
        this.active = true;
      }
    } else {
      this.active = false;
      this.hide();
    }
  },
  
  markPrevious: function() {
    if(this.index > 0) this.index--
      else this.index = this.entryCount-1;
    this.getEntry(this.index).scrollIntoView(true);
  },
  
  markNext: function() {
    if(this.index < this.entryCount-1) this.index++
      else this.index = 0;
    this.getEntry(this.index).scrollIntoView(false);
  },
  
  getEntry: function(index) {
    return this.update.firstChild.childNodes[index];
  },
  
  getCurrentEntry: function() {
    return this.getEntry(this.index);
  },
  
  selectEntry: function() {
    this.active = false;
    this.updateElement(this.getCurrentEntry());
  },

  updateElement: function(selectedElement) {
    if (this.options.updateElement) {
      this.options.updateElement(selectedElement);
      return;
    }
    var value = '';
    if (this.options.select) {
      var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
    } else
      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
    
    var lastTokenPos = this.findLastToken();
    if (lastTokenPos != -1) {
      var newValue = this.element.value.substr(0, lastTokenPos + 1);
      var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
      if (whitespace)
        newValue += whitespace[0];
      this.element.value = newValue + value;
    } else {
      this.element.value = value;
    }
    this.element.focus();
    
    if (this.options.afterUpdateElement)
      this.options.afterUpdateElement(this.element, selectedElement);
  },

  updateChoices: function(choices) {
    if(!this.changed && this.hasFocus) {
      this.update.innerHTML = choices;
      Element.cleanWhitespace(this.update);
      Element.cleanWhitespace(this.update.down());

      if(this.update.firstChild && this.update.down().childNodes) {
        this.entryCount = 
          this.update.down().childNodes.length;
        for (var i = 0; i < this.entryCount; i++) {
          var entry = this.getEntry(i);
          entry.autocompleteIndex = i;
          this.addObservers(entry);
        }
      } else { 
        this.entryCount = 0;
      }

      this.stopIndicator();
      this.index = 0;
      
      if(this.entryCount==1 && this.options.autoSelect) {
        this.selectEntry();
        this.hide();
      } else {
        this.render();
      }
    }
  },

  addObservers: function(element) {
    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
  },

  onObserverEvent: function() {
    this.changed = false;   
    if(this.getToken().length>=this.options.minChars) {
      this.startIndicator();
      this.getUpdatedChoices();
    } else {
      this.active = false;
      this.hide();
    }
  },

  getToken: function() {
    var tokenPos = this.findLastToken();
    if (tokenPos != -1)
      var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
    else
      var ret = this.element.value;

    return /\n/.test(ret) ? '' : ret;
  },

  findLastToken: function() {
    var lastTokenPos = -1;

    for (var i=0; i<this.options.tokens.length; i++) {
      var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
      if (thisTokenPos > lastTokenPos)
        lastTokenPos = thisTokenPos;
    }
    return lastTokenPos;
  }
}

Ajax.Autocompleter = Class.create();
Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
  initialize: function(element, update, url, options) {
    this.baseInitialize(element, update, options);
    this.options.asynchronous  = true;
    this.options.onComplete    = this.onComplete.bind(this);
    this.options.defaultParams = this.options.parameters || null;
    this.url                   = url;
  },

  getUpdatedChoices: function() {
    entry = encodeURIComponent(this.options.paramName) + '=' + 
      encodeURIComponent(this.getToken());

    this.options.parameters = this.options.callback ?
      this.options.callback(this.element, entry) : entry;

    if(this.options.defaultParams) 
      this.options.parameters += '&' + this.options.defaultParams;

    new Ajax.Request(this.url, this.options);
  },

  onComplete: function(request) {
    this.updateChoices(request.responseText);
  }

});

// The local array autocompleter. Used when you'd prefer to
// inject an array of autocompletion options into the page, rather
// than sending out Ajax queries, which can be quite slow sometimes.
//
// The constructor takes four parameters. The first two are, as usual,
// the id of the monitored textbox, and id of the autocompletion menu.
// The third is the array you want to autocomplete from, and the fourth
// is the options block.
//
// Extra local autocompletion options:
// - choices - How many autocompletion choices to offer
//
// - partialSearch - If false, the autocompleter will match entered
//                    text only at the beginning of strings in the 
//                    autocomplete array. Defaults to true, which will
//                    match text at the beginning of any *word* in the
//                    strings in the autocomplete array. If you want to
//                    search anywhere in the string, additionally set
//                    the option fullSearch to true (default: off).
//
// - fullSsearch - Search anywhere in autocomplete array strings.
//
// - partialChars - How many characters to enter before triggering
//                   a partial match (unlike minChars, which defines
//                   how many characters are required to do any match
//                   at all). Defaults to 2.
//
// - ignoreCase - Whether to ignore case when autocompleting.
//                 Defaults to true.
//
// It's possible to pass in a custom function as the 'selector' 
// option, if you prefer to write your own autocompletion logic.
// In that case, the other options above will not apply unless
// you support them.

Autocompleter.Local = Class.create();
Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
  initialize: function(element, update, array, options) {
    this.baseInitialize(element, update, options);
    this.options.array = array;
  },

  getUpdatedChoices: function() {
    this.updateChoices(this.options.selector(this));
  },

  setOptions: function(options) {
    this.options = Object.extend({
      choices: 10,
      partialSearch: true,
      partialChars: 2,
      ignoreCase: true,
      fullSearch: false,
      selector: function(instance) {
        var ret       = []; // Beginning matches
        var partial   = []; // Inside matches
        var entry     = instance.getToken();
        var count     = 0;

        for (var i = 0; i < instance.options.array.length &&  
          ret.length < instance.options.choices ; i++) { 

          var elem = instance.options.array[i];
          var foundPos = instance.options.ignoreCase ? 
            elem.toLowerCase().indexOf(entry.toLowerCase()) : 
            elem.indexOf(entry);

          while (foundPos != -1) {
            if (foundPos == 0 && elem.length != entry.length) { 
              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 
                elem.substr(entry.length) + "</li>");
              break;
            } else if (entry.length >= instance.options.partialChars && 
              instance.options.partialSearch && foundPos != -1) {
              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
                  foundPos + entry.length) + "</li>");
                break;
              }
            }

            foundPos = instance.options.ignoreCase ? 
              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
              elem.indexOf(entry, foundPos + 1);

          }
        }
        if (partial.length)
          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
        return "<ul>" + ret.join('') + "</ul>";
      }
    }, options || {});
  }
});

// AJAX in-place editor
//
// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor

// Use this if you notice weird scrolling problems on some browsers,
// the DOM might be a bit confused when this gets called so do this
// waits 1 ms (with setTimeout) until it does the activation
Field.scrollFreeActivate = function(field) {
  setTimeout(function() {
    Field.activate(field);
  }, 1);
}

Ajax.InPlaceEditor = Class.create();
Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
Ajax.InPlaceEditor.prototype = {
  initialize: function(element, url, options) {
    this.url = url;
    this.element = $(element);

    this.options = Object.extend({
      paramName: "value",
      okButton: true,
      okText: "ok",
      cancelLink: true,
      cancelText: "cancel",
      savingText: "Saving...",
      clickToEditText: "Click to edit",
      okText: "ok",
      rows: 1,
      onComplete: function(transport, element) {
        new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
      },
      onFailure: function(transport) {
        alert("Error communicating with the server: " + transport.responseText.stripTags());
      },
      callback: function(form) {
        return Form.serialize(form);
      },
      handleLineBreaks: true,
      loadingText: 'Loading...',
      savingClassName: 'inplaceeditor-saving',
      loadingClassName: 'inplaceeditor-loading',
      formClassName: 'inplaceeditor-form',
      highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
      highlightendcolor: "#FFFFFF",
      externalControl: null,
      submitOnBlur: false,
      ajaxOptions: {},
      evalScripts: false
    }, options || {});

    if(!this.options.formId && this.element.id) {
      this.options.formId = this.element.id + "-inplaceeditor";
      if ($(this.options.formId)) {
        // there's already a form with that name, don't specify an id
        this.options.formId = null;
      }
    }
    
    if (this.options.externalControl) {
      this.options.externalControl = $(this.options.externalControl);
    }
    
    this.originalBackground = Element.getStyle(this.element, 'background-color');
    if (!this.originalBackground) {
      this.originalBackground = "transparent";
    }
    
    this.element.title = this.options.clickToEditText;
    
    this.onclickListener = this.enterEditMode.bindAsEventListener(this);
    this.mouseoverListener = this.enterHover.bindAsEventListener(this);
    this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
    Event.observe(this.element, 'click', this.onclickListener);
    Event.observe(this.element, 'mouseover', this.mouseoverListener);
    Event.observe(this.element, 'mouseout', this.mouseoutListener);
    if (this.options.externalControl) {
      Event.observe(this.options.externalControl, 'click', this.onclickListener);
      Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
      Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
    }
  },
  enterEditMode: function(evt) {
    if (this.saving) return;
    if (this.editing) return;
    this.editing = true;
    this.onEnterEditMode();
    if (this.options.externalControl) {
      Element.hide(this.options.externalControl);
    }
    Element.hide(this.element);
    this.createForm();
    this.element.parentNode.insertBefore(this.form, this.element);
    if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField);
    // stop the event to avoid a page refresh in Safari
    if (evt) {
      Event.stop(evt);
    }
    return false;
  },
  createForm: function() {
    this.form = document.createElement("form");
    this.form.id = this.options.formId;
    Element.addClassName(this.form, this.options.formClassName)
    this.form.onsubmit = this.onSubmit.bind(this);

    this.createEditField();

    if (this.options.textarea) {
      var br = document.createElement("br");
      this.form.appendChild(br);
    }

    if (this.options.okButton) {
      okButton = document.createElement("input");
      okButton.type = "submit";
      okButton.value = this.options.okText;
      okButton.className = 'editor_ok_button';
      this.form.appendChild(okButton);
    }

    if (this.options.cancelLink) {
      cancelLink = document.createElement("a");
      cancelLink.href = "#";
      cancelLink.appendChild(document.createTextNode(this.options.cancelText));
      cancelLink.onclick = this.onclickCancel.bind(this);
      cancelLink.className = 'editor_cancel';      
      this.form.appendChild(cancelLink);
    }
  },
  hasHTMLLineBreaks: function(string) {
    if (!this.options.handleLineBreaks) return false;
    return string.match(/<br/i) || string.match(/<p>/i);
  },
  convertHTMLLineBreaks: function(string) {
    return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
  },
  createEditField: function() {
    var text;
    if(this.options.loadTextURL) {
      text = this.options.loadingText;
    } else {
      text = this.getText();
    }

    var obj = this;
    
    if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
      this.options.textarea = false;
      var textField = document.createElement("input");
      textField.obj = this;
      textField.type = "text";
      textField.name = this.options.paramName;
      textField.value = text;
      textField.style.backgroundColor = this.options.highlightcolor;
      textField.className = 'editor_field';
      var size = this.options.size || this.options.cols || 0;
      if (size != 0) textField.size = size;
      if (this.options.submitOnBlur)
        textField.onblur = this.onSubmit.bind(this);
      this.editField = textField;
    } else {
      this.options.textarea = true;
      var textArea = document.createElement("textarea");
      textArea.obj = this;
      textArea.name = this.options.paramName;
      textArea.value = this.convertHTMLLineBreaks(text);
      textArea.rows = this.options.rows;
      textArea.cols = this.options.cols || 40;
      textArea.className = 'editor_field';      
      if (this.options.submitOnBlur)
        textArea.onblur = this.onSubmit.bind(this);
      this.editField = textArea;
    }
    
    if(this.options.loadTextURL) {
      this.loadExternalText();
    }
    this.form.appendChild(this.editField);
  },
  getText: function() {
    return this.element.innerHTML;
  },
  loadExternalText: function() {
    Element.addClassName(this.form, this.options.loadingClassName);
    this.editField.disabled = true;
    new Ajax.Request(
      this.options.loadTextURL,
      Object.extend({
        asynchronous: true,
        onComplete: this.onLoadedExternalText.bind(this)
      }, this.options.ajaxOptions)
    );
  },
  onLoadedExternalText: function(transport) {
    Element.removeClassName(this.form, this.options.loadingClassName);
    this.editField.disabled = false;
    this.editField.value = transport.responseText.stripTags();
    Field.scrollFreeActivate(this.editField);
  },
  onclickCancel: function() {
    this.onComplete();
    this.leaveEditMode();
    return false;
  },
  onFailure: function(transport) {
    this.options.onFailure(transport);
    if (this.oldInnerHTML) {
      this.element.innerHTML = this.oldInnerHTML;
      this.oldInnerHTML = null;
    }
    return false;
  },
  onSubmit: function() {
    // onLoading resets these so we need to save them away for the Ajax call
    var form = this.form;
    var value = this.editField.value;
    
    // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
    // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
    // to be displayed indefinitely
    this.onLoading();
    
    if (this.options.evalScripts) {
      new Ajax.Request(
        this.url, Object.extend({
          parameters: this.options.callback(form, value),
          onComplete: this.onComplete.bind(this),
          onFailure: this.onFailure.bind(this),
          asynchronous:true, 
          evalScripts:true
        }, this.options.ajaxOptions));
    } else  {
      new Ajax.Updater(
        { success: this.element,
          // don't update on failure (this could be an option)
          failure: null }, 
        this.url, Object.extend({
          parameters: this.options.callback(form, value),
          onComplete: this.onComplete.bind(this),
          onFailure: this.onFailure.bind(this)
        }, this.options.ajaxOptions));
    }
    // stop the event to avoid a page refresh in Safari
    if (arguments.length > 1) {
      Event.stop(arguments[0]);
    }
    return false;
  },
  onLoading: function() {
    this.saving = true;
    this.removeForm();
    this.leaveHover();
    this.showSaving();
  },
  showSaving: function() {
    this.oldInnerHTML = this.element.innerHTML;
    this.element.innerHTML = this.options.savingText;
    Element.addClassName(this.element, this.options.savingClassName);
    this.element.style.backgroundColor = this.originalBackground;
    Element.show(this.element);
  },
  removeForm: function() {
    if(this.form) {
      if (this.form.parentNode) Element.remove(this.form);
      this.form = null;
    }
  },
  enterHover: function() {
    if (this.saving) return;
    this.element.style.backgroundColor = this.options.highlightcolor;
    if (this.effect) {
      this.effect.cancel();
    }
    Element.addClassName(this.element, this.options.hoverClassName)
  },
  leaveHover: function() {
    if (this.options.backgroundColor) {
      this.element.style.backgroundColor = this.oldBackground;
    }
    Element.removeClassName(this.element, this.options.hoverClassName)
    if (this.saving) return;
    this.effect = new Effect.Highlight(this.element, {
      startcolor: this.options.highlightcolor,
      endcolor: this.options.highlightendcolor,
      restorecolor: this.originalBackground
    });
  },
  leaveEditMode: function() {
    Element.removeClassName(this.element, this.options.savingClassName);
    this.removeForm();
    this.leaveHover();
    this.element.style.backgroundColor = this.originalBackground;
    Element.show(this.element);
    if (this.options.externalControl) {
      Element.show(this.options.externalControl);
    }
    this.editing = false;
    this.saving = false;
    this.oldInnerHTML = null;
    this.onLeaveEditMode();
  },
  onComplete: function(transport) {
    this.leaveEditMode();
    this.options.onComplete.bind(this)(transport, this.element);
  },
  onEnterEditMode: function() {},
  onLeaveEditMode: function() {},
  dispose: function() {
    if (this.oldInnerHTML) {
      this.element.innerHTML = this.oldInnerHTML;
    }
    this.leaveEditMode();
    Event.stopObserving(this.element, 'click', this.onclickListener);
    Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
    Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
    if (this.options.externalControl) {
      Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
      Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
      Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
    }
  }
};

Ajax.InPlaceCollectionEditor = Class.create();
Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
  createEditField: function() {
    if (!this.cached_selectTag) {
      var selectTag = document.createElement("select");
      var collection = this.options.collection || [];
      var optionTag;
      collection.each(function(e,i) {
        optionTag = document.createElement("option");
        optionTag.value = (e instanceof Array) ? e[0] : e;
        if((typeof this.options.value == 'undefined') && 
          ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true;
        if(this.options.value==optionTag.value) optionTag.selected = true;
        optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
        selectTag.appendChild(optionTag);
      }.bind(this));
      this.cached_selectTag = selectTag;
    }

    this.editField = this.cached_selectTag;
    if(this.options.loadTextURL) this.loadExternalText();
    this.form.appendChild(this.editField);
    this.options.callback = function(form, value) {
      return "value=" + encodeURIComponent(value);
    }
  }
});

// Delayed observer, like Form.Element.Observer, 
// but waits for delay after last key input
// Ideal for live-search fields

Form.Element.DelayedObserver = Class.create();
Form.Element.DelayedObserver.prototype = {
  initialize: function(element, delay, callback) {
    this.delay     = delay || 0.5;
    this.element   = $(element);
    this.callback  = callback;
    this.timer     = null;
    this.lastValue = $F(this.element); 
    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
  },
  delayedListener: function(event) {
    if(this.lastValue == $F(this.element)) return;
    if(this.timer) clearTimeout(this.timer);
    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
    this.lastValue = $F(this.element);
  },
  onTimerEvent: function() {
    this.timer = null;
    this.callback(this.element, $F(this.element));
  }
};

// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//           (c) 2005, 2006 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
// 
// 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/

if(typeof Effect == 'undefined')
  throw("dragdrop.js requires including script.aculo.us' effects.js library");

var Droppables = {
  drops: [],

  remove: function(element) {
    this.drops = this.drops.reject(function(d) { return d.element==$(element) });
  },

  add: function(element) {
    element = $(element);
    var options = Object.extend({
      greedy:     true,
      hoverclass: null,
      tree:       false
    }, arguments[1] || {});

    // cache containers
    if(options.containment) {
      options._containers = [];
      var containment = options.containment;
      if((typeof containment == 'object') && 
        (containment.constructor == Array)) {
        containment.each( function(c) { options._containers.push($(c)) });
      } else {
        options._containers.push($(containment));
      }
    }
    
    if(options.accept) options.accept = [options.accept].flatten();

    Element.makePositioned(element); // fix IE
    options.element = element;

    this.drops.push(options);
  },
  
  findDeepestChild: function(drops) {
    deepest = drops[0];
      
    for (i = 1; i < drops.length; ++i)
      if (Element.isParent(drops[i].element, deepest.element))
        deepest = drops[i];
    
    return deepest;
  },

  isContained: function(element, drop) {
    var containmentNode;
    if(drop.tree) {
      containmentNode = element.treeNode; 
    } else {
      containmentNode = element.parentNode;
    }
    return drop._containers.detect(function(c) { return containmentNode == c });
  },
  
  isAffected: function(point, element, drop) {
    return (
      (drop.element!=element) &&
      ((!drop._containers) ||
        this.isContained(element, drop)) &&
      ((!drop.accept) ||
        (Element.classNames(element).detect( 
          function(v) { return drop.accept.include(v) } ) )) &&
      Position.within(drop.element, point[0], point[1]) );
  },

  deactivate: function(drop) {
    if(drop.hoverclass)
      Element.removeClassName(drop.element, drop.hoverclass);
    this.last_active = null;
  },

  activate: function(drop) {
    if(drop.hoverclass)
      Element.addClassName(drop.element, drop.hoverclass);
    this.last_active = drop;
  },

  show: function(point, element) {
    if(!this.drops.length) return;
    var affected = [];
    
    if(this.last_active) this.deactivate(this.last_active);
    this.drops.each( function(drop) {
      if(Droppables.isAffected(point, element, drop))
        affected.push(drop);
    });
        
    if(affected.length>0) {
      drop = Droppables.findDeepestChild(affected);
      Position.within(drop.element, point[0], point[1]);
      if(drop.onHover)
        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
      
      Droppables.activate(drop);
    }
  },

  fire: function(event, element) {
    if(!this.last_active) return;
    Position.prepare();

    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
      if (this.last_active.onDrop) 
        this.last_active.onDrop(element, this.last_active.element, event);
  },

  reset: function() {
    if(this.last_active)
      this.deactivate(this.last_active);
  }
}

var Draggables = {
  drags: [],
  observers: [],
  
  register: function(draggable) {
    if(this.drags.length == 0) {
      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
      this.eventKeypress  = this.keyPress.bindAsEventListener(this);
      
      Event.observe(document, "mouseup", this.eventMouseUp);
      Event.observe(document, "mousemove", this.eventMouseMove);
      Event.observe(document, "keypress", this.eventKeypress);
    }
    this.drags.push(draggable);
  },
  
  unregister: function(draggable) {
    this.drags = this.drags.reject(function(d) { return d==draggable });
    if(this.drags.length == 0) {
      Event.stopObserving(document, "mouseup", this.eventMouseUp);
      Event.stopObserving(document, "mousemove", this.eventMouseMove);
      Event.stopObserving(document, "keypress", this.eventKeypress);
    }
  },
  
  activate: function(draggable) {
    if(draggable.options.delay) { 
      this._timeout = setTimeout(function() { 
        Draggables._timeout = null; 
        window.focus(); 
        Draggables.activeDraggable = draggable; 
      }.bind(this), draggable.options.delay); 
    } else {
      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
      this.activeDraggable = draggable;
    }
  },
  
  deactivate: function() {
    this.activeDraggable = null;
  },
  
  updateDrag: function(event) {
    if(!this.activeDraggable) return;
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
    // Mozilla-based browsers fire successive mousemove events with
    // the same coordinates, prevent needless redrawing (moz bug?)
    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
    this._lastPointer = pointer;
    
    this.activeDraggable.updateDrag(event, pointer);
  },
  
  endDrag: function(event) {
    if(this._timeout) { 
      clearTimeout(this._timeout); 
      this._timeout = null; 
    }
    if(!this.activeDraggable) return;
    this._lastPointer = null;
    this.activeDraggable.endDrag(event);
    this.activeDraggable = null;
  },
  
  keyPress: function(event) {
    if(this.activeDraggable)
      this.activeDraggable.keyPress(event);
  },
  
  addObserver: function(observer) {
    this.observers.push(observer);
    this._cacheObserverCallbacks();
  },
  
  removeObserver: function(element) {  // element instead of observer fixes mem leaks
    this.observers = this.observers.reject( function(o) { return o.element==element });
    this._cacheObserverCallbacks();
  },
  
  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
    if(this[eventName+'Count'] > 0)
      this.observers.each( function(o) {
        if(o[eventName]) o[eventName](eventName, draggable, event);
      });
    if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
  },
  
  _cacheObserverCallbacks: function() {
    ['onStart','onEnd','onDrag'].each( function(eventName) {
      Draggables[eventName+'Count'] = Draggables.observers.select(
        function(o) { return o[eventName]; }
      ).length;
    });
  }
}

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

var Draggable = Class.create();
Draggable._dragging    = {};

Draggable.prototype = {
  initialize: function(element) {

    var defaults = {
      handle: false,
      reverteffect: function(element, top_offset, left_offset) {
        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
          queue: {scope:'_draggable', position:'end'}
        });
      },
      endeffect: function(element) {
        var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0;
        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 
          queue: {scope:'_draggable', position:'end'},
          afterFinish: function(){ 
            Draggable._dragging[element] = false 
          }
        }); 
      },
      zindex: 1000,
      revert: false,
      scroll: false,
      scrollSensitivity: 20,
      scrollSpeed: 15,
      snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
      delay: 0
    };

    if(!arguments[1] || typeof arguments[1].endeffect == 'undefined')
      Object.extend(defaults, {
        starteffect: function(element) {
          element._opacity = Element.getOpacity(element);
          Draggable._dragging[element] = true;
          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 
        }
      });
    

    var options = Object.extend(defaults, arguments[1] || {});

    this.element = $(element);
    

    if(options.handle && (typeof options.handle == 'string'))
      this.handle = this.element.down('.'+options.handle, 0);
    
    if(!this.handle) this.handle = $(options.handle);
    if(!this.handle) this.handle = this.element;
    
    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
      options.scroll = $(options.scroll);
      this._isScrollChild = Element.childOf(this.element, options.scroll);
    }

    Element.makePositioned(this.element); // fix IE    

    this.delta    = this.currentDelta();
    this.options  = options;
    this.dragging = false;   

    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
    Event.observe(this.handle, "mousedown", this.eventMouseDown);
    
    Draggables.register(this);
  },
  
  destroy: function() {
    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
    Draggables.unregister(this);
  },
  
  currentDelta: function() {
    return([
      parseInt(Element.getStyle(this.element,'left') || '0'),
      parseInt(Element.getStyle(this.element,'top') || '0')]);
  },
  
  initDrag: function(event) {
    if(typeof Draggable._dragging[this.element] != 'undefined' &&
      Draggable._dragging[this.element]) return;
    if(Event.isLeftClick(event)) {    
      // abort on form elements, fixes a Firefox issue
      var src = Event.element(event);
      if(src.tagName && (
        src.tagName=='INPUT' ||
        src.tagName=='SELECT' ||
        src.tagName=='OPTION' ||
        src.tagName=='BUTTON' ||
        src.tagName=='TEXTAREA')) return;
        
      var pointer = [Event.pointerX(event), Event.pointerY(event)];
      var pos     = Position.cumulativeOffset(this.element);
      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
      
      Draggables.activate(this);
      Event.stop(event);
    }
  },
  
  startDrag: function(event) {
    this.dragging = true;
    
    if(this.options.zindex) {
      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
      this.element.style.zIndex = this.options.zindex;
    }
    
    if(this.options.ghosting) {
      //this._clone = this.element.cloneNode(true);
      //Position.absolutize(this.element);
      //this.element.parentNode.insertBefore(this._clone, this.element);
      if(this.options.dragelement){
        this._originalElement = this.element;
        this.element = this.options.dragelement(this.element, event);
        this.offset = [0,0]
      }
      else {
        this._clone = this.element.cloneNode(true);
        Position.absolutize(this.element);
        this.element.parentNode.insertBefore(this._clone, this.element);
      }
    }
    
    if(this.options.scroll) {
      if (this.options.scroll == window) {
        var where = this._getWindowScroll(this.options.scroll);
        this.originalScrollLeft = where.left;
        this.originalScrollTop = where.top;
      } else {
        this.originalScrollLeft = this.options.scroll.scrollLeft;
        this.originalScrollTop = this.options.scroll.scrollTop;
      }
    }
    
    Draggables.notify('onStart', this, event);
        
    if(this.options.starteffect) this.options.starteffect(this.element);
  },
  
  updateDrag: function(event, pointer) {
    if(!this.dragging) this.startDrag(event);
    Position.prepare();
    Droppables.show(pointer, this.element);
    Draggables.notify('onDrag', this, event);
    
    this.draw(pointer);
    if(this.options.change) this.options.change(this);
    
    if(this.options.scroll) {
      this.stopScrolling();
      
      var p;
      if (this.options.scroll == window) {
        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
      } else {
        p = Position.page(this.options.scroll);
        p[0] += this.options.scroll.scrollLeft + Position.deltaX;
        p[1] += this.options.scroll.scrollTop + Position.deltaY;
        p.push(p[0]+this.options.scroll.offsetWidth);
        p.push(p[1]+this.options.scroll.offsetHeight);
      }
      var speed = [0,0];
      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
      this.startScrolling(speed);
    }
    
    // fix AppleWebKit rendering
    if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
    
    Event.stop(event);
  },
  
  finishDrag: function(event, success) {
    this.dragging = false;

    if(this.options.ghosting) {
      //Position.relativize(this.element);
      //Element.remove(this._clone);
      //this._clone = null;
      if(this.options.dragelement){
        Element.remove(this.element);
        this.element = this._originalElement;
        this._originalElement = null;
      }
      else {
        Position.relativize(this.element);
        Element.remove(this._clone);
        this._clone = null;
      }
    }

    if(success) Droppables.fire(event, this.element);
    Draggables.notify('onEnd', this, event);

    var revert = this.options.revert;
    if(revert && typeof revert == 'function') revert = revert(this.element);
    
    var d = this.currentDelta();
    if(revert && this.options.reverteffect) {
      this.options.reverteffect(this.element, 
        d[1]-this.delta[1], d[0]-this.delta[0]);
    } else {
      this.delta = d;
    }

    if(this.options.zindex)
      this.element.style.zIndex = this.originalZ;

    if(this.options.endeffect) 
      this.options.endeffect(this.element);
      
    Draggables.deactivate(this);
    Droppables.reset();
  },
  
  keyPress: function(event) {
    if(event.keyCode!=Event.KEY_ESC) return;
    this.finishDrag(event, false);
    Event.stop(event);
  },
  
  endDrag: function(event) {
    if(!this.dragging) return;
    this.stopScrolling();
    this.finishDrag(event, true);
    Event.stop(event);
  },
  
  draw: function(point) {
    var pos = Position.cumulativeOffset(this.element);
    if(this.options.ghosting) {
      var r   = Position.realOffset(this.element);
      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
    }
    
    var d = this.currentDelta();
    pos[0] -= d[0]; pos[1] -= d[1];
    
    if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
    }
    
    var p = [0,1].map(function(i){ 
      return (point[i]-pos[i]-this.offset[i]) 
    }.bind(this));
    
    if(this.options.snap) {
      if(typeof this.options.snap == 'function') {
        p = this.options.snap(p[0],p[1],this);
      } else {
      if(this.options.snap instanceof Array) {
        p = p.map( function(v, i) {
          return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
      } else {
        p = p.map( function(v) {
          return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
      }
    }}
    
    var style = this.element.style;
    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
      style.left = p[0] + "px";
    if((!this.options.constraint) || (this.options.constraint=='vertical'))
      style.top  = p[1] + "px";
    
    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
  },
  
  stopScrolling: function() {
    if(this.scrollInterval) {
      clearInterval(this.scrollInterval);
      this.scrollInterval = null;
      Draggables._lastScrollPointer = null;
    }
  },
  
  startScrolling: function(speed) {
    if(!(speed[0] || speed[1])) return;
    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
    this.lastScrolled = new Date();
    this.scrollInterval = setInterval(this.scroll.bind(this), 10);
  },
  
  scroll: function() {
    var current = new Date();
    var delta = current - this.lastScrolled;
    this.lastScrolled = current;
    if(this.options.scroll == window) {
      with (this._getWindowScroll(this.options.scroll)) {
        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
          var d = delta / 1000;
          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
        }
      }
    } else {
      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
    }
    
    Position.prepare();
    Droppables.show(Draggables._lastPointer, this.element);
    Draggables.notify('onDrag', this);
    if (this._isScrollChild) {
      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
      if (Draggables._lastScrollPointer[0] < 0)
        Draggables._lastScrollPointer[0] = 0;
      if (Draggables._lastScrollPointer[1] < 0)
        Draggables._lastScrollPointer[1] = 0;
      this.draw(Draggables._lastScrollPointer);
    }
    
    if(this.options.change) this.options.change(this);
  },
  
  _getWindowScroll: function(w) {
    var T, L, W, H;
    with (w.document) {
      if (w.document.documentElement && documentElement.scrollTop) {
        T = documentElement.scrollTop;
        L = documentElement.scrollLeft;
      } else if (w.document.body) {
        T = body.scrollTop;
        L = body.scrollLeft;
      }
      if (w.innerWidth) {
        W = w.innerWidth;
        H = w.innerHeight;
      } else if (w.document.documentElement && documentElement.clientWidth) {
        W = documentElement.clientWidth;
        H = documentElement.clientHeight;
      } else {
        W = body.offsetWidth;
        H = body.offsetHeight
      }
    }
    return { top: T, left: L, width: W, height: H };
  }
}

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

var SortableObserver = Class.create();
SortableObserver.prototype = {
  initialize: function(element, observer) {
    this.element   = $(element);
    this.observer  = observer;
    this.lastValue = Sortable.serialize(this.element);
  },
  
  onStart: function() {
    this.lastValue = Sortable.serialize(this.element);
  },
  
  onEnd: function() {
    Sortable.unmark();
    if(this.lastValue != Sortable.serialize(this.element))
      this.observer(this.element)
  }
}

var Sortable = {
  SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
  
  sortables: {},
  
  _findRootElement: function(element) {
    while (element.tagName != "BODY") {  
      if(element.id && Sortable.sortables[element.id]) return element;
      element = element.parentNode;
    }
  },

  options: function(element) {
    element = Sortable._findRootElement($(element));
    if(!element) return;
    return Sortable.sortables[element.id];
  },
  
  destroy: function(element){
    var s = Sortable.options(element);
    
    if(s) {
      Draggables.removeObserver(s.element);
      s.droppables.each(function(d){ Droppables.remove(d) });
      s.draggables.invoke('destroy');
      
      delete Sortable.sortables[s.element.id];
    }
  },

  create: function(element) {
    element = $(element);
    var options = Object.extend({ 
      element:     element,
      tag:         'li',       // assumes li children, override with tag: 'tagname'
      dropOnEmpty: false,
      tree:        false,
      treeTag:     'ul',
      overlap:     'vertical', // one of 'vertical', 'horizontal'
      constraint:  'vertical', // one of 'vertical', 'horizontal', false
      containment: element,    // also takes array of elements (or id's); or false
      handle:      false,      // or a CSS class
      only:        false,
      delay:       0,
      hoverclass:  null,
      ghosting:    false,
      scroll:      false,
      scrollSensitivity: 20,
      scrollSpeed: 15,
      format:      this.SERIALIZE_RULE,
      onChange:    Prototype.emptyFunction,
      onUpdate:    Prototype.emptyFunction
    }, arguments[1] || {});

    // clear any old sortable with same element
    this.destroy(element);

    // build options for the draggables
    var options_for_draggable = {
      revert:      true,
      scroll:      options.scroll,
      scrollSpeed: options.scrollSpeed,
      scrollSensitivity: options.scrollSensitivity,
      delay:       options.delay,
      ghosting:    options.ghosting,
      constraint:  options.constraint,
      handle:      options.handle };

    if(options.starteffect)
      options_for_draggable.starteffect = options.starteffect;

    if(options.reverteffect)
      options_for_draggable.reverteffect = options.reverteffect;
    else
      if(options.ghosting) options_for_draggable.reverteffect = function(element) {
        element.style.top  = 0;
        element.style.left = 0;
      };

    if(options.endeffect)
      options_for_draggable.endeffect = options.endeffect;

    if(options.zindex)
      options_for_draggable.zindex = options.zindex;

    // build options for the droppables  
    var options_for_droppable = {
      overlap:     options.overlap,
      containment: options.containment,
      tree:        options.tree,
      hoverclass:  options.hoverclass,
      onHover:     Sortable.onHover
    }
    
    var options_for_tree = {
      onHover:      Sortable.onEmptyHover,
      overlap:      options.overlap,
      containment:  options.containment,
      hoverclass:   options.hoverclass
    }

    // fix for gecko engine
    Element.cleanWhitespace(element); 

    options.draggables = [];
    options.droppables = [];

    // drop on empty handling
    if(options.dropOnEmpty || options.tree) {
      Droppables.add(element, options_for_tree);
      options.droppables.push(element);
    }

    (this.findElements(element, options) || []).each( function(e) {
      // handles are per-draggable
      var handle = options.handle ? 
        $(e).down('.'+options.handle,0) : e;    
      options.draggables.push(
        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
      Droppables.add(e, options_for_droppable);
      if(options.tree) e.treeNode = element;
      options.droppables.push(e);      
    });
    
    if(options.tree) {
      (Sortable.findTreeElements(element, options) || []).each( function(e) {
        Droppables.add(e, options_for_tree);
        e.treeNode = element;
        options.droppables.push(e);
      });
    }

    // keep reference
    this.sortables[element.id] = options;

    // for onupdate
    Draggables.addObserver(new SortableObserver(element, options.onUpdate));

  },

  // return all suitable-for-sortable elements in a guaranteed order
  findElements: function(element, options) {
    return Element.findChildren(
      element, options.only, options.tree ? true : false, options.tag);
  },
  
  findTreeElements: function(element, options) {
    return Element.findChildren(
      element, options.only, options.tree ? true : false, options.treeTag);
  },

  onHover: function(element, dropon, overlap) {
    if(Element.isParent(dropon, element)) return;

    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
      return;
    } else if(overlap>0.5) {
      Sortable.mark(dropon, 'before');
      if(dropon.previousSibling != element) {
        var oldParentNode = element.parentNode;
        element.style.visibility = "hidden"; // fix gecko rendering
        dropon.parentNode.insertBefore(element, dropon);
        if(dropon.parentNode!=oldParentNode) 
          Sortable.options(oldParentNode).onChange(element);
        Sortable.options(dropon.parentNode).onChange(element);
      }
    } else {
      Sortable.mark(dropon, 'after');
      var nextElement = dropon.nextSibling || null;
      if(nextElement != element) {
        var oldParentNode = element.parentNode;
        element.style.visibility = "hidden"; // fix gecko rendering
        dropon.parentNode.insertBefore(element, nextElement);
        if(dropon.parentNode!=oldParentNode) 
          Sortable.options(oldParentNode).onChange(element);
        Sortable.options(dropon.parentNode).onChange(element);
      }
    }
  },
  
  onEmptyHover: function(element, dropon, overlap) {
    var oldParentNode = element.parentNode;
    var droponOptions = Sortable.options(dropon);
        
    if(!Element.isParent(dropon, element)) {
      var index;
      
      var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
      var child = null;
            
      if(children) {
        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
        
        for (index = 0; index < children.length; index += 1) {
          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
            offset -= Element.offsetSize (children[index], droponOptions.overlap);
          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
            child = index + 1 < children.length ? children[index + 1] : null;
            break;
          } else {
            child = children[index];
            break;
          }
        }
      }
      
      dropon.insertBefore(element, child);
      
      Sortable.options(oldParentNode).onChange(element);
      droponOptions.onChange(element);
    }
  },

  unmark: function() {
    if(Sortable._marker) Sortable._marker.hide();
  },

  mark: function(dropon, position) {
    // mark on ghosting only
    var sortable = Sortable.options(dropon.parentNode);
    if(sortable && !sortable.ghosting) return; 

    if(!Sortable._marker) {
      Sortable._marker = 
        ($('dropmarker') || Element.extend(document.createElement('DIV'))).
          hide().addClassName('dropmarker').setStyle({position:'absolute'});
      document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
    }    
    var offsets = Position.cumulativeOffset(dropon);
    Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
    
    if(position=='after')
      if(sortable.overlap == 'horizontal') 
        Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
      else
        Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
    
    Sortable._marker.show();
  },
  
  _tree: function(element, options, parent) {
    var children = Sortable.findElements(element, options) || [];
  
    for (var i = 0; i < children.length; ++i) {
      var match = children[i].id.match(options.format);

      if (!match) continue;
      
      var child = {
        id: encodeURIComponent(match ? match[1] : null),
        element: element,
        parent: parent,
        children: [],
        position: parent.children.length,
        container: $(children[i]).down(options.treeTag)
      }
      
      /* Get the element containing the children and recurse over it */
      if (child.container)
        this._tree(child.container, options, child)
      
      parent.children.push (child);
    }

    return parent; 
  },

  tree: function(element) {
    element = $(element);
    var sortableOptions = this.options(element);
    var options = Object.extend({
      tag: sortableOptions.tag,
      treeTag: sortableOptions.treeTag,
      only: sortableOptions.only,
      name: element.id,
      format: sortableOptions.format
    }, arguments[1] || {});
    
    var root = {
      id: null,
      parent: null,
      children: [],
      container: element,
      position: 0
    }
    
    return Sortable._tree(element, options, root);
  },

  /* Construct a [i] index for a particular node */
  _constructIndex: function(node) {
    var index = '';
    do {
      if (node.id) index = '[' + node.position + ']' + index;
    } while ((node = node.parent) != null);
    return index;
  },

  sequence: function(element) {
    element = $(element);
    var options = Object.extend(this.options(element), arguments[1] || {});
    
    return $(this.findElements(element, options) || []).map( function(item) {
      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
    });
  },

  setSequence: function(element, new_sequence) {
    element = $(element);
    var options = Object.extend(this.options(element), arguments[2] || {});
    
    var nodeMap = {};
    this.findElements(element, options).each( function(n) {
        if (n.id.match(options.format))
            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
        n.parentNode.removeChild(n);
    });
   
    new_sequence.each(function(ident) {
      var n = nodeMap[ident];
      if (n) {
        n[1].appendChild(n[0]);
        delete nodeMap[ident];
      }
    });
  },
  
  serialize: function(element) {
    element = $(element);
    var options = Object.extend(Sortable.options(element), arguments[1] || {});
    var name = encodeURIComponent(
      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
    
    if (options.tree) {
      return Sortable.tree(element, arguments[1]).children.map( function (item) {
        return [name + Sortable._constructIndex(item) + "[id]=" + 
                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
      }).flatten().join('&');
    } else {
      return Sortable.sequence(element, arguments[1]).map( function(item) {
        return name + "[]=" + encodeURIComponent(item);
      }).join('&');
    }
  }
}

// Returns true if child is contained within element
Element.isParent = function(child, element) {
  if (!child.parentNode || child == element) return false;
  if (child.parentNode == element) return true;
  return Element.isParent(child.parentNode, element);
}

Element.findChildren = function(element, only, recursive, tagName) {    
  if(!element.hasChildNodes()) return null;
  tagName = tagName.toUpperCase();
  if(only) only = [only].flatten();
  var elements = [];
  $A(element.childNodes).each( function(e) {
    if(e.tagName && e.tagName.toUpperCase()==tagName &&
      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
        elements.push(e);
    if(recursive) {
      var grandchildren = Element.findChildren(e, only, recursive, tagName);
      if(grandchildren) elements.push(grandchildren);
    }
  });

  return (elements.length>0 ? elements.flatten() : []);
}

Element.offsetSize = function (element, type) {
  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
}

