Upgrade to Rails 1.1.
[selectricity-live] / public / javascripts / prototype.js
index 0ba70a77da3d5281a8e5cbc48e818d791d9950cf..0caf9cd7f0b7d902b0dada3752de348ed27c02cd 100644 (file)
@@ -1,17 +1,14 @@
-/*  Prototype JavaScript framework, version 1.4.0_rc2
+/*  Prototype JavaScript framework, version 1.5.0_rc0
  *  (c) 2005 Sam Stephenson <sam@conio.net>
  *
- *  THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff
- *  against the source tree, available from the Prototype darcs repository.
- *
  *  Prototype is freely distributable under the terms of an MIT-style license.
- *
  *  For details, see the Prototype web site: http://prototype.conio.net/
  *
 /*--------------------------------------------------------------------------*/
 
 var Prototype = {
-  Version: '1.4.0_rc2',
+  Version: '1.5.0_rc0',
+  ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
 
   emptyFunction: function() {},
   K: function(x) {return x}
@@ -28,7 +25,7 @@ var Class = {
 var Abstract = new Object();
 
 Object.extend = function(destination, source) {
-  for (property in source) {
+  for (var property in source) {
     destination[property] = source[property];
   }
   return destination;
@@ -45,10 +42,10 @@ Object.inspect = function(object) {
   }
 }
 
-Function.prototype.bind = function(object) {
-  var __method = this;
+Function.prototype.bind = function() {
+  var __method = this, args = $A(arguments), object = args.shift();
   return function() {
-    return __method.apply(object, arguments);
+    return __method.apply(object, args.concat($A(arguments)));
   }
 }
 
@@ -119,30 +116,69 @@ PeriodicalExecuter.prototype = {
     }
   }
 }
+Object.extend(String.prototype, {
+  gsub: function(pattern, replacement) {
+    var result = '', source = this, match;
+    replacement = arguments.callee.prepareReplacement(replacement);
+
+    while (source.length > 0) {
+      if (match = source.match(pattern)) {
+        result += source.slice(0, match.index);
+        result += (replacement(match) || '').toString();
+        source  = source.slice(match.index + match[0].length);
+      } else {
+        result += source, source = '';
+      }
+    }
+    return result;
+  },
 
-/*--------------------------------------------------------------------------*/
+  sub: function(pattern, replacement, count) {
+    replacement = this.gsub.prepareReplacement(replacement);
+    count = count === undefined ? 1 : count;
 
-function $() {
-  var elements = new Array();
+    return this.gsub(pattern, function(match) {
+      if (--count < 0) return match[0];
+      return replacement(match);
+    });
+  },
 
-  for (var i = 0; i < arguments.length; i++) {
-    var element = arguments[i];
-    if (typeof element == 'string')
-      element = document.getElementById(element);
+  scan: function(pattern, iterator) {
+    this.gsub(pattern, iterator);
+    return this;
+  },
 
-    if (arguments.length == 1)
-      return element;
+  truncate: function(length, truncation) {
+    length = length || 30;
+    truncation = truncation === undefined ? '...' : truncation;
+    return this.length > length ?
+      this.slice(0, length - truncation.length) + truncation : this;
+  },
 
-    elements.push(element);
-  }
+  strip: function() {
+    return this.replace(/^\s+/, '').replace(/\s+$/, '');
+  },
 
-  return elements;
-}
-Object.extend(String.prototype, {
   stripTags: function() {
     return this.replace(/<\/?[^>]+>/gi, '');
   },
 
+  stripScripts: function() {
+    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
+  },
+
+  extractScripts: function() {
+    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
+    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
+    return (this.match(matchAll) || []).map(function(scriptTag) {
+      return (scriptTag.match(matchOne) || ['', ''])[1];
+    });
+  },
+
+  evalScripts: function() {
+    return this.extractScripts().map(function(script) { return eval(script) });
+  },
+
   escapeHTML: function() {
     var div = document.createElement('div');
     var text = document.createTextNode(this);
@@ -186,12 +222,35 @@ Object.extend(String.prototype, {
   },
 
   inspect: function() {
-    return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'";
+    return "'" + this.replace(/\\/g, '\\\\').replace(/'/g, '\\\'') + "'";
   }
 });
 
+String.prototype.gsub.prepareReplacement = function(replacement) {
+  if (typeof replacement == 'function') return replacement;
+  var template = new Template(replacement);
+  return function(match) { return template.evaluate(match) };
+}
+
 String.prototype.parseQuery = String.prototype.toQueryParams;
 
+var Template = Class.create();
+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
+Template.prototype = {
+  initialize: function(template, pattern) {
+    this.template = template.toString();
+    this.pattern  = pattern || Template.Pattern;
+  },
+
+  evaluate: function(object) {
+    return this.template.gsub(this.pattern, function(match) {
+      var before = match[1];
+      if (before == '\\') return match[2];
+      return before + (object[match[3]] || '').toString();
+    });
+  }
+}
+
 var $break    = new Object();
 var $continue = new Object();
 
@@ -214,8 +273,8 @@ var Enumerable = {
   all: function(iterator) {
     var result = true;
     this.each(function(value, index) {
-      if (!(result &= (iterator || Prototype.K)(value, index)))
-        throw $break;
+      result = result && !!(iterator || Prototype.K)(value, index);
+      if (!result) throw $break;
     });
     return result;
   },
@@ -223,7 +282,7 @@ var Enumerable = {
   any: function(iterator) {
     var result = true;
     this.each(function(value, index) {
-      if (result &= (iterator || Prototype.K)(value, index))
+      if (result = !!(iterator || Prototype.K)(value, index))
         throw $break;
     });
     return result;
@@ -296,7 +355,7 @@ var Enumerable = {
     var result;
     this.each(function(value, index) {
       value = (iterator || Prototype.K)(value, index);
-      if (value >= (result || value))
+      if (result == undefined || value >= result)
         result = value;
     });
     return result;
@@ -306,7 +365,7 @@ var Enumerable = {
     var result;
     this.each(function(value, index) {
       value = (iterator || Prototype.K)(value, index);
-      if (value <= (result || value))
+      if (result == undefined || value < result)
         result = value;
     });
     return result;
@@ -358,8 +417,7 @@ var Enumerable = {
 
     var collections = [this].concat(args).map($A);
     return this.map(function(value, index) {
-      iterator(value = collections.pluck(index));
-      return value;
+      return iterator(collections.pluck(index));
     });
   },
 
@@ -376,6 +434,7 @@ Object.extend(Enumerable, {
   entries: Enumerable.toArray
 });
 var $A = Array.from = function(iterable) {
+  if (!iterable) return [];
   if (iterable.toArray) {
     return iterable.toArray();
   } else {
@@ -388,12 +447,20 @@ var $A = Array.from = function(iterable) {
 
 Object.extend(Array.prototype, Enumerable);
 
+if (!Array.prototype._reverse)
+  Array.prototype._reverse = Array.prototype.reverse;
+
 Object.extend(Array.prototype, {
   _each: function(iterator) {
     for (var i = 0; i < this.length; i++)
       iterator(this[i]);
   },
 
+  clear: function() {
+    this.length = 0;
+    return this;
+  },
+
   first: function() {
     return this[0];
   },
@@ -410,7 +477,7 @@ Object.extend(Array.prototype, {
 
   flatten: function() {
     return this.inject([], function(array, value) {
-      return array.concat(value.constructor == Array ?
+      return array.concat(value && value.constructor == Array ?
         value.flatten() : [value]);
     });
   },
@@ -425,14 +492,11 @@ Object.extend(Array.prototype, {
   indexOf: function(object) {
     for (var i = 0; i < this.length; i++)
       if (this[i] == object) return i;
-    return false;
+    return -1;
   },
 
-  reverse: function() {
-    var result = [];
-    for (var i = this.length; i > 0; i--)
-      result.push(this[i-1]);
-    return result;
+  reverse: function(inline) {
+    return (inline !== false ? this : this.toArray())._reverse();
   },
 
   inspect: function() {
@@ -441,7 +505,7 @@ Object.extend(Array.prototype, {
 });
 var Hash = {
   _each: function(iterator) {
-    for (key in this) {
+    for (var key in this) {
       var value = this[key];
       if (typeof value == 'function') continue;
 
@@ -486,9 +550,9 @@ function $H(object) {
   Object.extend(hash, Hash);
   return hash;
 }
-var Range = Class.create();
-Object.extend(Range.prototype, Enumerable);
-Object.extend(Range.prototype, {
+ObjectRange = Class.create();
+Object.extend(ObjectRange.prototype, Enumerable);
+Object.extend(ObjectRange.prototype, {
   initialize: function(start, end, exclusive) {
     this.start = start;
     this.end = end;
@@ -513,15 +577,15 @@ Object.extend(Range.prototype, {
 });
 
 var $R = function(start, end, exclusive) {
-  return new Range(start, end, exclusive);
+  return new ObjectRange(start, end, exclusive);
 }
 
 var Ajax = {
   getTransport: function() {
     return Try.these(
+      function() {return new XMLHttpRequest()},
       function() {return new ActiveXObject('Msxml2.XMLHTTP')},
-      function() {return new ActiveXObject('Microsoft.XMLHTTP')},
-      function() {return new XMLHttpRequest()}
+      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
     ) || false;
   },
 
@@ -549,8 +613,7 @@ Ajax.Responders = {
       if (responder[callback] && typeof responder[callback] == 'function') {
         try {
           responder[callback].apply(responder, [request, transport, json]);
-        } catch (e) {
-        }
+        } catch (e) {}
       }
     });
   }
@@ -574,6 +637,7 @@ Ajax.Base.prototype = {
     this.options = {
       method:       'post',
       asynchronous: true,
+      contentType:  'application/x-www-form-urlencoded',
       parameters:   ''
     }
     Object.extend(this.options, options || {});
@@ -626,19 +690,18 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
       this.transport.send(this.options.method == 'post' ? body : null);
 
     } catch (e) {
-      (this.options.onException || Prototype.emptyFunction)(this, e);
-      Ajax.Responders.dispatch('onException', this, e);
+      this.dispatchException(e);
     }
   },
 
   setRequestHeaders: function() {
     var requestHeaders =
       ['X-Requested-With', 'XMLHttpRequest',
-       'X-Prototype-Version', Prototype.Version];
+       'X-Prototype-Version', Prototype.Version,
+       'Accept', 'text/javascript, text/html, application/xml, text/xml, */*'];
 
     if (this.options.method == 'post') {
-      requestHeaders.push('Content-type',
-        'application/x-www-form-urlencoded');
+      requestHeaders.push('Content-type', this.options.contentType);
 
       /* Force "Connection: close" for Mozilla browsers to work around
        * a bug where XMLHttpReqeuest sends an incorrect Content-length
@@ -661,12 +724,23 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
       this.respondToReadyState(this.transport.readyState);
   },
 
+  header: function(name) {
+    try {
+      return this.transport.getResponseHeader(name);
+    } catch (e) {}
+  },
+
   evalJSON: function() {
     try {
-      var json = this.transport.getResponseHeader('X-JSON'), object;
-      object = eval(json);
-      return object;
+      return eval('(' + this.header('X-JSON') + ')');
+    } catch (e) {}
+  },
+
+  evalResponse: function() {
+    try {
+      return eval(this.transport.responseText);
     } catch (e) {
+      this.dispatchException(e);
     }
   },
 
@@ -674,22 +748,38 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
     var event = Ajax.Request.Events[readyState];
     var transport = this.transport, json = this.evalJSON();
 
-    if (event == 'Complete')
-      (this.options['on' + this.transport.status]
-       || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
-       || Prototype.emptyFunction)(transport, json);
+    if (event == 'Complete') {
+      try {
+        (this.options['on' + this.transport.status]
+         || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
+         || Prototype.emptyFunction)(transport, json);
+      } catch (e) {
+        this.dispatchException(e);
+      }
 
-    (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
-    Ajax.Responders.dispatch('on' + event, this, transport, json);
+      if ((this.header('Content-type') || '').match(/^text\/javascript/i))
+        this.evalResponse();
+    }
+
+    try {
+      (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
+      Ajax.Responders.dispatch('on' + event, this, transport, json);
+    } catch (e) {
+      this.dispatchException(e);
+    }
 
     /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
     if (event == 'Complete')
       this.transport.onreadystatechange = Prototype.emptyFunction;
+  },
+
+  dispatchException: function(exception) {
+    (this.options.onException || Prototype.emptyFunction)(this, exception);
+    Ajax.Responders.dispatch('onException', this, exception);
   }
 });
 
 Ajax.Updater = Class.create();
-Ajax.Updater.ScriptFragment = '(?:<script.*?>)((\n|.)*?)(?:<\/script>)';
 
 Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
   initialize: function(container, url, options) {
@@ -714,16 +804,16 @@ Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
   updateContent: function() {
     var receiver = this.responseIsSuccess() ?
       this.containers.success : this.containers.failure;
+    var response = this.transport.responseText;
 
-    var match    = new RegExp(Ajax.Updater.ScriptFragment, 'img');
-    var response = this.transport.responseText.replace(match, '');
-    var scripts  = this.transport.responseText.match(match);
+    if (!this.options.evalScripts)
+      response = response.stripScripts();
 
     if (receiver) {
       if (this.options.insertion) {
         new this.options.insertion(receiver, response);
       } else {
-        receiver.innerHTML = response;
+        Element.update(receiver, response);
       }
     }
 
@@ -731,14 +821,6 @@ Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
       if (this.onComplete)
         setTimeout(this.onComplete.bind(this), 10);
     }
-
-    if (this.options.evalScripts && scripts) {
-      match = new RegExp(Ajax.Updater.ScriptFragment, 'im');
-      setTimeout((function() {
-        for (var i = 0; i < scripts.length; i++)
-          eval(scripts[i].match(match)[1]);
-      }).bind(this), 10);
-    }
   }
 });
 
@@ -784,22 +866,57 @@ Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
     this.updater = new Ajax.Updater(this.container, this.url, this.options);
   }
 });
+function $() {
+  var results = [], element;
+  for (var i = 0; i < arguments.length; i++) {
+    element = arguments[i];
+    if (typeof element == 'string')
+      element = document.getElementById(element);
+    results.push(Element.extend(element));
+  }
+  return results.length < 2 ? results[0] : results;
+}
+
 document.getElementsByClassName = function(className, parentElement) {
   var children = ($(parentElement) || document.body).getElementsByTagName('*');
   return $A(children).inject([], function(elements, child) {
     if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
-      elements.push(child);
+      elements.push(Element.extend(child));
     return elements;
   });
 }
 
 /*--------------------------------------------------------------------------*/
 
-if (!window.Element) {
+if (!window.Element)
   var Element = new Object();
+
+Element.extend = function(element) {
+  if (!element) return;
+  if (_nativeExtensions) return element;
+
+  if (!element._extended && element.tagName && element != window) {
+    var methods = Element.Methods, cache = Element.extend.cache;
+    for (property in methods) {
+      var value = methods[property];
+      if (typeof value == 'function')
+        element[property] = cache.findOrStore(value);
+    }
+  }
+
+  element._extended = true;
+  return element;
 }
 
-Object.extend(Element, {
+Element.extend.cache = {
+  findOrStore: function(value) {
+    return this[value] = this[value] || function() {
+      return value.apply(null, [this].concat($A(arguments)));
+    }
+  }
+}
+
+Element.Methods = {
   visible: function(element) {
     return $(element).style.display != 'none';
   },
@@ -830,6 +947,24 @@ Object.extend(Element, {
     element.parentNode.removeChild(element);
   },
 
+  update: function(element, html) {
+    $(element).innerHTML = html.stripScripts();
+    setTimeout(function() {html.evalScripts()}, 10);
+  },
+
+  replace: function(element, html) {
+    element = $(element);
+    if (element.outerHTML) {
+      element.outerHTML = html.stripScripts();
+    } else {
+      var range = element.ownerDocument.createRange();
+      range.selectNodeContents(element);
+      element.parentNode.replaceChild(
+        range.createContextualFragment(html.stripScripts()), element);
+    }
+    setTimeout(function() {html.evalScripts()}, 10);
+  },
+
   getHeight: function(element) {
     element = $(element);
     return element.offsetHeight;
@@ -868,6 +1003,13 @@ Object.extend(Element, {
     return $(element).innerHTML.match(/^\s*$/);
   },
 
+  childOf: function(element, ancestor) {
+    element = $(element), ancestor = $(ancestor);
+    while (element = element.parentNode)
+      if (element == ancestor) return true;
+    return false;
+  },
+
   scrollTo: function(element) {
     element = $(element);
     var x = element.x ? element.x : element.offsetLeft,
@@ -893,6 +1035,12 @@ Object.extend(Element, {
     return value == 'auto' ? null : value;
   },
 
+  setStyle: function(element, style) {
+    element = $(element);
+    for (var name in style)
+      element.style[name.camelize()] = style[name];
+  },
+
   getDimensions: function(element) {
     element = $(element);
     if (Element.getStyle(element, 'display') != 'none')
@@ -955,7 +1103,32 @@ Object.extend(Element, {
     element.style.overflow = element._overflow;
     element._overflow = undefined;
   }
-});
+}
+
+Object.extend(Element, Element.Methods);
+
+var _nativeExtensions = false;
+
+if(!HTMLElement && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
+  var HTMLElement = {}
+  HTMLElement.prototype = document.createElement('div').__proto__;
+}
+
+Element.addMethods = function(methods) {
+  Object.extend(Element.Methods, methods || {});
+
+  if(typeof HTMLElement != 'undefined') {
+    var methods = Element.Methods, cache = Element.extend.cache;
+    for (property in methods) {
+      var value = methods[property];
+      if (typeof value == 'function')
+        HTMLElement.prototype[property] = cache.findOrStore(value);
+    }
+    _nativeExtensions = true;
+  }
+}
+
+Element.addMethods();
 
 var Toggle = new Object();
 Toggle.display = Element.toggle;
@@ -969,13 +1142,14 @@ Abstract.Insertion = function(adjacency) {
 Abstract.Insertion.prototype = {
   initialize: function(element, content) {
     this.element = $(element);
-    this.content = content;
+    this.content = content.stripScripts();
 
     if (this.adjacency && this.element.insertAdjacentHTML) {
       try {
         this.element.insertAdjacentHTML(this.adjacency, this.content);
       } catch (e) {
-        if (this.element.tagName.toLowerCase() == 'tbody') {
+        var tagName = this.element.tagName.toLowerCase();
+        if (tagName == 'tbody' || tagName == 'tr') {
           this.insertContent(this.contentFromAnonymousTable());
         } else {
           throw e;
@@ -986,6 +1160,8 @@ Abstract.Insertion.prototype = {
       if (this.initializeRange) this.initializeRange();
       this.insertContent([this.range.createContextualFragment(this.content)]);
     }
+
+    setTimeout(function() {content.evalScripts()}, 10);
   },
 
   contentFromAnonymousTable: function() {
@@ -1018,7 +1194,7 @@ Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
   },
 
   insertContent: function(fragments) {
-    fragments.reverse().each((function(fragment) {
+    fragments.reverse(false).each((function(fragment) {
       this.element.insertBefore(fragment, this.element.firstChild);
     }).bind(this));
   }
@@ -1079,7 +1255,7 @@ Element.ClassNames.prototype = {
     if (!this.include(classNameToRemove)) return;
     this.set(this.select(function(className) {
       return className != classNameToRemove;
-    }));
+    }).join(' '));
   },
 
   toString: function() {
@@ -1088,6 +1264,116 @@ Element.ClassNames.prototype = {
 }
 
 Object.extend(Element.ClassNames.prototype, Enumerable);
+var Selector = Class.create();
+Selector.prototype = {
+  initialize: function(expression) {
+    this.params = {classNames: []};
+    this.expression = expression.toString().strip();
+    this.parseExpression();
+    this.compileMatcher();
+  },
+
+  parseExpression: function() {
+    function abort(message) { throw 'Parse error in selector: ' + message; }
+
+    if (this.expression == '')  abort('empty expression');
+
+    var params = this.params, expr = this.expression, match, modifier, clause, rest;
+    while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
+      params.attributes = params.attributes || [];
+      params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
+      expr = match[1];
+    }
+
+    if (expr == '*') return this.params.wildcard = true;
+
+    while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
+      modifier = match[1], clause = match[2], rest = match[3];
+      switch (modifier) {
+        case '#':       params.id = clause; break;
+        case '.':       params.classNames.push(clause); break;
+        case '':
+        case undefined: params.tagName = clause.toUpperCase(); break;
+        default:        abort(expr.inspect());
+      }
+      expr = rest;
+    }
+
+    if (expr.length > 0) abort(expr.inspect());
+  },
+
+  buildMatchExpression: function() {
+    var params = this.params, conditions = [], clause;
+
+    if (params.wildcard)
+      conditions.push('true');
+    if (clause = params.id)
+      conditions.push('element.id == ' + clause.inspect());
+    if (clause = params.tagName)
+      conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
+    if ((clause = params.classNames).length > 0)
+      for (var i = 0; i < clause.length; i++)
+        conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')');
+    if (clause = params.attributes) {
+      clause.each(function(attribute) {
+        var value = 'element.getAttribute(' + attribute.name.inspect() + ')';
+        var splitValueBy = function(delimiter) {
+          return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
+        }
+
+        switch (attribute.operator) {
+          case '=':       conditions.push(value + ' == ' + attribute.value.inspect()); break;
+          case '~=':      conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
+          case '|=':      conditions.push(
+                            splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()
+                          ); break;
+          case '!=':      conditions.push(value + ' != ' + attribute.value.inspect()); break;
+          case '':
+          case undefined: conditions.push(value + ' != null'); break;
+          default:        throw 'Unknown operator ' + attribute.operator + ' in selector';
+        }
+      });
+    }
+
+    return conditions.join(' && ');
+  },
+
+  compileMatcher: function() {
+    this.match = new Function('element', 'if (!element.tagName) return false; \
+      return ' + this.buildMatchExpression());
+  },
+
+  findElements: function(scope) {
+    var element;
+
+    if (element = $(this.params.id))
+      if (this.match(element))
+        if (!scope || Element.childOf(element, scope))
+          return [element];
+
+    scope = (scope || document).getElementsByTagName(this.params.tagName || '*');
+
+    var results = [];
+    for (var i = 0; i < scope.length; i++)
+      if (this.match(element = scope[i]))
+        results.push(Element.extend(element));
+
+    return results;
+  },
+
+  toString: function() {
+    return this.expression;
+  }
+}
+
+function $$() {
+  return $A(arguments).map(function(expression) {
+    return expression.strip().split(/\s+/).inject([null], function(results, expr) {
+      var selector = new Selector(expr);
+      return results.map(selector.findElements.bind(selector)).flatten();
+    });
+  }).flatten();
+}
 var Field = {
   clear: function() {
     for (var i = 0; i < arguments.length; i++)
@@ -1109,8 +1395,10 @@ var Field = {
   },
 
   activate: function(element) {
-    $(element).focus();
-    $(element).select();
+    element = $(element);
+    element.focus();
+    if (element.select)
+      element.select();
   }
 }
 
@@ -1134,7 +1422,7 @@ var Form = {
     form = $(form);
     var elements = new Array();
 
-    for (tagName in Form.Element.Serializers) {
+    for (var tagName in Form.Element.Serializers) {
       var tagElements = form.getElementsByTagName(tagName);
       for (var j = 0; j < tagElements.length; j++)
         elements.push(tagElements[j]);
@@ -1178,16 +1466,15 @@ var Form = {
     }
   },
 
+  findFirstElement: function(form) {
+    return Form.getElements(form).find(function(element) {
+      return element.type != 'hidden' && !element.disabled &&
+        ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
+    });
+  },
+
   focusFirstElement: function(form) {
-    form = $(form);
-    var elements = Form.getElements(form);
-    for (var i = 0; i < elements.length; i++) {
-      var element = elements[i];
-      if (element.type != 'hidden' && !element.disabled) {
-        Field.activate(element);
-        break;
-      }
-    }
+    Field.activate(Form.findFirstElement(form));
   },
 
   reset: function(form) {
@@ -1201,9 +1488,17 @@ Form.Element = {
     var method = element.tagName.toLowerCase();
     var parameter = Form.Element.Serializers[method](element);
 
-    if (parameter)
-      return encodeURIComponent(parameter[0]) + '=' +
-        encodeURIComponent(parameter[1]);
+    if (parameter) {
+      var key = encodeURIComponent(parameter[0]);
+      if (key.length == 0) return;
+
+      if (parameter[1].constructor != Array)
+        parameter[1] = [parameter[1]];
+
+      return parameter[1].map(function(value) {
+        return key + '=' + encodeURIComponent(value);
+      }).join('&');
+    }
   },
 
   getValue: function(element) {
@@ -1249,23 +1544,17 @@ Form.Element.Serializers = {
     var value = '', opt, index = element.selectedIndex;
     if (index >= 0) {
       opt = element.options[index];
-      value = opt.value;
-      if (!value && !('value' in opt))
-        value = opt.text;
+      value = opt.value || opt.text;
     }
     return [element.name, value];
   },
 
   selectMany: function(element) {
-    var value = new Array();
+    var value = [];
     for (var i = 0; i < element.length; i++) {
       var opt = element.options[i];
-      if (opt.selected) {
-        var optValue = opt.value;
-        if (!optValue && !('value' in opt))
-          optValue = opt.text;
-        value.push(optValue);
-      }
+      if (opt.selected)
+        value.push(opt.value || opt.text);
     }
     return [element.name, value];
   }
@@ -1349,24 +1638,14 @@ Abstract.EventObserver.prototype = {
       switch (element.type.toLowerCase()) {
         case 'checkbox':
         case 'radio':
-          element.target = this;
-          element.prev_onclick = element.onclick || Prototype.emptyFunction;
-          element.onclick = function() {
-            this.prev_onclick();
-            this.target.onElementEvent();
-          }
+          Event.observe(element, 'click', this.onElementEvent.bind(this));
           break;
         case 'password':
         case 'text':
         case 'textarea':
         case 'select-one':
         case 'select-multiple':
-          element.target = this;
-          element.prev_onchange = element.onchange || Prototype.emptyFunction;
-          element.onchange = function() {
-            this.prev_onchange();
-            this.target.onElementEvent();
-          }
+          Event.observe(element, 'change', this.onElementEvent.bind(this));
           break;
       }
     }
@@ -1492,7 +1771,8 @@ Object.extend(Event, {
 });
 
 /* prevent memory leaks in IE */
-Event.observe(window, 'unload', Event.unloadCache, false);
+if (navigator.appVersion.match(/\bMSIE\b/))
+  Event.observe(window, 'unload', Event.unloadCache, false);
 var Position = {
   // set to true if needed, warning: firefox performance problems
   // NOT neeeded for page scrolling, only if draggable contained in

Benjamin Mako Hill || Want to submit a patch?