/*  Prototype JavaScript framework, version 1.5.0_rc1
 *  (c) 2005 Sam Stephenson <sam@conio.net>
 *
 *  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_rc1',
  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; i < arguments.length; i++) {
      var lambda = arguments[i];
      try {
        returnValue = lambda();
        break;
      } catch (e) {}
    }

    return returnValue;
  }
}

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

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

    this.registerCallback();
  },

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

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

  onTimerEvent: function() {
    if (!this.currentlyExecuting) {
      try {
        this.currentlyExecuting = true;
        this.callback(this);
      } finally {
        this.currentlyExecuting = false;
      }
    }
  }
}
Object.extend(String.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 += (replacement(match) || '').toString();
        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[0].nodeValue : '';
  },

  toQueryParams: function() {
    var match = this.strip().match(/[^?]*$/)[0];
    if (!match) return {};
    var pairs = match.split('&');
    return pairs.inject({}, function(params, pairString) {
      var pair  = pairString.split('=');
      var value = pair[1] ? decodeURIComponent(pair[1]) : undefined;
      params[decodeURIComponent(pair[0])] = value;
      return params;
    });
  },

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

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

    var camelizedString = this.indexOf('-') == 0
      ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
      : oStringList[0];

    for (var i = 1, length = oStringList.length; i < length; i++) {
      var s = oStringList[i];
      camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
    }

    return camelizedString;
  },

  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 + (object[match[3]] || '').toString();
    });
  }
}

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.collect(iterator || Prototype.K);
  },

  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(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 || null;
    var results = this.eachSlice(number);
    if (results.length > 0) (number - results.last().length).times(function() {
      results.last().push(fillWith)
    });
    return results;
  },

  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.collect(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.collect(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.collect(Prototype.K);
  },

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

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

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

Array.prototype.toArray = Array.prototype.clone;
var Hash = {
  _each: function(iterator) {
    for (var key in this) {
      var value = this[key];
      if (typeof value == 'function') 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;
    });
  },

  toQueryString: function() {
    return this.map(function(pair) {
      if (!pair.value && pair.value !== 0) pair[1] = '';
      if (!pair.key) return;
      return pair.map(encodeURIComponent).join('=');
    }).join('&');
  },

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

function $H(object) {
  var hash = Object.extend({}, object || {});
  Object.extend(hash, Enumerable);
  Object.extend(hash, Hash);
  return hash;
}
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();
    this.options.parameters = $H(typeof this.options.parameters == 'string' ?
      this.options.parameters.toQueryParams() : this.options.parameters);
  }
}

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

Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
  initialize: function(url, options) {
    this.transport = Ajax.getTransport();
    this.setOptions(options);
    this.request(url);
  },

  request: function(url) {
    var params = this.options.parameters;
    if (params.any()) params['_'] = '';

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

    this.url = url;

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

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

      // topix (sawka): don't pass username / password unless we have it
      if (this.options.username)
      {
          this.transport.open(this.options.method.toUpperCase(), this.url, this.options.asynchronous,
                              this.options.username, this.options.password);
      }
      else
      {
          this.transport.open(this.options.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.options.method == 'post' ?
        (this.options.postBody || params.toQueryString()) : 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)
      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.options.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; i < extras.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') {
      // topix (sawka): add pre-handlers
      try {
        Ajax.Responders.dispatch('onPre' + (this.success() ? 'Success' : 'Failure'), this, transport, json);
      } catch(e) {
        this.dispatchException(e);
      }

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

    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') {
      if ((this.getHeader('Content-type') || '').strip().
        match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i))
          this.evalResponse();

      // 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() {
    // topix (sawka): evaluating the X-JSON header is not a very smart way
    //     to get JSON output from the server.  instead we should evaluate
    //     JSON output from the request body if the content-type is application/json
   
    // topix (sawka): old code commented out
    //try {
    //  var json = this.getHeader('X-JSON');
    //  return json ? eval('(' + json + ')') : null;
    //} catch (e) { return null }

    try {
      if (this.getHeader('Content-type') == "application/json") {
        var json = this.transport.responseText;
        return json ? eval('(' + json + ')') : null;
      }
      return 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);
  }
});

// topix (sawka): add a wrapper class to do ajax requests with HTML form objects
Ajax.FormRequest = Class.create();
Ajax.FormRequest.prototype = Object.extend(Ajax.Request, {
    initialize: function(form, options) {
        form = $(form);
        options.parameters = Form.serialize(form);
        options.method = form.method;
        return new Ajax.Request(form.action, options);
    }
});

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, len = query.snapshotLength; i < len; 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) return;
  if (_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);

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

    var methods = Object.clone(Element.Methods.Simulated), cache = Element.extend.cache;
    for (var property in methods) {
      var value = methods[property];
      if ('function' == typeof value && !(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);
    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) {
    element = $(element);
    return $A(element.getElementsByTagName('*'));
  },

  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) {
    element = $(element);
    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) {
    element = $(element);
    return document.getElementsByClassName(className, element);
  },

  getHeight: function(element) {
    element = $(element);
    return element.offsetHeight;
  },

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

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

  childOf: function(element, ancestor) {
    element = $(element), ancestor = $(ancestor);
    while (element = element.parentNode)
      if (element == ancestor) return true;
    return false;
  },

  scrollTo: function(element) {
    element = $(element);
    var x = element.x ? element.x : element.offsetLeft,
        y = element.y ? element.y : element.offsetTop;
    window.scrollTo(x, y);
    return element;
  },

  getStyle: function(element, style) {
    element = $(element);
    var value = element.style[style.camelize()];
    if (!value) {
      if (document.defaultView && document.defaultView.getComputedStyle) {
        var css = document.defaultView.getComputedStyle(element, null);
        value = css ? css.getPropertyValue(style) : null;
      } else if (element.currentStyle) {
        value = element.currentStyle[style.camelize()];
      }
    }

    if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
      if (Element.getStyle(element, 'position') == 'static') value = 'auto';

    return value == 'auto' ? null : value;
  },

  setStyle: function(element, style) {
    element = $(element);
    for (var name in style)
      element.style[name.camelize()] = style[name];
    return element;
  },

  getDimensions: function(element) {
    element = $(element);
    if (Element.getStyle(element, 'display') != 'none')
      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;
    els.visibility = 'hidden';
    els.position = 'absolute';
    els.display = '';
    var originalWidth = element.clientWidth;
    var originalHeight = element.clientHeight;
    els.display = 'none';
    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;
  },

  // topix (sawka): added 11/01/06 to "move" an element to an absolute position...
  moveTo: function(elem, x, y) {
    Element.setStyle($(elem), { position: 'absolute', left: x + 'px', top: y + 'px' });
  },

  moveTo4: function(elem, top, right, bottom, left) {
    Element.setStyle($(elem), { position: 'absolute' });
    if (top != null) { Element.setStyle($(elem), { top: top + 'px' }); }
    if (right != null) { Element.setStyle($(elem), { right: right + 'px' }); }
    if (bottom != null) { Element.setStyle($(elem), { bottom: bottom + 'px' }); }
    if (left != null) { Element.setStyle($(elem), { left: left + 'px' }); }
  },

  // topix (sawka)
  moveToWithFit: function(elem, x, y) {
    var dims = Element.getDimensions(elem);
    Element.moveTo(elem, Position.fitXInViewport(x, dims.width), Position.fitYInViewport(y, dims.height));  
  }
}

Element.Methods.Simulated = {
  hasAttribute: function(element, attribute) {
    return $(element).getAttributeNode(attribute).specified;
  }
}

// IE is missing .innerHTML support for TABLE-related elements
if(document.all){
  Element.Methods.update = function(element, html) {
    element = $(element);
    html = typeof html == 'undefined' ? '' : html.toString();
    var tagName = element.tagName.toUpperCase();
    if (['THEAD','TBODY','TR','TD'].indexOf(tagName) > -1) {
      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.toLowerCase();
        if (tagName == 'tbody' || tagName == 'tr') {
          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.id == ' + clause.inspect());
    if (clause = params.tagName)
      conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
    if ((clause = params.classNames).length > 0)
      for (var i = 0; i < clause.length; i++)
        conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')');
    if (clause = params.attributes) {
      clause.each(function(attribute) {
        var value = 'element.getAttribute(' + 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(value + ' != null'); break;
          default:        throw 'Unknown operator ' + attribute.operator + ' in selector';
        }
      });
    }

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

  compileMatcher: function() {
    this.match = new Function('element', 'if (!element.tagName) return false; \
      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)).collect(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.strip().split(/\s+/).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) {
    return elements.inject([], function(queryComponents, element) {
      var queryComponent = Form.Element.serialize(element);
      if (queryComponent) queryComponents.push(queryComponent);
      return queryComponents;
    }).join('&');
  }
};

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

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

    var matchingInputs = new Array();
    for (var i = 0, 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) return '';
    var method = element.tagName.toLowerCase();
    var parameter = Form.Element.Serializers[method](element);

    if (parameter) {
      var key = encodeURIComponent(parameter[0]);
      if (key.length == 0) return;

      if (parameter[1].constructor != Array)
        parameter[1] = [parameter[1]];

      return parameter[1].map(function(value) {
        return key + '=' + encodeURIComponent(value);
      }).join('&');
    }
  },

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

    if (parameter)
      return parameter[1];
  },

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

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

  activate: function(element) {
    element = $(element);
    element.focus();
    if (element.select)
      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;

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

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

  inputSelector: function(element) {
    if (element.checked)
      return [element.name, element.value];
  },

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

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

  selectOne: function(element) {
    var value = '', opt, index = element.selectedIndex;
    if (index >= 0) {
      opt = Element.extend(element.options[index]);
      // Uses the new potential extension if hasAttribute isn't native.
      value = opt.hasAttribute('value') ? opt.value : opt.text;
    }
    return [element.name, value];
  },

  selectMany: function(element) {
    var value = [];
    for (var i = 0; i < element.length; i++) {
      var opt = Element.extend(element.options[i]);
      if (opt.selected)
        // Uses the new potential extension if hasAttribute isn't native.
        value.push(opt.hasAttribute('value') ? opt.value : opt.text);
    }
    return [element.name, value];
  }
}

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

var $F = Form.Element.getValue;

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

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();
    if (this.lastValue != value) {
      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];
  },

  // topix (sawka)
  absoluteOffset: function(element) {
    var dims = Position.page(element);
    dims[0] += Client.scrollLeft();
    dims[1] += Client.scrollTop();
    return dims;
  },

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

  // topix (sawka)
  fitXInViewport: function(x, width) {
    var viewportwidth = Client.viewportWidth();
    var scrollleft = Client.scrollLeft();
    if (x + width - scrollleft > viewportwidth - 20)
        x = scrollleft + viewportwidth - width - 20;
    if (x < scrollleft)
        x = scrollleft;
    return x;
  },

  fitYInViewport: function(y, height) {
    var viewportheight = Client.viewportHeight();
    var scrolltop = Client.scrollTop();
    if (y + height - scrolltop > viewportheight - 20)
        y = scrolltop + viewportheight - height - 20;
    if (y < scrolltop)
        y = scrolltop;
    return y;
  }
}

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


// topix (sawka): added some code to get the viewport width / height
//     from: http://www.andrewdupont.net/categories/web/development/javascript/
// which was dapted from the functions posted on QuirksMode
//     http://www.quirksmode.org/viewport/compatibility.html
var Client = {
  viewportWidth: function() {
    return self.innerWidth || (document.documentElement.clientWidth || document.body.clientWidth);
  },

  viewportHeight: function() {
    return self.innerHeight || (document.documentElement.clientHeight || document.body.clientHeight);
  },
  
  viewportSize: function() {
    return { width: this.viewportWidth(), height: this.viewportHeight() };
  },

  // topix (sawka): added these 3 functions as direct adaptations of quicksmode.org code
  scrollTop: function() {
    return self.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
  },

  scrollLeft: function() {
    return self.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
  },

  scrollOffsets: function() {
    return { left: this.scrollLeft(), top: this.scrollTop() };
  }
};

/* Topix JS functions 
/*--------------------------------------------------------------------------*/


var g_ajaxresp = new Object();
var page_node;   // declared in chrome

function ajax_onCreate(ajaxobj, ajaxtransport)
{
    g_ajaxresp = new Object();
    g_ajaxresp.url = ajaxobj.url;
    g_ajaxresp.parameters = {};
    if (ajaxobj.options.method == 'post')
    {
        g_ajaxresp.url += " (post)";
        g_ajaxresp.parameters = ajaxobj.options.parameters;
    }

    if (g_ajaxdebug)
    {
        var out = "DEBUG AJAX\n" + ajaxobj.url;
        if (ajaxobj.options.method == 'post')
        {
            out += " (post)\n";
            if (ajaxobj.options.parameters)
            {
                out += ajaxobj.options.parameters.toQueryString();
            }
        }
        alert(out);
    }
}

function ajax_onPreSuccess(ajaxobj, x)
{
    try
    {
        g_ajaxresp.tt = x.getResponseHeader("X-Topix-TT");
        g_ajaxresp.action = x.getResponseHeader("X-Topix-Action");
    }
    catch(e) { }
}

function ajax_successWrapper(origfn)
{
    return function(x, json) {
        // older responseText way to throw errors
        var resp = x.responseText;
        if (resp.indexOf("error:") == 0)
        {
            alert(resp.replace("error: ", ""));
            return;
        }

        // new structured way to throw errors from the server
        if (json && json.error) 
        {
            alert(json.error);
            return;
        }

        return origfn(x, json);
    };
}

var topix_responder = { onCreate: ajax_onCreate, onPreSuccess: ajax_onPreSuccess };
Ajax.Responders.register(topix_responder);

function ajax_request(url, params)
{
    if (params.onSuccess)
    {
        params.onSuccess = ajax_successWrapper(params.onSuccess);
    }
    new Ajax.Request(url, params);
}

function ajax_form_request(form, params)
{
    if (params.onSuccess)
    {
        params.onSuccess = ajax_successWrapper(params.onSuccess);
    }
    new Ajax.FormRequest(form, params);
}

function add_favorite(cat)
{
    ajax_request("/member/profile-edit-cats", {
        parameters: { ajax: 1, add: 1, cat: cat },
        onSuccess: function(x) {
            window.location = "";
        }
    });
}


function showForumTracker(elem)
{
    var div = $("forumTrackerDiv");
    if (!Element.visible(div))
    {
        ajax_request("/forum/tracker/ajax", {
            onSuccess: function(x) {
                var link = elem || $("forumTrackerLink");
                Element.update(div, x.responseText);
                var pos = Position.absoluteOffset(link);
                Element.moveToWithFit(div, pos[0] - 120, pos[1] + 20);
                Element.show(div);
            }
        });
    }
    else
    {
        closeForumTracker();
    }
    return false;
}   

function closeForumTracker()
{
    Element.hide("forumTrackerDiv");
}

function excludeFTItem(threadid)
{
    ajax_request("/forum/tracker/exclude", {
        parameters: { threadid: threadid },
        onSuccess: function(x) {
            Element.update("forumTrackerDiv", x.responseText);
        }
    });
    return false;
}


function getAndPopulate (url, divId, nonsync)
{
    ajax_request(url, {
        onSuccess: function(x) {
            Element.update(divId, x.responseText);
        }
	, method : "get"
    });
}


function sendForm(form, callback)
{
    ajax_form_request(form, { onSuccess: function(x) { callback(x); } });
}


function sendUrl(url, callback)
{
    ajax_request(url, { onSuccess: function(x) { callback(x); }, method: 'get' });
}

function addLinkTracker()
{
    if (!document.getElementsByTagName) return false;
    
    var elements = document.getElementsByTagName('a');
    for (var i = 0; i < elements.length; i++) 
    {
	// Only track a link with attribute t set
	if (! elements[i].getAttribute('t') )
	    continue; 
	addEvent(elements[i], 'mousedown', recordClick);
	// alert("added mousedown event on " + elements[i].getAttribute('href'));
    }
}


function addLinkTrackerToContainer( container )
{
    container = $(container);
    if (!container || !container.getElementsByTagName) return false;
    
    var elements = container.getElementsByTagName('a');
    for (var i = 0; i < elements.length; i++) 
    {
        // Only track a link with attribute t set
	    if (! elements[i].getAttribute('t') )
	       continue; 
	       addEvent(elements[i], 'mousedown', recordClick);
	    // alert("added mousedown event on " + elements[i].getAttribute('href'));
    }

}

function updateLinksToNewWindow(container)
{
    container = $(container);
    if (!container || !container.getElementsByTagName)
        return false;

    var elements = container.getElementsByTagName("a");
    for (var i = 0; i < elements.length; i++) 
    {
        elements[i].target = "_blank";
    }

    elements = container.getElementsByTagName("form");
    for (var i = 0; i < elements.length; i++) 
    {
        elements[i].target = "_blank";
    }

    return true;
}



function documentClickHandler( event )
{
    var elem = Event.element( event );

    if( elem.tagName == "A" && elem.getAttribute('t') )
    {
        recordClick( event );
    }
}


function addEvent(elm, evType, fn) 
{
    // alert("Adding listener " + fn + " to event " +  evType);
    // cross-browser event handling
    if (elm.addEventListener) { 
	elm.addEventListener(evType, fn, false); 
	return true; 
    } else if (elm.attachEvent) { 
	var r = elm.attachEvent('on' + evType, fn); 
	return r; 
    } else {
	elm['on' + evType] = fn;
    }
}

function recordClick(e)
{
    if (typeof e == 'undefined')
        e = window.event;

    var source;
    if (typeof e.target != 'undefined') {
        source = e.target;
    } else if (typeof e.srcElement != 'undefined') {
        source = e.srcElement;
    } else {
        return true;
    }

    var lid;
    var elem = source;
    var durl;
    var htarget;
    while (elem && elem.tagName != "BODY")
    {
        if (elem.tagName == "A")
        {
            lid = elem.getAttribute("t");
            durl = elem.getAttribute("href");
            htarget = elem.getAttribute("target");
            break;
        }
        elem = elem.parentNode;
    }

    sendClickEvent(lid, durl, htarget);

    return false;
}

function sendClickEvent(lid, durl, htarget)
{
    durl = "" + durl;
    if (durl.length > 300)
        durl = durl.substr(0, 300);
    if (t_v == undefined)
        return false;
    var url;
    if (t_v.indexOf("/clk") == -1)
        url = "/clk?v=" + t_v;
    else
        url = t_v;
    url += "&lid=" + lid + "&ht=" + escape(htarget) + "&durl=" + escape(durl);
    ajax_request(url, { 
        onSuccess: function(x) { 
            // alert("Sent clk event " + url);
        } 
    });
   
    return true;
}

addEvent(window, 'load', addLinkTracker);

// addEvent( window, 'load', function() { Event.observe( document, 'mousedown', documentClickHandler ) });

function recordLoadTime()
{
    var load_time = ((new Date()).getTime() - start_time) / 1000;
    document.images["tr"].src =
	"/t6track/ld?" +
        "lt=" + load_time +
        "&v=" + encodeURIComponent(ld_v);

}

var activeHide;

function showHideThreadLink(td, elem)
{
    elem = $(elem);
    if (activeHide == elem) return;
    if (activeHide) Element.hide(activeHide);
    var pos = Position.absoluteOffset(td);
    var dims = Element.getDimensions(elem);
    Element.moveToWithFit(elem, pos[0] - dims.width - 2, pos[1]);
    Element.show(elem);
    activeHide = elem;
}


var flagpost_current;

function flagPost_closePopup()
{
    Element.hide('flagpostdiv');
    flagpost_current = null;
}

function flagPost_popup(elem, forum, threadid, postid)
{
    if ($('flagpostdiv') == null) return;
    var fpid = threadid + postid;
    if (fpid == flagpost_current)
    {
        flagPost_closePopup();
        return;
    }
   
    $('flagpostdiv_flaglink').onclick = function() { flagPost(forum, threadid, postid); return false; };
    var permalink = "http://www.topix.com/forum/" + forum + "/T" + threadid + "/post" + postid;
    $('flagpostdiv_paidlink').onclick = function() { open_feedback('', { 'node' : forum, 'type' : 'postpaid', 'permalink1': permalink }); return false; };

    Element.hide("flagpostdiv_flaglinkmsg");
    Element.show("flagpostdiv_flaglink");

    var pos = Position.absoluteOffset(elem);
    Element.moveTo('flagpostdiv', pos[0] - 140, pos[1] + 20);
    Element.show('flagpostdiv');
    flagpost_current = fpid;
}

function flagPost(forum, threadid, postid)
{
    ajax_request("/forum/flag", {
        parameters: { forum: forum, threadid: threadid, postid: postid }
    });
    Element.hide("flagpostdiv_flaglink");
    Element.show("flagpostdiv_flaglinkmsg");
    return false;
}

function showHistogram( url, div )
{
	ajax_request( url, { onSuccess : function ( x ) { Element.update( div, x.responseText ); }, method : "get" } );
}

function setHistogram( source )
{
	if ( source.getAttribute && source.getAttribute( "goto" ) )
		ajax_update_search( 'main', source.getAttribute( "goto" ) );

	var next = source;
	while ( next = next.nextSibling )
	{
		var tmpstr = next.className;
		if ( tmpstr && tmpstr.match( /histogramBar/ ) )
		{
			next.className = tmpstr.replace( /hist(enable|select)/, "histdisable" );
		}
	}

	var next = source;
	while ( next = next.previousSibling )
	{
		var tmpstr = next.className;
		if ( tmpstr && tmpstr.match( /histogramBar/ ) )
		{
			next.className = tmpstr.replace( /hist(disable|select)/, "histenable" );
		}
	}

	var tmpstr = source.className;
	if ( tmpstr && tmpstr.match( /histogramBar/ ) )
	{
		source.className = tmpstr.replace( /hist(disable|select|enable)/, 'histselect' );
	}
}

function ajax_update_search( main, url )
{
	Element.update( main, $( 'loading' ).innerHTML );
	Element.scrollTo( 'wrapper' );
	ajax_request( url, { onSuccess : function ( x ) { Element.update( main, x.responseText ) }, method : "get" } );
}

var calendar_curmonth = 0;
var calendar_monthlist = new Array();
var calendar_curdisplay;

function calendar_addMonth( month )
{
  calendar_monthlist.push( month );
}

function calendar_showMonth()
{
  if ( calendar_curdisplay )
    $( calendar_curdisplay ).style.display = 'none';

  calendar_curdisplay = $( calendar_monthlist[calendar_curmonth] );

  if ( calendar_curdisplay )
  { 
    $( calendar_curdisplay ).style.display = 'block';
  }
}

function calendar_lastMonth()
{
  calendar_curmonth = calendar_monthlist.length - 1;
  calendar_showMonth();
}

function calendar_nextMonth()
{
  if ( calendar_curmonth < calendar_monthlist.length - 1 )
  {
    calendar_curmonth++;
    calendar_showMonth();
  }
}
function calendar_prevMonth()
{
  if ( calendar_curmonth > 0 )
  {
    calendar_curmonth--;
    calendar_showMonth();
  }
}

function searchValidate (frm)
{
	if (frm.elements['q'] == '')
	{
	    return false;
	}

	var lid = frm.getAttribute("t");
	if (lid) {
	    sendClickEvent(lid, null, null);
	}

	return true;
}

function setBlogs (n, frm)
{			
    if (frm && frm.elements['blogs'])
    {
    	frm.elements['blogs'].value = n;		
    }
}

function showFavorites()
{
    var pos = Position.absoluteOffset($('favoritesdiv'));
    Element.moveToWithFit("favoritesblock", pos[0] - 30, pos[1] + 20);
    Element.toggle("favoritesblock");
    
}

function toggleClassSingle(element, classname)
{
    if (Element.hasClassName(element, classname))
    {
        Element.removeClassName(element, classname);
    }
    else
    {
        Element.addClassName(element, classname);
    }
}

function showAndHide(elemtoshow, elemstohide)
{
    elemtoshow = $(elemtoshow);
    Element.show(elemtoshow);
    if (elemstohide != null)
    {
        for (var i=0; i<elemstohide.length; i++)
        {
            var tohide = $(elemstohide[i]);
            if (tohide != null && tohide != elemtoshow)
            {
                Element.hide(tohide);
            }
        }
    }
}

function toggleClass (allElements, activeElement, active)
{
	allElements.each (function (elem) {Element.removeClassName (elem,active)});
	Element.addClassName (activeElement,active);
}

function gridme() {
	var grid = document.createElement("div");
	grid.setAttribute("style","width:100%; height:3000px; position:absolute;top:0px;background:transparent url(http://64.13.129.136/pics/grid_v2.png) top center repeat-y;opacity:0.5;");
	document.body.appendChild(grid);
}

function showExplore(explorelinkdiv)
{
    closeOtherHatBlocks("exploreblock");
    var pos = Position.absoluteOffset(explorelinkdiv);
    Element.moveToWithFit("exploreblock", pos[0] - 0, pos[1] + 26);
    Element.toggle("exploreblock");
}

function closeOtherHatBlocks(id)
{
    if (id != "editorblock" && $('editorblock')) Element.hide ('editorblock');
    if (id != "adminblock" && $('adminblock')) Element.hide ('adminblock');
    if (id != "timesblock" && $('timesblock')) Element.hide ('timesblock');
    if (id != "exploreblock" && $('exploreblock')) Element.hide ('exploreblock');
}


function google_ad_request_done(google_ads)
{
    if (google_ads.length == 0)
    {
        return;
    }
    var s = '';
    if (google_ads[0].type == "flash")
    {   
        s = '<a href="' + google_info.feedback_url + '" style="color:000000; font-size:11px">Ads by Google</a><br>' +
            '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"' +
            ' codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0" WIDTH="' +
            google_ad.image_width + '" HEIGHT="' +
            google_ad.image_height + '"> <PARAM NAME="movie" VALUE="' +
            google_ad.image_url + '">' +
            '<PARAM NAME="quality" VALUE="high">' +
            '<PARAM NAME="AllowScriptAccess" VALUE="never">' +
            '<EMBED src="' + google_ad.image_url +
            '" WIDTH="' + google_ad.image_width +
            '" HEIGHT="' + google_ad.image_height +
            '" TYPE="application/x-shockwave-flash"' +
            ' AllowScriptAccess="never" ' +
            ' PLUGINSPAGE="http://www.macromedia.com/go/getflashplayer"></EMBED></OBJECT>';
    }
    else if (google_ads[0].type == "image")
    {
        s = '<div style="margin:auto; width:468px"><div style="text-align:left"><a href="' + google_info.feedback_url +
            '" style="color:#000000; font-size:11px">Ads by Google</a></div>' +
            '<a href="' + google_ads[0].url +
            '" target="_top" title="go to ' + google_ads[0].visible_url +
            '" onmouseout="window.status=\'\'; return true;" onmouseover="window.status=\'go to ' +
            google_ads[0].visible_url + '\';return true;"><img border="0" src="' + google_ads[0].image_url +
            '"width="' + google_ads[0].image_width +
            '"height="' + google_ads[0].image_height + '"></a></div>';
    }
    else if (google_ads[0].type == "html")
    {
        s = google_ads[0].snippet;
    }
    else if (google_ads.length > 1 && topix_custom_adtype == "forum")
    {
        // 200px ad + 2 * 3px margin = 206
        var adswidth = (206 * google_ads.length) + 2;
        s = '<div style="margin:auto; width:' + adswidth + 'px">';
        s += '<a href="' + google_info.feedback_url + '" style="margin:3px; color:#000000; font-size:11px">Ads by Google</a>';
        for (i = 0; i < google_ads.length; ++i)
        {
            s += '<div style="float:left; text-align:left; width:200px; margin: 3px"><a href="' + google_ads[i].url + '" ' + 
                'onmouseout="window.status=\'\'; return true;" ' +
                'onmouseover="window.status=\'go to ' +
                google_ads[i].visible_url + '\'; return true;" style="text-decoration:none; color:#164c97">' +
                '<span style="text-decoration:underline"><b>' + google_ads[i].line1 + '</b></span></a>' + 
                '<span style="color:#000000; font-size:11px"><br>' +
                google_ads[i].line2 + ' ' + google_ads[i].line3 + '</span><br>' +
                '<a href="' + google_ads[i].url + '" ' + 
                'onmouseout="window.status=\'\'; return true;" ' +
                'onmouseover="window.status=\'go to ' +
                google_ads[i].visible_url + '\'; return true;" style="text-decoration:none; color:#164c97">' +
                '<span style="color:#' + topix_urlcolor + '; font-size:11px">' +
                google_ads[i].visible_url + '</span></a></div>';
        }
        s += '<div class="divclear"></div></div>';
    }
    else
    {
        var i;
        s = '<a href="' + google_info.feedback_url + '" style="color:#000000; font-size:11px">Ads by Google</a>';
        for (i = 0; i < google_ads.length; ++i)
        {
            s += '<div style="padding-top:3px"><a href="' + google_ads[i].url + '" ' + 
                'onmouseout="window.status=\'\'; return true;" ' +
                'onmouseover="window.status=\'go to ' +
                google_ads[i].visible_url + '\'; return true;" style="text-decoration:none; color:#164c97">' +
                '<span style="text-decoration:underline"><b>' + google_ads[i].line1 + '</b></span></a>' + 
                '<span style="color:#000000; font-size:11px"> - ' +
                google_ads[i].line2 + ' ' + google_ads[i].line3 + '</span><br>' +
                '<a href="' + google_ads[i].url + '" ' + 
                'onmouseout="window.status=\'\'; return true;" ' +
                'onmouseover="window.status=\'go to ' +
                google_ads[i].visible_url + '\'; return true;" style="text-decoration:none; color:#164c97">' +
                '<span style="color:#' + topix_urlcolor + '; font-size:11px">' +
                google_ads[i].visible_url + '</span></a></div>';
        }
    }    
    document.write(s);
    return;
} 


function set_user_pref(userid, name, value, callback)
{
    var params = { targetuserid : userid };
    params[name] = value;

    ajax_request("/member/set-prefs",
    {
        parameters: params,
        onSuccess: function() { callback(value); }
    });
    return false;
}

function toggle_recentposts(isprivate)
{
    if (isprivate)
    {
        Element.addClassName("user_recentposts", "rp_private");
    }
    else
    {
        Element.removeClassName("user_recentposts", "rp_private");
    }
}

function homepage_showMoreActive()
{
    ajax_request("/ajax/homepage-activity",
    {
        onSuccess: function(x) { Element.update("homepage_activeUsers", x.responseText) }
    });
}

function addthis_click(obj, addthis_url, addthis_title, addthis_pub)
{
    var aturl  = 'http://www.addthis.com/bookmark.php';
    aturl += '?v=1';
    aturl += '&pub='+addthis_pub;
    aturl += '&url='+encodeURIComponent(addthis_url);
    aturl += '&title='+encodeURIComponent(addthis_title);
    window.open(aturl,'addthis','scrollbars=yes,menubar=no,width=620,height=520,resizable=yes,toolbar=no,location=no,status=no,screenX=200,screenY=100,left=200,top=100');
    return false;
}

function poll_vote(polluiid, pollid, optionidx, timestamp, answerhash, redir_url, mode)
{
    ajax_request('/poll/vote', {
        parameters: {
            pollid: pollid,
            optionidx: optionidx,
            timestamp: timestamp,
            answerhash: answerhash,
            mode: mode },
        onSuccess: function(x) {
            if (redir_url != null && redir_url.length > 0)
            {
                window.location = redir_url;
            }
            else
            {
                Element.update('pollwrapper-' + polluiid, x.responseText);
            }
        }
    });
    sendClickEvent('poll/vote', redir_url);
}

function poll_mover(e) { Element.addClassName(e, 'mousehover'); }
function poll_mout(e) { Element.removeClassName(e, 'mousehover'); }

// remove background image flicker in IE
try {
  document.execCommand("BackgroundImageCache", false, true);
} catch(err) {}

function zoomMap (selected_pic, tab_id)
{
	var source = selected_pic.getAttribute ('href');
	var pos = Position.absoluteOffset ($('weather_maps'));
	Element.show ('weather_maps_zoom');	
	Element.moveTo ('weather_maps_zoom', pos[0] + 10, pos[1] + 35);
	$('placeholder').setAttribute ('src', source);
	toggleClass ($$ ('#weather_maps_zoom li a'),tab_id,'active');
}

function swapMap (selected_pic)
{
	var source = selected_pic.getAttribute ('href');
	$('placeholder').setAttribute ('src', source);
	toggleClass ($$ ('#weather_maps_zoom li a'),selected_pic,'active');
}

function hide_random_quote()
{
    var div = $("randomPostDiv");
    if (Element.visible(div))
    {
        Element.hide("randomPostDiv");
    }
    return false;
}

function show_random_quote(userid, elem)
{
    var div = $("randomPostDiv");
    ajax_request("/member/random-post", {
        parameters: { userid : userid },
        onSuccess: function(x, json) {
            if (json && json.content)
            {
                Element.update(div, json.content);
            }
            var pos = Position.absoluteOffset(elem);
            var dim = Element.getDimensions(div);
            Element.moveToWithFit(div, pos[0] - 36, pos[1] - 106 - dim.height);
            Element.show(div);
        }
    });
    return false;
}

var Spotlight = Class.create();

Spotlight.prototype = { 

    initialize: function( textfield_id, callback ) 
    {

        // Controls whether or not we display the results
        this.sp_closed = 0;

        // Textfield we are watching
        this.textfield_id = textfield_id; 
        
        // Controls out of order requests
        this.sid = 0;

        // Use this to not blur on spotlight clicks
        this.cancel_blur = 0;

        if( callback )
        {
            this.onclick_callback_fn = callback;
        }

        this.create_and_append_container();
        
        this.textfield_handler = this.observer_callback.bindAsEventListener( this );
        Event.observe( this.textfield_id, 'keyup', this.textfield_handler );
        
        this.blur_handler = this.handle_blur.bindAsEventListener( this );
        Event.observe( textfield_id, 'blur', this.blur_handler );
        
        if( callback )
        {
            this.document_click_handler = this.handle_document_click.bindAsEventListener( this );
            Event.observe( document, 'click', this.document_click_handler );
        }

        this.document_mousedown_handler = this.handle_document_mousedown.bindAsEventListener( this );
        Event.observe( document, 'mousedown', this.document_mousedown_handler );
    },

    // dispatches click handlers 
    handle_document_click : function( event )
    {
        var elem = Event.element( event );
        var asoc_tf = elem.getAttribute( 'tf' );
                             
        if( asoc_tf == this.textfield_id )
        {
            
            var node = elem.getAttribute( 'node' );
            var pn = elem.getAttribute( 'pn' );                              
                                    
            this.onclick_callback_fn.bind( this );
            
            // Don't call spotlight until we return from the handler
            this.disable_spotlight();

            // If the handler returns true, re-enable spotlight
            if( this.onclick_callback_fn( node, pn ) )
            {
                setTimeout( this.enable_spotlight.bind( this ), 150 );
            }
            this.hide_container();

            return false;
        }

    },

    // Just use this to cancel blurs
    handle_document_mousedown : function( event )
    {
        var elem = Event.element( event );

        if( elem.getAttribute( 'cb' ) )
        {
            this.cancel_blur = 1;
        }
    },

    // Creates and appends the div that holds the spotlight results
    create_and_append_container: function() 
    {
        this.container = document.createElement( 'div' );
        Element.extend( this.container );

        this.container.id = this.textfield_id + '_sp_container';

        var body_ref = document.getElementsByTagName( 'body' ).item(0);
        body_ref.appendChild( this.container );
                     
        Element.hide(this.container);                
    },
        
    // Called on successful ajax return    
    ajax_handler : function( transport, transport_copy, json )
    {
        if( json.sid == this.sid )
        {
            this.container.update( json.content );
            this.attach_to_close();
            this.display_container();           
        }
    },
        
    // Called by the Observer when the textfield changes
    observer_callback : function( element, element_copy )
    {
    
        var value = $F(this.textfield_id);
        if ( this.sp_closed == 0 )
        {
            var params = 'q=' + value + '&sid=' + ++this.sid + '&tf=' + this.textfield_id;
            
            if( this.onclick_callback_fn )
            {
                params = params + '&handler=1';
            }

            new Ajax.Request( '/ajax/spotlight',
                              { method: 'post',
                                parameters: params,
                                onSuccess: this.ajax_handler.bindAsEventListener(this)
                              }
                            );
        }

        if( $(this.textfield_id).value == '' )
        {
            this.enable_spotlight();
        }

    },

    display_container : function()
    {
        // Returns left, top
        var dims = Position.absoluteOffset( $(this.textfield_id) );
        var size = $(this.textfield_id).getDimensions();

        $(this.container).moveTo( dims[0], dims[1] + size.height );
        $(this.container).show();
    },
     
    handle_blur : function( e ) 
    { 
        if( this.cancel_blur == 1)
        {
            this.cancel_blur = 0;
            Form.Element.focus( this.textfield_id );
        }
        else
        {
            this.hide_container();
        }
        
        return true;
    },

    hide_container : function() { this.container.hide(); },
    
    close_spotlight : function () 
    { 
                           
        this.disable_spotlight();
        this.container.hide(); 

        return false;
    },

    attach_to_close : function()
    {
        var close_links = $(this.container).getElementsBySelector( 'a[id="close_sp_anchor"]' );

        // Should only be 1 element, if > 1 element we have a problem
        if( close_links.length == 1 && $(close_links[0]).id == 'close_sp_anchor' )
        { 
            this.close_link_id = close_links[0].id;
            this.close_link_handler = this.close_spotlight.bindAsEventListener( this );

            Event.observe( close_links[0].id , 'click', this.close_link_handler );
        }
    },

    destroy: function() 
    { 
        $(this.container).remove();
        Event.stopObserving( this.close_link_id, 'click', this.close_link_handler );
        Event.stopObserving( this.textfield_id, 'blur', this.blur_handler );
        Event.stopObserving( document, 'click', this.document_click_handler );
        Event.stopObserving( document, 'mousedown', this.document_mousedown_handler  );
        Event.stopObserving( this.textfield_id, 'keyup', this.textfield_handler );

    },

    enable_spotlight: function()
    {
        this.sp_closed = 0;
    },

    disable_spotlight: function()
    {
        this.sp_closed = 1;
    }
};

function nodebox_init_spotlight(nodeboxid)
{
    Element.hide(nodeboxid + '-nodedisplay'); 
    Element.show(nodeboxid + '-nodeupdate');
    var spotlight = new Spotlight(nodeboxid + '-nodefield-input', spotlight_fill_textfield);
    spotlight.nodeboxid = nodeboxid;
    return false;
}

function spotlight_fill_textfield(node, printname)
{
    $(this.nodeboxid + '-nodefield-input').value = printname;
    $(this.nodeboxid + '-nodefield-pn').innerHTML = printname;
    $(this.nodeboxid + '-config-node').value = node;
    Element.show(this.nodeboxid + '-nodedisplay'); 
    Element.hide(this.nodeboxid + '-nodeupdate');
    this.destroy();
    return false;
}

function nodebox_run_config_callback(callback, nodeboxid)
{
    var data = {};
    data.num_arts = $F(nodeboxid + "-config-num_arts");
    data.num_threads = $F(nodeboxid + "-config-num_threads");
    data.node = $F(nodeboxid + "-config-node");
    data.expand_first_art = $F(nodeboxid + "-config-expand");
    callback(nodeboxid, data);
}


function showBigPic(e)
{
  if ( ! e )
    e = window.event;
  var bigpic = document.getElementById( 'bigpic' );
  if ( bigpic )
    bigpic.style.display = 'block';

  cancelEvent( e );
}

function hideBigPic(e)
{
  if ( ! e )
    var e = window.event;

  var bigpic = document.getElementById( 'bigpic' );
  if ( bigpic )
    bigpic.style.display = 'none';

  cancelEvent( e );
}

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

function show_citysearch_listings(url, div)
{
	ajax_request( url, { onSuccess : function ( x ) { Element.update( div, x.responseText ); }, method : "get" } );
}

function show_krillion_listings(url, div)
{
	ajax_request(url, { onSuccess : function ( x ) { Element.update( div, x.responseText ); }, method : "get" } );
}

function show_blinkx_video(url, div)
{
	ajax_request(url, { onSuccess : function ( x ) { Element.update( div, x.responseText ); }, method : "get" } );
}

function show_oodle_pet_listings(url, div)
{
	ajax_request(url, { onSuccess : function ( x ) { Element.update( div, x.responseText ); }, method : "get" } );

}

function general_ajax_content(url, div)
{
	ajax_request(url, { onSuccess : function ( x ) { Element.update( div, x.responseText ); }, method : "get" } );
}

var flag_judgeCurrent;
var flag_judgeSelected;


var flag_judgePostConflicts =
{
    "brilliant" : [ "clueless" ],
    "clueless" : [ "brilliant" ],
    "touching" : [ "mean" ],
    "mean" : [ "touching" ],
    "agree" : [ "disagree" ],
    "disagree" : [ "agree" ]
};

function flag_judgePost(elem, jid)
{
    if ($('judgediv') == null) return;
    if (jid == flag_judgeCurrent)
    {
        flag_judgePost_closePopup();
        return;
    }

    var pos = Position.absoluteOffset(elem);
    ajax_request("/ajax/flagpost/display", {
        parameters: { ajax: 1, jid: jid },
        onSuccess: function(x) {
            Element.update('judgediv', x.responseText);
            Element.moveTo('judgediv', pos[0] - 140, pos[1] + 20);
            Element.show('judgediv');
            flag_judgeCurrent = jid;
            flag_judgeSelected = [];
        }
    });
}

function flag_judgePost_closePopup()
{
    Element.hide('judgediv');
    flag_judgeCurrent = null;
}


function flag_judgePhoto(elem, jid)
{
    if ($('judgediv') == null) return;
    if (jid == flag_judgeCurrent)
    {
        flag_judgePost_closePopup();
        return;
    }

    var pos = Position.absoluteOffset(elem);
    ajax_request("/ajax/flagphoto/display", {
        parameters: { ajax: 1, jid: jid },
        onSuccess: function(x) {
            Element.update('judgediv', x.responseText);
            Element.moveTo('judgediv', pos[0] - 140, pos[1] + 20);
            Element.show('judgediv');
            flag_judgeCurrent = jid;
            flag_judgeSelected = [];
        }
    });
}

function flag_judgeSubmit(form)
{
    var jitems = $$("#judgediv .judgeitem");
    var selected = [];
    for (var index=0; index<jitems.length; index++)
    {
        var jitem = jitems[index];
        if (Element.hasClassName(jitem, "selected"))
        {
            selected.push(jitem.getAttribute("jtoken"));
        }
    }
    var tokens = selected.join(",");
    form.jtokens.value = tokens;
    ajax_form_request(form, {
        onSuccess: function(x, json) {
            Element.hide('judgediv');
            if (json && json.jid && json.htmlrtn) {
                Element.update("judge-" + json.jid, json.htmlrtn);
                $("judge-" + json.jid).onclick = function() { return false; };
            }
        }
    });
    return false;
}

function del_ad(elem, node, ypid)
{
    var answer = confirm("Are you sure you want to delete this ad?");
    if (!answer) {
	return false;
    }

    ajax_request("/ad/delete/ajax", {
        parameters: { node: node, ypid: ypid },
        onSuccess: function(x) {
            Element.update("editad", x.responseText);
            // var pos = Position.absoluteOffset(elem);
            // Element.moveToWithFit("editaddiv", pos[0] - 50, pos[1] + 20);
            Element.show("editad");
        }
    });
    return false;
}

function unsponsor(ypid, cities_str)
{
    var answer = confirm("Remove sponsorship from these cities?\n" + cities_str);
    if (!answer) {
    return false;
    }
    
    ajax_request("/ajax/yp/unsponsor", {
        parameters: { ypid : ypid },
        onSuccess: function(x) {
            Element.update("unsponsor", x.responseText);
            Element.show("unsponsor");
        }
    });
    return false;
}

function markDeleted(x, key)
{
            // change the "delete" link to "deleted" text
            var del = document.getElementById("del_" + key);
            if (del)
            {
                if (x.responseText == "success")
                {
                    del.innerHTML = "<span class=\"sm\" style=\"color:#aaaaaa\">deleted</span>";
                }
                else
                {
                    del.innerHTML = "<span class=\"sm\" style=\"color:red\">" + x.responseText + "</span>";
                }
            }

            // gray out the image
            var img = document.getElementById("img_" + key);
            if (img)
            {
                img.style.opacity = "0.35";
                img.style.filter = "Alpha(opacity=35)";
                img.style.cursor = "auto";
            }
}

function delete_photo(key, title, callback)
{
    var answer = confirm("Are you sure you want to delete photo entitled '" + title + "'?"); 
    if (!answer) {
        return false;
    }

    var url = "/album/delete/ajax";

    ajax_request(url, {
        method: 'get',
        parameters: {key:key}, 
        onSuccess: function(x){ callback(x, key) }
        });

    return false;
}

function delete_event(delete_url, event_title) {
	var answer = confirm("Are you sure you want to delete the '" + event_title + "' event?");
	if (answer) {
		window.location = delete_url;
	} else {
		return false;
	}
}

function chrome_changecity(form, urltype)
{
    var conduit = 0;
    if(urltype == 'conduitnews')
    {
        conduit = 1;
        urltype = 'news';
    }
    var newLocation;

    $('chrome_changecity_submit').disabled = 'true';
    $('chrome_changecity_submit').value = 'Changing...';
    ajax_form_request(form, {
        onSuccess: function(x, json) {
            if (json.success && json.urls[urltype]) {

                if(conduit == 0)
                {
                    // regular case
                    window.location = json.urls[urltype];
                }
                else
                {
                    RefreshToolbar(); // works in gadget or html component only
                    CloseFloatingWindow();
                }

            }
            else {
                
                var message = json.message || "Could not find a city match.";
                alert(message);
                $('chrome_changecity_submit').disabled = '';
                $('chrome_changecity_submit').value = 'Change It';
            }
        },
        onFailure: function(x, json) {
            alert('Change location failed.');
            $('chrome_changecity_submit').disabled = '';
            $('chrome_changecity_submit').value = 'Change It';
        }
    });  
}


function flag_removeSelected(tag)
{
    if (flag_judgeSelected != null)
    {
        for (var i = 0; i < flag_judgeSelected.length; i++)
        {
            var selectedElem = flag_judgeSelected[i];
            if (tag == selectedElem.getAttribute("judgetype"))
            {
                flag_judgeSelected.splice(i, 1);
                toggleClassSingle(selectedElem, "selected");
                break;
            }
        }
    }
}

function flag_judgeSelect(parent)
{
    var className = "selected";

    if (Element.hasClassName(parent, className))
    {
        var currentTag = parent.getAttribute("judgetype");
        flag_removeSelected(currentTag);
        Element.removeClassName(parent, className);
    }
    else
    {
        // remove any previously selected conflicting items
        if (flag_judgeSelected.length > 0)
        {
            var currentTag = parent.getAttribute("judgetype");
            var conflictArr = flag_judgePostConflicts[currentTag];
            if (conflictArr != null)
            {
                // remove each conflicting tag that has been selected
                for (var i = 0; i < conflictArr.length; i++)
                {
                    flag_removeSelected(conflictArr[i]);
                }
            }
        }

        // if there are still too many selected, remove the earliest
        if (flag_judgeSelected.length >= 3)
        {
            var first = flag_judgeSelected.shift();
            toggleClassSingle(first, className);
        }

        Element.addClassName(parent, className);
        flag_judgeSelected.push(parent);
    }
}


var hiddenSelects = [];
function hideSelects()
{
    var elems = $$("select");
    if (elems == null) return;
    for (var idx=0; idx<elems.length; idx++)
    {
        Element.hide(elems[idx]);
        hiddenSelects.push(elems[idx]);
    }
}

function unhideSelects()
{
    for (var idx=0; idx<hiddenSelects.length; idx++)
    {
        Element.show(hiddenSelects[idx]);
    }
    hiddenSelects = [];
}

function overlay_show(opts)
{
    var dims = Element.getDimensions(document.body);
    hideSelects();
    var bgcolor = "#333333";
    if (opts && opts.backgroundColor) bgcolor = opts.backgroundColor;
    var opacity = 0.8;
    if (opts && opts.opacity) opacity = opts.opacity;
    var ie_opacity = Math.round(opacity * 100);
    var height = dims.height;
    if (height < 1200) height = 1200;
    Element.setStyle('overlaydiv', { height: height + "px", width: dims.width + "px", background: bgcolor,
                                     opacity: opacity, MozOpacity: opacity, filter: "alpha(opacity=" + ie_opacity + ")" });
    Element.show('overlaydiv');
    $('overlaydiv').observe('click', dialog_close);
}

function overlay_hide()
{
    Element.setStyle('overlaydiv', { height: "0px" });
    Element.hide('overlaydiv');
    unhideSelects();
}

var dialog_elem;

function dialog_show(elem, x, y, opts)
{
    if (!$(elem))
        return;
    overlay_show(opts);
    if (!x) x = 100;
    if (!y) y = 50;
    Element.moveToWithFit(elem, x, y);
    Element.show(elem);
    dialog_elem = elem;
}

function dialog_ajax(url, params, xpos, ypos, opts)
{
    if (dialog_elem)
        return;
    if (!xpos) xpos = 200;
    if (!ypos) ypos = 200;
    if (!params)
        params = {};
    if (params.onSuccess)
    {
        var origfn = params.onSuccess;
        params.onSuccess = function(x) { 
            if (origfn(x))
            {
                overlay_show(opts);
                Element.update('dialogdiv', x.responseText); 
                Element.moveToWithFit('dialogdiv', xpos, ypos);
                Element.show('dialogdiv');
                dialog_elem = $('dialogdiv');
            }
        };
    }
    else
    {
        params.onSuccess = function(x) { 
            overlay_show(opts);
            Element.update('dialogdiv', x.responseText); 
            Element.moveTo('dialogdiv', xpos, ypos);
            Element.show('dialogdiv');
            dialog_elem = $('dialogdiv');
        };
    }
    ajax_request(url, params);
}

function dialog_close()
{
    if (dialog_elem)
    {
        Element.hide(dialog_elem);
        dialog_elem = null;
    }
    overlay_hide();
}

function passAgeCheck()
{
    dialog_close();
    ajax_request("/ajax/pass-age-check", {
        onSuccess: function(x) { return; }
    });
}

function failAgeCheck()
{
    ajax_request("/ajax/fail-age-check", {
        onSuccess: function(x) { window.location = "/"; }
    });
}

function show_other_topics(link)
{
	var pos = Position.absoluteOffset ($(link));
	Element.addClassName(link, "active");
	Element.show ('other_topics_menu');	
	Element.moveTo ('other_topics_menu', pos[0] + 1, pos[1] + 18);
}

function hide_other_topics(link)
{
	Element.removeClassName(link, "active");
	Element.hide ('other_topics_menu');	
}

function show_other_topics_sm(link)
{
	var pos = Position.absoluteOffset ($(link));
	Element.addClassName(link, "active");
	Element.show ('other_topics_sm_menu');	
	Element.moveTo ('other_topics_sm_menu', pos[0] + 1, pos[1] + 18);
}

function hide_other_topics_sm(link)
{
	Element.removeClassName(link, "active");
	Element.hide ('other_topics_sm_menu');	
}

function skybox_toggle_pause()
{
    pe.currentlyExecuting = !pe.currentlyExecuting;
    return false;
}

function skybox_play(pe)
{
    if (undefined == pe.i)
    {
        pe.i = 0;
    }
    toggleClass($$('#hat_skybox .skybox_number'), $('skybox_number' + pe.i), 'active');
    Element.show('skybox' + pe.i);

    var next = (pe.i + 1) % 3;
    Element.hide('skybox' + next);

    var prev = (pe.i + 2) % 3;
    Element.hide('skybox' + prev);

    pe.i = next;
    return false;
}

function alerts_unsubscribe_thread(email, threadid, token)
{
    ajax_request("/alerts/unsubscribe-thread", {
        parameters: { email: email, threadid: threadid, token: token },
        onSuccess: function(x) {
            Element.addClassName($("threadrow_" + threadid), "unsubscribed");
            $("sub_" + threadid).hide();
        }
    });
    return false;
}

function alerts_block(email, token)
{
    ajax_request("/alerts/block", {
        parameters: { email: email, token: token },
        onSuccess: function(x) {
            Element.hide("alerts_block");
            Element.show("alerts_unblock");
        }
    });
    return false;
}

function alerts_unblock(email, token)
{
    ajax_request("/alerts/unblock", {
        parameters: { email: email, token: token },
        onSuccess: function(x) {
            Element.show("alerts_block");
            Element.hide("alerts_unblock");
        }
    });
    return false;
}

var yp_flyover_iternum = 0;
var yp_flyover_insubnav = false;
var yp_flyover_inflyover = false;
var yp_flyover_hasfocus = false;

function yp_subnav_mouseover(sn_elem)
{
    yp_flyover_insubnav = true;
    if (Element.visible("subnav_yp_flyover"))
        return;
    var pos = Position.absoluteOffset(sn_elem);
    Element.moveTo("subnav_yp_flyover", pos[0], pos[1] + 33);
    Element.show("subnav_yp_flyover")
}

function yp_subnav_mouseout()
{
    yp_flyover_insubnav = false;
    var iternum = ++yp_flyover_iternum;
    setTimeout(function() { yp_subnav_flyover_tryclose(iternum) }, 1500);
}

function yp_flyover_mouseover()
{
    yp_flyover_inflyover = true;
}

function yp_flyover_mouseout()
{
    yp_flyover_inflyover = false;
    var iternum = ++yp_flyover_iternum;
    setTimeout(function() { yp_subnav_flyover_tryclose(iternum) }, 1500);
}

function yp_flyover_onfocus()
{
    yp_flyover_hasfocus = true;
}

function yp_flyover_onblur()
{
    yp_flyover_hasfocus = false;
    var iternum = ++yp_flyover_iternum;
    setTimeout(function() { yp_subnav_flyover_tryclose(iternum) }, 1500);
}

function yp_subnav_flyover_tryclose(iternum)
{
    if (iternum != yp_flyover_iternum)
        return;
    if (!yp_flyover_insubnav && !yp_flyover_inflyover && !yp_flyover_hasfocus)
    {
        Element.hide("subnav_yp_flyover");
        return;
    }
}

function inc_yp_pvs(type, ypids)
{
    ajax_request("/ajax/yp/pv", { parameters: { type : type, ypids : ypids } });
}

function open_feedback(url_prefix, params)
{
	var js_url = escape(document.location.href);
	var js_title = document.title;
	var window_params ='/ext/feedback?clicked_page=' + js_url + '&page_title=' + js_title;

    if (params)
    {
        for (var key in params)
        {
            window_params += '&';
            window_params += key;
            window_params += '=';
            window_params += params[key];
        }
    }

    if (url_prefix)
    {
        window_params = url_prefix + window_params;
    }
	var new_window = window.open(window_params, 'feedback', 'width=660, height=680, scrollbars=yes, resizable=yes');
	new_window.focus();
}

function open_new_window (href) {
	var newWindow = window.open(href, '_blank' );
	newWindow.focus();
	return false;
}

function show_intad(intad)
{
    var intdiv = $('intaddiv');
    if (!intdiv)
        return;
    Element.remove(intdiv);
    $('dialogdiv').appendChild(intdiv);
    Element.show(intdiv);
    var vpwidth = Client.viewportWidth();
    var xpos = (vpwidth - (intad.width + 20)) / 2;
    dialog_show('dialogdiv', xpos, 100);
    setTimeout(function() { dialog_close(); }, intad.timeout);
}

function render_intad(intad)
{
    document.write("<div id=\"intaddiv\" style=\"padding:10px;text-align:middle;background-color:white;position:absolute;z-index:110;border:5px solid black;width:" + (intad.width + 20) + "px;height:" + (intad.height + 30) + "px;display:none\">");
    if (intad.pvimg && intad.pvimg.length > 0)
        document.write("<img width=\"1\" height=\"1\" src=\"" + intad.pvimg + "\"/>");
    if (intad.pvimg2 && intad.pvimg2.length > 0)
        document.write("<img width=\"1\" height=\"1\" src=\"" + intad.pvimg2 + "\"/>");
    document.write("<div id=\"intadclose\" onclick=\"dialog_close(); return false;\" style=\"float:right;cursor:pointer;margin-top:-8px;font-weight:bold;padding:3px\">CLOSE <img src=\"http://topix.cachefly.net/pics/button_close.png\" style=\"border:none;vertical-align:middle;margin-bottom:4px\"/></div>");
    document.write("<div class=\"divclear\"></div>");
    document.write(intad.creative);
    setTimeout(function() { show_intad(intad) }, 2000);
}

function dfp_intad(intad)
{
    intadhook1 = function() { render_intad(intad); };
    intadhook2 = function() { document.write("</div>"); };
}


function postNew()
{
    document.forms['commentForm'].submit();
    return false;
}

function quotePost(postid)
{
    document.forms['commentForm'].quotepostid.value = postid;
    document.forms['commentForm'].submit(); 
    return false;
}

function issue_vote(node, issue, vote, successfn)
{
    ajax_request("/ajax/issue/vote", {
        parameters: { node: node, issue: issue, vote: vote },
        onSuccess: successfn
    });
}

function issue_clickSupport()
{
    var vtc = $('votediv');
    Element.addClassName(Element.down(vtc, "#vote-support"), "highlighted");
    Element.removeClassName(Element.down(vtc, "#vote-oppose"), "highlighted");
    Element.down(vtc, "input[name=vote]").value = "support";
}

function issue_clickOppose()
{
    var vtc = $('votediv');
    Element.addClassName(Element.down(vtc, "#vote-oppose"), "highlighted");
    Element.removeClassName(Element.down(vtc, "#vote-support"), "highlighted");
    Element.down(vtc, "input[name=vote]").value = "oppose";
}

var numbers_str = [ "", "1st", "2nd", "3rd", "4th", "5th" ];

var issue_errmsg_map = {
    "captcha.missing": "Please type in the numbers you see in the image.",
    "captcha": "The numbers you typed in did not match the nubmers in the image.  Please try again.",
    "input.comments": "There was a problem processing your comments.  Please try again.",
    "input.sn": "Invalid or reserved name.  Please enter a different name to post your comment.",
    "profanity.allcaps": "Posting in all caps makes your posts harder to read and is discouraged.",
    "profanity.profanity": "Please refrain from posting profanity or offensive material.  <b>Be polite</b>. Inappropriate posts may be removed by the moderator.",
    "protocol.userauth": "Bad screename or user.  Did you maybe just logout?  Refreshing the page should fix the problem.",
    "protocol.nonce": "Something went wrong processing your comment.  Did you maybe just logout?  Please refresh the page and try again."
};
var issue_defaulterrmsg = "Something went wrong while trying to process your vote.  Please refresh the page and try again.";

var vote_data;

function issue_spawnVoteWindow(initialvote, node, issue_threadid)
{
    if (initialvote == "support")
        issue_clickSupport();
    if (initialvote == "oppose")
        issue_clickOppose();
    var votediv = $('votediv');
    var errdiv = Element.down(votediv, '#vote_errdiv');
    Element.down(votediv, "textarea[name=comments]").value = "";
    Element.down(votediv, "input[name=captcha]").value = "";
    Element.down(votediv, "input[name=node]").value = node;
    Element.down(votediv, "input[name=threadid]").value = issue_threadid;
    errdiv.innerHTML = "";
    var captchaimg = $('vote-captchaimg');
    
    vote_data = {};
    ajax_request("/ajax/forum/forum-captcha", {
        parameters: { ajax: 1, threadid: issue_threadid, node: node },
        onSuccess: function(x, json) {
            if (!json) {
                errdiv.innerHTML = issue_defaulterrmsg;
                return;
            }
            if (json.errorstr) {
                errdiv.innerHTML = json.errorstr;
                return;
            }
            if (json.captcha_params) {
                vote_data.timestamp = json.timestamp;
                vote_data.nonce = json.nonce;
                vote_data.captcha_params = json.captcha_params;
                vote_data.num_errors = 0;
                captchaimg.src = vote_data.captcha_params.captcha_url;
                dialog_show('votediv', 200 + Client.scrollLeft(), 200 + Client.scrollTop());
            }
        }
    });
}


function issue_verifyAndSubmit(issueform, votedonefn)
{
    var commentinput = Element.down(issueform, 'textarea[name=comments]');
    if (commentinput.value && commentinput.value.length > 1 && !commentinput.value.match(/^\s+$/)) {
        issue_verifyAndSubmitWithComment(issueform, votedonefn);
        return;
    }
    var vote = Element.down(issueform, 'input[name=vote]').value;
    var issue = Element.down(issueform, 'input[name=issue]').value;
    var node = Element.down(issueform, 'input[name=node]').value;
    issue_vote(node, issue, vote, votedonefn);
}

function issue_verifyAndSubmitWithComment(issueform, votedonefn)
{
    var cvalue = Element.down(issueform, "input[name=captcha]").value;
    var errdiv = Element.down(issueform, "#vote_errdiv");
    var commentvalue = Element.down(issueform, "textarea[name=comments]").value;
    var snelem = Element.down(issueform, "input[name=screenname]");
    var namevalue;
    if (snelem)
        namevalue = snelem.value;
    var issue_threadid = Element.down(issueform, "input[name=threadid]").value;
    var nodevalue = Element.down(issueform, "input[name=node]").value;

    if (snelem && (!namevalue || namevalue.length == 0)) {
        errdiv.innerHTML = "Name cannot be blank.";
        return;
    }
    if (!commentvalue || commentvalue.length == 0) {
        errdiv.innerHTML = "Comment cannot be blank.";
        return;
    }
    ajax_request("/ajax/forum/forum-captcha-check", {
        parameters: { nonce: vote_data.nonce, node: nodevalue, timestamp: vote_data.timestamp,
                      threadid: issue_threadid, captchavalue: cvalue },
        onSuccess: function(x, json) {
            if (!json || !json.captchaok) {
                vote_data.num_errors++;
                if (vote_data.num_errors > 3) {
                    alert("Sorry, we were unable to process your vote.  If you are unable to see the numbers, please refresh the page and try again.");
                    dialog_close();
                }
                errdiv.innerHTML = "The numbers you typed in did not match the numbers in the image.  Please try again. (" + numbers_str[vote_data.num_errors] + " try)";
                return;
            }
            issue_submitComment(issueform, votedonefn);
        }
    });
}

function issue_submitComment(issueform, votedonefn)
{
    var cvalue = Element.down(issueform, "input[name=captcha]").value;
    var errdiv = Element.down(issueform, "#vote_errdiv");
    var commentvalue = Element.down(issueform, "textarea[name=comments]").value;
    var namevalue;
    var snelem = Element.down(issueform, "input[name=screenname]");
    if (snelem)
        namevalue = snelem.value;
    var issue = Element.down(issueform, "input[name=issue]").value;
    var vote = Element.down(issueform, "input[name=vote]").value;
    var subject = Element.down(issueform, "input[name=subject]").value;
    var issue_threadid = Element.down(issueform, "input[name=threadid]").value;
    var node = Element.down(issueform, "input[name=node]").value;

    ajax_request("/ajax/forum/forum-post", {
        parameters: {
            node: node,
            nonce: vote_data.nonce,
            timestamp: vote_data.timestamp,
            threadid: issue_threadid,
            captchavalue: cvalue,
            comments: commentvalue,
            anon_sn: namevalue,
            issue: issue,
            warned: vote_data.warned,
            issuevote: vote,
            subject: subject
        },
        onSuccess: function(x, json) {
            if (json && json.success) {
                issue_vote(node, issue, vote, votedonefn);
                return;
            }
            if (json && json.errorcode) {
                if (!json.errormsg)
                    json.errormsg = issue_errmsg_map[json.errorcode];
                if (!json.errormsg)
                    json.errormsg = issue_defaulterrmsg;
                errdiv.innerHTML = json.errormsg;
                if (json.setwarned)
                    vote_data.warned = 1;
                if (json.errorcode && json.errorcode.match(/^(protocol|captcha)/)) {
                    alert('There was a problem submitting your comment.  Please refresh the page and try again');
                    dialog_close();
                }
            }
            else {
                errdiv.innerHTML = "Bad Response from the server.  Please try again.";
            }
        }
    });
}

function conduit_refresh()
{
    try 
    { 
        // Only works when called from the main browser page
        RefreshToolbarByCTID("CT2325934"); 
    } 
    catch(e) {}
}

function topixconduit_closeAfterClick()
{
    setTimeout(function() { CloseFloatingWindow(); }, 2000);
    return true;
}


function topix_loginDisplay(loginDisplay, forgotPasswordDisplay)
{
    var loginDiv = document.getElementById("loginDiv");
    if (loginDiv)
    {
        loginDiv.style.display = loginDisplay;
    }
    var forgotPasswordDiv = document.getElementById("forgotPasswordDiv");
    if (forgotPasswordDiv)
    {
        forgotPasswordDiv.style.display = forgotPasswordDisplay;
    }
    return false;
}

function topix_switchAuth(newOption)
{
    var ids = new Array('at_cookie', 'at_anon', 'at_reg', 'at_login');
    var id = "at_" + newOption;

    // var vis = document.getElementById("visible_tr").style.display;
    var vis = "block";

    for (var i = 0; i < ids.length; i++)
    {
        vis = document.getElementById(ids[i]).style.display;
        if (vis != "none")
        {
            break;
        }
    }
    
	for (var i = 0; i < ids.length; i++)
    {
        if (ids[i] == id)
        {
            topix_setVis(ids[i], vis);
        }
        else
        {
            topix_setVis(ids[i], 'none');
        }
	}
    document.getElementById("authtype_field").value = newOption;
    return false;
}


function topix_forgotPassword(emailFieldId)
{
    var emailElem = document.getElementById(emailFieldId);
    var url = "/member/forgotPassword?login_email=" + escape(emailElem.value);

    sendUrl(url, function(x) {
        if (x.responseText == "success")
        {
            var elem = document.getElementById("forgotPassword");
            elem.innerHTML = "<p style=\"margin-bottom:10px; border:1px solid #f90; background-color:#ffc; padding:4px;\"><strong>Check your Email: </strong>We have sent you an email with instructions on how to reset your password.</p>";
        }
        else
        {
            var elem = document.getElementById("forgotPasswordError");
            elem.innerHTML = x.responseText;
        }
    });
    return false;
}

function topix_setVis(id, visibility)
{
    var elem = document.getElementById(id);
    if (elem)
    {
        elem.style.display = visibility;
    }
}

var art_post_data = new Array();
var ini_post_data = new Array();
var art_click_data = new Array();
var disable_trackArtClick;

function news_trackArtClick(url)
{
    if (!art_click_data[url])
    {
        art_click_data[url] = 1;

        if (disable_trackArtClick)
        {
            return;
        }

        ajax_request("/ajax/track-art-click", {
            parameters: { ajax: 1, url: url, node: page_node }
        });
    }
}

function news_clickHeadline(storyid)
{
    var storyelem = $("storyid-" + storyid);
    news_trackArtClick(storyelem.down("a.headline").href);
    news_toggleHighlightStory(storyid);
    return false;
}

function news_clickFullStory(storyid)
{
    var storyelem = $("storyid-" + storyid);
    news_trackArtClick(storyelem.down("a.headline").href);
    news_highlightStory(storyid);
    return true;
}

function news_verifyAndSpawnCaptcha(storyid, node, threadid)
{
    var storyelem = $('storyid-' + storyid);
    $('storyid-input').value = storyid;
    var errdiv = storyelem.down(".comment_error");
    errdiv.innerHTML = "";  // clear the errors
    var post_data = art_post_data[storyid];
    var asdiv = storyelem.down("input[name=anon_sn]");
    if (asdiv) {
        post_data.anon_sn = asdiv.value;
        if (post_data.anon_sn.length == 0) {
            errdiv.innerHTML = "Name cannot be blank.";
            return false;
        }
        if (!post_data.anon_sn.match(/[a-z].*[a-z].*[a-z]/i)) {
            errdiv.innerHTML = "Name must container at least 3 letters (a-z).";
            return false;
        }
        if (post_data.anon_sn.length > 25) {
            errdiv.innerHTML = "Name can only be 25 characters long (currently " + post_data.anon_sn.length + " characters).";
            return false;
        }
        if (!post_data.anon_sn.match(/^[a-z0-9_ -]+$/i)) {
            errdiv.innerHTML = "Name must only contain alphanumerics, underscore, or dash.";
            return false;
        }
    }

    post_data.comments = storyelem.down("textarea[name=comments]").value;
    if (post_data.comments.length == 0) {
        errdiv.innerHTML = "Comment cannot be blank.";
        return false;
    }

    storyelem.down("input[name=commentsubmit]").disabled = 1;

    if (post_data.captchaok) {
        news_submitPost(storyid);
        return;
    }

    ajax_request("/ajax/forum/forum-captcha", {
        parameters: { ajax: 1, threadid: post_data.threadid, node: page_node },
        onSuccess: function(x, json) {
            storyelem.down("input[name=commentsubmit]").disabled = 0;
            if (!json) {
                errdiv.innerHTML = errmsg_default;
                return;
            }
            if (json.errorstr) {
                errdiv.innerHTML = json.errorstr;
                return;
            }
            if (json.captcha_params) {
                post_data.timestamp = json.timestamp;
                post_data.nonce = json.nonce;
                post_data.captcha_params = json.captcha_params;
                news_spawnCaptcha(storyid);
            }
        }
    });
}

function news_spawnCaptcha(storyid)
{
    var post_data = art_post_data[storyid];
    $('captchaerror').innerHTML = "";
    $('captchasubmit').disabled = 0;
    $('captchainput').value = "";
    $('captchaimg').src = post_data.captcha_params.captcha_url;
    dialog_show('captchadiv', 200 + Client.scrollLeft(), 200 + Client.scrollTop());
    $('captchainput').focus();
}

function news_submitCaptcha()
{
    var storyid = $('storyid-input').value;
    var storyelem = $('storyid-' + storyid);
    var post_data = art_post_data[storyid];
    var captchavalue = $('captchainput').value;
    $('captchasubmit').disabled = 1;
    post_data.captchavalue = captchavalue;
    sendClickEvent("newspage/captcha-submit", storyelem.down("a.headline").href);
    ajax_request("/ajax/forum/forum-captcha-check", {
        parameters: { nonce: post_data.nonce, node: page_node, timestamp: post_data.timestamp,
                      threadid: post_data.threadid, captchavalue: captchavalue },
        onSuccess: function(x, json) {
            $('captchasubmit').disabled = 0;
            if (json && json.captchaok) {
                post_data.captchaok = 1;
                dialog_close();
                news_submitPost(storyid);
            }
            else {
                if (!post_data.captchaerror)
                    post_data.captchaerror = 0;
                post_data.captchaerror++;
                if (post_data.captchaerror >= 3) {
                    art_post_data[storyid] = Object.clone(ini_post_data[storyid]);
                    storyelem.down('.comment_error').innerHTML = news_errmsg_map["captcha"];
                    dialog_close();
                }
                $('captchaerror').innerHTML = "The characters are incorrect.  Please try again.";
            }
        }
    });
    return false;
}

var errmsg_map = {
    "protocol.nonce": "Something went wrong processing your comment.  Did you maybe just logout?  Please refresh the page and try again.",
    "protocol.art": "There is a problem with the article you are trying to comment on.  Please refresh the page and try again.",
    "protocol.userauth": "Bad screename or user.  Did you maybe just logout?  Refreshing the page should fix the problem.",
    "captcha": "The numbers you typed in did not match the nubmers in the image.  Please try again.",
    "input.comments": "There was a problem processing your comments.  Please try again.",
    "input.sn": "Invalid or reserved name.  Please enter a different name to post your comment.",
    "profanity.allcaps": "Posting in all caps makes your posts harder to read and is discouraged.",
    "profanity.profanity": "Please refrain from posting profanity or offensive material.  <b>Be polite</b>. Inappropriate posts may be removed by the moderator."
};
var errmsg_default = "Something went wrong processing your comment.  Please refresh the page and try again.";

function news_submitPost(storyid)
{
    var storyelem = $("storyid-" + storyid);
    var post_data = art_post_data[storyid];
    var errdiv = storyelem.down(".comment_error");
    ajax_request("/ajax/forum/forum-post", {
        parameters: { 
            node: page_node,
            nonce: post_data.nonce,
            timestamp: post_data.timestamp,
            threadid: post_data.threadid,
            captchavalue: post_data.captchavalue,
            comments: post_data.comments,
            anon_sn: post_data.anon_sn,
            artstruct: post_data.artstruct,
            warned: post_data.warned
        },
        onSuccess: function(x, json) {
            storyelem.down("input[name=commentsubmit]").disabled = 0;
            if (json && json.success) {
                news_instantiateThanksForm(storyid);
                Element.addClassName('storyid-' + storyid, "doneposting");
            }
            else if (json && json.errorcode) {
                if (!json.errormsg)
                    json.errormsg = news_errmsg_map[json.errorcode];
                if (!json.errormsg)
                    json.errormsg = errmsg_default;
                errdiv.innerHTML = json.errormsg;
                if (json.setwarned)
                    post_data.warned = 1;
                if (json.errorcode && json.errorcode.match(/^(protocol|captcha)/))
                    art_post_data[storyid] = Object.clone(ini_post_data[storyid]);
            }
            else {
                errdiv.innerHTML = "Bad response from the server.  Please try again.";
            }
        }
    });
    return;
}

function news_subscribeThread(storyid)
{
    var post_data = art_post_data[storyid];
    $('forumalert-threadid').value = post_data.threadid;
    $('forumalert-artstruct').value = post_data.artstruct;
    $('forumalert-form').submit();
    return false;
}

function news_highlightStory(storyid)
{
    var storyelem = $('storyid-' + storyid);
    news_instantiatePostForm(storyid);
    Element.addClassName(storyelem, 'highlighted');
    var ta = storyelem.down(".postarea").down("textarea");
    if (ta)
        ta.focus();
    return false;
}

function news_toggleHighlightStory(storyid)
{
    var storyelem = $('storyid-' + storyid);
    news_instantiatePostForm(storyid);
    toggleClassSingle(storyelem, 'highlighted');
    if (Element.hasClassName(storyelem, 'highlighted'))
    {
        var ta = storyelem.down(".postarea").down("textarea");
        if (ta)
            ta.focus();
    }
    return false;
}

function news_instantiatePostForm(storyid)
{
    var storyelem = $('storyid-' + storyid);
    var postarea = storyelem.down('.postarea');
    if (postarea.down("form"))
        return;
    var postformhtml = $('proto-postform').innerHTML;
    postarea.innerHTML = postformhtml;
    addLinkTrackerToContainer(postarea);
    postarea.down("form").onsubmit = function() { 
        sendClickEvent("newspage/post-comment", storyelem.down("a.headline").href);
        news_verifyAndSpawnCaptcha(storyid); return false;
    };

    postarea.down("textarea").tabIndex = storyid * 5;
    if (postarea.down("input[name=anon_sn]"))
        postarea.down("input[name=anon_sn]").tabIndex = (storyid * 5) + 1;
    postarea.down("input[name=commentsubmit]").tabIndex = (storyid * 5) + 2;
}

function news_instantiateThanksForm(storyid)
{
    var storyelem = $('storyid-' + storyid);
    var thanksarea = storyelem.down('.thanksarea');
    if (thanksarea.down("a"))
        return;
    var thanksareahtml = $('proto-thanksarea').innerHTML;
    thanksarea.innerHTML = thanksareahtml;
    addLinkTrackerToContainer(thanksarea);

    var post_data = art_post_data[storyid];
    thanksarea.down("a.thanksforumalertlink").onclick = function() { return news_subscribeThread(storyid); };
    
    thanksarea.down("a.thanksthreadlink").href = "/forum/" + page_nnode + "/T" + post_data.threadid;
}
