/*  Prototype JavaScript framework, version 1.5.0
 *  (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://prototype.conio.net/
 *
/*--------------------------------------------------------------------------*/

var Prototype = {
  Version: '1.5.0',
  BrowserFeatures: {
    XPath: !!document.evaluate
  },

  ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
  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;
    }
  },

  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).concat($A(arguments)));
  }
}

Object.extend(Number.prototype, {
  toColorPart: function() {
    var digits = this.toString(16);
    if (this < 16) return '0' + digits;
    return digits;
  },

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

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

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;
      }
    }
  }
}
String.interpret = function(value){
  return value == null ? '' : String(value);
}

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 div = document.createElement('div');
    var text = document.createTextNode(this);
    div.appendChild(text);
    return 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 name = decodeURIComponent(pair[0]);
        var value = pair[1] ? decodeURIComponent(pair[1]) : undefined;

        if (hash[name] !== undefined) {
          if (hash[name].constructor != Array)
            hash[name] = [hash[name]];
          if (value) hash[name].push(value);
        }
        else hash[name] = 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);
  },

  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.replace(/\\/g, '\\\\');
    if (useDoubleQuotes)
      return '"' + escapedString.replace(/"/g, '\\"') + '"';
    else
      return "'" + escapedString.replace(/'/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;

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    = new Object();
var $continue = new Object();

var Enumerable = {
  each: function(iterator) {
    var index = 0;
    try {
      this._each(function(value) {
        try {
          iterator(value, index++);
        } catch (e) {
          if (e != $continue) throw e;
        }
      });
    } 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;
  }
}

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() {
    return this.inject([], function(array, value) {
      return array.include(value) ? array : array.concat([value]);
    });
  },

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

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

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

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

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

if(window.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(obj) {
  Object.extend(this, obj || {});
};

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

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

      if (pair.value && pair.value.constructor == Array) {
        var values = pair.value.compact();
        if (values.length < 2) pair.value = values.reduce();
        else {
        	key = encodeURIComponent(pair.key);
          values.each(function(value) {
            value = value != undefined ? encodeURIComponent(value) : '';
            parts.push(key + '=' + encodeURIComponent(value));
          });
          return;
        }
      }
      if (pair.value == undefined) pair[1] = '';
      parts.push(pair.map(encodeURIComponent).join('='));
	  });

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

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

function $H(object) {
  if (object && object.constructor == Hash) return object;
  return new Hash(object);
};
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 = this.options.parameters;

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

    params = Hash.toQueryString(params);
    if (params && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_='

    // when GET, append parameters to URL
    if (this.method == 'get' && params)
      this.url += (this.url.indexOf('?') > -1 ? '&' : '?') + params;

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

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

      this.transport.send(body);

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

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

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

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

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

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

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

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

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

  success: function() {
    return !this.transport.status
        || (this.transport.status >= 200 && this.transport.status < 300);
  },

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

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

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

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

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

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

  evalJSON: function() {
    try {
      var json = this.getHeader('X-JSON');
      return json ? eval('(' + json + ')') : null;
    } catch (e) { return null }
  },

  evalResponse: function() {
    try {
      return eval(this.transport.responseText);
    } 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) {
  if (Prototype.BrowserFeatures.XPath) {
    var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
    return document._getElementsByXPath(q, parentElement);
  } else {
    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 = new Object();

Element.extend = function(element) {
  if (!element || _nativeExtensions || element.nodeType == 3) return element;

  if (!element._extended && element.tagName && element != window) {
    var methods = Object.clone(Element.Methods), cache = Element.extend.cache;

    if (element.tagName == 'FORM')
      Object.extend(methods, Form.Methods);
    if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName))
      Object.extend(methods, Form.Element.Methods);

    Object.extend(methods, Element.Methods.Simulated);

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

  element._extended = true;
  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('*'));
  },

  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) {
    return Selector.findElement($(element).ancestors(), expression, index);
  },

  down: function(element, expression, index) {
    return Selector.findElement($(element).descendants(), expression, index);
  },

  previous: function(element, expression, index) {
    return Selector.findElement($(element).previousSiblings(), expression, index);
  },

  next: function(element, expression, index) {
    return Selector.findElement($(element).nextSiblings(), expression, index);
  },

  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 (document.all && !window.opera) {
      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];
      if(attribute) return attribute.nodeValue;
    }
    return element.getAttribute(name);
  },

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

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

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

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

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

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

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

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

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

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

  empty: function(element) {
    return $(element).innerHTML.match(/^\s*$/);
  },

  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);
    if (['float','cssFloat'].include(style))
      style = (typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : 'cssFloat');
    style = style.camelize();
    var value = element.style[style];
    if (!value) {
      if (document.defaultView && document.defaultView.getComputedStyle) {
        var css = document.defaultView.getComputedStyle(element, null);
        value = css ? css[style] : null;
      } else if (element.currentStyle) {
        value = element.currentStyle[style];
      }
    }

    if((value == 'auto') && ['width','height'].include(style) && (element.getStyle('display') != 'none'))
      value = element['offset'+style.capitalize()] + 'px';

    if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
      if (Element.getStyle(element, 'position') == 'static') value = 'auto';
    if(style == 'opacity') {
      if(value) return parseFloat(value);
      if(value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
        if(value[1]) return parseFloat(value[1]) / 100;
      return 1.0;
    }
    return value == 'auto' ? null : value;
  },

  setStyle: function(element, style) {
    element = $(element);
    for (var name in style) {
      var value = style[name];
      if(name == 'opacity') {
        if (value == 1) {
          value = (/Gecko/.test(navigator.userAgent) &&
            !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 0.999999 : 1.0;
          if(/MSIE/.test(navigator.userAgent) && !window.opera)
            element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'');
        } else if(value == '') {
          if(/MSIE/.test(navigator.userAgent) && !window.opera)
            element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'');
        } else {
          if(value < 0.00001) value = 0;
          if(/MSIE/.test(navigator.userAgent) && !window.opera)
            element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') +
              'alpha(opacity='+value*100+')';
        }
      } else if(['float','cssFloat'].include(name)) name = (typeof element.style.styleFloat != 'undefined') ? 'styleFloat' : 'cssFloat';
      element.style[name.camelize()] = value;
    }
    return element;
  },

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

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

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

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

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

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

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

Element._attributeTranslations = {};

Element._attributeTranslations.names = {
  colspan:   "colSpan",
  rowspan:   "rowSpan",
  valign:    "vAlign",
  datetime:  "dateTime",
  accesskey: "accessKey",
  tabindex:  "tabIndex",
  enctype:   "encType",
  maxlength: "maxLength",
  readonly:  "readOnly",
  longdesc:  "longDesc"
};

Element._attributeTranslations.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;
  }
};

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

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

// IE is missing .innerHTML support for TABLE-related elements
if (document.all && !window.opera){
  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;
  }
};

Object.extend(Element, Element.Methods);

var _nativeExtensions = false;

if(/Konqueror|Safari|KHTML/.test(navigator.userAgent))
  ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) {
    var className = 'HTML' + tag + 'Element';
    if(window[className]) return;
    var klass = window[className] = {};
    klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__;
  });

Element.addMethods = function(methods) {
  Object.extend(Element.Methods, 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);
    }
  }

  if (typeof HTMLElement != 'undefined') {
    copy(Element.Methods, HTMLElement.prototype);
    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
    copy(Form.Methods, HTMLFormElement.prototype);
    [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) {
      copy(Form.Element.Methods, klass.prototype);
    });
    _nativeExtensions = true;
  }
}

var Toggle = new Object();
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);
var Selector = Class.create();
Selector.prototype = {
  initialize: function(expression) {
    this.params = {classNames: []};
    this.expression = expression.toString().strip();
    this.parseExpression();
    this.compileMatcher();
  },

  parseExpression: function() {
    function abort(message) { throw 'Parse error in selector: ' + message; }

    if (this.expression == '')  abort('empty expression');

    var params = this.params, expr = this.expression, match, modifier, clause, rest;
    while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
      params.attributes = params.attributes || [];
      params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
      expr = match[1];
    }

    if (expr == '*') return this.params.wildcard = true;

    while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
      modifier = match[1], clause = match[2], rest = match[3];
      switch (modifier) {
        case '#':       params.id = clause; break;
        case '.':       params.classNames.push(clause); break;
        case '':
        case undefined: params.tagName = clause.toUpperCase(); break;
        default:        abort(expr.inspect());
      }
      expr = rest;
    }

    if (expr.length > 0) abort(expr.inspect());
  },

  buildMatchExpression: function() {
    var params = this.params, conditions = [], clause;

    if (params.wildcard)
      conditions.push('true');
    if (clause = params.id)
      conditions.push('element.readAttribute("id") == ' + clause.inspect());
    if (clause = params.tagName)
      conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
    if ((clause = params.classNames).length > 0)
      for (var i = 0, length = clause.length; i < length; i++)
        conditions.push('element.hasClassName(' + clause[i].inspect() + ')');
    if (clause = params.attributes) {
      clause.each(function(attribute) {
        var value = 'element.readAttribute(' + attribute.name.inspect() + ')';
        var splitValueBy = function(delimiter) {
          return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
        }

        switch (attribute.operator) {
          case '=':       conditions.push(value + ' == ' + attribute.value.inspect()); break;
          case '~=':      conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
          case '|=':      conditions.push(
                            splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()
                          ); break;
          case '!=':      conditions.push(value + ' != ' + attribute.value.inspect()); break;
          case '':
          case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect() + ')'); break;
          default:        throw 'Unknown operator ' + attribute.operator + ' in selector';
        }
      });
    }

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

  compileMatcher: function() {
    this.match = new Function('element', 'if (!element.tagName) return false; \
      element = $(element); \
      return ' + this.buildMatchExpression());
  },

  findElements: function(scope) {
    var element;

    if (element = $(this.params.id))
      if (this.match(element))
        if (!scope || Element.childOf(element, scope))
          return [element];

    scope = (scope || document).getElementsByTagName(this.params.tagName || '*');

    var results = [];
    for (var i = 0, length = scope.length; i < length; i++)
      if (this.match(element = scope[i]))
        results.push(Element.extend(element));

    return results;
  },

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

Object.extend(Selector, {
  matchElements: function(elements, expression) {
    var selector = new Selector(expression);
    return elements.select(selector.match.bind(selector)).map(Element.extend);
  },

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

  findChildElements: function(element, expressions) {
    return expressions.map(function(expression) {
      return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) {
        var selector = new Selector(expr);
        return results.inject([], function(elements, result) {
          return elements.concat(selector.findElements(result || element));
        });
      });
    }).flatten();
  }
});

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 != undefined) {
          if (result[key]) {
            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().each(function(element) {
      element.blur();
      element.disabled = 'true';
    });
    return form;
  },

  enable: function(form) {
    form = $(form);
    form.getElements().each(function(element) {
      element.disabled = '';
    });
    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;
  }
}

Object.extend(Form, Form.Methods);

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

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);
    element.focus();
    if (element.select && ( element.tagName.toLowerCase() != 'input' ||
      !['button', 'reset', 'submit'].include(element.type) ) )
      element.select();
    return element;
  },

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

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

Object.extend(Form.Element, Form.Element.Methods);
var Field = Form.Element;
var $F = Form.Element.getValue;

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

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

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

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

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

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

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

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

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

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

Abstract.TimedObserver = function() {}
Abstract.TimedObserver.prototype = {
  initialize: function(element, frequency, callback) {
    this.frequency = frequency;
    this.element   = $(element);
    this.callback  = callback;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  observers: false,

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

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

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

    if (name == 'keypress' &&
        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
        || element.attachEvent))
      name = 'keydown';

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

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

    if (name == 'keypress' &&
        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
        || element.detachEvent))
      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 (navigator.appVersion.match(/\bMSIE\b/))
  Event.observe(window, 'unload', Event.unloadCache, false);
var Position = {
  // set to true if needed, warning: firefox performance problems
  // NOT neeeded for page scrolling, only if draggable contained in
  // scrollable elements
  includeScrollOffsets: false,

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

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

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

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

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

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

    return document.body;
  },

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

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

  withinIncludingScrolloffsets: function(element, x, y) {
    var offsetcache = this.realOffset(element);

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

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

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

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

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

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

    } while (element = element.offsetParent);

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

    return [valueL, valueT];
  },

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

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

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

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

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

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

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

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

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

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

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

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

// Safari returns margins on body which is incorrect if the child is absolutely
// positioned.  For performance reasons, redefine Position.cumulativeOffset for
// KHTML/WebKit only.
if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
  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();// script.aculo.us builder.js v1.6.4, Wed Sep 06 11:30:58 CEST 2006

// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// See scriptaculous.js for full license.

var Builder = {
  NODEMAP: {
    AREA: 'map',
    CAPTION: 'table',
    COL: 'table',
    COLGROUP: 'table',
    LEGEND: 'fieldset',
    OPTGROUP: 'select',
    OPTION: 'select',
    PARAM: 'object',
    TBODY: 'table',
    TD: 'table',
    TFOOT: 'table',
    TH: 'table',
    THEAD: 'table',
    TR: 'table'
  },
  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
  //       due to a Firefox bug
  node: function(elementName) {
    elementName = elementName.toUpperCase();
    
    // try innerHTML approach
    var parentTag = this.NODEMAP[elementName] || 'div';
    var parentElement = document.createElement(parentTag);
    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
    } catch(e) {}
    var element = parentElement.firstChild || null;
      
    // see if browser added wrapping tags
    if(element && (element.tagName != elementName))
      element = element.getElementsByTagName(elementName)[0];
    
    // fallback to createElement approach
    if(!element) element = document.createElement(elementName);
    
    // abort if nothing could be created
    if(!element) return;

    // attributes (or text)
    if(arguments[1])
      if(this._isStringOrNumber(arguments[1]) ||
        (arguments[1] instanceof Array)) {
          this._children(element, arguments[1]);
        } else {
          var attrs = this._attributes(arguments[1]);
          if(attrs.length) {
            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
              parentElement.innerHTML = "<" +elementName + " " +
                attrs + "></" + elementName + ">";
            } catch(e) {}
            element = parentElement.firstChild || null;
            // workaround firefox 1.0.X bug
            if(!element) {
              element = document.createElement(elementName);
              for(attr in arguments[1]) 
                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
            }
            if(element.tagName != elementName)
              element = parentElement.getElementsByTagName(elementName)[0];
            }
        } 

    // text, or array of children
    if(arguments[2])
      this._children(element, arguments[2]);

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

// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Contributors:
//  Justin Palmer (http://encytemedia.com/)
//  Mark Pilgrim (http://diveintomark.org/)
//  Martin Bialasinki
// 
// See scriptaculous.js for full license.  

// converts rgb() and #xxx to #xxxxxx format,  
// returns self (or first argument) if not convertable  
String.prototype.parseColor = function() {  
  var color = '#';  
  if(this.slice(0,4) == 'rgb(') {  
    var cols = this.slice(4,this.length-1).split(',');  
    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
  } else {  
    if(this.slice(0,1) == '#') {  
      if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
      if(this.length==7) color = this.toLowerCase();  
    }  
  }  
  return(color.length==7 ? color : (arguments[0] || this));  
}

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

Element.collectTextNodes = function(element) {  
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue : 
      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
  }).flatten().join('');
}

Element.collectTextNodesIgnoreClass = function(element, className) {  
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue : 
      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 
        Element.collectTextNodesIgnoreClass(node, className) : ''));
  }).flatten().join('');
}

Element.setContentZoom = function(element, percent) {
  element = $(element);  
  Element.setStyle(element, {fontSize: (percent/100) + 'em'});   
  if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
}

Element.getOpacity = function(element){  
  var opacity;
  if (opacity = Element.getStyle(element, 'opacity'))  
    return parseFloat(opacity);  
  if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/))  
    if(opacity[1]) return parseFloat(opacity[1]) / 100;  
  return 1.0;  
}

Element.setOpacity = function(element, value){  
  element= $(element);  
  if (value == 1){
    Element.setStyle(element, { opacity: 
      (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 
      0.999999 : 1.0 });
    if(/MSIE/.test(navigator.userAgent) && !window.opera)  
      Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});  
  } else {  
    if(value < 0.00001) value = 0;  
    Element.setStyle(element, {opacity: value});
    if(/MSIE/.test(navigator.userAgent) && !window.opera)  
     Element.setStyle(element, 
       { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
                 'alpha(opacity='+value*100+')' });  
  }
}  
 
Element.getInlineOpacity = function(element){  
  return $(element).style.opacity || '';
}  

Element.childrenWithClassName = function(element, className, findFirst) {
  var classNameRegExp = new RegExp("(^|\\s)" + className + "(\\s|$)");
  var results = $A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) { 
    return (c.className && c.className.match(classNameRegExp));
  });
  if(!results) results = [];
  return results;
}

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(/MSIE/.test(navigator.userAgent) && !window.opera) tagifyStyle += ';zoom:1';
    element = $(element);
    $A(element.childNodes).each( function(child) {
      if(child.nodeType==3) {
        child.nodeValue.toArray().each( function(character) {
          element.insertBefore(
            Builder.node('span',{style: tagifyStyle},
              character == ' ' ? String.fromCharCode(160) : character), 
              child);
        });
        Element.remove(child);
      }
    });
  },
  multiple: function(element, effect) {
    var elements;
    if(((typeof element == 'object') || 
        (typeof element == 'function')) && 
       (element.length))
      elements = element;
    else
      elements = $(element).childNodes;
      
    var options = Object.extend({
      speed: 0.1,
      delay: 0.0
    }, arguments[2] || {});
    var masterDelay = options.delay;

    $A(elements).each( function(element, index) {
      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
    });
  },
  PAIRS: {
    'slide':  ['SlideDown','SlideUp'],
    'blind':  ['BlindDown','BlindUp'],
    'appear': ['Appear','Fade']
  },
  toggle: function(element, effect) {
    element = $(element);
    effect = (effect || 'appear').toLowerCase();
    var options = Object.extend({
      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
    }, arguments[2] || {});
    Effect[element.visible() ? 
      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
  }
};

var Effect2 = Effect; // deprecated

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

Effect.Transitions = {}

Effect.Transitions.linear = Prototype.K;

Effect.Transitions.sinoidal = function(pos) {
  return (-Math.cos(pos*Math.PI)/2) + 0.5;
}
Effect.Transitions.reverse  = function(pos) {
  return 1-pos;
}
Effect.Transitions.flicker = function(pos) {
  return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
}
Effect.Transitions.wobble = function(pos) {
  return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
}
Effect.Transitions.pulse = function(pos) {
  return (Math.floor(pos*10) % 2 == 0 ? 
    (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
}
Effect.Transitions.none = function(pos) {
  return 0;
}
Effect.Transitions.full = function(pos) {
  return 1;
}

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

Effect.ScopedQueue = Class.create();
Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
  initialize: function() {
    this.effects  = [];
    this.interval = null;
  },
  _each: function(iterator) {
    this.effects._each(iterator);
  },
  add: function(effect) {
    var timestamp = new Date().getTime();
    
    var position = (typeof effect.options.queue == 'string') ? 
      effect.options.queue : effect.options.queue.position;
    
    switch(position) {
      case 'front':
        // move unstarted effects after this effect  
        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
            e.startOn  += effect.finishOn;
            e.finishOn += effect.finishOn;
          });
        break;
      case 'end':
        // start effect after last queued effect has finished
        timestamp = this.effects.pluck('finishOn').max() || timestamp;
        break;
    }
    
    effect.startOn  += timestamp;
    effect.finishOn += timestamp;

    if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
      this.effects.push(effect);
    
    if(!this.interval) 
      this.interval = setInterval(this.loop.bind(this), 40);
  },
  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();
    this.effects.invoke('loop', timePos);
  }
});

Effect.Queues = {
  instances: $H(),
  get: function(queueName) {
    if(typeof queueName != 'string') return queueName;
    
    if(!this.instances[queueName])
      this.instances[queueName] = new Effect.ScopedQueue();
      
    return this.instances[queueName];
  }
}
Effect.Queue = Effect.Queues.get('global');

Effect.DefaultOptions = {
  transition: Effect.Transitions.sinoidal,
  duration:   1.0,   // seconds
  fps:        25.0,  // max. 25fps due to Effect.Queue implementation
  sync:       false, // true for combining
  from:       0.0,
  to:         1.0,
  delay:      0.0,
  queue:      'parallel'
}

Effect.Base = function() {};
Effect.Base.prototype = {
  position: null,
  start: function(options) {
    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.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.finishOn - this.startOn);
      var frame = Math.round(pos * this.options.fps * this.options.duration);
      if(frame > this.currentFrame) {
        this.render(pos);
        this.currentFrame = frame;
      }
    }
  },
  render: function(pos) {
    if(this.state == 'idle') {
      this.state = 'running';
      this.event('beforeSetup');
      if(this.setup) this.setup();
      this.event('afterSetup');
    }
    if(this.state == 'running') {
      if(this.options.transition) pos = this.options.transition(pos);
      pos *= (this.options.to-this.options.from);
      pos += this.options.from;
      this.position = pos;
      this.event('beforeUpdate');
      if(this.update) this.update(pos);
      this.event('afterUpdate');
    }
  },
  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() {
    return '#<Effect:' + $H(this).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.Opacity = Class.create();
Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    if(!this.element) throw(Effect._elementDoesNotExistError);
    // make this work on IE on elements without 'layout'
    if(/MSIE/.test(navigator.userAgent) && !window.opera && (!this.element.currentStyle.hasLayout))
      this.element.setStyle({zoom: 1});
    var options = Object.extend({
      from: this.element.getOpacity() || 0.0,
      to:   1.0
    }, arguments[1] || {});
    this.start(options);
  },
  update: function(position) {
    this.element.setOpacity(position);
  }
});

Effect.Move = Class.create();
Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    if(!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      x:    0,
      y:    0,
      mode: 'relative'
    }, arguments[1] || {});
    this.start(options);
  },
  setup: function() {
    // Bug in Opera: Opera returns the "real" position of a static element or
    // relative element that does not have top/left explicitly set.
    // ==> Always set top and left for position relative elements in your stylesheets 
    // (to 0 if you do not need them) 
    this.element.makePositioned();
    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
    if(this.options.mode == 'absolute') {
      // absolute movement, so we need to calc deltaX and deltaY
      this.options.x = this.options.x - this.originalLeft;
      this.options.y = this.options.y - this.originalTop;
    }
  },
  update: function(position) {
    this.element.setStyle({
      left: Math.round(this.options.x  * position + this.originalLeft) + 'px',
      top:  Math.round(this.options.y  * position + this.originalTop)  + 'px'
    });
  }
});

// for backwards compatibility
Effect.MoveBy = function(element, toTop, toLeft) {
  return new Effect.Move(element, 
    Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
};

Effect.Scale = Class.create();
Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
  initialize: function(element, percent) {
    this.element = $(element);
    if(!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      scaleX: true,
      scaleY: true,
      scaleContent: true,
      scaleFromCenter: false,
      scaleMode: 'box',        // 'box' or 'contents' or {} with provided values
      scaleFrom: 100.0,
      scaleTo:   percent
    }, arguments[2] || {});
    this.start(options);
  },
  setup: function() {
    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
    this.elementPositioning = this.element.getStyle('position');
    
    this.originalStyle = {};
    ['top','left','width','height','fontSize'].each( function(k) {
      this.originalStyle[k] = this.element.style[k];
    }.bind(this));
      
    this.originalTop  = this.element.offsetTop;
    this.originalLeft = this.element.offsetLeft;
    
    var fontSize = this.element.getStyle('font-size') || '100%';
    ['em','px','%','pt'].each( function(fontSizeType) {
      if(fontSize.indexOf(fontSizeType)>0) {
        this.fontSize     = parseFloat(fontSize);
        this.fontSizeType = fontSizeType;
      }
    }.bind(this));
    
    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
    
    this.dims = null;
    if(this.options.scaleMode=='box')
      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
    if(/^content/.test(this.options.scaleMode))
      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
    if(!this.dims)
      this.dims = [this.options.scaleMode.originalHeight,
                   this.options.scaleMode.originalWidth];
  },
  update: function(position) {
    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
    if(this.options.scaleContent && this.fontSize)
      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
  },
  finish: function(position) {
    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
  },
  setDimensions: function(height, width) {
    var d = {};
    if(this.options.scaleX) d.width = Math.round(width) + 'px';
    if(this.options.scaleY) d.height = Math.round(height) + 'px';
    if(this.options.scaleFromCenter) {
      var topd  = (height - this.dims[0])/2;
      var leftd = (width  - this.dims[1])/2;
      if(this.elementPositioning == 'absolute') {
        if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
        if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
      } else {
        if(this.options.scaleY) d.top = -topd + 'px';
        if(this.options.scaleX) d.left = -leftd + 'px';
      }
    }
    this.element.setStyle(d);
  }
});

Effect.Highlight = Class.create();
Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    if(!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({ startcolor: '#ffe89f' }, arguments[1] || {});
    this.start(options);
  },
  setup: function() {
    // Prevent executing on elements not in the layout flow
    if(this.element.getStyle('display')=='none') { this.cancel(); return; }
    // Disable background image during the effect
    this.oldStyle = {
      backgroundImage: this.element.getStyle('background-image') };
    this.element.setStyle({backgroundImage: 'none'});
    if(!this.options.endcolor)
      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
    if(!this.options.restorecolor)
      this.options.restorecolor = this.element.getStyle('background-color');
    // init color calculations
    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
  },
  update: function(position) {
    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
      return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
  },
  finish: function() {
    this.element.setStyle(Object.extend(this.oldStyle, {
      backgroundColor: this.options.restorecolor
    }));
  }
});

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

/* ------------- combination effects ------------- */

Effect.Fade = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  var options = Object.extend({
  from: element.getOpacity() || 1.0,
  to:   0.0,
  afterFinishInternal: function(effect) { 
    if(effect.options.to!=0) return;
    effect.element.hide();
    effect.element.setStyle({opacity: oldOpacity}); 
  }}, arguments[1] || {});
  return new Effect.Opacity(element,options);
}

Effect.Appear = function(element) {
  element = $(element);
  var options = Object.extend({
  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
  to:   1.0,
  // force Safari to render floated elements properly
  afterFinishInternal: function(effect) {
    effect.element.forceRerendering();
  },
  beforeSetup: function(effect) {
    effect.element.setOpacity(effect.options.from);
    effect.element.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();
         effect.effects[0].element.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();
        effect.element.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();
      effect.element.setStyle({height: '0px'});
      effect.element.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();
          effect.element.makeClipping();
        },
        afterFinishInternal: function(effect) {
          effect.element.hide();
          effect.element.undoClipping();
          effect.element.undoPositioned();
          effect.element.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();
          effect.effects[0].element.undoPositioned();
          effect.effects[0].element.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();
        effect.element.setStyle(oldStyle);
  }}) }}) }}) }}) }}) }});
}

Effect.SlideDown = function(element) {
  element = $(element);
  element.cleanWhitespace();
  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
  var oldInnerBottom = $(element.firstChild).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.firstChild.makePositioned();
      if(window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping();
      effect.element.setStyle({height: '0px'});
      effect.element.show(); },
    afterUpdateInternal: function(effect) {
      effect.element.firstChild.setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' }); 
    },
    afterFinishInternal: function(effect) {
      effect.element.undoClipping(); 
      // IE will crash if child is undoPositioned first
      if(/MSIE/.test(navigator.userAgent) && !window.opera){
        effect.element.undoPositioned();
        effect.element.firstChild.undoPositioned();
      }else{
        effect.element.firstChild.undoPositioned();
        effect.element.undoPositioned();
      }
      effect.element.firstChild.setStyle({bottom: oldInnerBottom}); }
    }, arguments[1] || {})
  );
}

Effect.SlideUp = function(element) {
  element = $(element);
  element.cleanWhitespace();
  var oldInnerBottom = $(element.firstChild).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.firstChild.makePositioned();
      if(window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping();
      effect.element.show(); },  
    afterUpdateInternal: function(effect) {
      effect.element.firstChild.setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' }); },
    afterFinishInternal: function(effect) {
      effect.element.hide();
      effect.element.undoClipping();
      effect.element.firstChild.undoPositioned();
      effect.element.undoPositioned();
      effect.element.setStyle({bottom: oldInnerBottom}); }
   }, arguments[1] || {})
  );
}

// Bug in opera makes the TD containing this element expand for a instance after finish 
Effect.Squish = function(element) {
  return new Effect.Scale(element, window.opera ? 1 : 0, 
    { restoreAfterFinish: true,
      beforeSetup: function(effect) {
        effect.element.makeClipping(effect.element); },  
      afterFinishInternal: function(effect) {
        effect.element.hide(effect.element); 
        effect.element.undoClipping(effect.element); }
  });
}

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();
      effect.element.makeClipping();
      effect.element.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'});
               effect.effects[0].element.show(); 
             },
             afterFinishInternal: function(effect) {
               effect.effects[0].element.undoClipping();
               effect.effects[0].element.undoPositioned();
               effect.effects[0].element.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();
           effect.effects[0].element.makeClipping(); },
         afterFinishInternal: function(effect) {
           effect.effects[0].element.hide();
           effect.effects[0].element.undoClipping();
           effect.effects[0].element.undoPositioned();
           effect.effects[0].element.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)) };
  reverser.bind(transition);
  return new Effect.Opacity(element, 
    Object.extend(Object.extend({  duration: 3.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(element);
  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();
        effect.element.undoClipping(); 
        effect.element.setStyle(oldStyle);
      } });
  }}, arguments[1] || {}));
};

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

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

Element.addMethods();// script.aculo.us controls.js v1.6.4, Wed Sep 06 11:30:58 CEST 2006

// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//           (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
//           (c) 2005 Jon Tirsen (http://www.tirsen.com)
// Contributors:
//  Richard Livsey
//  Rahul Bhargava
//  Rob Wills
// 
// See scriptaculous.js for full license.

// Autocompleter.Base handles all the autocompletion functionality 
// that's independent of the data source for autocompletion. This
// includes drawing the autocompletion menu, observing keyboard
// and mouse events, and similar.
//
// Specific autocompleters need to provide, at the very least, 
// a getUpdatedChoices function that will be invoked every time
// the text inside the monitored textbox changes. This method 
// should get the text for which to provide autocompletion by
// invoking this.getToken(), NOT by directly accessing
// this.element.value. This is to allow incremental tokenized
// autocompletion. Specific auto-completion logic (AJAX, etc)
// belongs in getUpdatedChoices.
//
// Tokenized incremental autocompletion is enabled automatically
// when an autocompleter is instantiated with the 'tokens' option
// in the options parameter, e.g.:
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
// will incrementally autocomplete with a comma as the token.
// Additionally, ',' in the above example can be replaced with
// a token array, e.g. { tokens: [',', '\n'] } which
// enables autocompletion on multiple tokens. This is most 
// useful when one of the tokens is \n (a newline), as it 
// allows smart autocompletion after linebreaks.

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

var Autocompleter = {}
Autocompleter.Base = function() {};
Autocompleter.Base.prototype = {
  baseInitialize: function(element, update, options) {
    this.element     = $(element); 
    this.update      = $(update);  
    this.hasFocus    = false; 
    this.changed     = false; 
    this.active      = false; 
    this.index       = 0;     
    this.entryCount  = 0;

    if(this.setOptions)
      this.setOptions(options);
    else
      this.options = options || {};

    this.options.paramName    = this.options.paramName || this.element.name;
    this.options.tokens       = this.options.tokens || [];
    this.options.frequency    = this.options.frequency || 0.4;
    this.options.minChars     = this.options.minChars || 1;
    this.options.onShow       = this.options.onShow || 
      function(element, update){ 
        if(!update.style.position || update.style.position=='absolute') {
          update.style.position = 'absolute';
          Position.clone(element, update, {
            setHeight: false, 
            offsetTop: element.offsetHeight
          });
        }
        Effect.Appear(update,{duration:0.15});
      };
    this.options.onHide = this.options.onHide || 
      function(element, update){ new Effect.Fade(update,{duration:0.15}) };

    if(typeof(this.options.tokens) == 'string') 
      this.options.tokens = new Array(this.options.tokens);

    this.observer = null;
    
    this.element.setAttribute('autocomplete','off');

    Element.hide(this.update);

    Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
    Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
  },

  show: function() {
    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
    if(!this.iefix && 
      (navigator.appVersion.indexOf('MSIE')>0) &&
      (navigator.userAgent.indexOf('Opera')<0) &&
      (Element.getStyle(this.update, 'position')=='absolute')) {
      new Insertion.After(this.update, 
       '<iframe id="' + this.update.id + '_iefix" '+
       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
      this.iefix = $(this.update.id+'_iefix');
    }
    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
  },
  
  fixIEOverlapping: function() {
    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
    this.iefix.style.zIndex = 1;
    this.update.style.zIndex = 2;
    Element.show(this.iefix);
  },

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

});

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

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

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

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

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

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

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

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

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

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

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

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

    this.options = Object.extend({
      okButton: false,
      okText: "ok",
      cancelLink: false,
      cancelText: "cancel",
      savingText: "Saving...",
      clickToEditText: "Click to edit",
      okText: "ok",
      rows: 1,
      onComplete: function(mxhrResponse) {
          if (mxhrResponse) {
              this.element.innerHTML = mxhrResponse.getPayloadData();
              new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
          }
      },
      onFailure: function(transport) {
//        alert("Error communicating with the server: " + transport.responseText.stripTags());
      },
      messageId: null,
      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: true,
      ajaxOptions: {},
      evalScripts: false
    }, options || {});

    if(!this.options.formId && this.element.id) {
      this.options.formId = this.element.id + "-inplaceeditor";
      if ($(this.options.formId)) {
        // there's already a form with that name, don't specify an id
        this.options.formId = null;
      }
    }
    
    if (this.options.externalControl) {
      this.options.externalControl = $(this.options.externalControl);
    }
    
    this.originalBackground = Element.getStyle(this.element, 'background-color');
    if (!this.originalBackground) {
      this.originalBackground = "transparent";
    }
    
    this.element.title = this.options.clickToEditText;
    
    this.onclickListener = this.enterEditMode.bindAsEventListener(this);
    this.mouseoverListener = this.enterHover.bindAsEventListener(this);
    this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
    Event.observe(this.element, 'click', this.onclickListener);
    Event.observe(this.element, 'mouseover', this.mouseoverListener);
    Event.observe(this.element, 'mouseout', this.mouseoutListener);
    if (this.options.externalControl) {
      Event.observe(this.options.externalControl, 'click', this.onclickListener);
      Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
      Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
    }
  },
  enterEditMode: function(evt) {
    if (this.saving) return;
    if (this.editing) return;
    this.editing = true;
    this.onEnterEditMode();
    if (this.options.externalControl) {
      Element.hide(this.options.externalControl);
    }
    this.height = Element.getHeight(this.element);
    this.width = this.element.offsetWidth;

    Element.hide(this.element);
    this.createForm();
    this.element.parentNode.insertBefore(this.form, this.element);
    if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField);
    // stop the event to avoid a page refresh in Safari
    if (evt) {
      Event.stop(evt);
    }
    return false;
  },
  createForm: function() {
    this.form = document.createElement("form");
    this.form.id = this.options.formId;
    Element.addClassName(this.form, this.options.formClassName)
    this.form.onsubmit = this.onSubmit.bind(this);

    this.createEditField();

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

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

    if (this.options.cancelLink) {
      cancelLink = document.createElement("a");
      cancelLink.href = "#";
      cancelLink.appendChild(document.createTextNode(this.options.cancelText));
      cancelLink.onclick = this.onclickCancel.bind(this);
      cancelLink.className = 'editor_cancel';      
      this.form.appendChild(cancelLink);
    }
    this.form.style.height = this.height + "px";
    this.editField.style.height = (this.height - 4) + "px";
    this.editField.style.width = this.width + "px";
  },
  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.text = this.getText();
      this.options.textarea = false;
      var textField = document.createElement("input");
      textField.obj = this;
      textField.type = "text";
      textField.name = "value";
      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 = "value";
      textArea.value = this.convertHTMLLineBreaks(text);
      this.text = textArea.value;
      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";
    
    var mxhr = new Mxhr({
        url: this.options.loadTextURL,
        parameters: "",
        onSuccessHandler: this.onLoadedExternalText.bind(this)
//        hourglassId: 'dlgFoodDetailsWeightChangeHourglass',
//        messageId: 'divFoodGridError'
    });

    mxhr.execute();

    
//    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 = "";
    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;
    }
    this.leaveEditMode();
    return false;
  },
  onSubmit: function() {
    // onLoading resets these so we need to save them away for the Ajax call
    var form = this.form;
    var value = this.editField.value;

    if (value != this.text) {
        // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
        // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
        // to be displayed indefinitely
        this.onLoading();

        var mxhr = new Mxhr({
            url: this.url,
            parameters: this.options.callback(form, value),
    //        options: this.options.ajaxOptions,
            onSuccessHandler: this.onComplete.bind(this),
            onFailureHandler: this.onFailure.bind(this),
    //        hourglassId: 'dlgFoodDetailsWeightChangeHourglass',
            messageId: this.options.messageId
        });

        mxhr.execute();
    } else {
        this.onclickCancel();
    }
    

    
//    if (this.options.evalScripts) {
//      new Ajax.Request(
//        this.url, Object.extend({
//          parameters: this.options.callback(form, value),
//          onComplete: this.onComplete.bind(this),
//          onFailure: this.onFailure.bind(this),
//          asynchronous:true, 
//          evalScripts:true
//        }, this.options.ajaxOptions));
//    } else  {
//      new Ajax.Updater(
//        { success: this.element,
          // don't update on failure (this could be an option)
//          failure: null }, 
//        this.url, Object.extend({
//          parameters: this.options.callback(form, value),
//          onComplete: this.onComplete.bind(this),
//          onFailure: this.onFailure.bind(this)
//        }, this.options.ajaxOptions));
//    }
    // stop the event to avoid a page refresh in Safari
    if (arguments.length > 1) {
      Event.stop(arguments[0]);
    }
    return false;
  },
  onLoading: function() {
    this.saving = true;
    this.removeForm();
    this.leaveHover();
    this.showSaving();
  },
  showSaving: function() {
    this.oldInnerHTML = this.element.innerHTML;
//    this.element.innerHTML = "<div class=\"" + this.options.savingClassName + "\" style=\"background-color:" + this.originalBackground + ";height:" + this.height + "px;\" >" + this.options.savingText + "</div>";
    this.element.innerHTML = this.options.savingText;
    Element.addClassName(this.element, this.options.savingClassName);
    this.element.style.backgroundColor = this.originalBackground;
    this.element.style.height = this.height + "px"
//    this.element.style.width = this.width + "px"
    Element.show(this.element);
  },
  removeForm: function() {
    if(this.form) {
      if (this.form.parentNode) Element.remove(this.form);
      this.form = null;
    }
  },
  enterHover: function() {
    if (this.saving) return;
    this.element.style.backgroundColor = this.options.highlightcolor;
    if (this.effect) {
      this.effect.cancel();
    }
    Element.addClassName(this.element, this.options.hoverClassName)
  },
  leaveHover: function() {
    if (this.options.backgroundColor) {
      this.element.style.backgroundColor = this.oldBackground;
    }
    Element.removeClassName(this.element, this.options.hoverClassName)
    if (this.saving) return;
    this.effect = new Effect.Highlight(this.element, {
      startcolor: this.options.highlightcolor,
      endcolor: this.options.highlightendcolor,
      restorecolor: this.originalBackground
    });
  },
  leaveEditMode: function() {
    Element.removeClassName(this.element, this.options.savingClassName);
    this.removeForm();
    this.leaveHover();
    this.element.style.backgroundColor = this.originalBackground;
    Element.show(this.element);
    if (this.options.externalControl) {
      Element.show(this.options.externalControl);
    }
    this.editing = false;
    this.saving = false;
    this.oldInnerHTML = null;
    this.onLeaveEditMode();
  },
  onComplete: function(transport) {
    this.leaveEditMode();
    this.options.onComplete.bind(this)(transport, this.element);
  },
  onEnterEditMode: function() {},
  onLeaveEditMode: function() {},
  dispose: function() {
    if (this.oldInnerHTML) {
      this.element.innerHTML = this.oldInnerHTML;
    }
    this.leaveEditMode();
    Event.stopObserving(this.element, 'click', this.onclickListener);
    Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
    Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
    if (this.options.externalControl) {
      Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
      Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
      Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
    }
  }
};

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

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

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

Form.Element.DelayedObserver = Class.create();
Form.Element.DelayedObserver.prototype = {
  initialize: function(element, delay, callback) {
    this.delay     = delay || 0.5;
    this.element   = $(element);
    this.callback  = callback;
    this.timer     = null;
    this.lastValue = $F(this.element); 
    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
  },
  delayedListener: function(event) {
    if(this.lastValue == $F(this.element)) return;
    if(this.timer) clearTimeout(this.timer);
    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
    this.lastValue = $F(this.element);
  },
  onTimerEvent: function() {
    this.timer = null;
    this.callback(this.element, $F(this.element));
  }
};
// script.aculo.us scriptaculous.js v1.6.4, Wed Sep 06 11:30:58 CEST 2006

// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

var Scriptaculous = {
  Version: '1.6.4',
  require: function(libraryName) {
    // inserting via DOM fails in Safari 2.0, so brute force approach
    document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
  },
  load: function() {
    if((typeof Prototype=='undefined') || 
       (typeof Element == 'undefined') || 
       (typeof Element.Methods=='undefined') ||
       parseFloat(Prototype.Version.split(".")[0] + "." +
                  Prototype.Version.split(".")[1]) < 1.5)
       throw("script.aculo.us requires the Prototype JavaScript framework >= 1.5.0");
    
    $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,controls').split(',').each(
       function(include) { Scriptaculous.require(path+include+'.js') });
    });
  }
}

Scriptaculous.load();// Copyright (c) 2006 Sébastien Gruhier (http://xilinus.com, http://itseb.com)
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// VERSION 0.96.2

var Window = Class.create();
Window.prototype = {
// Constructor
// Available parameters : className, title, minWidth, minHeight, maxWidth, maxHeight, width, height, top, left, bottom, right, resizable, zIndex, opacity,
//                        hideEffect, showEffect, showEffectOptions, hideEffectOptions, effectOptions, url, draggable, closable, minimizable, maximizable, parent, onload
    initialize: function(id) {
        if ($(id))
            alert("Window " + id + " is already register is the DOM!!, be sure to use setDestroyOnClose()")

        this.id = id;
        this.hasEffectLib = String.prototype.parseColor != null;
        this.options = Object.extend({
            className:         "dialog",
            minWidth:          100,
            minHeight:         20,
            resizable:         true,
            closable:          true,
            minimizable:       true,
            maximizable:       true,
            draggable:         true,
            userData:          null,
            showEffect:        (this.hasEffectLib ? Effect.Appear : Element.show),
            hideEffect:        (this.hasEffectLib ? Effect.Fade : Element.hide),
            showEffectOptions: {},
            hideEffectOptions: {},
            effectOptions:     null,
            parent:            document.getElementsByTagName("body").item(0),
            title:             "&nbsp;",
            url:               null,
            onload:            Prototype.emptyFunction,
            width:             200,
            height:            300,
            opacity:           1
        }, arguments[1] || {});

        if (this.options.effectOptions) {
            Object.extend(this.options.hideEffectOptions, this.options.effectOptions);
            Object.extend(this.options.showEffectOptions, this.options.effectOptions);
        }
        if (this.options.hideEffect == Element.hide)
            this.options.hideEffect = function() {
                Element.hide(this.element);
                if (this.destroyOnClose) this.destroy();
            }.bind(this)

        this.element = this._createWindow(id);

        // Bind event listener
        this.eventMouseDown = this._initDrag.bindAsEventListener(this);
        this.eventMouseUp = this._endDrag.bindAsEventListener(this);
        this.eventMouseMove = this._updateDrag.bindAsEventListener(this);
        this.eventKeyPress = this._keyPress.bindAsEventListener(this);
        this.eventOnLoad = this._getWindowBorderSize.bindAsEventListener(this);
        this.eventMouseDownContent = this.toFront.bindAsEventListener(this);
        this.eventResize = this._recenter.bindAsEventListener(this);

        this.topbar = $(this.element.id + "_top");
        this.bottombar = $(this.element.id + "_bottom");
        this.content = $(this.element.id + "_content");

        Event.observe(this.topbar, "mousedown", this.eventMouseDown);
        Event.observe(this.bottombar, "mousedown", this.eventMouseDown);
        Event.observe(this.content, "mousedown", this.eventMouseDownContent);
        Event.observe(window, "load", this.eventOnLoad);
        Event.observe(window, "resize", this.eventResize);
        Event.observe(window, "scroll", this.eventResize);

        if (this.options.draggable) {
            this.bottombar.addClassName("bottom_draggable");
            this.topbar.addClassName("top_draggable");
        }

        if (this.options.resizable) {
            this.sizer = $(this.element.id + "_sizer");
            Event.observe(this.sizer, "mousedown", this.eventMouseDown);
        }

        this.useLeft = null;
        this.useTop = null;
        if (arguments[1].left != null) {
            this.element.setStyle({left: parseFloat(arguments[1].left) + 'px'});
            this.useLeft = true;
        }
        if (arguments[1].right != null) {
            this.element.setStyle({right: parseFloat(arguments[1].right) + 'px'});
            this.useLeft = false;
        }
        if (this.useLeft == null) {
            this.element.setStyle({left: "0px"});
            this.useLeft = true;
        }

        if (arguments[1].top != null) {
            this.element.setStyle({top: parseFloat(arguments[1].top) + 'px'});
            this.useTop = true;
        }
        if (arguments[1].bottom != null) {
            this.element.setStyle({bottom: parseFloat(arguments[1].bottom) + 'px'});
            this.useTop = false;
        }
        if (this.useTop == null) {
            this.element.setStyle({top: "0px"});
            this.useTop = true;
        }

        this.storedLocation = null;

        this.setOpacity(this.options.opacity);
        if (this.options.zIndex)
            this.setZIndex(this.options.zIndex)
        else
            this.setZIndex(Windows.maxZIndex + 1);

        this.destroyOnClose = false;

        this._getWindowBorderSize();
        this.width = this.options.width;
        this.height = this.options.height;

        if (this.width && this.height)
            this.setSize(this.options.width, this.options.height);
        this.setTitle(this.options.title)
        Windows.register(this);
    },

// Destructor
    destroy: function() {
        Windows.notify("onDestroy", this);

        Event.stopObserving(this.topbar, "mousedown", this.eventMouseDown);
        Event.stopObserving(this.bottombar, "mousedown", this.eventMouseDown);
        Event.stopObserving(this.content, "mousedown", this.eventMouseDownContent);

        Event.stopObserving(window, "load", this.eventOnLoad);
        Event.stopObserving(window, "resize", this.eventResize);
        Event.stopObserving(window, "scroll", this.eventResize);

        Event.stopObserving(this.content, "load", this.options.onload);

        if (this.sizer)
            Event.stopObserving(this.sizer, "mousedown", this.eventMouseDown);

        if (this.options.url)
            this.content.src = null

        if (this.iefix)
            Element.remove(this.iefix);

        Element.remove(this.element);
        Windows.unregister(this);
    },

// Sets window deleagte, should have functions: "canClose(window)"
    setDelegate: function(delegate) {
        this.delegate = delegate
    },

// Gets current window delegate
    getDelegate: function() {
        return this.delegate;
    },

// Gets window content
    getContent: function () {
        return this.content;
    },

// Sets the content with an element id
    setContent: function(id, autoresize, autoposition) {
        var d = null;
        var p = null;

        if (autoresize)
            d = Element.getDimensions(id);
        if (autoposition)
            p = Position.cumulativeOffset($(id));

        var content = this.getContent()
        content.appendChild($(id));
        $(id).show();
        if (autoresize)
            this.setSize(d.width, d.height);
        if (autoposition)
            this.setLocation(p[1] - this.heightN, p[0] - this.widthW);
    },

    setAjaxContent: function(url, options, showCentered, showModal) {
        this.showFunction = showCentered ? "showCenter" : "show";
        this.showModal = showModal || false;

        if (options == null)
            options = {}
        this.onComplete = options.onComplete;
        options.onComplete = this._setAjaxContent.bind(this);

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

    _setAjaxContent: function(originalRequest) {
        this.getContent().innerHTML = originalRequest.responseText;
        if (this.onComplete)
            this.onComplete(originalRequest);
        this[this.showFunction](this.showModal)
    },

// Stores position/size in a cookie, by default named with window id
    setCookie: function(name, expires, path, domain, secure) {
        name = name || this.element.id;
        this.cookie = [name, expires, path, domain, secure];

        // Get cookie
        var value = WindowUtilities.getCookie(name)
        // If exists
        if (value) {
            var values = value.split(',');
            var x = values[0].split(':');
            var y = values[1].split(':');

            var w = parseFloat(values[2]), h = parseFloat(values[3]);
            var mini = values[4];
            var maxi = values[5];

            this.setSize(w, h);
            if (mini == "true")
                this.doMinimize = true; // Minimize will be done at onload window event
            else if (maxi == "true")
                this.doMaximize = true; // Maximize will be done at onload window event

            this.useLeft = x[0] == "l";
            this.useTop = y[0] == "t";

            this.element.setStyle(this.useLeft ? {left: x[1]} : {right: x[1]});
            this.element.setStyle(this.useTop ? {top: y[1]} : {bottom: y[1]});
        }
    },

// Gets window ID
    getId: function() {
        return this.id;
    },

// Detroys itself when closing
    setDestroyOnClose: function() {
        Object.extend(this.options.hideEffectOptions, {afterFinish:  this.destroy.bind(this)});
        this.destroyOnClose = true;
    },

// initDrag event
    _initDrag: function(event) {
        // Get pointer X,Y
        this.pointer = [Event.pointerX(event), Event.pointerY(event)];

        // Resize
        if (Event.element(event) == this.sizer) {
            this.doResize = true;
            this.widthOrg = this.width;
            this.heightOrg = this.height;
            this.bottomOrg = parseFloat(this.element.getStyle('bottom'));
            this.rightOrg = parseFloat(this.element.getStyle('right'));
            Windows.notify("onStartResize", this);
        }
        else {
            this.doResize = false;

            // Check if click on close button,
            var closeButton = $(this.getId() + '_close');
            if (closeButton && Position.within(closeButton, this.pointer[0], this.pointer[1]))
                return;

            this.toFront();

            if (! this.options.draggable)
                return;
            Windows.notify("onStartMove", this);
        }
        // Register global event to capture mouseUp and mouseMove
        Event.observe(document, "mouseup", this.eventMouseUp, false);
        Event.observe(document, "mousemove", this.eventMouseMove, false);

        // Add an invisible div to keep catching mouse event over iframes
        WindowUtilities.disableScreen('__invisible__', '__invisible__');

        // Stop selection while dragging
        document.body.ondrag = function () {
            return false;
        };
        document.body.onselectstart = function () {
            return false;
        };

        Event.stop(event);
    },

// updateDrag event
    _updateDrag: function(event) {
        var pointer = [Event.pointerX(event), Event.pointerY(event)];
        var dx = pointer[0] - this.pointer[0];
        var dy = pointer[1] - this.pointer[1];

        // Resize case, update width/height
        if (this.doResize) {
            this.setSize(this.widthOrg + dx, this.heightOrg + dy);

            dx = this.width - this.widthOrg
            dy = this.height - this.heightOrg

            // Check if it's a right position, update it to keep upper-left corner at the same position
            if (! this.useLeft)
                this.element.setStyle({right: (this.rightOrg - dx) + 'px'});
            // Check if it's a bottom position, update it to keep upper-left corner at the same position
            if (! this.useTop)
                this.element.setStyle({bottom: (this.bottomOrg - dy) + 'px'});
        }
        // Move case, update top/left
        else {
            this.pointer = pointer;

            if (this.useLeft)
                this.element.setStyle({left: parseFloat(this.element.getStyle('left')) + dx + 'px'});
            else
                this.element.setStyle({right: parseFloat(this.element.getStyle('right')) - dx + 'px'});

            if (this.useTop)
                this.element.setStyle({top: parseFloat(this.element.getStyle('top')) + dy + 'px'});
            else
                this.element.setStyle({bottom: parseFloat(this.element.getStyle('bottom')) - dy + 'px'});
        }
        if (this.iefix)
            this._fixIEOverlapping();

        this._removeStoreLocation();
        Event.stop(event);
    },

// endDrag callback
    _endDrag: function(event) {
        // Remove temporary div over iframes
        WindowUtilities.enableScreen('__invisible__');

        if (this.doResize)
            Windows.notify("onEndResize", this);
        else
            Windows.notify("onEndMove", this);

        // Release event observing
        Event.stopObserving(document, "mouseup", this.eventMouseUp, false);
        Event.stopObserving(document, "mousemove", this.eventMouseMove, false);

        // Store new location/size if need be
        this._saveCookie()

        Event.stop(event);

        // Restore selection
        document.body.ondrag = null;
        document.body.onselectstart = null;
    },

    _keyPress: function(event) {
        //Dialog.cancelCallback();
    },

// Creates HTML window code
    _createWindow: function(id) {
        var className = this.options.className;
        var win = document.createElement("div");
        win.setAttribute('id', id);
        win.className = "dialog";

        var content;
        if (this.options.url)
            content = "<iframe name=\"" + id + "_content\"  id=\"" + id + "_content\" src=\"" + this.options.url + "\"> </iframe>";
        else
            content = "<div id=\"" + id + "_content\" class=\"" + className + "_content\"> </div>";

        var closeDiv = this.options.closable ? "<div class='" + className + "_close' id='" + id + "_close' onmouseup='Windows.close(\"" + id + "\")'> </div>" : "";
        var minDiv = this.options.minimizable ? "<div class='" + className + "_minimize' id='" + id + "_minimize' onmouseup='Windows.minimize(\"" + id + "\")'> </div>" : "";
        var maxDiv = this.options.maximizable ? "<div class='" + className + "_maximize' id='" + id + "_maximize' onmouseup='Windows.maximize(\"" + id + "\")'> </div>" : "";
        var seAttributes = this.options.resizable ? "class='" + className + "_sizer' id='" + id + "_sizer'" : "class='" + className + "_se'";

        win.innerHTML = closeDiv + minDiv + maxDiv + "\
          <table id='" + id + "_row1' class=\"top table_window\">\
            <tr>\
              <td class='" + className + "_nw'>&nbsp;</td>\
              <td class='" + className + "_n'><div id='" + id + "_top' class='" + className + "_title title_window'>" + this.options.title + "</div></td>\
              <td class='" + className + "_ne'>&nbsp;</td>\
            </tr>\
          </table>\
          <table id='" + id + "_row2' class=\"mid table_window\">\
            <tr>\
              <td class='" + className + "_w'></td>\
                <td id='" + id + "_table_content' class='" + className + "_content' valign='top'>" + content + "</td>\
              <td class='" + className + "_e'></td>\
            </tr>\
          </table>\
            <table id='" + id + "_row3' class=\"bot table_window\">\
            <tr>\
              <td class='" + className + "_sw'>&nbsp;</td>\
                <td class='" + className + "_s'><div id='" + id + "_bottom' class='status_bar'>&nbsp;</div></td>\
                <td " + seAttributes + ">&nbsp;</td>\
            </tr>\
          </table>\
        ";

        Element.hide(win);
        this.options.parent.insertBefore(win, this.options.parent.firstChild);
        Event.observe($(id + "_content"), "load", this.options.onload);
        return win;
    },

// Sets window location
    setLocation: function(top, left) {
        if (top < 0)
            top = 0;
        if (left < 0)
            left = 0
        this.element.setStyle({top: top + 'px'});
        this.element.setStyle({left: left + 'px'});
        this.useLeft = true;
        this.useTop = true;
    },

// Gets window size
    getSize: function() {
        return {width: this.width, height: this.height};
    },

// Sets window size
    setSize: function(width, height) {
        width = parseFloat(width);
        height = parseFloat(height);

        // Check min and max size
        if (width < this.options.minWidth)
            width = this.options.minWidth;

        if (height < this.options.minHeight)
            height = this.options.minHeight;

        if (this.options.maxHeight && height > this.options.maxHeight)
            height = this.options.maxHeight;

        if (this.options.maxWidth && width > this.options.maxWidth)
            width = this.options.maxWidth;

        this.width = width;
        this.height = height;
        Element.setStyle(this.element, {width: width + this.widthW + this.widthE + "px"})
        Element.setStyle(this.element, {height: height + this.heightN + this.heightS + "px"})

        // Update content height
        var content = $(this.element.id + '_content')
        Element.setStyle(content, {height: height + 'px'});
        Element.setStyle(content, {width: width + 'px'});
    },

    updateHeight: function() {
        this.setSize(this.width, this.content.scrollHeight)
    },

    updateWidth: function() {
        this.setSize(this.content.scrollWidth, this.height)
    },

// Brings window to front
    toFront: function() {
        this.setZIndex(Windows.maxZIndex + 1);
        Windows.notify("onFocus", this);
    },

// Displays window modal state or not
    show: function(modal, onTop) {
        if (modal) {
            WindowUtilities.disableScreen(this.options.className, 'overlay_modal', this.getId());
            this.modal = true;
            this.setZIndex(Windows.maxZIndex + 20);
            Windows.unsetOverflow(this);
            Event.observe(document, "keypress", this.eventKeyPress);
        } else if (onTop && this.zIndex < Windows.maxZIndex) {
            this.setZIndex(Windows.maxZIndex + 1);
        }

        // To restore overflow if need be
        if (this.oldStyle)
            Element.setStyle(this.getContent(), {overflow: this.oldStyle});

        if (! this.width || !this.height) {
            var size = WindowUtilities._computeSize(this.content.innerHTML, this.content.id, this.width, this.height, 0)
            if (this.height)
                this.width = size + 5
            else
                this.height = size + 5
        }

        this.setSize(this.width, this.height);
        if (this.centered)
            this._center(this.centerTop, this.centerLeft);

        if (this.options.showEffect != Element.show && this.options.showEffectOptions)
            this.options.showEffect(this.element, this.options.showEffectOptions);
        else
            this.options.showEffect(this.element);

        this._checkIEOverlapping();
        Windows.notify("onShow", this);
    },

// Displays window modal state or not at the center of the page
    showCenter: function(modal, top, left) {
        this.centered = true;
        this.centerTop = top;
        this.centerLeft = left;

        this.show(modal);
    },

    isVisible: function() {
        return this.element.visible();
    },

    _center: function(top, left) {
        var windowScroll = WindowUtilities.getWindowScroll();
        var pageSize = WindowUtilities.getPageSize();

        if (!top)
            top = (pageSize.windowHeight - (this.height + this.heightN + this.heightS)) / 2;
        top += windowScroll.top

        if (!left)
            left = (pageSize.windowWidth - (this.width + this.widthW + this.widthE)) / 2;
        left += windowScroll.left

        this.setLocation(top, left);
        this.toFront();
    },

    _recenter: function(event) {
        if (this.modal) {
            var pageSize = WindowUtilities.getPageSize();
            // set height of Overlay to take up whole page and show
            if ($('overlay_modal')) {
                $('overlay_modal').style.height = (pageSize.pageHeight + 'px');
                $('overlay_modal').style.width = (pageSize.pageWidth + 'px');
            }
            if (this.centered)
                this._center(this.centerTop, this.centerLeft);
        }
    },

// Hides window
    hide: function() {
        if (this.modal) {
            WindowUtilities.enableScreen();
            Windows.resetOverflow();
            Event.stopObserving(document, "keypress", this.eventKeyPress);
        }
        // To avoid bug on scrolling bar
        this.oldStyle = Element.getStyle(this.getContent(),'overflow') || "auto"
        Element.setStyle(this.getContent(), {overflow: "hidden"});

        this.options.hideEffect(this.element, this.options.hideEffectOptions);

        if (this.iefix)
            this.iefix.hide();
        Windows.notify("onHide", this);
    },

    minimize: function() {
        var r2 = $(this.getId() + "_row2");
        var dh = r2.getDimensions().height;

        if (r2.visible()) {
            var h = this.element.getHeight() - dh
            r2.hide()
            this.element.setStyle({height: h + "px"})
            if (! this.useTop) {
                var bottom = parseFloat(this.element.getStyle('bottom'));
                this.element.setStyle({bottom: (bottom + dh) + 'px'});
            }
        }
        else {
            var h = this.element.getHeight() + dh;
            this.element.setStyle({height: h + "px"})
            if (! this.useTop) {
                var bottom = parseFloat(this.element.getStyle('bottom'));
                this.element.setStyle({bottom: (bottom - dh) + 'px'});
            }
            r2.show();

            this.toFront();
        }
        Windows.notify("onMinimize", this);

        // Store new location/size if need be
        this._saveCookie()
    },

    maximize: function() {
        if (this.storedLocation != null) {
            this._restoreLocation();
            if (this.iefix)
                this.iefix.hide();
        }
        else {
            this._storeLocation();
            Windows.unsetOverflow(this);

            var windowScroll = WindowUtilities.getWindowScroll();
            var pageSize = WindowUtilities.getPageSize();

            this.element.setStyle(this.useLeft ? {left: windowScroll.left} : {right: windowScroll.left});
            this.element.setStyle(this.useTop ? {top: windowScroll.top} : {bottom: windowScroll.top});

            this.setSize(pageSize.windowWidth - this.widthW - this.widthE, pageSize.windowHeight - this.heightN - this.heightS)
            this.toFront();
            if (this.iefix)
                this._fixIEOverlapping();
        }
        Windows.notify("onMaximize", this);

        // Store new location/size if need be
        this._saveCookie()
    },

    isMinimized: function() {
        var r2 = $(this.getId() + "_row2");
        return !r2.visible();
    },

    isMaximized: function() {
        return (this.storedLocation != null);
    },

    setOpacity: function(opacity) {
        if (Element.setOpacity)
            Element.setOpacity(this.element, opacity);
    },

    setZIndex: function(zindex) {
        this.zIndex = zindex;
        this.element.setStyle({zIndex: zindex});
        Windows.updateZindex(zindex, this);
    },

    setTitle: function(newTitle) {
        if (!newTitle || newTitle == "")
            newTitle = "&nbsp;";

        Element.update(this.element.id + '_top', newTitle);
    },

    setStatusBar: function(element) {
        var statusBar = $(this.getId() + "_bottom");

        if (typeof(element) == "object") {
            if (this.bottombar.firstChild)
                this.bottombar.replaceChild(element, this.bottombar.firstChild);
            else
                this.bottombar.appendChild(element);
        }
        else
            this.bottombar.innerHTML = element;
    },

    _checkIEOverlapping: function() {
        if (!this.iefix && (navigator.appVersion.indexOf('MSIE') > 0) && (navigator.userAgent.indexOf('Opera') < 0) && (this.element.getStyle('position') == 'absolute')) {
            new Insertion.After(this.element.id, '<iframe id="' + this.element.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.element.id + '_iefix');
        }
        if (this.iefix)
            setTimeout(this._fixIEOverlapping.bind(this), 50);
    },

    _fixIEOverlapping: function() {
        Position.clone(this.element, this.iefix);
        this.iefix.style.zIndex = this.element.style.zIndex - 1;
        this.iefix.show();
    },

    _getWindowBorderSize: function(event) {
        // Hack to get real window border size!!
        var div = this._createHiddenDiv(this.options.className + "_n")
        this.heightN = Element.getDimensions(div).height;
        div.parentNode.removeChild(div)

        var div = this._createHiddenDiv(this.options.className + "_s")
        this.heightS = Element.getDimensions(div).height;
        div.parentNode.removeChild(div)

        var div = this._createHiddenDiv(this.options.className + "_e")
        this.widthE = Element.getDimensions(div).width;
        div.parentNode.removeChild(div)

        var div = this._createHiddenDiv(this.options.className + "_w")
        this.widthW = Element.getDimensions(div).width;
        div.parentNode.removeChild(div);
        // Safari size fix
        if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
            this.setSize(this.width, this.height);
        if (this.doMaximize)
            this.maximize();
        if (this.doMinimize)
            this.minimize();
    },

    _createHiddenDiv: function(className) {
        var objBody = document.getElementsByTagName("body").item(0);
        var win = document.createElement("div");
        win.setAttribute('id', this.element.id + "_tmp");
        win.className = className;
        win.style.display = 'none'
        win.innerHTML = ''
        objBody.insertBefore(win, objBody.firstChild)
        return win
    },

    _storeLocation: function() {
        if (this.storedLocation == null) {
            this.storedLocation = {useTop: this.useTop, useLeft: this.useLeft,
                top: this.element.getStyle('top'), bottom: this.element.getStyle('bottom'),
                left: this.element.getStyle('left'), right: this.element.getStyle('right'),
                width: this.width, height: this.height };
        }
    },

    _restoreLocation: function() {
        if (this.storedLocation != null) {
            this.useLeft = this.storedLocation.useLeft;
            this.useTop = this.storedLocation.useTop;

            this.element.setStyle(this.useLeft ? {left: this.storedLocation.left} : {right: this.storedLocation.right});
            this.element.setStyle(this.useTop ? {top: this.storedLocation.top} : {bottom: this.storedLocation.bottom});
            this.setSize(this.storedLocation.width, this.storedLocation.height);

            Windows.resetOverflow();
            this._removeStoreLocation();
        }
    },

    _removeStoreLocation: function() {
        this.storedLocation = null;
    },

    _saveCookie: function() {
        if (this.cookie) {
            var value = "";
            if (this.useLeft)
                value += "l:" + (this.storedLocation ? this.storedLocation.left : this.element.getStyle('left'))
            else
                value += "r:" + (this.storedLocation ? this.storedLocation.right : this.element.getStyle('right'))
            if (this.useTop)
                value += ",t:" + (this.storedLocation ? this.storedLocation.top : this.element.getStyle('top'))
            else
                value += ",b:" + (this.storedLocation ? this.storedLocation.bottom : this.element.getStyle('bottom'))

            value += "," + (this.storedLocation ? this.storedLocation.width : this.width);
            value += "," + (this.storedLocation ? this.storedLocation.height : this.height);
            value += "," + this.isMinimized();
            value += "," + this.isMaximized();
            WindowUtilities.setCookie(value, this.cookie)
        }
    },

    toggleDialog: function(anchorElement) {
        if (!this.shown) {
            this.positionUnder(anchorElement);
            this.show(false, true);
        } else {
            this.hide();
        }
    },

    toggle: function() {
        if (!this.shown) {
            this.show(false, true);
        } else {
            this.hide();
        }
    },

    positionUnder: function(anchorElement) {
        anchorElement = $(anchorElement);
        var offset = Position.cumulativeOffset(anchorElement);
        var top = offset[1] + anchorElement.getHeight()
        var left = offset[0];
        this.setLocation(top, left);
    },

    positionCentered: function(top) {
        var left = this.getLeftForCentering();
        left += WindowUtilities.getWindowScroll().left
        this.setLocation(top, left);
    },

    positionSameTopCentered: function(anchorElement) {
        anchorElement = $(anchorElement);
        var offset = Position.cumulativeOffset(anchorElement);
        var top = offset[1];
        this.positionCentered(top);
    },

    getLeftForCentering: function() {
        return (WindowUtilities.getPageSize().windowWidth - (this.width + this.widthW + this.widthE)) / 2;
    },

    positionRelatively: function(anchorElement, xOffset, yOffset) {
        anchorElement = $(anchorElement);
        var offset = Position.cumulativeOffset(anchorElement);
        var top = offset[1] + yOffset;
        var left = offset[0] + xOffset;
        this.setLocation(top, left);
    }

};

// Windows containers, register all page windows
var Windows = {
    windows: [],
    observers: [],
    focusedWindow: null,
    maxZIndex: 1,

    addObserver: function(observer) {
        this.removeObserver(observer);
        this.observers.push(observer);
    },

    removeObserver: function(observer) {
        this.observers = this.observers.reject(function(o) {
            return o == observer
        });
    },

    notify: function(eventName, win) {  //  onStartResize(), onEndResize(), onStartMove(), onEndMove(), onClose(), onDestroy(), onMinimize(), onMaximize(), onHide(), onShow(), onFocus()
        this.observers.each(function(o) {
            if (o[eventName]) o[eventName](eventName, win);
        });
    },

// Gets window from its id
    getWindow: function(id) {
        return this.windows.detect(function(d) {
            return d.getId() == id
        });
    },

// Gets the last focused window
    getFocusedWindow: function() {
        return this.focusedWindow;
    },

// Registers a new window (called by Windows constructor)
    register: function(win) {
        this.windows.push(win);
    },

// Unregisters a window (called by Windows destructor)
    unregister: function(win) {
        this.windows = this.windows.reject(function(d) {
            return d == win
        });
    },

// Closes a window with its id
    close: function(id) {
        var win = this.getWindow(id);
        // Asks delegate if exists
        if (win) {
            if (win.getDelegate() && ! win.getDelegate().canClose(win))
                return;
            if ($(id + "_close"))
                $(id + "_close").onclick = null;
            if ($(id + "_minimize"))
                $(id + "_minimize").onclick = null;
            if ($(id + "_maximize"))
                $(id + "_maximize").onclick = null;

            this.notify("onClose", win);
            win.hide();
        }
    },

// Closes all windows
    closeAll: function() {
        this.windows.each(function(w) {
            Windows.close(w.getId())
        });
    },

// Minimizes a window with its id
    minimize: function(id) {
        var win = this.getWindow(id)
        if (win)
            win.minimize();
    },

// Maximizes a window with its id
    maximize: function(id) {
        var win = this.getWindow(id)
        if (win)
            win.maximize();
    },

    unsetOverflow: function(except) {
        this.windows.each(function(d) {
            d.oldOverflow = d.getContent().getStyle("overflow") || "auto";
            d.getContent().setStyle({overflow: "hidden"})
        });
        if (except && except.oldOverflow)
            except.getContent().setStyle({overflow: except.oldOverflow});
    },

    resetOverflow: function() {
        this.windows.each(function(d) {
            if (d.oldOverflow) d.getContent().setStyle({overflow: d.oldOverflow})
        });
    },

    updateZindex: function(zindex, win) {
        if (zindex > this.maxZIndex)
            this.maxZIndex = zindex;
        this.focusedWindow = win;
    }
};

var Dialog = {
    dialogId: null,
    win: null,
    onCompleteFunc: null,
    callFunc: null,
    parameters: null,

    confirm: function(content, parameters) {
        // Get Ajax return before
        if (typeof content != "string") {
            Dialog._runAjaxRequest(content, parameters, Dialog.confirm);
            return
        }

        parameters = parameters || {};
        var okLabel = parameters.okLabel ? parameters.okLabel : "Ok";
        var cancelLabel = parameters.cancelLabel ? parameters.cancelLabel : "Cancel";

        var windowParam = parameters.windowParameters || {};
        windowParam.className = windowParam.className || "alert";

        okButtonClass = "class ='" + (parameters.buttonClass ? parameters.buttonClass + " " : "") + " ok_button'"
        cancelButtonClass = "class ='" + (parameters.buttonClass ? parameters.buttonClass + " " : "") + " cancel_button'"
        var content = "\
            <div class='" + windowParam.className + "_message'>" + content + "</div>\
                <div class='" + windowParam.className + "_buttons'>\
                    <input type='button' value='" + okLabel + "' onclick='Dialog.okCallback()'" + okButtonClass + "/>\
                    <input type='button' value='" + cancelLabel + "' onclick='Dialog.cancelCallback()' " + cancelButtonClass + "/>\
                </div>\
        ";
        this._openDialog(content, parameters)
        return this.win
    },

    alert: function(content, parameters) {
        // Get Ajax return before
        if (typeof content != "string") {
            Dialog._runAjaxRequest(content, parameters, Dialog.alert);
            return
        }

        parameters = parameters || {};
        var okLabel = parameters.okLabel ? parameters.okLabel : "Ok";

        var windowParam = parameters.windowParameters || {};
        windowParam.className = windowParam.className || "alert";

        okButtonClass = "class ='" + (parameters.buttonClass ? parameters.buttonClass + " " : "") + " ok_button'"
        var content = "\
            <div class='" + windowParam.className + "_message'>" + content + "</div>\
                <div class='" + windowParam.className + "_buttons'>\
                    <input type='button' value='" + okLabel + "' onclick='Dialog.okCallback()'" + okButtonClass + "/>\
                </div>";
        return this._openDialog(content, parameters)
    },

    info: function(content, parameters) {
        // Get Ajax return before
        if (typeof content != "string") {
            Dialog._runAjaxRequest(content, parameters, Dialog.info);
            return
        }

        parameters = parameters || {};
        parameters.windowParameters = parameters.windowParameters || {};

        var className = parameters.windowParameters.className || "alert";

        var content = "<div id='modal_dialog_message' class='" + className + "_message'>" + content + "</div>";
        if (parameters.showProgress)
            content += "<div id='modal_dialog_progress' class='" + className + "_progress'>	</div>";

        parameters.windowParameters.ok = null;
        parameters.windowParameters.cancel = null;
        parameters.windowParameters.className = className;

        return this._openDialog(content, parameters)
    },

    setInfoMessage: function(message) {
        $('modal_dialog_message').update(message);
    },

    closeInfo: function() {
        Windows.close(this.dialogId);
    },

    _openDialog: function(content, parameters) {
        // remove old dialog
        if (this.win)
            this.win.destroy();

        if (! parameters.windowParameters.height && ! parameters.windowParameters.width) {
            parameters.windowParameters.width = WindowUtilities.getPageSize().pageWidth / 2;
        }
        this.dialogId = parameters.id ? parameters.id : 'modal_dialog'

        // compute height or width if need be
        if (! parameters.windowParameters.height || ! parameters.windowParameters.width) {
            var size = WindowUtilities._computeSize(content, this.dialogId, parameters.windowParameters.width, parameters.windowParameters.height)
            if (parameters.windowParameters.height)
                parameters.windowParameters.width = size + 5
            else
                parameters.windowParameters.height = size + 5
        }
        var windowParam = parameters && parameters.windowParameters ? parameters.windowParameters : {};
        windowParam.resizable = windowParam.resizable || false;

        windowParam.effectOptions = windowParam.effectOptions || {duration: 1};
        windowParam.minimizable = false;
        windowParam.maximizable = false;
        windowParam.closable = false;
        this.win = new Window(this.dialogId, windowParam);
        this.win.getContent().innerHTML = content;
        this.win.showCenter(true, parameters.top, parameters.left);

        this.win.cancelCallback = parameters.cancel;
        this.win.okCallback = parameters.ok;

        return this.win;
    },

    _getAjaxContent: function(originalRequest) {
        Dialog.callFunc(originalRequest.responseText, Dialog.parameters)
    },

    _runAjaxRequest: function(message, parameters, callFunc) {
        if (message.options == null)
            message.options = {}
        Dialog.onCompleteFunc = message.options.onComplete;
        Dialog.parameters = parameters;
        Dialog.callFunc = callFunc;

        message.options.onComplete = Dialog._getAjaxContent;
        new Ajax.Request(message.url, message.options);
    },

    okCallback: function() {
        if (!this.win.okCallback || this.win.okCallback(this.win))
            this.win.hide();
    },

    cancelCallback: function() {
        this.win.hide();
        if (this.win.cancelCallback)
            this.win.cancelCallback(this.win);
    }
}
/*
	Based on Lightbox JS: Fullsize Image Overlays 
	by Lokesh Dhakar - http://www.huddletogether.com

	For more information on this script, visit:
	http://huddletogether.com/projects/lightbox/

	Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/
	(basically, do anything you want, just leave my name and link)
*/

var isIE = navigator.appVersion.match(/MSIE/) == "MSIE";

var WindowUtilities = {
// From script.aculo.us
    getWindowScroll: function() {
        var w = window;
        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 };

    },
//
// getPageSize()
// Returns array with page width, height and window width, height
// Core code from - quirksmode.org
// Edit for Firefox by pHaez
//
    getPageSize: function() {
        var xScroll, yScroll;

        if (window.innerHeight && window.scrollMaxY) {
            xScroll = document.body.scrollWidth;
            yScroll = window.innerHeight + window.scrollMaxY;
        } else if (document.body.scrollHeight > document.body.offsetHeight) { // all but Explorer Mac
            xScroll = document.body.scrollWidth;
            yScroll = document.body.scrollHeight;
        } else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
            xScroll = document.body.offsetWidth;
            yScroll = document.body.offsetHeight;
        }

        var windowWidth, windowHeight;

        if (self.innerHeight) {    // all except Explorer
            windowWidth = self.innerWidth;
            windowHeight = self.innerHeight;
        } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
            windowWidth = document.documentElement.clientWidth;
            windowHeight = document.documentElement.clientHeight;
        } else if (document.body) { // other Explorers
            windowWidth = document.body.clientWidth;
            windowHeight = document.body.clientHeight;
        }
        var pageHeight, pageWidth;

        // for small pages with total height less then height of the viewport
        if (yScroll < windowHeight) {
            pageHeight = windowHeight;
        } else {
            pageHeight = yScroll;
        }

        // for small pages with total width less then width of the viewport
        if (xScroll < windowWidth) {
            pageWidth = windowWidth;
        } else {
            pageWidth = xScroll;
        }

        return {pageWidth: pageWidth ,pageHeight: pageHeight , windowWidth: windowWidth, windowHeight: windowHeight};
    },

    disableScreen: function(className, overlayId, contentId) {
        WindowUtilities.initLightbox(overlayId, className);
        var objBody = document.getElementsByTagName("body").item(0);

        // prep objects
        var objOverlay = $(overlayId);

        var pageSize = WindowUtilities.getPageSize();

        // Hide select boxes as they will 'peek' through the image in IE
        if (contentId && isIE) {
            $$('select').each(function(element) {
                element.style.visibility = "hidden"
            });
            $$('#' + contentId + ' select').each(function(element) {
                element.style.visibility = "visible"
            });
        }

        // set height of Overlay to take up whole page and show
        objOverlay.style.height = (pageSize.pageHeight + 'px');
        objOverlay.style.width = (pageSize.windowWidth + 'px');
        objOverlay.style.display = 'block';
    },

    enableScreen: function(id) {
        id = id || 'overlay_modal'
        var objOverlay = $(id);
        if (objOverlay) {
            // hide lightbox and overlay
            objOverlay.style.display = 'none';

            // make select boxes visible
            if (isIE) {
                $$('select').each(function(element) {
                    element.style.visibility = "visible"
                });
            }
            objOverlay.parentNode.removeChild(objOverlay);
        }
    },

// initLightbox()
// Function runs on window load, going through link tags looking for rel="lightbox".
// These links receive onclick events that enable the lightbox display for their targets.
// The function also inserts html markup at the top of the page which will be used as a
// container for the overlay pattern and the inline image.
    initLightbox: function(id, className) {
        // Already done, just update zIndex
        if ($(id)) {
            Element.setStyle(id, {zIndex: Windows.maxZIndex + 10});
        }
        // create overlay div and hardcode some functional styles (aesthetic styles are in CSS file)
        else {
            var objBody = document.getElementsByTagName("body").item(0);
            var objOverlay = document.createElement("div");
            objOverlay.setAttribute('id', id);
            objOverlay.className = "overlay_" + className
            objOverlay.style.display = 'none';
            objOverlay.style.position = 'absolute';
            objOverlay.style.top = '0';
            objOverlay.style.left = '0';
            objOverlay.style.zIndex = Windows.maxZIndex + 10;
            objOverlay.style.width = '100%';
            objBody.insertBefore(objOverlay, objBody.firstChild);
        }
    },

    setCookie: function(value, parameters) {
        document.cookie = parameters[0] + "=" + escape(value) +
                          ((parameters[1]) ? "; expires=" + parameters[1].toGMTString() : "") +
                          ((parameters[2]) ? "; path=" + parameters[2] : "") +
                          ((parameters[3]) ? "; domain=" + parameters[3] : "") +
                          ((parameters[4]) ? "; secure" : "");
    },

    getCookie: function(name) {
        var dc = document.cookie;
        var prefix = name + "=";
        var begin = dc.indexOf("; " + prefix);
        if (begin == -1) {
            begin = dc.indexOf(prefix);
            if (begin != 0) return null;
        } else {
            begin += 2;
        }
        var end = document.cookie.indexOf(";", begin);
        if (end == -1) {
            end = dc.length;
        }
        return unescape(dc.substring(begin + prefix.length, end));
    },

    _computeSize: function(content, id, width, height, margin) {
        if (margin == null)
            margin = 5;

        var objBody = document.getElementsByTagName("body").item(0);
        var tmpObj = document.createElement("div");
        tmpObj.setAttribute('id', id);

        if (height)
            tmpObj.style.height = height + "px"
        else
            tmpObj.style.width = width + "px"

        tmpObj.style.position = 'absolute';
        tmpObj.style.top = '0';
        tmpObj.style.left = '0';
        tmpObj.style.display = 'none';

        tmpObj.innerHTML = content;
        objBody.insertBefore(tmpObj, objBody.firstChild);

        var size;
        if (height)
            size = $(id).getDimensions().width + margin;
        else
            size = $(id).getDimensions().height + margin;
        objBody.removeChild(tmpObj);

        return size;
    }
}

var MWindow = Class.create();
Object.extend(Object.extend(MWindow.prototype, Window.prototype), {
    initialize : function(id, parameters) {
        if (eval("typeof " + id + " != 'undefined'")) {
            var tmp = eval(id);
            parameters.top = tmp.y;
            parameters.left = tmp.x;
            parameters.width = tmp.width;
            parameters.height = tmp.height;
        }
        Window.prototype.initialize.call(this, id, parameters);
    },
    _endDrag : function(event) {
        Window.prototype._endDrag.call(this, event);

        if (this.shouldSaveMWindow()) {
            var x = Element.getStyle(this.element, "left");
            x = x.substr(0, x.length - 2);
            var y = Element.getStyle(this.element, "top");
            y = y.substr(0, y.length - 2);

            var pars = "name=" + this.element.id + "&x=" + x + "&y=" + y + "&width=" + this.width + "&height=" + this.height;

            var mxhr = new Mxhr({
                url: 'saveWindow.do',
                parameters: pars,
                onSuccessHandler: function() {
                }
            });

            mxhr.execute();
        }
    },

    shouldSaveMWindow:function(){
      return true;
    },

    isCustomPositioned : function() {
        return eval("typeof " + this.getId() + " != 'undefined' && typeof " + (this.getId() + ".x") + " != 'undefined'");
    }
});

Windows.addObserver({
    onHide:function(event, window) {
        window.shown = false;
    },
    onShow:function(event, window) {
        window.shown = true;
    }
});
/*
Copyright (c) 2005 JSON.org

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The Software shall be used for Good, not Evil.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

/*
    The global object JSON contains two methods.

    JSON.stringify(value) takes a JavaScript value and returns JSON text.
    The value must not be cyclical.

    JSON.parse(text) takes a JSON text and returns JavaScript value. It will
    throw a 'JSONError' exception if there is an error.
*/
var JSON = {
    copyright: '(c)2005 JSON.org',
    license: 'http://www.crockford.com/JSON/license.html',
/*
    Stringify a JavaScript value, returns JSON text.
*/
    stringify: function (v) {
        var a = [];

/*
    Emit a string.
*/
        function e(s) {
            a[a.length] = s;
        }

/*
    Convert a value.
*/
        function g(x) {
            var c, i, l, v;

            switch (typeof x) {
            case 'object':
                if (x) {
                    if (x instanceof Array) {
                        e('[');
                        l = a.length;
                        for (i = 0; i < x.length; i += 1) {
                            v = x[i];
                            if (typeof v != 'undefined' &&
                                    typeof v != 'function') {
                                if (l < a.length) {
                                    e(',');
                                }
                                g(v);
                            }
                        }
                        e(']');
                        return;
                    } else if (typeof x.toString != 'undefined') {
                        e('{');
                        l = a.length;
                        for (i in x) {
                            v = x[i];
                            if (x.hasOwnProperty(i) &&
                                    typeof v != 'undefined' &&
                                    typeof v != 'function') {
                                if (l < a.length) {
                                    e(',');
                                }
                                g(i);
                                e(':');
                                g(v);
                            }
                        }
                        return e('}');
                    }
                }
                e('null');
                return;
            case 'number':
                e(isFinite(x) ? +x : 'null');
                return;
            case 'string':
                l = x.length;
                e('"');
                for (i = 0; i < l; i += 1) {
                    c = x.charAt(i);
                    if (c >= ' ') {
                        if (c == '\\' || c == '"') {
                            e('\\');
                        }
                        e(c);
                    } else {
                        switch (c) {
                            case '\b':
                                e('\\b');
                                break;
                            case '\f':
                                e('\\f');
                                break;
                            case '\n':
                                e('\\n');
                                break;
                            case '\r':
                                e('\\r');
                                break;
                            case '\t':
                                e('\\t');
                                break;
                            default:
                                c = c.charCodeAt();
                                e('\\u00' + Math.floor(c / 16).toString(16) +
                                    (c % 16).toString(16));
                        }
                    }
                }
                e('"');
                return;
            case 'boolean':
                e(String(x));
                return;
            default:
                e('null');
                return;
            }
        }
        g(v);
        return a.join('');
    },
/*
    Parse a JSON text, return JavaScript value.
*/
    parse: function (text) {
        var p = /^\s*(([,:{}\[\]])|"(\\.|[^\x00-\x1f"\\])*"|-?\d+(\.\d*)?([eE][+-]?\d+)?|true|false|null)\s*/,
            token,
            operator;

        function error(m, t) {
            throw {
                name: 'JSONError',
                message: m,
                text: t || operator || token
            };
        }

        function next(b) {
            if (b && b != operator) {
                error("Expected '" + b + "'");
            }
            if (text) {
                var t = p.exec(text);
                if (t) {
                    if (t[2]) {
                        token = null;
                        operator = t[2];
                    } else {
                        operator = null;
                        try {
                            token = eval(t[1]);
                        } catch (e) {
                            error("Bad token", t[1]);
                        }
                    }
                    text = text.substring(t[0].length);
                } else {
                    error("Unrecognized token", text);
                }
            } else {
                token = operator = undefined;
            }
        }


        function val() {
            var k, o;
            switch (operator) {
            case '{':
                next('{');
                o = {};
                if (operator != '}') {
                    for (;;) {
                        if (operator || typeof token != 'string') {
                            error("Missing key");
                        }
                        k = token;
                        next();
                        next(':');
                        o[k] = val();
                        if (operator != ',') {
                            break;
                        }
                        next(',');
                    }
                }
                next('}');
                return o;
            case '[':
                next('[');
                o = [];
                if (operator != ']') {
                    for (;;) {
                        o.push(val());
                        if (operator != ',') {
                            break;
                        }
                        next(',');
                    }
                }
                next(']');
                return o;
            default:
                if (operator !== null) {
                    error("Missing value");
                }
                k = token;
                next();
                return k;
            }
        }
        next();
        return val();
    }
};
//variables populated at the server side:start
var productionMode;
var externalUrl;
var externalSecureUrl;
var staticUrl;
var userIsReadOnly;
var frolAdmin;

//var initialized in set_diary_js_vars.st
var userTypeFreeWeb;
//variables populated at the server side:end

//a test page can set this path to an absolute value
var minderPath = "";

var StdRegex = {
	NUMBER: /^[0-9]+\.?[0-9]*$/,
	NUMBER_AND_UNITS: /^[0-9]+\.?[0-9]*.*$/,
	WEIGHT: /^[0-9]+\.?[0-9]*\s?(g|oz|G|OZ)?$/,
	PERCENT: /^[0-9]+\.?[0-9]*\s?(%)?$/,
	ZIP: /^\d{5}$/,
	AMEX: /^\d{15}$/,
	NONAMEX: /(^\d{16}$)|(^\d{13}$)/,
	CVV2AMEX: /^\d{4}$/,
	CVV2NONAMEX: /^\d{3}$/,
	EMAIL: /^([a-zA-Z0-9_\.\-\+])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,
	USERNAME_OR_EMAIL: /^([a-zA-Z0-9_\@\.\-])+\s?$/,
	USERNAME: /^([a-zA-Z0-9_\.\-])+\s?$/
};

//Browser detection
//see much more comprehensive detection source: Professional JavaScript for Web Developers, Nicholas C. Zakas
var ProDetect = {};
ProDetect.agentHas = function(sign) {
	return navigator.userAgent.toLowerCase().indexOf(sign.toLowerCase()) > -1;
}
ProDetect.isOpera = ProDetect.agentHas("Opera");
ProDetect.isIE = ProDetect.agentHas("compatible") && ProDetect.agentHas("MSIE") && !ProDetect.isOpera;
ProDetect.isWin = (navigator.platform == "Win32") || (navigator.platform == "Windows");
ProDetect.isIEnot7 = (document.all && !window.opera && window.XMLHttpRequest == null);
ProDetect.isIE7 = (document.all && !window.opera && window.XMLHttpRequest != null);
ProDetect.isKHTML = ProDetect.agentHas('KHTML') || ProDetect.agentHas('Konqueror') || ProDetect.agentHas('AppleWebKit');
ProDetect.isIPod = ProDetect.agentHas('iPhone') || ProDetect.agentHas('ipod');

var MxhrResponse = Class.create();
MxhrResponse.prototype = {
	initialize: function(xhrResponseText, sMessage) {
		this.xhrResponseText = xhrResponseText;
		this.message = sMessage;
		this.aPayloads = [];
	},

	add: function(payloadId, payloadData) {
		var payload = {};
		payload.payloadId = payloadId;
		payload.payloadData = payloadData;
		this.aPayloads.push(payload);
	},

	getPayloadData: function(payloadId) {
		if (arguments.length == 0) {
			return this.aPayloads[0].payloadData;
		} else {
			for (var i = 0; i < this.aPayloads.length; i++) {
				//                console.log('mxhr payload id' + this.aPayloads[i].payloadId)
				if (this.aPayloads[i].payloadId == payloadId) {
					return this.aPayloads[i].payloadData;
				}
			}
		}

		//        logDefault("Mxhr getPayloadData: payloadId " + payloadId + " not found");
		//        logDefault("response: " + this.xhrResponseText);

		return null;
	}
};

var Mxhr = Class.create();
Mxhr.DEFAULT_HOURGLASS_MSECS = 500;
Mxhr.prototype = {
	initialize: function(options) {
		//required fields in options:
		//url
		//parameters
		//onSuccessHandler

		//default field values:
		this.options = {
			onFailureHandler: null,
			hourglassId: null,
			disableId: null,
			loadingMessageId: null,
			messageId: null,
			showSystemErrors: true,
			allowRelogon: true,
			timeoutMsecs: 10000,
			hourglassMsecs: Mxhr.DEFAULT_HOURGLASS_MSECS,
			onSuccessHandler: function() {
			}
		};
		Object.extend(this.options, options);
		assert('Mxhr requires url', options.url);

		this.xhrOptions = {
			parameters: this.options.parameters ? this.options.parameters : "",//prototype 1.5.0 does not like parameters==null
			postBody: this.options.postBody,
			onSuccess: this.xhrSuccess.bind(this),
			onFailure: this.xhrFailure.bind(this)
		};
	},

	DELIMITER: "`+`",

	execute: function() {
		if (this.options.messageId != null) {
			var elt = $(this.options.messageId);
			if (elt) {
				elt.innerHTML = "";
			}
		}
		this.xhr = new Ajax.Request(this.options.url, this.xhrOptions);

		var requestTimeoutClosure = this.requestTimeoutHandler.bind(this);
		var hourglassTimeoutClosure = this.hourglassTimeoutHandler.bind(this);

		this.callTimeoutId = window.setTimeout(requestTimeoutClosure, this.options.timeoutMsecs);

		if (this.options.hourglassId != null || this.options.disableId != null) {
			this.hourglassTimeoutId = window.setTimeout(hourglassTimeoutClosure, this.options.hourglassMsecs);
		}
	},

	xhrSuccess: function(xhr) {
		//logDefault('xhr.responseText=' + xhr.responseText.substring(0, 400));
		this.clearTimeouts();

		var responseStatus = null;
		var message = null;

		var items = xhr.responseText.split(this.DELIMITER);
		var firstSpace = items[0].indexOf(" ");
		if (firstSpace > -1) {
			responseStatus = items[0].substring(0, firstSpace);
			message = (responseStatus == "OK" || responseStatus == "ERROR") ? items[0].substring(firstSpace + 1) : null;
		}

		var mxhrResponse = new MxhrResponse(xhr.responseText, message);
		try {
			//logDefault("items.length=" + items.length);

			for (var i = 1; i < items.length; i++) {
				var spaceIndex = items[i].indexOf(" ");
				var type = items[i].substring(0, spaceIndex);

				var payloadId = null;
				if (type == "dom" || type == "idjson" || type == "idtext") {
					var spaceIndex2 = items[i].indexOf(" ", spaceIndex + 1);
					payloadId = items[i].substring(spaceIndex + 1, spaceIndex2);
					spaceIndex = spaceIndex2;
				}

				var payloadData = items[i].substring(spaceIndex + 1);
				//logDefault("payloadData=" + payloadData);
				if (type == "dom") {
					setInnerHtml($(payloadId), payloadData);
					//                    if (payloadId == "divTipBox" && $('TIPBOX').visible()) {
					//                        logDefault("highlighting tipbox")
					//                        new Effect.Highlight($("divTipBox"));
					//                    }
				} else {
					if (type == "json" || type == "idjson") {
						payloadData = JSON.parse(payloadData);
					}
					//logDefault("adding " + payloadData);
					mxhrResponse.add(payloadId, payloadData);
				}
			}

			if (responseStatus == "ERROR") {
				this.commonErrorHandler(false, message, mxhrResponse);
			} else {
				try {
					this.options.onSuccessHandler(mxhrResponse);
				} catch(error) {
					logDefault('ERROR while handling Mxhr response:' + error + ";responseText=" + xhr.responseText);
					this.commonErrorHandler(true, UserMessage.get(), mxhrResponse);
				}
			}
		} catch(error) {
			logDefault('ERROR in Mxhr:error=' + error);
			this.commonErrorHandler(false, UserMessage.get(), mxhrResponse);
		}
	},

	xhrFailure: function(xhr) {
		this.clearTimeouts();
		if (xhr.status == 401) {
			if (this.options.allowRelogon) {
				if (location.protocol == "http:") {
					if (location.port == "") {
						//redirect to http to let server establish session over http to carry it over to https and back
						location.href = "http://" + location.host + "/" + "timeout.do";
					} else {
						location.href = "http://" + location.host + ":" + location.port + "/" + "timeout.do";
					}
				} else {
					this.showRelogonDom();
				}
			} else {
				this.commonErrorHandler(true, 'Username or password is not correct.', null);
			}
		} else {
			this.commonErrorHandler(true, UserMessage.get(), null);
		}
	},

	requestTimeoutHandler: function() {
		var state = this.xhr.transport.readyState;
		if (state == 1 || state == 2 || state == 3) {
			this.xhr.transport.abort();
			this.clearTimeouts();
			this.commonErrorHandler(true, 'Cannot communicate with the server, it seems that the network is down or busy.  Please, try again shortly.', null);
		}
	},

	hourglassTimeoutHandler: function() {
		if (this.options.hourglassId != null) {
			$(this.options.hourglassId).style.visibility = 'visible';
		}
		if (this.options.disableId != null) {
			$(this.options.disableId).disabled = "true";
		}
	},

	clearTimeouts: function() {
		window.clearTimeout(this.callTimeoutId);

		if (this.options.hourglassId != null || this.options.disableId != null) {
			window.clearTimeout(this.hourglassTimeoutId);
			if (this.options.hourglassId != null) {
				$(this.options.hourglassId).style.visibility = 'hidden';
			}
			if (this.options.disableId != null) {
				$(this.options.disableId).disabled = "";
			}
		}

		if (this.options.loadingMessageId != null) {
			$(this.options.loadingMessageId).innerHTML = '';
		}
	},

	commonErrorHandler: function(isSystemError, message, mxhrResponse) {
		logDefault('ERROR in Mxhr:message=' + message)
		if (!isSystemError || this.options.showSystemErrors) {
			UserMessage.show(message, this.options.messageId);
		}
		if (this.options.onFailureHandler != null) {
			this.options.onFailureHandler(mxhrResponse);
		}
	},

	showRelogonDom: function() {
		var strMessage = "Your session had been inactive and was expired to protect your private data.  " +
						 "Please enter your username and password to sign in to MyNetDiary";

		var h = "<form class='stdform'>" +
				"  <fieldset><div id='divRelogonErrors' class='error'>" + strMessage + "</div>" +
				"<div class='field'>" +
				"  <label for='txtRelogonName' class='required'>Username</label>" +
				"  <input type='text' name='txtRelogonName' id='txtRelogonName' tabindex='1'/>" +
				"</div>" +
				"<div class='field'>" +
				"  <label for='txtRelogonPassword' class='required'>Password</label>" +
				"  <input type='password' name='txtRelogonPassword' id='txtRelogonPassword' tabindex='2'/>" +
				"</div>" +
				"</fieldset>" +
				"  <img src='img/hourglass.gif' id='relogonHourglass' style='visibility:hidden'/>" +
				"  <div class='buttons'>" +
				"    <input type='submit' class='submit' name='cmdRelogonOK' id='cmdRelogonOK' value='Sign In' tabindex='3'/>" +
				"  </div>" +
				"</form>";

		if (!Mxhr.relogonDialog) {
			Mxhr.relogonDialog = new Window('relogonDialog', {
				title:"Sign in required",
				resizable: false,
				minimizable:false,
				maximizable:false,
				closable:false,
				draggable:false,
				showEffect: Element.show,
				hideEffect: Element.hide,
				width:		  630,
				minHeight:	  200
			});
		}
		Mxhr.relogonDialog.getContent().innerHTML = h;
		Mxhr.relogonDialog.showCenter(true);

		$('cmdRelogonOK').onclick = this.handleRelogon.bindAsEventListener(this);

		$("txtRelogonName").focus();
	},

	handleRelogon: function() {
		var username = $F("txtRelogonName");
		var password = $F("txtRelogonPassword");

		if (username == "") {
			$("divRelogonErrors").innerHTML = "Username is required.";
		} else if (password == "") {
			$("divRelogonErrors").innerHTML = "Password is required.";
		} else {
			var retryClosure = this.execute.bind(this);
			assert("externalSecureUrl expected", externalSecureUrl);
			var mxhr = new Mxhr({url: externalSecureUrl + 'relogon.do',
				parameters: "logonName=" + username + "&password=" + password,
				onSuccessHandler: function(value) {
					Mxhr.relogonDialog.hide();
					retryClosure();
				},
				hourglassId: 'relogonHourglass',
				disableId: 'cmdRelogonOK',
				messageId: 'divRelogonErrors',
				allowRelogon: false});

			mxhr.execute();
		}

		return false;
	}

}


var MxhrChannel = Class.create();
MxhrChannel.prototype = {
	initialize: function(url, timeWindowMsec, fSuccessHandler) {
		this.url = url;
		this.timeWindowMsec = timeWindowMsec;
		this.fSuccessHandler = fSuccessHandler;

		this.lastProcessedValue = null;
		this.queuedValue = null;
		this.isRequestPending = false;
		this.lastRequestTime = new Date();

		window.setInterval(this.onTimerEvent.bind(this), this.timeWindowMsec);
	},


	submit: function(value) {
		this.queuedValue = value;
		if (!this.isRequestPending && (new Date()).getTime() - this.lastRequestTime.getTime() > this.timeWindowMsec) {
			this.execute();
		}
	},

	onTimerEvent: function() {
		if (this.lastProcessedValue != this.queuedValue && !this.isRequestPending) {
			this.execute();
		}
	},

	execute: function() {
		this.lastProcessedValue = this.queuedValue;
		this.isRequestPending = true;
		this.lastRequestTime = new Date();

		var mxhr = new Mxhr({
			url: this.url,
			parameters: this.queuedValue,
			onSuccessHandler: this.onSuccessHandler.bind(this),
			onFailureHandler: this.onFailureHandler.bind(this),
			showSystemErrors: false
		});

		mxhr.execute();
	},

	onSuccessHandler: function(mxhrResponse) {
		this.fSuccessHandler(mxhrResponse);
		this.isRequestPending = false;
	},

	onFailureHandler: function() {
		this.isRequestPending = false;
	}
}


function getTip(userAction) {
	var mxhr = new Mxhr({
		url: 'getTip.do',
		parameters: "value=" + userAction,
		onSuccessHandler: function() {
			var div = $("divTipBox");

			//the next line has been commented out because of elementool issue11
			//            new Effect.Highlight(div);
		}
	});

	mxhr.execute();
}

function xhrShowComponent(componentName) {
	var mxhr = new Mxhr({
		url: 'showComponent.do',
		parameters: "value=" + componentName,
		onSuccessHandler: function() {
		}
	});

	mxhr.execute();
}

function xhrHideComponent(componentName) {

	var mxhr = new Mxhr({
		url: 'hideComponent.do',
		parameters: "value=" + componentName,
		onSuccessHandler: function() {
		}
	});

	mxhr.execute();
}

function hideComponent(componentName) {
    if(userTypeFreeWeb){
        return;//the message will be shown by another handler
    }

	Element.toggle(componentName);

	var chkCheckbox = $("chk" + componentName);
	if (chkCheckbox != null) {
		chkCheckbox.checked = false;
	}

	for (var i = 0; i < aPageComponents.length; i++) {
		if (aPageComponents[i].type == componentName) {
			aPageComponents[i].hidden = true;
		}
	}

	xhrHideComponent(componentName);
}

var PageComponentManager = Class.create();
PageComponentManager.prototype = {
	initialize: function() {
		this.dlgSelection = null;

		Event.observe('aComponentSelDialog', 'click', this._showComponentSelection.bindAsEventListener(this), false)
	},
	_showComponentSelection: function() {
		logDefault("in _showComponentSelection")
		if (this.dlgSelection == null) {

			var innerHtml = "<div>";
			for (var i = 0; i < aPageComponents.length; i++) {
				var oComponent = aPageComponents[i];
				innerHtml += "<input type='checkbox' id='chk" + oComponent.type + "'" + (oComponent.hidden ? ">" : " checked='checked'>");
				innerHtml += "<label for='chk" + oComponent.type + "'>" + oComponent.name + "</label><br>"
			}
			innerHtml += "</div>"

			this.dlgSelection = new Window('componentSelectionDialog', {
				title:"Select page parts",
				resizable: true, minimizable:false,maximizable:false,
				showEffect: Element.show, hideEffect: Element.hide});

			setInnerHtml(this.dlgSelection.getContent(), innerHtml);

			for (var i = 0; i < aPageComponents.length; i++) {
				var oComponent = aPageComponents[i];
				Event.observe($("chk" + oComponent.type), "click", function(event) {
                    if(userTypeFreeWeb){
                        return noFreeWebHandler(event);
                    }

					ProEventUtil.formatEvent(event);
					var componentName = event.target.id.substring(3);
					var component = $(componentName);
					if (Element.visible(component)) {
						Element.hide(component);
						xhrHideComponent(componentName);
					} else {
						Element.show(component);
						xhrShowComponent(componentName);
						new Effect.Highlight(component);
					}
				}, false);
			}

		}

		this.dlgSelection.toggleDialog($('aComponentSelDialog'));
	}
}

function enableAction(actionLink, enable) {
	var element = $(actionLink);
	var cls = element.className;
	if (!enable && cls.indexOf("Disabled") == -1) {
		element.className = cls + "Disabled";
	} else if (enable && cls.indexOf("Disabled") > -1) {
		element.className = cls.substring(0, cls.indexOf("Disabled"));
	}
}

function round(numberToRound, numberOfDigitsAfterComma) {
	var pow = Math.pow(10, numberOfDigitsAfterComma);
	return Math.round(numberToRound * pow) / pow;
}

function trimToNull(value) {
	value = trim(value);
	if (value == null || value.length == 0) {
		return null;
	}
	return value;
}

function trimToEmpty(value) {
	value = trimToNull(value);
	return value?value:'';
}

function startsWith(string, sign) {
	if (string && sign) {
		return string.indexOf(sign) == 0;
	}
	return false;
}

function trim(value) {
	if (value == null) {
		return null;
	}
	var temp = value;
	var obj = /^(\s*)([\W\w]*)(\b\s*$)/;
	if (obj.test(temp)) {
		temp = temp.replace(obj, '$2');
	}
	obj = / +/g;
	temp = temp.replace(obj, " ");

	//khtml_kbrd_fix "Non breaking space" character (ASCII 160, nbsp, \u00A0)
	temp = temp.replace(String.fromCharCode(160), '');

	if (temp == " ") {
		temp = "";
	}
	return temp;
}

//split a string using space,dashes and slashes as delimiters, do not include empty tokens in result
function splitCommonDelim(string) {
	string = trimToNull(string);
	if (!string) {
		return;
	}
	string = string.replace("/", " ").replace('\\', ' ').replace('-', ' ');
	var result = [];

	string.split(" ").each(function(part) {
		part = trimToNull(part);
		if (part) {
			result.push(part);
		}
	});
	return result;
}

function endsWith(s, sign) {
	s += '';
	var sub = s.substring(s.length - sign.length, s.length)
	return sign == sub;
}

function isEmpty(value) {
	if (value == null) {
		return true;
	} else {
		return trim(value.toString()).length == 0;
	}
}

var UserMessage = {
	get:function() {
		return "We apologize, an internal server error occured. Please, try again shortly. " +
			   "In case of persistent problems, use our support page or e-mail to support@mynetdiary.com.";
	},

	show:function(message, elementOrId) {
		if (elementOrId) {
			setInnerHtml(elementOrId, message);
		} else {
			//IMPORTANT: there should be ONLY ONE alert function call in the whole application
			alert(message);
		}
	}
}

var minderLoggingIsSupported = true;
var defaultLogger = {
	name:"defaultLogger",
	append:function(message) {
		if (productionMode) {
			return;
		}

		if (!minderLoggingIsSupported) {
			return;
		}
		message = message + "";
		try {
			//the next statment should work for both, KHTML and FireFox
		    console.log(message);
		} catch(e) {
			minderLoggingIsSupported = false;
		}
	}};
function getDefaultLogger() {
	assert("Default logger must be initialized", defaultLogger);
	return defaultLogger;
}
function logDefault(message) {
	getDefaultLogger().append(message);
}

function setDefaultLogger(logger) {
	if (!logger) {
		defaultLogger = getNullLogger();
		return;
	}
	if (logger.append == null || !typeof(logger.append) == 'function') {
		defaultLogger = getNullLogger();
		throw "logger.append is not a function, setting nullLogger will be used by default";
	}
	defaultLogger = logger;
}

function getNullLogger() {
	return {
		name:"nullLogger",
		append:function() {
		}}
}

//DomLogger is a cross-browser logging tool, unlike JavaScript console or JavaConsole ; source: Ajax In Action book
var DomLogger = Class.create();
DomLogger.prototype = {
	initialize:function(loggerElementId, loggerClearControlId, loggerVerbosityControlId) {
		this.loggerElement = $(loggerElementId);
		if (!this.loggerElement) {
			throw "Invalid input, cannot find console element by id:" + loggerElementId;
		}
		this.verbose = true;
		this.clear();

		var self = this;
		if (loggerClearControlId) {
			var loggerClearControl = $(loggerClearControlId);
			if (loggerClearControl) {
				Event.observe(loggerClearControl, 'click', function() {
					self.clear();
					return false;
				});
			} else {
				this.append("WARNING:Cannot find loggerClearControl by id:" + loggerClearControlId);
			}
		}

		if (loggerVerbosityControlId) {
			var loggerVerbosityControl = $(loggerVerbosityControlId);
			if (loggerVerbosityControl) {
				Event.observe(loggerVerbosityControlId, 'click', function() {
					self.setVerbose(!self.getVerbose());
					//check box needs this to be able to display state of the action
					return true;
				});
			} else {
				this.append("WARNING:Cannot find loggerVerbosityControl by id:" + loggerVerbosityControlId);
			}
		}
	},
	append : function(message, style) {
		if (!this.verbose) {
			return;
		}

		var messageItem = document.createElement('div');
		messageItem.appendChild(document.createTextNode(String(message)));
		if (style) {
			messageItem.className = style;
		}
		this.loggerElement.appendChild(messageItem);
	} ,
	setVerbose : function(verbose) {
		this.verbose = verbose;
	},
	getVerbose : function(verbose) {
		return this.verbose;
	},
	clear : function() {
		var e = this.loggerElement;
		while (e.firstChild) {
			e.removeChild(e.firstChild);
		}
	}
}

function getActionUrl(actionName) {
	return minderPath + actionName;
}

function observeClick(element, action) {
	element = $(element)
	Event.observe(element, 'click', function(event) {
		ProEventUtil.formatEvent(event);
		event.preventDefault();
		action.call(element, event);
	});
}

function observeClickIfExists(element, action) {
	if ($(element)) {
		observeClick(element, action);
	}
}

function observeResize(action, actionDelayForIE) {
	var resizeHandler = function(e) {

		if (ProEventUtil.isIE) {
			if (resizeHandler.resizeTimeout) {
				clearTimeout(resizeHandler.resizeTimeout);
			}
			resizeHandler.resizeTimeout = setTimeout(function() {
				action.call(window);
			}, actionDelayForIE);
		} else {
			action.call(window);
		}
	};
	Event.observe(window, 'resize', resizeHandler);
}

function setInputDescription(htmlMessage) {
	setInnerHtml('divInputDesc', htmlMessage);
}

function setTip(text) {
	var h = "";
	//we need to reset the whole structure since Save/Undo/Redo actions change h4 caption of the tip box
	h += "<h4>Tip</h4>";
	h += "<div class='containerBody'>";
	h += "<div id='divTipBody'>";
	h += text;
	h += "</div>";
	setInnerHtml('divTipBox', h);
}

function getImagePath(pathSuffix) {
	return minderPath + "img/" + pathSuffix;
}

function inspect(object) {
	if (object == null) {
		return "null";
	}
	Object.extend(object, Enumerable);
	Object.extend(object, Hash);
	return object.inspect();
}

function today() {
	var d = new Date();
	return (d.getMonth() + 1) + "/" + d.getDate() + "/" + d.getFullYear()
}

function failNotImplemented(classMethodName) {
	assert("Method '" + classMethodName + "' is not implemented", false);
}

function assert(message, statement) {
	if (!statement) {
		message = "ASSERTION FAILURE:" + message;
		fail(message);
	}
}

function fail(message) {
	getDefaultLogger().append(message);
	UserMessage.show(message, null);
}

/*
Important memory leak notes:
This function traverses DOM element and nullifies all functional references, including event listeners.
It should be called:
 1. before calling removeChild DOM method or calling Element.remove() method in prototype.js
    alternatievely, call removeFromParent() method below
 2. before setting innerHTML property
    alternatively, call setInnerHtml() method below
Using purge method is essential on IE, harmless on Mozilla and Opera.

Also, use prototype.js Event.observe() instead of onclick=function(){....} sunscription method.
This allows prototype.js to unsubscribe all DOM listeners when window is unloaded.

see also http://www.crockford.com/javascript/memory/leak.html
*/
function purge(domElement) {
	var attributes = domElement.attributes;
	if (attributes) {
		var len = attributes.length;
		for (var i = 0; i < len; i += 1) {
			var attributeName = attributes[i].name;
			if (typeof domElement[attributeName] === 'function') {
				domElement[attributeName] = null;
			}
		}
	}
	if (attributes) {
		var len = domElement.childNodes.length;
		for (var i = 0; i < len; i += 1) {
			purge(domElement.childNodes[i]);
		}
	}
}

//see "memory leak notes" above
function removeFromParent(element) {
	element = $(element);
	purge(element);
	if (element.parentNode) {
		element.parentNode.removeChild(element);
	}
}
//see "memory leak notes" above
function setInnerHtml(element, innerHtml) {
	element = $(element);
	purge(element);
	element.innerHTML = innerHtml;
}

//Null-safe, comma insensitive parsing
function parseFloatFromString(string) {
	if (string == null) {
		return null;
	}
	//account for using comma "," as fraction delimiter
	return parseFloat(string.replace(",", "."))
}


/*
Event handling guidelines,
needed for preventing IE memory leaks and enforcing coding consistency
1. Use prototype.js Event.observe instead of 'element.on<eventType>' subscription method
2. Use prototype.js bindAsEventListener while calling Event.observe method
3. While working with event object use ProEventUtil.formatEvent method
4. Use diary.js purge() method while dynamically removing DOM elemens with event listeners or setting innerHTML
5. Use EventTracer for non-intrusive logging of event flows
6. Rely only on cross-browser events and techniques described at "Event compatibility tables"
   http://www.quirksmode.org/js/events_compinfo.html
*/

var ProEventUtil = new Object;
//source: Professional JavaScript for Web Developers, Nicholas C. Zakas
ProEventUtil.formatEvent = function (e) {
	if (ProDetect.isIE && ProDetect.isWin) {
		e.charCode = (e.type == "keypress") ? e.keyCode : 0;
		e.eventPhase = 2;
		e.isChar = (e.charCode > 0);
		e.pageX = e.clientX + document.body.scrollLeft;
		e.pageY = e.clientY + document.body.scrollTop;
		e.preventDefault = function () {
			this.returnValue = false;
		};

		if (e.type == "mouseout") {
			e.relatedTarget = e.toElement;
		} else if (e.type == "mouseover") {
			e.relatedTarget = e.fromElement;
		}

		e.stopPropagation = function () {
			this.cancelBubble = true;
		};

		e.target = e.srcElement;
		e.time = (new Date).getTime();
	}
	return e;
};

ProEventUtil.formatAndStop = function(e) {
	ProEventUtil.formatEvent(e);
	e.preventDefault();
	e.stopPropagation();
};

ProEventUtil.getModifiers = function(event) {
	var result = [];
	if (event.shiftKey && event.shiftKey == true) {
		result.push("shift");
	}
	if (event.altKey && event.altKey == true) {
		result.push("alt");
	}
	if (event.ctrlKey && event.ctrlKey == true) {
		result.push("ctrl");
	}
	return result;
};

function checkBrowserCompatibility() {
	var compatible = !ProDetect.isOpera;
	if (!compatible) {
		alert('We apologize for inconveniences but MyNetDiary is not fully tested with your browser type yet.' +
			  '\nWe recommend using Firefox browser downloadable from getfirefox.com');
	}
	return compatible;
}


var EventTracer = Class.create();
//global event count for logging
EventTracer.eventCount = 0;
EventTracer.EDIT_EVENTS = 'keyup,keypress,blur,focus,change,click';
EventTracer.ALL_EVENTS = EventTracer.EDIT_EVENTS + 'keydown,dblclick,select,mousemove,mousedown,mouseup,mouseover,mouseout';
EventTracer.EXTREMELY_VERBOSE = false;
//FireFox echo Note:
//observing 'keydown' on text input or its parent results in all chars duplicated
EventTracer.prototype = {
	initialize:function(element, tracedEvents, optionalLogger) {
		this.element = $(element);
		assert("Element or id expected, obtained " + this.element, this.element);
		this.logger = optionalLogger ? optionalLogger : getDefaultLogger();
		this.startTracing(tracedEvents)
	},

	getIdForLog:function(element) {
		assert("Element expected", element);
		var result;
		if (this.element.id) {
			result = this.element.id;
		} else {
			if (element.payloadId == null || element.payloadId == '') {
				if (element.tagName || element.tagName == '') {
					result = "tag_" + element.tagName;
					if (element.className != null && element.className != '') {
						result += "_cls_" + element.className.replace(" ", "_");
					}
				} else {
					result = 'typeof_' + typeof(element);
				}
			} else {
				result = "id_" + element.payloadId;
			}
		}
		return result;
	},

	createMessage:function(event) {
		var msg = "e#" + EventTracer.eventCount + ":" ;
		//assume that event has not been formatted yet
		msg += this.getIdForLog(event.target ? event.target : event.srcElement);
		msg += ".on" + event.type;
		return msg;
	},

	trace:function(event) {
		EventTracer.eventCount++;

		var msg = this.createMessage(event);
		if (EventTracer.EXTREMELY_VERBOSE) {
			msg += " " + inspect(event);
			this.logger.append(msg);
			return;
		}

		event = ProEventUtil.formatEvent(event);

		var modifiers = ProEventUtil.getModifiers(event);
		if (modifiers.length > 0) {
			msg += " mod=";
			for (var i = 0; i < modifiers.length; i++) {
				msg += modifiers[i];
				if (i < modifiers.length - 1) {
					msg += ",";
				}
			}
		}
		if (event.button) {
			var button = event.button;
			if (button == 65535) {
				button = "n/a";
			}
			msg += " button=" + button;
		}
		if (event.detail) {
			msg += " detail=" + event.detail;
			//number of times mouse was clicked
		}
		if (event.keyCode) {
			msg += " key=" + event.keyCode;
			//see www.asciitable.com
		}
		if (event.charCode) {
			msg += " char=" + String.fromCharCode(event.charCode);
		}
		if (event.pageX) {
			msg += " pageX=" + event.pageX + " Y=" + event.pageY;
		}
		if (event.relatedTarget) {
			msg += " relatedTarget=" + this.getIdForLog(event.relatedTarget);
		}

		this.logger.append(msg);
	},

	startTracing:function(tracedEvents) {
		if (!tracedEvents) {
			tracedEvents = EventTracer.ALL_EVENTS;
		}
		this.logger.append("e#EventTracer attached to " + this.getIdForLog(this.element) +
						   " element for the following events:" + tracedEvents);

		if (typeof(tracedEvents) == 'string') {
			this.tracedEvents = tracedEvents.split(',');
		}
		var self = this;
		this.tracedEvents.each(function(eventType) {
			Event.observe(self.element, eventType, self.trace.bindAsEventListener(self));
		});
	}
}


var PositionFixer = Class.create();
PositionFixer.prototype = {
	initialize:function(elt, fixedY) {
		elt.style.position = "relative";
		this.elt = elt;
		this.fixedY = Position.cumulativeOffset(elt)[1];
		//		this.fixedY = fixedY;

		this.currentY = Position.cumulativeOffset(elt)[1];
		this.targetY = fixedY;
		this.periodicalExecuter = null;
		this._timerCallback = this.timerCallback.bind(this);

		Event.observe(document.documentElement, "scroll", this.eventHandler.bind(this), false);
		//comment out in the future as this won't be called for DOM-compliant which will use position: fixed
		Event.observe(window, "scroll", this.eventHandler.bind(this), false);
	},

//calculates target position and sets timer
	eventHandler: function(eventName, win) {
		var pos = document.documentElement.scrollTop;

		this.targetY = pos < this.fixedY ? 0 : pos - this.fixedY + 10;
		// < this.fixedY ? this.fixedY - pos : pos - this.fixedY;

		this._timerCallback();

		if (this.periodicalExecuter == null) {
			this.periodicalExecuter = new PeriodicalExecuter(this._timerCallback, 0.05);
		}
	},

//moves elt further to the target position, if close enough cancels timer
	timerCallback: function() {

		var change = parseInt(0.2 * (this.targetY - this.currentY));
		if (change > -1 && change < 0) {
			change = -1;
		} else if (change < 1 && change > 0) {
			change = 1;
		}
		//        logDefault("timer event, change=" + change);

		if (change != 0) {
			this.currentY += change;
			this.elt.style.top = this.currentY + "px";
		} else if (this.periodicalExecuter != null) {
			this.periodicalExecuter.stop();
			this.periodicalExecuter = null;
		}
	}
}

function fixSidebar() {
	if (ProDetect.isIEnot7) {
		new PositionFixer($('divSidebarContainer'), 30);
	}
}

function naInTestDrive(e) {
	e = e || window.event;
	alert("This functionality is not available during Test Drive.");
	ProEventUtil.formatAndStop(e);
}

var TipSetter = Class.create();
TipSetter.prototype = {
	initialize:function() {
	},
	setTipOnce:function(text) {
		if (!this.tipIsSet) {
			setTip(text);
			this.tipIsSet = true;
		}
	}

}

function attachOneTimeVideoLinkHandler(linkId) {

	var elt = $(linkId);

	Event.observe(elt, 'click', function(e) {

		if (eval("typeof " + linkId + "Flag == 'undefined'")) {

			eval(linkId + "Flag = true;");

			Element.removeClassName(elt, "video");
			Element.addClassName(elt, "videoShown");

			saveAsShownToServer(linkId);
		}

	}, false);

}

function saveAsShownToServer(instructionId) {
	var mxhr = new Mxhr({
		url: 'markAsShown.do',
		parameters: "value=" + instructionId,
		onSuccessHandler: function() {
		}
	});

	mxhr.execute();
}

function enforceReadOnlyUser() {
	if (userIsReadOnly) {
		alert("Sorry, but we cannot save your input since payment is due.");
	}
	return userIsReadOnly;
}

function hideNotification(isGlobal, id) {

	Element.toggle("divNotification_" + id);

	var sUrl = isGlobal ? "hideGlobalNotification.do" : "hideUserNotification.do";

	var mxhr = new Mxhr({
		url: sUrl,
		parameters: "value=" + id,
		onSuccessHandler: function() {
		}
	});

	mxhr.execute();

}

var GRAM_INPUT_REGEXP = "^( )*(\\d)+( )*((g)|(oz))( )*$";
//Check if input is in grams or oz
function extractGramInput(text) {
	var mathes = text.match(GRAM_INPUT_REGEXP, "i");
	if (mathes && mathes.length > 0) {
		return mathes[0];
	} else {
		return null;
	}
}

function minderLightbox(aLightBoxes, eltHotspot, imageUrl, title, width, height, doHighlight, targetUrl) {

	var elt = $(eltHotspot);
	var wndId = "wnd" + eltHotspot;

	Element.setStyle(elt, {cursor: 'pointer'});

	if (doHighlight) {
		Event.observe(elt, 'mouseover', function(e) {
			elt.addClassName("current");
		}, false);

		Event.observe(elt, 'mouseout', function(e) {
			elt.removeClassName("current");
		}, false);
	}


	aLightBoxes.push(wndId);

	Event.observe(elt, 'click', function(e) {

		for (var i = 0; i < aLightBoxes.length; i++) {
			var wnd = Windows.getWindow(aLightBoxes[i]);
			if ($(wnd) && wnd.isVisible()) {
				wnd.hide();
			}
		}

		if ($(wndId)) {
			var wnd = Windows.getWindow(wndId);
			wnd.show();
		} else {

			var offset = Position.cumulativeOffset(elt);
			var top = height < 700 ? offset[1] - 40 : offset[1] - 150;

			var wnd = new Window(wndId, {
				title: title,
				resizable: false,
				minimizable:false,
				maximizable:false,
				closable:true,
				draggable:true,
				showEffect: Element.show,
				hideEffect: Element.hide,
				width:		  width + 14,
				minHeight:	  height + 31,
				right: 5,
				top: top
			});

			if (targetUrl != null) {
				wnd.getContent().innerHTML = "<a style='background: url(" + imageUrl + "); display:block;width: + " + width + "px;height: " + height + "px;margin:3px 5px;' title='Learn More' href='" + targetUrl + "'> </a>";
			} else {
				wnd.getContent().innerHTML = "<img src='" + imageUrl + "' alt='Screenshot' style='margin:3px 5px;' />";
			}
			wnd.show();

		}


	}, false);

}


function minderLightboxImage(aLightBoxes, eltHotspot, imageUrl, highlightSrc, title, width, height) {

	var elt = $(eltHotspot);
	var wndId = "wnd" + eltHotspot;
	var normalSrc = elt.src;

	Element.setStyle(elt, {cursor: 'pointer'});

	Event.observe(elt, 'mouseover', function(e) {
		$(elt).src = highlightSrc;
	}, false);

	Event.observe(elt, 'mouseout', function(e) {
		$(elt).src = normalSrc;
	}, false);

	aLightBoxes.push(wndId);

	Event.observe(elt, 'click', function(e) {

		for (var i = 0; i < aLightBoxes.length; i++) {
			var wnd = Windows.getWindow(aLightBoxes[i]);
			if ($(wnd) && wnd.isVisible()) {
				wnd.hide();
			}
		}

		if ($(wndId)) {
			var wnd = Windows.getWindow(wndId);
			wnd.show();
		} else {

			var offset = Position.cumulativeOffset(elt);
			var top = offset[1] - 200;

			var wnd = new Window(wndId, {
				title: title,
				resizable: false,
				minimizable:false,
				maximizable:false,
				closable:true,
				draggable:true,
				showEffect: Element.show,
				hideEffect: Element.hide,
				width:		  width + 4,
				minHeight:	  height + 25,
				right: 5,
				top: top
			});

			wnd.getContent().innerHTML = "<img src='" + imageUrl + "' alt='lighbox'/>";
			wnd.show();

		}

	}, false);

}

function linkPopup(wndId, anchor, sContents, title, width, height, verticalOffset) {

	anchor = $(anchor);

	Event.observe(anchor, 'click', function(e) {

		var wnd = Windows.getWindow(wndId);
		if (wnd != null) {
			wnd.show();
		} else {

			var offset = Position.cumulativeOffset(anchor);
			var top = verticalOffset < 0 ? offset[1] - height + verticalOffset : offset[1] + verticalOffset;
			var left = offset[0] - 20;

			wnd = new Window(wndId, {
				title: title,
				resizable: true,
				minimizable:false,
				maximizable:false,
				closable:true,
				draggable:true,
				showEffect: Element.show,
				hideEffect: Element.hide,
				width:		  width,
				minHeight:	  height,
				left: left,
				top: top
			});

			wnd.getContent().innerHTML = sContents;
			wnd.show();

		}

		ProEventUtil.formatAndStop(e);

	}, false);

}

function whyPayPopup(anchor, verticalOffset) {
	linkPopup('wndWhyPay',
			anchor,
			"<div id='whyPayPopup' class='windowContents'>" +
            "<ul><img src='img/noads.png' alt='No ads on MyNetDiary'/>" +
            "<li class='narrow'><strong>MyNetDiary is easier and faster.</strong> Free sites are designed to have more steps and show more pages, even for simple tasks - to show more ads. MyNetDiary is designed to make things easier for you and take the minimum of your time - most actions are done in-place, on the same page.</li> <li class='narrow'><strong>Large, accurate, and up-to-date food database.</strong> Free sites often rely on users to enter foods, resulting in duplicates, errors, and outdated food information.</li> <li><strong>No intrusive and distracting ads</strong> blinking on top, side, and bottom of your diary, trying to sell you a new cell phone or a \"miracle\" weight loss pill.</li> <li><strong>Real, knowledgeable, and friendly support</strong> people to answer your e-mails quickly and to help you.</li> <li><strong>State-of-the-art technology</strong>, light-years ahead of free sites (and ahead of all paid sites, too).</li> <li>Did we say <strong>\"no ads and no spam\"</strong>?</li>" +
            "</ul>" +
            "<p>There are no free sites, you always pay - if not with money, then with your time, convenience, and the risk of giving up your diet overwhelmed by an awkward \"free\" diary.</p>" +
            "</div>",
			"Why pay when some sites are free?",
			700,
			510,
			verticalOffset);
}

function entryDemoPopup(anchor, verticalOffset) {
    if(!$(anchor)){
        return;
    }
    linkPopup('wndEntryDemo',
            anchor,
            "<div id='entryDemoPopup' class='windowContents'>" +
            "  <iframe src='"+externalUrl+"/entryDemo.do' frameborder='0' width='100%' height='430px'></iframe>"+
            "</div>",
            "Food entry demo",
            830,
            460,
            verticalOffset);
}

function overviewVideoPopup(anchor, verticalOffset) {
    linkPopup('wndOverviewVideo',
            anchor,
            '<div>' +  
				'<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,28,0" width="477" height="360">' +
					 '<param name="movie" value="Why2.swf" />' +
					 '<param name="quality" value="high" />' +
					 '<param name="wmode" value="transparent" />' +
					 '<param name="menu" value="false" />' +
				     '<embed src="Why2.swf" quality="high" pluginspage="http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash" type="application/x-shockwave-flash" width="477" height="360" wmode="transparent" menu="false"></embed>' +
				     '</object>' +
            '</div>'
            ,
            "Click arrow below to watch video",
            477,
            360,
            verticalOffset);
}


/* Modal html dialogs are implemented by windows.js and replace hard-to-test alert() and confirm() browser methods*/
function modalAlert(content, okHandler) {
	var params = Object.extend({}, modalDialogParameters);
	params.ok = function() {
		if (okHandler) {
			okHandler();
		}
		;
		return true;
	};
	Dialog.alert(content, params);
}

function modalConfirm(content, okHandler, cancelHandler) {
	var params = Object.extend({}, modalDialogParameters);
	params.ok = function() {
		if (okHandler) {
			okHandler();
		}
		return true;
	};
	params.cancel = cancelHandler;
	Dialog.confirm(content, params);
}

var modalDialogParameters = {
	windowParameters: {
		width:300,
		effectOptions:{
			duration:0
		}
	},
	buttonClass: "modalButtonClass",
	id: "modalDialogId",
	cancel:function(win) {
	},
	ok:function(win) {
		//return flag telling in order to close dialog
		return true;
	}
};


function focusAndSelect(textElement) {
	textElement = $(textElement);
	if (textElement) {
		setTimeout(function() {
			Form.Element.focus(textElement);
			textElement.select();
		});
	}
}
function focus(textElement) {
	textElement = $(textElement);
	if (textElement) {
		setTimeout(function() {
			Form.Element.focus(textElement);
		});
	}
}


function showInlineConfirmation(element, message) {
	element = $(element);
	if (element) {
		setInnerHtml(element, message);
		Element.show(element);
		setTimeout(function() {
			setInnerHtml(element, '');
			Element.hide(element);
		}, 5000);
	}
}

function printFunction(url) {
    if(userTypeFreeWeb){
        return;//another handler will display the message
    }
	window.open(url,
			'', 'scrollbars=yes,menubar=no,height=600,width=900,' +
				'resizable=yes,toolbar=no,location=no,status=no');
}

function trackReferrer() {
    var name="MyNetDiary_refr";
    var cmpName="MyNetDiary_cmp";
    var ref = document.referrer;
    var ind =  document.location.href.indexOf('=');
    var cmp = ind > 0 ? document.location.href.substring(ind + 1) : '';

    if (ref == '' && cmp == '') return;

    var ca = document.cookie.split(';');
	for(var i=0; i < ca.length; i++) {
	  var c = ca[i];
	  while (c.charAt(0)==' ')
          c = c.substring(1, c.length);

	  if (c.indexOf(name) == 0)
        ref = '';
      if (c.indexOf(cmpName) == 0)
        cmp = '';
	}

    var expires = new Date((new Date()).getTime() + 7776000000);
    if (ref != '')
        document.cookie = name + '=' + encodeURIComponent(ref) + '; expires=' + expires.toGMTString() + '; path=/; domain=mynetdiary.com';
    if (cmp != '')
        document.cookie = cmpName + '=' + encodeURIComponent(cmp) + '; expires=' + expires.toGMTString() + '; path=/; domain=mynetdiary.com';
}

var noFreeWebDlg;

function noFreeWebHandler(event) {
    ProEventUtil.formatEvent(event);
    var title = "Feature is not available for FreeWeb members";
    try {
        if (!noFreeWebDlg) {
            noFreeWebDlg = new MWindow('idNoFreeWebDialog', {
                title:title,
                width:600, height:450,
                resizable: true, minimizable:false,maximizable:true,
                showEffect: Element.show, hideEffect: Element.hide});
            noFreeWebDlg.getContent().innerHTML = "<div id='divNoWebFreeDlgContent' style='padding:10px,10px,10px,10px'>" + title + "</div>";
            new Mxhr({ url: 'accountTypesContent.do' }).execute();
        }
        noFreeWebDlg.show(false, true);
        noFreeWebDlg.positionUnder(event.target);
    } catch(e) {
        alert(title);
    }

    event.preventDefault();
    event.stopPropagation();
    return true;//should mean that event is processed
}

//called from layout_standard.st
function observeNoFreeWeb() {
    document.getElementsByClassName('noFreeWeb').each(function(link) {
        observeClick(link, noFreeWebHandler);
    });
}
/*
* generic form utilities
*/

function disable(elt) {
    elt.disabled = "disabled";
    Element.addClassName(elt, "disabled");
}

function enable(elt) {
    elt.disabled = "";
    Element.removeClassName(elt, "disabled");
}

function setEnabled(elt, flag) {
    if(flag) enable(elt); else disable(elt);
}

function disableOnFormSubmit(elt) {
    Event.observe(document.forms[0], "submit", function(e) {
        disable($(elt));
    }, false);
}

var InputValidator = Class.create();
InputValidator.prototype = {
    initialize: function(eltInput, fnCustomValidator, bRequired, rRegex) {
        this.eltInput = eltInput;
        this.fnCustomValidator = fnCustomValidator != null ? fnCustomValidator : null;
        this.bRequired = bRequired;
        this.rRegex = arguments.length == 4 ? rRegex : null;


        var sBaseName = eltInput.id.substr(3);
        this.eltError = $("err" + sBaseName);
        this.eltLabel = $("lbl" + sBaseName);
        this.sLabel = (this.eltLabel == null) ? null : this.eltLabel.innerHTML;
        if (this.sLabel != null && this.sLabel.charAt(this.sLabel.length - 1) == ":") {
            this.sLabel = this.sLabel.substr(0, this.sLabel.length - 2)
        }
    },

    validate: function(bEvent) {

        if (this.fnCustomValidator != null) {
            return this.fnCustomValidator(bEvent);
        } else {
            var sValue = Form.Element.getValue(this.eltInput);
            if (this.bRequired && sValue == "") {
                return this.sLabel == null ? "required" : this.sLabel + " is required";
            } else if (this.rRegex != null && !(!this.bRequired && sValue == "") && !this.rRegex.test(sValue)) {
                return this.sLabel == null ? "invalid" : this.sLabel + " is invalid";
            }

            return "";
        }
    },

    showErr: function(msg) {
        if (this.eltError != null) {
            this.eltError.innerHTML = msg;
            //                console.log(this.sLabel + " " + this.eltError.innerHTML);
        }

        if (this.eltLabel != null) {
            if (msg != "") {
                Element.addClassName(this.eltLabel, "error");
            } else {
                Element.removeClassName(this.eltLabel, "error");
            }
        }

    }
}

var GroupValidator = Class.create();
GroupValidator.prototype = {
    initialize: function(aElements, fnCustomValidator) {
        this.lastMsg = "";
        this.fnCustomValidator = fnCustomValidator;
        this.aEltLblPair = [];

        var elt;
        var eltLabel;
        for (var i = 0; i < aElements.length; i++) {
            elt = $(aElements[i]);
            var sBaseName = elt.id.substr(3);
            eltLabel = $("lbl" + sBaseName);
            if (!eltLabel) {
                eltLabel = null;
            }
            this.aEltLblPair.push({"elt": elt, "lbl": eltLabel})
        }
    },

    hasValidationObserver: function(eltInput) {
        for (var i = 0; i < this.aEltLblPair.length; i++) {
            if (this.aEltLblPair[i].elt == eltInput) {
                return true;
            }
        }
        return false;
    },

//checks group validation if all individual controls are valid
    validateGroupAfterInput: function(formValidator, eltInput, bEvent) {
        this.lastMsg = "";
        for (var i = 0; i < this.aEltLblPair.length; i++) {
            if (this.aEltLblPair[i].elt == eltInput) {
                if (this._otherElementsCorrect(formValidator, eltInput)) {
                    this.lastMsg = this.fnCustomValidator();
                    break;
                }
            }
        }
        return this.lastMsg;
    },

//could be called when all individual elements are valid
    hasErrors: function(formValidator) {
        this.lastMsg = this.fnCustomValidator();
        return this.lastMsg != "";
    },

    showErr: function(eltGlobalErr) {
        eltGlobalErr.innerHTML = this.lastMsg;
        for (var i = 0; i < this.aEltLblPair.length; i++) {
            if (this.aEltLblPair[i].lbl != null) {
                if (this.lastMsg != "") {
                    Element.addClassName(this.aEltLblPair[i].lbl, "error");
                } else {
                    Element.removeClassName(this.aEltLblPair[i].lbl, "error");
                }
            }
        }
    },

    _otherElementsCorrect: function(formValidator, eltExcluded) {
        var validationResult = null;
        for (var i = 0; i < this.aEltLblPair.length; i++) {
            if (this.aEltLblPair[i].elt != eltExcluded) {
                validationResult = formValidator.validateElementOnly(this.aEltLblPair[i].elt, false);
                if (validationResult != null && validationResult.msg != "") {
                    return false;
                }
            }
        }
        return true;
    }
}

var ValidationResult = Class.create();
ValidationResult.prototype = {
    initialize: function(eltInput, msg, validator, groupValidator) {
        this.eltInput = eltInput;
        this.msg = msg;
        this.validator = validator;
        this.groupValidator = groupValidator;
    }
}

var FormValidator = Class.create();
FormValidator.prototype = {
    initialize: function(eltForm, eltGlobalErr, eltSubmit) {
        this.eltGlobalErr = eltGlobalErr;
        this.eltSubmit = eltSubmit;
        this.eltLastKeyDown = null;
        this.oLastValidationResult = null;
        this.hasGlobalErr = eltGlobalErr != null && eltGlobalErr.innerHTML != "";
        this.controlValidators = new Array();
        this.groupValidators = new Array();

        this.keydownListener = this._keyDown.bindAsEventListener(this);
        this.keyupListener = this._keyUp.bindAsEventListener(this);
        this.blurListener = this._blur.bindAsEventListener(this);
        this.focusListener = this._focus.bindAsEventListener(this);
        this.clickListener = this._click.bindAsEventListener(this);

        this.submitInProgress = false;
        Event.observe(eltForm, "submit", this._submit.bindAsEventListener(this), false);
    },

    _keyDown: function(event) {
        this.eltLastKeyDown = Event.element(event);
    },

    _keyUp: function(event) {
        if (this.eltLastKeyDown == Event.element(event)) {
            var validationResult = this._validateElementAndGroup(this.eltLastKeyDown, true);
            this.showErr(validationResult);
        }
    },

    _blur: function(event) {
        this.oLastValidationResult = this._validateElementAndGroup(Event.element(event), true);
    },

    _focus: function(event) {
        this.showLastErr();
    },

    _click: function(event) {
        var validationResult = this._validateElementAndGroup(Event.element(event), true);
        this.showErr(validationResult);
    },

    _submit: function() {
        this.submitInProgress = true;
    },

//for single elements
    attachStandardValidation: function(eltInput, bRequired, rRegex) {
        var oInputValidator = arguments.length == 2 ? new InputValidator(eltInput, null, bRequired) : new InputValidator(eltInput, null, bRequired, rRegex);
        this._attachEventObservers(oInputValidator.eltInput);
        this.controlValidators.push(oInputValidator);
    },

    attachRequired: function(aRequiredElements) {
        if (aRequiredElements.push) {
            for (var i = 0; i < aRequiredElements.length; i++) {
                this.attachStandardValidation($(aRequiredElements[i]), true);
            }
        } else {
            for (var i = 0; i < arguments.length; i++) {
                this.attachStandardValidation($(arguments[i]), true);
            }
        }
    },

    attachNumeric: function(aNumericElements) {
        if (aNumericElements.push) {
            for (var i = 0; i < aNumericElements.length; i++) {
                this.attachStandardValidation($(aNumericElements[i]), false, StdRegex.NUMBER);
            }
        } else {
            for (var i = 0; i < arguments.length; i++) {
                this.attachStandardValidation($(arguments[i]), false, StdRegex.NUMBER)
            }
        }
    },

    attachRequiredNumeric: function(aNumericElements) {
        if (aNumericElements.push) {
            for (var i = 0; i < aNumericElements.length; i++) {
                this.attachStandardValidation($(aNumericElements[i]), true, StdRegex.NUMBER);
            }
        } else {
            for (var i = 0; i < arguments.length; i++) {
                this.attachStandardValidation($(arguments[i]), true, StdRegex.NUMBER)
            }
        }
    },

    attachValidation: function(eltInput, fnValidator) {
        var oInputValidator = new InputValidator(eltInput, fnValidator);
        this._attachEventObservers(oInputValidator.eltInput);
        this.controlValidators.push(oInputValidator);
    },

    attachGroupValidation: function(aElements, fnValidator) {
        var oGroupValidator = new GroupValidator(aElements, fnValidator);
        for (var i = 0; i < aElements.length; i++) {
            this._attachEventObservers(aElements[i]);
        }
        this.groupValidators.push(oGroupValidator);
    },

    _attachEventObservers: function(eltInput) {
        if (!this._hasValidationObserver(eltInput)) {
            Event.observe(eltInput, "keydown", this.keydownListener, false);
            Event.observe(eltInput, "keyup", this.keyupListener, false);
            Event.observe(eltInput, "blur", this.blurListener, false);
            Event.observe(eltInput, "focus", this.focusListener, false);

            if (eltInput.type == "checkbox") {
                Event.observe(eltInput, "click", this.clickListener, false);
            }

            if (eltInput.type.indexOf("select") >= 0) {
                Event.observe(eltInput, "change", this.clickListener, false);
            }
        }
    },

    _hasValidationObserver: function(eltInput) {
        for (var i = 0; i < this.controlValidators.length; i++) {
            if (this.controlValidators[i] == eltInput) {
                return true;
            }
        }
        for (var i = 0; i < this.groupValidators.length; i++) {
            if (this.groupValidators[i].hasValidationObserver(eltInput)) {
                return true;
            }
        }
        return false;
    },

    attachNonValidating: function(elements) {
        if (typeof elements == 'array') {
            for (var i = 0; i < elements.length; i++) {
                var elt = elements[i];
                Event.observe(elt, "blur", this._blur.bindAsEventListener(this), false);
                Event.observe(elt, "focus", this._focus.bindAsEventListener(this), false);
            }
        } else {
            for (var i = 0; i < arguments.length; i++) {
                var elt = arguments[i];
                Event.observe(elt, "blur", this._blur.bindAsEventListener(this), false);
                Event.observe(elt, "focus", this._focus.bindAsEventListener(this), false);
            }
        }

    },

    _validateInput: function(elements) {
        var hasErrors = false;
        if (typeof elements == 'array') {
            for (var i = 0; i < elements.length; i++) {
                if (this.elementHasErrors(elements[i])) {
                    hasErrors = true;
                    break;
                }
            }
        } else {
            for (var i = 0; i < arguments.length; i++) {
                if (this.elementHasErrors(arguments[i])) {
                    hasErrors = true;
                    break;
                }
            }
        }

    },

    elementHasErrors: function(element) {
        var oInputValidator = this.findInputValidator(element);
        return oInputValidator != null && oInputValidator.validate(false) != "";
    },

    showErr: function(validationResult) {

        var eltInput = null;
        var oInputValidator = null;
        var oGroupValidator = null;

        if (validationResult != null) {
            eltInput = validationResult.eltInput;
            oInputValidator = validationResult.validator;
            oGroupValidator = validationResult.groupValidator;
        }

        var msg = (eltInput != null && validationResult.msg != null) ? validationResult.msg : "";

        if (oInputValidator != null) {
            oInputValidator.showErr(msg);
        }

        if (oGroupValidator != null) {
            logDefault("calling group validator, msg=" + oGroupValidator.lastMsg);
            oGroupValidator.showErr(this.eltGlobalErr);
        }

        if (this.hasGlobalErr || this.eltSubmit != null) {
            var hasErrors = this.hasErrors();

            if (this.hasGlobalErr && !hasErrors) {
                this.eltGlobalErr.innerHTML = "";
                this.hasGlobalErr = false;
            }

            if (this.eltSubmit != null && !this.submitInProgress) {
                if (this.eltSubmit.length) {
                    for (var i = 0; i < this.eltSubmit.length; i++) {
                        setEnabled(this.eltSubmit[i], !hasErrors);
                    }
                } else {
                    setEnabled(this.eltSubmit, !hasErrors);
                }
            }

        }
    },

//to be used from custom validation functions updating other controls
    showEltErr: function(eltInput, msg) {
        var oControlValidator = this.findInputValidator(eltInput);
        if (oControlValidator != null) {
            var validationResult = new ValidationResult(eltInput, msg, oControlValidator, null);
            this.showErr(validationResult);
        }
    },

    showLastErr: function() {
        this.showErr(this.oLastValidationResult);
    },

    hasErrors: function() {
        for (var i = 0; i < this.controlValidators.length; i++) {
            if (this.controlValidators[i].validate(false) != "") {
                return true;
            }
        }

        for (var i = 0; i < this.groupValidators.length; i++) {
            var validator = this.groupValidators[i];
            if (validator.hasErrors(this)) {
                return true;
            }
        }

        return false;
    },

    refreshErrors: function() {
        var bHasErrors = false;
        for (var i = 0; i < this.controlValidators.length; i++) {
            var sError = this.controlValidators[i].validate(true);
            this.controlValidators[i].showErr(sError);
            if (sError != "") {
                bHasErrors = true;
            }

        }

        for (var i = 0; i < this.groupValidators.length; i++) {
            var validator = this.groupValidators[i];
            if (validator.hasErrors(this)) {
                bHasErrors = true;
                validator.showErr(this.eltGlobalErr);
            }
        }

        if (!bHasErrors) {
            this.oLastValidationResult = null;
            this.hasGlobalErr = false;
            if (this.eltSubmit.length) {
                for (var i = 0; i < this.eltSubmit.length; i++) {
                    enable(this.eltSubmit[i]);
                }
            } else {
                enable(this.eltSubmit);
            }

            if (this.eltGlobalErr) {
                this.eltGlobalErr.innerHTML = "";
            }
        }
    },

    findInputValidator: function(eltInput) {
        for (var i = 0; i < this.controlValidators.length; i++) {
            if (this.controlValidators[i].eltInput == eltInput) {
                return this.controlValidators[i];
            }
        }
        return null;
    },

    validateElementOnly: function(eltInput, bEvent) {
        var validationResult = null;
        for (var i = 0; i < this.controlValidators.length; i++) {
            var validator = this.controlValidators[i];
            if (validator.eltInput == eltInput) {
                var msg = validator.validate(bEvent);
                validationResult = new ValidationResult(eltInput, msg, validator, null);
                if (msg != "") {
                    break;
                }
            }
        }
        return validationResult;
    },

    _validateElementAndGroup: function(eltInput, bEvent) {
        var validationResult = this.validateElementOnly(eltInput, bEvent);

        if (validationResult == null || validationResult.msg == "") {
            for (var i = 0; i < this.groupValidators.length; i++) {
                var validator = this.groupValidators[i];
                var msg = validator.validateGroupAfterInput(this, eltInput, bEvent);
                validationResult = validationResult == null ?
                                   new ValidationResult(eltInput, "", null, validator) :
                                   new ValidationResult(eltInput, validationResult.msg, validationResult.validator, validator);
                break;
            }
        }

        return validationResult;
    }

};

var FormSubmitter = Class.create();
FormSubmitter.prototype = {
    initialize: function(submitUrl, fnAttachValidators, fnExtraSubmitSuccessHandler) {
        this.submitUrl = submitUrl;
        this.fnAttachValidators = fnAttachValidators;
        this.fnExtraSubmitSuccessHandler = fnExtraSubmitSuccessHandler;

        this.formState = Form.serialize($('frmCurrentForm'));
        this._successHandler = this._mxhrSuccessHandler.bind(this);
        this._failureHandler = this._mxhrFailureHandler.bind(this);
        this._okObserver = this._okHandler.bindAsEventListener(this);
        this.eltCancel = $('cmdCancel');
        this.sMessageId = $('divSystemError') == null ? null : 'divSystemError';

        Event.observe($('frmCurrentForm'), "submit", this._okObserver, false);
        this._attachObservers();

        this.feedbackProvider = new FeedbackProvider('formFeedbackContainer');

        Form.focusFirstElement($('frmCurrentForm'));

    },

    _attachObservers: function() {

        if (this.eltCancel != null) {
            Event.observe(this.eltCancel, "click", this._cancelHandler.bindAsEventListener(this), false);
        }

        if (this.fnAttachValidators) {
            this.formValidator = this.fnAttachValidators();
        }
    },

    _okHandler: function(e) {
        disable($('cmdOK'));
        if (this.eltCancel != null) {
            disable($('cmdCancel'));
        }

        ProEventUtil.formatAndStop(e);


        var mxhr = new Mxhr({
            url: this.submitUrl,
            parameters: Form.serialize($('frmCurrentForm')),
            onSuccessHandler: this._successHandler,
            onFailureHandler: this._failureHandler,
            hourglassId: 'imgCurrentFormHourglass',
            messageId: this.sMessageId});

        mxhr.execute();
    },

    _cancelHandler: function(e) {
        this._deserializeForm();

        if (this.formValidator) {
            this.formValidator.refreshErrors();
        }

        ProEventUtil.formatAndStop(e);
    },

    _mxhrSuccessHandler: function(mxhrResponse) {
        this.formState = Form.serialize($('frmCurrentForm'));
        this._attachObservers();

        var sMessage = mxhrResponse.getPayloadData("feedbackString");
        if (sMessage == null) {
            sMessage = "Changes saved.";
        }

        this.feedbackProvider.provideFeedback(sMessage);

        if (this.fnExtraSubmitSuccessHandler) {
            this.fnExtraSubmitSuccessHandler(mxhrResponse);
        }

        if ($('cmdOK') != null) {
            enable($('cmdOK'));
        }

        if ($('cmdCancel') != null) {
            enable($('cmdCancel'));
        }

        if ($('divSystemError') != null) {
            $('divSystemError').innerHTML = "&nbsp;"
        }

        this._fireSubmitSuccess();
    },
//public
    addSubmitSuccessListener:function(listener) {
        this._submitSuccessListeners.push(listener);
    },
//private
    _submitSuccessListeners:[],
//private
    _fireSubmitSuccess:function() {
        var formSubmitter = this;
        formSubmitter._submitSuccessListeners.each(function(listener) {
            listener.call(formSubmitter);
        });
    },

    _mxhrFailureHandler: function() {
        enable($('cmdOK'));
        if (this.eltCancel) {
            enable($('cmdCancel'));
        }
    },

    _deserializeForm: function() {

        var pairs = this.formState.split("&");
        for (var i = 0; i < pairs.length; i++) {
            pairs[i] = pairs[i].split("=");
        }

        var elements = Form.getElements($('frmCurrentForm'));

        for (var i = 0; i < elements.length; i++) {
            var elt = elements[i];
            var tagName = elt.tagName.toLowerCase();
            if (tagName == "input" && elt.type == "text") {
                elt.value = decodeURIComponent(this._getValue(pairs, elt.name));
            } else if (tagName == "input" && (elt.type == "radio" || elt.type == "checkbox")) {
                var value = this._getValue(pairs, elt.name);
                if (value != elt.value) {
                    elt.checked = false;
                } else {
                    elt.checked = "checked";
                }
            } else if (tagName == "select") {
                var value = this._getValue(pairs, elt.name);
                for (var j = 0; j < elt.options.length; j++) {
                    if (elt.options[j].value != value) {
                        elt.options[j].removeAttribute("selected");
                    } else {
                        elt.options[j].selected = "selected";
                    }
                }
            }
        }

    },

    _getValue: function(pairs, name) {
        for (var i = 0; i < pairs.length; i++) {
            if ((pairs[i])[0] == name) {
                return (pairs[i])[1];
            }
        }
        return null;
    }

}

var FeedbackProvider = Class.create();
FeedbackProvider.prototype = {
    initialize: function(eltFeedbackContainer) {
        this.sFeedbackContainer = eltFeedbackContainer;
        this.messageTimeoutClosure = this._messageTimeout.bind(this);
        this.timeoutId = null;
    },

    provideFeedback: function(sMessage) {
        $(this.sFeedbackContainer).innerHTML = "<span>" + sMessage + "</span><div class='formFeedback'></div>";
        $(this.sFeedbackContainer).style.visibility = 'visible';

        this.timeoutId = window.setTimeout(this.messageTimeoutClosure, 5 * 1000);
    },

    stopFeedback: function() {
        if (this.timeoutId != null) {
            window.clearTimeout(this.timeoutId);
            this._messageTimeout();
        }
    },

    _messageTimeout: function() {
        $(this.sFeedbackContainer).style.visibility = 'hidden';
        this.timeoutId = null;
    }

}
function observeSearchMessagesInGroup(socialGroupId,optionalSuffix) {
    assert("socialGroupId expected for group message search",socialGroupId);
    optionalSuffix=trimToEmpty(optionalSuffix);
    var actionLink=$('searchMessagesInGroup'+optionalSuffix);
    observeClickIfExists(actionLink, function() {
        var txtCriteria=$('txtSearchCriteriaMessagesInGroup'+optionalSuffix);
        var searchCriteria = trimToNull(txtCriteria.value);
        if (searchCriteria == null) {
            alert('Please, enter message search criteria first');
            focusAndSelect(txtCriteria);
            return;
        }
        searchCriteria+= " socialGroupId:" + socialGroupId;
        var pars = "";
        pars += "&searchCriteria=" + encodeURIComponent(searchCriteria);
        pars += "&searchType=Messages";
        window.open("communitySearch.do?" + pars, "_self");
    });
}

function observeCompleteMemberList(socialGroupId, hourglassId, messageId) {
    observeClickIfExists('completeMemberList', function() {
        var pars = "";
        pars += "&socialGroupId=" + socialGroupId;
        new Mxhr({
            url: 'socialGroupMemberList.do',
            parameters: pars,
            hourglassId: hourglassId,
            messageId: messageId
        }).execute();
    });
}

function observeCompleteTopicList(hourglassId, messageId) {
    document.getElementsByClassName('completeTopicList').each(function(link) {
        observeClick(link, function() {
            var pars = "";
            var socialGroupId = link.id.split("completeTopicList_groupId")[1];
            pars += "&socialGroupId=" + socialGroupId;
            new Mxhr({
                url: 'socialGroupTopicList.do',
                parameters: pars,
                hourglassId: hourglassId,
                messageId: messageId
            }).execute();
        });
    });
}

function observeSocialProfile(form, welcome, successCallback) {
    Element.observe(form, 'submit', function (event) {
        ProEventUtil.formatEvent(event);
        event.preventDefault();

        var pars = "";
        pars += "&" + $(form).serialize();
        pars += "&welcome=" + welcome;

        new Mxhr({
            url: 'socialProfileSave.do',
            parameters: pars,
            hourglassId: 'hourglassImg',
            messageId: 'pageError',

            onSuccessHandler: function(mxhrResp) {
                var resp = mxhrResp.getPayloadData();
                if (resp['saved']) {
                    if (successCallback) {
                        successCallback();
                    }
                } else {
                    setInnerHtml('pageError', resp['message']);
                }
            }
        }).execute();
    });
}

var FoodImageDialog = Class.create();
//public static factory method
FoodImageDialog.openFoodImageDialog = function(imageSelectionHandler, invitation, anchorElement, matchParamsGetter) {
    assert('fdImgDlg:imageSelectionHandler expected', imageSelectionHandler);
    assert('fdImgDlg:anchorElement expected', anchorElement);
    assert('fdImgDlg:matchParamsGetter expected', matchParamsGetter);
    anchorElement = $(anchorElement);

    if (FoodImageDialog.INSTANCE == undefined) {
        FoodImageDialog.INSTANCE = new FoodImageDialog(imageSelectionHandler, invitation, anchorElement, matchParamsGetter);
    } else {
        FoodImageDialog.INSTANCE.invitation = invitation;
        FoodImageDialog.INSTANCE.imageSelectionHandler = imageSelectionHandler;
        FoodImageDialog.INSTANCE.anchorElement = anchorElement;
        FoodImageDialog.INSTANCE.matchParamsGetter = matchParamsGetter;

        FoodImageDialog.INSTANCE.showInstance();
        FoodImageDialog.INSTANCE.refreshInvitation();
    }

    FoodImageDialog.INSTANCE.doMatch();
};

FoodImageDialog.prototype = {
    initialize: function(imageSelectionHandler, invitation, anchorElement, matchParamsGetter) {
        var dialog = this;
        this.dialogWindow = new MWindow('dlgFoodImageSelector', {title:"Select food image",
            width:600, height:450, resizable: true,
            showEffect: Element.show, hideEffect: Element.hide
        });

        dialog.imageSelectionHandler = imageSelectionHandler;
        dialog.invitation = invitation;
        dialog.anchorElement = anchorElement;
        dialog.matchParamsGetter = matchParamsGetter;


        dialog.dialogWindow.shouldSaveMWindow = function() {
            return false;
        };

        dialog.dialogWindow.positionUnder = function(anchorElement) {
            anchorElement = $(anchorElement);
            var offset = Position.cumulativeOffset(anchorElement);
            var top = offset[1] + anchorElement.getHeight() + 20;
            var left = offset[0] + 45;
            this.setLocation(top, left);
        };

        dialog.showInstance();

        var h = "";
        h += "<div id='idImgLstError' class='error'></div>";
        h += "<img src='img/hourglass.gif' id='idImgLstHourglass' style='visibility:hidden'/>";

        h += "<table><tr>";
        h += "  <td>";
        h += "    &nbsp;&nbsp;<a id='idImgLstMatchLink' " +
             "       title='Click to find images based on food descriptions'" +
             "       href='#'>Match</a>&nbsp;&nbsp;";
        h += "  </td>";
        h += "  <td>";
        h += "    <div id='divImgLstInvitation' style='display:none'></div>";
        h += "  </td>";
        h += "</tr></table>";
        h += "<div id='divFoodImageList'>Loading food images...</div>";
        dialog.dialogWindow.content.innerHTML = h;

        dialog.refreshInvitation();

        observeClickIfExists('idImgLstMatchLink', function() {
            dialog.doMatch();
        });
    },

    doMatch:function() {
        try {
            var dialog = this;
            var matchParams = dialog.matchParamsGetter();
            dialog.frolImageListRefresh(matchParams);
        } catch(e) {
            alert('ERROR while matching, matchParams='+matchParams+', error='+e);
        }
    },

    showInstance: function() {
        this.dialogWindow.show(false, true);
        this.dialogWindow.positionUnder(this.anchorElement);
        focus('txtsearchCriteria');
    },

    afterRefresh:function() {
        var dialog = this;
        focus('txtsearchCriteria');

        observeClickIfExists('idKeywordsLink', function() {
            Element.show('divPossibleKeywords');
        });

        document.getElementsByClassName('possibleKeywordLink').each(function(link) {
            observeClick(link, function() {
                $('txtsearchCriteria').value += ' +' + link.text;
                dialog.frolImageListRefresh();
            });
        });

        document.getElementsByClassName('foodImageLink').each(function(link) {
            observeClick(link, function() {
                var foodImageId = link.id.split('foodImageLink')[1];
                dialog.imageSelectionHandler(foodImageId);
            });
        });

        observeClickIfExists('idClear', function() {
            $('txtsearchCriteria').value ='';
            dialog.frolImageListRefresh();
        });

        observeClickIfExists('idFindImages', function() {
            dialog.frolImageListRefresh();
        });

        observeClickIfExists('idHelpLink', function() {
            Element.hide('idHelpLink');
            Element.show('idHelpBody');
        });


    },

    refreshInvitation:function() {
        if (this.invitation) {
            setInnerHtml('divImgLstInvitation', this.invitation);
            Element.show('divImgLstInvitation');
        }
    },

    frolImageListRefresh:function(matchingParams) {
        var dialog = this;
        var params = '';

        if (matchingParams) {
            params = "matchFoodDesc=true&" + matchingParams;
        } else {
            params = $('frmSearch').serialize();
        }

        new Mxhr({
            url: 'frolImageListRefresh.do',
            parameters: params,
            onSuccessHandler: function() {
                dialog.afterRefresh();
            },
            hourglassId: 'idImgLstHourglass',
            messageId: 'idImgLstError'
        }).execute();
    }
};


