PHP Cross Reference of WordPress Subversion HEAD

[ Index ]     [ Classes ]     [ Functions ]     [ Variables ]     [ Constants ]

title

Body

[close]

/wp-includes/js/scriptaculous/ -> prototype.js (source)

   1  /*  Prototype JavaScript framework, version 1.5.1.1
   2   *  (c) 2005-2007 Sam Stephenson
   3   *
   4   *  Prototype is freely distributable under the terms of an MIT-style license.
   5   *  For details, see the Prototype web site: http://www.prototypejs.org/
   6   *
   7  /*--------------------------------------------------------------------------*/
   8  
   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 }
  32  }
  33  
  34  var Class = {
  35    create: function() {
  36      return function() {
  37        this.initialize.apply(this, arguments);
  38      }
  39    }
  40  }
  41  
  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;
  49  }
  50  
  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    }
 100  });
 101  
 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    }
 138  });
 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) + '"';
 147  };
 148  
 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    }
 210  });
 211  
 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    }
 400  });
 401  
 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    }
 409  });
 410  
 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('')
 422  });
 423  
 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
 639  });
 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    }
 753  });
 754  
 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.