/* AUTO-GENERATED FILE - DO NOT EDIT.
@title:  globalOptimized.js
@date: auto-generated
@author: Andrew Southwick
@desc: File is auto-generated from multiple source files to 
optimize browser and client / server request performance.
builder,effects,dragdrop,controls,slider,sound
*/
/*  Prototype JavaScript framework, version 1.5.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',

  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[^>]*>([\u0001-\uFFFF]*?)</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}');
  },

  evalJSON: function(sanitize) {
    var json = this.unfilterJSON();
    try {
      if (!sanitize || (/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(json)))
        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);
};


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)) {
      
      params['_method'] = this.method;
      this.method = 'post';
    }

    this.parameters = params;

    if (params = Hash.toQueryString(params)) {
      
      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';
    }

    
    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)
      
      if (typeof headers[name] != 'function') {
      	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') {
      
      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();
      onComplete(transport, param);
    }).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();

    if (receiver = $(receiver)) {
      if (this.options.insertion)
        new this.options.insertion(receiver, response);
      else
        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;
  for (var i = 0, length = children.length; i < length; i++) {
    child = children[i];
    if (Element.hasClassName(child, className))
      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;

  
  if (!F.ElementExtensions) {
    Object.extend(methods, Element.Methods),
    Object.extend(methods, Element.Methods.Simulated);
  }

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

  
  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) 
      return {width: element.offsetWidth, height: element.offsetHeight};

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

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

    if (this.adjacency && this.element.insertAdjacentHTML) {
      try {
        this.element.insertAdjacentHTML(this.adjacency, this.content);
      } catch (e) {
        var tagName = this.element.tagName.toUpperCase();
        if (['TBODY', 'TR'].include(tagName)) {
          this.insertContent(this.contentFromAnonymousTable());
        } 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);
  }
}

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() {
    this.range.selectNodeContents(this.element);
    this.range.collapse(this.element);
  },

  insertContent: function(fragments) {
    fragments.each((function(fragment) {
      this.element.appendChild(fragment);
    }).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() {
    
    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+)$/)) 
          return '[' + fragment + "= " + mm[1] + ']';
        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { 
          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: {
    
    
    laterSibling: /^\s*~\s*/,
    child:        /^\s*>\s*/,
    adjacent:     /^\s*\+\s*/,
    descendant:   /^\s/,

    
    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: {
    
    
    concat: function(a, b) {
      for (var i = 0, node; node = b[i]; i++)
        a.push(node);
      return a;
    },

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

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

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

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

    
    tagName: function(nodes, root, tagName, combinator) {
      tagName = tagName.toUpperCase();
      var results = [], h = Selector.handlers;
      if (nodes) {
        if (combinator) {
          
          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);
    },

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

    
    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+$/)) { 
        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+))?/)) { 
        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++) {
        
        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) {
    
    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;
    }
  },

  
  
  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 = {
  
  
  
  includeScrollOffsets: false,

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

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

  
  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;

      
      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] || {})

    
    source = $(source);
    var p = Position.page(source);

    
    target = $(target);
    var delta = [0, 0];
    var parent = null;
    
    
    if (Element.getStyle(target,'position') == 'absolute') {
      parent = Position.offsetParent(target);
      delta = Position.page(parent);
    }

    
    if (parent == document.body) {
      delta[0] -= document.body.offsetLeft;
      delta[1] -= document.body.offsetTop;
    }

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




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


/* -----------------------------------*/
/* --->>> onDOMReady Extension <<<----*/
/* -----------------------------------*/


Object.extend(Event, {
  _domReady : function() {
    if (arguments.callee.done) return;
    arguments.callee.done = true;

    if (this._timer)  clearInterval(this._timer);
    
    this._readyCallbacks.each(function(f) { f() });
    this._readyCallbacks = null;
},
  onDOMReady : function(f) {
    if (!this._readyCallbacks) {
      var domReady = this._domReady.bind(this);
      
      if (document.addEventListener)
        document.addEventListener("DOMContentLoaded", domReady, false);
        
        /*@cc_on @*/
        /*@if (@_win32)
        var proto = "src=javascript:void(0)";
	        if (location.protocol == "https:") proto = "src='/gid/js/common/layouts/en/empty.js'";
	        document.write("<scr"+"ipt id=__ie_onload defer " + proto + "><\/scr"+"ipt>");
    	    document.getElementById("__ie_onload").onreadystatechange = function() {
        		if (this.readyState == "complete") domReady();
        	};
        /*@end @*/        
        
        if (/WebKit/i.test(navigator.userAgent)) { 
          this._timer = setInterval(function() {
            if (/loaded|complete/.test(document.readyState)) domReady(); 
          }, 10);
        }
        
        Event.observe(window, 'load', domReady);
        Event._readyCallbacks =  [];
    }
    Event._readyCallbacks.push(f);
  }
});

























var Scriptaculous = {
  Version: '1.7.1_beta3',
  require: function(libraryName) {
    
    document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
  },
  REQUIRED_PROTOTYPE: '1.5.1',
  load: function() {
    function convertVersionString(versionString){
      var r = versionString.split('.');
      return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
    }
 
    if((typeof Prototype=='undefined') || 
       (typeof Element == 'undefined') || 
       (typeof Element.Methods=='undefined') ||
       (convertVersionString(Prototype.Version) < 
        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
        Scriptaculous.REQUIRED_PROTOTYPE);
    
    /*
    $A(document.getElementsByTagName("script")).findAll( function(s) {
      return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
    }).each( function(s) {
      var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
      var includes = s.src.match(/\?.*load=([a-z,]*)/);
      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
       function(include) { Scriptaculous.require(path+include+'.js') });
    });
    */
  }
}

Scriptaculous.load();







var Builder = {
  NODEMAP: {
    AREA: 'map',
    CAPTION: 'table',
    COL: 'table',
    COLGROUP: 'table',
    LEGEND: 'fieldset',
    OPTGROUP: 'select',
    OPTION: 'select',
    PARAM: 'object',
    TBODY: 'table',
    TD: 'table',
    TFOOT: 'table',
    TH: 'table',
    THEAD: 'table',
    TR: 'table'
  },
  
  
  node: function(elementName) {
    elementName = elementName.toUpperCase();
    
    
    var parentTag = this.NODEMAP[elementName] || 'div';
    var parentElement = document.createElement(parentTag);
    try { 
      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
    } catch(e) {}
    var element = parentElement.firstChild || null;
      
    
    if(element && (element.tagName.toUpperCase() != elementName))
      element = element.getElementsByTagName(elementName)[0];
    
    
    if(!element) element = document.createElement(elementName);
    
    
    if(!element) return;

    
    if(arguments[1])
      if(this._isStringOrNumber(arguments[1]) ||
        (arguments[1] instanceof Array) ||
        arguments[1].tagName) {
          this._children(element, arguments[1]);
        } else {
          var attrs = this._attributes(arguments[1]);
          if(attrs.length) {
            try { 
              parentElement.innerHTML = "<" +elementName + " " +
                attrs + "></" + elementName + ">";
            } catch(e) {}
            element = parentElement.firstChild || null;
            
            if(!element) {
              element = document.createElement(elementName);
              for(attr in arguments[1]) 
                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
            }
            if(element.tagName.toUpperCase() != elementName)
              element = parentElement.getElementsByTagName(elementName)[0];
          }
        } 

    
    if(arguments[2])
      this._children(element, arguments[2]);

     return element;
  },
  _text: function(text) {
     return document.createTextNode(text);
  },

  ATTR_MAP: {
    'className': 'class',
    'htmlFor': 'for'
  },

  _attributes: function(attributes) {
    var attrs = [];
    for(attribute in attributes)
      attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
          '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
    return attrs.join(" ");
  },
  _children: function(element, children) {
    if(children.tagName) {
      element.appendChild(children);
      return;
    }
    if(typeof children=='object') { 
      children.flatten().each( function(e) {
        if(typeof e=='object')
          element.appendChild(e)
        else
          if(Builder._isStringOrNumber(e))
            element.appendChild(Builder._text(e));
      });
    } else
      if(Builder._isStringOrNumber(children))
        element.appendChild(Builder._text(children));
  },
  _isStringOrNumber: function(param) {
    return(typeof param=='string' || typeof param=='number');
  },
  build: function(html) {
    var element = this.node('div');
    $(element).update(html.strip());
    return element.down();
  },
  dump: function(scope) { 
    if(typeof scope != 'object' && typeof scope != 'function') scope = window; 
  
    var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
      "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
      "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
      "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
      "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
      "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
  
    tags.each( function(tag){ 
      scope[tag] = function() { 
        return Builder.node.apply(Builder, [tag].concat($A(arguments)));  
      } 
    });
  }
}














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; 

/* ------------- 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':
        
        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':
        
        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,   
  fps:        100,   
  sync:       false, 
  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);
    
    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() {
    
    
    
    
    this.element.makePositioned();
    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
    if(this.options.mode == 'absolute') {
      
      this.options.x = this.options.x - this.originalLeft;
      this.options.y = this.options.y - this.originalTop;
    }
  },
  update: function(position) {
    this.element.setStyle({
      left: Math.round(this.options.x  * position + this.originalLeft) + 'px',
      top:  Math.round(this.options.y  * position + this.originalTop)  + 'px'
    });
  }
});


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',        
      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() {
    
    if(this.element.getStyle('display')=='none') { this.cancel(); return; }
    
    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');
    
    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,
  
  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();
  
  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] || {})
  );
}


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








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

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

  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(); 
      this.activeDraggable = draggable;
    }
  },
  
  deactivate: function() {
    this.activeDraggable = null;
  },
  
  updateDrag: function(event) {
    if(!this.activeDraggable) return;
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
    
    
    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) {  
    this.observers = this.observers.reject( function(o) { return o.element==element });
    this._cacheObserverCallbacks();
  },
  
  notify: function(eventName, draggable, event) {  
    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,
      quiet: false,
      scroll: false,
      scrollSensitivity: 20,
      scrollSpeed: 15,
      snap: false,  
      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); 

    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)) {    
      
      var src = Event.element(event);
      if((tag_name = src.tagName.toUpperCase()) && (
        tag_name=='INPUT' ||
        tag_name=='SELECT' ||
        tag_name=='OPTION' ||
        tag_name=='BUTTON' ||
        tag_name=='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.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);
    
    if(!this.options.quiet){
      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);
    }
    
    
    if(Prototype.Browser.WebKit) window.scrollBy(0,0);
    
    Event.stop(event);
  },
  
  finishDrag: function(event, success) {
    this.dragging = false;
    
    if(this.options.quiet){
      Position.prepare();
      var pointer = [Event.pointerX(event), Event.pointerY(event)];
      Droppables.show(pointer, this.element);
    }

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

    var dropped = false; 
    if(success) { 
      dropped = Droppables.fire(event, this.element); 
      if (!dropped) dropped = false; 
    }
    if(dropped && this.options.onDropped) this.options.onDropped(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) {
      if (dropped == 0 || revert != 'failure')
        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 = ""; 
  },
  
  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.toUpperCase() != "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',       
      dropOnEmpty: false,
      tree:        false,
      treeTag:     'ul',
      overlap:     'vertical', 
      constraint:  'vertical', 
      containment: element,    
      handle:      false,      
      only:        false,
      delay:       0,
      hoverclass:  null,
      ghosting:    false,
      quiet:       false, 
      scroll:      false,
      scrollSensitivity: 20,
      scrollSpeed: 15,
      format:      this.SERIALIZE_RULE,
      
      
      
      elements:    false,
      handles:     false,
      
      onChange:    Prototype.emptyFunction,
      onUpdate:    Prototype.emptyFunction
    }, arguments[1] || {});

    
    this.destroy(element);

    
    var options_for_draggable = {
      revert:      true,
      quiet:       options.quiet,
      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;

    
    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
    }

    
    Element.cleanWhitespace(element); 

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

    
    if(options.dropOnEmpty || options.tree) {
      Droppables.add(element, options_for_tree);
      options.droppables.push(element);
    }

    (options.elements || this.findElements(element, options) || []).each( function(e,i) {
      var handle = options.handles ? $(options.handles[i]) :
        (options.handle ? $(e).getElementsByClassName(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);
      });
    }

    
    this.sortables[element.id] = options;

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

  },

  
  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"; 
        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"; 
        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) {
    
    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('&');
    }
  }
}


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






































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) {
    element          = $(element)
    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));

    
    
    Event.observe(window, 'beforeunload', function(){ 
      element.setAttribute('autocomplete', 'on'); 
    });
  },

  show: function() {
    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
    if(!this.iefix && 
      (Prototype.Browser.IE) &&
      (Element.getStyle(this.update, 'position')=='absolute')) {
      new Insertion.After(this.update, 
       '<iframe id="' + this.update.id + '_iefix" '+
       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
       '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(Prototype.Browser.WebKit) Event.stop(event);
         return;
       case Event.KEY_DOWN:
         this.markNext();
         this.render();
         if(Prototype.Browser.WebKit) Event.stop(event);
         return;
      }
     else 
       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
         (Prototype.Browser.WebKit > 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) {
    
    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.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() {
    this.startIndicator();
    
    var 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);
  }

});




































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       = []; 
        var partial   = []; 
        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 || {});
  }
});








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,
      okLink: false,
      okText: "ok",
      cancelButton: false,
      cancelLink: true,
      cancelText: "cancel",
      textBeforeControls: '',
      textBetweenControls: '',
      textAfterControls: '',
      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)) {
        
        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);
    
    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.textBeforeControls)
      this.form.appendChild(document.createTextNode(this.options.textBeforeControls));

    if (this.options.okButton) {
      var okButton = document.createElement("input");
      okButton.type = "submit";
      okButton.value = this.options.okText;
      okButton.className = 'editor_ok_button';
      this.form.appendChild(okButton);
    }
    
    if (this.options.okLink) {
      var okLink = document.createElement("a");
      okLink.href = "#";
      okLink.appendChild(document.createTextNode(this.options.okText));
      okLink.onclick = this.onSubmit.bind(this);
      okLink.className = 'editor_ok_link';
      this.form.appendChild(okLink);
    }
    
    if (this.options.textBetweenControls && 
      (this.options.okLink || this.options.okButton) && 
      (this.options.cancelLink || this.options.cancelButton))
      this.form.appendChild(document.createTextNode(this.options.textBetweenControls));
      
    if (this.options.cancelButton) {
      var cancelButton = document.createElement("input");
      cancelButton.type = "submit";
      cancelButton.value = this.options.cancelText;
      cancelButton.onclick = this.onclickCancel.bind(this);
      cancelButton.className = 'editor_cancel_button';
      this.form.appendChild(cancelButton);
    }

    if (this.options.cancelLink) {
      var cancelLink = document.createElement("a");
      cancelLink.href = "#";
      cancelLink.appendChild(document.createTextNode(this.options.cancelText));
      cancelLink.onclick = this.onclickCancel.bind(this);
      cancelLink.className = 'editor_cancel editor_cancel_link';      
      this.form.appendChild(cancelLink);
    }
    
    if (this.options.textAfterControls)
      this.form.appendChild(document.createTextNode(this.options.textAfterControls));
  },
  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() {
    
    var form = this.form;
    var value = this.editField.value;
    
    
    
    
    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,
          
          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));
    }
    
    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);
    }
  }
});





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







if(!Control) var Control = {};
Control.Slider = Class.create();







Control.Slider.prototype = {
  initialize: function(handle, track, options) {
    var slider = this;
    
    if(handle instanceof Array) {
      this.handles = handle.collect( function(e) { return $(e) });
    } else {
      this.handles = [$(handle)];
    }
    
    this.track   = $(track);
    this.options = options || {};

    this.axis      = this.options.axis || 'horizontal';
    this.increment = this.options.increment || 1;
    this.step      = parseInt(this.options.step || '1');
    this.range     = this.options.range || $R(0,1);
    
    this.value     = 0; 
    this.values    = this.handles.map( function() { return 0 });
    this.spans     = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
    this.options.startSpan = $(this.options.startSpan || null);
    this.options.endSpan   = $(this.options.endSpan || null);

    this.restricted = this.options.restricted || false;

    this.maximum   = this.options.maximum || this.range.end;
    this.minimum   = this.options.minimum || this.range.start;

    
    this.alignX = parseInt(this.options.alignX || '0');
    this.alignY = parseInt(this.options.alignY || '0');
    
    this.trackLength = this.maximumOffset() - this.minimumOffset();

    this.handleLength = this.isVertical() ? 
      (this.handles[0].offsetHeight != 0 ? 
        this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) : 
      (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth : 
        this.handles[0].style.width.replace(/px$/,""));

    this.active   = false;
    this.dragging = false;
    this.disabled = false;

    if(this.options.disabled) this.setDisabled();

    
    this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
    if(this.allowedValues) {
      this.minimum = this.allowedValues.min();
      this.maximum = this.allowedValues.max();
    }

    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
    this.eventMouseMove = this.update.bindAsEventListener(this);

    
    this.handles.each( function(h,i) {
      i = slider.handles.length-1-i;
      slider.setValue(parseFloat(
        (slider.options.sliderValue instanceof Array ? 
          slider.options.sliderValue[i] : slider.options.sliderValue) || 
         slider.range.start), i);
      Element.makePositioned(h); 
      Event.observe(h, "mousedown", slider.eventMouseDown);
    });
    
    Event.observe(this.track, "mousedown", this.eventMouseDown);
    Event.observe(document, "mouseup", this.eventMouseUp);
    Event.observe(document, "mousemove", this.eventMouseMove);
    
    this.initialized = true;
  },
  dispose: function() {
    var slider = this;    
    Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
    Event.stopObserving(document, "mouseup", this.eventMouseUp);
    Event.stopObserving(document, "mousemove", this.eventMouseMove);
    this.handles.each( function(h) {
      Event.stopObserving(h, "mousedown", slider.eventMouseDown);
    });
  },
  setDisabled: function(){
    this.disabled = true;
  },
  setEnabled: function(){
    this.disabled = false;
  },  
  getNearestValue: function(value){
    if(this.allowedValues){
      if(value >= this.allowedValues.max()) return(this.allowedValues.max());
      if(value <= this.allowedValues.min()) return(this.allowedValues.min());
      
      var offset = Math.abs(this.allowedValues[0] - value);
      var newValue = this.allowedValues[0];
      this.allowedValues.each( function(v) {
        var currentOffset = Math.abs(v - value);
        if(currentOffset <= offset){
          newValue = v;
          offset = currentOffset;
        } 
      });
      return newValue;
    }
    if(value > this.range.end) return this.range.end;
    if(value < this.range.start) return this.range.start;
    return value;
  },
  setValue: function(sliderValue, handleIdx){
    if(!this.active) {
      this.activeHandleIdx = handleIdx || 0;
      this.activeHandle    = this.handles[this.activeHandleIdx];
      this.updateStyles();
    }
    handleIdx = handleIdx || this.activeHandleIdx || 0;
    if(this.initialized && this.restricted) {
      if((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
        sliderValue = this.values[handleIdx-1];
      if((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
        sliderValue = this.values[handleIdx+1];
    }
    sliderValue = this.getNearestValue(sliderValue);
    this.values[handleIdx] = sliderValue;
    this.value = this.values[0]; 
    
    this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] = 
      this.translateToPx(sliderValue);
    
    this.drawSpans();
    if(!this.dragging || !this.event) this.updateFinished();
  },
  setValueBy: function(delta, handleIdx) {
    this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta, 
      handleIdx || this.activeHandleIdx || 0);
  },
  translateToPx: function(value) {
    return Math.round(
      ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) * 
      (value - this.range.start)) + "px";
  },
  translateToValue: function(offset) {
    return ((offset/(this.trackLength-this.handleLength) * 
      (this.range.end-this.range.start)) + this.range.start);
  },
  getRange: function(range) {
    var v = this.values.sortBy(Prototype.K); 
    range = range || 0;
    return $R(v[range],v[range+1]);
  },
  minimumOffset: function(){
    return(this.isVertical() ? this.alignY : this.alignX);
  },
  maximumOffset: function(){
    return(this.isVertical() ? 
      (this.track.offsetHeight != 0 ? this.track.offsetHeight :
        this.track.style.height.replace(/px$/,"")) - this.alignY : 
      (this.track.offsetWidth != 0 ? this.track.offsetWidth : 
        this.track.style.width.replace(/px$/,"")) - this.alignY);
  },  
  isVertical:  function(){
    return (this.axis == 'vertical');
  },
  drawSpans: function() {
    var slider = this;
    if(this.spans)
      $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
    if(this.options.startSpan)
      this.setSpan(this.options.startSpan,
        $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
    if(this.options.endSpan)
      this.setSpan(this.options.endSpan, 
        $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
  },
  setSpan: function(span, range) {
    if(this.isVertical()) {
      span.style.top = this.translateToPx(range.start);
      span.style.height = this.translateToPx(range.end - range.start + this.range.start);
    } else {
      span.style.left = this.translateToPx(range.start);
      span.style.width = this.translateToPx(range.end - range.start + this.range.start);
    }
  },
  updateStyles: function() {
    this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
    Element.addClassName(this.activeHandle, 'selected');
  },
  startDrag: function(event) {
    if(Event.isLeftClick(event)) {
      if(!this.disabled){
        this.active = true;
        
        var handle = Event.element(event);
        var pointer  = [Event.pointerX(event), Event.pointerY(event)];
        var track = handle;
        if(track==this.track) {
          var offsets  = Position.cumulativeOffset(this.track); 
          this.event = event;
          this.setValue(this.translateToValue( 
           (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
          ));
          var offsets  = Position.cumulativeOffset(this.activeHandle);
          this.offsetX = (pointer[0] - offsets[0]);
          this.offsetY = (pointer[1] - offsets[1]);
        } else {
          
          while((this.handles.indexOf(handle) == -1) && handle.parentNode) 
            handle = handle.parentNode;
            
          if(this.handles.indexOf(handle)!=-1) {
            this.activeHandle    = handle;
            this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
            this.updateStyles();
            
            var offsets  = Position.cumulativeOffset(this.activeHandle);
            this.offsetX = (pointer[0] - offsets[0]);
            this.offsetY = (pointer[1] - offsets[1]);
          }
        }
      }
      Event.stop(event);
    }
  },
  update: function(event) {
   if(this.active) {
      if(!this.dragging) this.dragging = true;
      this.draw(event);
      if(Prototype.Browser.WebKit) window.scrollBy(0,0);
      Event.stop(event);
   }
  },
  draw: function(event) {
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
    var offsets = Position.cumulativeOffset(this.track);
    pointer[0] -= this.offsetX + offsets[0];
    pointer[1] -= this.offsetY + offsets[1];
    this.event = event;
    this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
    if(this.initialized && this.options.onSlide)
      this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
  },
  endDrag: function(event) {
    if(this.active && this.dragging) {
      this.finishDrag(event, true);
      Event.stop(event);
    }
    this.active = false;
    this.dragging = false;
  },  
  finishDrag: function(event, success) {
    this.active = false;
    this.dragging = false;
    this.updateFinished();
  },
  updateFinished: function() {
    if(this.initialized && this.options.onChange) 
      this.options.onChange(this.values.length>1 ? this.values : this.value, this);
    this.event = null;
  }
}









Sound = {
  tracks: {},
  _enabled: true,
  template:
    new Template('<embed style="height:0" id="sound_#{track}_#{id}" src="#{url}" loop="false" autostart="true" hidden="true"/>'),
  enable: function(){
    Sound._enabled = true;
  },
  disable: function(){
    Sound._enabled = false;
  },
  play: function(url){
    if(!Sound._enabled) return;
    var options = Object.extend({
      track: 'global', url: url, replace: false
    }, arguments[1] || {});
    
    if(options.replace && this.tracks[options.track]) {
      $R(0, this.tracks[options.track].id).each(function(id){
        var sound = $('sound_'+options.track+'_'+id);
        sound.Stop && sound.Stop();
        sound.remove();
      })
      this.tracks[options.track] = null;
    }
      
    if(!this.tracks[options.track])
      this.tracks[options.track] = { id: 0 }
    else
      this.tracks[options.track].id++;
      
    options.id = this.tracks[options.track].id;
    if (Prototype.Browser.IE) {
      var sound = document.createElement('bgsound');
      sound.setAttribute('id','sound_'+options.track+'_'+options.id);
      sound.setAttribute('src',options.url);
      sound.setAttribute('loop','1');
      sound.setAttribute('autostart','true');
      $$('body')[0].appendChild(sound);
    }  
    else
      new Insertion.Bottom($$('body')[0], Sound.template.evaluate(options));
  }
};

if(Prototype.Browser.Gecko && navigator.userAgent.indexOf("Win") > 0){
  if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('QuickTime') != -1 }))
    Sound.template = new Template('<object id="sound_#{track}_#{id}" width="0" height="0" type="audio/mpeg" data="#{url}"/>')
  else
    Sound.play = function(){}
}

/*****************************************************************************
 * @title:   gidFunctions.js
 * @author:  Andrew Southwick, Byung Kim
 * @date:    10-24-2007
 * @rev:     4.20
 * @desc:    Core GID Javascript Library
 * @assumes: prototype.js 1.5 rel., scriptaculous.js, brandConstants.js
 * 
 * (C) Copyright 2004-2007 by Gap Inc.
 *  All Rights Reserved.
 *
 * This software is the confidential and proprietary information
 * of Gap Inc. ("Confidential Information"). Redistribution of the source 
 * code or binary form is not permitted without prior authorization
 * from the Gap Inc.
 *
 *****************************************************************************/

/**
 * ClientBrowser is a class that defines our existing browser support.
 * It defines variables that can be accessed at runtime to determine 
 * a user's browser type and version.  
 * Instantiated as clientBrowser.
 * 
 * Based on PLONE Browser Support document (10/9/2007)
 * Safari applewebkit versions from http://developer.apple.com/internet/safari/uamatrix.html
 * 
 * @constructor
 * @author Byung Kim
 * @date 10/24/2007
 */
var ClientBrowser = Class.create();
ClientBrowser.prototype = {
	userAgent:navigator.userAgent,
	product:navigator.product,
	mimeTypes:navigator.mimeTypes,
	supportLevel:null,
	
	/* All currently supported browsers.
	 * 
	 * name - string we use to name the new properties
	 * key - userAgent search string. Can be a partial RegExp string
	 * versions - browser versions supported.
	 * 		alias - the string used to name the property
	 * 		key - the RegExp to match all version numbers represented
	 * 		baseVersion - the minimum version number represented for the browser version we're checking.  This is used to determine the "Up" boolean.
	 */
	browsers:[
		{name:"IE",key:"MSIE ",versions:[
			{alias:"55",key:"5.5",baseVersion:"5.5"}, 
			{alias:"6",key:"6",baseVersion:"6"}, 
			{alias:"7",key:"7",baseVersion:"7"} 
		]},
		{name:"Firefox",key:"Firefox/",versions:[
			{alias:"1",key:"^1\.0[0-9\.]*",baseVersion:"1"}, 
			{alias:"15",key:"^1\.5[0-9\.]*",baseVersion:"1.5"}, 
			{alias:"2",key:"^2[0-9\.]*",baseVersion:"2"}, 
			{alias:"3",key:"^3[0-9\.]*",baseVersion:"3"} 
		]},
		{name:"Safari",key:"AppleWebKit/",versions:[
			{alias:"132",key:"^(312\.8)|(312\.8\.1)",baseVersion:"312.8"}, 
			{alias:"203",key:"^(417\.9)|(418)",baseVersion:"417.9"}, 
			{alias:"204",key:"^(418\.8)|(418\.9)|(418\.9\.1)|(419)|(419\.2\.1)|(419\.3)",baseVersion:"418.8"}, 
			{alias:"304",key:"^(523\.10)",baseVersion:"523.10"} 
		]},
		{name:"Netscape",key:"((Netscape)|(Navigator))/",versions:[
			{alias:"7",key:"^7[0-9\.]*",baseVersion:"7"}, 
			{alias:"8",key:"^8[0-9\.]*",baseVersion:"8"}, 
			{alias:"9",key:"^9[0-9\.]*",baseVersion:"9"} 
		]}
	],

	/**
	 * initialize clientBrowser 
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	initialize:function() {
		this.setBrowserInfo();
		this.setFlashDetection();
		this.setSupportLevel();
	},
	
	/**
	 * setBrowserInfo sets browser info properties
	 * Code flow:
	 * 1) check if the browser is in the userAgent string. If the browser wasn't found, we end here and no property is created.
	 * 2) create a new boolean property to denote that the browser was found in the userAgent.
	 * 3) get the full browser version string
	 * 4) create a new property for the full browser version string
	 * 5) check each supported version defined for the browser and set booleans for each version
	 * 6) set the boolean for each browser version (e.g.  isFirefox2, isIE55, isSafari203 )
	 *    The property is only set when it is not true.  This is to allow multiple checks to the same alias with different keys.  
	 *    Useful for Safari versions which are based on multiple applewebkit versions.
	 * 7) set the boolean for each browser version and above (e.g. isFirefox2up, isIE55Up, isSafari203Up )
	 *    The property is only set when it doesn't exist.  The "Up" boolean is only set with the initial version number for a given alias. 
	 *    Useful for Safari versions which are based on multiple applewebkit versions.
	 * 8) If the browser is detected, break the loop to minimize iterations.
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	setBrowserInfo:function() {
		var client = this;
		var iterator = function(browser) {
			var inUA = (client.userAgent.match(browser.key) != null); 
			if (inUA) {
				client["is"+browser.name] = inUA; 
				var uaVer = client.getVersion(browser.key); 
				if (uaVer) {
					client["ver"+browser.name] = uaVer; 
					if (browser.versions) { 
						browser.versions.each( 
							function(version) {
								var prop = "is"+browser.name+version.alias;
								if (!client[prop]) client[prop] = (uaVer.match(new RegExp(version.key)) != null); 
								if (client[prop+"Up"] == undefined) client[prop+"Up"] = (uaVer >= version.baseVersion); 
							}
						);
					}
				}
				$break; 
			}
		} 
		this.browsers.each(iterator);

		
		this.isGecko = (navigator.product == "Gecko");
		if (this.isGecko) {
			this.verGecko = this.getVersion("rv:");
		}
		
		
		this.isMac = this.userAgent.include("Mac");
		this.isWin = this.userAgent.include("Windows");

	},
	
	/**
	 * getVersion returns the full version string from the userAgent
	 * @param {string} key The key used to search the userAgent string
	 * @return a string representing the version of the browser
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	getVersion:function(key) {
		var keyMatch = this.userAgent.match(new RegExp(key+"[0-9\.]*"));
		var keyValue = null;
		if (keyMatch && keyMatch.length > 0) {
			keyValue = keyMatch[0].match(/[0-9\.]+/)[0];	
		}
		return keyValue;
	},
	
	/**
	 * setSupportLevel sets the business support level
	 * 0 = no support
	 * 1 = semi support
	 * 2 = full support
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	setSupportLevel:function(){
		if (this.isFirefox15Up || this.isIE6Up || this.isSafari204Up) {
			this.supportLevel = 2;
		} else if (this.isSafari132Up || this.isFirefox1Up || this.isNetscape7Up) {
			this.supportLevel = 1;
		} else {
			this.supportLevel = 0;
		}
		
	},
	
	/**
	 * setFlashDetection detects the flash plugin
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	setFlashDetection:function() {
		var mimeTypes = this.mimeTypes;
		if (mimeTypes && mimeTypes["application/x-shockwave-flash"] && mimeTypes["application/x-shockwave-flash"].enabledPlugin) {
			var flashPlugin = mimeTypes["application/x-shockwave-flash"].enabledPlugin;
			this.isFlash = true;
			this.verFlash = flashPlugin.description.match(/[\d.]+/)[0];
		} else if (this.isWin && this.isIE6Up) {
			document.write('<scr' + 'ipt language=VBScript>' + '\n' +
			'Dim hasPlayer, playerversion' + '\n' +
			'hasPlayer = false' + '\n' +
			'playerversion = 10' + '\n' +
			'Do While playerversion > 0' + '\n' +
			'On Error Resume Next' + '\n' +
			'hasPlayer = (IsObject(CreateObject("ShockwaveFlash.ShockwaveFlash." & playerversion)))' + '\n' +
			'If hasPlayer = true Then Exit Do' + '\n' +
			'playerversion = playerversion - 1' + '\n' +
			'Loop' + '\n' +
			'client.verFlash = playerversion' + '\n' +
			'client.isFlash = hasPlayer' + '\n' +
			'<\/sc' + 'ript>');
		}
	}
	
};

var clientBrowser = new ClientBrowser();

/**
 * GidLib is a class containing the core javascript library for GID.
 * Instantiated as gidLib.
 *
 * @constructor
 * @author Byung Kim
 * @date 10/24/2007
 */
var LastKnownHiddenDropDowns = [];

var GidLib = Class.create();
GidLib.prototype = {
	
	/**
	 * GID constants
	 * @base GidLib
	 * 
	 * @author Byung Kim
	 * @date 12/19/2007
	 */
	constants:{
			
	},
	
	/**
	 * initialize for GidLib
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	initialize:function() {
		this.addPrototypeMethods();
	},

    /**
     * clone - makes a shallow copy of desired obj, only sets the prototype to point to the original obj
     * this is different than prototypes own clone function which copies basic type attributes of original obj
     * @author yoshi
     */
     clone: function(obj) {
         function F() {}
         F.prototype = obj;
         return new F;
     },

    loadDomObjMap : function(obj, map) {
		map.keys().each(function(key) {
			obj[key] = $(map[key]);
		});
	},

	loadBtnImgMap : function(obj, map, path) {
		map.keys().each(function(key) { obj[key] = {src:path + map[key]}; });
	},

    /**
	 * addPrototypeMethods is used to add prototype methods to native classes
	 * 
	 * @author Byung Kim
	 * @date 10/31/2007
	 */
	addPrototypeMethods:function() {
        /**
         * extend Array method to return index of found element instead of element value
         */
        Object.extend(Array.prototype, {findIndex : function(iterator) {
            var result;
            this.each(function(value, index) {
              if (iterator(value, index)) {
                result = index;
                throw $break;
              }
            });
            return result;
        }});

        /**
		 * add hasOwnProperty method to Object class if it doesn't exist. (e.g. Safari 1.3.2)
		 */
		if(!Object.prototype.hasOwnProperty) {
			Object.prototype.hasOwnProperty = function(prop) {
				return this.constructor.prototype[prop] === undefined;
			}
		}
		
		/**
		 * add setSrc method to IMG and INPUT tags via Prototype Element.addMethods();
		 */
		 Element.addMethods(['IMG','INPUT'],{
			setSrc:function(element,src) {
				$(element)["src"] = src;
			}
		 });
		
		/**
		 * add trim methods to String class
		 */
		 String.prototype.lTrim = function() {
		 	return this.replace(/^\s+/,'');
		 }
		 String.prototype.rTrim = function() {
		 	return this.replace(/\s+$/,'');
		 }
		 String.prototype.trim = function() {
		 	return this.replace(/^\s+|\s+$/g,'');
		 }
	},
	
	/**
	 * onLoadHandler is a collection of methods to execute on load of a page.  This is called globally
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	onLoadHandler:function() {
		if (window["GIDPageViewAdapter"]) {
			objGIDPageViewAdapter = new GIDPageViewAdapter();
			objGIDPageViewAdapter.objGIDProducts = new GIDProducts();
		}
	    var disableLayer = $('disableLayer');
        if(disableLayer) disableLayer.style.display = 'none';
		this.setButtonEvents();
		this.setLabelOnClick();
		
		/* pageOnLoadFunctions() is deprecated - use Event.observe() to attach events */
		pageOnLoadFunctions();
	},

    /**
	 * stop events from bubbling up
	 *
	 * @author yoshi
	 * @date 07/09/2008
	 */
    stopBubbling: function(e) {
        if (!e) var e = window.event
        e.cancelBubble = true;
        if (e.stopPropagation) e.stopPropagation();
    },

    /**
	 * onUnloadHandler is a collection of methods to execute on unload of a page.  This is called globally
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	onUnloadHandler:function() {
		/* pageOnLoadFunctions() is deprecated - use Event.observe() to attach events */
        var disableLayer = $('disableLayer');
        if(disableLayer) disableLayer.style.display = 'block';
        pageOnUnloadFunctions();
	},


	/**
	 * hideDropDownsUnderElement hides all select boxes under a specified element for IE6
	 * @param {object} element A pointer reference to the element we want to hide <select> elements under
	 * @param {string} optional css selector
	 * @return an array of <select> elements that were hidden
	 *
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	 
	hideDropDownsUnderElement:function(element, exp) {
		var hiddenDropDowns = [];
		
		if 	(LastKnownHiddenDropDowns.length > 0) {
			hiddenDropDowns = LastKnownHiddenDropDowns;
		}
		
		if (element && clientBrowser.isIE6 == true) {
			var dropDowns = $$(exp||'select');
			var iterator = function(dropDown) {
				var isHidden = (dropDown.getStyle("visibility") == "hidden" || dropDown.visible == false); 
				if (dropDown && isHidden == false) {
					var dropDownPosition = Position.cumulativeOffset(dropDown);
					var dropDownWidth = dropDown.offsetWidth;
					var dropDownHeight = dropDown.offsetHeight;
					var isTopLeftUnderElement = Position.within(element,dropDownPosition[0],dropDownPosition[1]);
					var isTopRightUnderElement = Position.within(element,dropDownPosition[0]+dropDownWidth,dropDownPosition[1]);
					var isBottomLeftUnderElement = Position.within(element,dropDownPosition[0],dropDownPosition[1]+dropDownHeight);
					var isBottomRightUnderElement = Position.within(element,dropDownPosition[0]+dropDownWidth,dropDownPosition[1]+dropDownHeight);
					if (isTopLeftUnderElement || isTopRightUnderElement || isBottomLeftUnderElement || isBottomRightUnderElement) {
						dropDown.style.visibility = "hidden";
						hiddenDropDowns.push(dropDown);
					}
				}
			}
			dropDowns.each(iterator);
		}
		LastKnownHiddenDropDowns = hiddenDropDowns;
		return hiddenDropDowns;
	},
	
	/**
	 * showDropDowns shows all select boxes in the array created by the result of hideDropDownsUnderElement
	 * @param {object} hiddenDropDowns An array of <select> elements to set style visibility = visible
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	showDropDowns:function(hiddenDropDowns) {
		if (hiddenDropDowns&& hiddenDropDowns.length > 0 && clientBrowser.isIE6 == true) {
			hiddenDropDowns.invoke("setStyle", {visibility:"visible"});
		}
	},
	
	/**
	 * setFocus tries to focus on an element. If an exception is caught, return false.
	 * This method is necessary for IE which throws a JS error when you try to focus on
	 * an element where the element or any parent element is hidden or disabled.
	 * 
	 * TODO: Only use the try..catch for IE.
	 * 
	 * @param {object} element The DOM element we want to try to set focus() to.
	 * @return true or false depending on if the method was successful in setting the focus() on the specified element.
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	setFocus:function(element) {
		var isFocusable = true;
		try {
			element.focus();
		} catch(e) {
			
			isFocusable = false;
		}
		return isFocusable;
	},
	
	/**
	 * setButtonEvents sets button mouseover, mouseout, focus, and blur events for all navigational buttons
	 * @param {object} root The root DOM element to start searching for elements to modify
	 * 
	 * Modified 11/12/2007 Byung Kim - fixed to support "_sm" type buttons
	 * Modified 7/22/2008 Byung Kim - added support for setting button events on specific root nodes even with HAS_BUTTON_MOUSE_OVERS = false
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	setButtonEvents:function(root) {
		var elements = [];

       	var spriteBaseRegExp = /(universalButtonSprite\S+)(On|Over|Off)/;
       	var spriteClassRegExp = /universalButtonSprite\S+/;
		
		var addElements = function(attr) {
			var rootElement = (root ? $(root) : Element.extend(document.body));
			elements = elements.concat(rootElement ? rootElement.getElementsBySelector('img'+attr,'input'+attr) : $$('img'+attr,'input'+attr));
		}
		
		
		if (root != null && root != undefined && $(root)) {
			addElements("[src*=/common/buttons/en/button_]");
		
		
		} else {
			
			if (brandConst.HAS_BUTTON_MOUSE_OVERS) addElements("[src*=/common/buttons/en/button_]");
		}

		
		if (brandConst.UNIVERSAL_BUTTON_CONTENT_PATH && brandConst.UNIVERSAL_BUTTON_CONTENT_PATH != "") {
			addElements("[src*=" + brandConst.UNIVERSAL_BUTTON_CONTENT_PATH + "]");
			addElements("[class*=universalButtonSprite]");
		}


		var setOverState = function(event) {
			var targetElement = Event.element(event);
            var isDisabled = targetElement.getAttribute('isDisabled')
            if(isDisabled) return;
            
            if (targetElement.isSpriteBased) {
            	targetElement.className = targetElement.className.replace(
            		spriteClassRegExp,
            		targetElement.spriteBase + "Over");
            } else {
	            targetElement.src = targetElement.overState;
	        }
		}
		
		var setOnState = function(event) {
			var targetElement = Event.element(event);
            var isDisabled = targetElement.getAttribute('isDisabled')
            if(isDisabled) return;

            if (targetElement.isSpriteBased) {
            	targetElement.className = targetElement.className.replace(
            		spriteClassRegExp,
            		targetElement.spriteBase + "On");
            } else {
	            targetElement.src = targetElement.onState;
	        }
		}

        var setOffState = function(event) {
            var targetElement = Event.element(event);
            var isDisabled = targetElement.getAttribute('isDisabled')
            if(isDisabled) return;
            
            if (targetElement.isSpriteBased) {
            	targetElement.className = targetElement.className.replace(
            		spriteClassRegExp,
            		targetElement.spriteBase + "On");
            } else {
	            targetElement.src = isDisabled ? targetElement.offState : targetElement.onState;
	        }
        }

        var iterator = function(image) {
			var imgsrc = image.src;
			var searchIndex = image.src.indexOf("?");
			var imgIndex = image.src.indexOf(".gif");
			if (searchIndex == -1 || searchIndex > imgIndex) { 
				var suffix = (imgsrc.indexOf("_sm.gif") > 0 ? "_sm.gif" : ".gif");
				if (imgsrc.search(/(_off|_on)/) != -1) {
					image.offState = imgsrc;
		            image.onState = imgsrc.search(/_on(_sm)?\.gif/) != -1 ? imgsrc : imgsrc.replace(/(_off)?(_sm)?\.gif/, "_on" + suffix);
		            image.overState = imgsrc.replace(/(_off|_on)?(_sm)?\.gif/,"_over" + suffix);

					Event.observe(image,"mouseover",setOverState);
					Event.observe(image,"focus",setOnState);
					Event.observe(image,"mouseout",setOffState);
					Event.observe(image,"blur",setOffState);
					
					image.isSpriteBased = false;
				}
				
				else if (spriteClassRegExp.test(image.className)) {
					var matches = spriteBaseRegExp.exec(image.className);
					image.isSpriteBased = true;
					image.spriteBase = matches[1];
					
					Event.observe(image,"mouseover",setOverState);
					Event.observe(image,"focus",setOnState);
					Event.observe(image,"mouseout",setOffState);
					Event.observe(image,"blur",setOffState);
				}			
			}
		}

		elements.each(iterator);
	},
	
	/**
	 * selectOption selects a form field option based on the provided value.
	 * Works for select, select-multiple, radio, and checkboxes.
	 * @param {object} element The DOM Form element we want to modify
	 * @param {string} value The value of the option we want to set as selected
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	selectOption:function(element,value) {
		var fieldType = element.type;
		if (!fieldType && element[0]) fieldType = element[0].type;
		if (fieldType == "select-one" || fieldType == "select-multiple") {
			for (i=0;i<element.options.length;i++) {
				if (element.options[i].value == value) {
					element.options[i].selected = true;
					break;
				}
			}
		} else if (fieldType == "radio" || fieldType == "checkbox") {
			for (i=0;i<element.length;i++) {
				if (element[i].value == value) {
					element[i].checked = true;
					break;
				}
			}
		}
	},
	
	/**
	 * setLabelOnClick fixes an IE defect where the label tag doesn't act to focus on the respective form element when clicked
	 * 
	 * Modified 12/18/2007 Byung Kim - refactored to work better
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	setLabelOnClick:function() {
		if (clientBrowser.isIE) {
			var labels = $$('label');
			
			var eventMethod = function(e) {
				var element = $(Event.element(e).htmlFor);
				if (element) gidLib.setFocus(element);
				return false;
			}
	
			var iterator = function(element) {
				Event.observe(element, "click", eventMethod.bindAsEventListener(this));
			}
			
			labels.each(iterator);
		}
	},
	

	/**
	 * setCookie sets a cookie to the document object
	 * @param {string} name The cookie name
	 * @param {string} value The cookie value.  This needs to be an escape() value under most cases.
	 * @param {object} expires The cookie expire date.  It should be a Date() object.  If its the millisecond representation, it is converted to the Date() object.
	 * @param {string} path The cookie path
	 * @param {string} domain The cookie domain
	 * @param {boolean} secure Whether the cookie is set secure
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	setCookie:function(name,value,expires,path,domain,secure) {
		if (typeof expires != "object") expires = new Date(expires);
		document.cookie = name + "=" + value +
			((expires) ? "; expires=" + expires.toGMTString() : "") +
			((path) ? "; path=" + path : "") +
			((domain) ? "; domain=" + domain : "") +
			((secure) ? "; secure" : "");
	},
	
	/**
	 * getCookie retrieves the value of the specified document cookie
	 * @param {string} cookieName The name of the cookie we want to retrieve the value for
	 * @return the value of the cookie.  If no cookie is found, it returns zero (0).
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	getCookie:function(cookieName) {
		var cookie = document.cookie;
	  	var key = cookieName + "=";
	  	var value = 0;
		var keyMatch = cookie.match(new RegExp(key+"[^;]*"));
		if (keyMatch && keyMatch.length > 0) {
			value = unescape(keyMatch[0].substr(key.length));
		}

	 	return value;
	},

	/** 
	 * getCookieVar returns the value of a key-value pair within the specified document.cookie
	 * @param {string} cookieName The cookie we want to get the value from
	 * @param {string} key The key to check in the cookie
	 * @return the value of the key-value pair.  If the key is not found, it returns a blank string ("")
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	getCookieVar:function(cookieName, key) {
		var cookieValue = gidLib.getCookie(cookieName);
		var keyValue = "";
		if (cookieValue != 0 && key && key != "") {
			var keyMatch = cookieValue.match(new RegExp(key+"=[^&]*"));
			if (keyMatch && keyMatch.length > 0) {
				keyValue = unescape(keyMatch[0].substr(key.length+1));
			}
		}
		return keyValue;
	},
	
	/**
	 * setCookieParamsHelper return the appropriate values for cookie expiration, domain, path, and secure flag.  
	 * @param {string} cookieName The cookie name we want to use.  
	 * @param {object} expDate The Date() object representing the date we want to expire the cookie
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	setCookieParamsHelper : function(cookieName,expDate) {
		var exp = null;
		var domain = null;
		var path = "/";
		var isSecure = (location.protocol == "https:");

		
		var securePrefix = "secure-"
		var domain  = null; 
		var  renderOnOldDomain = true;
		var h = location.hostname;
		if (window["gidBrandSiteConstruct"]) {
			if (gidBrandSiteConstruct.sslPrefix && gidBrandSiteConstruct.sslPrefix != "") securePrefix = gidBrandSiteConstruct.sslPrefix;
			domain = gidBrandSiteConstruct.cookieDomain;
			renderOnOldDomain = gidBrandSiteConstruct.renderOnCurrentBrandUrl;
			if(h.indexOf(domain) == -1){
				renderOnOldDomain =  true;
			}
		} 

		if(renderOnOldDomain){
			securePrefix = "secure."
			domain = (h == "localhost" ?  null : "." + (h.indexOf(securePrefix) == 0 ? h.substr(securePrefix.length+(securePrefix.indexOf(".") != -1 ? 0 : 1)) : h));
		} 
		
		if ((cookieName.toLowerCase()).indexOf("persist") != -1) {
			exp = (expDate != null ? expDate : gidLib.getFutureDate({"years":5}));
		}

		return {exp:exp,domain:domain,path:path,isSecure:isSecure};
	},

	/**
	 * setCookieVar sets a key value pair in the specified cookie.  
	 * If the key already exists, it updates the key value.  If the cookie doesn't exist, it creates a new one.
	 * @param {string} cookieName The cookie name we want to use.  Include "persist" in the name to make it a persistent cookie.  Include "session" in the name to make it a session cookie.
	 * @param {string} cookieVarKey The key of the key-value pair we want to set
	 * @param {string} cookieVarValue The value of the key-value pair we want to set
	 * @param {object} expDate The Date() object representing the date we want to expire the cookie
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	setCookieVar:function(cookieName,cookieVarKey,cookieVarValue,expDate) {
		
		var newCookieValue = "";
		
		
		var cookieValue = gidLib.getCookie(cookieName);
		var cookieKeyValuePair = cookieVarKey + "=" + escape(cookieVarValue);
		if (cookieValue == 0 || cookieValue == "") {
			newCookieValue = cookieKeyValuePair;
		} else {
			if (cookieValue.indexOf(cookieVarKey + "=") != -1) {
				newCookieValue = cookieValue.replace(new RegExp(cookieVarKey+"=[^&]*"),cookieKeyValuePair);
			} else {
				newCookieValue = cookieValue + "&" + cookieKeyValuePair;
			}
		}
		
		var cookieParams = gidLib.setCookieParamsHelper(cookieName,expDate);

		
		gidLib.setCookie(cookieName,escape(newCookieValue),cookieParams.exp,cookieParams.path,cookieParams.domain,cookieParams.isSecure);
	},
	
	/**
	 * getCookieKeyValuePairs returns an array of key value pairs for a specified cookie
	 * @param {string} cookieName The cookie name we want to use.
	 * @return an array of JSON objects that represent the key value pairs in the cookie
	 * 
	 * @author Byung Kim
	 * @date 8/11/2008
	 */
	getCookieKeyValuePairs : function(cookieName) {
		var cookieValue = gidLib.getCookie(cookieName);
		var keyValuePairs = [];
		if (cookieValue != 0) {
			var cookieKeyValuePairs = unescape(cookieValue).split("&");
			cookieKeyValuePairs.each(function(keyValuePair) {
				var pair = keyValuePair.split("=");
				keyValuePairs.push({key:pair[0],value:pair[1]});
			});
		}		
		return keyValuePairs;
	},
	
	/**
	 * removeCookieVar removes a key value pair from a cookie
	 * @param {string} cookieName The cookie name we want to use.
	 * @param {string} cookieVarKey The key of the key-value pair we want to remove
	 * @param {object} expDate The Date() object representing the date we want to expire the cookie
	 * 
	 * @date 8/11/2008
	 * @author Byung Kim
	 */
	removeCookieVar : function(cookieName, cookieVarKey, expDate) {
		var cookieValue = gidLib.getCookie(cookieName);
		if (cookieValue != 0) {
			cookieValue = cookieValue.replace(new RegExp(cookieVarKey+"=[^&]*&?"),"").replace(/&$/,""); 
			var cookieParams = gidLib.setCookieParamsHelper(cookieName,expDate);
			gidLib.setCookie(cookieName,escape(cookieValue),cookieParams.exp,cookieParams.path,cookieParams.domain,cookieParams.isSecure);
		}
	},

	/**
	 * getFutureDate gets the future time based on the current time and a given incremented time.
	 * @param {object} futureDate A JSON Object that represents the incremented time. 
	 * 		Valid parameters are: days, weeks, years, hours, minutes, seconds, milliseconds
	 * 		e.g  {"days":7,"years":1,"months":5}  -- denotes a date one year, five months, and seven days in the future 
	 * @return newDate The future time in milliseconds 
	 *
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	getFutureDate:function(futureDate) {
		var newDate = new Date();
		var c = [];
		c["milliseconds"] = 1;
		c["seconds"] = c["milliseconds"]*1000;
		c["minutes"] = c["seconds"]*60;
		c["hours"] = c["minutes"]*60;
		c["days"] = c["hours"]*24;
		c["weeks"] = c["days"]*7;
		c["years"] = c["weeks"]*52;
		var ms = 0;
		var params = Object.keys(futureDate);
		if (params && params.length) {
			params.each(function(param) {
				ms += c[param]*futureDate[param];
			});
		}
		newDate = newDate.setTime(newDate.getTime() + ms);
		return newDate;
	},

	/**
	 * loadImage loads an image into memory. Useful for postloading images.  
	 * Not to be used if you only want to create an image object.  Use {src:"/image/path/imageName.gif"} for that.
	 * @param {string} src The path to the image
	 * @return the Image object with the src set  
	 * 
	 * @author Byung Kim
	 * @date 10/25/2007
	 */
	 loadImage:function(src) {
	 	var obj = document.createElement("img");
	 	$(obj).setSrc(src);
	 	return obj;
	 },
	
	/**
	 * setObjPosition sets a DOM elements style.top and style.left CSS properties
	 * @param {object} element The DOM element we want to set position
	 * @param {integer} intX The X coordinate value
	 * @param {integer} intY The Y coordinate value
	 * 
	 * Modified 12/19/2007 Byung Kim - refactored to use setStyle
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	setObjPosition:function(element,intX,intY) {
		strX = String(intX);
		strY = String(intY);
		$(element).setStyle({
			top: intY + (strY.indexOf("px") == -1 ? "px" : ""),
			left: intX + (strX.indexOf("px") == -1 ? "px" : "")
		});
	},
	
	/**
	 * setObjCenter sets a DOM element absolutely positioned to the center of the screen
	 * @param {object} target The DOM element we want to modify
	 * 
	 * Modified 12/19/2007 Byung Kim - refactored to use setStyle
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	setObjCenter:function(target) {
	    var halfW = (document.body.clientWidth ? document.body.clientWidth : window.innerWidth) * 0.5;
	    var halfH = (document.body.clientHeight ? document.body.clientHeight : window.innerHeight)  * 0.5;
	    $(target).setStyle({
	    	position:"absolute",
	    	left:(halfW - target.offsetWidth * 0.5) + 'px',
	    	top:(halfH - target.offsetHeight * 0.5) + 'px'
	    });
	},

	/**
	 * getQuerystringParam gets the value of the querystrings key-value pair
	 * @param {string} keyArg The key of the key-value pair
	 * @param {boolean} preserveCase Whether to preserve the case of the querystring & keyArg
	 * @return the value of the key-value pair
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	getQuerystringParam:function(keyArg,preserveCase) {
		var query = (preserveCase ? location.search : location.search.toLowerCase());
		var paramKey = (preserveCase ? keyArg : keyArg.toLowerCase());
		var paramVal = "";

	  	var regex = new RegExp("[\\?&]"+paramKey+"=([^&#]*)");
	  	var keyVal = regex.exec(query);
	  	if (keyVal != null) paramVal = keyVal[1];
	
	  	paramVal = unescape(paramVal);
	  	return paramVal;
	},
	
	/**
	 * removeQueryStringParam removes a key-value pair from the specified string
	 * @param {string} url The url string to remove the key-value pair from
	 * @param {string} param The key of the key-value pair to remove
	 * @return the modified string
	 *  
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	removeQueryStringParam:function(url,param) {
		var regExp =  new RegExp("&"+param+"=[^&]*");
		var regExp2 =  new RegExp("/?"+param+"=[^&]*&");
	
		
		return (url.match(regExp) ? url.replace(regExp,"") : url.replace(regExp2,""));
	},

	/**
	 * addCurrentDomain takes a relative url and returns a fully qualified url string
	 * @param {string} url The relative url to modify
	 * @return a fully qualified absolute path url based on the current page location protocol & location.host
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	addCurrentDomain:function(url) {
		return location.protocol + "//" + location.host + url;
	},
	
	/**
	 * openWindow opens a popup window
	 * @param {string} url The url of the popup
	 * @param {integer} width The width of the popup.  If one isn't specified, it defaults to the value in brandConst
	 * @param {integer} height The height of the popup.  If one isn't specified, it defaults to the value in brandConst
	 * @param {string} title The name of the popup window  
	 * @param {integer} left The X coordinate for where the popup should appear on the screen. If one isn't specified, it defaults to the value in brandConst
	 * @param {integer} top The Y coordinate for where the popup should appear on the screen. If one isn't specified, it defaults to the value in brandConst
	 * @param {boolean} location Whether to display the location bar in the popup. If this isn't specified, it defaults to no.
	 * @param {boolean} resizable Whether the popup is resizable. If this isn't specified, it defaults to no.
	 * @param {boolean} scrollbars Whether to show scrollbars. If this isn't specified, it defaults to no.
	 * @param {boolean} status Whether to display the status bar. If this isn't specified, it defaults to no.
	 * @param {boolean} toolbar Whether to display the toolbar. If this isn't specified, it defaults to no.
	 * @param {boolean} menubar Whether to display the menubar. If this isn't specified, it defaults to no.
	 * @return the window object
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	openWindow:function(url,width,height,title,left,top,location,resizable,scrollbars,status,toolbar,menubar) {
		width = parseInt(width);
		height = parseInt(height);
		width = (isNaN(width))? brandConst.POPUP_DEFAULT_WIDTH : width;
		height = (isNaN(height))? brandConst.POPUP_DEFAULT_HEIGHT : height;
		title = (title)? title : "popup";
		if (!left) {
			screenWidth = screen.availWidth;
			left = (screenWidth)? Math.max((screenWidth/2 - width/2),0) : 100;
		}
		if (!top) {
			screenHeight = screen.availHeight;
			top = (screenHeight)? Math.max((screenHeight/2 - height/2),0) : 100;
		}
		var params = "width=" + width + ",height=" + height + ",left=" +left  + ",top=" + top;
		params += (resizable)? ",resizable=yes" : "";
		params += (scrollbars)? ",scrollbars=yes" : "";
		params += (status)? ",status=yes" : "";
		params += (location)? ",location=yes" : "";
		params += (toolbar)? ",toolbar=yes" : "";
		params += (menubar) ? ",menubar=yes" : "";
		
		var obj = window.open(url,title,params);
		gidLib.setFocus(obj);
		return obj;
	},
	
	/**
	 * set800pxUniversalNav sets the Tab Navigation to resize and hide the marketing content
	 * so the width of 5 Tabs can fit in a 800px Screen Width
	 *  
	 * @author Keo Keonorasak
	 * @date 01/01/2009
	 */
	set800pxUniversalNav:function() {
	    var screenWidth = screen.width;
	    if((screenWidth != null) && (screenWidth <= 800)){
	    	if (gidBrandSiteConstruct.gidBrandSites){
	    		if (gidBrandSiteConstruct.gidBrandSites.compact().uniq().length > 4){
			    	var universalBarCenter = $("universalBarCenter");
			    	var universalBarCenterContainer = $("universalBarContainer");
			    	var universalMarketingContainerTop = $("universalMarketingContainerTop");		    		    
			    	universalBarCenter.className = "universalBarContainer800Screen";
			    	universalBarCenterContainer.className = "universalBarContainer800Screen";
			    	universalMarketingContainerTop.setStyle({width: "54px"});
			    	universalMarketingContainerTop.firstDescendant().setStyle({visibility: "hidden"});
			    }
	    	}
	    }
	},
	
	/**
	 * closeWindow closes the current window
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	closeWindow:function() {
		if (window.opener) window.opener.focus();
		window.close();
	},
	
	/**
	 * contentItemLink takes the user to a url but first appends values for reporting
	 * @param {string} domTarget The content item element id
	 * @param {string} strURL The url to modify and add the appropriate parameters
	 * @param {string} linkId Used to distinguish between similar links originating from the same content item
	 * @param {string} urlTarget Used to specify a window target
	 * @return a boolean depending on urlTarget.  It returns false if it needs to target a different window
	 * 
	 * Modified 12/18/2007 Byung Kim - check objTarget is null before looking up the className 
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	contentItemLink:function(domTarget,strURL,linkId,urlTarget) {
		var objTarget = null;
		var contentItemId = "";
		var strContentItemContainerPrefix = "contentItemContainer";
		var refBusinessId = null;
		var isHardCodedId = (typeof domTarget == "string" || typeof domTarget == "number");
		var isHardCodedURL = (strURL && strURL != '');
		if(!(reportingService||{}).isActive){
			refBusinessId = (window["omni"] ? omni.strCurrentBusinessId : "");
		}
		else {
			refBusinessId = reportingService.controller.viewManagers.commonViewManager.model.commonCurrentBusinessId;
		}
		if (isHardCodedId) {
			objTarget = (contentItemId = domTarget);
		} else {
			objTarget = domTarget;
			do {
				isFound = ((objTarget && objTarget.id && objTarget.id.match(new RegExp(strContentItemContainerPrefix))) || objTarget == null);
				if (!isFound) objTarget = objTarget.parentNode;
			} while (!isFound);
			if ($(objTarget)) contentItemId = $(objTarget).classNames();
		}
		var updateURLString = function(strURL) {
			strURL += (strURL.indexOf("?") == -1 ? "?" : "&") +
				"mlink=" +
				refBusinessId + "," +
				contentItemId +
				(linkId && linkId != "" ? "," + linkId : "") +
				"&clink=" + contentItemId;
			return strURL;
		}
		if (objTarget) {
			if (!isHardCodedURL) {
				if (domTarget.useMap && document.getElementsByName(domTarget.useMap.substr(1)).length > 0) {
					strLink = document.getElementsByName(domTarget.useMap.substr(1))[0].areas[0].href;
				} else if (domTarget.href) {
					strLink = domTarget.href;
				} else if (domTarget.parentNode && domTarget.parentNode.href) {
					strLink = domTarget.parentNode.href;
				}
				strURL = strLink.replace(new RegExp(".+"+location.host),"");
			}
	
			var isUrl = strURL.match(/^(\/|http|about)/);
			if (isUrl) {
				if (strURL.match(/^\//)) strURL = updateURLString(strURL);
			} else {
				var expURL = new RegExp(/[^']*\.do\?[^']*/g);
				var arrayURLs = strURL.match(expURL);
				if (arrayURLs) {
					for (i=0;i<arrayURLs.length;i++) {
						strURL = strURL.replace(arrayURLs[i],updateURLString(arrayURLs[i]));
					}
				}
			}
			if (urlTarget && urlTarget == "_new") {
					var newWindow = window.open(strURL,'');
					newWindow.focus();
			} else {
				var objTargetWindow = (urlTarget && window[urlTarget] ? window[urlTarget] : window);
					objTargetWindow.location.href = strURL;
			}
			if (!isHardCodedURL) return false;
		} else {
			if (!isHardCodedURL) return true;
		}
	},
	
	/**
	 * unUnicode converts all unicode characters to ascii
	 * @param {string} str The string to modify
	 * @return the modified string
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	unUnicode:function(str) {
		if (str) {
			var arrayMatch = str.match(/&#[0-9]+;/g);
			if (arrayMatch) {
				for (var i=0;i<arrayMatch.length;i++) {
					str = str.replace(arrayMatch[i],String.fromCharCode(arrayMatch[i].match(/[0-9]+/)));
				}
			}
		}
		return str;
	},
	
	/**
	 * openGiftCardWindow opens a layered popup with the giftcard balance page
	 * @param {boolean} hasDisplayButton determines whether to show the close button
	 * 
	 * Modified 2/15/2008 Byung Kim - updated openLayeredPopup call to new API
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	openGiftCardWindow:function(hasDisplayButton) {
		var shouldDisplayButton = "";
		if (hasDisplayButton) shouldDisplayButton = 'displayButton=true';
		gidLib.layeredPopup.openLayeredPopup({
			str:'/buy/giftCardBalance.do?'+shouldDisplayButton,
			id:'giftCardPopupContent',
			width:brandConst.POPUP_GIFTCARD_WIDTH,
			height:brandConst.POPUP_GIFTCARD_HEIGHT,
			title:'GiftCard'
		});
	},
	
	/**
	 * isMouseOut
	 * 
	 * TODO: Yoshi to add documentation
	 * 
	 */
    isMouseOut: function(x, y, boundBox) {
		return (x < boundBox.left ) || (x > boundBox.right) || (y < boundBox.top ) || (y > boundBox.btm) ;
	},
	
	/**
	 * setIePngTransparency automatically sets png transparency via CSS for elements
	 * Checks to see if the element has a png set in the background-image and adjusts accordingly, else checks to see if the element has a src 
	 * attribute and adjusts the image accordingly.  This does not support an element that has both a src and background-image as pngs because the IE fix
	 * relies on the IE specific CSS filter to display the png.  Background-image takes precedence if there are both.
	 * NOTE: for background-image to work properly, it must be an inline style in the tag. JS cannot read this value from CSS files.
	 * @param {object} either a collection or single element to set PNG alpha.
	 * 
	 * @author Byung Kim
	 * @date 2/5/2008
	 */
	setIePngTransparency: function(arg) {
		if (clientBrowser.isIE && !clientBrowser.isIE7Up) {
			var setIePngTransparencyIterator = function(element) {
				if (element && element.tagName) {
					var backgroundImage = element.style.backgroundImage; 
					var src = element.src;
					var filterTemplate = new Template("progid:DXImageTransform.Microsoft.AlphaImageLoader(src='#{filter}', sizingMethod='scale');");
					
					
					if (backgroundImage != "" && backgroundImage.indexOf(".png") != -1) { 
						element.setStyle({background:"transparent"});
						element.style.filter = filterTemplate.evaluate({filter:backgroundImage.replace(/(url|\)|\(|'|")/g,"")});
					
					} else if (src && src.indexOf(".png") != -1) { 
						var dimensions = element.getDimensions();
						element.src = "/assets/common/clear.gif";
						element.setStyle({
							width:dimensions.width + "px",
							height:dimensions.height + "px"
						});
						element.style.filter = filterTemplate.evaluate({filter:src.replace(location.protocol+"//"+location.host,"")});
						if (element.onmouseover) element.onmouseover = function() {
								var filter = element.style.filter;
								element.style.filter = filter.replace(/_off/,"_over");
						};
						if (element.onmouseout) element.onmouseout = function() {
								var filter = element.style.filter;
								element.style.filter = filter.replace(/_over/,"_off");
						};
					}
					
					
					element.setStyle({visibility:"visible"});
				}
			};

			
			if (arg) (arg.length ? arg : [arg]).each(setIePngTransparencyIterator);
		}
	},
	
	/**
	 * loadScript uses a dynamic script tag to load JS into the site.  Used for cross-domain AJAX.
	 * @param {object} param JSON object with all the required parameters
	 * 			callerObject : The class/object that's calling loadScript
	 * 			src : The src attribute value of the script tag.
	 * 			timeout : JSON object with the handler and args for timing out
	 * 			callBack: JSON object with the handler and args for the callback
	 * 
	 * @author Byung Kim
	 * @date 11/2/2007
	 */
	loadScript:function(param) {
		var script = $(document.createElement("script"));
		var targetId = "dynamicScriptLoader";
		script.type = "text/javascript";
		script.src = param.src;
		var target = $(targetId);
		if (!target) {
			target = $(document.createElement("div"));
			target.id = targetId;
			target.setStyle({display:"none"});
		}
		document.body.appendChild(target);
		target.appendChild(script);
		
		param.callerObject.loadScriptTimedOut = false; 
		param.callerObject.loadScriptSuccess = false; 
		
		
		var timeout = param.timeout;
		if (timeout && timeout.handler) {
			new PeriodicalExecuter(function(periodicalExecuterRef) {
				if (!param.callerObject.loadScriptSuccess) {
					param.callerObject.loadScriptTimedOut = timeout.handler(timeout.args);
				}
				periodicalExecuterRef.stop();
			},timeout.timeDelay);
		}

		
		var callback = param.callback;
		if (callback && callback.handler) {
			new PeriodicalExecuter(function(periodicalExecuterRef) {
				if (param.callerObject.loadScriptTimedOut) {
					periodicalExecuterRef.stop();
				} else {
					param.callerObject.loadScriptSuccess = callback.handler(callback.args,periodicalExecuterRef);
				}
			},callback.timeDelay);
		}
	},

    /**
	 * check if target element contains ele, use for mouseout and mouse over testing
	 * @base GidLib
	 *
	 * @author yoshi
	 * @date 10/21/2008
	 */
    checkMouseEvent: function(ele, target) {
        var isContain;
        if(target.contains) { isContain = !target.contains(ele); }
        if(isContain == undefined && target.compareDocumentPosition) { isContain = target.compareDocumentPosition(ele); } 

        return target.contains ? isContain : isContain < 16;
    },
	
	/**
	 * layeredPopup is a subclass of GidLib for all things related to layered popup functionality
	 * @base GidLib
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	layeredPopup: {
		dragger:null,
		hiddenDropDowns:null,
		effects:null,
		isOpen:false,
		popupId:"",
		
		/**
		 * layeredPopups can have different styles.  Each object within styles contains everything required to display differently.
		 * 
		 * @author Byung Kim
		 * @date 1/8/2008
		 */
		styles : {

			/**
			 * commonTemplates contains templates that are common to all styles
			 * 
			 * @author Byung Kim
			 * @date 1/8/2008
			 */
			commonTemplates : {
				iFrameContentTemplate : new Template('<iframe id="#{id}PopupContent" name="#{id}PopupContent" src="#{src}" class="content" style="width:#{width}px;height:#{height}px;" frameborder="0"></iframe>'),
				markupContentTemplate : new Template('<div id="#{id}PopupContent" class="content" style="width:#{width}px;height:#{height}px;">#{content}</div>'),
				closeAnchorTemplate : new Template('<a href="#" onclick="return gidLib.layeredPopup.closeLayeredPopup();">#{text}</a>'),
				universalCloseButtonTemplate : new Template('<img src="/assets/common/clear.gif" alt="#{altText}" class="universalButtonSprite universalButtonSpriteCloseWindowOn"/>')
			},
			
			/**
			 * brandedPopup is a style type for layeredPopup display
			 * 
			 * @author Byung Kim
			 * @date 1/8/2008
			 */
			brandedPopup : {
				constants : brandConst,
				templates : {
					mainTemplate : new Template(
						'<div id="popupMain" style="width:#{totalWidth}px;">' +
							'<div class="topBorder clearfix">' +
								'<div class="topLeftCorner"></div>' +
								'<div id="layeredPopup#{topId}FrameTop" class="topMiddle#{topClass}" style="width:#{topWidth}px;">#{title}</div>' +
								'<div class="topCloseButton"><a href="#" onclick="return gidLib.layeredPopup.closeLayeredPopup();">&#160;</a></div>' +
								'<div class="topRightCorner"></div>' +
							'</div>' +
							'<div class="mainContent clearfix">' +
								'<div class="leftBar" style="height:#{borderHeight}px;"></div>' +
								'#{content}' +
								'<div class="rightBar" style="height:#{borderHeight}px;"></div>' +
							'</div>' +
							'<div class="bottom clearfix ">' +
								'<div class="bottomLeftCorner"></div>' +
								'<div class="bottomMiddle" style="width:#{innerContentWidth}px;">#{bottonCloseButton}</div>' +
								'<div class="bottomRightCorner"></div>' +
							'</div>' +
						'</div>'
					)
				},
				
				/**
				 * getMarkup returns the markup for brandedPopup styled popups
				 * 
				 * @author Byung Kim
				 * @date 1/8/2008
				 */
				getMarkup:function(useIFrame, width, height, title, strContent, id) {
					var constants = this.constants;
					var templates = this.templates;
					var commonTemplates = gidLib.layeredPopup.styles.commonTemplates;
					var topId = ""
					var topClass = "";
					var content = "";
					var bottomCloseButton = (brandConst.LAYERED_POPUP_CLOSE_COPY != "" ? commonTemplates.closeAnchorTemplate.evaluate({copy:brandConst.LAYERED_POPUP_CLOSE_COPY}) : "");
					var topTitle = brandConst.LAYERED_POPUP_BRAND_NAME + (title && title != "" ? " - " + title : "");
					if (!topTitle || topTitle == "") topTitle = "&#160;";

					var innerContentWidth = width - constants.LAYERED_POPUP_SIDE_IMAGE_WIDTH;
					var borderHeight = height + constants.LAYERED_POPUP_CONTENT_COMBINED_BORDER_HEIGHT;
		
					if (useIFrame) {
						topId = "NoDrag";
						content = commonTemplates.iFrameContentTemplate.evaluate({
							id:id,
							src:gidLib.addCurrentDomain(strContent),
							width:innerContentWidth,
							height:height
						});
					} else {
						topClass = " cursorMove";
						content = commonTemplates.markupContentTemplate.evaluate({
							id:id,
							content:strContent,
							width:innerContentWidth,
							height:height
						});
					}
		
					return templates.mainTemplate.evaluate({
						topId:topId,
						topClass:topClass,
						totalWidth:width+constants.LAYERED_POPUP_COMBINED_BORDER_WIDTH+constants.LAYERED_POPUP_CONTENT_COMBINED_BORDER_WIDTH+constants.LAYERED_POPUP_SHADOW_WIDTH,
						topWidth:width-constants.LAYERED_POPUP_MAIN_WIDTH_OFFSET,
						innerContentWidth:innerContentWidth,
						borderHeight:borderHeight,
						title:topTitle,
						content:content,
						bottomCloseButton:bottomCloseButton
					});
				}
			},
			
			/**
			 * universalPopup is a style type for layeredPopup display
			 * 
			 * @author Byung Kim
			 * @date 1/8/2008
			 */
			universalPopup : {
				constants : {
					LAYERED_POPUP_COMBINED_BORDER_WIDTH: 21, 
					LAYERED_POPUP_COMBINED_BORDER_HEIGHT: 59, 
					LAYERED_POPUP_MAIN_WIDTH_OFFSET:19 
				},
				templates : {
					mainTemplate : new Template(
						'<div id="popupMain" class="universalLayeredPopup" style="width:#{popupWidth}px;">' +
							'<div class="topBorder clearfix">' +
								'<div class="pop-sprites topLeftCorner"></div>' +
								'<div id="layeredPopup#{topId}FrameTop" class=" pop-sprites topMiddle#{topClass}" style="width:#{topWidth}px;">#{title}</div>' +
								'<div class="topCloseButton">#{topCloseButton}</div>' +
								'<div class="pop-sprites topRightCorner"></div>' +
							'</div>' +
							'<div class="mainContent clearfix">' +
								'<div class="leftBar" style="height:#{borderHeight}px;"></div>' +
								'#{content}' +
								'<div class="rightBar" style="height:#{borderHeight}px;"></div>' +
							'</div>' +
							'<div class="bottom clearfix ">' +
								'<div class="pop-sprites bottomLeftCorner"></div>' +
								'<div class="pop-sprites bottomMiddle" style="width:#{innerContentWidth}px;"></div>' +
								'<div class="pop-sprites bottomRightCorner"></div>' +
							'</div>' +
						'</div>'
					)
				},

				/**
				 * getMarkup returns the markup for universalPopup styled popups
				 * 
				 * @author Byung Kim
				 * @date 1/8/2008
				 */
				getMarkup:function(useIFrame, width, height, title, strContent, id) {
					var constants = this.constants;
					var templates = this.templates;
					var commonTemplates = gidLib.layeredPopup.styles.commonTemplates;
					var topId = ""
					var topClass = "";
					var content = "";
					var topCloseButton = commonTemplates.closeAnchorTemplate.evaluate({text:commonTemplates.universalCloseButtonTemplate.evaluate({path:brandConst.UNIVERSAL_BUTTON_CONTENT_PATH,altText:resourceBundleValues.infoPopupsAltTextClose })});
					var topTitle = title;
					if (!topTitle || topTitle == "") topTitle = "&#160;";
		
					if (useIFrame) {
						topId = "NoDrag";
						content = commonTemplates.iFrameContentTemplate.evaluate({
							id:id,
							src:gidLib.addCurrentDomain(strContent),
							width:width,
							height:height
						});
					} else {
						topClass = " cursorMove";
						content = commonTemplates.markupContentTemplate.evaluate({
							id:id,
							content:strContent,
							width:width,
							height:height
						});
					}
		
					return templates.mainTemplate.evaluate({
						popupWidth:width + constants.LAYERED_POPUP_COMBINED_BORDER_WIDTH,
						topId:topId,
						topClass:topClass,
						topWidth:width-constants.LAYERED_POPUP_MAIN_WIDTH_OFFSET,
						innerContentWidth:width,
						borderHeight:height,
						title:topTitle,
						content:content,
						topCloseButton:topCloseButton
					});
				}
			},
			
			/**
			 * universalPanel is a style type for layeredPopup display
			 * 
			 * @author Byung Kim
			 * @date 1/8/2008
			 */
			universalPanel : {
				constants : {
					PANEL_COMBINED_BORDER_WIDTH: 14, 
					PANEL_MAIN_WIDTH_OFFSET:27, 
					PANEL_TITLE_WIDTH_OFFSET:50 
				},
				templates : {
					mainTemplate : new Template(
						'<div id="popupMain" class="universalPanel" style="width:#{mainWidth}px;">' +
							'<div class="row top clearfix" style="width:#{topBottomWidth}px;">' +
								'<div class="pop-sprites topLeft">&#160;</div>' +
								'<div class="pop-sprites topCenter" style="width:#{topBottomCenterWidth}px;">&#160;</div>' +
								'<div class="pop-sprites topRight">&#160;</div>' +
							'</div>' +
							'<div class="row">' +
								'<div class="leftCenter clearfix" style="width:#{leftCenterWidth}px;">' +
									'#{calloutLeft}' +
									'<div class="rightCenter" style="width:#{rightCenterWidth}px;#{offset}">' +
										'<div class="close" style="width:#{closeWidth}px;">' +
											'#{topBar}' +
											'<div class="panelContent" style="width:#{contentWidth}px;height:#{contentHeight}px;">#{content}</div>' +
										'</div>' +
									'</div>' +
								'</div>' +
							'</div>' +
							'<div class="row bottom clearfix" style="width:#{topBottomWidth}px;">' +
								'<div class="pop-sprites bottomLeft">&#160;</div>' +
								'<div class="pop-sprites bottomCenter" style="width:#{topBottomCenterWidth}px;">&#160;</div>' +
								'<div class="pop-sprites bottomRight">&#160;</div>' +
							'</div>' +
							'#{calloutBottom}' +
						'</div>'
					),
					
					panelCalloutBottomTemplate : new Template('<div class="pop-sprites row callout" style="#{offset}">&#160;</div>'),
					panelCalloutLeftTemplate : new Template('<div id="universalPanelLeftCallout" class="pop-sprites calloutLeft" style="#{offset}">&#160;</div>'),
					panelTitleTemplate : new Template('<div class="panelTitle" style="width:#{width}px;">#{title}</div>'),
					panelCloseButtonTemplate : new Template('<div class="closeButton">#{topCloseButton}</div>'),
					panelTopBarTemplate : new Template('<div class="clearfix">#{titleTemplate}#{buttonTemplate}</div>')
				},

				/**
				 * getMarkup returns the markup for universalPanel styled popups
				 * 
				 * @author Byung Kim
				 * @date 1/8/2008
				 */
				getMarkup:function(useIFrame, width, height, title, strContent, id, style) {
					var constants = this.constants;
					var templates = this.templates;
					var commonTemplates = gidLib.layeredPopup.styles.commonTemplates;
					var hasCloseButton = true;
					var callout = null;
					var calloutOffset = null;
					var content = "";
					var mainWidth = width+constants.PANEL_MAIN_WIDTH_OFFSET;
					if (style && style.hasCloseButton != undefined) hasCloseButton = style.hasCloseButton;
					if (style && style.callout) callout = style.callout;
					if (style && style.calloutOffset) calloutOffset = style.calloutOffset;
					var topTitle = "";
					var topCloseButton = "";
					var calloutLeft = "";
					var calloutBottom = "";
					var topBar = "";
					
					if (title) {
						topTitle = templates.panelTitleTemplate.evaluate({
							title:title,
							width:mainWidth-constants.PANEL_TITLE_WIDTH_OFFSET
						})
					}

					if (hasCloseButton) {
						topCloseButton = templates.panelCloseButtonTemplate.evaluate({
							topCloseButton:commonTemplates.closeAnchorTemplate.evaluate({
								text:commonTemplates.universalCloseButtonTemplate.evaluate({
									path:brandConst.UNIVERSAL_BUTTON_CONTENT_PATH, altText:resourceBundleValues.infoPopupsAltTextClose
								})
							})
						})
					}
					
					if (title || hasCloseButton) {
						topBar = templates.panelTopBarTemplate.evaluate({
							titleTemplate:topTitle,
							buttonTemplate:topCloseButton
						});
					}
					
					if (callout == "left") {
						calloutOffset = "top:"+calloutOffset+"px;"
						calloutLeft = templates.panelCalloutLeftTemplate.evaluate({offset:calloutOffset});
					} else if (callout == "bottom") {
						calloutOffset = "left:"+calloutOffset+"px;"
						calloutBottom = templates.panelCalloutBottomTemplate.evaluate({offset:calloutOffset});
					}
					
					if (useIFrame) {
						topId = "NoDrag";
						content = commonTemplates.iFrameContentTemplate.evaluate({
							id:id,
							src:gidLib.addCurrentDomain(strContent),
							width:width,
							height:height
						});
					} else {
						topClass = " cursorMove";
						content = commonTemplates.markupContentTemplate.evaluate({
							id:id,
							content:strContent,
							width:width,
							height:height
						});
					}
		
					return templates.mainTemplate.evaluate({
						mainWidth:mainWidth,
						topBottomWidth:mainWidth,
						topBottomCenterWidth:(mainWidth-constants.PANEL_COMBINED_BORDER_WIDTH),
						leftCenterWidth:mainWidth,
						rightCenterWidth:mainWidth,
						closeWidth:width,
						topBar:topBar,
						contentWidth:width,
						contentHeight:height,
						content:content,
						calloutLeft:calloutLeft,
						calloutBottom:calloutBottom
					});
				}
			}
		},

		/**
		 * openLayeredPopup opens a DHTML layer that looks like a popup.
		 * @param {object} argsGraph The JSON object with values for the popup
		 * 		str : The content of the layered Popup.  This can be a string of HTML, a URL to open, or a form to submit 
		 * 		id : The id for the layer parent element
		 * 		width : The width of the popup
		 * 		height : The height of the popup
		 * 		title : The title to display in the fake titlebar
		 * 		left : The x coordinate to position the popup
		 * 		top : The y coordinate to position the popup
		 * 		calleeElement : The calling element for the event
		 *		isDraggable : Whether the popover is draggable by clicking on it 
		 * 		style : A JSON object for the look and feel of the popup.
		 * 			name : which template to use (optional, default is the brandedPopup template)
		 * 			hasCloseButton : whether to display the close button for the universalPanel (optional, default is true)
		 * 			callout : used for displaying a callout for the universalPanel. values are 'left' or 'bottom' (optional, default is no callout)
		 * 			calloutOffset : used to offset the callout on the left or top depending on which is used.
		 *			isDraggable : Whether the poopup is draggagle (defaults to true)
		 * 		interstitial : JSON object -- when it exists, display as an interstitial (rest of the page greys out)
		 * 			color: hex value color of the interstitial (e.g. "#fff")
		 * 			opacity: opacity of the interstitial (e.g. "0.7" is 70%)
		 * 			buzz: whether to buzz the popup if a user clicks on the interstitial (e.g. true or false)
		 * 		effects : JSON object describing how to show and hide the layeredPopup.  Default is CSS visibility visible/hidden
		 * 			show : JSON object for showing the layered popup
		 * 				method : Scriptaculous Effect method
		 * 				args : JSON object of arguments for the method
		 * 			hide : JSON object for hiding the layered popup
		 * 				method : Scriptaculous Effect method
		 * 				args : JSON object of arguments for the method
		 * 			
		 * 
		 * Modified 12/19/2007 Byung Kim - converted method arguments to JSON with legacy API support
		 * Modified 1/1/2008 Byung Kim - reorganized to accept different styles
		 * Modified 1/8/2008 Byung Kim - updated FORM submit implementation
		 * Modified 1/31/2008 Byung Kim - added interstitial option
		 * Modified 7/25/2008 Byung Kim - added effects option for showing / hiding layered popup
		 * Modified 7/31/2008 Byung Kim - added callee element arg for returning focus to the callee once the popup closes
		 * 
		 * TODO: refactor implementation to use only JSON
		 * TODO: implement a dynamic root element for the popups 
		 * 
		 * @author Byung Kim
		 * @date 10/24/2007
		 */
		openLayeredPopup:function(argsGraph) {
			var args = arguments;
			if (args.length > 1) {
				
				var str = args[0];
				var id = args[1];
				var width = args[2];
				var height = args[3];
				var title = args[4];
				var left = args[5];
				var top = args[6];
			} else {
				var str = argsGraph.str;
				var id = argsGraph.id;
				var width = argsGraph.width;
				var height = argsGraph.height;
				var title = argsGraph.title;
				var left = argsGraph.left;
				var top = argsGraph.top;
				var isDraggable = argsGraph.isDraggable;
				var style = argsGraph.style;
				var interstitial = argsGraph.interstitial;
				this.effects = argsGraph.effects;
				this.calleeElement = argsGraph.calleeElement;
			}
			var popup = $("popupContent");
			
			var layerStyle = this.styles[(style && style.name ? style.name : "brandedPopup")]; 
			var constants = layerStyle.constants;
			var useIFrame = false;
			var isForm = false;
			var strContent = "";
			var strIgnoredFormTypes = "submit,button,image,file,reset";
			this.popupId = id;

			popup.setStyle({visibility:"hidden",display:"block"});

			if (str.match(/^(\/|http|about)/)) {
				useIFrame = true;
				strContent = str;
				var cid = str.substr(str.indexOf('?')+1, str.length);
			} else {
				var targetElement = $(str);
				if (targetElement) {
					if (targetElement.tagName && targetElement.tagName.toLowerCase() == 'form') {
						useIFrame = true;
						isForm = true;
						strContent = "/gid/html/en/blank.html";
						targetElement.target = id + "PopupContent";
					} else {
						
						strContent = targetElement.innerHTML;
					}
				} else {
					
					strContent = str;
				}
			}
			popup.update(layerStyle.getMarkup(useIFrame,width,height,title,strContent,id,style));
			if (!useIFrame && isDraggable) {
				gidLib.setButtonEvents("popupContent");
				this.dragger = new Draggable('popupContent',{handle:'layeredPopupFrameTop'});
			}
			this.hiddenDropDowns = gidLib.hideDropDownsUnderElement(popup);
			if (interstitial) this.openInterstitialDisplay(interstitial);
			this.setPosition(popup,left,top);
			this.launchLayeredPopup(id);
			if (isForm) targetElement.submit();
			return false;
		},
		
		/**
		 * setPosition sets the position of the layeredPopup
		 * 
		 * Modified 7/23/2008 - Byung Kim : removed scrollTop, scrollLeft calculation on x,y coordinates.  values passed to openlayeredpopup should account for the scroll values
		 * 
		 * @author Byung Kim
		 * @date 1/8/2008
		 */
		setPosition : function(popup,x,y) {
			var popupX = parseInt(x);
			var popupY = parseInt(y);
			var dimensions = popup.getDimensions();
			if (isNaN(popupX) || isNaN(popupY)) {
				var clientWidth = (document.documentElement && document.documentElement.clientWidth ? document.documentElement.clientWidth : document.body.clientWidth);
				var clientHeight = (document.documentElement && document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight);
				var scrollLeft = (document.documentElement && document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft);
				var scrollTop = (document.documentElement && document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop);
				if (clientBrowser.isSafari) {
					/*
					TD 14933: Safari does not seem to properly support the document.documentElement.clientHeight -
					reporting a constant value of 1330.
					*/
					var clientHeight = window.innerHeight;
				}
				popupX = Math.max(0,clientWidth/2 - dimensions.width/2)+scrollLeft;
				popupY = Math.max(0,clientHeight/2 - dimensions.height/2)+scrollTop;
				
			}
			gidLib.setObjPosition(popup,popupX,popupY);
		},
		
		/**
		 * launchLayeredPopup reveals the layered popup
		 * 
		 * @author Byung Kim
		 * @date 7/25/2008
		 */
		launchLayeredPopup : function(id) {
			var effects = this.effects;
			var popup = $("popupContent");
			
			var afterFinish = function(id) {
				setTimeout("gidLib.setFocus($('popupContent'))",100);
				this.isOpen = true;
			};
			
			if (effects && effects.show && effects.show.method) {
				popup.setStyle({display:"none",visibility:"visible"});
				var method = effects.show.method;
				var args = effects.show.args;
				var aF = {afterFinish:afterFinish.bind(this,id)};
				if (args) {
					Object.extend(args,aF);
					method(popup,args);
				} else {
					method(popup,aF);
				}
			} else {
				popup.setStyle({visibility:"visible",display:"block"});
				afterFinish(id);
			}
		},

		/**
		 * closeLayeredPopup closes the layered popup
		 * 
		 * Modified 7/25/2008 Byung Kim - added effect
		 * Modified 7/31/2008 Byung Kim - added returning focus to the callee element
		 * 
		 * @author Byung Kim
		 * @date 10/24/2007
		 */
		closeLayeredPopup:function() {
			var popup = $("popupContent");
			this.closeInterstitialDisplay();
			var afterFinish = function() {
				while(popup.hasChildNodes()) {
					popup.removeChild(popup.firstChild);
				}
				gidLib.showDropDowns(this.hiddenDropDowns);
				this.hiddenDropDowns = null;
				if (this.dragger) this.dragger.destroy();
				if (this.calleeElement) {
					gidLib.setFocus(this.calleeElement);
				}
				this.isOpen = false;
				this.popupId = "";
			};

			var effects = this.effects;
			if (effects && effects.hide && effects.hide.method) {
				var method = effects.hide.method;
				var args = effects.hide.args;
				var aF = {afterFinish:afterFinish.bind(this)};
				if (args) {
					Object.extend(args,aF);
					method(popup,args);
				} else {
					method(popup,aF);
				}
			} else {
				popup.setStyle({visibility:"hidden",display:"block"});
				afterFinish();
			}
			return false;
		},
		
		/**
		 * openInterstitialDisplay adds an interstitial layer behind the layeredPopup to grey out the page. 
		 * 
		 * @author Byung Kim
		 * @date 1/30/2008
		 */
		openInterstitialDisplay:function(args) {
			var color = (args.color ? args.color : "#fff");
			var opacity = (args.opacity ? args.opacity : "0.65");
			this.hasBuzz = (args.buzz != undefined ? args.buzz : true);
			var id = "layeredPopupInterstitial";
			var interstitial = $(id);
			if (!interstitial) {
				var interstitial = $(document.createElement("div"));
				interstitial.id = id;
				document.body.appendChild(interstitial);
				Event.observe(interstitial,"click",this.interstitialBuzzEffect.bind(this));
			}
			var clientWidth = (document.documentElement && document.documentElement.clientWidth ? document.documentElement.clientWidth : document.body.clientWidth);
			var clientHeight = (document.documentElement && document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight);
			var scrollLeft = (document.documentElement && document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft);
			var scrollTop = (document.documentElement && document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop);
			var docW = clientWidth + scrollLeft;
			var docH = (clientBrowser.isIE ? document.body.offsetHeight : (document.documentElement ? document.documentElement.offsetHeight : document.body.offsetHeight)); 
			if (clientHeight > docH) docH = clientHeight;
			interstitial.setStyle({
				position:"absolute",
				top:"0px",
				left:"0px",
				width:docW+"px",
				height:docH+"px",
				display:"block",
				background:color,
				zIndex:"98",
				opacity:opacity,
				filter:"alpha(opacity=" + (opacity*100) + ")" 
			});
		},
		
		/**
		 * closeInterstitialDisplay hides the interstitial layer
		 * 
		 * @author Byung Kim
		 * @date 1/30/2008
		 */
		closeInterstitialDisplay:function() {
			var interstitial = $("layeredPopupInterstitial");
			if (interstitial) {
				interstitial.setStyle({display:"none"});
				Event.stopObserving(interstitial,"click",this.interstitialBuzzEffect.bind(this));
			}
		},
		
		/**
		 * interstitialBuzzEffect does the effect for the popup
		 * 
		 * @author Byung Kim
		 * @date 1/31/2008
		 */
		interstitialBuzzEffect:function(hasBuzz) {
			if (this.hasBuzz) Effect.Shake("popupContent");
		}
	},
	
	/**
	 * rolloverBubble is a subclass of GidLib for all functionality related to rollover bubbles.
 	 * Usage Example:
 	 * 		<a href="#" onmouseover="gidLib.rolloverBubble.openBubble(event,{content:'Hello World!',align:'left',vposition:'top'});" onmouseout="gidLib.rolloverBubble.closeBubble();" onclick="return false;"><img .../></a>
 	 * 
 	 * Note on accessibility:
 	 * 		The text that goes in the bubble should also be represented in the alt tag of the image or as a visually hidden element.
 	 * 
	 * @base GidLib
	 * 
	 * @author Byung Kim
	 * @date 12/19/2007
	 * @edited Keo
	 * @date 03/21/2008
	 * Note: Added the vertical positioning of the bubble's location
	 */
	 rolloverBubble:{
	 	hiddenDropDowns:null,
	 	targetElement:null,
	 	bubbleElement:null,
	 	calloutSide:"",
	 	calloutVert:"",

	 	constants:{
	 		CONTAINER_ID:"rolloverBubbleContainer",
	 		CONTENT_ID:"rolloverBubbleContent",
	 		LEFT_POSITION_OFFSET:(clientBrowser.isIE ? 4 : 12),
	 		RIGHT_POSITION_OFFSET:(clientBrowser.isIE ? 30 : 23),
	 		TOP_POSITION_OFFSET:2,
	 		BOTTOM_POSITION_OFFSET:20
	 	},
	 	
	 	templates:{
		 	mainTemplate : new Template(
		 		'<div class="bubbleCallout callout#{calloutTop}"></div>' +
		 		'<div class="bubbleTop clearfix">' +
		 			'<div class="bubble-sprites left"></div>' +
		 			'<div class="bubble-sprites center"></div>' +
		 			'<div class="bubble-sprites right"></div>' +
		 		'</div>' +
		 		'<div id=#{contentId} class="bubbleMiddle #{calloutTextAlign} clearfix">#{content}</div>' +
		 		'<div class="bubbleBottom clearfix">' +
		 			'<div class="bubble-sprites left"></div>' +
		 			'<div class="bubble-sprites center"></div>' +
		 			'<div class="bubble-sprites right"></div>' +
		 		'</div>' +
		 		'<div class="bubble-sprites bubbleCallout callout#{calloutBottom}"></div>'
		 	)
	 	},
	 	
	 	/**
	 	 * openBubble opens the rollover bubble. It dynamically creates the containing element if it doesn't exist in the DOM.  
	 	 * The content of the rollover bubble is not refreshed and the position is not recalculated if the user rolls over the same bubble again.  
	 	 * We use visibility instead of display because the offsetHeight needs to be calculated.  display:none returns an offsetHeight of 0px.
	 	 * @param {object} e The event object
	 	 * @param {object} args A JSON object containing:
	 	 * 		content : the string to display in the bubble
	 	 *		align : optional - determines where the callout appears on the bubble.  Values are "left" or "right".  "left" is the default value 
	 	 *		vposition : optional - determines to appear above or belove the element  "top" is the default value 
	 	 * 
	 	 * @author Byung Kim
	 	 * @date 12/20/2007
	 	 * @edit Keo
	 	 * @date 03/21/2008
	 	 */
	 	openBubble:function(e,args) {
	 		var constants = this.constants;
	 		var containerId = constants.CONTAINER_ID;
	 		var contentId = constants.CONTENT_ID;
	 		var content = $(contentId);
	 		var container = $(containerId);
	 		if (!container) {
	 			container = $(document.createElement("div"));
	 			container.id = containerId;
	 			container.setStyle({
	 				visibility:"hidden",
	 				position:"absolute"
	 			});
	 			document.body.appendChild(container);
	 		}
	 		if (!content || (content && content.innerHTML != args.content) ) {
		 		this.targetElement = Event.element(e);
		 		this.calloutSide = (args.align ? args.align.capitalize() : "Left");
		 		var calloutSide = this.calloutSide;
		 		var calloutTextAlign = (args.textalign ? args.textalign.capitalize() : "Left");
		 		var calloutTop = "None";
		 		var calloutBottom = "Bottom"+calloutSide;
		 		this.calloutVert = (args.vposition ? args.vposition.capitalize() : "Top");
		 		var calloutVert = this.calloutVert;
	 			if(calloutVert == "Bottom"){
	 				calloutTop = "Top"+calloutSide;
	 				calloutBottom = "None";
	 			}
	 			container.update(this.templates.mainTemplate.evaluate({
	 				contentId:contentId,
	 				content:args.content,
	 				calloutTop:calloutTop,
	 				calloutBottom:calloutBottom,
	 				calloutTextAlign:calloutTextAlign
	 			}));
	 			this.bubbleElement = container;
	 			
	 			this.setBubblePosition();
	 		}
			this.hiddenDropDowns = gidLib.hideDropDownsUnderElement(container);
 			container.setStyle({visibility:"visible"});
 			
 			Event.observe(window,"resize",this.setBubblePosition.bind(this));
	 	},
	 	
	 	/**
	 	 * setBubblePosition sets the position of the bubble
	 	 * 
	 	 * @author Byung Kim
	 	 */
	 	setBubblePosition:function() {
	 		var position = Position.cumulativeOffset(this.targetElement);
	 		var constants = this.constants;
	 		var bubbleElement = this.bubbleElement;
 			gidLib.setObjPosition(bubbleElement,
 				position[0]-(this.calloutSide == "Left" ? constants.LEFT_POSITION_OFFSET : (bubbleElement.offsetWidth - constants.RIGHT_POSITION_OFFSET)),
 				this.calloutVert == "Top" ? position[1]-bubbleElement.offsetHeight-constants.TOP_POSITION_OFFSET : position[1]+constants.BOTTOM_POSITION_OFFSET
 			);
	 	},
	 	
	 	/**
	 	 * closeBubble closes the rollover bubble.  Note that it does not clear the markup contents incase the user rolls over the same bubble again.
	 	 * 
	 	 * @author Byung Kim
	 	 * @date 12/20/2007 
	 	 */
	 	closeBubble:function() {
	 		var container = $(this.constants.CONTAINER_ID);
 			container.setStyle({visibility:"hidden"});
			gidLib.showDropDowns(this.hiddenDropDowns);
			this.hiddenDropDowns = null;
	 	}
	 },
	 
	 inProgressIndicator : {
		 hiddenDropDowns : null,
		 args : null,
		 
		 setPosition : function() {
		 	var args = gidLib.inProgressIndicator.args;
			var requestInProgressMaskNode = $("gidLibRequestInProgressMask");
			
			if (args.isFullScreen == true) {
				var maskNodeX = 0;
				var maskNodeY = 0;
				var maskNodeWidth = (document.body.clientWidth ? document.body.clientWidth : window.innerWidth);
				var maskNodeHeight = (document.body.clientHeight ? document.body.clientHeight : window.innerHeight);
			} else {
				var targetElement = $(args.targetElement);
				var targetElementDimensions = targetElement.getDimensions();
				var targetElementPosition = Position.cumulativeOffset(targetElement);
				var maskNodeX = targetElementPosition[0];
				var maskNodeY = targetElementPosition[1];
				var maskNodeWidth = targetElementDimensions.width;
				var maskNodeHeight = targetElementDimensions.height;
			}
			
			requestInProgressMaskNode.style.width = maskNodeWidth + "px";
			requestInProgressMaskNode.style.height = maskNodeHeight + "px";
			requestInProgressMaskNode.style.left = maskNodeX + "px";
			requestInProgressMaskNode.style.top = maskNodeY + "px";
			
			if (args.messageText) {
				var yOffset = ((maskNodeHeight / 2) + (args.messageYOffset ? Number(args.messageYOffset) : 0)) + "px";
				var xOffset = (args.messageXOffset ? args.messageXOffset : 0) + "px";
				$("gidLibRequestInProgressMaskMessage").setStyle({
					"position" : "relative",
					"top" :  yOffset,
					"left" : xOffset
				});
			}
		},
		
		createRequestInProgressNode : function() {
			var requestInProgressMaskNode = $("gidLibRequestInProgressMask");
			var args = gidLib.inProgressIndicator.args;
			if (!requestInProgressMaskNode) {
				var element = $(document.createElement("div"));
				element.id = "gidLibRequestInProgressMask";
				document.body.appendChild(element);
				requestInProgressMaskNode = $("gidLibRequestInProgressMask");
			}

			requestInProgressMaskNode.setStyle({
				"background" : "white url(/gid/assets/common/en_US/loadIndicator24.gif) no-repeat scroll center center",
				"height" : "100px",
				"width" : "250px",
				"left" : "0px",
				"top" : "0px",
				"opacity" : (args.opacity ? args.opacity : 0.7),
				"position" : "absolute",
				"zIndex" : 99,
				"textAlign" : "center"
			});
			
			return requestInProgressMaskNode;
		},
		
		setInProgressNodeMessage : function() {
			var requestInProgressMaskNode = $("gidLibRequestInProgressMask");
			var args = gidLib.inProgressIndicator.args;
			var message = "";
			if (args.messageText) {
				message = '<div id="gidLibRequestInProgressMaskMessage" style="' + (args.messageStyle ? args.messageStyle : "") + '">' + args.messageText + '</div>';
			}
			requestInProgressMaskNode.update(message);
		},
		
		/**
		 * parameters for args are as follows:
		 *   'isFullScreen'	 : whether to place the in progress indicator over the entire screen.  If omitted, it assumes we are targetting an element 
		 *   'targetElement' : the element to place the in progress indicator over.  Not used when isFullScreen is true.
		 *   'messageText'       : adds a message below the spinny graphic.  Accepts HTML for custom styling. 
		 *   'messageYOffset' : offsets the position of the message vertically (by default, it is centered)
		 *   'messageXOffset' : offsets the position of the message horizontally (by default, it is centered)
		 *   'messageStyle'  : additional CSS styling for the message
		 *   'opacity'       : sets the opacity of the interstitial
		 */
		open : function(args) {
			this.args = args;
			var requestInProgressMaskNode = this.createRequestInProgressNode();
			this.setInProgressNodeMessage();
			this.setPosition();
			requestInProgressMaskNode.setStyle({"display":"block"});
			this.hiddenDropDownNodes = gidLib.hideDropDownsUnderElement(requestInProgressMaskNode); 
			Event.observe(window,"resize",gidLib.inProgressIndicator.setPosition);
	 	},
	 	close : function() {
	 		var requestInProgressMaskNode = $("gidLibRequestInProgressMask");
	 		if (requestInProgressMaskNode) {
		 		requestInProgressMaskNode.setStyle({"display":"none"});
		 		gidLib.showDropDowns(this.hiddenDropDownNodes);  
				Event.stopObserving(window,"resize",gidLib.inProgressIndicator.setPosition);
	 		}
	 	}
	 },

	/**
	 * reporting is a subclass of GidLib for functionality specifically for reporting purposes
	 * @base GidLib
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	reporting:{
		
		/**
		 * getContentItemIds returns contentItemIds array.  used by sOmni.js
		 * @return an array of all contentItemContainer elements in the DOM 
		 * 
		 * @author Byung Kim
		 * @date 10/24/2007
		 */
		getContentItemIds:function() {
			var elements = $$("[id^=contentItemContainer]");
			var contentItemIds = new Array();
			elementsIterator = function(element) {
				contentItemIds.push(element.className);
			}
			elements.each(elementsIterator);
			return contentItemIds;
		},
		
		/**
		 * getBrandSite returns the brand site object given the brandCode.  This reads from gidBrandSiteConstruct so it must exist for this to work.
		 * This should be used when trying to retrieve the brandSite object since it performs the check to see if gidBrandSiteConstruct exists or not.
		 * @param brandCode 
		 * @return the brand object
		 * 
		 * @author Byung Kim
		 * @date 1/29/2008
		 */
		getBrandSite:function(brandCode) {
			var brandSite = null;
			if (window["gidBrandSiteConstruct"] && brandCode) {
				if (gidBrandSiteConstruct.gidBrandSites) brandSite = gidBrandSiteConstruct.gidBrandSites[brandCode];
			}
			return brandSite;
		}
	}
}
var gidLib = new GidLib();


/** 
 * InlineBag is a class for functionality related to the inline shopping bag.
 * Instantiated as inlineBag.
 * @constructor
 * 
 * @author Byung Kim
 * @date 10/24/2007
 */
var InlineBag = Class.create();
InlineBag.prototype = {
	isOpen:false,
	doOpenBag:false,
	isAnimating:false,
	hasMarketing:false,
	closeDelay:60,
	marketingDelay:0.25,
	headerElementId:"inlineBagHeader",
	headerOpenElementId:"inlineBagHeaderOpen",
	contentClipElementId:"inlineBagClip",
	contentElementId:"inlineBagContent",
	marketingClipElementId:"inlineBagMarketingClip",
	marketingContentElementId:"inlineBagMarketingContent",
	priceElementId:"inlineBagTopPriceLayer",
	priceOpenElementId:"inlineBagTopPriceLayerOpen",
	universalBarElementId:"universalBar",
	contentDataElementId:brandConst.inlineBag.CONTENT_DATA_ELEMENT_ID,
	marketingDataElementId:brandConst.inlineBag.MARKETING_DATA_ELEMENT_ID,
	contentXOffset:brandConst.inlineBag.CONTENT_X_OFFSET,
	contentYOffset:brandConst.inlineBag.CONTENT_Y_OFFSET,
	marketingContentXOffset:brandConst.inlineBag.MARKETING_CONTENT_X_OFFSET,
	marketingContentYOffset:brandConst.inlineBag.MARKETING_CONTENT_Y_OFFSET,
	hiddenDropDowns:[],
	labelColor:brandProperties.inLineBag.labelColor,
	labelSize:brandProperties.inLineBag.labelSize,
	labelQuantity:brandProperties.inLineBag.labelQuantity,
	labelPrice:brandProperties.inLineBag.labelPrice,
	itemInSingular:brandProperties.inLineBag.itemInSingular,
	itemInPlural:brandProperties.inLineBag.itemInPlural,
	lineItemBag:brandProperties.inLineBag.lineItemBag,
	addedTo:brandProperties.inLineBag.addedTo,
	yourBag:brandProperties.inLineBag.yourBag,
	
	
	model:{
	    data:{}
	},
	controller:{
	},
	constants:{
	},
	
	/**
	 * initialize inlineBag
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	initialize:function() {},

	setWindowResizeHandler:function() {
		if (objInlineBag.isOpen) {
		    objInlineBag.positionInlineBag()
		    
		    if (objInlineBag.hasMarketing) {
			    objInlineBag.positionInlineBagMarketing();
			}
		}
	},

	/**
	 * initializeElements sets pointer references to DOM elements
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	initializeElements:function() {
		this.headerBlock = $(this.headerElementId);
		this.headerBlockOpen = $(this.headerOpenElementId);
		this.inlineBagWrapper = $(this.contentClipElementId);
		this.inlineBagContent = $(this.contentElementId);
	},

	/**
	 * positionInlineBag positions the inline bag display
	 * 
	 * Modified 3/17/09 -- subtract the universalbar Y position to fix inlinebag positioning in IE when the preview bar is enabled
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	positionInlineBag:function() {
		if (this.inlineBagWrapper.style && this.inlineBagContent.style) {
			var position = Position.cumulativeOffset($(this.headerBlock));
	
            var xValue = position[0];
			var yValue = position[1];
				
			
			if (this.isOpen) {
    			position = Position.cumulativeOffset($(this.headerBlockOpen));
	
	        	xValue = position[0];
			    yValue = position[1] + this.headerBlockOpen.offsetHeight;
	        } else {
    	        yValue += this.headerBlock.offsetHeight;
	        }
	        
			var universalBarPositionY = 0; 
	        if ($(this.universalBarElementId) != null && clientBrowser.isIE) {
		        universalBarPositionY = Position.cumulativeOffset($(this.universalBarElementId))[1];
	        }

			var x = xValue + "px";
			var y = (yValue-universalBarPositionY) + "px";
			gidLib.setObjPosition(this.inlineBagWrapper,x,y);
		} else {
			var inlineBag = this;
			new PeriodicalExecuter(function(pe) {
				inlineBag.positionInlineBag();
				pe.stop();
			},0.1);
		}
	},

	/**
	 * positionInlineBagMarketing positions the marketing portion of the inline bag display
	 * 
	 * Modified 3/19/09 -- subtract the universalbar Y position to fix inlinebag positioning in IE when the preview bar is enabled
	 * 
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	positionInlineBagMarketing:function() {
		var position = Position.cumulativeOffset(this.inlineBagContent);
		
		var xValue = position[0];
		var yValue = position[1];
		
		var universalBarPositionY = 0; 
        if ($(this.universalBarElementId) != null && clientBrowser.isIE) {
	        universalBarPositionY = Position.cumulativeOffset($(this.universalBarElementId))[1];
        }

		var x = xValue + "px";
		var y = (yValue + this.inlineBagContent.offsetHeight - universalBarPositionY) + "px";
		gidLib.setObjPosition($(this.marketingClipElementId),x,y);
	},

	/**
	 * openInlineBag opens the inline bag display
	 * @param {boolean} isAuto ?? not sure what this is for
	 * 
	 * Modified 12/04/2007 Byung Kim - added reference to openInlineBagAfterFinish
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	openInlineBag:function(isAuto) {
		if ((!this.isOpen || $(this.contentDataElementId).innerHTML != "") && (!this.isAnimating || isAuto)) {
			this.isAnimating = true;
			this.headerBlock.hide();
			this.headerBlockOpen.show();
            
            
    		
            var searchInputs = $('topSearchInputs');
            if(searchInputs) { searchInputs.hide(); }
            
            
            
    		Event.observe(
	    		window,
		    	"resize",
			    this.setWindowResizeHandler
		    );
            
			Effect.SlideDown(inlineBag.contentElementId,
				{
					duration:0.5,
					afterFinish:this.openInlineBagAfterFinish.bind(this)
				}
			);
		}
 	},
 	
	/**
	 * openInlineBagAfterFinish executes after the inline bag opening animation finishes
	 * 
	 * @author Byung Kim
	 * @date 12/04/2007
	 */
 	openInlineBagAfterFinish:function() {
		this.hiddenDropDowns = gidLib.hideDropDownsUnderElement(this.inlineBagWrapper);
		
		if (this.hasMarketing) {
			this.positionInlineBagMarketing();
			var inlineBag = this;
			new PeriodicalExecuter(function(pe) {
				inlineBag.openInlineBagMarketing();
				pe.stop();
			},this.marketingDelay);
		} else {
			this.openAfterFinish();
		}
 	},
 	
 	/**
 	 * openInlineBagMarketing opens the marketing message portion of the inline bag display
 	 * 
	 * Modified 12/04/2007 Byung Kim - added bind() to openAfterFinish

	 * 	 * @author Byung Kim
	 * @date 10/24/2007
 	 */
	openInlineBagMarketing:function() {
		Effect.SlideDown(inlineBag.marketingContentElementId,
			{
				duration:0.5,
				afterFinish:this.openAfterFinish.bind(this)
			}
		);
	},
	
	/**
	 * openAfterFinish is code executed after the completion of the opening effect
	 * 
	 * Modified 12/04/2007 Byung Kim - replaced parent references with "this"
	 * Modified 12/10/2007 Byung Kim - wrapped setFocusToInlineBag call with a PE to fix an IE problem where the inline bag will blank out

	 * 	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	openAfterFinish:function() {
		this.isOpen = true;
		this.isAnimating = false;
		var inlineBag = this;
		new PeriodicalExecuter(function(pe) {
			inlineBag.closeInlineBag();
			pe.stop();
		},this.closeDelay);
		Event.observe(document,"mousedown",this.checkInlineBag.bind(this));
		new PeriodicalExecuter(function(pe) {
			inlineBag.setFocusToInlineBag();
			pe.stop();
		},0.1);
	},
	
	/**
	 * closeInlineBag closes the inline bag
	 * @param {boolean} isAuto ?? not sure what this is for
	 * 
	 * Modified 12/04/2007 Byung Kim - added reference to closeInlineBagAfterFinish
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	closeInlineBag:function(isAuto) {
		if (this.isOpen && (!this.isAnimating || isAuto)) {
			this.isAnimating = true;
			if (this.hasMarketing) {
				this.closeInlineBagMarketing();
			} else {
				Effect.SlideUp(inlineBag.contentElementId,
					{
						duration:0.5,
						afterFinish:this.closeInlineBagAfterFinish.bind(this)
					}
				);
			}
		}
	},
	
	/**
	 * closeInlineBagAfterFinish executes after the inline bag closing animation finishes
	 * 
	 * @author Byung Kim
	 * @date 12/04/2007
	 */
	closeInlineBagAfterFinish:function() {
		this.isOpen = false;
		this.isAnimating = false;
		this.headerBlock.show();
		this.headerBlockOpen.hide();
		
        
        
        var searchInputs = $('topSearchInputs');
        if(searchInputs) { $('topSearchInputs').show(); }
        
		
		gidLib.showDropDowns(this.hiddenDropDowns);

		Event.stopObserving(document,"resize",this.checkInlineBag.bind(this));
		
		Event.stopObserving(document,"mousedown",this.checkInlineBag.bind(this));
		this.removeFocusFromInlineBag();
	},
	
	/** 
	 * closeInlineBagMarketing closes the marketing portion of the inline bag display
	 * 
	 * Modified 12/04/2007 Byung Kim - added reference to closeInlineBagMarketingAfterFinish
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	closeInlineBagMarketing:function() {
		Effect.SlideUp(inlineBag.marketingContentElementId,
			{
				duration:0.5,
				afterFinish:this.closeInlineBagMarketingAfterFinish.bind(this)
			}
		);
	},
	
	/**
	 * closeInlineBagMarketingAfterFinish executes after the inline bag marketing closing animation finishes
	 * 
	 * @author Byung Kim
	 * @date 12/04/2007
	 */
	closeInlineBagMarketingAfterFinish:function() {
		this.hasMarketing = false;
		var inlineBag = this;
		new PeriodicalExecuter(function(pe) {
			inlineBag.closeInlineBag(true);
			pe.stop();
		},this.marketingDelay);
	},
	
	/** 
	 * setInlineShoppingBagData updates the DOM with the response from the inlineBag data call.
	 * All markup passed to this method is generated within the data call.
	 * @param {string} subTotal The markup for the subTotal
	 * @param {string} markup The markup for the inlinebag display
	 * @param {string} openTotal The markup for the total when the inlinebag is open
	 *  
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	setInlineShoppingBagData:function(subTotal,markup,openTotal) {
		this.setSubTotal(subTotal,openTotal);
	
		/*
		 *	Hacked resetting of "absolute" position for inside bag items.
		 *  This is a hacky workaround for a problem with IE6 in which the
		 *  absolutely styled elements were not having their height and width
		 *  set initially
		 */
		Element.setStyle(this.contentClipElementId,{position: 'absolute'});
		Element.setStyle(this.marketingClipElementId,{position: 'absolute'});
	
		if (markup) $(this.contentDataElementId).update(markup);
		this.positionInlineBag();
	},

	/**
	 * setInlineShoppingBagMarketing updates the marketing message portion of the inline bag display from the data call
	 * @param {string} markup The markup for the marketing message.  The markup is generated in the data call.
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	setInlineShoppingBagMarketing:function(markup) {
		if (markup) $(this.marketingDataElementId).update(markup);
		this.hasMarketing = true;
	},

	/**
	 * setSubTotal sets the subTotal display in the close and open state of the inline bag header
	 * @param {string} subTotal The closed header markup
	 * @param {string} openTotal the open header markup
	 *  
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	setSubTotal:function(subTotal,openTotal) {
		$(this.priceElementId).update(subTotal);
		if (openTotal) $(this.priceOpenElementId).update(openTotal);
	},

	/**
	 * checkInlineBag checks if the inlinebag is open and closes it.  This is set as a document.mousedown event when the inline bag is open.
	 * This event is removed when the inline bag is closed.
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	checkInlineBag:function() {
		if (this.isOpen) {
			var inlineBag = this;
			new PeriodicalExecuter(function(pe) {
				inlineBag.closeInlineBag();
				pe.stop();
			},0.5);
		}
	},
	
	/**
	 * Set focus to inline bag for accessibilty story #216
	 * 
	 * Modified 12/04/2007 Byung Kim - added local variable and updated references
	 * Modified 2/2/08 Amy Tang - changed focus to inlineBagItemMessage for screen reader
	 *
	 * @author Hillel Familant
	 * @date 12/1/2007
	 */
	setFocusToInlineBag:function() {
		gidLib.setFocus($('inlineBagItemMessageItem'));
	},
	
	/**
	 * Set focus to inline bag Item Message Item for screenreader story #747
	 * 
	 * 
	 * @author Amy Tang 
	 * @date 2/2/2009
	 */
	setInLineBagMessage:function(message){
		$('inlineBagItemMessageItem').title = message;
	},	
	
	
	/**
	 * Remove focus from inline bag to one of the main containers:
	 * mainContainer, bodyContainer, etc.. for accessibilty story #216
	 *
	 * Modified 12/04/2007 Byung Kim - added local variables and updated references
	 * 
	 * @author Hillel Familant
	 * @date 12/1/2007
	 */
	removeFocusFromInlineBag:function() {
		var mainContent = $('mainContent');
		if (mainContent) gidLib.setFocus(mainContent);
	}
}
var inlineBag = new InlineBag();


/**
 * Site Navigation is a class for tools related to navigating the site.
 * Instantiated as siteNavigation.
 * 
 * @constructor
 * @author Andrew Southwick
 * @date 10/24/2007
 */
var SiteNavigation = Class.create();
SiteNavigation.prototype = {
	shoppingBagUrl:"/buy/shopping_bag.do",
	signInUrlTemplate:new Template("#{url}?context=nav&targetURL=#{targetUrl}"),
	signOutUrlTemplate:new Template("#{url}?returnURL=#{returnUrl}&targetURL=#{targetUrl}"),
	joinEmailUrlTemplate:new Template("/profile/join_email_list_confirm.do?targetURL=#{returnUrl}&src=#{src}#{email}"),
	relativeUrl:(top != self ? "/browse/home.do" : window.location.pathname + window.location.search + window.location.hash),
		
	/**
	 * initialize SiteNavigation
	 * 
	 * @author Andrew Southwick
	 * @date 10/24/2007
	 */
	initialize:function() {
		this.initializeSignInUrls();
	},

	/**
	 * initializeSignInUrls initializes the signIn Urls for profile
	 * 
	 * @author Byung Kim
	 * @date 11/5/2007
	 */
	initializeSignInUrls:function() {
	
	    this.relativeUrl = this.relativeUrl.replace("sem=true","");
		this.signInReturnUrl = escape(this.relativeUrl);
		this.signInTargetUrl = escape(this.relativeUrl);
		this.signInShoppingUrl = escape(this.relativeUrl);
	},
	
	/**
	 * onLoadHandler declares SiteNavigation related methods to be called onload of the page.
	 * 
	 * @author Byung Kim
	 * @date 10/30/2007
	 */
	onLoadHandler:function() {
		this.setPreviousPageUrl();
		this.footer.setFooterImages();
	},
	
	/**
	 * getAccessibleCheckout takes a user to the more accessible checkout path.  To be removed with the 4.2 release
	 * @deprecated
	 * 
	 * @author Andrew Southwick
	 * @date 10/24/2007
	 */
	getAccessibleCheckout:function() {
		if (window.location.pathname == this.shoppingBagUrl) {
			if (checkout) {
				if (shoppingBag.stateMonitor.isBuyActive == true) {
					checkout();
				}
			}
		}
		return false;
	},

	/**
	 * goToShoppingBag sends the user to the shopping bag page.  This is currently used in the TopNav.
	 * Only use when an anchor tag cannot be used.  Try to write out the URL as much as possible for SEO & accessibility reasons.
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	goToShoppingBag:function() {
		if (location.pathname != this.shoppingBagUrl) {
			location.href = this.shoppingBagUrl;
		}
	},

	/**
	 * goSignIn takes the user to the sign in page
	 * @param {string} signInHref The url for the sign in page
	 * @return false for cancelling the href or form submit
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	goSignIn:function(signInHref) {
		parent.location.href = this.signInUrlTemplate.evaluate({
	    	url:signInHref,
	    	targetUrl:this.signInTargetUrl
	    });
		return false;
	},
	
	/**
	 * goSignOut takes the user to the sign out page
	 * @param {string} signOutHref The url for the signout page
	 * @return false for cancelling the href or form submit
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	goSignOut:function(signOutHref) {
		parent.location.href = this.signOutUrlTemplate.evaluate({
			url:signOutHref,
			returnUrl:this.signInReturnUrl,
			targetUrl:this.signInTargetUrl
		});
		return false;
	},
	
	/**
	 * goJoinEmail opens the join email url
	 * @param {string} ref The referring content item element id for contentItemLink
	 * @param {string} srcValue A value to pass to the popup url
	 * @param {string} emailAddress A email address value
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	goJoinEmail:function(ref,srcValue,emailAddress) {
			var optionalEmail = (emailAddress != null && emailAddress != "" ? "&emailAddress=" + emailAddress : "");
			var strGoURL = this.joinEmailUrlTemplate.evaluate({
				returnUrl:this.signInReturnUrl,
				src:srcValue,
				email:optionalEmail
			});
			gidLib.contentItemLink(ref,strGoURL);
	},
	
	/**
	 * setPreviousPageUrl creates a URL for the current page to be set in a cookie
	 * for use to navigate back to the page.  This is done ONLY if the page is one of 
	 * the following:
	 *
	 *	home
	 *	division or subdivision
	 *	category
	 *	outfit
	 * 
	 * 10/30/2007 Byung Kim - moved method to SiteNavigation Class.
	 * 
	 * @author Aaron Liebling
	 * @date 10/25/2007
	 */
	setPreviousPageUrl:function() {
		var path = location.pathname;
		
		if (/^\/$/.exec(path) ||
			/\/browse\/home\.do/.exec(path) ||
			/division\.do/.exec(path) ||
			/subDivision\.do/.exec(path) ||
			/category.*\.do/.exec(path) ||
			/outfit\.do/.exec(path)) {		
			var url = path + location.search;
			gidLib.setCookieVar("globalSession","previousPageUrl",url);
		}
	},
	

	/**
	 * footer is a subclass of SiteNavigation for footer related functionality.
	 * @base SiteNavigation
	 * 
	 * @author Byung Kim
	 * @date 10/25/2007
	 */
	footer:{
		
		/**
		 * setFooterImages sets properties for footer image swapping
		 * Modified: Byung Kim 11/5/07 -- moved footer image definition from brandFunctions to brandConstants.  Now just read if the object exists in brandConst and extend properties.
		 * 
		 * @author Byung Kim
		 * @date 10/25/2007
		 */
		setFooterImages:function() {
			if (brandConst.footer && brandConst.footer.IMAGES) Object.extend(this,brandConst.footer.IMAGES);
		}
	},
	
	/**
	 * topNav is a subclass of SiteNavigation for TopNav related functionality.
	 * @base SiteNavigation
	 * 
	 * @author Byung Kim
	 * @date 10/31/2007
	 */
	topNav: {
		activeDivision:"",
		divisionTimer:0,
		subDivisionTimer:0,
		
		/**
		 * divisionsOver handles the mouseover event for Division Navigation.
		 * This is to be extended in brandFunctions.js
		 * 
		 * @author Byung Kim
		 * @date 10/31/2007
		 */
		divisionOver:function() {},

		/**
		 * divisionsOut handles the mouseout event for Division Navigation.
		 * This is to be extended in brandFunctions.js
		 * 
		 * @author Byung Kim
		 * @date 10/31/2007
		 */
		divisionOut:function() {},

		/**
		 * subDivisionOver handles the mouseover event for SubDivision Navigation.
		 * This is to be extended in brandFunctions.js
		 * 
		 * @author Byung Kim
		 * @date 10/31/2007
		 */
		subDivisionOver:function() {},

		/**
		 * subDivisionOut handles the mouseout event for SubDivision Navigation.
		 * This is to be extended in brandFunctions.js
		 * 
		 * @author Byung Kim
		 * @date 10/31/2007
		 */
		subDivisionOut:function() {},

		/**
		 * subDivisionsOver handles the mouseover event for the whole SubDivision Navigation Menu.
		 * This is to be extended in brandFunctions.js
		 * 
		 * @author Byung Kim
		 * @date 10/31/2007
		 */
		subDivisionsOver:function() {},

		/**
		 * subDivisionsOut handles the mouseout event for the whole SubDivision Navigation Menu.
		 * This is to be extended in brandFunctions.js
		 * 
		 * @author Byung Kim
		 * @date 10/31/2007
		 */
		subDivisionsOut:function() {},

		/**
		 * hideSubDivisions hides the subdivision navigation menu.
		 * This is to be extended in brandFunctions.js
		 * 
		 * @author Byung Kim
		 * @date 10/31/2007
		 */
		hideSubDivisions:function() {}
	},
	
	/**
	 * searchBox is a subclass of SiteNavigation for functionality related to the site search form
	 * @base SiteNavigation
	 */
	searchBox: {
		hasSearchText:false,
		
		/**
		 * checkSearchText
		 * once you are on a search page the hasSearchText variable will aways be true - so check the default text explicitly
		 * on clearing the field - also get rid of any error class on the field - so when the user types, it is standard text
		 * 
		 */
		checkSearchText:function(objFormField) {
			if (objFormField.value == this.strDefaultText) {
				if (objFormField.className != "searchTextStandard") {
					objFormField.className = "searchTextStandard";
				}
				objFormField.value = "";
			}
		},
		
		/**
		 * checkInput checks that the user didn't click go with an invalid search term (blank or the default text)
		 * 
		 */
		checkInput:function (objForm) {
			
			var searchTextInput = objForm.searchText.value.trim();
			
			if ((searchTextInput == "") || (searchTextInput == this.strDefaultText)) {
				objForm.searchText.value = this.strDefaultText;
				objForm.searchText.className = "searchTextError";
				return false;
			} else {
				/**
 				* Modified:  Keo 07/28/08 - Added wrapper for G to H 
 				*/
				if(!(reportingService||{}).isActive){
					omni.objSearchProcessor.setKeyword(objForm.searchText.value, objForm.searchDivName.options[objForm.searchDivName.selectedIndex].text, omni.strCurrentPageName);
				} else {
					var searchAppManager = reportingService.controller.appManagers.searchAppManager;
					var commonModel = reportingService.controller.viewManagers.commonViewManager.model;
					var text = objForm && objForm.searchDivName ? objForm.searchDivName.options[objForm.searchDivName.selectedIndex].text : '';
					searchAppManager.setKeyword(objForm.searchText.value, text, commonModel.commonCurrentPageName);
				}
				return true;
			}
		}
	},
	
	/**
	 * sideNav is a subclass of SiteNavigation for functionality related to the sidenav.
	 * 
	 * TODO: cleanup
	 */
	sideNav: {
		rootElement:$("sideNavCategories"),
		strSelectedHeaderNodeId:"5003",
		strUserExpandedHeaderNodeId:"",
		objCategoryListElement:null,
		objCategoryNavElement:null,

		setCategoryGroupDisplay:function(strCategoryGroupId, strElementExtension) {
			var strExtension = "";
			if (strElementExtension) strExtension = strElementExtension;
			if (strCategoryGroupId != this.strSelectedHeaderNodeId) {
				this.setPreviousCategoryElements(strElementExtension);
				this.setCategoryElements(strCategoryGroupId, strExtension);
				if (this.strUserExpandedHeaderNodeId == "") {
					this.setDisplay(this.objCategoryListElement, this.objCategoryNavElement, true, strElementExtension);
					this.strUserExpandedHeaderNodeId = strCategoryGroupId;
				}
				else if (strCategoryGroupId == this.strUserExpandedHeaderNodeId) {
					
					
					this.setDisplay(this.objPreviousCategoryListElement, this.objPreviousCategoryNavElement, false, strElementExtension);
					this.strUserExpandedHeaderNodeId = "";
				}
				else if (strCategoryGroupId != this.strUserExpandedHeaderNodeId) {
					
					
					this.setDisplay(this.objPreviousCategoryListElement, this.objPreviousCategoryNavElement, false, this.strPreviousElementExtension);
					this.setDisplay(this.objCategoryListElement, this.objCategoryNavElement, true, strElementExtension);
					this.strUserExpandedHeaderNodeId = strCategoryGroupId;
				}
			}
		},
	
		setCategoryElements:function(strCategoryGroupId, strExtension) {
			this.objCategoryListElement = $("categoryList" + strCategoryGroupId + strExtension);
			this.objCategoryNavElement = $("categoryNav" + strCategoryGroupId + strExtension);
		},
	
		setPreviousCategoryElements:function(strElementExtension) {
			this.objPreviousCategoryListElement = this.objCategoryListElement;
			this.objPreviousCategoryNavElement = this.objCategoryNavElement;
			
			this.strPreviousElementExtension = strElementExtension;
		},
	
		setDisplay:function(objCategoryListElement, objCategoryNavElement, isVisible, strElementExtension) {
			
			if (objCategoryListElement && objCategoryNavElement) {
				var strCssClassSuffix = "";
				if (strElementExtension == "Scrolling") {
					
					strCssClassSuffix = " scrolling";
					
				}
	
				if (isVisible) {
					objCategoryNavElement.className = "header expanded" + strCssClassSuffix;
					objCategoryListElement.style.display = "list-item";
				}
				else {
					objCategoryNavElement.className = "header collapsed" + strCssClassSuffix;
					objCategoryListElement.style.display = "none";
				}
				
	 		}
		},
	
		setTrimHeaderSeeAll:function(strCategoryGroupId) {
			this.objCategoryNavElementLimited = $("categoryNav" + strCategoryGroupId + "Limited");
			this.objCategoryNavElementScrolling = $("categoryNav" + strCategoryGroupId + "Scrolling");
		
			if ((this.objCategoryNavElementLimited != null) || (this.objCategoryNavElementScrolling != null)) {
				this.objCategoryNavElementLimited.style.display = "none";
				this.objCategoryNavElementScrolling.style.display = "list-item";
			}
		}
	}
};
var siteNavigation = new SiteNavigation();


/**
 * GIDBrandSiteConstructor is a class that is a data store for sister GID site data.
 * Instantiated as gidBrandSiteConstruct in JsGlobalFunctions.jsp by a method in NavLogicTag.java
 * @constructor 
 * 
 * @author Byung Kim
 * @date 10/24/2007
 */
var GIDBrandSiteConstructor = Class.create();
GIDBrandSiteConstructor.prototype = {
	
	/**
	 * initialize GIDBrandSiteConstructor
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	initialize:function(
		currentBrandCode,
		cookieDomain,
		currentBrandSiteId,
		sbsViewAllState,
		sitesVisited,
		sbsPreferences,
		sslPrefix,
		isCommonDomainOn,
		renderOnCurrentBrandUrl,
		sisterSiteActionPath,
		gidBrandSites) {
		this.currentBrandCode = currentBrandCode;
		this.cookieDomain  = cookieDomain;
		this.currentBrandSiteId = currentBrandSiteId;
		this.sbsViewAllState = sbsViewAllState;
		this.sbsPreferences = sbsPreferences;
		this.sslPrefix = sslPrefix;
		this.isCommonDomainOn  = isCommonDomainOn;
		this.renderOnCurrentBrandUrl  = renderOnCurrentBrandUrl;
		this.sisterSiteActionPath = sisterSiteActionPath;
		this.hasGIDBrandSites = (gidBrandSites && gidBrandSites.length > 0);
		this.sisterSiteUrlParams = "?isViewAll=" + this.sbsViewAllState + this.sbsPreferences;
		this.tempBrandSites = gidBrandSites;
		this.gidBrandSites = new Array();
		this.setBrandSites();
		this.visitedSites = sitesVisited;

		
		if (this.hasGIDBrandSites && !this.isCommonDomainOn) Event.onDOMReady(sisterSiteSync.syncUser.bind(sisterSiteSync));
	},

	/**
	 * setBrandSites adds brand site objects to gidBrandSites array. The index is the brandCode.
	 * This is done for easy referencing.
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	setBrandSites:function() {
		for (var i=0;i<this.tempBrandSites.length;i++) {
			var brandSite = this.tempBrandSites[i];
			this.gidBrandSites[Number(brandSite.brandCode)] = brandSite;
			var badgePath = brandConst["BRAND"+brandSite.brandCode+"_BADGE_CONTENT_PATH"]; 
			if (badgePath) this.gidBrandSites[Number(brandSite.brandCode)].badgeContentPath = badgePath; 
		}
	},
	
	/**
	 * openBrand opens another brand's site in the same browser window
	 * @param {integer} brandcode The brand to open
	 * @param {string} trackingId The tracking Id
	 * @return false to cancel the href
	 * 
	 * @author Byung Kim
	 * @date 2/5/2008
	 */
	openBrand:function(brandcode,trackingId) {
		var visited = (this.visitedSites != null ? this.visitedSites : "");
		var brandSite = this.gidBrandSites[brandcode];
		var currentBrandSite = this.gidBrandSites[this.currentBrandCode];
		var url = "";
		if (brandSite) {
			if(this.renderOnCurrentBrandUrl){
				url = currentBrandSite.unsecureUrl + this.sisterSiteActionPath;
			}else{
				url = brandSite.unsecureUrl + this.sisterSiteActionPath;
			}
			url += "?ssiteID=" + (trackingId ? trackingId : this.currentBrandSiteId)+ "&lBrdCd=" + brandcode;

			parent.location.href = url;
		}
		return false;
	}
}


/**
 * SisterSiteSync is a class used for cross domain user data synchronization.
 * Instantiated as sisterSiteSync.
 * @constructor
 * 
 * @author Byung Kim
 * @date 10/24/2007
 */
var SisterSiteSync = Class.create();
SisterSiteSync.prototype = {
	hasUserSyncFailed:false,
	synchronizationPage:"/gid/html/en/userSynchronization.html",
	serverCookies:{
		jSessionId:{param:"jsid",cookieName:"JSESSIONID",isPersist:false,isSecure:false,isServerCookie:true},
		gidSecureToken:{param:"gst",cookieName:"gidSecureToken",isPersist:false,isSecure:true,isServerCookie:true},
		unknownShopperId:{param:"usid",cookieName:"unknownShopperId",isPersist:true,isSecure:false,isServerCookie:true,expTime:{"days":72}},
		customerId:{param:"cid",cookieName:"customerId",isPersist:true,isSecure:false,isServerCookie:true,expTime:{"days":72}},
		customerIdVerified:{param:"cidv",cookieName:"customerIdVerified",isPersist:true,isSecure:false,isServerCookie:true,expTime:{"days":72}},
		mktUniversalPersist:{param:"mup",cookieName:"mktUniversalPersist",isPersist:true,isSecure:false,isServerCookie:false,expTime:{"years":5}},
        brandTidMap:{param:"brandTidMap",cookieName:"BRAND_TID_MAP",isPersist:true,isSecure:false,isServerCookie:true,expTime:{"days":7}},
		partner:{param:"partner",cookieName:"PARTNER",isPersist:true,isSecure:false,isServerCookie:true,expTime:{"days":7}},
        locale:{param:"locale",cookieName:"locale",isPersist:true,isSecure:false,isServerCookie:true,expTime:{"days":72}}
	},
	
	/**
	 * initialize for SisterSiteSync
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	initialize:function() {
	},


	/**
	 * createIFrame creates an IFrame and calls the userSynchronization.html page to synchronize data
	 * @param {object} gidBrandSite An object with all the relevant info about the brand
	 * @param {string} params The querystring parameters to attach to the url
	 *  
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	createIFrame:function(gidBrandSite,params) {
		var frameID = "brand"+gidBrandSite.brandCode+"DataSync";
		var cont = document.body;
		var isSsl = (location.protocol == "https:");
		var brandSiteUrl = (isSsl ? gidBrandSite.secureUrl : gidBrandSite.unsecureUrl);
		if (brandSiteUrl && brandSiteUrl.length > 0 && cont) {
			var newIFrame = $(document.createElement("IFRAME"));
			newIFrame.id = frameID;
			newIFrame.style.display = "none";
			newIFrame.src = brandSiteUrl + this.synchronizationPage + "?" + params;
			cont.appendChild(newIFrame);
		}
	},
	
	/**
	 * syncUser synchronizes cookie information to all the GID sister brand sites
	 * 
	 * Modified 2/12/2008 Byung Kim - added mktuniversalpersist cookie and cleaned up code 
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	syncUser:function() {
		if (navigator.cookieEnabled && gidBrandSiteConstruct.hasGIDBrandSites && !gidBrandSiteConstruct.isCommonDomainOn) {
			var aParams = [];
  			var securePrefix = null;
			var unknownShopperId = this.serverCookies.unknownShopperId;
			var jSessionId = this.serverCookies.jSessionId;
			var customerId = this.serverCookies.customerId;
			var customerIdVerified = this.serverCookies.customerIdVerified;
			var mktUniversalPersist = this.serverCookies.mktUniversalPersist; 
			var gidSecureToken = this.serverCookies.gidSecureToken;
            var partner = this.serverCookies.partner;
            var brandTidMap = this.serverCookies.brandTidMap;
            var locale = this.serverCookies.locale;
            
			
			aParams.push("brand=" + escape(location.host));

			
			var usid = gidLib.getCookie(unknownShopperId.cookieName);
			if (usid && usid != "") aParams.push(unknownShopperId.param+"="+escape(usid));
			
			
			var jsid = gidLib.getCookie(jSessionId.cookieName);
			if (jsid && jsid != "") aParams.push(jSessionId.param+"="+escape(jsid));


			
			var cid = gidLib.getCookie(customerId.cookieName);
			if (cid && cid != "") aParams.push(customerId.param+"="+escape(cid));

			
			var cidv = gidLib.getCookie(customerIdVerified.cookieName);
			if (cidv && cidv != "") aParams.push(customerIdVerified.param+"="+escape(cidv));

			
			var mup = gidLib.getCookie(mktUniversalPersist.cookieName);
			if (mup && mup != "") aParams.push(mktUniversalPersist.param+"="+escape(mup)); 

			
			var gst = gidLib.getCookie(gidSecureToken.cookieName);
			if (gst && gst != "") aParams.push(gidSecureToken.param+"="+escape(gst));

            
            var partnerCookie = gidLib.getCookie(partner.cookieName);
            if (partnerCookie && partnerCookie != "") aParams.push(partner.param+"="+escape(partnerCookie));              

            
            var brandTidMapCookie = gidLib.getCookie(brandTidMap.cookieName);
            if (brandTidMapCookie && brandTidMapCookie != "") aParams.push(brandTidMap.param+"="+escape(brandTidMapCookie));  
            
            
			var localeCode = gidLib.getCookie(locale.cookieName);
			if (localeCode && localeCode != "") aParams.push(locale.param + "=" + escape(localeCode));
			
			
			aParams.push("sslPrefix=" + gidBrandSiteConstruct.sslPrefix);

			var sParams = aParams.join("&");

			for (i=0;i<gidBrandSiteConstruct.gidBrandSites.length;i++) {
				var gidBrandSite = gidBrandSiteConstruct.gidBrandSites[i];
				if (gidBrandSite && gidBrandSite.brandCode != gidBrandSiteConstruct.currentBrandCode) {
					this.createIFrame(gidBrandSite,sParams);
				}
			}
		}
	},
	/**
	 * userSyncFailed displays a message stating that cookie synchronization failed.
	 * It also sets a global boolean flag hasUserSyncFailed.
	 * Finally it sets the appropriate reporting metrics for G and H code.
	 * 
	 * Modified 8/19/2008 Andrew Southwick - moved to gidFunctions to re-enable broken
	 * functionality related to synch failure.  This was broken when this method was
	 * moved to userSynchronization.html which redirects in the event of failure to 
	 * userSyncFailure.html which then calls the parent userSyncFailed method below.
	 * 
	 * @author Byung Kim
	 * @date 10/24/2007
	 */
	userSyncFailed:function() {
		var message = $('thirdPartyCookieBlockedMessage');
		if (!this.hasUserSyncFailed) {
			if (message) {
				var cookieMessage = $(document.createElement("div"));
				cookieMessage.setStyle({
					padding:"10px",
					margin:"10px 0px",
					background:"#fff",
					border:"2px solid #000",
					fontSize:"12px",
					fontWeight:"bold",
					color:"#000"
				});
				cookieMessage.update(message.innerHTML);
				$('topNav').insertBefore(cookieMessage,$('universalTopNav'));
			}
			if (getCookieVar("globalSession","userSyncFailed") != "true") {
				setCookieVar("globalSession","userSyncFailed","true");
				if(!(reportingService||{}).isActive){
					
					var strViewType = omni.strViewType;
					omni.strViewType = "thirdPartyCookieBlocked";
					omni.setTransmitData();
					omni.strViewType = strViewType;
				}
				else {
					
					var commonViewManager = reportingService.controller.viewManagers.commonViewManager;
					var commonModel = reportingService.controller.viewManagers.commonViewManager.model;
					commonModel.commonIsThirdPartyCookieBlocked = true;
					commonViewManager.controller.getReportRequest();
				}
			}
		}
		this.hasUserSyncFailed = true;
	}
};
var sisterSiteSync = new SisterSiteSync();

/**
 * Survey is a class to manage all of the pixel tracking
 * 
 * TODO: move survey code out of brandFunctions to this class.
 * TODO: instantiate the class
 * 
 * @constructor
 * 
 * @author Byung Kim
 * @date 11/1/2007
 */
var Survey = Class.create();
Survey.prototype = {
	cookieExpiration:null,
	
	/**
	 * initialize Survey
	 * 
	 * @author Byung Kim
	 * @date 11/1/2007
	 */
	initialize:function() {
		this.cookieExpirationTime = gidLib.getFutureDate({"weeks":26}); 
	}
	 
};


var DateUtils = Class.create();

DateUtils.prototype = {
	currentDate:null,
	expireDate:null,
	expireDateFromCookie:null,
	initialize:function() {
		
	},

	getDateFromCookieVar:function(cookieName,varName) {
		var cookieVarTimeValue = gidLib.getCookieVar(cookieName,varName);
		var date = null;
		var currentDate = new Date();
		var dateMsValue = null;
		
		
		
		if ((isNaN(cookieVarTimeValue) == false) && (cookieVarTimeValue != "")) {
			
			dateMsValue = parseInt(cookieVarTimeValue);
			currentDate.setTime(dateMsValue);
			date = currentDate;
		}

		return date;
		
	},
	setDateToCookieVar:function(cookieName, varName, date) {
		
		var dateTimeString = date.getTime() + "";
		setCookieVar(cookieName, varName, dateTimeString);
	},
	getDateComparisonPresentOrFutureByDay:function(expirationDate,compareDate) {
		
		var isDatePresentOrFutureByDay = false;
		var expirationDateDay = expirationDate.getDate();
		var expirationDateMonth = expirationDate.getMonth();
		var expirationDateYear = expirationDate.getYear();
		var compareDateDay = compareDate.getDate();
		var compareDateMonth = compareDate.getMonth();
		var compareDateYear = compareDate.getYear();
		if ((expirationDateDay == compareDateDay) && (expirationDateMonth == compareDateMonth) && (expirationDateYear == compareDateYear)) {
			isDatePresentOrFutureByDay = true;
		}
		else if (compareDate > expirationDate) {
			isDatePresentOrFutureByDay = true;
		}
		return isDatePresentOrFutureByDay;
	},
	getFutureDateByDay:function(daysToAdd) {
		
		var futureDate = new Date();
		futureDate.setDate(futureDate.getDate()+daysToAdd);
		return futureDate;
	}
}

gidLib.dateUtils = new DateUtils();

var PerformanceMonitor = Class.create();
PerformanceMonitor.prototype = {
	domReadyDate:null,
	domReadyTime:null,
	domLoadDate:null,
	domLoadTime:null,
	pageReadyDate:null,
	pageReadyTime:null,
	initDate:null,
	initTime:null,
	previousPageName:null,
	previousPageBusinessId:null,
	previousPageUnloadDate:null,
	previousPageUnloadTime:null,
	timeToReadyFromPreviousPage:null,
	timeToReadyFromInit:null,
	timeToCheckoutReadyFromPreviousPage:null,
	timeToLoadFromInit:null,
	timeToLoadFromPreviousPage:null,
	hasNoPreviousPage:false,
	isReferrerValid:false,
	isTestEnvironment:false,
	initialize:function() {
		
	},
	reporting:{
		timeToReadyFromPreviousPage:null,
		timeToReadyFromPreviousPageFormatted:null,
		timeToLoadFromPreviousPage:null,
		timeToLoadFromPreviousPageFormatted:null,
		previousPage:{
			businessId:null,
			timeToLoadFromPreviousPage:null,
			timeToLoadFromPreviousPageFormatted:null
		}
	},
	controller:{
		init:{
			main:function() {
				var pm = performanceMonitor;
				Event.onDOMReady(pm.controller.pageOnDOMReadyHandler);
				Event.observe(window, 'load', pm.controller.pageOnLoadHandler);
				Event.observe(window, 'beforeunload', pm.controller.pageOnUnloadHandler);
				pm.initDate = new Date();
				pm.initTime = pm.initDate.getTime();
				pm.controller.init.getCookieData();
			},
			getCookieData:function() {
				var pm = performanceMonitor;
				var previousPageUnloadDate = gidLib.dateUtils.getDateFromCookieVar('globalSession', 'previousPageUnloadDate');
				if (previousPageUnloadDate != null) {
					pm.previousPageName = gidLib.getCookieVar('globalSession', 'previousPageName');
					pm.previousPageUnloadDate = gidLib.dateUtils.getDateFromCookieVar('globalSession', 'previousPageUnloadDate');
					if (pm.previousPageUnloadDate instanceof Date) {
						pm.previousPageUnloadTime = previousPageUnloadDate.getTime();
					}
					pm.reporting.previousPage.timeToLoadFromPreviousPage = gidLib.getCookieVar('globalSession', 'timeToLoadFromPreviousPage');
					pm.reporting.previousPage.businessId = gidLib.getCookieVar('globalSession', 'previousPageBusinessId');
				}
				else {
					pm.hasNoPreviousPage = true;
				}
			}
		},
		pageOnUnloadHandler:function() {
			var dateUtils = gidLib.dateUtils;
			var date = new Date();
			var pageName = null;
			var businessId = null;
			var timeToLoadFromPreviousPage = (window["performanceMonitor"] ? performanceMonitor.reporting.timeToLoadFromPreviousPage : "");
			if(!(window['reportingService']||{}).isActive) {
				pageName = (window["omni"] ? omni.strCurrentPageName : ""); 
		        businessId = (window["omni"] ? omni.strCurrentBusinessId : "");
			}
			else {
				pageName = reportingService.controller.viewManagers.commonViewManager.model.commonCurrentPageName;
				businessId = reportingService.controller.viewManagers.commonViewManager.model.commonCurrentBusinessId;
			}
	        dateUtils.setDateToCookieVar('globalSession', 'previousPageUnloadDate', date);
	        gidLib.setCookieVar('globalSession', 'previousPageName', pageName);
			gidLib.setCookieVar('globalSession', 'previousPageBusinessId', businessId);
			gidLib.setCookieVar('globalSession', 'timeToLoadFromPreviousPage', timeToLoadFromPreviousPage);
		},
		pageOnDOMReadyHandler:function() {
			var pm = performanceMonitor;
			var timeToReadyFromPreviousPage = null;
			var timeToReadyFromInit = null;
			var domReadyDate = new Date();
			var domReadyTime = domReadyDate.getTime();
			pm.domReadyDate = domReadyDate;
			pm.domReadyTime = domReadyTime;
			
			if (pm.previousPageUnloadTime != null) {
				if (pm.controller.isReferrerValid()) {
					performanceMonitor.isReferrerValid = true;
					timeToReadyFromPreviousPage = domReadyTime - pm.previousPageUnloadTime;
					pm.timeToReadyFromPreviousPage = timeToReadyFromPreviousPage;
					pm.controller.setReportingMetrics();
				}
			}
			if (pm.initTime != null) {
				timeToReadyFromInit = domReadyTime - pm.initTime;
				pm.timeToReadyFromInit = timeToReadyFromInit;
			}
		},
		pageOnLoadHandler:function() {
			var pm = performanceMonitor;
			var timeToLoadFromPreviousPage = null;
			var timeToLoadFromInit = null;
			var domLoadDate = new Date();
			var domLoadTime = domLoadDate.getTime();
			pm.domLoadDate = domLoadDate;
			pm.domLoadTime = domLoadTime;
			
			if (pm.previousPageUnloadTime != null) {
				if (pm.isReferrerValid == true) {
					timeToLoadFromPreviousPage = domLoadTime - pm.previousPageUnloadTime;
					pm.timeToLoadFromPreviousPage = timeToLoadFromPreviousPage;
					pm.controller.setDomLoadMetrics();
				}
			}
			if (pm.initTime != null) {
				timeToLoadFromInit = domLoadTime - pm.initTime;
				pm.timeToLoadFromInit = timeToLoadFromInit;
			}
		},
		/**
		 * pageOnPageReadyHandler records the time it takes to for a page to be ready for interaction by the user on pages with post-load operations.
		 * This needs to be called manually once the post-load operations are completed but before the omniture transmission. 
		 */
		pageOnPageReadyHandler:function() {
			var pm = performanceMonitor;
			var timeToPageReadyFromPreviousPage = null;
			var timeToPageReadyFromInit = null;
			var timeToPageReadyFromDomReady = null;
			var timeToPageReadyFromOnLoad = null;
			var pageReadyDate = new Date();
			var pageReadyTime = pageReadyDate.getTime();
			pm.pageReadyDate = pageReadyDate;
			pm.pageReadyTime = pageReadyTime;
			
			
			if (pm.previousPageUnloadTime != null) {
				if (pm.isReferrerValid == true) {
					timeToPageReadyFromPreviousPage = pageReadyTime - pm.previousPageUnloadTime;
					pm.timeToPageReadyFromPreviousPage = timeToPageReadyFromPreviousPage;
				}
			}
			
			
			if (pm.initTime != null) {
				timeToPageReadyFromInit = pageReadyTime - pm.initTime;
				pm.timeToPageReadyFromInit = timeToPageReadyFromInit;
			}
			
			
			if (pm.domReadyTime != null) {
				timeToPageReadyFromDomReady = pageReadyTime - pm.domReadyTime;
				pm.timeToPageReadyFromDomReady = timeToPageReadyFromDomReady;
			}
			
			
			if (pm.domLoadTime != null) {
				timeToPageReadyFromOnLoad = pageReadyTime - pm.domLoadTime;
				pm.timeToPageReadyFromOnLoad = timeToPageReadyFromOnLoad;
			}
			
		},
		
		/**
		 * setTimeToReadyAsTimeToPageReady overrides the timeToReadyFromPreviousPage value with the timeToPageReadyFromPreviousPage value
		 * This used to report more accurate page READY times on pages that have subsequent AJAX calls. e.g. product page
		 */
		setTimeToReadyAsTimeToPageReady : function() {
			var pm = performanceMonitor;
			pm.timeToReadyFromPreviousPage = pm.timeToPageReadyFromPreviousPage;
			pm.controller.setReportingMetrics();
		},

		/**
		 * setTimeToLoadAsTimeToPageReady overrides the timeToLoadFromPreviousPage value with the timeToPageReadyFromPreviousPage value
		 * This used to report more accurate page LOAD times on pages that have subsequent AJAX calls. e.g. product page
		 */
		setTimeToLoadAsTimeToPageReady : function() {
			var pm = performanceMonitor;
			pm.timeToLoadFromPreviousPage = pm.timeToPageReadyFromPreviousPage;
			pm.controller.setDomLoadMetrics();
		},
		
		
		isReferrerValid:function() {
			var isReferrerValid = false;
			var isTestEnvironment = false;
			var referrer = null;
			var brandUrl = brandConst.BRAND_URL;
			var expressionValue = "^https?://.*(" + brandUrl + "|127.0.0.1)";
			var expressionValueForTestEnvironments = "^https?://.*(" + brandUrl + "|gidgol.com|gidgol.ca|gidgol.co.uk|gidapps.com|gidapps.ca|gidapps.co.uk)";
			var urlExpression = new RegExp(expressionValue, "i");
			var urlExpressionForTestEnvironments = new RegExp(expressionValueForTestEnvironments, "i");
			if (document.referrer) {
				referrer = document.referrer;
				isReferrerValid = urlExpression.test(referrer);
				if (isReferrerValid == false) {
					
					isReferrerValid = urlExpressionForTestEnvironments.test(referrer);
					if (isReferrerValid == true) {
						isTestEnvironment = true;
					}
				}
			}
			if (clientBrowser.isIE6up) {
				if (performanceMonitor.previousPageUnloadDate != null) {
					isReferrerValid = true;
				}
			}
			performanceMonitor.referrer = referrer;
			performanceMonitor.expressionValue = expressionValue;
			performanceMonitor.isReferrerValid = isReferrerValid;
			performanceMonitor.isReferrerValid = isTestEnvironment;
			return isReferrerValid;
		},
		
		setReportingMetrics:function() {
			var pm = performanceMonitor;
			var reporting = pm.reporting;
			var isReferrerValid = pm.isReferrerValid;
			var timeToReadyFromPreviousPageFormatted = null;
			var timeToReadyFromPreviousPage = pm.timeToReadyFromPreviousPage;
			var previousPage = reporting.previousPage;
			var timeToLoadFromPreviousPage = previousPage.timeToLoadFromPreviousPage;
			
			if (isReferrerValid) {
				if (timeToReadyFromPreviousPage != null) {
					timeToReadyFromPreviousPageFormatted = pm.controller.getFormattedReportingTimeValue(timeToReadyFromPreviousPage);
					reporting.timeToReadyFromPreviousPageFormatted = timeToReadyFromPreviousPageFormatted;
					reporting.timeToReadyFromPreviousPage = timeToReadyFromPreviousPage;
					
					
					
					if ((timeToLoadFromPreviousPage != "") || (timeToLoadFromPreviousPage != null)) {
						if ((previousPage.businessId != "") || (previousPage.businessId != null)) {
							previousPage.timeToLoadFromPreviousPageFormatted = previousPage.businessId + ":" + pm.controller.getFormattedReportingTimeValue(timeToLoadFromPreviousPage);
							
						}
						else {
							previousPage.timeToLoadFromPreviousPageFormatted = "invalid data";
						}
					}
					else {
						previousPage.timeToLoadFromPreviousPageFormatted = "invalid data";
					}
				}
				else {
					reporting.timeToReadyFromPreviousPageFormatted = "invalid data";
					previousPage.timeToLoadFromPreviousPageFormatted = "invalid data";
					reporting.timeToReadyStatus = "invalid data";
					reporting.timeToLoadStatus = "invalid data";
				}
			}
			else {
				reporting.timeToReadyFromPreviousPageFormatted = "invalid referrer";
				previousPage.timeToLoadFromPreviousPageFormatted = "invalid referrer";
				reporting.timeToReadyStatus = "invalid referrer";
				reporting.timeToLoadStatus = "invalid referrer";
			}
			
			
			
			if (previousPage.timeToLoadFromPreviousPageFormatted != null) {
				if(!(window['reportingService']||{}).isActive) {
					s_prop42 = previousPage.timeToLoadFromPreviousPageFormatted;
				}
			}
			
			if (reporting.timeToReadyFromPreviousPageFormatted != null) {
				if(!(window['reportingService']||{}).isActive) {
					s_prop45 = reporting.timeToReadyFromPreviousPageFormatted;
				}
			}
		},
		setDomLoadMetrics:function() {
			var pm = performanceMonitor;
			var reporting = pm.reporting;
			var isReferrerValid = pm.isReferrerValid;
			var timeToLoadFromPreviousPageFormatted = null;
			var timeToLoadFromPreviousPage = pm.timeToLoadFromPreviousPage;

			if (isReferrerValid) {
				if (timeToLoadFromPreviousPage != null) {
					timeToLoadFromPreviousPageFormatted = pm.controller.getFormattedReportingTimeValue(timeToLoadFromPreviousPage);
					reporting.timeToLoadFromPreviousPageFormatted = timeToLoadFromPreviousPageFormatted;
					reporting.timeToLoadFromPreviousPage = timeToLoadFromPreviousPage;
				}
				else {
					reporting.timeToLoadStatus = "invalid referrer";
				}
			}
			else {
				reporting.timeToLoadStatus = "invalid referrer";
			}
		},
		
		/**
		 * getFormattedReportingTimeValue converts milliseconds to the desired reporting time value
		 */
		getFormattedReportingTimeValue : function(ms) {
			var reportingTime = ms;

			return reportingTime;
		}
	}
}

var performanceMonitor = new PerformanceMonitor();
performanceMonitor.controller.init.main();

Event.observe(window, 'load', function() {
	gidLib.onLoadHandler();
	gidLib.set800pxUniversalNav();
	siteNavigation.onLoadHandler();
	inlineBag.initializeElements();
});

Event.observe(window, 'beforeunload', function() {
	gidLib.onUnloadHandler();
});

Event.onDOMReady(function() {
	gidLib.setIePngTransparency($$('.pngAlpha'));
});

var LocaleUtils = Class.create();

LocaleUtils.prototype = {
	initialize:function() {
		
	},
	constants : {
					EMPTY_STRING: "", 
					PARAMETER_LOCALE: "locale", 
					QUESTIONMARK: "?", 
					PARAMETER_DELIMITER: "&", 
					EQUAL_SIGN: "=",
					ASH_SIGN: "#"
	},
	/**
	*  toggle Locale function is used to switch the locale value.
	*/
	toggleLocale : function(locale){
	    var constants = this.constants;
	    var newUrl = constants.EMPTY_STRING;
	    var localeParameter = constants.PARAMETER_LOCALE+constants.EQUAL_SIGN;
	    var previousUrl = window.location;
	    var temporaryUrl = previousUrl+constants.EMPTY_STRING;
	    if(temporaryUrl.indexOf(constants.ASH_SIGN)!=-1){
	      temporaryUrl = temporaryUrl.substring(0,temporaryUrl.length-1);
	    }
		var removedLocaleUrl = gidLib.removeQueryStringParam(temporaryUrl,constants.PARAMETER_LOCALE);
		var urlArray = removedLocaleUrl.split(constants.QUESTIONMARK);
		if(urlArray.length>=2){
		    var parameterArray=urlArray[1].split(constants.PARAMETER_DELIMITER);
			if(parameterArray.length>=1 && parameterArray[0].indexOf(constants.PARAMETER_LOCALE)==-1){
			 newURL=removedLocaleUrl+constants.PARAMETER_DELIMITER+localeParameter+locale;
			}else{
				newURL=urlArray[0]+constants.QUESTIONMARK+localeParameter+locale;
			}
		}else{
		 newURL=removedLocaleUrl+constants.QUESTIONMARK+localeParameter+locale;
		}
		window.location=newURL;
	}
}

gidLib.localeUtils = new LocaleUtils();
/*********************************************
DEPRECATED METHODS - do not use. do not remove
*********************************************/

/**
 * returnImg - returns an object with a src property
 * @deprecated - use JSON syntax to create this instead (e.g. {src:"/image/path/image.jpg"}  ).  Wrapper function is redundant.
 * 				 To preload an image, use gidLib.loadImage()
 */
function returnImg(strSrc) {
	return gidLib.loadImage(strSrc);
}

/**
 * returnObjById
 * @deprecated - use prototype method $() instead
 */
function returnObjById(strId) {
	return $(strId);
}

/**
 * replaceHTML
 * @deprecated
 */
function replaceHTML(obj, html) { 
	if(obj) obj.update(html); 
	return obj; 
}

/**
 * setObjInnerHTML 
 * @deprecated - use prototype method update() instead 
 */
function setObjInnerHTML(objLayer,strHTML) {
	if(typeof(objLayer) == 'string') {
        $(objLayer).innerHTML = strHTML;
    } else { objLayer.innerHTML = strHTML; };
}

/**
 * returnObjPosition
 * @deprecated - use prototype method Position.cumulativeOffset()
 */
function returnObjPosition(target){
	var pos = Position.cumulativeOffset($(target));
	return {x:pos[0], y:pos[1]};
}

/**
 * isPrototypeSafe
 * @deprecated - use hasOwnProperty instead to check if the key isn't a method.
 */
function isPrototypeSafe(key, object) {
	return object.hasOwnProperty(key);
}

/**
 * getElementsByAttribute
 * @deprecated - use prototype methods $$() or Element.getElementsBySelector() instead
 */
function getElementsByAttribute(objElement,strAttribute,searchValue,arrayResults) {
	if (clientBrowser.isIE && strAttribute == "class") strAttribute = "className";
	if (!arrayResults) arrayResults = new Array();
	if (objElement.hasChildNodes()) {
		for (var i in objElement.childNodes) {
			if (objElement.childNodes.hasOwnProperty(i)) {
				var objChild = objElement.childNodes[i];
				if (objChild.getAttribute &&
						objChild.getAttribute(strAttribute) &&
						(typeof searchValue != "string" ? objChild.getAttribute(strAttribute).search(searchValue) != -1 : objChild.getAttribute(strAttribute) == searchValue))
						arrayResults[arrayResults.length] = objChild;
				if (objChild.hasChildNodes && objChild.hasChildNodes()) getElementsByAttribute(objChild,strAttribute,searchValue,arrayResults);
			}
		}
	}
	return arrayResults;
}

/**
 * stringFilter - removes certain characters from a form value
 * @deprecated - use regExp matching instead
 */
function stringFilter (input, filteredValues) {
	s = input.value;
	var i;
	var returnString = "";
		for (i = 0; i < s.length; i++) {
		var c = s.charAt(i);
			if (filteredValues.indexOf(c) == -1) returnString += c;
		}
	input.value = returnString;
}

/**
 * getElementsWithMatchingId
 * @deprecated - use prototype method $$() or Element.getElementsBySelector()
 */
function getElementsWithMatchingId(idName) {
	var objContentItemContainerElements = new Array();
	var divs = document.getElementsByTagName("div");
	var y = 0;
	for (var i = 0; i < divs.length; i++) {
		var id = divs[i].getAttribute("id");
		if (id && id.match(idName)) {
			objContentItemContainerElements[y] = divs[i];
			y++;
		}
	}
	return objContentItemContainerElements;
}

/**
 * changeFormDropDownVisibility
 * @deprecated use gidLib.hideDropDownsUnderElement() instead
 */
function changeFormDropDownVisibility(state) {
	if (clientBrowser.isIE6 || clientBrowser.isIE55) {
		var arrayDropDowns = $$("select");
		var iterator = function(element) {
			if (element && element.style) element.style.visibility = state;
		}
		arrayDropDowns.each(iterator);
	}
}

/**
 * setObjVisibility
 * @deprecated use Prototype method setStyle instead
 */
function setObjVisibility(element,state) {
	if (element) element.setStyle({visibility:state});
}

/**
 * setImgSrc
 * @deprecated use prototype method setSrc() native to IMG and INPUT elements instead
 */
function setImgSrc(id, src) {
	$(id).setSrc(src);
}


var pageOnLoadFunctions = function() {}
var pageOnUnloadFunctions = function() {}
/*
 * Legacy pointer references to new gidLib methods
 */
var setCookieVar = gidLib.setCookieVar.bind(gidLib);
var setCookie = gidLib.setCookie.bind(gidLib);
var getCookie = gidLib.getCookie.bind(gidLib);
var getCookieVar = gidLib.getCookieVar.bind(gidLib);
var setObjPosition = gidLib.setObjPosition.bind(gidLib);
var setObjCenter = gidLib.setObjCenter.bind(gidLib);
var getQuerystringParam = gidLib.getQuerystringParam.bind(gidLib);
var removeQueryStringParam = gidLib.removeQueryStringParam.bind(gidLib);
var openLayeredPopup = function(a1,a2,a3,a4,a5,a6,a7,a8) {
	/* This is to capture the "return false" of the new method.
	 * Legacy implementations aren't handling the return properly.
	 */
	gidLib.layeredPopup.openLayeredPopup(a1,a2,a3,a4,a5,a6,a7,a8);
}
var closeLayeredPopup = function() {
	/* This is to capture the "return false" of the new method.
	 * Legacy implementations aren't handling the return properly.
	 */
	gidLib.layeredPopup.closeLayeredPopup();
}
var addCurrentDomain = gidLib.addCurrentDomain.bind(gidLib);
var openWindow = gidLib.openWindow.bind(gidLib);
var closeWindow = gidLib.closeWindow.bind(gidLib);
var contentItemLink = gidLib.contentItemLink.bind(gidLib);
var unUnicode = gidLib.unUnicode.bind(gidLib);
var openGiftCardWindow = gidLib.openGiftCardWindow.bind(gidLib);
var setButtonEvents = gidLib.setButtonEvents.bind(gidLib);
var setLabelOnClick = gidLib.setLabelOnClick.bind(gidLib);
var getContentItemIds = gidLib.reporting.getContentItemIds.bind(gidLib.reporting);
var selectOption = gidLib.selectOption.bind(gidLib);

var objInlineBag = inlineBag;

var goSignIn = siteNavigation.goSignIn.bind(siteNavigation);
var goSignOut = siteNavigation.goSignOut.bind(siteNavigation);
var goJoinEmail = siteNavigation.goJoinEmail.bind(siteNavigation);
var goToShoppingBag = siteNavigation.goToShoppingBag.bind(siteNavigation);
var objFooter = siteNavigation.footer;
var strRelativeURL = siteNavigation.relativeUrl;
var strSignInReturnURL = siteNavigation.signInReturnUrl;
var strSignInTargetURL = siteNavigation.signInTargetUrl;
var strSignInShoppingURL = siteNavigation.signInShoppingUrl;
var objTopNavController = siteNavigation.topNav;
var objSearchBox = siteNavigation.searchBox;
var objSideNav = siteNavigation.sideNav;

/*
 * The following Legacy references are wrapped with anonymous functions because the instantiated object is extended in brandFunctions.js
 */
var setFooterImages = function() {
	siteNavigation.footer.setFooterImages();
}
var divisionOver = function(a,b) {
	siteNavigation.topNav.divisionOver(a,b);
}
var divisionOut = function(a,b) {
	siteNavigation.topNav.divisionOut(a,b); 
}
var subDivisionOver = function(a,b) {
	siteNavigation.topNav.subDivisionOver(a,b);
}
var subDivisionOut = function (a,b) {
	siteNavigation.topNav.subDivisionOut(a,b);
}
var subDivisionsOver = function(a,b) {
	siteNavigation.topNav.subDivisionsOver(a,b);
}
var subDivisionsOut = function(a,b) {
	siteNavigation.topNav.subDivisionsOut(a,b);
}
var hideSubDivisions = function(a,b) {
	siteNavigation.topNav.hideSubDivisions(a,b);
}



/*
 * Legacy Browser Detection & Properties
 */
var objClient = clientBrowser;
Object.extend(clientBrowser,{
		intFlashVer:parseInt(clientBrowser.verFlash),
		isNav:clientBrowser.isNetscape,
		isNav7:clientBrowser.isNetscape7,
		isNav7up:clientBrowser.isNetscape7Up,
		intGver:clientBrowser.verGecko,
		isIE6up:clientBrowser.isIE6Up,
		isIE5_5:clientBrowser.isIE55,
		isIE5_5up:clientBrowser.isIE55Up
	}
);



