IT News

Prototype 1.6.0 release candidate

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 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.

    Release notes

    ...
    $("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

new Element("input", { name: "user", disabled: true });
//-> 

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: "
  • an item
  • " }); $("items").insert("
  • another item
  • "); // 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 - and