Changeset 2424

Show
Ignore:
Timestamp:
07/06/08 00:13:28 (5 years ago)
Author:
rvelices
Message:

- feature 832: Folders with no images sql optimization
- recent pics are sorted by date descending, and only then by conforder_by
- updated prototype.js to version 1.6

Location:
trunk
Files:
4 modified

Legend:

Unmodified
Added
Removed
  • trunk/include/category_cats.inc.php

    r2330 r2424  
    8989  else if ($conf['allow_random_representative']) 
    9090  {// searching a random representant among elements in sub-categories 
    91     $query = ' 
     91    if ($row['count_images']>0) 
     92    { 
     93      $query = ' 
    9294SELECT image_id 
    9395  FROM '.CATEGORIES_TABLE.' AS c INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic 
    9496    ON ic.category_id = c.id'; 
    95     $query.= ' 
     97      $query.= ' 
    9698  WHERE (c.id='.$row['id'].' OR uppercats LIKE \''.$row['uppercats'].',%\')' 
    97   .get_sql_condition_FandF 
    98   ( 
    99     array 
    100       ( 
    101         'forbidden_categories' => 'c.id', 
    102         'visible_categories' => 'c.id', 
    103         'visible_images' => 'image_id' 
    104       ), 
    105     "\n  AND" 
    106   ).' 
     99    .get_sql_condition_FandF 
     100    ( 
     101      array 
     102        ( 
     103          'forbidden_categories' => 'c.id', 
     104          'visible_categories' => 'c.id', 
     105          'visible_images' => 'image_id' 
     106        ), 
     107      "\n  AND" 
     108    ).' 
    107109  ORDER BY RAND() 
    108110  LIMIT 0,1 
    109111;'; 
    110     $subresult = pwg_query($query); 
    111     if (mysql_num_rows($subresult) > 0) 
    112     { 
    113       list($image_id) = mysql_fetch_row($subresult); 
     112      $subresult = pwg_query($query); 
     113      if (mysql_num_rows($subresult) > 0) 
     114      { 
     115        list($image_id) = mysql_fetch_row($subresult); 
     116      } 
    114117    } 
    115118  } 
    116119  else 
    117120  { // searching a random representant among representant of sub-categories 
    118     $query = ' 
    119 SELECT representative_picture_id 
    120   FROM '.CATEGORIES_TABLE.' INNER JOIN '.USER_CACHE_CATEGORIES_TABLE.' 
    121   ON id = cat_id and user_id = '.$user['id'].' 
    122   WHERE uppercats LIKE \''.$row['uppercats'].',%\' 
    123     AND representative_picture_id IS NOT NULL' 
    124   .get_sql_condition_FandF 
    125   ( 
    126     array 
    127       ( 
    128         'visible_categories' => 'id', 
    129       ), 
    130     "\n  AND" 
    131   ).' 
    132   ORDER BY RAND() 
    133   LIMIT 0,1 
    134 ;'; 
    135     $subresult = pwg_query($query); 
    136     if (mysql_num_rows($subresult) > 0) 
    137     { 
    138       list($image_id) = mysql_fetch_row($subresult); 
     121    if ($row['count_categories']>0 and $row['count_images']>0) 
     122    { 
     123      $query = ' 
     124  SELECT representative_picture_id 
     125    FROM '.CATEGORIES_TABLE.' INNER JOIN '.USER_CACHE_CATEGORIES_TABLE.' 
     126    ON id = cat_id and user_id = '.$user['id'].' 
     127    WHERE uppercats LIKE \''.$row['uppercats'].',%\' 
     128      AND representative_picture_id IS NOT NULL' 
     129    .get_sql_condition_FandF 
     130    ( 
     131      array 
     132        ( 
     133          'visible_categories' => 'id', 
     134        ), 
     135      "\n  AND" 
     136    ).' 
     137    ORDER BY RAND() 
     138    LIMIT 0,1 
     139  ;'; 
     140      $subresult = pwg_query($query); 
     141      if (mysql_num_rows($subresult) > 0) 
     142      { 
     143        list($image_id) = mysql_fetch_row($subresult); 
     144      } 
    139145    } 
    140146  } 
  • trunk/include/section_init.inc.php

    r2327 r2424  
    236236      { // get all allowed sub-categories 
    237237        $query = ' 
    238 SELECT id  
     238SELECT id 
    239239  FROM '.CATEGORIES_TABLE.' 
    240   WHERE  
     240  WHERE 
    241241    uppercats LIKE "'.$page['category']['uppercats'].',%" ' 
    242242    .get_sql_condition_FandF( 
     
    395395  else if ($page['section'] == 'recent_pics') 
    396396  { 
     397    if ( !isset($page['super_order_by']) ) 
     398    { 
     399      $conf['order_by'] = str_replace( 
     400        'ORDER BY ', 
     401        'ORDER BY date_available DESC,', 
     402        $conf['order_by'] 
     403        ); 
     404    } 
     405 
    397406    $query = ' 
    398407SELECT DISTINCT(id) 
     
    524533  WHERE file LIKE "' . $page['image_file'] . '.%" ESCAPE "|"'; 
    525534    if ( count($page['items']) < 500) 
    526     {// for very large item sets do not add IN - because slow  
     535    {// for very large item sets do not add IN - because slow 
    527536      $query .= ' 
    528537  AND id IN ('.implode(',',$page['items']).') 
     
    534543      case 0: break; 
    535544      case 1: 
    536         list($page['image_id'], $page['image_file']) = mysql_fetch_row($result);  
     545        list($page['image_id'], $page['image_file']) = mysql_fetch_row($result); 
    537546        break; 
    538547      default: // more than 1 file name match 
  • trunk/tools/prototype.js

    r2107 r2424  
    1 /*  Prototype JavaScript framework, version 1.5.1.1 
    2  *  (c) 2005-2007 Sam Stephenson 
     1/*  Prototype JavaScript framework, version 1.6.0.2 
     2 *  (c) 2005-2008 Sam Stephenson 
    33 * 
    44 *  Prototype is freely distributable under the terms of an MIT-style license. 
    55 *  For details, see the Prototype web site: http://www.prototypejs.org/ 
    66 * 
     7 *--------------------------------------------------------------------------*/ 
     8 
     9var Prototype = { 
     10  Version: '1.6.0.2', 
     11 
     12  Browser: { 
     13    IE:     !!(window.attachEvent && !window.opera), 
     14    Opera:  !!window.opera, 
     15    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, 
     16    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1, 
     17    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) 
     18  }, 
     19 
     20  BrowserFeatures: { 
     21    XPath: !!document.evaluate, 
     22    ElementExtensions: !!window.HTMLElement, 
     23    SpecificElementExtensions: 
     24      document.createElement('div').__proto__ && 
     25      document.createElement('div').__proto__ !== 
     26        document.createElement('form').__proto__ 
     27  }, 
     28 
     29  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>', 
     30  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, 
     31 
     32  emptyFunction: function() { }, 
     33  K: function(x) { return x } 
     34}; 
     35 
     36if (Prototype.Browser.MobileSafari) 
     37  Prototype.BrowserFeatures.SpecificElementExtensions = false; 
     38 
     39 
     40/* Based on Alex Arnell's inheritance implementation. */ 
     41var Class = { 
     42  create: function() { 
     43    var parent = null, properties = $A(arguments); 
     44    if (Object.isFunction(properties[0])) 
     45      parent = properties.shift(); 
     46 
     47    function klass() { 
     48      this.initialize.apply(this, arguments); 
     49    } 
     50 
     51    Object.extend(klass, Class.Methods); 
     52    klass.superclass = parent; 
     53    klass.subclasses = []; 
     54 
     55    if (parent) { 
     56      var subclass = function() { }; 
     57      subclass.prototype = parent.prototype; 
     58      klass.prototype = new subclass; 
     59      parent.subclasses.push(klass); 
     60    } 
     61 
     62    for (var i = 0; i < properties.length; i++) 
     63      klass.addMethods(properties[i]); 
     64 
     65    if (!klass.prototype.initialize) 
     66      klass.prototype.initialize = Prototype.emptyFunction; 
     67 
     68    klass.prototype.constructor = klass; 
     69 
     70    return klass; 
     71  } 
     72}; 
     73 
     74Class.Methods = { 
     75  addMethods: function(source) { 
     76    var ancestor   = this.superclass && this.superclass.prototype; 
     77    var properties = Object.keys(source); 
     78 
     79    if (!Object.keys({ toString: true }).length) 
     80      properties.push("toString", "valueOf"); 
     81 
     82    for (var i = 0, length = properties.length; i < length; i++) { 
     83      var property = properties[i], value = source[property]; 
     84      if (ancestor && Object.isFunction(value) && 
     85          value.argumentNames().first() == "$super") { 
     86        var method = value, value = Object.extend((function(m) { 
     87          return function() { return ancestor[m].apply(this, arguments) }; 
     88        })(property).wrap(method), { 
     89          valueOf:  function() { return method }, 
     90          toString: function() { return method.toString() } 
     91        }); 
     92      } 
     93      this.prototype[property] = value; 
     94    } 
     95 
     96    return this; 
     97  } 
     98}; 
     99 
     100var Abstract = { }; 
     101 
     102Object.extend = function(destination, source) { 
     103  for (var property in source) 
     104    destination[property] = source[property]; 
     105  return destination; 
     106}; 
     107 
     108Object.extend(Object, { 
     109  inspect: function(object) { 
     110    try { 
     111      if (Object.isUndefined(object)) return 'undefined'; 
     112      if (object === null) return 'null'; 
     113      return object.inspect ? object.inspect() : String(object); 
     114    } catch (e) { 
     115      if (e instanceof RangeError) return '...'; 
     116      throw e; 
     117    } 
     118  }, 
     119 
     120  toJSON: function(object) { 
     121    var type = typeof object; 
     122    switch (type) { 
     123      case 'undefined': 
     124      case 'function': 
     125      case 'unknown': return; 
     126      case 'boolean': return object.toString(); 
     127    } 
     128 
     129    if (object === null) return 'null'; 
     130    if (object.toJSON) return object.toJSON(); 
     131    if (Object.isElement(object)) return; 
     132 
     133    var results = []; 
     134    for (var property in object) { 
     135      var value = Object.toJSON(object[property]); 
     136      if (!Object.isUndefined(value)) 
     137        results.push(property.toJSON() + ': ' + value); 
     138    } 
     139 
     140    return '{' + results.join(', ') + '}'; 
     141  }, 
     142 
     143  toQueryString: function(object) { 
     144    return $H(object).toQueryString(); 
     145  }, 
     146 
     147  toHTML: function(object) { 
     148    return object && object.toHTML ? object.toHTML() : String.interpret(object); 
     149  }, 
     150 
     151  keys: function(object) { 
     152    var keys = []; 
     153    for (var property in object) 
     154      keys.push(property); 
     155    return keys; 
     156  }, 
     157 
     158  values: function(object) { 
     159    var values = []; 
     160    for (var property in object) 
     161      values.push(object[property]); 
     162    return values; 
     163  }, 
     164 
     165  clone: function(object) { 
     166    return Object.extend({ }, object); 
     167  }, 
     168 
     169  isElement: function(object) { 
     170    return object && object.nodeType == 1; 
     171  }, 
     172 
     173  isArray: function(object) { 
     174    return object != null && typeof object == "object" && 
     175      'splice' in object && 'join' in object; 
     176  }, 
     177 
     178  isHash: function(object) { 
     179    return object instanceof Hash; 
     180  }, 
     181 
     182  isFunction: function(object) { 
     183    return typeof object == "function"; 
     184  }, 
     185 
     186  isString: function(object) { 
     187    return typeof object == "string"; 
     188  }, 
     189 
     190  isNumber: function(object) { 
     191    return typeof object == "number"; 
     192  }, 
     193 
     194  isUndefined: function(object) { 
     195    return typeof object == "undefined"; 
     196  } 
     197}); 
     198 
     199Object.extend(Function.prototype, { 
     200  argumentNames: function() { 
     201    var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip"); 
     202    return names.length == 1 && !names[0] ? [] : names; 
     203  }, 
     204 
     205  bind: function() { 
     206    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; 
     207    var __method = this, args = $A(arguments), object = args.shift(); 
     208    return function() { 
     209      return __method.apply(object, args.concat($A(arguments))); 
     210    } 
     211  }, 
     212 
     213  bindAsEventListener: function() { 
     214    var __method = this, args = $A(arguments), object = args.shift(); 
     215    return function(event) { 
     216      return __method.apply(object, [event || window.event].concat(args)); 
     217    } 
     218  }, 
     219 
     220  curry: function() { 
     221    if (!arguments.length) return this; 
     222    var __method = this, args = $A(arguments); 
     223    return function() { 
     224      return __method.apply(this, args.concat($A(arguments))); 
     225    } 
     226  }, 
     227 
     228  delay: function() { 
     229    var __method = this, args = $A(arguments), timeout = args.shift() * 1000; 
     230    return window.setTimeout(function() { 
     231      return __method.apply(__method, args); 
     232    }, timeout); 
     233  }, 
     234 
     235  wrap: function(wrapper) { 
     236    var __method = this; 
     237    return function() { 
     238      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); 
     239    } 
     240  }, 
     241 
     242  methodize: function() { 
     243    if (this._methodized) return this._methodized; 
     244    var __method = this; 
     245    return this._methodized = function() { 
     246      return __method.apply(null, [this].concat($A(arguments))); 
     247    }; 
     248  } 
     249}); 
     250 
     251Function.prototype.defer = Function.prototype.delay.curry(0.01); 
     252 
     253Date.prototype.toJSON = function() { 
     254  return '"' + this.getUTCFullYear() + '-' + 
     255    (this.getUTCMonth() + 1).toPaddedString(2) + '-' + 
     256    this.getUTCDate().toPaddedString(2) + 'T' + 
     257    this.getUTCHours().toPaddedString(2) + ':' + 
     258    this.getUTCMinutes().toPaddedString(2) + ':' + 
     259    this.getUTCSeconds().toPaddedString(2) + 'Z"'; 
     260}; 
     261 
     262var Try = { 
     263  these: function() { 
     264    var returnValue; 
     265 
     266    for (var i = 0, length = arguments.length; i < length; i++) { 
     267      var lambda = arguments[i]; 
     268      try { 
     269        returnValue = lambda(); 
     270        break; 
     271      } catch (e) { } 
     272    } 
     273 
     274    return returnValue; 
     275  } 
     276}; 
     277 
     278RegExp.prototype.match = RegExp.prototype.test; 
     279 
     280RegExp.escape = function(str) { 
     281  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); 
     282}; 
     283 
    7284/*--------------------------------------------------------------------------*/ 
    8285 
    9 var Prototype = { 
    10         Version: '1.5.1.1', 
    11  
    12         Browser: { 
    13                 IE:     !!(window.attachEvent && !window.opera), 
    14                 Opera:  !!window.opera, 
    15                 WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, 
    16                 Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1 
    17         }, 
    18  
    19         BrowserFeatures: { 
    20                 XPath: !!document.evaluate, 
    21                 ElementExtensions: !!window.HTMLElement, 
    22                 SpecificElementExtensions: 
    23                         (document.createElement('div').__proto__ !== 
    24                          document.createElement('form').__proto__) 
    25         }, 
    26  
    27         ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>', 
    28         JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, 
    29  
    30         emptyFunction: function() { }, 
    31         K: function(x) { return x } 
     286var PeriodicalExecuter = Class.create({ 
     287  initialize: function(callback, frequency) { 
     288    this.callback = callback; 
     289    this.frequency = frequency; 
     290    this.currentlyExecuting = false; 
     291 
     292    this.registerCallback(); 
     293  }, 
     294 
     295  registerCallback: function() { 
     296    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); 
     297  }, 
     298 
     299  execute: function() { 
     300    this.callback(this); 
     301  }, 
     302 
     303  stop: function() { 
     304    if (!this.timer) return; 
     305    clearInterval(this.timer); 
     306    this.timer = null; 
     307  }, 
     308 
     309  onTimerEvent: function() { 
     310    if (!this.currentlyExecuting) { 
     311      try { 
     312        this.currentlyExecuting = true; 
     313        this.execute(); 
     314      } finally { 
     315        this.currentlyExecuting = false; 
     316      } 
     317    } 
     318  } 
     319}); 
     320Object.extend(String, { 
     321  interpret: function(value) { 
     322    return value == null ? '' : String(value); 
     323  }, 
     324  specialChar: { 
     325    '\b': '\\b', 
     326    '\t': '\\t', 
     327    '\n': '\\n', 
     328    '\f': '\\f', 
     329    '\r': '\\r', 
     330    '\\': '\\\\' 
     331  } 
     332}); 
     333 
     334Object.extend(String.prototype, { 
     335  gsub: function(pattern, replacement) { 
     336    var result = '', source = this, match; 
     337    replacement = arguments.callee.prepareReplacement(replacement); 
     338 
     339    while (source.length > 0) { 
     340      if (match = source.match(pattern)) { 
     341        result += source.slice(0, match.index); 
     342        result += String.interpret(replacement(match)); 
     343        source  = source.slice(match.index + match[0].length); 
     344      } else { 
     345        result += source, source = ''; 
     346      } 
     347    } 
     348    return result; 
     349  }, 
     350 
     351  sub: function(pattern, replacement, count) { 
     352    replacement = this.gsub.prepareReplacement(replacement); 
     353    count = Object.isUndefined(count) ? 1 : count; 
     354 
     355    return this.gsub(pattern, function(match) { 
     356      if (--count < 0) return match[0]; 
     357      return replacement(match); 
     358    }); 
     359  }, 
     360 
     361  scan: function(pattern, iterator) { 
     362    this.gsub(pattern, iterator); 
     363    return String(this); 
     364  }, 
     365 
     366  truncate: function(length, truncation) { 
     367    length = length || 30; 
     368    truncation = Object.isUndefined(truncation) ? '...' : truncation; 
     369    return this.length > length ? 
     370      this.slice(0, length - truncation.length) + truncation : String(this); 
     371  }, 
     372 
     373  strip: function() { 
     374    return this.replace(/^\s+/, '').replace(/\s+$/, ''); 
     375  }, 
     376 
     377  stripTags: function() { 
     378    return this.replace(/<\/?[^>]+>/gi, ''); 
     379  }, 
     380 
     381  stripScripts: function() { 
     382    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); 
     383  }, 
     384 
     385  extractScripts: function() { 
     386    var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); 
     387    var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); 
     388    return (this.match(matchAll) || []).map(function(scriptTag) { 
     389      return (scriptTag.match(matchOne) || ['', ''])[1]; 
     390    }); 
     391  }, 
     392 
     393  evalScripts: function() { 
     394    return this.extractScripts().map(function(script) { return eval(script) }); 
     395  }, 
     396 
     397  escapeHTML: function() { 
     398    var self = arguments.callee; 
     399    self.text.data = this; 
     400    return self.div.innerHTML; 
     401  }, 
     402 
     403  unescapeHTML: function() { 
     404    var div = new Element('div'); 
     405    div.innerHTML = this.stripTags(); 
     406    return div.childNodes[0] ? (div.childNodes.length > 1 ? 
     407      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : 
     408      div.childNodes[0].nodeValue) : ''; 
     409  }, 
     410 
     411  toQueryParams: function(separator) { 
     412    var match = this.strip().match(/([^?#]*)(#.*)?$/); 
     413    if (!match) return { }; 
     414 
     415    return match[1].split(separator || '&').inject({ }, function(hash, pair) { 
     416      if ((pair = pair.split('='))[0]) { 
     417        var key = decodeURIComponent(pair.shift()); 
     418        var value = pair.length > 1 ? pair.join('=') : pair[0]; 
     419        if (value != undefined) value = decodeURIComponent(value); 
     420 
     421        if (key in hash) { 
     422          if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; 
     423          hash[key].push(value); 
     424        } 
     425        else hash[key] = value; 
     426      } 
     427      return hash; 
     428    }); 
     429  }, 
     430 
     431  toArray: function() { 
     432    return this.split(''); 
     433  }, 
     434 
     435  succ: function() { 
     436    return this.slice(0, this.length - 1) + 
     437      String.fromCharCode(this.charCodeAt(this.length - 1) + 1); 
     438  }, 
     439 
     440  times: function(count) { 
     441    return count < 1 ? '' : new Array(count + 1).join(this); 
     442  }, 
     443 
     444  camelize: function() { 
     445    var parts = this.split('-'), len = parts.length; 
     446    if (len == 1) return parts[0]; 
     447 
     448    var camelized = this.charAt(0) == '-' 
     449      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) 
     450      : parts[0]; 
     451 
     452    for (var i = 1; i < len; i++) 
     453      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); 
     454 
     455    return camelized; 
     456  }, 
     457 
     458  capitalize: function() { 
     459    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); 
     460  }, 
     461 
     462  underscore: function() { 
     463    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); 
     464  }, 
     465 
     466  dasherize: function() { 
     467    return this.gsub(/_/,'-'); 
     468  }, 
     469 
     470  inspect: function(useDoubleQuotes) { 
     471    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { 
     472      var character = String.specialChar[match[0]]; 
     473      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); 
     474    }); 
     475    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; 
     476    return "'" + escapedString.replace(/'/g, '\\\'') + "'"; 
     477  }, 
     478 
     479  toJSON: function() { 
     480    return this.inspect(true); 
     481  }, 
     482 
     483  unfilterJSON: function(filter) { 
     484    return this.sub(filter || Prototype.JSONFilter, '#{1}'); 
     485  }, 
     486 
     487  isJSON: function() { 
     488    var str = this; 
     489    if (str.blank()) return false; 
     490    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); 
     491    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); 
     492  }, 
     493 
     494  evalJSON: function(sanitize) { 
     495    var json = this.unfilterJSON(); 
     496    try { 
     497      if (!sanitize || json.isJSON()) return eval('(' + json + ')'); 
     498    } catch (e) { } 
     499    throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); 
     500  }, 
     501 
     502  include: function(pattern) { 
     503    return this.indexOf(pattern) > -1; 
     504  }, 
     505 
     506  startsWith: function(pattern) { 
     507    return this.indexOf(pattern) === 0; 
     508  }, 
     509 
     510  endsWith: function(pattern) { 
     511    var d = this.length - pattern.length; 
     512    return d >= 0 && this.lastIndexOf(pattern) === d; 
     513  }, 
     514 
     515  empty: function() { 
     516    return this == ''; 
     517  }, 
     518 
     519  blank: function() { 
     520    return /^\s*$/.test(this); 
     521  }, 
     522 
     523  interpolate: function(object, pattern) { 
     524    return new Template(this, pattern).evaluate(object); 
     525  } 
     526}); 
     527 
     528if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { 
     529  escapeHTML: function() { 
     530    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); 
     531  }, 
     532  unescapeHTML: function() { 
     533    return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>'); 
     534  } 
     535}); 
     536 
     537String.prototype.gsub.prepareReplacement = function(replacement) { 
     538  if (Object.isFunction(replacement)) return replacement; 
     539  var template = new Template(replacement); 
     540  return function(match) { return template.evaluate(match) }; 
     541}; 
     542 
     543String.prototype.parseQuery = String.prototype.toQueryParams; 
     544 
     545Object.extend(String.prototype.escapeHTML, { 
     546  div:  document.createElement('div'), 
     547  text: document.createTextNode('') 
     548}); 
     549 
     550with (String.prototype.escapeHTML) div.appendChild(text); 
     551 
     552var Template = Class.create({ 
     553  initialize: function(template, pattern) { 
     554    this.template = template.toString(); 
     555    this.pattern = pattern || Template.Pattern; 
     556  }, 
     557 
     558  evaluate: function(object) { 
     559    if (Object.isFunction(object.toTemplateReplacements)) 
     560      object = object.toTemplateReplacements(); 
     561 
     562    return this.template.gsub(this.pattern, function(match) { 
     563      if (object == null) return ''; 
     564 
     565      var before = match[1] || ''; 
     566      if (before == '\\') return match[2]; 
     567 
     568      var ctx = object, expr = match[3]; 
     569      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; 
     570      match = pattern.exec(expr); 
     571      if (match == null) return before; 
     572 
     573      while (match != null) { 
     574        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; 
     575        ctx = ctx[comp]; 
     576        if (null == ctx || '' == match[3]) break; 
     577        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); 
     578        match = pattern.exec(expr); 
     579      } 
     580 
     581      return before + String.interpret(ctx); 
     582    }); 
     583  } 
     584}); 
     585Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; 
     586 
     587var $break = { }; 
     588 
     589var Enumerable = { 
     590  each: function(iterator, context) { 
     591    var index = 0; 
     592    iterator = iterator.bind(context); 
     593    try { 
     594      this._each(function(value) { 
     595        iterator(value, index++); 
     596      }); 
     597    } catch (e) { 
     598      if (e != $break) throw e; 
     599    } 
     600    return this; 
     601  }, 
     602 
     603  eachSlice: function(number, iterator, context) { 
     604    iterator = iterator ? iterator.bind(context) : Prototype.K; 
     605    var index = -number, slices = [], array = this.toArray(); 
     606    while ((index += number) < array.length) 
     607      slices.push(array.slice(index, index+number)); 
     608    return slices.collect(iterator, context); 
     609  }, 
     610 
     611  all: function(iterator, context) { 
     612    iterator = iterator ? iterator.bind(context) : Prototype.K; 
     613    var result = true; 
     614    this.each(function(value, index) { 
     615      result = result && !!iterator(value, index); 
     616      if (!result) throw $break; 
     617    }); 
     618    return result; 
     619  }, 
     620 
     621  any: function(iterator, context) { 
     622    iterator = iterator ? iterator.bind(context) : Prototype.K; 
     623    var result = false; 
     624    this.each(function(value, index) { 
     625      if (result = !!iterator(value, index)) 
     626        throw $break; 
     627    }); 
     628    return result; 
     629  }, 
     630 
     631  collect: function(iterator, context) { 
     632    iterator = iterator ? iterator.bind(context) : Prototype.K; 
     633    var results = []; 
     634    this.each(function(value, index) { 
     635      results.push(iterator(value, index)); 
     636    }); 
     637    return results; 
     638  }, 
     639 
     640  detect: function(iterator, context) { 
     641    iterator = iterator.bind(context); 
     642    var result; 
     643    this.each(function(value, index) { 
     644      if (iterator(value, index)) { 
     645        result = value; 
     646        throw $break; 
     647      } 
     648    }); 
     649    return result; 
     650  }, 
     651 
     652  findAll: function(iterator, context) { 
     653    iterator = iterator.bind(context); 
     654    var results = []; 
     655    this.each(function(value, index) { 
     656      if (iterator(value, index)) 
     657        results.push(value); 
     658    }); 
     659    return results; 
     660  }, 
     661 
     662  grep: function(filter, iterator, context) { 
     663    iterator = iterator ? iterator.bind(context) : Prototype.K; 
     664    var results = []; 
     665 
     666    if (Object.isString(filter)) 
     667      filter = new RegExp(filter); 
     668 
     669    this.each(function(value, index) { 
     670      if (filter.match(value)) 
     671        results.push(iterator(value, index)); 
     672    }); 
     673    return results; 
     674  }, 
     675 
     676  include: function(object) { 
     677    if (Object.isFunction(this.indexOf)) 
     678      if (this.indexOf(object) != -1) return true; 
     679 
     680    var found = false; 
     681    this.each(function(value) { 
     682      if (value == object) { 
     683        found = true; 
     684        throw $break; 
     685      } 
     686    }); 
     687    return found; 
     688  }, 
     689 
     690  inGroupsOf: function(number, fillWith) { 
     691    fillWith = Object.isUndefined(fillWith) ? null : fillWith; 
     692    return this.eachSlice(number, function(slice) { 
     693      while(slice.length < number) slice.push(fillWith); 
     694      return slice; 
     695    }); 
     696  }, 
     697 
     698  inject: function(memo, iterator, context) { 
     699    iterator = iterator.bind(context); 
     700    this.each(function(value, index) { 
     701      memo = iterator(memo, value, index); 
     702    }); 
     703    return memo; 
     704  }, 
     705 
     706  invoke: function(method) { 
     707    var args = $A(arguments).slice(1); 
     708    return this.map(function(value) { 
     709      return value[method].apply(value, args); 
     710    }); 
     711  }, 
     712 
     713  max: function(iterator, context) { 
     714    iterator = iterator ? iterator.bind(context) : Prototype.K; 
     715    var result; 
     716    this.each(function(value, index) { 
     717      value = iterator(value, index); 
     718      if (result == null || value >= result) 
     719        result = value; 
     720    }); 
     721    return result; 
     722  }, 
     723 
     724  min: function(iterator, context) { 
     725    iterator = iterator ? iterator.bind(context) : Prototype.K; 
     726    var result; 
     727    this.each(function(value, index) { 
     728      value = iterator(value, index); 
     729      if (result == null || value < result) 
     730        result = value; 
     731    }); 
     732    return result; 
     733  }, 
     734 
     735  partition: function(iterator, context) { 
     736    iterator = iterator ? iterator.bind(context) : Prototype.K; 
     737    var trues = [], falses = []; 
     738    this.each(function(value, index) { 
     739      (iterator(value, index) ? 
     740        trues : falses).push(value); 
     741    }); 
     742    return [trues, falses]; 
     743  }, 
     744 
     745  pluck: function(property) { 
     746    var results = []; 
     747    this.each(function(value) { 
     748      results.push(value[property]); 
     749    }); 
     750    return results; 
     751  }, 
     752 
     753  reject: function(iterator, context) { 
     754    iterator = iterator.bind(context); 
     755    var results = []; 
     756    this.each(function(value, index) { 
     757      if (!iterator(value, index)) 
     758        results.push(value); 
     759    }); 
     760    return results; 
     761  }, 
     762 
     763  sortBy: function(iterator, context) { 
     764    iterator = iterator.bind(context); 
     765    return this.map(function(value, index) { 
     766      return {value: value, criteria: iterator(value, index)}; 
     767    }).sort(function(left, right) { 
     768      var a = left.criteria, b = right.criteria; 
     769      return a < b ? -1 : a > b ? 1 : 0; 
     770    }).pluck('value'); 
     771  }, 
     772 
     773  toArray: function() { 
     774    return this.map(); 
     775  }, 
     776 
     777  zip: function() { 
     778    var iterator = Prototype.K, args = $A(arguments); 
     779    if (Object.isFunction(args.last())) 
     780      iterator = args.pop(); 
     781 
     782    var collections = [this].concat(args).map($A); 
     783    return this.map(function(value, index) { 
     784      return iterator(collections.pluck(index)); 
     785    }); 
     786  }, 
     787 
     788  size: function() { 
     789    return this.toArray().length; 
     790  }, 
     791 
     792  inspect: function() { 
     793    return '#<Enumerable:' + this.toArray().inspect() + '>'; 
     794  } 
     795}; 
     796 
     797Object.extend(Enumerable, { 
     798  map:     Enumerable.collect, 
     799  find:    Enumerable.detect, 
     800  select:  Enumerable.findAll, 
     801  filter:  Enumerable.findAll, 
     802  member:  Enumerable.include, 
     803  entries: Enumerable.toArray, 
     804  every:   Enumerable.all, 
     805  some:    Enumerable.any 
     806}); 
     807function $A(iterable) { 
     808  if (!iterable) return []; 
     809  if (iterable.toArray) return iterable.toArray(); 
     810  var length = iterable.length || 0, results = new Array(length); 
     811  while (length--) results[length] = iterable[length]; 
     812  return results; 
    32813} 
    33814 
    34 var Class = { 
    35         create: function() { 
    36                 return function() { 
    37                         this.initialize.apply(this, arguments); 
    38                 } 
    39         } 
     815if (Prototype.Browser.WebKit) { 
     816  $A = function(iterable) { 
     817    if (!iterable) return []; 
     818    if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') && 
     819        iterable.toArray) return iterable.toArray(); 
     820    var length = iterable.length || 0, results = new Array(length); 
     821    while (length--) results[length] = iterable[length]; 
     822    return results; 
     823  }; 
    40824} 
    41825 
    42 var Abstract = new Object(); 
    43  
    44 Object.extend = function(destination, source) { 
    45         for (var property in source) { 
    46                 destination[property] = source[property]; 
    47         } 
    48         return destination; 
     826Array.from = $A; 
     827 
     828Object.extend(Array.prototype, Enumerable); 
     829 
     830if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; 
     831 
     832Object.extend(Array.prototype, { 
     833  _each: function(iterator) { 
     834    for (var i = 0, length = this.length; i < length; i++) 
     835      iterator(this[i]); 
     836  }, 
     837 
     838  clear: function() { 
     839    this.length = 0; 
     840    return this; 
     841  }, 
     842 
     843  first: function() { 
     844    return this[0]; 
     845  }, 
     846 
     847  last: function() { 
     848    return this[this.length - 1]; 
     849  }, 
     850 
     851  compact: function() { 
     852    return this.select(function(value) { 
     853      return value != null; 
     854    }); 
     855  }, 
     856 
     857  flatten: function() { 
     858    return this.inject([], function(array, value) { 
     859      return array.concat(Object.isArray(value) ? 
     860        value.flatten() : [value]); 
     861    }); 
     862  }, 
     863 
     864  without: function() { 
     865    var values = $A(arguments); 
     866    return this.select(function(value) { 
     867      return !values.include(value); 
     868    }); 
     869  }, 
     870 
     871  reverse: function(inline) { 
     872    return (inline !== false ? this : this.toArray())._reverse(); 
     873  }, 
     874 
     875  reduce: function() { 
     876    return this.length > 1 ? this : this[0]; 
     877  }, 
     878 
     879  uniq: function(sorted) { 
     880    return this.inject([], function(array, value, index) { 
     881      if (0 == index || (sorted ? array.last() != value : !array.include(value))) 
     882        array.push(value); 
     883      return array; 
     884    }); 
     885  }, 
     886 
     887  intersect: function(array) { 
     888    return this.uniq().findAll(function(item) { 
     889      return array.detect(function(value) { return item === value }); 
     890    }); 
     891  }, 
     892 
     893  clone: function() { 
     894    return [].concat(this); 
     895  }, 
     896 
     897  size: function() { 
     898    return this.length; 
     899  }, 
     900 
     901  inspect: function() { 
     902    return '[' + this.map(Object.inspect).join(', ') + ']'; 
     903  }, 
     904 
     905  toJSON: function() { 
     906    var results = []; 
     907    this.each(function(object) { 
     908      var value = Object.toJSON(object); 
     909      if (!Object.isUndefined(value)) results.push(value); 
     910    }); 
     911    return '[' + results.join(', ') + ']'; 
     912  } 
     913}); 
     914 
     915// use native browser JS 1.6 implementation if available 
     916if (Object.isFunction(Array.prototype.forEach)) 
     917  Array.prototype._each = Array.prototype.forEach; 
     918 
     919if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) { 
     920  i || (i = 0); 
     921  var length = this.length; 
     922  if (i < 0) i = length + i; 
     923  for (; i < length; i++) 
     924    if (this[i] === item) return i; 
     925  return -1; 
     926}; 
     927 
     928if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) { 
     929  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; 
     930  var n = this.slice(0, i).reverse().indexOf(item); 
     931  return (n < 0) ? n : i - n - 1; 
     932}; 
     933 
     934Array.prototype.toArray = Array.prototype.clone; 
     935 
     936function $w(string) { 
     937  if (!Object.isString(string)) return []; 
     938  string = string.strip(); 
     939  return string ? string.split(/\s+/) : []; 
    49940} 
    50941 
    51 Object.extend(Object, { 
    52         inspect: function(object) { 
    53                 try { 
    54                         if (object === undefined) return 'undefined'; 
    55                         if (object === null) return 'null'; 
    56                         return object.inspect ? object.inspect() : object.toString(); 
    57                 } catch (e) { 
    58                         if (e instanceof RangeError) return '...'; 
    59                         throw e; 
    60                 } 
    61         }, 
    62  
    63         toJSON: function(object) { 
    64                 var type = typeof object; 
    65                 switch(type) { 
    66                         case 'undefined': 
    67                         case 'function': 
    68                         case 'unknown': return; 
    69                         case 'boolean': return object.toString(); 
    70                 } 
    71                 if (object === null) return 'null'; 
    72                 if (object.toJSON) return object.toJSON(); 
    73                 if (object.ownerDocument === document) return; 
    74                 var results = []; 
    75                 for (var property in object) { 
    76                         var value = Object.toJSON(object[property]); 
    77                         if (value !== undefined) 
    78                                 results.push(property.toJSON() + ': ' + value); 
    79                 } 
    80                 return '{' + results.join(', ') + '}'; 
    81         }, 
    82  
    83         keys: function(object) { 
    84                 var keys = []; 
    85                 for (var property in object) 
    86                         keys.push(property); 
    87                 return keys; 
    88         }, 
    89  
    90         values: function(object) { 
    91                 var values = []; 
    92                 for (var property in object) 
    93                         values.push(object[property]); 
    94                 return values; 
    95         }, 
    96  
    97         clone: function(object) { 
    98                 return Object.extend({}, object); 
    99         } 
     942if (Prototype.Browser.Opera){ 
     943  Array.prototype.concat = function() { 
     944    var array = []; 
     945    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); 
     946    for (var i = 0, length = arguments.length; i < length; i++) { 
     947      if (Object.isArray(arguments[i])) { 
     948        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) 
     949          array.push(arguments[i][j]); 
     950      } else { 
     951        array.push(arguments[i]); 
     952      } 
     953    } 
     954    return array; 
     955  }; 
     956} 
     957Object.extend(Number.prototype, { 
     958  toColorPart: function() { 
     959    return this.toPaddedString(2, 16); 
     960  }, 
     961 
     962  succ: function() { 
     963    return this + 1; 
     964  }, 
     965 
     966  times: function(iterator) { 
     967    $R(0, this, true).each(iterator); 
     968    return this; 
     969  }, 
     970 
     971  toPaddedString: function(length, radix) { 
     972    var string = this.toString(radix || 10); 
     973    return '0'.times(length - string.length) + string; 
     974  }, 
     975 
     976  toJSON: function() { 
     977    return isFinite(this) ? this.toString() : 'null'; 
     978  } 
    100979}); 
    101980 
    102 Function.prototype.bind = function() { 
    103         var __method = this, args = $A(arguments), object = args.shift(); 
    104         return function() { 
    105                 return __method.apply(object, args.concat($A(arguments))); 
    106         } 
    107 } 
    108  
    109 Function.prototype.bindAsEventListener = function(object) { 
    110         var __method = this, args = $A(arguments), object = args.shift(); 
    111         return function(event) { 
    112                 return __method.apply(object, [event || window.event].concat(args)); 
    113         } 
    114 } 
    115  
    116 Object.extend(Number.prototype, { 
    117         toColorPart: function() { 
    118                 return this.toPaddedString(2, 16); 
    119         }, 
    120  
    121         succ: function() { 
    122                 return this + 1; 
    123         }, 
    124  
    125         times: function(iterator) { 
    126                 $R(0, this, true).each(iterator); 
    127                 return this; 
    128         }, 
    129  
    130         toPaddedString: function(length, radix) { 
    131                 var string = this.toString(radix || 10); 
    132                 return '0'.times(length - string.length) + string; 
    133         }, 
    134  
    135         toJSON: function() { 
    136                 return isFinite(this) ? this.toString() : 'null'; 
    137         } 
     981$w('abs round ceil floor').each(function(method){ 
     982  Number.prototype[method] = Math[method].methodize(); 
    138983}); 
    139  
    140 Date.prototype.toJSON = function() { 
    141         return '"' + this.getFullYear() + '-' + 
    142                 (this.getMonth() + 1).toPaddedString(2) + '-' + 
    143                 this.getDate().toPaddedString(2) + 'T' + 
    144                 this.getHours().toPaddedString(2) + ':' + 
    145                 this.getMinutes().toPaddedString(2) + ':' + 
    146                 this.getSeconds().toPaddedString(2) + '"'; 
     984function $H(object) { 
     985  return new Hash(object); 
    147986}; 
    148987 
    149 var Try = { 
    150         these: function() { 
    151                 var returnValue; 
    152  
    153                 for (var i = 0, length = arguments.length; i < length; i++) { 
    154                         var lambda = arguments[i]; 
    155                         try { 
    156                                 returnValue = lambda(); 
    157                                 break; 
    158                         } catch (e) {} 
    159                 } 
    160  
    161                 return returnValue; 
    162         } 
    163 } 
    164  
    165 /*--------------------------------------------------------------------------*/ 
    166  
    167 var PeriodicalExecuter = Class.create(); 
    168 PeriodicalExecuter.prototype = { 
    169         initialize: function(callback, frequency) { 
    170                 this.callback = callback; 
    171                 this.frequency = frequency; 
    172                 this.currentlyExecuting = false; 
    173  
    174                 this.registerCallback(); 
    175         }, 
    176  
    177         registerCallback: function() { 
    178                 this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); 
    179         }, 
    180  
    181         stop: function() { 
    182                 if (!this.timer) return; 
    183                 clearInterval(this.timer); 
    184                 this.timer = null; 
    185         }, 
    186  
    187         onTimerEvent: function() { 
    188                 if (!this.currentlyExecuting) { 
    189                         try { 
    190                                 this.currentlyExecuting = true; 
    191                                 this.callback(this); 
    192                         } finally { 
    193                                 this.currentlyExecuting = false; 
    194                         } 
    195                 } 
    196         } 
    197 } 
    198 Object.extend(String, { 
    199         interpret: function(value) { 
    200                 return value == null ? '' : String(value); 
    201         }, 
    202         specialChar: { 
    203                 '\b': '\\b', 
    204                 '\t': '\\t', 
    205                 '\n': '\\n', 
    206                 '\f': '\\f', 
    207                 '\r': '\\r', 
    208                 '\\': '\\\\' 
    209         } 
     988var Hash = Class.create(Enumerable, (function() { 
     989 
     990  function toQueryPair(key, value) { 
     991    if (Object.isUndefined(value)) return key; 
     992    return key + '=' + encodeURIComponent(String.interpret(value)); 
     993  } 
     994 
     995  return { 
     996    initialize: function(object) { 
     997      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); 
     998    }, 
     999 
     1000    _each: function(iterator) { 
     1001      for (var key in this._object) { 
     1002        var value = this._object[key], pair = [key, value]; 
     1003        pair.key = key; 
     1004        pair.value = value; 
     1005        iterator(pair); 
     1006      } 
     1007    }, 
     1008 
     1009    set: function(key, value) { 
     1010      return this._object[key] = value; 
     1011    }, 
     1012 
     1013    get: function(key) { 
     1014      return this._object[key]; 
     1015    }, 
     1016 
     1017    unset: function(key) { 
     1018      var value = this._object[key]; 
     1019      delete this._object[key]; 
     1020      return value; 
     1021    }, 
     1022 
     1023    toObject: function() { 
     1024      return Object.clone(this._object); 
     1025    }, 
     1026 
     1027    keys: function() { 
     1028      return this.pluck('key'); 
     1029    }, 
     1030 
     1031    values: function() { 
     1032      return this.pluck('value'); 
     1033    }, 
     1034 
     1035    index: function(value) { 
     1036      var match = this.detect(function(pair) { 
     1037        return pair.value === value; 
     1038      }); 
     1039      return match && match.key; 
     1040    }, 
     1041 
     1042    merge: function(object) { 
     1043      return this.clone().update(object); 
     1044    }, 
     1045 
     1046    update: function(object) { 
     1047      return new Hash(object).inject(this, function(result, pair) { 
     1048        result.set(pair.key, pair.value); 
     1049        return result; 
     1050      }); 
     1051    }, 
     1052 
     1053    toQueryString: function() { 
     1054      return this.map(function(pair) { 
     1055        var key = encodeURIComponent(pair.key), values = pair.value; 
     1056 
     1057        if (values && typeof values == 'object') { 
     1058          if (Object.isArray(values)) 
     1059            return values.map(toQueryPair.curry(key)).join('&'); 
     1060        } 
     1061        return toQueryPair(key, values); 
     1062      }).join('&'); 
     1063    }, 
     1064 
     1065    inspect: function() { 
     1066      return '#<Hash:{' + this.map(function(pair) { 
     1067        return pair.map(Object.inspect).join(': '); 
     1068      }).join(', ') + '}>'; 
     1069    }, 
     1070 
     1071    toJSON: function() { 
     1072      return Object.toJSON(this.toObject()); 
     1073    }, 
     1074 
     1075    clone: function() { 
     1076      return new Hash(this); 
     1077    } 
     1078  } 
     1079})()); 
     1080 
     1081Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; 
     1082Hash.from = $H; 
     1083var ObjectRange = Class.create(Enumerable, { 
     1084  initialize: function(start, end, exclusive) { 
     1085    this.start = start; 
     1086    this.end = end; 
     1087    this.exclusive = exclusive; 
     1088  }, 
     1089 
     1090  _each: function(iterator) { 
     1091    var value = this.start; 
     1092    while (this.include(value)) { 
     1093      iterator(value); 
     1094      value = value.succ(); 
     1095    } 
     1096  }, 
     1097 
     1098  include: function(value) { 
     1099    if (value < this.start) 
     1100      return false; 
     1101    if (this.exclusive) 
     1102      return value < this.end; 
     1103    return value <= this.end; 
     1104  } 
    2101105}); 
    2111106 
    212 Object.extend(String.prototype, { 
    213         gsub: function(pattern, replacement) { 
    214                 var result = '', source = this, match; 
    215                 replacement = arguments.callee.prepareReplacement(replacement); 
    216  
    217                 while (source.length > 0) { 
    218                         if (match = source.match(pattern)) { 
    219                                 result += source.slice(0, match.index); 
    220                                 result += String.interpret(replacement(match)); 
    221                                 source  = source.slice(match.index + match[0].length); 
    222                         } else { 
    223                                 result += source, source = ''; 
    224                         } 
    225                 } 
    226                 return result; 
    227         }, 
    228  
    229         sub: function(pattern, replacement, count) { 
    230                 replacement = this.gsub.prepareReplacement(replacement); 
    231                 count = count === undefined ? 1 : count; 
    232  
    233                 return this.gsub(pattern, function(match) { 
    234                         if (--count < 0) return match[0]; 
    235                         return replacement(match); 
    236                 }); 
    237         }, 
    238  
    239         scan: function(pattern, iterator) { 
    240                 this.gsub(pattern, iterator); 
    241                 return this; 
    242         }, 
    243  
    244         truncate: function(length, truncation) { 
    245                 length = length || 30; 
    246                 truncation = truncation === undefined ? '...' : truncation; 
    247                 return this.length > length ? 
    248                         this.slice(0, length - truncation.length) + truncation : this; 
    249         }, 
    250  
    251         strip: function() { 
    252                 return this.replace(/^\s+/, '').replace(/\s+$/, ''); 
    253         }, 
    254  
    255         stripTags: function() { 
    256                 return this.replace(/<\/?[^>]+>/gi, ''); 
    257         }, 
    258  
    259         stripScripts: function() { 
    260                 return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); 
    261         }, 
    262  
    263         extractScripts: function() { 
    264                 var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); 
    265                 var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); 
    266                 return (this.match(matchAll) || []).map(function(scriptTag) { 
    267                         return (scriptTag.match(matchOne) || ['', ''])[1]; 
    268                 }); 
    269         }, 
    270  
    271         evalScripts: function() { 
    272                 return this.extractScripts().map(function(script) { return eval(script) }); 
    273         }, 
    274  
    275         escapeHTML: function() { 
    276                 var self = arguments.callee; 
    277                 self.text.data = this; 
    278                 return self.div.innerHTML; 
    279         }, 
    280  
    281         unescapeHTML: function() { 
    282                 var div = document.createElement('div'); 
    283                 div.innerHTML = this.stripTags(); 
    284                 return div.childNodes[0] ? (div.childNodes.length > 1 ? 
    285                         $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : 
    286                         div.childNodes[0].nodeValue) : ''; 
    287         }, 
    288  
    289         toQueryParams: function(separator) { 
    290                 var match = this.strip().match(/([^?#]*)(#.*)?$/); 
    291                 if (!match) return {}; 
    292  
    293                 return match[1].split(separator || '&').inject({}, function(hash, pair) { 
    294                         if ((pair = pair.split('='))[0]) { 
    295                                 var key = decodeURIComponent(pair.shift()); 
    296                                 var value = pair.length > 1 ? pair.join('=') : pair[0]; 
    297                                 if (value != undefined) value = decodeURIComponent(value); 
    298  
    299                                 if (key in hash) { 
    300                                         if (hash[key].constructor != Array) hash[key] = [hash[key]]; 
    301                                         hash[key].push(value); 
    302                                 } 
    303                                 else hash[key] = value; 
    304                         } 
    305                         return hash; 
    306                 }); 
    307         }, 
    308  
    309         toArray: function() { 
    310                 return this.split(''); 
    311         }, 
    312  
    313         succ: function() { 
    314                 return this.slice(0, this.length - 1) + 
    315                         String.fromCharCode(this.charCodeAt(this.length - 1) + 1); 
    316         }, 
    317  
    318         times: function(count) { 
    319                 var result = ''; 
    320                 for (var i = 0; i < count; i++) result += this; 
    321                 return result; 
    322         }, 
    323  
    324         camelize: function() { 
    325                 var parts = this.split('-'), len = parts.length; 
    326                 if (len == 1) return parts[0]; 
    327  
    328                 var camelized = this.charAt(0) == '-' 
    329                         ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) 
    330                         : parts[0]; 
    331  
    332                 for (var i = 1; i < len; i++) 
    333                         camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); 
    334  
    335                 return camelized; 
    336         }, 
    337  
    338         capitalize: function() { 
    339                 return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); 
    340         }, 
    341  
    342         underscore: function() { 
    343                 return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); 
    344         }, 
    345  
    346         dasherize: function() { 
    347                 return this.gsub(/_/,'-'); 
    348         }, 
    349  
    350         inspect: function(useDoubleQuotes) { 
    351                 var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { 
    352                         var character = String.specialChar[match[0]]; 
    353                         return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); 
    354                 }); 
    355                 if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; 
    356                 return "'" + escapedString.replace(/'/g, '\\\'') + "'"; 
    357         }, 
    358  
    359         toJSON: function() { 
    360                 return this.inspect(true); 
    361         }, 
    362  
    363         unfilterJSON: function(filter) { 
    364                 return this.sub(filter || Prototype.JSONFilter, '#{1}'); 
    365         }, 
    366  
    367         isJSON: function() { 
    368                 var str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); 
    369                 return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); 
    370         }, 
    371  
    372         evalJSON: function(sanitize) { 
    373                 var json = this.unfilterJSON(); 
    374                 try { 
    375                         if (!sanitize || json.isJSON()) return eval('(' + json + ')'); 
    376                 } catch (e) { } 
    377                 throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); 
    378         }, 
    379  
    380         include: function(pattern) { 
    381                 return this.indexOf(pattern) > -1; 
    382         }, 
    383  
    384         startsWith: function(pattern) { 
    385                 return this.indexOf(pattern) === 0; 
    386         }, 
    387  
    388         endsWith: function(pattern) { 
    389                 var d = this.length - pattern.length; 
    390                 return d >= 0 && this.lastIndexOf(pattern) === d; 
    391         }, 
    392  
    393         empty: function() { 
    394                 return this == ''; 
    395         }, 
    396  
    397         blank: function() { 
    398                 return /^\s*$/.test(this); 
    399         } 
     1107var $R = function(start, end, exclusive) { 
     1108  return new ObjectRange(start, end, exclusive); 
     1109}; 
     1110 
     1111var Ajax = { 
     1112  getTransport: function() { 
     1113    return Try.these( 
     1114      function() {return new XMLHttpRequest()}, 
     1115      function() {return new ActiveXObject('Msxml2.XMLHTTP')}, 
     1116      function() {return new ActiveXObject('Microsoft.XMLHTTP')} 
     1117    ) || false; 
     1118  }, 
     1119 
     1120  activeRequestCount: 0 
     1121}; 
     1122 
     1123Ajax.Responders = { 
     1124  responders: [], 
     1125 
     1126  _each: function(iterator) { 
     1127    this.responders._each(iterator); 
     1128  }, 
     1129 
     1130  register: function(responder) { 
     1131    if (!this.include(responder)) 
     1132      this.responders.push(responder); 
     1133  }, 
     1134 
     1135  unregister: function(responder) { 
     1136    this.responders = this.responders.without(responder); 
     1137  }, 
     1138 
     1139  dispatch: function(callback, request, transport, json) { 
     1140    this.each(function(responder) { 
     1141      if (Object.isFunction(responder[callback])) { 
     1142        try { 
     1143          responder[callback].apply(responder, [request, transport, json]); 
     1144        } catch (e) { } 
     1145      } 
     1146    }); 
     1147  } 
     1148}; 
     1149 
     1150Object.extend(Ajax.Responders, Enumerable); 
     1151 
     1152Ajax.Responders.register({ 
     1153  onCreate:   function() { Ajax.activeRequestCount++ }, 
     1154  onComplete: function() { Ajax.activeRequestCount-- } 
    4001155}); 
    4011156 
    402 if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { 
    403         escapeHTML: function() { 
    404                 return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); 
    405         }, 
    406         unescapeHTML: function() { 
    407                 return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>'); 
    408         } 
     1157Ajax.Base = Class.create({ 
     1158  initialize: function(options) { 
     1159    this.options = { 
     1160      method:       'post', 
     1161      asynchronous: true, 
     1162      contentType:  'application/x-www-form-urlencoded', 
     1163      encoding:     'UTF-8', 
     1164      parameters:   '', 
     1165      evalJSON:     true, 
     1166      evalJS:       true 
     1167    }; 
     1168    Object.extend(this.options, options || { }); 
     1169 
     1170    this.options.method = this.options.method.toLowerCase(); 
     1171 
     1172    if (Object.isString(this.options.parameters)) 
     1173      this.options.parameters = this.options.parameters.toQueryParams(); 
     1174    else if (Object.isHash(this.options.parameters)) 
     1175      this.options.parameters = this.options.parameters.toObject(); 
     1176  } 
    4091177}); 
    4101178 
    411 String.prototype.gsub.prepareReplacement = function(replacement) { 
    412         if (typeof replacement == 'function') return replacement; 
    413         var template = new Template(replacement); 
    414         return function(match) { return template.evaluate(match) }; 
    415 } 
    416  
    417 String.prototype.parseQuery = String.prototype.toQueryParams; 
    418  
    419 Object.extend(String.prototype.escapeHTML, { 
    420         div:  document.createElement('div'), 
    421         text: document.createTextNode('') 
     1179Ajax.Request = Class.create(Ajax.Base, { 
     1180  _complete: false, 
     1181 
     1182  initialize: function($super, url, options) { 
     1183    $super(options); 
     1184    this.transport = Ajax.getTransport(); 
     1185    this.request(url); 
     1186  }, 
     1187 
     1188  request: function(url) { 
     1189    this.url = url; 
     1190    this.method = this.options.method; 
     1191    var params = Object.clone(this.options.parameters); 
     1192 
     1193    if (!['get', 'post'].include(this.method)) { 
     1194      // simulate other verbs over post 
     1195      params['_method'] = this.method; 
     1196      this.method = 'post'; 
     1197    } 
     1198 
     1199    this.parameters = params; 
     1200 
     1201    if (params = Object.toQueryString(params)) { 
     1202      // when GET, append parameters to URL 
     1203      if (this.method == 'get') 
     1204        this.url += (this.url.include('?') ? '&' : '?') + params; 
     1205      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) 
     1206        params += '&_='; 
     1207    } 
     1208 
     1209    try { 
     1210      var response = new Ajax.Response(this); 
     1211      if (this.options.onCreate) this.options.onCreate(response); 
     1212      Ajax.Responders.dispatch('onCreate', this, response); 
     1213 
     1214      this.transport.open(this.method.toUpperCase(), this.url, 
     1215        this.options.asynchronous); 
     1216 
     1217      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); 
     1218 
     1219      this.transport.onreadystatechange = this.onStateChange.bind(this); 
     1220      this.setRequestHeaders(); 
     1221 
     1222      this.body = this.method == 'post' ? (this.options.postBody || params) : null; 
     1223      this.transport.send(this.body); 
     1224 
     1225      /* Force Firefox to handle ready state 4 for synchronous requests */ 
     1226      if (!this.options.asynchronous && this.transport.overrideMimeType) 
     1227        this.onStateChange(); 
     1228 
     1229    } 
     1230    catch (e) { 
     1231      this.dispatchException(e); 
     1232    } 
     1233  }, 
     1234 
     1235  onStateChange: function() { 
     1236    var readyState = this.transport.readyState; 
     1237    if (readyState > 1 && !((readyState == 4) && this._complete)) 
     1238      this.respondToReadyState(this.transport.readyState); 
     1239  }, 
     1240 
     1241  setRequestHeaders: function() { 
     1242    var headers = { 
     1243      'X-Requested-With': 'XMLHttpRequest', 
     1244      'X-Prototype-Version': Prototype.Version, 
     1245      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' 
     1246    }; 
     1247 
     1248    if (this.method == 'post') { 
     1249      headers['Content-type'] = this.options.contentType + 
     1250        (this.options.encoding ? '; charset=' + this.options.encoding : ''); 
     1251 
     1252      /* Force "Connection: close" for older Mozilla browsers to work 
     1253       * around a bug where XMLHttpRequest sends an incorrect 
     1254       * Content-length header. See Mozilla Bugzilla #246651. 
     1255       */ 
     1256      if (this.transport.overrideMimeType && 
     1257          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) 
     1258            headers['Connection'] = 'close'; 
     1259    } 
     1260 
     1261    // user-defined headers 
     1262    if (typeof this.options.requestHeaders == 'object') { 
     1263      var extras = this.options.requestHeaders; 
     1264 
     1265      if (Object.isFunction(extras.push)) 
     1266        for (var i = 0, length = extras.length; i < length; i += 2) 
     1267          headers[extras[i]] = extras[i+1]; 
     1268      else 
     1269        $H(extras).each(function(pair) { headers[pair.key] = pair.value }); 
     1270    } 
     1271 
     1272    for (var name in headers) 
     1273      this.transport.setRequestHeader(name, headers[name]); 
     1274  }, 
     1275 
     1276  success: function() { 
     1277    var status = this.getStatus(); 
     1278    return !status || (status >= 200 && status < 300); 
     1279  }, 
     1280 
     1281  getStatus: function() { 
     1282    try { 
     1283      return this.transport.status || 0; 
     1284    } catch (e) { return 0 } 
     1285  }, 
     1286 
     1287  respondToReadyState: function(readyState) { 
     1288    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); 
     1289 
     1290    if (state == 'Complete') { 
     1291      try { 
     1292        this._complete = true; 
     1293        (this.options['on' + response.status] 
     1294         || this.options['on' + (this.success() ? 'Success' : 'Failure')] 
     1295         || Prototype.emptyFunction)(response, response.headerJSON); 
     1296      } catch (e) { 
     1297        this.dispatchException(e); 
     1298      } 
     1299 
     1300      var contentType = response.getHeader('Content-type'); 
     1301      if (this.options.evalJS == 'force' 
     1302          || (this.options.evalJS && this.isSameOrigin() && contentType 
     1303          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) 
     1304        this.evalResponse(); 
     1305    } 
     1306 
     1307    try { 
     1308      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); 
     1309      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); 
     1310    } catch (e) { 
     1311      this.dispatchException(e); 
     1312    } 
     1313 
     1314    if (state == 'Complete') { 
     1315      // avoid memory leak in MSIE: clean up 
     1316      this.transport.onreadystatechange = Prototype.emptyFunction; 
     1317    } 
     1318  }, 
     1319 
     1320  isSameOrigin: function() { 
     1321    var m = this.url.match(/^\s*https?:\/\/[^\/]*/); 
     1322    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ 
     1323      protocol: location.protocol, 
     1324      domain: document.domain, 
     1325      port: location.port ? ':' + location.port : '' 
     1326    })); 
     1327  }, 
     1328 
     1329  getHeader: function(name) { 
     1330    try { 
     1331      return this.transport.getResponseHeader(name) || null; 
     1332    } catch (e) { return null } 
     1333  }, 
     1334 
     1335  evalResponse: function() { 
     1336    try { 
     1337      return eval((this.transport.responseText || '').unfilterJSON()); 
     1338    } catch (e) { 
     1339      this.dispatchException(e); 
     1340    } 
     1341  }, 
     1342 
     1343  dispatchException: function(exception) { 
     1344    (this.options.onException || Prototype.emptyFunction)(this, exception); 
     1345    Ajax.Responders.dispatch('onException', this, exception); 
     1346  } 
    4221347}); 
    4231348 
    424 with (String.prototype.escapeHTML) div.appendChild(text); 
    425  
    426 var Template = Class.create(); 
    427 Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; 
    428 Template.prototype = { 
    429         initialize: function(template, pattern) { 
    430                 this.template = template.toString(); 
    431                 this.pattern  = pattern || Template.Pattern; 
    432         }, 
    433  
    434         evaluate: function(object) { 
    435                 return this.template.gsub(this.pattern, function(match) { 
    436                         var before = match[1]; 
    437                         if (before == '\\') return match[2]; 
    438                         return before + String.interpret(object[match[3]]); 
    439                 }); 
    440         } 
    441 } 
    442  
    443 var $break = {}, $continue = new Error('"throw $continue" is deprecated, use "return" instead'); 
    444  
    445 var Enumerable = { 
    446         each: function(iterator) { 
    447                 var index = 0; 
    448                 try { 
    449                         this._each(function(value) { 
    450                                 iterator(value, index++); 
    451                         }); 
    452                 } catch (e) { 
    453                         if (e != $break) throw e; 
    454                 } 
    455                 return this; 
    456         }, 
    457  
    458         eachSlice: function(number, iterator) { 
    459                 var index = -number, slices = [], array = this.toArray(); 
    460                 while ((index += number) < array.length) 
    461                         slices.push(array.slice(index, index+number)); 
    462                 return slices.map(iterator); 
    463         }, 
    464  
    465         all: function(iterator) { 
    466                 var result = true; 
    467                 this.each(function(value, index) { 
    468                         result = result && !!(iterator || Prototype.K)(value, index); 
    469                         if (!result) throw $break; 
    470                 }); 
    471                 return result; 
    472         }, 
    473  
    474         any: function(iterator) { 
    475                 var result = false; 
    476                 this.each(function(value, index) { 
    477                         if (result = !!(iterator || Prototype.K)(value, index)) 
    478                                 throw $break; 
    479                 }); 
    480                 return result; 
    481         }, 
    482  
    483         collect: function(iterator) { 
    484                 var results = []; 
    485                 this.each(function(value, index) { 
    486                         results.push((iterator || Prototype.K)(value, index)); 
    487                 }); 
    488                 return results; 
    489         }, 
    490  
    491         detect: function(iterator) { 
    492                 var result; 
    493                 this.each(function(value, index) { 
    494                         if (iterator(value, index)) { 
    495                                 result = value; 
    496                                 throw $break; 
    497                         } 
    498                 }); 
    499                 return result; 
    500         }, 
    501  
    502         findAll: function(iterator) { 
    503                 var results = []; 
    504                 this.each(function(value, index) { 
    505                         if (iterator(value, index)) 
    506                                 results.push(value); 
    507                 }); 
    508                 return results; 
    509         }, 
    510  
    511         grep: function(pattern, iterator) { 
    512                 var results = []; 
    513                 this.each(function(value, index) { 
    514                         var stringValue = value.toString(); 
    515                         if (stringValue.match(pattern)) 
    516                                 results.push((iterator || Prototype.K)(value, index)); 
    517                 }) 
    518                 return results; 
    519         }, 
    520  
    521         include: function(object) { 
    522                 var found = false; 
    523                 this.each(function(value) { 
    524                         if (value == object) { 
    525                                 found = true; 
    526                                 throw $break; 
    527                         } 
    528                 }); 
    529                 return found; 
    530         }, 
    531  
    532         inGroupsOf: function(number, fillWith) { 
    533                 fillWith = fillWith === undefined ? null : fillWith; 
    534                 return this.eachSlice(number, function(slice) { 
    535                         while(slice.length < number) slice.push(fillWith); 
    536                         return slice; 
    537                 }); 
    538         }, 
    539  
    540         inject: function(memo, iterator) { 
    541                 this.each(function(value, index) { 
    542                         memo = iterator(memo, value, index); 
    543                 }); 
    544                 return memo; 
    545         }, 
    546  
    547         invoke: function(method) { 
    548                 var args = $A(arguments).slice(1); 
    549                 return this.map(function(value) { 
    550                         return value[method].apply(value, args); 
    551                 }); 
    552         }, 
    553  
    554         max: function(iterator) { 
    555                 var result; 
    556                 this.each(function(value, index) { 
    557                         value = (iterator || Prototype.K)(value, index); 
    558                         if (result == undefined || value >= result) 
    559                                 result = value; 
    560                 }); 
    561                 return result; 
    562         }, 
    563  
    564         min: function(iterator) { 
    565                 var result; 
    566                 this.each(function(value, index) { 
    567                         value = (iterator || Prototype.K)(value, index); 
    568                         if (result == undefined || value < result) 
    569                                 result = value; 
    570                 }); 
    571                 return result; 
    572         }, 
    573  
    574         partition: function(iterator) { 
    575                 var trues = [], falses = []; 
    576                 this.each(function(value, index) { 
    577                         ((iterator || Prototype.K)(value, index) ? 
    578                                 trues : falses).push(value); 
    579                 }); 
    580                 return [trues, falses]; 
    581         }, 
    582  
    583         pluck: function(property) { 
    584                 var results = []; 
    585                 this.each(function(value, index) { 
    586                         results.push(value[property]); 
    587                 }); 
    588                 return results; 
    589         }, 
    590  
    591         reject: function(iterator) { 
    592                 var results = []; 
    593                 this.each(function(value, index) { 
    594                         if (!iterator(value, index)) 
    595                                 results.push(value); 
    596                 }); 
    597                 return results; 
    598         }, 
    599  
    600         sortBy: function(iterator) { 
    601                 return this.map(function(value, index) { 
    602                         return {value: value, criteria: iterator(value, index)}; 
    603                 }).sort(function(left, right) { 
    604                         var a = left.criteria, b = right.criteria; 
    605                         return a < b ? -1 : a > b ? 1 : 0; 
    606                 }).pluck('value'); 
    607         }, 
    608  
    609         toArray: function() { 
    610                 return this.map(); 
    611         }, 
    612  
    613         zip: function() { 
    614                 var iterator = Prototype.K, args = $A(arguments); 
    615                 if (typeof args.last() == 'function') 
    616                         iterator = args.pop(); 
    617  
    618                 var collections = [this].concat(args).map($A); 
    619                 return this.map(function(value, index) { 
    620                         return iterator(collections.pluck(index)); 
    621                 }); 
    622         }, 
    623  
    624         size: function() { 
    625                 return this.toArray().length; 
    626         }, 
    627  
    628         inspect: function() { 
    629                 return '#<Enumerable:' + this.toArray().inspect() + '>'; 
    630         } 
    631 } 
    632  
    633 Object.extend(Enumerable, { 
    634         map:     Enumerable.collect, 
    635         find:    Enumerable.detect, 
    636         select:  Enumerable.findAll, 
    637         member:  Enumerable.include, 
    638         entries: Enumerable.toArray 
     1349Ajax.Request.Events = 
     1350  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; 
     1351 
     1352Ajax.Response = Class.create({ 
     1353  initialize: function(request){ 
     1354    this.request = request; 
     1355    var transport  = this.transport  = request.transport, 
     1356        readyState = this.readyState = transport.readyState; 
     1357 
     1358    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { 
     1359      this.status       = this.getStatus(); 
     1360      this.statusText   = this.getStatusText(); 
     1361      this.responseText = String.interpret(transport.responseText); 
     1362      this.headerJSON   = this._getHeaderJSON(); 
     1363    } 
     1364 
     1365    if(readyState == 4) { 
     1366      var xml = transport.responseXML; 
     1367      this.responseXML  = Object.isUndefined(xml) ? null : xml; 
     1368      this.responseJSON = this._getResponseJSON(); 
     1369    } 
     1370  }, 
     1371 
     1372  status:      0, 
     1373  statusText: '', 
     1374 
     1375  getStatus: Ajax.Request.prototype.getStatus, 
     1376 
     1377  getStatusText: function() { 
     1378    try { 
     1379      return this.transport.statusText || ''; 
     1380    } catch (e) { return '' } 
     1381  }, 
     1382 
     1383  getHeader: Ajax.Request.prototype.getHeader, 
     1384 
     1385  getAllHeaders: function() { 
     1386    try { 
     1387      return this.getAllResponseHeaders(); 
     1388    } catch (e) { return null } 
     1389  }, 
     1390 
     1391  getResponseHeader: function(name) { 
     1392    return this.transport.getResponseHeader(name); 
     1393  }, 
     1394 
     1395  getAllResponseHeaders: function() { 
     1396    return this.transport.getAllResponseHeaders(); 
     1397  }, 
     1398 
     1399  _getHeaderJSON: function() { 
     1400    var json = this.getHeader('X-JSON'); 
     1401    if (!json) return null; 
     1402    json = decodeURIComponent(escape(json)); 
     1403    try { 
     1404      return json.evalJSON(this.request.options.sanitizeJSON || 
     1405        !this.request.isSameOrigin()); 
     1406    } catch (e) { 
     1407      this.request.dispatchException(e); 
     1408    } 
     1409  }, 
     1410 
     1411  _getResponseJSON: function() { 
     1412    var options = this.request.options; 
     1413    if (!options.evalJSON || (options.evalJSON != 'force' && 
     1414      !(this.getHeader('Content-type') || '').include('application/json')) || 
     1415        this.responseText.blank()) 
     1416          return null; 
     1417    try { 
     1418      return this.responseText.evalJSON(options.sanitizeJSON || 
     1419        !this.request.isSameOrigin()); 
     1420    } catch (e) { 
     1421      this.request.dispatchException(e); 
     1422    } 
     1423  } 
    6391424}); 
    640 var $A = Array.from = function(iterable) { 
    641         if (!iterable) return []; 
    642         if (iterable.toArray) { 
    643                 return iterable.toArray(); 
    644         } else { 
    645                 var results = []; 
    646                 for (var i = 0, length = iterable.length; i < length; i++) 
    647                         results.push(iterable[i]); 
    648                 return results; 
    649         } 
    650 } 
    651  
    652 if (Prototype.Browser.WebKit) { 
    653         $A = Array.from = function(iterable) { 
    654                 if (!iterable) return []; 
    655                 if (!(typeof iterable == 'function' && iterable == '[object NodeList]') && 
    656                         iterable.toArray) { 
    657                         return iterable.toArray(); 
    658                 } else { 
    659                         var results = []; 
    660                         for (var i = 0, length = iterable.length; i < length; i++) 
    661                                 results.push(iterable[i]); 
    662                         return results; 
    663                 } 
    664         } 
    665 } 
    666  
    667 Object.extend(Array.prototype, Enumerable); 
    668  
    669 if (!Array.prototype._reverse) 
    670         Array.prototype._reverse = Array.prototype.reverse; 
    671  
    672 Object.extend(Array.prototype, { 
    673         _each: function(iterator) { 
    674                 for (var i = 0, length = this.length; i < length; i++) 
    675                         iterator(this[i]); 
    676         }, 
    677  
    678         clear: function() { 
    679                 this.length = 0; 
    680                 return this; 
    681         }, 
    682  
    683         first: function() { 
    684                 return this[0]; 
    685         }, 
    686  
    687         last: function() { 
    688                 return this[this.length - 1]; 
    689         }, 
    690  
    691         compact: function() { 
    692                 return this.select(function(value) { 
    693                         return value != null; 
    694                 }); 
    695         }, 
    696  
    697         flatten: function() { 
    698                 return this.inject([], function(array, value) { 
    699                         return array.concat(value && value.constructor == Array ? 
    700                                 value.flatten() : [value]); 
    701                 }); 
    702         }, 
    703  
    704         without: function() { 
    705                 var values = $A(arguments); 
    706                 return this.select(function(value) { 
    707                         return !values.include(value); 
    708                 }); 
    709         }, 
    710  
    711         indexOf: function(object) { 
    712                 for (var i = 0, length = this.length; i < length; i++) 
    713                         if (this[i] == object) return i; 
    714                 return -1; 
    715         }, 
    716  
    717         reverse: function(inline) { 
    718                 return (inline !== false ? this : this.toArray())._reverse(); 
    719         }, 
    720  
    721         reduce: function() { 
    722                 return this.length > 1 ? this : this[0]; 
    723         }, 
    724  
    725         uniq: function(sorted) { 
    726                 return this.inject([], function(array, value, index) { 
    727                         if (0 == index || (sorted ? array.last() != value : !array.include(value))) 
    728                                 array.push(value); 
    729                         return array; 
    730                 }); 
    731         }, 
    732  
    733         clone: function() { 
    734                 return [].concat(this); 
    735         }, 
    736  
    737         size: function() { 
    738                 return this.length; 
    739         }, 
    740  
    741         inspect: function() { 
    742                 return '[' + this.map(Object.inspect).join(', ') + ']'; 
    743         }, 
    744  
    745         toJSON: function() { 
    746                 var results = []; 
    747                 this.each(function(object) { 
    748                         var value = Object.toJSON(object); 
    749                         if (value !== undefined) results.push(value); 
    750                 }); 
    751                 return '[' + results.join(', ') + ']'; 
    752         } 
     1425 
     1426Ajax.Updater = Class.create(Ajax.Request, { 
     1427  initialize: function($super, container, url, options) { 
     1428    this.container = { 
     1429      success: (container.success || container), 
     1430      failure: (container.failure || (container.success ? null : container)) 
     1431    }; 
     1432 
     1433    options = Object.clone(options); 
     1434    var onComplete = options.onComplete; 
     1435    options.onComplete = (function(response, json) { 
     1436      this.updateContent(response.responseText); 
     1437      if (Object.isFunction(onComplete)) onComplete(response, json); 
     1438    }).bind(this); 
     1439 
     1440    $super(url, options); 
     1441  }, 
     1442 
     1443  updateContent: function(responseText) { 
     1444    var receiver = this.container[this.success() ? 'success' : 'failure'], 
     1445        options = this.options; 
     1446 
     1447    if (!options.evalScripts) responseText = responseText.stripScripts(); 
     1448 
     1449    if (receiver = $(receiver)) { 
     1450      if (options.insertion) { 
     1451        if (Object.isString(options.insertion)) { 
     1452          var insertion = { }; insertion[options.insertion] = responseText; 
     1453          receiver.insert(insertion); 
     1454        } 
     1455        else options.insertion(receiver, responseText); 
     1456      } 
     1457      else receiver.update(responseText); 
     1458    } 
     1459  } 
    7531460}); 
    7541461 
    755 Array.prototype.toArray = Array.prototype.clone; 
    756  
    757 function $w(string) { 
    758         string = string.strip(); 
    759         return string ? string.split(/\s+/) : []; 
    760 } 
    761  
    762 if (Prototype.Browser.Opera){ 
    763         Array.prototype.concat = function() { 
    764                 var array = []; 
    765                 for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); 
    766                 for (var i = 0, length = arguments.length; i < length; i++) { 
    767                         if (arguments[i].constructor == Array) { 
    768                                 for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) 
    769                                         array.push(arguments[i][j]); 
    770                         } else { 
    771                                 array.push(arguments[i]); 
    772                         } 
    773                 } 
    774                 return array; 
    775         } 
    776 } 
    777 var Hash = function(object) { 
    778         if (object instanceof Hash) this.merge(object); 
    779         else Object.extend(this, object || {}); 
    780 }; 
    781  
    782 Object.extend(Hash, { 
    783         toQueryString: function(obj) { 
    784                 var parts = []; 
    785                 parts.add = arguments.callee.addPair; 
    786  
    787                 this.prototype._each.call(obj, function(pair) { 
    788                         if (!pair.key) return; 
    789                         var value = pair.value; 
    790  
    791                         if (value && typeof value == 'object') { 
    792                                 if (value.constructor == Array) value.each(function(value) { 
    793                                         parts.add(pair.key, value); 
    794                                 }); 
    795                                 return; 
    796                         } 
    797                         parts.add(pair.key, value); 
    798                 }); 
    799  
    800                 return parts.join('&'); 
    801         }, 
    802  
    803         toJSON: function(object) { 
    804                 var results = []; 
    805                 this.prototype._each.call(object, function(pair) { 
    806                         var value = Object.toJSON(pair.value); 
    807                         if (value !== undefined) results.push(pair.key.toJSON() + ': ' + value); 
    808                 }); 
    809                 return '{' + results.join(', ') + '}'; 
    810         } 
    811 }); 
    812  
    813 Hash.toQueryString.addPair = function(key, value, prefix) { 
    814         key = encodeURIComponent(key); 
    815         if (value === undefined) this.push(key); 
    816         else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value))); 
    817 } 
    818  
    819 Object.extend(Hash.prototype, Enumerable); 
    820 Object.extend(Hash.prototype, { 
    821         _each: function(iterator) { 
    822                 for (var key in this) { 
    823                         var value = this[key]; 
    824                         if (value && value == Hash.prototype[key]) continue; 
    825  
    826                         var pair = [key, value]; 
    827                         pair.key = key; 
    828                         pair.value = value; 
    829                         iterator(pair); 
    830                 } 
    831         }, 
    832  
    833         keys: function() { 
    834                 return this.pluck('key'); 
    835         }, 
    836  
    837         values: function() { 
    838                 return this.pluck('value'); 
    839         }, 
    840  
    841         merge: function(hash) { 
    842                 return $H(hash).inject(this, function(mergedHash, pair) { 
    843                         mergedHash[pair.key] = pair.value; 
    844                         return mergedHash; 
    845                 }); 
    846         }, 
    847  
    848         remove: function() { 
    849                 var result; 
    850                 for(var i = 0, length = arguments.length; i < length; i++) { 
    851                         var value = this[arguments[i]]; 
    852                         if (value !== undefined){ 
    853                                 if (result === undefined) result = value; 
    854                                 else { 
    855                                         if (result.constructor != Array) result = [result]; 
    856                                         result.push(value) 
    857                                 } 
    858                         } 
    859                         delete this[arguments[i]]; 
    860                 } 
    861                 return result; 
    862         }, 
    863  
    864         toQueryString: function() { 
    865                 return Hash.toQueryString(this); 
    866         }, 
    867  
    868         inspect: function() { 
    869                 return '#<Hash:{' + this.map(function(pair) { 
    870                         return pair.map(Object.inspect).join(': '); 
    871                 }).join(', ') + '}>'; 
    872         }, 
    873  
    874         toJSON: function() { 
    875                 return Hash.toJSON(this); 
    876         } 
    877 }); 
    878  
    879 function $H(object) { 
    880         if (object instanceof Hash) return object; 
    881         return new Hash(object); 
    882 }; 
    883  
    884 // Safari iterates over shadowed properties 
    885 if (function() { 
    886         var i = 0, Test = function(value) { this.key = value }; 
    887         Test.prototype.key = 'foo'; 
    888         for (var property in new Test('bar')) i++; 
    889         return i > 1; 
    890 }()) Hash.prototype._each = function(iterator) { 
    891         var cache = []; 
    892         for (var key in this) { 
    893                 var value = this[key]; 
    894                 if ((value && value == Hash.prototype[key]) || cache.include(key)) continue; 
    895                 cache.push(key); 
    896                 var pair = [key, value]; 
    897                 pair.key = key; 
    898                 pair.value = value; 
    899                 iterator(pair); 
    900         } 
    901 }; 
    902 ObjectRange = Class.create(); 
    903 Object.extend(ObjectRange.prototype, Enumerable); 
    904 Object.extend(ObjectRange.prototype, { 
    905         initialize: function(start, end, exclusive) { 
    906                 this.start = start; 
    907                 this.end = end; 
    908                 this.exclusive = exclusive; 
    909         }, 
    910  
    911         _each: function(iterator) { 
    912                 var value = this.start; 
    913                 while (this.include(value)) { 
    914                         iterator(value); 
    915                         value = value.succ(); 
    916                 } 
    917         }, 
    918  
    919         include: function(value) { 
    920                 if (value < this.start) 
    921                         return false; 
    922                 if (this.exclusive) 
    923                         return value < this.end; 
    924                 return value <= this.end; 
    925         } 
    926 }); 
    927  
    928 var $R = function(start, end, exclusive) { 
    929         return new ObjectRange(start, end, exclusive); 
    930 } 
    931  
    932 var Ajax = { 
    933         getTransport: function() { 
    934                 return Try.these( 
    935                         function() {return new XMLHttpRequest()}, 
    936                         function() {return new ActiveXObject('Msxml2.XMLHTTP')}, 
    937                         function() {return new ActiveXObject('Microsoft.XMLHTTP')} 
    938                 ) || false; 
    939         }, 
    940  
    941         activeRequestCount: 0 
    942 } 
    943  
    944 Ajax.Responders = { 
    945         responders: [], 
    946  
    947         _each: function(iterator) { 
    948                 this.responders._each(iterator); 
    949         }, 
    950  
    951         register: function(responder) { 
    952                 if (!this.include(responder)) 
    953                         this.responders.push(responder); 
    954         }, 
    955  
    956         unregister: function(responder) { 
    957                 this.responders = this.responders.without(responder); 
    958         }, 
    959  
    960         dispatch: function(callback, request, transport, json) { 
    961                 this.each(function(responder) { 
    962                         if (typeof responder[callback] == 'function') { 
    963                                 try { 
    964                                         responder[callback].apply(responder, [request, transport, json]); 
    965                                 } catch (e) {} 
    966                         } 
    967                 }); 
    968         } 
    969 }; 
    970  
    971 Object.extend(Ajax.Responders, Enumerable); 
    972  
    973 Ajax.Responders.register({ 
    974         onCreate: function() { 
    975                 Ajax.activeRequestCount++; 
    976         }, 
    977         onComplete: function() { 
    978                 Ajax.activeRequestCount--; 
    979         } 
    980 }); 
    981  
    982 Ajax.Base = function() {}; 
    983 Ajax.Base.prototype = { 
    984         setOptions: function(options) { 
    985                 this.options = { 
    986                         method:       'post', 
    987                         asynchronous: true, 
    988                         contentType:  'application/x-www-form-urlencoded', 
    989                         encoding:     'UTF-8', 
    990                         parameters:   '' 
    991                 } 
    992                 Object.extend(this.options, options || {}); 
    993  
    994                 this.options.method = this.options.method.toLowerCase(); 
    995                 if (typeof this.options.parameters == 'string') 
    996                         this.options.parameters = this.options.parameters.toQueryParams(); 
    997         } 
    998 } 
    999  
    1000 Ajax.Request = Class.create(); 
    1001 Ajax.Request.Events = 
    1002         ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; 
    1003  
    1004 Ajax.Request.prototype = Object.extend(new Ajax.Base(), { 
    1005         _complete: false, 
    1006  
    1007         initialize: function(url, options) { 
    1008                 this.transport = Ajax.getTransport(); 
    1009                 this.setOptions(options); 
    1010                 this.request(url); 
    1011         }, 
    1012  
    1013         request: function(url) { 
    1014                 this.url = url; 
    1015                 this.method = this.options.method; 
    1016                 var params = Object.clone(this.options.parameters); 
    1017  
    1018                 if (!['get', 'post'].include(this.method)) { 
    1019                         // simulate other verbs over post 
    1020                         params['_method'] = this.method; 
    1021                         this.method = 'post'; 
    1022                 } 
    1023  
    1024                 this.parameters = params; 
    1025  
    1026                 if (params = Hash.toQueryString(params)) { 
    1027                         // when GET, append parameters to URL 
    1028                         if (this.method == 'get') 
    1029                                 this.url += (this.url.include('?') ? '&' : '?') + params; 
    1030                         else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) 
    1031                                 params += '&_='; 
    1032                 } 
    1033  
    1034                 try { 
    1035                         if (this.options.onCreate) this.options.onCreate(this.transport); 
    1036                         Ajax.Responders.dispatch('onCreate', this, this.transport); 
    1037  
    1038                         this.transport.open(this.method.toUpperCase(), this.url, 
    1039                                 this.options.asynchronous); 
    1040  
    1041                         if (this.options.asynchronous) 
    1042                                 setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); 
    1043  
    1044                         this.transport.onreadystatechange = this.onStateChange.bind(this); 
    1045                         this.setRequestHeaders(); 
    1046  
    1047                         this.body = this.method == 'post' ? (this.options.postBody || params) : null; 
    1048                         this.transport.send(this.body); 
    1049  
    1050                         /* Force Firefox to handle ready state 4 for synchronous requests */ 
    1051                         if (!this.options.asynchronous && this.transport.overrideMimeType) 
    1052                                 this.onStateChange(); 
    1053  
    1054                 } 
    1055                 catch (e) { 
    1056                         this.dispatchException(e); 
    1057                 } 
    1058         }, 
    1059  
    1060         onStateChange: function() { 
    1061                 var readyState = this.transport.readyState; 
    1062                 if (readyState > 1 && !((readyState == 4) && this._complete)) 
    1063                         this.respondToReadyState(this.transport.readyState); 
    1064         }, 
    1065  
    1066         setRequestHeaders: function() { 
    1067                 var headers = { 
    1068                         'X-Requested-With': 'XMLHttpRequest', 
    1069                         'X-Prototype-Version': Prototype.Version, 
    1070                         'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' 
    1071                 }; 
    1072  
    1073                 if (this.method == 'post') { 
    1074                         headers['Content-type'] = this.options.contentType + 
    1075                                 (this.options.encoding ? '; charset=' + this.options.encoding : ''); 
    1076  
    1077                         /* Force "Connection: close" for older Mozilla browsers to work 
    1078                          * around a bug where XMLHttpRequest sends an incorrect 
    1079                          * Content-length header. See Mozilla Bugzilla #246651. 
    1080                          */ 
    1081                         if (this.transport.overrideMimeType && 
    1082                                         (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) 
    1083                                                 headers['Connection'] = 'close'; 
    1084                 } 
    1085  
    1086                 // user-defined headers 
    1087                 if (typeof this.options.requestHeaders == 'object') { 
    1088                         var extras = this.options.requestHeaders; 
    1089  
    1090                         if (typeof extras.push == 'function') 
    1091                                 for (var i = 0, length = extras.length; i < length; i += 2) 
    1092                                         headers[extras[i]] = extras[i+1]; 
    1093                         else 
    1094                                 $H(extras).each(function(pair) { headers[pair.key] = pair.value }); 
    1095                 } 
    1096  
    1097                 for (var name in headers) 
    1098                         this.transport.setRequestHeader(name, headers[name]); 
    1099         }, 
    1100  
    1101         success: function() { 
    1102                 return !this.transport.status 
    1103                                 || (this.transport.status >= 200 && this.transport.status < 300); 
    1104         }, 
    1105  
    1106         respondToReadyState: function(readyState) { 
    1107                 var state = Ajax.Request.Events[readyState]; 
    1108                 var transport = this.transport, json = this.evalJSON(); 
    1109  
    1110                 if (state == 'Complete') { 
    1111                         try { 
    1112                                 this._complete = true; 
    1113                                 (this.options['on' + this.transport.status] 
    1114                                  || this.options['on' + (this.success() ? 'Success' : 'Failure')] 
    1115                                  || Prototype.emptyFunction)(transport, json); 
    1116                         } catch (e) { 
    1117                                 this.dispatchException(e); 
    1118                         } 
    1119  
    1120                         var contentType = this.getHeader('Content-type'); 
    1121                         if (contentType && contentType.strip(). 
    1122                                 match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) 
    1123                                         this.evalResponse(); 
    1124                 } 
    1125  
    1126                 try { 
    1127                         (this.options['on' + state] || Prototype.emptyFunction)(transport, json); 
    1128                         Ajax.Responders.dispatch('on' + state, this, transport, json); 
    1129                 } catch (e) { 
    1130                         this.dispatchException(e); 
    1131                 } 
    1132  
    1133                 if (state == 'Complete') { 
    1134                         // avoid memory leak in MSIE: clean up 
    1135                         this.transport.onreadystatechange = Prototype.emptyFunction; 
    1136                 } 
    1137         }, 
    1138  
    1139         getHeader: function(name) { 
    1140                 try { 
    1141                         return this.transport.getResponseHeader(name); 
    1142                 } catch (e) { return null } 
    1143         }, 
    1144  
    1145         evalJSON: function() { 
    1146                 try { 
    1147                         var json = this.getHeader('X-JSON'); 
    1148                         return json ? json.evalJSON() : null; 
    1149                 } catch (e) { return null } 
    1150         }, 
    1151  
    1152         evalResponse: function() { 
    1153                 try { 
    1154                         return eval((this.transport.responseText || '').unfilterJSON()); 
    1155                 } catch (e) { 
    1156                         this.dispatchException(e); 
    1157                 } 
    1158         }, 
    1159  
    1160         dispatchException: function(exception) { 
    1161                 (this.options.onException || Prototype.emptyFunction)(this, exception); 
    1162                 Ajax.Responders.dispatch('onException', this, exception); 
    1163         } 
    1164 }); 
    1165  
    1166 Ajax.Updater = Class.create(); 
    1167  
    1168 Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { 
    1169         initialize: function(container, url, options) { 
    1170                 this.container = { 
    1171                         success: (container.success || container), 
    1172                         failure: (container.failure || (container.success ? null : container)) 
    1173                 } 
    1174  
    1175                 this.transport = Ajax.getTransport(); 
    1176                 this.setOptions(options); 
    1177  
    1178                 var onComplete = this.options.onComplete || Prototype.emptyFunction; 
    1179                 this.options.onComplete = (function(transport, param) { 
    1180                         this.updateContent(); 
    1181                         onComplete(transport, param); 
    1182                 }).bind(this); 
    1183  
    1184                 this.request(url); 
    1185         }, 
    1186  
    1187         updateContent: function() { 
    1188                 var receiver = this.container[this.success() ? 'success' : 'failure']; 
    1189                 var response = this.transport.responseText; 
    1190  
    1191                 if (!this.options.evalScripts) response = response.stripScripts(); 
    1192  
    1193                 if (receiver = $(receiver)) { 
    1194                         if (this.options.insertion) 
    1195                                 new this.options.insertion(receiver, response); 
    1196                         else 
    1197                                 receiver.update(response); 
    1198                 } 
    1199  
    1200                 if (this.success()) { 
    1201                         if (this.onComplete) 
    1202                                 setTimeout(this.onComplete.bind(this), 10); 
    1203                 } 
    1204         } 
    1205 }); 
    1206  
    1207 Ajax.PeriodicalUpdater = Class.create(); 
    1208 Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { 
    1209         initialize: function(container, url, options) { 
    1210                 this.setOptions(options); 
    1211                 this.onComplete = this.options.onComplete; 
    1212  
    1213                 this.frequency = (this.options.frequency || 2); 
    1214                 this.decay = (this.options.decay || 1); 
    1215  
    1216                 this.updater = {}; 
    1217                 this.container = container; 
    1218                 this.url = url; 
    1219  
    1220                 this.start(); 
    1221         }, 
    1222  
    1223         start: function() { 
    1224                 this.options.onComplete = this.updateComplete.bind(this); 
    1225                 this.onTimerEvent(); 
    1226         }, 
    1227  
    1228         stop: function() { 
    1229                 this.updater.options.onComplete = undefined; 
    1230                 clearTimeout(this.timer); 
    1231                 (this.onComplete || Prototype.emptyFunction).apply(this, arguments); 
    1232         }, 
    1233  
    1234         updateComplete: function(request) { 
    1235                 if (this.options.decay) { 
    1236                         this.decay = (request.responseText == this.lastText ? 
    1237                                 this.decay * this.options.decay : 1); 
    1238  
    1239                         this.lastText = request.responseText; 
    1240                 } 
    1241                 this.timer = setTimeout(this.onTimerEvent.bind(this), 
    1242                         this.decay * this.frequency * 1000); 
    1243         }, 
    1244  
    1245         onTimerEvent: function() { 
    1246                 this.updater = new Ajax.Updater(this.container, this.url, this.options); 
    1247         } 
     1462Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { 
     1463  initialize: function($super, container, url, options) { 
     1464    $super(options); 
     1465    this.onComplete = this.options.onComplete; 
     1466 
     1467    this.frequency = (this.options.frequency || 2); 
     1468    this.decay = (this.options.decay || 1); 
     1469 
     1470    this.updater = { }; 
     1471    this.container = container; 
     1472    this.url = url; 
     1473 
     1474    this.start(); 
     1475  }, 
     1476 
     1477  start: function() { 
     1478    this.options.onComplete = this.updateComplete.bind(this); 
     1479    this.onTimerEvent(); 
     1480  }, 
     1481 
     1482  stop: function() { 
     1483    this.updater.options.onComplete = undefined; 
     1484    clearTimeout(this.timer); 
     1485    (this.onComplete || Prototype.emptyFunction).apply(this, arguments); 
     1486  }, 
     1487 
     1488  updateComplete: function(response) { 
     1489    if (this.options.decay) { 
     1490      this.decay = (response.responseText == this.lastText ? 
     1491        this.decay * this.options.decay : 1); 
     1492 
     1493      this.lastText = response.responseText; 
     1494    } 
     1495    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); 
     1496  }, 
     1497 
     1498  onTimerEvent: function() { 
     1499    this.updater = new Ajax.Updater(this.container, this.url, this.options); 
     1500  } 
    12481501}); 
    12491502function $(element) { 
    1250         if (arguments.length > 1) { 
    1251                 for (var i = 0, elements = [], length = arguments.length; i < length; i++) 
    1252                         elements.push($(arguments[i])); 
    1253                 return elements; 
    1254         } 
    1255         if (typeof element == 'string') 
    1256                 element = document.getElementById(element); 
    1257         return Element.extend(element); 
     1503  if (arguments.length > 1) { 
     1504    for (var i = 0, elements = [], length = arguments.length; i < length; i++) 
     1505      elements.push($(arguments[i])); 
     1506    return elements; 
     1507  } 
     1508  if (Object.isString(element)) 
     1509    element = document.getElementById(element); 
     1510  return Element.extend(element); 
    12581511} 
    12591512 
    12601513if (Prototype.BrowserFeatures.XPath) { 
    1261         document._getElementsByXPath = function(expression, parentElement) { 
    1262                 var results = []; 
    1263                 var query = document.evaluate(expression, $(parentElement) || document, 
    1264                         null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); 
    1265                 for (var i = 0, length = query.snapshotLength; i < length; i++) 
    1266                         results.push(query.snapshotItem(i)); 
    1267                 return results; 
    1268         }; 
    1269  
    1270         document.getElementsByClassName = function(className, parentElement) { 
    1271                 var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; 
    1272                 return document._getElementsByXPath(q, parentElement); 
    1273         } 
    1274  
    1275 } else document.getElementsByClassName = function(className, parentElement) { 
    1276         var children = ($(parentElement) || document.body).getElementsByTagName('*'); 
    1277         var elements = [], child, pattern = new RegExp("(^|\\s)" + className + "(\\s|$)"); 
    1278         for (var i = 0, length = children.length; i < length; i++) { 
    1279                 child = children[i]; 
    1280                 var elementClassName = child.className; 
    1281                 if (elementClassName.length == 0) continue; 
    1282                 if (elementClassName == className || elementClassName.match(pattern)) 
    1283                         elements.push(Element.extend(child)); 
    1284         } 
    1285         return elements; 
     1514  document._getElementsByXPath = function(expression, parentElement) { 
     1515    var results = []; 
     1516    var query = document.evaluate(expression, $(parentElement) || document, 
     1517      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); 
     1518    for (var i = 0, length = query.snapshotLength; i < length; i++) 
     1519      results.push(Element.extend(query.snapshotItem(i))); 
     1520    return results; 
     1521  }; 
     1522} 
     1523 
     1524/*--------------------------------------------------------------------------*/ 
     1525 
     1526if (!window.Node) var Node = { }; 
     1527 
     1528if (!Node.ELEMENT_NODE) { 
     1529  // DOM level 2 ECMAScript Language Binding 
     1530  Object.extend(Node, { 
     1531    ELEMENT_NODE: 1, 
     1532    ATTRIBUTE_NODE: 2, 
     1533    TEXT_NODE: 3, 
     1534    CDATA_SECTION_NODE: 4, 
     1535    ENTITY_REFERENCE_NODE: 5, 
     1536    ENTITY_NODE: 6, 
     1537    PROCESSING_INSTRUCTION_NODE: 7, 
     1538    COMMENT_NODE: 8, 
     1539    DOCUMENT_NODE: 9, 
     1540    DOCUMENT_TYPE_NODE: 10, 
     1541    DOCUMENT_FRAGMENT_NODE: 11, 
     1542    NOTATION_NODE: 12 
     1543  }); 
     1544} 
     1545 
     1546(function() { 
     1547  var element = this.Element; 
     1548  this.Element = function(tagName, attributes) { 
     1549    attributes = attributes || { }; 
     1550    tagName = tagName.toLowerCase(); 
     1551    var cache = Element.cache; 
     1552    if (Prototype.Browser.IE && attributes.name) { 
     1553      tagName = '<' + tagName + ' name="' + attributes.name + '">'; 
     1554      delete attributes.name; 
     1555      return Element.writeAttribute(document.createElement(tagName), attributes); 
     1556    } 
     1557    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); 
     1558    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); 
     1559  }; 
     1560  Object.extend(this.Element, element || { }); 
     1561}).call(window); 
     1562 
     1563Element.cache = { }; 
     1564 
     1565Element.Methods = { 
     1566  visible: function(element) { 
     1567    return $(element).style.display != 'none'; 
     1568  }, 
     1569 
     1570  toggle: function(element) { 
     1571    element = $(element); 
     1572    Element[Element.visible(element) ? 'hide' : 'show'](element); 
     1573    return element; 
     1574  }, 
     1575 
     1576  hide: function(element) { 
     1577    $(element).style.display = 'none'; 
     1578    return element; 
     1579  }, 
     1580 
     1581  show: function(element) { 
     1582    $(element).style.display = ''; 
     1583    return element; 
     1584  }, 
     1585 
     1586  remove: function(element) { 
     1587    element = $(element); 
     1588    element.parentNode.removeChild(element); 
     1589    return element; 
     1590  }, 
     1591 
     1592  update: function(element, content) { 
     1593    element = $(element); 
     1594    if (content && content.toElement) content = content.toElement(); 
     1595    if (Object.isElement(content)) return element.update().insert(content); 
     1596    content = Object.toHTML(content); 
     1597    element.innerHTML = content.stripScripts(); 
     1598    content.evalScripts.bind(content).defer(); 
     1599    return element; 
     1600  }, 
     1601 
     1602  replace: function(element, content) { 
     1603    element = $(element); 
     1604    if (content && content.toElement) content = content.toElement(); 
     1605    else if (!Object.isElement(content)) { 
     1606      content = Object.toHTML(content); 
     1607      var range = element.ownerDocument.createRange(); 
     1608      range.selectNode(element); 
     1609      content.evalScripts.bind(content).defer(); 
     1610      content = range.createContextualFragment(content.stripScripts()); 
     1611    } 
     1612    element.parentNode.replaceChild(content, element); 
     1613    return element; 
     1614  }, 
     1615 
     1616  insert: function(element, insertions) { 
     1617    element = $(element); 
     1618 
     1619    if (Object.isString(insertions) || Object.isNumber(insertions) || 
     1620        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) 
     1621          insertions = {bottom:insertions}; 
     1622 
     1623    var content, insert, tagName, childNodes; 
     1624 
     1625    for (var position in insertions) { 
     1626      content  = insertions[position]; 
     1627      position = position.toLowerCase(); 
     1628      insert = Element._insertionTranslations[position]; 
     1629 
     1630      if (content && content.toElement) content = content.toElement(); 
     1631      if (Object.isElement(content)) { 
     1632        insert(element, content); 
     1633        continue; 
     1634      } 
     1635 
     1636      content = Object.toHTML(content); 
     1637 
     1638      tagName = ((position == 'before' || position == 'after') 
     1639        ? element.parentNode : element).tagName.toUpperCase(); 
     1640 
     1641      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); 
     1642 
     1643      if (position == 'top' || position == 'after') childNodes.reverse(); 
     1644      childNodes.each(insert.curry(element)); 
     1645 
     1646      content.evalScripts.bind(content).defer(); 
     1647    } 
     1648 
     1649    return element; 
     1650  }, 
     1651 
     1652  wrap: function(element, wrapper, attributes) { 
     1653    element = $(element); 
     1654    if (Object.isElement(wrapper)) 
     1655      $(wrapper).writeAttribute(attributes || { }); 
     1656    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); 
     1657    else wrapper = new Element('div', wrapper); 
     1658    if (element.parentNode) 
     1659      element.parentNode.replaceChild(wrapper, element); 
     1660    wrapper.appendChild(element); 
     1661    return wrapper; 
     1662  }, 
     1663 
     1664  inspect: function(element) { 
     1665    element = $(element); 
     1666    var result = '<' + element.tagName.toLowerCase(); 
     1667    $H({'id': 'id', 'className': 'class'}).each(function(pair) { 
     1668      var property = pair.first(), attribute = pair.last(); 
     1669      var value = (element[property] || '').toString(); 
     1670      if (value) result += ' ' + attribute + '=' + value.inspect(true); 
     1671    }); 
     1672    return result + '>'; 
     1673  }, 
     1674 
     1675  recursivelyCollect: function(element, property) { 
     1676    element = $(element); 
     1677    var elements = []; 
     1678    while (element = element[property]) 
     1679      if (element.nodeType == 1) 
     1680        elements.push(Element.extend(element)); 
     1681    return elements; 
     1682  }, 
     1683 
     1684  ancestors: function(element) { 
     1685    return $(element).recursivelyCollect('parentNode'); 
     1686  }, 
     1687 
     1688  descendants: function(element) { 
     1689    return $(element).select("*"); 
     1690  }, 
     1691 
     1692  firstDescendant: function(element) { 
     1693    element = $(element).firstChild; 
     1694    while (element && element.nodeType != 1) element = element.nextSibling; 
     1695    return $(element); 
     1696  }, 
     1697 
     1698  immediateDescendants: function(element) { 
     1699    if (!(element = $(element).firstChild)) return []; 
     1700    while (element && element.nodeType != 1) element = element.nextSibling; 
     1701    if (element) return [element].concat($(element).nextSiblings()); 
     1702    return []; 
     1703  }, 
     1704 
     1705  previousSiblings: function(element) { 
     1706    return $(element).recursivelyCollect('previousSibling'); 
     1707  }, 
     1708 
     1709  nextSiblings: function(element) { 
     1710    return $(element).recursivelyCollect('nextSibling'); 
     1711  }, 
     1712 
     1713  siblings: function(element) { 
     1714    element = $(element); 
     1715    return element.previousSiblings().reverse().concat(element.nextSiblings()); 
     1716  }, 
     1717 
     1718  match: function(element, selector) { 
     1719    if (Object.isString(selector)) 
     1720      selector = new Selector(selector); 
     1721    return selector.match($(element)); 
     1722  }, 
     1723 
     1724  up: function(element, expression, index) { 
     1725    element = $(element); 
     1726    if (arguments.length == 1) return $(element.parentNode); 
     1727    var ancestors = element.ancestors(); 
     1728    return Object.isNumber(expression) ? ancestors[expression] : 
     1729      Selector.findElement(ancestors, expression, index); 
     1730  }, 
     1731 
     1732  down: function(element, expression, index) { 
     1733    element = $(element); 
     1734    if (arguments.length == 1) return element.firstDescendant(); 
     1735    return Object.isNumber(expression) ? element.descendants()[expression] : 
     1736      element.select(expression)[index || 0]; 
     1737  }, 
     1738 
     1739  previous: function(element, expression, index) { 
     1740    element = $(element); 
     1741    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); 
     1742    var previousSiblings = element.previousSiblings(); 
     1743    return Object.isNumber(expression) ? previousSiblings[expression] : 
     1744      Selector.findElement(previousSiblings, expression, index); 
     1745  }, 
     1746 
     1747  next: function(element, expression, index) { 
     1748    element = $(element); 
     1749    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); 
     1750    var nextSiblings = element.nextSiblings(); 
     1751    return Object.isNumber(expression) ? nextSiblings[expression] : 
     1752      Selector.findElement(nextSiblings, expression, index); 
     1753  }, 
     1754 
     1755  select: function() { 
     1756    var args = $A(arguments), element = $(args.shift()); 
     1757    return Selector.findChildElements(element, args); 
     1758  }, 
     1759 
     1760  adjacent: function() { 
     1761    var args = $A(arguments), element = $(args.shift()); 
     1762    return Selector.findChildElements(element.parentNode, args).without(element); 
     1763  }, 
     1764 
     1765  identify: function(element) { 
     1766    element = $(element); 
     1767    var id = element.readAttribute('id'), self = arguments.callee; 
     1768    if (id) return id; 
     1769    do { id = 'anonymous_element_' + self.counter++ } while ($(id)); 
     1770    element.writeAttribute('id', id); 
     1771    return id; 
     1772  }, 
     1773 
     1774  readAttribute: function(element, name) { 
     1775    element = $(element); 
     1776    if (Prototype.Browser.IE) { 
     1777      var t = Element._attributeTranslations.read; 
     1778      if (t.values[name]) return t.values[name](element, name); 
     1779      if (t.names[name]) name = t.names[name]; 
     1780      if (name.include(':')) { 
     1781        return (!element.attributes || !element.attributes[name]) ? null : 
     1782         element.attributes[name].value; 
     1783      } 
     1784    } 
     1785    return element.getAttribute(name); 
     1786  }, 
     1787 
     1788  writeAttribute: function(element, name, value) { 
     1789    element = $(element); 
     1790    var attributes = { }, t = Element._attributeTranslations.write; 
     1791 
     1792    if (typeof name == 'object') attributes = name; 
     1793    else attributes[name] = Object.isUndefined(value) ? true : value; 
     1794 
     1795    for (var attr in attributes) { 
     1796      name = t.names[attr] || attr; 
     1797      value = attributes[attr]; 
     1798      if (t.values[attr]) name = t.values[attr](element, value); 
     1799      if (value === false || value === null) 
     1800        element.removeAttribute(name); 
     1801      else if (value === true) 
     1802        element.setAttribute(name, name); 
     1803      else element.setAttribute(name, value); 
     1804    } 
     1805    return element; 
     1806  }, 
     1807 
     1808  getHeight: function(element) { 
     1809    return $(element).getDimensions().height; 
     1810  }, 
     1811 
     1812  getWidth: function(element) { 
     1813    return $(element).getDimensions().width; 
     1814  }, 
     1815 
     1816  classNames: function(element) { 
     1817    return new Element.ClassNames(element); 
     1818  }, 
     1819 
     1820  hasClassName: function(element, className) { 
     1821    if (!(element = $(element))) return; 
     1822    var elementClassName = element.className; 
     1823    return (elementClassName.length > 0 && (elementClassName == className || 
     1824      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); 
     1825  }, 
     1826 
     1827  addClassName: function(element, className) { 
     1828    if (!(element = $(element))) return; 
     1829    if (!element.hasClassName(className)) 
     1830      element.className += (element.className ? ' ' : '') + className; 
     1831    return element; 
     1832  }, 
     1833 
     1834  removeClassName: function(element, className) { 
     1835    if (!(element = $(element))) return; 
     1836    element.className = element.className.replace( 
     1837      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); 
     1838    return element; 
     1839  }, 
     1840 
     1841  toggleClassName: function(element, className) { 
     1842    if (!(element = $(element))) return; 
     1843    return element[element.hasClassName(className) ? 
     1844      'removeClassName' : 'addClassName'](className); 
     1845  }, 
     1846 
     1847  // removes whitespace-only text node children 
     1848  cleanWhitespace: function(element) { 
     1849    element = $(element); 
     1850    var node = element.firstChild; 
     1851    while (node) { 
     1852      var nextNode = node.nextSibling; 
     1853      if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) 
     1854        element.removeChild(node); 
     1855      node = nextNode; 
     1856    } 
     1857    return element; 
     1858  }, 
     1859 
     1860  empty: function(element) { 
     1861    return $(element).innerHTML.blank(); 
     1862  }, 
     1863 
     1864  descendantOf: function(element, ancestor) { 
     1865    element = $(element), ancestor = $(ancestor); 
     1866    var originalAncestor = ancestor; 
     1867 
     1868    if (element.compareDocumentPosition) 
     1869      return (element.compareDocumentPosition(ancestor) & 8) === 8; 
     1870 
     1871    if (element.sourceIndex && !Prototype.Browser.Opera) { 
     1872      var e = element.sourceIndex, a = ancestor.sourceIndex, 
     1873       nextAncestor = ancestor.nextSibling; 
     1874      if (!nextAncestor) { 
     1875        do { ancestor = ancestor.parentNode; } 
     1876        while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode); 
     1877      } 
     1878      if (nextAncestor && nextAncestor.sourceIndex) 
     1879       return (e > a && e < nextAncestor.sourceIndex); 
     1880    } 
     1881 
     1882    while (element = element.parentNode) 
     1883      if (element == originalAncestor) return true; 
     1884    return false; 
     1885  }, 
     1886 
     1887  scrollTo: function(element) { 
     1888    element = $(element); 
     1889    var pos = element.cumulativeOffset(); 
     1890    window.scrollTo(pos[0], pos[1]); 
     1891    return element; 
     1892  }, 
     1893 
     1894  getStyle: function(element, style) { 
     1895    element = $(element); 
     1896    style = style == 'float' ? 'cssFloat' : style.camelize(); 
     1897    var value = element.style[style]; 
     1898    if (!value) { 
     1899      var css = document.defaultView.getComputedStyle(element, null); 
     1900      value = css ? css[style] : null; 
     1901    } 
     1902    if (style == 'opacity') return value ? parseFloat(value) : 1.0; 
     1903    return value == 'auto' ? null : value; 
     1904  }, 
     1905 
     1906  getOpacity: function(element) { 
     1907    return $(element).getStyle('opacity'); 
     1908  }, 
     1909 
     1910  setStyle: function(element, styles) { 
     1911    element = $(element); 
     1912    var elementStyle = element.style, match; 
     1913    if (Object.isString(styles)) { 
     1914      element.style.cssText += ';' + styles; 
     1915      return styles.include('opacity') ? 
     1916        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; 
     1917    } 
     1918    for (var property in styles) 
     1919      if (property == 'opacity') element.setOpacity(styles[property]); 
     1920      else 
     1921        elementStyle[(property == 'float' || property == 'cssFloat') ? 
     1922          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : 
     1923            property] = styles[property]; 
     1924 
     1925    return element; 
     1926  }, 
     1927 
     1928  setOpacity: function(element, value) { 
     1929    element = $(element); 
     1930    element.style.opacity = (value == 1 || value === '') ? '' : 
     1931      (value < 0.00001) ? 0 : value; 
     1932    return element; 
     1933  }, 
     1934 
     1935  getDimensions: function(element) { 
     1936    element = $(element); 
     1937    var display = $(element).getStyle('display'); 
     1938    if (display != 'none' && display != null) // Safari bug 
     1939      return {width: element.offsetWidth, height: element.offsetHeight}; 
     1940 
     1941    // All *Width and *Height properties give 0 on elements with display none, 
     1942    // so enable the element temporarily 
     1943    var els = element.style; 
     1944    var originalVisibility = els.visibility; 
     1945    var originalPosition = els.position; 
     1946    var originalDisplay = els.display; 
     1947    els.visibility = 'hidden'; 
     1948    els.position = 'absolute'; 
     1949    els.display = 'block'; 
     1950    var originalWidth = element.clientWidth; 
     1951    var originalHeight = element.clientHeight; 
     1952    els.display = originalDisplay; 
     1953    els.position = originalPosition; 
     1954    els.visibility = originalVisibility; 
     1955    return {width: originalWidth, height: originalHeight}; 
     1956  }, 
     1957 
     1958  makePositioned: function(element) { 
     1959    element = $(element); 
     1960    var pos = Element.getStyle(element, 'position'); 
     1961    if (pos == 'static' || !pos) { 
     1962      element._madePositioned = true; 
     1963      element.style.position = 'relative'; 
     1964      // Opera returns the offset relative to the positioning context, when an 
     1965      // element is position relative but top and left have not been defined 
     1966      if (window.opera) { 
     1967        element.style.top = 0; 
     1968        element.style.left = 0; 
     1969      } 
     1970    } 
     1971    return element; 
     1972  }, 
     1973 
     1974  undoPositioned: function(element) { 
     1975    element = $(element); 
     1976    if (element._madePositioned) { 
     1977      element._madePositioned = undefined; 
     1978      element.style.position = 
     1979        element.style.top = 
     1980        element.style.left = 
     1981        element.style.bottom = 
     1982        element.style.right = ''; 
     1983    } 
     1984    return element; 
     1985  }, 
     1986 
     1987  makeClipping: function(element) { 
     1988    element = $(element); 
     1989    if (element._overflow) return element; 
     1990    element._overflow = Element.getStyle(element, 'overflow') || 'auto'; 
     1991    if (element._overflow !== 'hidden') 
     1992      element.style.overflow = 'hidden'; 
     1993    return element; 
     1994  }, 
     1995 
     1996  undoClipping: function(element) { 
     1997    element = $(element); 
     1998    if (!element._overflow) return element; 
     1999    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; 
     2000    element._overflow = null; 
     2001    return element; 
     2002  }, 
     2003 
     2004  cumulativeOffset: function(element) { 
     2005    var valueT = 0, valueL = 0; 
     2006    do { 
     2007      valueT += element.offsetTop  || 0; 
     2008      valueL += element.offsetLeft || 0; 
     2009      element = element.offsetParent; 
     2010    } while (element); 
     2011    return Element._returnOffset(valueL, valueT); 
     2012  }, 
     2013 
     2014  positionedOffset: function(element) { 
     2015    var valueT = 0, valueL = 0; 
     2016    do { 
     2017      valueT += element.offsetTop  || 0; 
     2018      valueL += element.offsetLeft || 0; 
     2019      element = element.offsetParent; 
     2020      if (element) { 
     2021        if (element.tagName == 'BODY') break; 
     2022        var p = Element.getStyle(element, 'position'); 
     2023        if (p !== 'static') break; 
     2024      } 
     2025    } while (element); 
     2026    return Element._returnOffset(valueL, valueT); 
     2027  }, 
     2028 
     2029  absolutize: function(element) { 
     2030    element = $(element); 
     2031    if (element.getStyle('position') == 'absolute') return; 
     2032    // Position.prepare(); // To be done manually by Scripty when it needs it. 
     2033 
     2034    var offsets = element.positionedOffset(); 
     2035    var top     = offsets[1]; 
     2036    var left    = offsets[0]; 
     2037    var width   = element.clientWidth; 
     2038    var height  = element.clientHeight; 
     2039 
     2040    element._originalLeft   = left - parseFloat(element.style.left  || 0); 
     2041    element._originalTop    = top  - parseFloat(element.style.top || 0); 
     2042    element._originalWidth  = element.style.width; 
     2043    element._originalHeight = element.style.height; 
     2044 
     2045    element.style.position = 'absolute'; 
     2046    element.style.top    = top + 'px'; 
     2047    element.style.left   = left + 'px'; 
     2048    element.style.width  = width + 'px'; 
     2049    element.style.height = height + 'px'; 
     2050    return element; 
     2051  }, 
     2052 
     2053  relativize: function(element) { 
     2054    element = $(element); 
     2055    if (element.getStyle('position') == 'relative') return; 
     2056    // Position.prepare(); // To be done manually by Scripty when it needs it. 
     2057 
     2058    element.style.position = 'relative'; 
     2059    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0); 
     2060    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); 
     2061 
     2062    element.style.top    = top + 'px'; 
     2063    element.style.left   = left + 'px'; 
     2064    element.style.height = element._originalHeight; 
     2065    element.style.width  = element._originalWidth; 
     2066    return element; 
     2067  }, 
     2068 
     2069  cumulativeScrollOffset: function(element) { 
     2070    var valueT = 0, valueL = 0; 
     2071    do { 
     2072      valueT += element.scrollTop  || 0; 
     2073      valueL += element.scrollLeft || 0; 
     2074      element = element.parentNode; 
     2075    } while (element); 
     2076    return Element._returnOffset(valueL, valueT); 
     2077  }, 
     2078 
     2079  getOffsetParent: function(element) { 
     2080    if (element.offsetParent) return $(element.offsetParent); 
     2081    if (element == document.body) return $(element); 
     2082 
     2083    while ((element = element.parentNode) && element != document.body) 
     2084      if (Element.getStyle(element, 'position') != 'static') 
     2085        return $(element); 
     2086 
     2087    return $(document.body); 
     2088  }, 
     2089 
     2090  viewportOffset: function(forElement) { 
     2091    var valueT = 0, valueL = 0; 
     2092 
     2093    var element = forElement; 
     2094    do { 
     2095      valueT += element.offsetTop  || 0; 
     2096      valueL += element.offsetLeft || 0; 
     2097 
     2098      // Safari fix 
     2099      if (element.offsetParent == document.body && 
     2100        Element.getStyle(element, 'position') == 'absolute') break; 
     2101 
     2102    } while (element = element.offsetParent); 
     2103 
     2104    element = forElement; 
     2105    do { 
     2106      if (!Prototype.Browser.Opera || element.tagName == 'BODY') { 
     2107        valueT -= element.scrollTop  || 0; 
     2108        valueL -= element.scrollLeft || 0; 
     2109      } 
     2110    } while (element = element.parentNode); 
     2111 
     2112    return Element._returnOffset(valueL, valueT); 
     2113  }, 
     2114 
     2115  clonePosition: function(element, source) { 
     2116    var options = Object.extend({ 
     2117      setLeft:    true, 
     2118      setTop:     true, 
     2119      setWidth:   true, 
     2120      setHeight:  true, 
     2121      offsetTop:  0, 
     2122      offsetLeft: 0 
     2123    }, arguments[2] || { }); 
     2124 
     2125    // find page position of source 
     2126    source = $(source); 
     2127    var p = source.viewportOffset(); 
     2128 
     2129    // find coordinate system to use 
     2130    element = $(element); 
     2131    var delta = [0, 0]; 
     2132    var parent = null; 
     2133    // delta [0,0] will do fine with position: fixed elements, 
     2134    // position:absolute needs offsetParent deltas 
     2135    if (Element.getStyle(element, 'position') == 'absolute') { 
     2136      parent = element.getOffsetParent(); 
     2137      delta = parent.viewportOffset(); 
     2138    } 
     2139 
     2140    // correct by body offsets (fixes Safari) 
     2141    if (parent == document.body) { 
     2142      delta[0] -= document.body.offsetLeft; 
     2143      delta[1] -= document.body.offsetTop; 
     2144    } 
     2145 
     2146    // set position 
     2147    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px'; 
     2148    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px'; 
     2149    if (options.setWidth)  element.style.width = source.offsetWidth + 'px'; 
     2150    if (options.setHeight) element.style.height = source.offsetHeight + 'px'; 
     2151    return element; 
     2152  } 
    12862153}; 
    12872154 
    1288 /*--------------------------------------------------------------------------*/ 
    1289  
    1290 if (!window.Element) var Element = {}; 
    1291  
    1292 Element.extend = function(element) { 
    1293         var F = Prototype.BrowserFeatures; 
    1294         if (!element || !element.tagName || element.nodeType == 3 || 
    1295          element._extended || F.SpecificElementExtensions || element == window) 
    1296                 return element; 
    1297  
    1298         var methods = {}, tagName = element.tagName, cache = Element.extend.cache, 
    1299          T = Element.Methods.ByTag; 
    1300  
    1301         // extend methods for all tags (Safari doesn't need this) 
    1302         if (!F.ElementExtensions) { 
    1303                 Object.extend(methods, Element.Methods), 
    1304                 Object.extend(methods, Element.Methods.Simulated); 
    1305         } 
    1306  
    1307         // extend methods for specific tags 
    1308         if (T[tagName]) Object.extend(methods, T[tagName]); 
    1309  
    1310         for (var property in methods) { 
    1311                 var value = methods[property]; 
    1312                 if (typeof value == 'function' && !(property in element)) 
    1313                         element[property] = cache.findOrStore(value); 
    1314         } 
    1315  
    1316         element._extended = Prototype.emptyFunction; 
    1317         return element; 
     2155Element.Methods.identify.counter = 1; 
     2156 
     2157Object.extend(Element.Methods, { 
     2158  getElementsBySelector: Element.Methods.select, 
     2159  childElements: Element.Methods.immediateDescendants 
     2160}); 
     2161 
     2162Element._attributeTranslations = { 
     2163  write: { 
     2164    names: { 
     2165      className: 'class', 
     2166      htmlFor:   'for' 
     2167    }, 
     2168    values: { } 
     2169  } 
    13182170}; 
    13192171 
    1320 Element.extend.cache = { 
    1321         findOrStore: function(value) { 
    1322                 return this[value] = this[value] || function() { 
    1323                         return value.apply(null, [this].concat($A(arguments))); 
    1324                 } 
    1325         } 
     2172if (Prototype.Browser.Opera) { 
     2173  Element.Methods.getStyle = Element.Methods.getStyle.wrap( 
     2174    function(proceed, element, style) { 
     2175      switch (style) { 
     2176        case 'left': case 'top': case 'right': case 'bottom': 
     2177          if (proceed(element, 'position') === 'static') return null; 
     2178        case 'height': case 'width': 
     2179          // returns '0px' for hidden elements; we want it to return null 
     2180          if (!Element.visible(element)) return null; 
     2181 
     2182          // returns the border-box dimensions rather than the content-box 
     2183          // dimensions, so we subtract padding and borders from the value 
     2184          var dim = parseInt(proceed(element, style), 10); 
     2185 
     2186          if (dim !== element['offset' + style.capitalize()]) 
     2187            return dim + 'px'; 
     2188 
     2189          var properties; 
     2190          if (style === 'height') { 
     2191            properties = ['border-top-width', 'padding-top', 
     2192             'padding-bottom', 'border-bottom-width']; 
     2193          } 
     2194          else { 
     2195            properties = ['border-left-width', 'padding-left', 
     2196             'padding-right', 'border-right-width']; 
     2197          } 
     2198          return properties.inject(dim, function(memo, property) { 
     2199            var val = proceed(element, property); 
     2200            return val === null ? memo : memo - parseInt(val, 10); 
     2201          }) + 'px'; 
     2202        default: return proceed(element, style); 
     2203      } 
     2204    } 
     2205  ); 
     2206 
     2207  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( 
     2208    function(proceed, element, attribute) { 
     2209      if (attribute === 'title') return element.title; 
     2210      return proceed(element, attribute); 
     2211    } 
     2212  ); 
     2213} 
     2214 
     2215else if (Prototype.Browser.IE) { 
     2216  // IE doesn't report offsets correctly for static elements, so we change them 
     2217  // to "relative" to get the values, then change them back. 
     2218  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( 
     2219    function(proceed, element) { 
     2220      element = $(element); 
     2221      var position = element.getStyle('position'); 
     2222      if (position !== 'static') return proceed(element); 
     2223      element.setStyle({ position: 'relative' }); 
     2224      var value = proceed(element); 
     2225      element.setStyle({ position: position }); 
     2226      return value; 
     2227    } 
     2228  ); 
     2229 
     2230  $w('positionedOffset viewportOffset').each(function(method) { 
     2231    Element.Methods[method] = Element.Methods[method].wrap( 
     2232      function(proceed, element) { 
     2233        element = $(element); 
     2234        var position = element.getStyle('position'); 
     2235        if (position !== 'static') return proceed(element); 
     2236        // Trigger hasLayout on the offset parent so that IE6 reports 
     2237        // accurate offsetTop and offsetLeft values for position: fixed. 
     2238        var offsetParent = element.getOffsetParent(); 
     2239        if (offsetParent && offsetParent.getStyle('position') === 'fixed') 
     2240          offsetParent.setStyle({ zoom: 1 }); 
     2241        element.setStyle({ position: 'relative' }); 
     2242        var value = proceed(element); 
     2243        element.setStyle({ position: position }); 
     2244        return value; 
     2245      } 
     2246    ); 
     2247  }); 
     2248 
     2249  Element.Methods.getStyle = function(element, style) { 
     2250    element = $(element); 
     2251    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); 
     2252    var value = element.style[style]; 
     2253    if (!value && element.currentStyle) value = element.currentStyle[style]; 
     2254 
     2255    if (style == 'opacity') { 
     2256      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) 
     2257        if (value[1]) return parseFloat(value[1]) / 100; 
     2258      return 1.0; 
     2259    } 
     2260 
     2261    if (value == 'auto') { 
     2262      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) 
     2263        return element['offset' + style.capitalize()] + 'px'; 
     2264      return null; 
     2265    } 
     2266    return value; 
     2267  }; 
     2268 
     2269  Element.Methods.setOpacity = function(element, value) { 
     2270    function stripAlpha(filter){ 
     2271      return filter.replace(/alpha\([^\)]*\)/gi,''); 
     2272    } 
     2273    element = $(element); 
     2274    var currentStyle = element.currentStyle; 
     2275    if ((currentStyle && !currentStyle.hasLayout) || 
     2276      (!currentStyle && element.style.zoom == 'normal')) 
     2277        element.style.zoom = 1; 
     2278 
     2279    var filter = element.getStyle('filter'), style = element.style; 
     2280    if (value == 1 || value === '') { 
     2281      (filter = stripAlpha(filter)) ? 
     2282        style.filter = filter : style.removeAttribute('filter'); 
     2283      return element; 
     2284    } else if (value < 0.00001) value = 0; 
     2285    style.filter = stripAlpha(filter) + 
     2286      'alpha(opacity=' + (value * 100) + ')'; 
     2287    return element; 
     2288  }; 
     2289 
     2290  Element._attributeTranslations = { 
     2291    read: { 
     2292      names: { 
     2293        'class': 'className', 
     2294        'for':   'htmlFor' 
     2295      }, 
     2296      values: { 
     2297        _getAttr: function(element, attribute) { 
     2298          return element.getAttribute(attribute, 2); 
     2299        }, 
     2300        _getAttrNode: function(element, attribute) { 
     2301          var node = element.getAttributeNode(attribute); 
     2302          return node ? node.value : ""; 
     2303        }, 
     2304        _getEv: function(element, attribute) { 
     2305          attribute = element.getAttribute(attribute); 
     2306          return attribute ? attribute.toString().slice(23, -2) : null; 
     2307        }, 
     2308        _flag: function(element, attribute) { 
     2309          return $(element).hasAttribute(attribute) ? attribute : null; 
     2310        }, 
     2311        style: function(element) { 
     2312          return element.style.cssText.toLowerCase(); 
     2313        }, 
     2314        title: function(element) { 
     2315          return element.title; 
     2316        } 
     2317      } 
     2318    } 
     2319  }; 
     2320 
     2321  Element._attributeTranslations.write = { 
     2322    names: Object.extend({ 
     2323      cellpadding: 'cellPadding', 
     2324      cellspacing: 'cellSpacing' 
     2325    }, Element._attributeTranslations.read.names), 
     2326    values: { 
     2327      checked: function(element, value) { 
     2328        element.checked = !!value; 
     2329      }, 
     2330 
     2331      style: function(element, value) { 
     2332        element.style.cssText = value ? value : ''; 
     2333      } 
     2334    } 
     2335  }; 
     2336 
     2337  Element._attributeTranslations.has = {}; 
     2338 
     2339  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + 
     2340      'encType maxLength readOnly longDesc').each(function(attr) { 
     2341    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; 
     2342    Element._attributeTranslations.has[attr.toLowerCase()] = attr; 
     2343  }); 
     2344 
     2345  (function(v) { 
     2346    Object.extend(v, { 
     2347      href:        v._getAttr, 
     2348      src:         v._getAttr, 
     2349      type:        v._getAttr, 
     2350      action:      v._getAttrNode, 
     2351      disabled:    v._flag, 
     2352      checked:     v._flag, 
     2353      readonly:    v._flag, 
     2354      multiple:    v._flag, 
     2355      onload:      v._getEv, 
     2356      onunload:    v._getEv, 
     2357      onclick:     v._getEv, 
     2358      ondblclick:  v._getEv, 
     2359      onmousedown: v._getEv, 
     2360      onmouseup:   v._getEv, 
     2361      onmouseover: v._getEv, 
     2362      onmousemove: v._getEv, 
     2363      onmouseout:  v._getEv, 
     2364      onfocus:     v._getEv, 
     2365      onblur:      v._getEv, 
     2366      onkeypress:  v._getEv, 
     2367      onkeydown:   v._getEv, 
     2368      onkeyup:     v._getEv, 
     2369      onsubmit:    v._getEv, 
     2370      onreset:     v._getEv, 
     2371      onselect:    v._getEv, 
     2372      onchange:    v._getEv 
     2373    }); 
     2374  })(Element._attributeTranslations.read.values); 
     2375} 
     2376 
     2377else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { 
     2378  Element.Methods.setOpacity = function(element, value) { 
     2379    element = $(element); 
     2380    element.style.opacity = (value == 1) ? 0.999999 : 
     2381      (value === '') ? '' : (value < 0.00001) ? 0 : value; 
     2382    return element; 
     2383  }; 
     2384} 
     2385 
     2386else if (Prototype.Browser.WebKit) { 
     2387  Element.Methods.setOpacity = function(element, value) { 
     2388    element = $(element); 
     2389    element.style.opacity = (value == 1 || value === '') ? '' : 
     2390      (value < 0.00001) ? 0 : value; 
     2391 
     2392    if (value == 1) 
     2393      if(element.tagName == 'IMG' && element.width) { 
     2394        element.width++; element.width--; 
     2395      } else try { 
     2396        var n = document.createTextNode(' '); 
     2397        element.appendChild(n); 
     2398        element.removeChild(n); 
     2399      } catch (e) { } 
     2400 
     2401    return element; 
     2402  }; 
     2403 
     2404  // Safari returns margins on body which is incorrect if the child is absolutely 
     2405  // positioned.  For performance reasons, redefine Element#cumulativeOffset for 
     2406  // KHTML/WebKit only. 
     2407  Element.Methods.cumulativeOffset = function(element) { 
     2408    var valueT = 0, valueL = 0; 
     2409    do { 
     2410      valueT += element.offsetTop  || 0; 
     2411      valueL += element.offsetLeft || 0; 
     2412      if (element.offsetParent == document.body) 
     2413        if (Element.getStyle(element, 'position') == 'absolute') break; 
     2414 
     2415      element = element.offsetParent; 
     2416    } while (element); 
     2417 
     2418    return Element._returnOffset(valueL, valueT); 
     2419  }; 
     2420} 
     2421 
     2422if (Prototype.Browser.IE || Prototype.Browser.Opera) { 
     2423  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements 
     2424  Element.Methods.update = function(element, content) { 
     2425    element = $(element); 
     2426 
     2427    if (content && content.toElement) content = content.toElement(); 
     2428    if (Object.isElement(content)) return element.update().insert(content); 
     2429 
     2430    content = Object.toHTML(content); 
     2431    var tagName = element.tagName.toUpperCase(); 
     2432 
     2433    if (tagName in Element._insertionTranslations.tags) { 
     2434      $A(element.childNodes).each(function(node) { element.removeChild(node) }); 
     2435      Element._getContentFromAnonymousElement(tagName, content.stripScripts()) 
     2436        .each(function(node) { element.appendChild(node) }); 
     2437    } 
     2438    else element.innerHTML = content.stripScripts(); 
     2439 
     2440    content.evalScripts.bind(content).defer(); 
     2441    return element; 
     2442  }; 
     2443} 
     2444 
     2445if ('outerHTML' in document.createElement('div')) { 
     2446  Element.Methods.replace = function(element, content) { 
     2447    element = $(element); 
     2448 
     2449    if (content && content.toElement) content = content.toElement(); 
     2450    if (Object.isElement(content)) { 
     2451      element.parentNode.replaceChild(content, element); 
     2452      return element; 
     2453    } 
     2454 
     2455    content = Object.toHTML(content); 
     2456    var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); 
     2457 
     2458    if (Element._insertionTranslations.tags[tagName]) { 
     2459      var nextSibling = element.next(); 
     2460      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); 
     2461      parent.removeChild(element); 
     2462      if (nextSibling) 
     2463        fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); 
     2464      else 
     2465        fragments.each(function(node) { parent.appendChild(node) }); 
     2466    } 
     2467    else element.outerHTML = content.stripScripts(); 
     2468 
     2469    content.evalScripts.bind(content).defer(); 
     2470    return element; 
     2471  }; 
     2472} 
     2473 
     2474Element._returnOffset = function(l, t) { 
     2475  var result = [l, t]; 
     2476  result.left = l; 
     2477  result.top = t; 
     2478  return result; 
    13262479}; 
    13272480 
    1328 Element.Methods = { 
    1329         visible: function(element) { 
    1330                 return $(element).style.display != 'none'; 
    1331         }, 
    1332  
    1333         toggle: function(element) { 
    1334                 element = $(element); 
    1335                 Element[Element.visible(element) ? 'hide' : 'show'](element); 
    1336                 return element; 
    1337         }, 
    1338  
    1339         hide: function(element) { 
    1340                 $(element).style.display = 'none'; 
    1341                 return element; 
    1342         }, 
    1343  
    1344         show: function(element) { 
    1345                 $(element).style.display = ''; 
    1346                 return element; 
    1347         }, 
    1348  
    1349         remove: function(element) { 
    1350                 element = $(element); 
    1351                 element.parentNode.removeChild(element); 
    1352                 return element; 
    1353         }, 
    1354  
    1355         update: function(element, html) { 
    1356                 html = typeof html == 'undefined' ? '' : html.toString(); 
    1357                 $(element).innerHTML = html.stripScripts(); 
    1358                 setTimeout(function() {html.evalScripts()}, 10); 
    1359                 return element; 
    1360         }, 
    1361  
    1362         replace: function(element, html) { 
    1363                 element = $(element); 
    1364                 html = typeof html == 'undefined' ? '' : html.toString(); 
    1365                 if (element.outerHTML) { 
    1366                         element.outerHTML = html.stripScripts(); 
    1367                 } else { 
    1368                         var range = element.ownerDocument.createRange(); 
    1369                         range.selectNodeContents(element); 
    1370                         element.parentNode.replaceChild( 
    1371                                 range.createContextualFragment(html.stripScripts()), element); 
    1372                 } 
    1373                 setTimeout(function() {html.evalScripts()}, 10); 
    1374                 return element; 
    1375         }, 
    1376  
    1377         inspect: function(element) { 
    1378                 element = $(element); 
    1379                 var result = '<' + element.tagName.toLowerCase(); 
    1380                 $H({'id': 'id', 'className': 'class'}).each(function(pair) { 
    1381                         var property = pair.first(), attribute = pair.last(); 
    1382                         var value = (element[property] || '').toString(); 
    1383                         if (value) result += ' ' + attribute + '=' + value.inspect(true); 
    1384                 }); 
    1385                 return result + '>'; 
    1386         }, 
    1387  
    1388         recursivelyCollect: function(element, property) { 
    1389                 element = $(element); 
    1390                 var elements = []; 
    1391                 while (element = element[property]) 
    1392                         if (element.nodeType == 1) 
    1393                                 elements.push(Element.extend(element)); 
    1394                 return elements; 
    1395         }, 
    1396  
    1397         ancestors: function(element) { 
    1398                 return $(element).recursivelyCollect('parentNode'); 
    1399         }, 
    1400  
    1401         descendants: function(element) { 
    1402                 return $A($(element).getElementsByTagName('*')).each(Element.extend); 
    1403         }, 
    1404  
    1405         firstDescendant: function(element) { 
    1406                 element = $(element).firstChild; 
    1407                 while (element && element.nodeType != 1) element = element.nextSibling; 
    1408                 return $(element); 
    1409         }, 
    1410  
    1411         immediateDescendants: function(element) { 
    1412                 if (!(element = $(element).firstChild)) return []; 
    1413                 while (element && element.nodeType != 1) element = element.nextSibling; 
    1414                 if (element) return [element].concat($(element).nextSiblings()); 
    1415                 return []; 
    1416         }, 
    1417  
    1418         previousSiblings: function(element) { 
    1419                 return $(element).recursivelyCollect('previousSibling'); 
    1420         }, 
    1421  
    1422         nextSiblings: function(element) { 
    1423                 return $(element).recursivelyCollect('nextSibling'); 
    1424         }, 
    1425  
    1426         siblings: function(element) { 
    1427                 element = $(element); 
    1428                 return element.previousSiblings().reverse().concat(element.nextSiblings()); 
    1429         }, 
    1430  
    1431         match: function(element, selector) { 
    1432                 if (typeof selector == 'string') 
    1433                         selector = new Selector(selector); 
    1434                 return selector.match($(element)); 
    1435         }, 
    1436  
    1437         up: function(element, expression, index) { 
    1438                 element = $(element); 
    1439                 if (arguments.length == 1) return $(element.parentNode); 
    1440                 var ancestors = element.ancestors(); 
    1441                 return expression ? Selector.findElement(ancestors, expression, index) : 
    1442                         ancestors[index || 0]; 
    1443         }, 
    1444  
    1445         down: function(element, expression, index) { 
    1446                 element = $(element); 
    1447                 if (arguments.length == 1) return element.firstDescendant(); 
    1448                 var descendants = element.descendants(); 
    1449                 return expression ? Selector.findElement(descendants, expression, index) : 
    1450                         descendants[index || 0]; 
    1451         }, 
    1452  
    1453         previous: function(element, expression, index) { 
    1454                 element = $(element); 
    1455                 if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); 
    1456                 var previousSiblings = element.previousSiblings(); 
    1457                 return expression ? Selector.findElement(previousSiblings, expression, index) : 
    1458                         previousSiblings[index || 0]; 
    1459         }, 
    1460  
    1461         next: function(element, expression, index) { 
    1462                 element = $(element); 
    1463                 if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); 
    1464                 var nextSiblings = element.nextSiblings(); 
    1465                 return expression ? Selector.findElement(nextSiblings, expression, index) : 
    1466                         nextSiblings[index || 0]; 
    1467         }, 
    1468  
    1469         getElementsBySelector: function() { 
    1470                 var args = $A(arguments), element = $(args.shift()); 
    1471                 return Selector.findChildElements(element, args); 
    1472         }, 
    1473  
    1474         getElementsByClassName: function(element, className) { 
    1475                 return document.getElementsByClassName(className, element); 
    1476         }, 
    1477  
    1478         readAttribute: function(element, name) { 
    1479                 element = $(element); 
    1480                 if (Prototype.Browser.IE) { 
    1481                         if (!element.attributes) return null; 
    1482                         var t = Element._attributeTranslations; 
    1483                         if (t.values[name]) return t.values[name](element, name); 
    1484                         if (t.names[name])  name = t.names[name]; 
    1485                         var attribute = element.attributes[name]; 
    1486                         return attribute ? attribute.nodeValue : null; 
    1487                 } 
    1488                 return element.getAttribute(name); 
    1489         }, 
    1490  
    1491         getHeight: function(element) { 
    1492                 return $(element).getDimensions().height; 
    1493         }, 
    1494  
    1495         getWidth: function(element) { 
    1496                 return $(element).getDimensions().width; 
    1497         }, 
    1498  
    1499         classNames: function(element) { 
    1500                 return new Element.ClassNames(element); 
    1501         }, 
    1502  
    1503         hasClassName: function(element, className) { 
    1504                 if (!(element = $(element))) return; 
    1505                 var elementClassName = element.className; 
    1506                 if (elementClassName.length == 0) return false; 
    1507                 if (elementClassName == className || 
    1508                                 elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) 
    1509                         return true; 
    1510                 return false; 
    1511         }, 
    1512  
    1513         addClassName: function(element, className) { 
    1514                 if (!(element = $(element))) return; 
    1515                 Element.classNames(element).add(className); 
    1516                 return element; 
    1517         }, 
    1518  
    1519         removeClassName: function(element, className) { 
    1520                 if (!(element = $(element))) return; 
    1521                 Element.classNames(element).remove(className); 
    1522                 return element; 
    1523         }, 
    1524  
    1525         toggleClassName: function(element, className) { 
    1526                 if (!(element = $(element))) return; 
    1527                 Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className); 
    1528                 return element; 
    1529         }, 
    1530  
    1531         observe: function() { 
    1532                 Event.observe.apply(Event, arguments); 
    1533                 return $A(arguments).first(); 
    1534         }, 
    1535  
    1536         stopObserving: function() { 
    1537                 Event.stopObserving.apply(Event, arguments); 
    1538                 return $A(arguments).first(); 
    1539         }, 
    1540  
    1541         // removes whitespace-only text node children 
    1542         cleanWhitespace: function(element) { 
    1543                 element = $(element); 
    1544                 var node = element.firstChild; 
    1545                 while (node) { 
    1546                         var nextNode = node.nextSibling; 
    1547                         if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) 
    1548                                 element.removeChild(node); 
    1549                         node = nextNode; 
    1550                 } 
    1551                 return element; 
    1552         }, 
    1553  
    1554         empty: function(element) { 
    1555                 return $(element).innerHTML.blank(); 
    1556         }, 
    1557  
    1558         descendantOf: function(element, ancestor) { 
    1559                 element = $(element), ancestor = $(ancestor); 
    1560                 while (element = element.parentNode) 
    1561                         if (element == ancestor) return true; 
    1562                 return false; 
    1563         }, 
    1564  
    1565         scrollTo: function(element) { 
    1566                 element = $(element); 
    1567                 var pos = Position.cumulativeOffset(element); 
    1568                 window.scrollTo(pos[0], pos[1]); 
    1569                 return element; 
    1570         }, 
    1571  
    1572         getStyle: function(element, style) { 
    1573                 element = $(element); 
    1574                 style = style == 'float' ? 'cssFloat' : style.camelize(); 
    1575                 var value = element.style[style]; 
    1576                 if (!value) { 
    1577                         var css = document.defaultView.getComputedStyle(element, null); 
    1578                         value = css ? css[style] : null; 
    1579                 } 
    1580                 if (style == 'opacity') return value ? parseFloat(value) : 1.0; 
    1581                 return value == 'auto' ? null : value; 
    1582         }, 
    1583  
    1584         getOpacity: function(element) { 
    1585                 return $(element).getStyle('opacity'); 
    1586         }, 
    1587  
    1588         setStyle: function(element, styles, camelized) { 
    1589                 element = $(element); 
    1590                 var elementStyle = element.style; 
    1591  
    1592                 for (var property in styles) 
    1593                         if (property == 'opacity') element.setOpacity(styles[property]) 
    1594                         else 
    1595                                 elementStyle[(property == 'float' || property == 'cssFloat') ? 
    1596                                         (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') : 
    1597                                         (camelized ? property : property.camelize())] = styles[property]; 
    1598  
    1599                 return element; 
    1600         }, 
    1601  
    1602         setOpacity: function(element, value) { 
    1603                 element = $(element); 
    1604                 element.style.opacity = (value == 1 || value === '') ? '' : 
    1605                         (value < 0.00001) ? 0 : value; 
    1606                 return element; 
    1607         }, 
    1608  
    1609         getDimensions: function(element) { 
    1610                 element = $(element); 
    1611                 var display = $(element).getStyle('display'); 
    1612                 if (display != 'none' && display != null) // Safari bug 
    1613                         return {width: element.offsetWidth, height: element.offsetHeight}; 
    1614  
    1615                 // All *Width and *Height properties give 0 on elements with display none, 
    1616                 // so enable the element temporarily 
    1617                 var els = element.style; 
    1618                 var originalVisibility = els.visibility; 
    1619                 var originalPosition = els.position; 
    1620                 var originalDisplay = els.display; 
    1621                 els.visibility = 'hidden'; 
    1622                 els.position = 'absolute'; 
    1623                 els.display = 'block'; 
    1624                 var originalWidth = element.clientWidth; 
    1625                 var originalHeight = element.clientHeight; 
    1626                 els.display = originalDisplay; 
    1627                 els.position = originalPosition; 
    1628                 els.visibility = originalVisibility; 
    1629                 return {width: originalWidth, height: originalHeight}; 
    1630         }, 
    1631  
    1632         makePositioned: function(element) { 
    1633                 element = $(element); 
    1634                 var pos = Element.getStyle(element, 'position'); 
    1635                 if (pos == 'static' || !pos) { 
    1636                         element._madePositioned = true; 
    1637                         element.style.position = 'relative'; 
    1638                         // Opera returns the offset relative to the positioning context, when an 
    1639                         // element is position relative but top and left have not been defined 
    1640                         if (window.opera) { 
    1641                                 element.style.top = 0; 
    1642                                 element.style.left = 0; 
    1643                         } 
    1644                 } 
    1645                 return element; 
    1646         }, 
    1647  
    1648         undoPositioned: function(element) { 
    1649                 element = $(element); 
    1650                 if (element._madePositioned) { 
    1651                         element._madePositioned = undefined; 
    1652                         element.style.position = 
    1653                                 element.style.top = 
    1654                                 element.style.left = 
    1655                                 element.style.bottom = 
    1656                                 element.style.right = ''; 
    1657                 } 
    1658                 return element; 
    1659         }, 
    1660  
    1661         makeClipping: function(element) { 
    1662                 element = $(element); 
    1663                 if (element._overflow) return element; 
    1664                 element._overflow = element.style.overflow || 'auto'; 
    1665                 if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') 
    1666                         element.style.overflow = 'hidden'; 
    1667                 return element; 
    1668         }, 
    1669  
    1670         undoClipping: function(element) { 
    1671                 element = $(element); 
    1672                 if (!element._overflow) return element; 
    1673                 element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; 
    1674                 element._overflow = null; 
    1675                 return element; 
    1676         } 
     2481Element._getContentFromAnonymousElement = function(tagName, html) { 
     2482  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; 
     2483  if (t) { 
     2484    div.innerHTML = t[0] + html + t[1]; 
     2485    t[2].times(function() { div = div.firstChild }); 
     2486  } else div.innerHTML = html; 
     2487  return $A(div.childNodes); 
    16772488}; 
    16782489 
    1679 Object.extend(Element.Methods, { 
    1680         childOf: Element.Methods.descendantOf, 
    1681         childElements: Element.Methods.immediateDescendants 
    1682 }); 
    1683  
    1684 if (Prototype.Browser.Opera) { 
    1685         Element.Methods._getStyle = Element.Methods.getStyle; 
    1686         Element.Methods.getStyle = function(element, style) { 
    1687                 switch(style) { 
    1688                         case 'left': 
    1689                         case 'top': 
    1690                         case 'right': 
    1691                         case 'bottom': 
    1692                                 if (Element._getStyle(element, 'position') == 'static') return null; 
    1693                         default: return Element._getStyle(element, style); 
    1694                 } 
    1695         }; 
     2490Element._insertionTranslations = { 
     2491  before: function(element, node) { 
     2492    element.parentNode.insertBefore(node, element); 
     2493  }, 
     2494  top: function(element, node) { 
     2495    element.insertBefore(node, element.firstChild); 
     2496  }, 
     2497  bottom: function(element, node) { 
     2498    element.appendChild(node); 
     2499  }, 
     2500  after: function(element, node) { 
     2501    element.parentNode.insertBefore(node, element.nextSibling); 
     2502  }, 
     2503  tags: { 
     2504    TABLE:  ['<table>',                '</table>',                   1], 
     2505    TBODY:  ['<table><tbody>',         '</tbody></table>',           2], 
     2506    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3], 
     2507    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4], 
     2508    SELECT: ['<select>',               '</select>',                  1] 
     2509  } 
     2510}; 
     2511 
     2512(function() { 
     2513  Object.extend(this.tags, { 
     2514    THEAD: this.tags.TBODY, 
     2515    TFOOT: this.tags.TBODY, 
     2516    TH:    this.tags.TD 
     2517  }); 
     2518}).call(Element._insertionTranslations); 
     2519 
     2520Element.Methods.Simulated = { 
     2521  hasAttribute: function(element, attribute) { 
     2522    attribute = Element._attributeTranslations.has[attribute] || attribute; 
     2523    var node = $(element).getAttributeNode(attribute); 
     2524    return node && node.specified; 
     2525  } 
     2526}; 
     2527 
     2528Element.Methods.ByTag = { }; 
     2529 
     2530Object.extend(Element, Element.Methods); 
     2531 
     2532if (!Prototype.BrowserFeatures.ElementExtensions && 
     2533    document.createElement('div').__proto__) { 
     2534  window.HTMLElement = { }; 
     2535  window.HTMLElement.prototype = document.createElement('div').__proto__; 
     2536  Prototype.BrowserFeatures.ElementExtensions = true; 
    16962537} 
    1697 else if (Prototype.Browser.IE) { 
    1698         Element.Methods.getStyle = function(element, style) { 
    1699                 element = $(element); 
    1700                 style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); 
    1701                 var value = element.style[style]; 
    1702                 if (!value && element.currentStyle) value = element.currentStyle[style]; 
    1703  
    1704                 if (style == 'opacity') { 
    1705                         if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) 
    1706                                 if (value[1]) return parseFloat(value[1]) / 100; 
    1707                         return 1.0; 
    1708                 } 
    1709  
    1710                 if (value == 'auto') { 
    1711                         if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) 
    1712                                 return element['offset'+style.capitalize()] + 'px'; 
    1713                         return null; 
    1714                 } 
    1715                 return value; 
    1716         }; 
    1717  
    1718         Element.Methods.setOpacity = function(element, value) { 
    1719                 element = $(element); 
    1720                 var filter = element.getStyle('filter'), style = element.style; 
    1721                 if (value == 1 || value === '') { 
    1722                         style.filter = filter.replace(/alpha\([^\)]*\)/gi,''); 
    1723                         return element; 
    1724                 } else if (value < 0.00001) value = 0; 
    1725                 style.filter = filter.replace(/alpha\([^\)]*\)/gi, '') + 
    1726                         'alpha(opacity=' + (value * 100) + ')'; 
    1727                 return element; 
    1728         }; 
    1729  
    1730         // IE is missing .innerHTML support for TABLE-related elements 
    1731         Element.Methods.update = function(element, html) { 
    1732                 element = $(element); 
    1733                 html = typeof html == 'undefined' ? '' : html.toString(); 
    1734                 var tagName = element.tagName.toUpperCase(); 
    1735                 if (['THEAD','TBODY','TR','TD'].include(tagName)) { 
    1736                         var div = document.createElement('div'); 
    1737                         switch (tagName) { 
    1738                                 case 'THEAD': 
    1739                                 case 'TBODY': 
    1740                                         div.innerHTML = '<table><tbody>' +  html.stripScripts() + '</tbody></table>'; 
    1741                                         depth = 2; 
    1742                                         break; 
    1743                                 case 'TR': 
    1744                                         div.innerHTML = '<table><tbody><tr>' +  html.stripScripts() + '</tr></tbody></table>'; 
    1745                                         depth = 3; 
    1746                                         break; 
    1747                                 case 'TD': 
    1748                                         div.innerHTML = '<table><tbody><tr><td>' +  html.stripScripts() + '</td></tr></tbody></table>'; 
    1749                                         depth = 4; 
    1750                         } 
    1751                         $A(element.childNodes).each(function(node) { element.removeChild(node) }); 
    1752                         depth.times(function() { div = div.firstChild }); 
    1753                         $A(div.childNodes).each(function(node) { element.appendChild(node) }); 
    1754                 } else { 
    1755                         element.innerHTML = html.stripScripts(); 
    1756                 } 
    1757                 setTimeout(function() { html.evalScripts() }, 10); 
    1758                 return element; 
    1759         } 
    1760 } 
    1761 else if (Prototype.Browser.Gecko) { 
    1762         Element.Methods.setOpacity = function(element, value) { 
    1763                 element = $(element); 
    1764                 element.style.opacity = (value == 1) ? 0.999999 : 
    1765                         (value === '') ? '' : (value < 0.00001) ? 0 : value; 
    1766                 return element; 
    1767         }; 
    1768 } 
    1769  
    1770 Element._attributeTranslations = { 
    1771         names: { 
    1772                 colspan:   "colSpan", 
    1773                 rowspan:   "rowSpan", 
    1774                 valign:    "vAlign", 
    1775                 datetime:  "dateTime", 
    1776                 accesskey: "accessKey", 
    1777                 tabindex:  "tabIndex", 
    1778                 enctype:   "encType", 
    1779                 maxlength: "maxLength", 
    1780                 readonly:  "readOnly", 
    1781                 longdesc:  "longDesc" 
    1782         }, 
    1783         values: { 
    1784                 _getAttr: function(element, attribute) { 
    1785                         return element.getAttribute(attribute, 2); 
    1786                 }, 
    1787                 _flag: function(element, attribute) { 
    1788                         return $(element).hasAttribute(attribute) ? attribute : null; 
    1789                 }, 
    1790                 style: function(element) { 
    1791                         return element.style.cssText.toLowerCase(); 
    1792                 }, 
    1793                 title: function(element) { 
    1794                         var node = element.getAttributeNode('title'); 
    1795                         return node.specified ? node.nodeValue : null; 
    1796                 } 
    1797         } 
     2538 
     2539Element.extend = (function() { 
     2540  if (Prototype.BrowserFeatures.SpecificElementExtensions) 
     2541    return Prototype.K; 
     2542 
     2543  var Methods = { }, ByTag = Element.Methods.ByTag; 
     2544 
     2545  var extend = Object.extend(function(element) { 
     2546    if (!element || element._extendedByPrototype || 
     2547        element.nodeType != 1 || element == window) return element; 
     2548 
     2549    var methods = Object.clone(Methods), 
     2550      tagName = element.tagName, property, value; 
     2551 
     2552    // extend methods for specific tags 
     2553    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); 
     2554 
     2555    for (property in methods) { 
     2556      value = methods[property]; 
     2557      if (Object.isFunction(value) && !(property in element)) 
     2558        element[property] = value.methodize(); 
     2559    } 
     2560 
     2561    element._extendedByPrototype = Prototype.emptyFunction; 
     2562    return element; 
     2563 
     2564  }, { 
     2565    refresh: function() { 
     2566      // extend methods for all tags (Safari doesn't need this) 
     2567      if (!Prototype.BrowserFeatures.ElementExtensions) { 
     2568        Object.extend(Methods, Element.Methods); 
     2569        Object.extend(Methods, Element.Methods.Simulated); 
     2570      } 
     2571    } 
     2572  }); 
     2573 
     2574  extend.refresh(); 
     2575  return extend; 
     2576})(); 
     2577 
     2578Element.hasAttribute = function(element, attribute) { 
     2579  if (element.hasAttribute) return element.hasAttribute(attribute); 
     2580  return Element.Methods.Simulated.hasAttribute(element, attribute); 
    17982581}; 
    17992582 
    1800 (function() { 
    1801         Object.extend(this, { 
    1802                 href: this._getAttr, 
    1803                 src:  this._getAttr, 
    1804                 type: this._getAttr, 
    1805                 disabled: this._flag, 
    1806                 checked:  this._flag, 
    1807                 readonly: this._flag, 
    1808                 multiple: this._flag 
    1809         }); 
    1810 }).call(Element._attributeTranslations.values); 
    1811  
    1812 Element.Methods.Simulated = { 
    1813         hasAttribute: function(element, attribute) { 
    1814                 var t = Element._attributeTranslations, node; 
    1815                 attribute = t.names[attribute] || attribute; 
    1816                 node = $(element).getAttributeNode(attribute); 
    1817                 return node && node.specified; 
    1818         } 
     2583Element.addMethods = function(methods) { 
     2584  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; 
     2585 
     2586  if (!methods) { 
     2587    Object.extend(Form, Form.Methods); 
     2588    Object.extend(Form.Element, Form.Element.Methods); 
     2589    Object.extend(Element.Methods.ByTag, { 
     2590      "FORM":     Object.clone(Form.Methods), 
     2591      "INPUT":    Object.clone(Form.Element.Methods), 
     2592      "SELECT":   Object.clone(Form.Element.Methods), 
     2593      "TEXTAREA": Object.clone(Form.Element.Methods) 
     2594    }); 
     2595  } 
     2596 
     2597  if (arguments.length == 2) { 
     2598    var tagName = methods; 
     2599    methods = arguments[1]; 
     2600  } 
     2601 
     2602  if (!tagName) Object.extend(Element.Methods, methods || { }); 
     2603  else { 
     2604    if (Object.isArray(tagName)) tagName.each(extend); 
     2605    else extend(tagName); 
     2606  } 
     2607 
     2608  function extend(tagName) { 
     2609    tagName = tagName.toUpperCase(); 
     2610    if (!Element.Methods.ByTag[tagName]) 
     2611      Element.Methods.ByTag[tagName] = { }; 
     2612    Object.extend(Element.Methods.ByTag[tagName], methods); 
     2613  } 
     2614 
     2615  function copy(methods, destination, onlyIfAbsent) { 
     2616    onlyIfAbsent = onlyIfAbsent || false; 
     2617    for (var property in methods) { 
     2618      var value = methods[property]; 
     2619      if (!Object.isFunction(value)) continue; 
     2620      if (!onlyIfAbsent || !(property in destination)) 
     2621        destination[property] = value.methodize(); 
     2622    } 
     2623  } 
     2624 
     2625  function findDOMClass(tagName) { 
     2626    var klass; 
     2627    var trans = { 
     2628      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", 
     2629      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", 
     2630      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", 
     2631      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", 
     2632      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": 
     2633      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": 
     2634      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": 
     2635      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": 
     2636      "FrameSet", "IFRAME": "IFrame" 
     2637    }; 
     2638    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; 
     2639    if (window[klass]) return window[klass]; 
     2640    klass = 'HTML' + tagName + 'Element'; 
     2641    if (window[klass]) return window[klass]; 
     2642    klass = 'HTML' + tagName.capitalize() + 'Element'; 
     2643    if (window[klass]) return window[klass]; 
     2644 
     2645    window[klass] = { }; 
     2646    window[klass].prototype = document.createElement(tagName).__proto__; 
     2647    return window[klass]; 
     2648  } 
     2649 
     2650  if (F.ElementExtensions) { 
     2651    copy(Element.Methods, HTMLElement.prototype); 
     2652    copy(Element.Methods.Simulated, HTMLElement.prototype, true); 
     2653  } 
     2654 
     2655  if (F.SpecificElementExtensions) { 
     2656    for (var tag in Element.Methods.ByTag) { 
     2657      var klass = findDOMClass(tag); 
     2658      if (Object.isUndefined(klass)) continue; 
     2659      copy(T[tag], klass.prototype); 
     2660    } 
     2661  } 
     2662 
     2663  Object.extend(Element, Element.Methods); 
     2664  delete Element.ByTag; 
     2665 
     2666  if (Element.extend.refresh) Element.extend.refresh(); 
     2667  Element.cache = { }; 
    18192668}; 
    18202669 
    1821 Element.Methods.ByTag = {}; 
    1822  
    1823 Object.extend(Element, Element.Methods); 
    1824  
    1825 if (!Prototype.BrowserFeatures.ElementExtensions && 
    1826  document.createElement('div').__proto__) { 
    1827         window.HTMLElement = {}; 
    1828         window.HTMLElement.prototype = document.createElement('div').__proto__; 
    1829         Prototype.BrowserFeatures.ElementExtensions = true; 
    1830 } 
    1831  
    1832 Element.hasAttribute = function(element, attribute) { 
    1833         if (element.hasAttribute) return element.hasAttribute(attribute); 
    1834         return Element.Methods.Simulated.hasAttribute(element, attribute); 
     2670document.viewport = { 
     2671  getDimensions: function() { 
     2672    var dimensions = { }; 
     2673    var B = Prototype.Browser; 
     2674    $w('width height').each(function(d) { 
     2675      var D = d.capitalize(); 
     2676      dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] : 
     2677        (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D]; 
     2678    }); 
     2679    return dimensions; 
     2680  }, 
     2681 
     2682  getWidth: function() { 
     2683    return this.getDimensions().width; 
     2684  }, 
     2685 
     2686  getHeight: function() { 
     2687    return this.getDimensions().height; 
     2688  }, 
     2689 
     2690  getScrollOffsets: function() { 
     2691    return Element._returnOffset( 
     2692      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, 
     2693      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); 
     2694  } 
    18352695}; 
    1836  
    1837 Element.addMethods = function(methods) { 
    1838         var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; 
    1839  
    1840         if (!methods) { 
    1841                 Object.extend(Form, Form.Methods); 
    1842                 Object.extend(Form.Element, Form.Element.Methods); 
    1843                 Object.extend(Element.Methods.ByTag, { 
    1844                         "FORM":     Object.clone(Form.Methods), 
    1845                         "INPUT":    Object.clone(Form.Element.Methods), 
    1846                         "SELECT":   Object.clone(Form.Element.Methods), 
    1847                         "TEXTAREA": Object.clone(Form.Element.Methods) 
    1848                 }); 
    1849         } 
    1850  
    1851         if (arguments.length == 2) { 
    1852                 var tagName = methods; 
    1853                 methods = arguments[1]; 
    1854         } 
    1855  
    1856         if (!tagName) Object.extend(Element.Methods, methods || {}); 
    1857         else { 
    1858                 if (tagName.constructor == Array) tagName.each(extend); 
    1859                 else extend(tagName); 
    1860         } 
    1861  
    1862         function extend(tagName) { 
    1863                 tagName = tagName.toUpperCase(); 
    1864                 if (!Element.Methods.ByTag[tagName]) 
    1865                         Element.Methods.ByTag[tagName] = {}; 
    1866                 Object.extend(Element.Methods.ByTag[tagName], methods); 
    1867         } 
    1868  
    1869         function copy(methods, destination, onlyIfAbsent) { 
    1870                 onlyIfAbsent = onlyIfAbsent || false; 
    1871                 var cache = Element.extend.cache; 
    1872                 for (var property in methods) { 
    1873                         var value = methods[property]; 
    1874                         if (!onlyIfAbsent || !(property in destination)) 
    1875                                 destination[property] = cache.findOrStore(value); 
    1876                 } 
    1877         } 
    1878  
    1879         function findDOMClass(tagName) { 
    1880                 var klass; 
    1881                 var trans = { 
    1882                         "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", 
    1883                         "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", 
    1884                         "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", 
    1885                         "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", 
    1886                         "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": 
    1887                         "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": 
    1888                         "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": 
    1889                         "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": 
    1890                         "FrameSet", "IFRAME": "IFrame" 
    1891                 }; 
    1892                 if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; 
    1893                 if (window[klass]) return window[klass]; 
    1894                 klass = 'HTML' + tagName + 'Element'; 
    1895                 if (window[klass]) return window[klass]; 
    1896                 klass = 'HTML' + tagName.capitalize() + 'Element'; 
    1897                 if (window[klass]) return window[klass]; 
    1898  
    1899                 window[klass] = {}; 
    1900                 window[klass].prototype = document.createElement(tagName).__proto__; 
    1901                 return window[klass]; 
    1902         } 
    1903  
    1904         if (F.ElementExtensions) { 
    1905                 copy(Element.Methods, HTMLElement.prototype); 
    1906                 copy(Element.Methods.Simulated, HTMLElement.prototype, true); 
    1907         } 
    1908  
    1909         if (F.SpecificElementExtensions) { 
    1910                 for (var tag in Element.Methods.ByTag) { 
    1911                         var klass = findDOMClass(tag); 
    1912                         if (typeof klass == "undefined") continue; 
    1913                         copy(T[tag], klass.prototype); 
    1914                 } 
    1915         } 
    1916  
    1917         Object.extend(Element, Element.Methods); 
    1918         delete Element.ByTag; 
    1919 }; 
    1920  
    1921 var Toggle = { display: Element.toggle }; 
    1922  
    1923 /*--------------------------------------------------------------------------*/ 
    1924  
    1925 Abstract.Insertion = function(adjacency) { 
    1926         this.adjacency = adjacency; 
    1927 } 
    1928  
    1929 Abstract.Insertion.prototype = { 
    1930         initialize: function(element, content) { 
    1931                 this.element = $(element); 
    1932                 this.content = content.stripScripts(); 
    1933  
    1934                 if (this.adjacency && this.element.insertAdjacentHTML) { 
    1935                         try { 
    1936                                 this.element.insertAdjacentHTML(this.adjacency, this.content); 
    1937                         } catch (e) { 
    1938                                 var tagName = this.element.tagName.toUpperCase(); 
    1939                                 if (['TBODY', 'TR'].include(tagName)) { 
    1940                                         this.insertContent(this.contentFromAnonymousTable()); 
    1941                                 } else { 
    1942                                         throw e; 
    1943                                 } 
    1944                         } 
    1945                 } else { 
    1946                         this.range = this.element.ownerDocument.createRange(); 
    1947                         if (this.initializeRange) this.initializeRange(); 
    1948                         this.insertContent([this.range.createContextualFragment(this.content)]); 
    1949                 } 
    1950  
    1951                 setTimeout(function() {content.evalScripts()}, 10); 
    1952         }, 
    1953  
    1954         contentFromAnonymousTable: function() { 
    1955                 var div = document.createElement('div'); 
    1956                 div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>'; 
    1957                 return $A(div.childNodes[0].childNodes[0].childNodes); 
    1958         } 
    1959 } 
    1960  
    1961 var Insertion = new Object(); 
    1962  
    1963 Insertion.Before = Class.create(); 
    1964 Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { 
    1965         initializeRange: function() { 
    1966                 this.range.setStartBefore(this.element); 
    1967         }, 
    1968  
    1969         insertContent: function(fragments) { 
    1970                 fragments.each((function(fragment) { 
    1971                         this.element.parentNode.insertBefore(fragment, this.element); 
    1972                 }).bind(this)); 
    1973         } 
    1974 }); 
    1975  
    1976 Insertion.Top = Class.create(); 
    1977 Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { 
    1978         initializeRange: function() { 
    1979                 this.range.selectNodeContents(this.element); 
    1980                 this.range.collapse(true); 
    1981         }, 
    1982  
    1983         insertContent: function(fragments) { 
    1984                 fragments.reverse(false).each((function(fragment) { 
    1985                         this.element.insertBefore(fragment, this.element.firstChild); 
    1986                 }).bind(this)); 
    1987         } 
    1988 }); 
    1989  
    1990 Insertion.Bottom = Class.create(); 
    1991 Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { 
    1992         initializeRange: function() { 
    1993                 this.range.selectNodeContents(this.element); 
    1994                 this.range.collapse(this.element); 
    1995         }, 
    1996  
    1997         insertContent: function(fragments) { 
    1998                 fragments.each((function(fragment) { 
    1999                         this.element.appendChild(fragment); 
    2000                 }).bind(this)); 
    2001         } 
    2002 }); 
    2003  
    2004 Insertion.After = Class.create(); 
    2005 Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { 
    2006         initializeRange: function() { 
    2007                 this.range.setStartAfter(this.element); 
    2008         }, 
    2009  
    2010         insertContent: function(fragments) { 
    2011                 fragments.each((function(fragment) { 
    2012                         this.element.parentNode.insertBefore(fragment, 
    2013                                 this.element.nextSibling); 
    2014                 }).bind(this)); 
    2015         } 
    2016 }); 
    2017  
    2018 /*--------------------------------------------------------------------------*/ 
    2019  
    2020 Element.ClassNames = Class.create(); 
    2021 Element.ClassNames.prototype = { 
    2022         initialize: function(element) { 
    2023                 this.element = $(element); 
    2024         }, 
    2025  
    2026         _each: function(iterator) { 
    2027                 this.element.className.split(/\s+/).select(function(name) { 
    2028                         return name.length > 0; 
    2029                 })._each(iterator); 
    2030         }, 
    2031  
    2032         set: function(className) { 
    2033                 this.element.className = className; 
    2034         }, 
    2035  
    2036         add: function(classNameToAdd) { 
    2037                 if (this.include(classNameToAdd)) return; 
    2038                 this.set($A(this).concat(classNameToAdd).join(' ')); 
    2039         }, 
    2040  
    2041         remove: function(classNameToRemove) { 
    2042                 if (!this.include(classNameToRemove)) return; 
    2043                 this.set($A(this).without(classNameToRemove).join(' ')); 
    2044         }, 
    2045  
    2046         toString: function() { 
    2047                 return $A(this).join(' '); 
    2048         } 
    2049 }; 
    2050  
    2051 Object.extend(Element.ClassNames.prototype, Enumerable); 
    20522696/* Portions of the Selector class are derived from Jack Slocum’s DomQuery, 
    20532697 * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style 
    20542698 * license.  Please see http://www.yui-ext.com/ for more information. */ 
    20552699 
    2056 var Selector = Class.create(); 
    2057  
    2058 Selector.prototype = { 
    2059         initialize: function(expression) { 
    2060                 this.expression = expression.strip(); 
    2061                 this.compileMatcher(); 
    2062         }, 
    2063  
    2064         compileMatcher: function() { 
    2065                 // Selectors with namespaced attributes can't use the XPath version 
    2066                 if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression)) 
    2067                         return this.compileXPathMatcher(); 
    2068  
    2069                 var e = this.expression, ps = Selector.patterns, h = Selector.handlers, 
    2070                                 c = Selector.criteria, le, p, m; 
    2071  
    2072                 if (Selector._cache[e]) { 
    2073                         this.matcher = Selector._cache[e]; return; 
    2074                 } 
    2075                 this.matcher = ["this.matcher = function(root) {", 
    2076                                                                                 "var r = root, h = Selector.handlers, c = false, n;"]; 
    2077  
    2078                 while (e && le != e && (/\S/).test(e)) { 
    2079                         le = e; 
    2080                         for (var i in ps) { 
    2081                                 p = ps[i]; 
    2082                                 if (m = e.match(p)) { 
    2083                                         this.matcher.push(typeof c[i] == 'function' ? c[i](m) : 
    2084                                                 new Template(c[i]).evaluate(m)); 
    2085                                         e = e.replace(m[0], ''); 
    2086                                         break; 
    2087                                 } 
    2088                         } 
    2089                 } 
    2090  
    2091                 this.matcher.push("return h.unique(n);\n}"); 
    2092                 eval(this.matcher.join('\n')); 
    2093                 Selector._cache[this.expression] = this.matcher; 
    2094         }, 
    2095  
    2096         compileXPathMatcher: function() { 
    2097                 var e = this.expression, ps = Selector.patterns, 
    2098                                 x = Selector.xpath, le,  m; 
    2099  
    2100                 if (Selector._cache[e]) { 
    2101                         this.xpath = Selector._cache[e]; return; 
    2102                 } 
    2103  
    2104                 this.matcher = ['.//*']; 
    2105                 while (e && le != e && (/\S/).test(e)) { 
    2106                         le = e; 
    2107                         for (var i in ps) { 
    2108                                 if (m = e.match(ps[i])) { 
    2109                                         this.matcher.push(typeof x[i] == 'function' ? x[i](m) : 
    2110                                                 new Template(x[i]).evaluate(m)); 
    2111                                         e = e.replace(m[0], ''); 
    2112                                         break; 
    2113                                 } 
    2114                         } 
    2115                 } 
    2116  
    2117                 this.xpath = this.matcher.join(''); 
    2118                 Selector._cache[this.expression] = this.xpath; 
    2119         }, 
    2120  
    2121         findElements: function(root) { 
    2122                 root = root || document; 
    2123                 if (this.xpath) return document._getElementsByXPath(this.xpath, root); 
    2124                 return this.matcher(root); 
    2125         }, 
    2126  
    2127         match: function(element) { 
    2128                 return this.findElements(document).include(element); 
    2129         }, 
    2130  
    2131         toString: function() { 
    2132                 return this.expression; 
    2133         }, 
    2134  
    2135         inspect: function() { 
    2136                 return "#<Selector:" + this.expression.inspect() + ">"; 
    2137         } 
    2138 }; 
     2700var Selector = Class.create({ 
     2701  initialize: function(expression) { 
     2702    this.expression = expression.strip(); 
     2703    this.compileMatcher(); 
     2704  }, 
     2705 
     2706  shouldUseXPath: function() { 
     2707    if (!Prototype.BrowserFeatures.XPath) return false; 
     2708 
     2709    var e = this.expression; 
     2710 
     2711    // Safari 3 chokes on :*-of-type and :empty 
     2712    if (Prototype.Browser.WebKit && 
     2713     (e.include("-of-type") || e.include(":empty"))) 
     2714      return false; 
     2715 
     2716    // XPath can't do namespaced attributes, nor can it read 
     2717    // the "checked" property from DOM nodes 
     2718    if ((/(\[[\w-]*?:|:checked)/).test(this.expression)) 
     2719      return false; 
     2720 
     2721    return true; 
     2722  }, 
     2723 
     2724  compileMatcher: function() { 
     2725    if (this.shouldUseXPath()) 
     2726      return this.compileXPathMatcher(); 
     2727 
     2728    var e = this.expression, ps = Selector.patterns, h = Selector.handlers, 
     2729        c = Selector.criteria, le, p, m; 
     2730 
     2731    if (Selector._cache[e]) { 
     2732      this.matcher = Selector._cache[e]; 
     2733      return; 
     2734    } 
     2735 
     2736    this.matcher = ["this.matcher = function(root) {", 
     2737                    "var r = root, h = Selector.handlers, c = false, n;"]; 
     2738 
     2739    while (e && le != e && (/\S/).test(e)) { 
     2740      le = e; 
     2741      for (var i in ps) { 
     2742        p = ps[i]; 
     2743        if (m = e.match(p)) { 
     2744          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : 
     2745              new Template(c[i]).evaluate(m)); 
     2746          e = e.replace(m[0], ''); 
     2747          break; 
     2748        } 
     2749      } 
     2750    } 
     2751 
     2752    this.matcher.push("return h.unique(n);\n}"); 
     2753    eval(this.matcher.join('\n')); 
     2754    Selector._cache[this.expression] = this.matcher; 
     2755  }, 
     2756 
     2757  compileXPathMatcher: function() { 
     2758    var e = this.expression, ps = Selector.patterns, 
     2759        x = Selector.xpath, le, m; 
     2760 
     2761    if (Selector._cache[e]) { 
     2762      this.xpath = Selector._cache[e]; return; 
     2763    } 
     2764 
     2765    this.matcher = ['.//*']; 
     2766    while (e && le != e && (/\S/).test(e)) { 
     2767      le = e; 
     2768      for (var i in ps) { 
     2769        if (m = e.match(ps[i])) { 
     2770          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) : 
     2771            new Template(x[i]).evaluate(m)); 
     2772          e = e.replace(m[0], ''); 
     2773          break; 
     2774        } 
     2775      } 
     2776    } 
     2777 
     2778    this.xpath = this.matcher.join(''); 
     2779    Selector._cache[this.expression] = this.xpath; 
     2780  }, 
     2781 
     2782  findElements: function(root) { 
     2783    root = root || document; 
     2784    if (this.xpath) return document._getElementsByXPath(this.xpath, root); 
     2785    return this.matcher(root); 
     2786  }, 
     2787 
     2788  match: function(element) { 
     2789    this.tokens = []; 
     2790 
     2791    var e = this.expression, ps = Selector.patterns, as = Selector.assertions; 
     2792    var le, p, m; 
     2793 
     2794    while (e && le !== e && (/\S/).test(e)) { 
     2795      le = e; 
     2796      for (var i in ps) { 
     2797        p = ps[i]; 
     2798        if (m = e.match(p)) { 
     2799          // use the Selector.assertions methods unless the selector 
     2800          // is too complex. 
     2801          if (as[i]) { 
     2802            this.tokens.push([i, Object.clone(m)]); 
     2803            e = e.replace(m[0], ''); 
     2804          } else { 
     2805            // reluctantly do a document-wide search 
     2806            // and look for a match in the array 
     2807            return this.findElements(document).include(element); 
     2808          } 
     2809        } 
     2810      } 
     2811    } 
     2812 
     2813    var match = true, name, matches; 
     2814    for (var i = 0, token; token = this.tokens[i]; i++) { 
     2815      name = token[0], matches = token[1]; 
     2816      if (!Selector.assertions[name](element, matches)) { 
     2817        match = false; break; 
     2818      } 
     2819    } 
     2820 
     2821    return match; 
     2822  }, 
     2823 
     2824  toString: function() { 
     2825    return this.expression; 
     2826  }, 
     2827 
     2828  inspect: function() { 
     2829    return "#<Selector:" + this.expression.inspect() + ">"; 
     2830  } 
     2831}); 
    21392832 
    21402833Object.extend(Selector, { 
    2141         _cache: {}, 
    2142  
    2143         xpath: { 
    2144                 descendant:   "//*", 
    2145                 child:        "/*", 
    2146                 adjacent:     "/following-sibling::*[1]", 
    2147                 laterSibling: '/following-sibling::*', 
    2148                 tagName:      function(m) { 
    2149                         if (m[1] == '*') return ''; 
    2150                         return "[local-name()='" + m[1].toLowerCase() + 
    2151                                                  "' or local-name()='" + m[1].toUpperCase() + "']"; 
    2152                 }, 
    2153                 className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]", 
    2154                 id:           "[@id='#{1}']", 
    2155                 attrPresence: "[@#{1}]", 
    2156                 attr: function(m) { 
    2157                         m[3] = m[5] || m[6]; 
    2158                         return new Template(Selector.xpath.operators[m[2]]).evaluate(m); 
    2159                 }, 
    2160                 pseudo: function(m) { 
    2161                         var h = Selector.xpath.pseudos[m[1]]; 
    2162                         if (!h) return ''; 
    2163                         if (typeof h === 'function') return h(m); 
    2164                         return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); 
    2165                 }, 
    2166                 operators: { 
    2167                         '=':  "[@#{1}='#{3}']", 
    2168                         '!=': "[@#{1}!='#{3}']", 
    2169                         '^=': "[starts-with(@#{1}, '#{3}')]", 
    2170                         '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", 
    2171                         '*=': "[contains(@#{1}, '#{3}')]", 
    2172                         '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", 
    2173                         '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" 
    2174                 }, 
    2175                 pseudos: { 
    2176                         'first-child': '[not(preceding-sibling::*)]', 
    2177                         'last-child':  '[not(following-sibling::*)]', 
    2178                         'only-child':  '[not(preceding-sibling::* or following-sibling::*)]', 
    2179                         'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]", 
    2180                         'checked':     "[@checked]", 
    2181                         'disabled':    "[@disabled]", 
    2182                         'enabled':     "[not(@disabled)]", 
    2183                         'not': function(m) { 
    2184                                 var e = m[6], p = Selector.patterns, 
    2185                                                 x = Selector.xpath, le, m, v; 
    2186  
    2187                                 var exclusion = []; 
    2188                                 while (e && le != e && (/\S/).test(e)) { 
    2189                                         le = e; 
    2190                                         for (var i in p) { 
    2191                                                 if (m = e.match(p[i])) { 
    2192                                                         v = typeof x[i] == 'function' ? x[i](m) : new Template(x[i]).evaluate(m); 
    2193                                                         exclusion.push("(" + v.substring(1, v.length - 1) + ")"); 
    2194                                                         e = e.replace(m[0], ''); 
    2195                                                         break; 
    2196                                                 } 
    2197                                         } 
    2198                                 } 
    2199                                 return "[not(" + exclusion.join(" and ") + ")]"; 
    2200                         }, 
    2201                         'nth-child':      function(m) { 
    2202                                 return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); 
    2203                         }, 
    2204                         'nth-last-child': function(m) { 
    2205                                 return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); 
    2206                         }, 
    2207                         'nth-of-type':    function(m) { 
    2208                                 return Selector.xpath.pseudos.nth("position() ", m); 
    2209                         }, 
    2210                         'nth-last-of-type': function(m) { 
    2211                                 return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); 
    2212                         }, 
    2213                         'first-of-type':  function(m) { 
    2214                                 m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); 
    2215                         }, 
    2216                         'last-of-type':   function(m) { 
    2217                                 m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); 
    2218                         }, 
    2219                         'only-of-type':   function(m) { 
    2220                                 var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); 
    2221                         }, 
    2222                         nth: function(fragment, m) { 
    2223                                 var mm, formula = m[6], predicate; 
    2224                                 if (formula == 'even') formula = '2n+0'; 
    2225                                 if (formula == 'odd')  formula = '2n+1'; 
    2226                                 if (mm = formula.match(/^(\d+)$/)) // digit only 
    2227                                         return '[' + fragment + "= " + mm[1] + ']'; 
    2228                                 if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b 
    2229                                         if (mm[1] == "-") mm[1] = -1; 
    2230                                         var a = mm[1] ? Number(mm[1]) : 1; 
    2231                                         var b = mm[2] ? Number(mm[2]) : 0; 
    2232                                         predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + 
    2233                                         "((#{fragment} - #{b}) div #{a} >= 0)]"; 
    2234                                         return new Template(predicate).evaluate({ 
    2235                                                 fragment: fragment, a: a, b: b }); 
    2236                                 } 
    2237                         } 
    2238                 } 
    2239         }, 
    2240  
    2241         criteria: { 
    2242                 tagName:      'n = h.tagName(n, r, "#{1}", c);   c = false;', 
    2243                 className:    'n = h.className(n, r, "#{1}", c); c = false;', 
    2244                 id:           'n = h.id(n, r, "#{1}", c);        c = false;', 
    2245                 attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;', 
    2246                 attr: function(m) { 
    2247                         m[3] = (m[5] || m[6]); 
    2248                         return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m); 
    2249                 }, 
    2250                 pseudo:       function(m) { 
    2251                         if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); 
    2252                         return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); 
    2253                 }, 
    2254                 descendant:   'c = "descendant";', 
    2255                 child:        'c = "child";', 
    2256                 adjacent:     'c = "adjacent";', 
    2257                 laterSibling: 'c = "laterSibling";' 
    2258         }, 
    2259  
    2260         patterns: { 
    2261                 // combinators must be listed first 
    2262                 // (and descendant needs to be last combinator) 
    2263                 laterSibling: /^\s*~\s*/, 
    2264                 child:        /^\s*>\s*/, 
    2265                 adjacent:     /^\s*\+\s*/, 
    2266                 descendant:   /^\s/, 
    2267  
    2268                 // selectors follow 
    2269                 tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/, 
    2270                 id:           /^#([\w\-\*]+)(\b|$)/, 
    2271                 className:    /^\.([\w\-\*]+)(\b|$)/, 
    2272                 pseudo:       /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|\s|(?=:))/, 
    2273                 attrPresence: /^\[([\w]+)\]/, 
    2274                 attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/ 
    2275         }, 
    2276  
    2277         handlers: { 
    2278                 // UTILITY FUNCTIONS 
    2279                 // joins two collections 
    2280                 concat: function(a, b) { 
    2281                         for (var i = 0, node; node = b[i]; i++) 
    2282                                 a.push(node); 
    2283                         return a; 
    2284                 }, 
    2285  
    2286                 // marks an array of nodes for counting 
    2287                 mark: function(nodes) { 
    2288                         for (var i = 0, node; node = nodes[i]; i++) 
    2289                                 node._counted = true; 
    2290                         return nodes; 
    2291                 }, 
    2292  
    2293                 unmark: function(nodes) { 
    2294                         for (var i = 0, node; node = nodes[i]; i++) 
    2295                                 node._counted = undefined; 
    2296                         return nodes; 
    2297                 }, 
    2298  
    2299                 // mark each child node with its position (for nth calls) 
    2300                 // "ofType" flag indicates whether we're indexing for nth-of-type 
    2301                 // rather than nth-child 
    2302                 index: function(parentNode, reverse, ofType) { 
    2303                         parentNode._counted = true; 
    2304                         if (reverse) { 
    2305                                 for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { 
    2306                                         node = nodes[i]; 
    2307                                         if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; 
    2308                                 } 
    2309                         } else { 
    2310                                 for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) 
    2311                                         if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; 
    2312                         } 
    2313                 }, 
    2314  
    2315                 // filters out duplicates and extends all nodes 
    2316                 unique: function(nodes) { 
    2317                         if (nodes.length == 0) return nodes; 
    2318                         var results = [], n; 
    2319                         for (var i = 0, l = nodes.length; i < l; i++) 
    2320                                 if (!(n = nodes[i])._counted) { 
    2321                                         n._counted = true; 
    2322                                         results.push(Element.extend(n)); 
    2323                                 } 
    2324                         return Selector.handlers.unmark(results); 
    2325                 }, 
    2326  
    2327                 // COMBINATOR FUNCTIONS 
    2328                 descendant: function(nodes) { 
    2329                         var h = Selector.handlers; 
    2330                         for (var i = 0, results = [], node; node = nodes[i]; i++) 
    2331                                 h.concat(results, node.getElementsByTagName('*')); 
    2332                         return results; 
    2333                 }, 
    2334  
    2335                 child: function(nodes) { 
    2336                         var h = Selector.handlers; 
    2337                         for (var i = 0, results = [], node; node = nodes[i]; i++) { 
    2338                                 for (var j = 0, children = [], child; child = node.childNodes[j]; j++) 
    2339                                         if (child.nodeType == 1 && child.tagName != '!') results.push(child); 
    2340                         } 
    2341                         return results; 
    2342                 }, 
    2343  
    2344                 adjacent: function(nodes) { 
    2345                         for (var i = 0, results = [], node; node = nodes[i]; i++) { 
    2346                                 var next = this.nextElementSibling(node); 
    2347                                 if (next) results.push(next); 
    2348                         } 
    2349                         return results; 
    2350                 }, 
    2351  
    2352                 laterSibling: function(nodes) { 
    2353                         var h = Selector.handlers; 
    2354                         for (var i = 0, results = [], node; node = nodes[i]; i++) 
    2355                                 h.concat(results, Element.nextSiblings(node)); 
    2356                         return results; 
    2357                 }, 
    2358  
    2359                 nextElementSibling: function(node) { 
    2360                         while (node = node.nextSibling) 
    2361                                 if (node.nodeType == 1) return node; 
    2362                         return null; 
    2363                 }, 
    2364  
    2365                 previousElementSibling: function(node) { 
    2366                         while (node = node.previousSibling) 
    2367                                 if (node.nodeType == 1) return node; 
    2368                         return null; 
    2369                 }, 
    2370  
    2371                 // TOKEN FUNCTIONS 
    2372                 tagName: function(nodes, root, tagName, combinator) { 
    2373                         tagName = tagName.toUpperCase(); 
    2374                         var results = [], h = Selector.handlers; 
    2375                         if (nodes) { 
    2376                                 if (combinator) { 
    2377                                         // fastlane for ordinary descendant combinators 
    2378                                         if (combinator == "descendant") { 
    2379                                                 for (var i = 0, node; node = nodes[i]; i++) 
    2380                                                         h.concat(results, node.getElementsByTagName(tagName)); 
    2381                                                 return results; 
    2382                                         } else nodes = this[combinator](nodes); 
    2383                                         if (tagName == "*") return nodes; 
    2384                                 } 
    2385                                 for (var i = 0, node; node = nodes[i]; i++) 
    2386                                         if (node.tagName.toUpperCase() == tagName) results.push(node); 
    2387                                 return results; 
    2388                         } else return root.getElementsByTagName(tagName); 
    2389                 }, 
    2390  
    2391                 id: function(nodes, root, id, combinator) { 
    2392                         var targetNode = $(id), h = Selector.handlers; 
    2393                         if (!nodes && root == document) return targetNode ? [targetNode] : []; 
    2394                         if (nodes) { 
    2395                                 if (combinator) { 
    2396                                         if (combinator == 'child') { 
    2397                                                 for (var i = 0, node; node = nodes[i]; i++) 
    2398                                                         if (targetNode.parentNode == node) return [targetNode]; 
    2399                                         } else if (combinator == 'descendant') { 
    2400                                                 for (var i = 0, node; node = nodes[i]; i++) 
    2401                                                         if (Element.descendantOf(targetNode, node)) return [targetNode]; 
    2402                                         } else if (combinator == 'adjacent') { 
    2403                                                 for (var i = 0, node; node = nodes[i]; i++) 
    2404                                                         if (Selector.handlers.previousElementSibling(targetNode) == node) 
    2405                                                                 return [targetNode]; 
    2406                                         } else nodes = h[combinator](nodes); 
    2407                                 } 
    2408                                 for (var i = 0, node; node = nodes[i]; i++) 
    2409                                         if (node == targetNode) return [targetNode]; 
    2410                                 return []; 
    2411                         } 
    2412                         return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; 
    2413                 }, 
    2414  
    2415                 className: function(nodes, root, className, combinator) { 
    2416                         if (nodes && combinator) nodes = this[combinator](nodes); 
    2417                         return Selector.handlers.byClassName(nodes, root, className); 
    2418                 }, 
    2419  
    2420                 byClassName: function(nodes, root, className) { 
    2421                         if (!nodes) nodes = Selector.handlers.descendant([root]); 
    2422                         var needle = ' ' + className + ' '; 
    2423                         for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { 
    2424                                 nodeClassName = node.className; 
    2425                                 if (nodeClassName.length == 0) continue; 
    2426                                 if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) 
    2427                                         results.push(node); 
    2428                         } 
    2429                         return results; 
    2430                 }, 
    2431  
    2432                 attrPresence: function(nodes, root, attr) { 
    2433                         var results = []; 
    2434                         for (var i = 0, node; node = nodes[i]; i++) 
    2435                                 if (Element.hasAttribute(node, attr)) results.push(node); 
    2436                         return results; 
    2437                 }, 
    2438  
    2439                 attr: function(nodes, root, attr, value, operator) { 
    2440                         if (!nodes) nodes = root.getElementsByTagName("*"); 
    2441                         var handler = Selector.operators[operator], results = []; 
    2442                         for (var i = 0, node; node = nodes[i]; i++) { 
    2443                                 var nodeValue = Element.readAttribute(node, attr); 
    2444                                 if (nodeValue === null) continue; 
    2445                                 if (handler(nodeValue, value)) results.push(node); 
    2446                         } 
    2447                         return results; 
    2448                 }, 
    2449  
    2450                 pseudo: function(nodes, name, value, root, combinator) { 
    2451                         if (nodes && combinator) nodes = this[combinator](nodes); 
    2452                         if (!nodes) nodes = root.getElementsByTagName("*"); 
    2453                         return Selector.pseudos[name](nodes, value, root); 
    2454                 } 
    2455         }, 
    2456  
    2457         pseudos: { 
    2458                 'first-child': function(nodes, value, root) { 
    2459                         for (var i = 0, results = [], node; node = nodes[i]; i++) { 
    2460                                 if (Selector.handlers.previousElementSibling(node)) continue; 
    2461                                         results.push(node); 
    2462                         } 
    2463                         return results; 
    2464                 }, 
    2465                 'last-child': function(nodes, value, root) { 
    2466                         for (var i = 0, results = [], node; node = nodes[i]; i++) { 
    2467                                 if (Selector.handlers.nextElementSibling(node)) continue; 
    2468                                         results.push(node); 
    2469                         } 
    2470                         return results; 
    2471                 }, 
    2472                 'only-child': function(nodes, value, root) { 
    2473                         var h = Selector.handlers; 
    2474                         for (var i = 0, results = [], node; node = nodes[i]; i++) 
    2475                                 if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) 
    2476                                         results.push(node); 
    2477                         return results; 
    2478                 }, 
    2479                 'nth-child':        function(nodes, formula, root) { 
    2480                         return Selector.pseudos.nth(nodes, formula, root); 
    2481                 }, 
    2482                 'nth-last-child':   function(nodes, formula, root) { 
    2483                         return Selector.pseudos.nth(nodes, formula, root, true); 
    2484                 }, 
    2485                 'nth-of-type':      function(nodes, formula, root) { 
    2486                         return Selector.pseudos.nth(nodes, formula, root, false, true); 
    2487                 }, 
    2488                 'nth-last-of-type': function(nodes, formula, root) { 
    2489                         return Selector.pseudos.nth(nodes, formula, root, true, true); 
    2490                 }, 
    2491                 'first-of-type':    function(nodes, formula, root) { 
    2492                         return Selector.pseudos.nth(nodes, "1", root, false, true); 
    2493                 }, 
    2494                 'last-of-type':     function(nodes, formula, root) { 
    2495                         return Selector.pseudos.nth(nodes, "1", root, true, true); 
    2496                 }, 
    2497                 'only-of-type':     function(nodes, formula, root) { 
    2498                         var p = Selector.pseudos; 
    2499                         return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); 
    2500                 }, 
    2501  
    2502                 // handles the an+b logic 
    2503                 getIndices: function(a, b, total) { 
    2504                         if (a == 0) return b > 0 ? [b] : []; 
    2505                         return $R(1, total).inject([], function(memo, i) { 
    2506                                 if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); 
    2507                                 return memo; 
    2508                         }); 
    2509                 }, 
    2510  
    2511                 // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type 
    2512                 nth: function(nodes, formula, root, reverse, ofType) { 
    2513                         if (nodes.length == 0) return []; 
    2514                         if (formula == 'even') formula = '2n+0'; 
    2515                         if (formula == 'odd')  formula = '2n+1'; 
    2516                         var h = Selector.handlers, results = [], indexed = [], m; 
    2517                         h.mark(nodes); 
    2518                         for (var i = 0, node; node = nodes[i]; i++) { 
    2519                                 if (!node.parentNode._counted) { 
    2520                                         h.index(node.parentNode, reverse, ofType); 
    2521                                         indexed.push(node.parentNode); 
    2522                                 } 
    2523                         } 
    2524                         if (formula.match(/^\d+$/)) { // just a number 
    2525                                 formula = Number(formula); 
    2526                                 for (var i = 0, node; node = nodes[i]; i++) 
    2527                                         if (node.nodeIndex == formula) results.push(node); 
    2528                         } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b 
    2529                                 if (m[1] == "-") m[1] = -1; 
    2530                                 var a = m[1] ? Number(m[1]) : 1; 
    2531                                 var b = m[2] ? Number(m[2]) : 0; 
    2532                                 var indices = Selector.pseudos.getIndices(a, b, nodes.length); 
    2533                                 for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { 
    2534                                         for (var j = 0; j < l; j++) 
    2535                                                 if (node.nodeIndex == indices[j]) results.push(node); 
    2536                                 } 
    2537                         } 
    2538                         h.unmark(nodes); 
    2539                         h.unmark(indexed); 
    2540                         return results; 
    2541                 }, 
    2542  
    2543                 'empty': function(nodes, value, root) { 
    2544                         for (var i = 0, results = [], node; node = nodes[i]; i++) { 
    2545                                 // IE treats comments as element nodes 
    2546                                 if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue; 
    2547                                 results.push(node); 
    2548                         } 
    2549                         return results; 
    2550                 }, 
    2551  
    2552                 'not': function(nodes, selector, root) { 
    2553                         var h = Selector.handlers, selectorType, m; 
    2554                         var exclusions = new Selector(selector).findElements(root); 
    2555                         h.mark(exclusions); 
    2556                         for (var i = 0, results = [], node; node = nodes[i]; i++) 
    2557                                 if (!node._counted) results.push(node); 
    2558                         h.unmark(exclusions); 
    2559                         return results; 
    2560                 }, 
    2561  
    2562                 'enabled': function(nodes, value, root) { 
    2563                         for (var i = 0, results = [], node; node = nodes[i]; i++) 
    2564                                 if (!node.disabled) results.push(node); 
    2565                         return results; 
    2566                 }, 
    2567  
    2568                 'disabled': function(nodes, value, root) { 
    2569                         for (var i = 0, results = [], node; node = nodes[i]; i++) 
    2570                                 if (node.disabled) results.push(node); 
    2571                         return results; 
    2572                 }, 
    2573  
    2574                 'checked': function(nodes, value, root) { 
    2575                         for (var i = 0, results = [], node; node = nodes[i]; i++) 
    2576                                 if (node.checked) results.push(node); 
    2577                         return results; 
    2578                 } 
    2579         }, 
    2580  
    2581         operators: { 
    2582                 '=':  function(nv, v) { return nv == v; }, 
    2583                 '!=': function(nv, v) { return nv != v; }, 
    2584                 '^=': function(nv, v) { return nv.startsWith(v); }, 
    2585                 '$=': function(nv, v) { return nv.endsWith(v); }, 
    2586                 '*=': function(nv, v) { return nv.include(v); }, 
    2587                 '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, 
    2588                 '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } 
    2589         }, 
    2590  
    2591         matchElements: function(elements, expression) { 
    2592                 var matches = new Selector(expression).findElements(), h = Selector.handlers; 
    2593                 h.mark(matches); 
    2594                 for (var i = 0, results = [], element; element = elements[i]; i++) 
    2595                         if (element._counted) results.push(element); 
    2596                 h.unmark(matches); 
    2597                 return results; 
    2598         }, 
    2599  
    2600         findElement: function(elements, expression, index) { 
    2601                 if (typeof expression == 'number') { 
    2602                         index = expression; expression = false; 
    2603                 } 
    2604                 return Selector.matchElements(elements, expression || '*')[index || 0]; 
    2605         }, 
    2606  
    2607         findChildElements: function(element, expressions) { 
    2608                 var exprs = expressions.join(','), expressions = []; 
    2609                 exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { 
    2610                         expressions.push(m[1].strip()); 
    2611                 }); 
    2612                 var results = [], h = Selector.handlers; 
    2613                 for (var i = 0, l = expressions.length, selector; i < l; i++) { 
    2614                         selector = new Selector(expressions[i].strip()); 
    2615                         h.concat(results, selector.findElements(element)); 
    2616                 } 
    2617                 return (l > 1) ? h.unique(results) : results; 
    2618         } 
     2834  _cache: { }, 
     2835 
     2836  xpath: { 
     2837    descendant:   "//*", 
     2838    child:        "/*", 
     2839    adjacent:     "/following-sibling::*[1]", 
     2840    laterSibling: '/following-sibling::*', 
     2841    tagName:      function(m) { 
     2842      if (m[1] == '*') return ''; 
     2843      return "[local-name()='" + m[1].toLowerCase() + 
     2844             "' or local-name()='" + m[1].toUpperCase() + "']"; 
     2845    }, 
     2846    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]", 
     2847    id:           "[@id='#{1}']", 
     2848    attrPresence: function(m) { 
     2849      m[1] = m[1].toLowerCase(); 
     2850      return new Template("[@#{1}]").evaluate(m); 
     2851    }, 
     2852    attr: function(m) { 
     2853      m[1] = m[1].toLowerCase(); 
     2854      m[3] = m[5] || m[6]; 
     2855      return new Template(Selector.xpath.operators[m[2]]).evaluate(m); 
     2856    }, 
     2857    pseudo: function(m) { 
     2858      var h = Selector.xpath.pseudos[m[1]]; 
     2859      if (!h) return ''; 
     2860      if (Object.isFunction(h)) return h(m); 
     2861      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); 
     2862    }, 
     2863    operators: { 
     2864      '=':  "[@#{1}='#{3}']", 
     2865      '!=': "[@#{1}!='#{3}']", 
     2866      '^=': "[starts-with(@#{1}, '#{3}')]", 
     2867      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", 
     2868      '*=': "[contains(@#{1}, '#{3}')]", 
     2869      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", 
     2870      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" 
     2871    }, 
     2872    pseudos: { 
     2873      'first-child': '[not(preceding-sibling::*)]', 
     2874      'last-child':  '[not(following-sibling::*)]', 
     2875      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]', 
     2876      'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]", 
     2877      'checked':     "[@checked]", 
     2878      'disabled':    "[@disabled]", 
     2879      'enabled':     "[not(@disabled)]", 
     2880      'not': function(m) { 
     2881        var e = m[6], p = Selector.patterns, 
     2882            x = Selector.xpath, le, v; 
     2883 
     2884        var exclusion = []; 
     2885        while (e && le != e && (/\S/).test(e)) { 
     2886          le = e; 
     2887          for (var i in p) { 
     2888            if (m = e.match(p[i])) { 
     2889              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m); 
     2890              exclusion.push("(" + v.substring(1, v.length - 1) + ")"); 
     2891              e = e.replace(m[0], ''); 
     2892              break; 
     2893            } 
     2894          } 
     2895        } 
     2896        return "[not(" + exclusion.join(" and ") + ")]"; 
     2897      }, 
     2898      'nth-child':      function(m) { 
     2899        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); 
     2900      }, 
     2901      'nth-last-child': function(m) { 
     2902        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); 
     2903      }, 
     2904      'nth-of-type':    function(m) { 
     2905        return Selector.xpath.pseudos.nth("position() ", m); 
     2906      }, 
     2907      'nth-last-of-type': function(m) { 
     2908        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); 
     2909      }, 
     2910      'first-of-type':  function(m) { 
     2911        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); 
     2912      }, 
     2913      'last-of-type':   function(m) { 
     2914        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); 
     2915      }, 
     2916      'only-of-type':   function(m) { 
     2917        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); 
     2918      }, 
     2919      nth: function(fragment, m) { 
     2920        var mm, formula = m[6], predicate; 
     2921        if (formula == 'even') formula = '2n+0'; 
     2922        if (formula == 'odd')  formula = '2n+1'; 
     2923        if (mm = formula.match(/^(\d+)$/)) // digit only 
     2924          return '[' + fragment + "= " + mm[1] + ']'; 
     2925        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b 
     2926          if (mm[1] == "-") mm[1] = -1; 
     2927          var a = mm[1] ? Number(mm[1]) : 1; 
     2928          var b = mm[2] ? Number(mm[2]) : 0; 
     2929          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + 
     2930          "((#{fragment} - #{b}) div #{a} >= 0)]"; 
     2931          return new Template(predicate).evaluate({ 
     2932            fragment: fragment, a: a, b: b }); 
     2933        } 
     2934      } 
     2935    } 
     2936  }, 
     2937 
     2938  criteria: { 
     2939    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;', 
     2940    className:    'n = h.className(n, r, "#{1}", c);    c = false;', 
     2941    id:           'n = h.id(n, r, "#{1}", c);           c = false;', 
     2942    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', 
     2943    attr: function(m) { 
     2944      m[3] = (m[5] || m[6]); 
     2945      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); 
     2946    }, 
     2947    pseudo: function(m) { 
     2948      if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); 
     2949      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); 
     2950    }, 
     2951    descendant:   'c = "descendant";', 
     2952    child:        'c = "child";', 
     2953    adjacent:     'c = "adjacent";', 
     2954    laterSibling: 'c = "laterSibling";' 
     2955  }, 
     2956 
     2957  patterns: { 
     2958    // combinators must be listed first 
     2959    // (and descendant needs to be last combinator) 
     2960    laterSibling: /^\s*~\s*/, 
     2961    child:        /^\s*>\s*/, 
     2962    adjacent:     /^\s*\+\s*/, 
     2963    descendant:   /^\s/, 
     2964 
     2965    // selectors follow 
     2966    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/, 
     2967    id:           /^#([\w\-\*]+)(\b|$)/, 
     2968    className:    /^\.([\w\-\*]+)(\b|$)/, 
     2969    pseudo: 
     2970/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, 
     2971    attrPresence: /^\[([\w]+)\]/, 
     2972    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ 
     2973  }, 
     2974 
     2975  // for Selector.match and Element#match 
     2976  assertions: { 
     2977    tagName: function(element, matches) { 
     2978      return matches[1].toUpperCase() == element.tagName.toUpperCase(); 
     2979    }, 
     2980 
     2981    className: function(element, matches) { 
     2982      return Element.hasClassName(element, matches[1]); 
     2983    }, 
     2984 
     2985    id: function(element, matches) { 
     2986      return element.id === matches[1]; 
     2987    }, 
     2988 
     2989    attrPresence: function(element, matches) { 
     2990      return Element.hasAttribute(element, matches[1]); 
     2991    }, 
     2992 
     2993    attr: function(element, matches) { 
     2994      var nodeValue = Element.readAttribute(element, matches[1]); 
     2995      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); 
     2996    } 
     2997  }, 
     2998 
     2999  handlers: { 
     3000    // UTILITY FUNCTIONS 
     3001    // joins two collections 
     3002    concat: function(a, b) { 
     3003      for (var i = 0, node; node = b[i]; i++) 
     3004        a.push(node); 
     3005      return a; 
     3006    }, 
     3007 
     3008    // marks an array of nodes for counting 
     3009    mark: function(nodes) { 
     3010      var _true = Prototype.emptyFunction; 
     3011      for (var i = 0, node; node = nodes[i]; i++) 
     3012        node._countedByPrototype = _true; 
     3013      return nodes; 
     3014    }, 
     3015 
     3016    unmark: function(nodes) { 
     3017      for (var i = 0, node; node = nodes[i]; i++) 
     3018        node._countedByPrototype = undefined; 
     3019      return nodes; 
     3020    }, 
     3021 
     3022    // mark each child node with its position (for nth calls) 
     3023    // "ofType" flag indicates whether we're indexing for nth-of-type 
     3024    // rather than nth-child 
     3025    index: function(parentNode, reverse, ofType) { 
     3026      parentNode._countedByPrototype = Prototype.emptyFunction; 
     3027      if (reverse) { 
     3028        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { 
     3029          var node = nodes[i]; 
     3030          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; 
     3031        } 
     3032      } else { 
     3033        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) 
     3034          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; 
     3035      } 
     3036    }, 
     3037 
     3038    // filters out duplicates and extends all nodes 
     3039    unique: function(nodes) { 
     3040      if (nodes.length == 0) return nodes; 
     3041      var results = [], n; 
     3042      for (var i = 0, l = nodes.length; i < l; i++) 
     3043        if (!(n = nodes[i])._countedByPrototype) { 
     3044          n._countedByPrototype = Prototype.emptyFunction; 
     3045          results.push(Element.extend(n)); 
     3046        } 
     3047      return Selector.handlers.unmark(results); 
     3048    }, 
     3049 
     3050    // COMBINATOR FUNCTIONS 
     3051    descendant: function(nodes) { 
     3052      var h = Selector.handlers; 
     3053      for (var i = 0, results = [], node; node = nodes[i]; i++) 
     3054        h.concat(results, node.getElementsByTagName('*')); 
     3055      return results; 
     3056    }, 
     3057 
     3058    child: function(nodes) { 
     3059      var h = Selector.handlers; 
     3060      for (var i = 0, results = [], node; node = nodes[i]; i++) { 
     3061        for (var j = 0, child; child = node.childNodes[j]; j++) 
     3062          if (child.nodeType == 1 && child.tagName != '!') results.push(child); 
     3063      } 
     3064      return results; 
     3065    }, 
     3066 
     3067    adjacent: function(nodes) { 
     3068      for (var i = 0, results = [], node; node = nodes[i]; i++) { 
     3069        var next = this.nextElementSibling(node); 
     3070        if (next) results.push(next); 
     3071      } 
     3072      return results; 
     3073    }, 
     3074 
     3075    laterSibling: function(nodes) { 
     3076      var h = Selector.handlers; 
     3077      for (var i = 0, results = [], node; node = nodes[i]; i++) 
     3078        h.concat(results, Element.nextSiblings(node)); 
     3079      return results; 
     3080    }, 
     3081 
     3082    nextElementSibling: function(node) { 
     3083      while (node = node.nextSibling) 
     3084              if (node.nodeType == 1) return node; 
     3085      return null; 
     3086    }, 
     3087 
     3088    previousElementSibling: function(node) { 
     3089      while (node = node.previousSibling) 
     3090        if (node.nodeType == 1) return node; 
     3091      return null; 
     3092    }, 
     3093 
     3094    // TOKEN FUNCTIONS 
     3095    tagName: function(nodes, root, tagName, combinator) { 
     3096      var uTagName = tagName.toUpperCase(); 
     3097      var results = [], h = Selector.handlers; 
     3098      if (nodes) { 
     3099        if (combinator) { 
     3100          // fastlane for ordinary descendant combinators 
     3101          if (combinator == "descendant") { 
     3102            for (var i = 0, node; node = nodes[i]; i++) 
     3103              h.concat(results, node.getElementsByTagName(tagName)); 
     3104            return results; 
     3105          } else nodes = this[combinator](nodes); 
     3106          if (tagName == "*") return nodes; 
     3107        } 
     3108        for (var i = 0, node; node = nodes[i]; i++) 
     3109          if (node.tagName.toUpperCase() === uTagName) results.push(node); 
     3110        return results; 
     3111      } else return root.getElementsByTagName(tagName); 
     3112    }, 
     3113 
     3114    id: function(nodes, root, id, combinator) { 
     3115      var targetNode = $(id), h = Selector.handlers; 
     3116      if (!targetNode) return []; 
     3117      if (!nodes && root == document) return [targetNode]; 
     3118      if (nodes) { 
     3119        if (combinator) { 
     3120          if (combinator == 'child') { 
     3121            for (var i = 0, node; node = nodes[i]; i++) 
     3122              if (targetNode.parentNode == node) return [targetNode]; 
     3123          } else if (combinator == 'descendant') { 
     3124            for (var i = 0, node; node = nodes[i]; i++) 
     3125              if (Element.descendantOf(targetNode, node)) return [targetNode]; 
     3126          } else if (combinator == 'adjacent') { 
     3127            for (var i = 0, node; node = nodes[i]; i++) 
     3128              if (Selector.handlers.previousElementSibling(targetNode) == node) 
     3129                return [targetNode]; 
     3130          } else nodes = h[combinator](nodes); 
     3131        } 
     3132        for (var i = 0, node; node = nodes[i]; i++) 
     3133          if (node == targetNode) return [targetNode]; 
     3134        return []; 
     3135      } 
     3136      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; 
     3137    }, 
     3138 
     3139    className: function(nodes, root, className, combinator) { 
     3140      if (nodes && combinator) nodes = this[combinator](nodes); 
     3141      return Selector.handlers.byClassName(nodes, root, className); 
     3142    }, 
     3143 
     3144    byClassName: function(nodes, root, className) { 
     3145      if (!nodes) nodes = Selector.handlers.descendant([root]); 
     3146      var needle = ' ' + className + ' '; 
     3147      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { 
     3148        nodeClassName = node.className; 
     3149        if (nodeClassName.length == 0) continue; 
     3150        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) 
     3151          results.push(node); 
     3152      } 
     3153      return results; 
     3154    }, 
     3155 
     3156    attrPresence: function(nodes, root, attr, combinator) { 
     3157      if (!nodes) nodes = root.getElementsByTagName("*"); 
     3158      if (nodes && combinator) nodes = this[combinator](nodes); 
     3159      var results = []; 
     3160      for (var i = 0, node; node = nodes[i]; i++) 
     3161        if (Element.hasAttribute(node, attr)) results.push(node); 
     3162      return results; 
     3163    }, 
     3164 
     3165    attr: function(nodes, root, attr, value, operator, combinator) { 
     3166      if (!nodes) nodes = root.getElementsByTagName("*"); 
     3167      if (nodes && combinator) nodes = this[combinator](nodes); 
     3168      var handler = Selector.operators[operator], results = []; 
     3169      for (var i = 0, node; node = nodes[i]; i++) { 
     3170        var nodeValue = Element.readAttribute(node, attr); 
     3171        if (nodeValue === null) continue; 
     3172        if (handler(nodeValue, value)) results.push(node); 
     3173      } 
     3174      return results; 
     3175    }, 
     3176 
     3177    pseudo: function(nodes, name, value, root, combinator) { 
     3178      if (nodes && combinator) nodes = this[combinator](nodes); 
     3179      if (!nodes) nodes = root.getElementsByTagName("*"); 
     3180      return Selector.pseudos[name](nodes, value, root); 
     3181    } 
     3182  }, 
     3183 
     3184  pseudos: { 
     3185    'first-child': function(nodes, value, root) { 
     3186      for (var i = 0, results = [], node; node = nodes[i]; i++) { 
     3187        if (Selector.handlers.previousElementSibling(node)) continue; 
     3188          results.push(node); 
     3189      } 
     3190      return results; 
     3191    }, 
     3192    'last-child': function(nodes, value, root) { 
     3193      for (var i = 0, results = [], node; node = nodes[i]; i++) { 
     3194        if (Selector.handlers.nextElementSibling(node)) continue; 
     3195          results.push(node); 
     3196      } 
     3197      return results; 
     3198    }, 
     3199    'only-child': function(nodes, value, root) { 
     3200      var h = Selector.handlers; 
     3201      for (var i = 0, results = [], node; node = nodes[i]; i++) 
     3202        if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) 
     3203          results.push(node); 
     3204      return results; 
     3205    }, 
     3206    'nth-child':        function(nodes, formula, root) { 
     3207      return Selector.pseudos.nth(nodes, formula, root); 
     3208    }, 
     3209    'nth-last-child':   function(nodes, formula, root) { 
     3210      return Selector.pseudos.nth(nodes, formula, root, true); 
     3211    }, 
     3212    'nth-of-type':      function(nodes, formula, root) { 
     3213      return Selector.pseudos.nth(nodes, formula, root, false, true); 
     3214    }, 
     3215    'nth-last-of-type': function(nodes, formula, root) { 
     3216      return Selector.pseudos.nth(nodes, formula, root, true, true); 
     3217    }, 
     3218    'first-of-type':    function(nodes, formula, root) { 
     3219      return Selector.pseudos.nth(nodes, "1", root, false, true); 
     3220    }, 
     3221    'last-of-type':     function(nodes, formula, root) { 
     3222      return Selector.pseudos.nth(nodes, "1", root, true, true); 
     3223    }, 
     3224    'only-of-type':     function(nodes, formula, root) { 
     3225      var p = Selector.pseudos; 
     3226      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); 
     3227    }, 
     3228 
     3229    // handles the an+b logic 
     3230    getIndices: function(a, b, total) { 
     3231      if (a == 0) return b > 0 ? [b] : []; 
     3232      return $R(1, total).inject([], function(memo, i) { 
     3233        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); 
     3234        return memo; 
     3235      }); 
     3236    }, 
     3237 
     3238    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type 
     3239    nth: function(nodes, formula, root, reverse, ofType) { 
     3240      if (nodes.length == 0) return []; 
     3241      if (formula == 'even') formula = '2n+0'; 
     3242      if (formula == 'odd')  formula = '2n+1'; 
     3243      var h = Selector.handlers, results = [], indexed = [], m; 
     3244      h.mark(nodes); 
     3245      for (var i = 0, node; node = nodes[i]; i++) { 
     3246        if (!node.parentNode._countedByPrototype) { 
     3247          h.index(node.parentNode, reverse, ofType); 
     3248          indexed.push(node.parentNode); 
     3249        } 
     3250      } 
     3251      if (formula.match(/^\d+$/)) { // just a number 
     3252        formula = Number(formula); 
     3253        for (var i = 0, node; node = nodes[i]; i++) 
     3254          if (node.nodeIndex == formula) results.push(node); 
     3255      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b 
     3256        if (m[1] == "-") m[1] = -1; 
     3257        var a = m[1] ? Number(m[1]) : 1; 
     3258        var b = m[2] ? Number(m[2]) : 0; 
     3259        var indices = Selector.pseudos.getIndices(a, b, nodes.length); 
     3260        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { 
     3261          for (var j = 0; j < l; j++) 
     3262            if (node.nodeIndex == indices[j]) results.push(node); 
     3263        } 
     3264      } 
     3265      h.unmark(nodes); 
     3266      h.unmark(indexed); 
     3267      return results; 
     3268    }, 
     3269 
     3270    'empty': function(nodes, value, root) { 
     3271      for (var i = 0, results = [], node; node = nodes[i]; i++) { 
     3272        // IE treats comments as element nodes 
     3273        if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue; 
     3274        results.push(node); 
     3275      } 
     3276      return results; 
     3277    }, 
     3278 
     3279    'not': function(nodes, selector, root) { 
     3280      var h = Selector.handlers, selectorType, m; 
     3281      var exclusions = new Selector(selector).findElements(root); 
     3282      h.mark(exclusions); 
     3283      for (var i = 0, results = [], node; node = nodes[i]; i++) 
     3284        if (!node._countedByPrototype) results.push(node); 
     3285      h.unmark(exclusions); 
     3286      return results; 
     3287    }, 
     3288 
     3289    'enabled': function(nodes, value, root) { 
     3290      for (var i = 0, results = [], node; node = nodes[i]; i++) 
     3291        if (!node.disabled) results.push(node); 
     3292      return results; 
     3293    }, 
     3294 
     3295    'disabled': function(nodes, value, root) { 
     3296      for (var i = 0, results = [], node; node = nodes[i]; i++) 
     3297        if (node.disabled) results.push(node); 
     3298      return results; 
     3299    }, 
     3300 
     3301    'checked': function(nodes, value, root) { 
     3302      for (var i = 0, results = [], node; node = nodes[i]; i++) 
     3303        if (node.checked) results.push(node); 
     3304      return results; 
     3305    } 
     3306  }, 
     3307 
     3308  operators: { 
     3309    '=':  function(nv, v) { return nv == v; }, 
     3310    '!=': function(nv, v) { return nv != v; }, 
     3311    '^=': function(nv, v) { return nv.startsWith(v); }, 
     3312    '$=': function(nv, v) { return nv.endsWith(v); }, 
     3313    '*=': function(nv, v) { return nv.include(v); }, 
     3314    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, 
     3315    '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } 
     3316  }, 
     3317 
     3318  split: function(expression) { 
     3319    var expressions = []; 
     3320    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { 
     3321      expressions.push(m[1].strip()); 
     3322    }); 
     3323    return expressions; 
     3324  }, 
     3325 
     3326  matchElements: function(elements, expression) { 
     3327    var matches = $$(expression), h = Selector.handlers; 
     3328    h.mark(matches); 
     3329    for (var i = 0, results = [], element; element = elements[i]; i++) 
     3330      if (element._countedByPrototype) results.push(element); 
     3331    h.unmark(matches); 
     3332    return results; 
     3333  }, 
     3334 
     3335  findElement: function(elements, expression, index) { 
     3336    if (Object.isNumber(expression)) { 
     3337      index = expression; expression = false; 
     3338    } 
     3339    return Selector.matchElements(elements, expression || '*')[index || 0]; 
     3340  }, 
     3341 
     3342  findChildElements: function(element, expressions) { 
     3343    expressions = Selector.split(expressions.join(',')); 
     3344    var results = [], h = Selector.handlers; 
     3345    for (var i = 0, l = expressions.length, selector; i < l; i++) { 
     3346      selector = new Selector(expressions[i].strip()); 
     3347      h.concat(results, selector.findElements(element)); 
     3348    } 
     3349    return (l > 1) ? h.unique(results) : results; 
     3350  } 
    26193351}); 
    26203352 
     3353if (Prototype.Browser.IE) { 
     3354  Object.extend(Selector.handlers, { 
     3355    // IE returns comment nodes on getElementsByTagName("*"). 
     3356    // Filter them out. 
     3357    concat: function(a, b) { 
     3358      for (var i = 0, node; node = b[i]; i++) 
     3359        if (node.tagName !== "!") a.push(node); 
     3360      return a; 
     3361    }, 
     3362 
     3363    // IE improperly serializes _countedByPrototype in (inner|outer)HTML. 
     3364    unmark: function(nodes) { 
     3365      for (var i = 0, node; node = nodes[i]; i++) 
     3366        node.removeAttribute('_countedByPrototype');