The first release candidate of Prototype 1.6.0 has arrived! The core team is continuing its tradition of bringing thoughtful incremental upgrades to the core APIs in addition to performance improvements and bug fixes. Keep reading for some of the highlights of this major release, or [download it now](/assets/2007/8/15/prototype.js) for instant gratification.

Event API Enhancements

We dubbed 1.6.0 the “event overhaul” release internally, and it shows — one of our worst APIs has become one of our best, overnight. Here’s what’s changed:

  • Event handlers registered with Event.observe or Element#observe are now automatically bound to the event’s target element in all browsers. This means that by default, this in an event handler refers to the element that fired the event. You can override this behavior by passing a bound function to observe.
  • Event objects are now extended with a collection of instance methods. This means you can now write event.stop() instead of Event.stop(event). Furthermore, the event object is normalized with W3C-standard property names in all browsers, so you can now write event.target instead of Event.element(event).
  • The event name and handler arguments to Event.stopObserving and Element#stopObserving are now optional, so for a given element, you can now quickly unregister all its handlers, or unregister all handlers for a single event.
  • References to observed elements are no longer stored in an internal cache, to prevent leaks.
  • Prototype now has support for custom events. Fire them on DOM elements by calling Element#fire with an event name and optional memo object. Internally, Prototype piggybacks your custom events on real DOM events, so they bubble up the document just like click events. This means custom event observers can respond to events fired from child elements. You can observe and fire on the document object for global events.
language: html <div id=“container”> <h1><span id=“title”>Release notes</span></h1> … </div> $(“container”).observe(“titleChanged”, function(event) { this.highlight({ duration: 0.5 }); }); $(“title”).fire(“titleChanged”);
  • We’ve built in cross-browser support for the DOMContentLoaded event using our custom event system, so you can now be notified as soon as the document is fully loaded:
document.observe(“contentloaded”, function() { … })

Function API Enhancements

We’ve added several methods on Function.prototype to better support functional and aspect-oriented programming techniques.

  • Function#wrap distills the essence of aspect-oriented programming into a single method, letting you easily build on existing functions by specifying before and after behavior, transforming the return value, or even preventing the original function from being called. Here’s an example of using wrap to add behavior to an existing Prototype String method:
String.prototype.capitalize = String.prototype.capitalize.wrap( function(proceed, eachWord) { if (eachWord && this.include(" ")) { // capitalize each word in the string return this.split(" “).invoke(”capitalize").join(" "); } else { // proceed using the original function return proceed(); } }); “hello world”.capitalize() // “Hello world” “hello world”.capitalize(true) // “Hello World”

For a less-contrived example, see how you can add jQuery-style element collection methods ($$(“div.widget”).show().highlight()) in under 40 lines of code by wrapping $$ and Element.addMethods.

  • Function#curry allows for partial function application, like Function#bind, but leaves the function’s scope unmodified.
function sum(a, b) { return a + b; } sum(10, 5) // 15 var addTen = sum.curry(10); addTen(5) // 15
  • Function#methodize encapsulates the pattern of converting a function’s first argument into its this value.
function addBorder(element, color) { return $(element).setStyle({ border: "3px solid " + (color || “red”) }); } addBorder(“sidebar”, “#ddd”); $(“sidebar”).addBorder = addBorder.methodize(); $(“sidebar”).addBorder(“#888”);

Prototype makes heavy use of methodize internally; for example, many Math methods are added to Number instances:

$w(“abs round ceil floor”).each(function(method) { Number.prototype[method] = Math[method].methodize(); });
  • Function#argumentNames returns an array of strings representing the function’s named arguments, as extracted from the function’s toString() value.
  • Function#delay provides a convenient wrapper around window.setTimeout, and Function#defer is delay with a 10ms timeout.
// add class “busy” after one second (function() { $(“form”).addClassName(“busy”) }).delay(1); // fire the “requestSent” event asynchronously (function() { $(“form”).fire(“requestSent”) }).defer();

Class API Enhancements

This release marks the first change to Prototype’s class API since version 1.0, and adds true support for inheritance and superclass methods. It’s backwards-compatible with the existing API.

  • Class.create now supports three alternate forms of invocation: you can now pass a class to inherit from, an anonymous object to mix into the new class’s prototype, or both.
  • The new Class.extend method works like Object.extend, but mixes the source object into the destination class’s prototype.
  • If you’re overriding a method from a parent class, you can now access the superclass method by naming the overriding function’s first argument $super. It works just like Function#wrap (in fact, it uses Function#wrap internally).
var Animal = Class.create({ initialize: function(name) { this.name = name; }, eat: function() { return this.say(“Yum!”); }, say: function(message) { return this.name + ": " + message; } }); // subclass that augments a method var Cat = Class.create(Animal, { eat: function($super, food) { if (food instanceof Mouse) return $super(); else return this.say(“Yuk! I only eat mice.”); } });
  • Classes now have constructor, superclass, and subclasses properties for powerful introspection of the inheritance hierarchy.

Ajax API Enhancements

Ajax.Request JSON support has been considerably improved in Prototype 1.6.0:

  • You can now access JSON response bodies as JavaScript objects using the transport object’s responseJSON property. (JSON responses must have a Content-type header of application/json.)
new Ajax.Request(“/people/5.json”, { // >> GET /people/5.json HTTP/1.1 onSuccess: function(transport) { // << Content-type: application/json var person = transport.responseJSON; // << { id: 5, name: “Tobie Langel”, person.city // “Geneva” // << city: “Geneva”, … } … }, method: “get” });
  • The transport object itself is now wrapped by Prototype in an Ajax.Response instance so it can be extended with properties like responseJSON in all browsers. Ajax.Response also features two new, error-proof methods to access headers, getHeader and getAllHeaders; easy access to the transport and request object themselves, with the request and transport properties; and direct access to all the other properties and methods of the transport object for full compatibility.

The following new options are available to Ajax.Request:

  • sanitizeJSON (true by default) checks the string for possible malicious fragments and prevents evaluation if any are detected.
  • evalJSON (true by default) auto-evaluates JSON data if the response’s Content-type is application/json and makes it available as the responseJSON property of the response object.
  • evalJS (true by default) auto-evaluates JavaScript if the response’s Content-type is text/javascript or equivalent.

DOM API Enhancements

  • Prototype now boasts a [new cross-browser DOM Builder](http://prototypejs.org/2007/5/12/dom-builder)
    :
new Element(“input”, { name: “user”, disabled: true }); //-> <input name=“user” disabled=“disabled” />

Internally the DOM builder uses Element#writeAttribute, another new addition to the DOM API which facilitates setting element attribute values in a cross-browser fashion.

  • We’ve deprecated the Insertion object and Position namespace in this release, and replaced them with methods on Element instead. Element#insert accepts multiple forms of content (DOM elements, HTML, simple text, or any object):
$(“items”).insert({ after: new Element(“p”) }); $(“items”).insert({ top: “<li>an item</li>” }); $(“items”).insert(“<li>another item</li>”); // defaults to bottom

If an object passed to insert contains a toHTML or a toElement method, that method will be used to produce an HTML string or DOM element for insertion.

var Calendar = Class.create({ // …, toElement: function() { var container = new Element(“div”); // … return container; } }); $(“sidebar”).insert(new Calendar()); // same as $(“sidebar”).insert({ bottom: new Calendar() }) or // $(“sidebar”).insert({ bottom: new Calendar().toElement() })
  • Element#update and Element#replace also both now accept DOM elements
    or objects with a toHTML or atoElement defined. We’ve also smoothed over issues with <table>- and <select>-related elements in IE and Opera 9.
  • Element#setStyle now accepts either a string of CSS rules (new in this version) or a hash of style/value pairs.
$(“header”).setStyle(“font-size: 12px; float: left; opacity: 0.5”);

Note that for performance reasons, we’ve deprecated the use of uncamelized style property names when setting styles using a hash. So If you have code that looks like this:

$(“header”).setStyle({ “font-size”: “12px” });

You need to replace it with either of the following:

$(“header”).setStyle({ fontSize: “12px” }); $(“header”).setStyle(“font-size: 12px”);
  • Element#identify is a new method which returns the element’s ID if one exists, or sets and returns a unique, auto-generated ID (of the form “anonymous_element_” + auto-incremented digit) otherwise:
language: html <div id=“my_div”> <p>some content</p> </div> $(“my_div”).identify(); // -> “my_div” $(“my_div”).down().identify(); // -> “anonymous_element_1” language: html <div id=“my_div”> <p id=“anonymous_element_1”>some content</p> </div>
  • Element#wrap is a convenient and flexible way to wrap DOM elements:
language: html <img id=“my_img” /> <span id=“my_span”>a picture</span> $(“my_img”).wrap(); $(“my_span”).wrap(‘p’, { className: “caption” }); language: html <div><img id=“my_img” /></div> <p class=“caption”><span id=“my_span”>a picture</span></p>

The new document.viewport object lets you calculate the dimensions and position of the browser’s viewport:

document.viewport.getDimensions() // { width: 1149, height: 923 } document.viewport.getWidth() // 1149 document.viewport.getHeight() // 923 document.viewport.getScrollOffsets() // { left: 0, top: 1592 }

Template API Enhancements

  • You can now perform one-off template replacements with String#interpolate, instead of having to first create a Template object and then call evaluate.
“#{last}, #{first}”.interpolate({ first: “Andrew”, last: “Dupont” }) // “Dupont, Andrew”
  • If you pass String#interpolate or Template#evaluate an object with a toTemplateReplacements method, the return value of that method will be used as the replacement object.
  • You can now substitute properties of template replacement values in template strings, using dot or bracket notation (or both).
“#{name.last}, #{name.first0}. (#{location})”.interpolate({ name: { first: “Christophe”, last: “Porteneuve” }, location: “Paris” }) // “Porteneuve, C. (Paris)”

Extended grep semantics

Prototype 1.6.0 introduces a new convention for the first argument to Enumerable#grep: the argument must be an object with a method named match, which method grep will call for each element in the Enumerable; if the method returns true, the element will be present in the array returned by grep. What this means is that you can now filter arrays with grep by passing it any object that has a match method. For example, you can now easily pare down an array of DOM elements to only those elements matching a particular CSS selector:

elements.grep(new Selector(“div.widget:first-child”))

Prototype aliases RegExp#test to RegExp#match, so existing code that e.g. greps string arrays using a regular expression will still work as expected. The match method convention is inspired by Ruby’s triple-equal (===) operator.

Improved support for JavaScript 1.6 and WHATWG 1.0 standards

We’ve emphasized our commitment to web standards in this release with improved support for JavaScript 1.6 and the WHATWG Web Applications 1.0 specification.

  • Array#indexOf no longer overrides the native method if present.
  • Enumerable now uses the native Array#forEach instead of Array#_each when possible.
  • Enumerable now has aliases for the JavaScript 1.6 Array methods filter, entries, every, and some, which map to findAll, toArray, all, and any, respectively.
  • All Enumerable methods now have an additional parameter, context, which, if present, specifies the object to which the iterators’ this is bound, for compatibility with JavaScript 1.6 Array methods.
  • Element#getElementsByClassName now supports multiple class names given as a whitespace-separated string, as specified by WHATWG and implemented by Firefox 3. The native version of getElementsByClassName is used in browsers that support it.

…And a whole lot more

These are just the highlights; see the CHANGELOG for the full story. We think the numerous small improvements to Prototype in this release have resulted in a supremely cohesive and intuitive API. So please give it a shot, let us know what you think on the mailing lists, and report any bugs you encounter (preferably with tested patches).

A note about this release candidate

We intend for Prototype 1.6.0_rc0 to be feature-complete, but we reserve the right to make adjustments to our new APIs and add or remove features before the final version is released.

Download

  • [Download Prototype 1.6.0_rc0](/assets/2007/8/15/prototype.js)
  • [Get Prototype help](/discuss) on the rails-spinoffs mailing list or #prototype IRC channel
  • [Interact with the Core Team](http://groups.google.com/group/prototype-core) on the prototype-core mailing list
  • [Submit bug reports](http://dev.rubyonrails.org/) to Rails Trac

Thanks to the many contributors who made this release possible!