perfection kills

Exploring prototype by example

Extending the limits

August 28th, 2007 by kangax

One of the beauties of prototype is in the way it allows us to extend its functionality. Building custom snippets of code has never been easier. The philosophy behind it is to keep core-level things at a minimum, but provide a convenient way to extend them.
The magic method is Element.addMethods

The syntax is quite simple:

Element.addMethods({
  method1: function() { ... },
  method2: function() { ... },
  ...
})

When defining a custom method, make sure that:

  • First passed argument is an element
    Element.addMethods({
      myAwesomeMethod: function(element, ... ) {
        element = $(element);
        ...
      }
    })
  • That same element is returned from the method (and is therefore chain-friendly)
    Element.addMethods({
      anotherAwesomeMethod: function(element, ... ) {
        element = $(element);
        ...
        return element;
      }
    })

We can also skip “var” when assigning element reference (since it’s passed as a first argument).

As an example, here’s a little helper, that I use quite often to display notifications (usually when form verification fails). We will update element with content, make it appear, wait couple of seconds and fade it out.

Note: The following example uses prototype version 1.6.0_rc0 and requires Scriptaculous’ effects module:

Element.addMethods({
  flash: function(element, content) {
    element = $(element);
    new Effect.Appear(element, {
      beforeStart: function() {
        element.update(content);
      },
      afterFinish: function() {
        Effect.Appear(element, {to: 0, delay: 3,
          afterFinish: function(){
            element.hide().setOpacity(1);
          }})
      }
    })
    return element;
  }
})

Now we can simply do:

$('errorBox').flash('login field should not be empty');

Try it out! Pretty convenient, huh?

Just to get you started, here are few more examples that might be convenient in every day use:

1) Form#populateFrom

Ever wanted to populate form via ajax? All it takes is a few lines of “magic”:

  • Description: Fills form with data via json (requires: v1.6.0_rc0+, ‘Content-type: application/json’ header)
  • Usage: $(’myForm’).populateFrom(’blah.php’);
  • Invoked on: Form
  populateFrom: function(element, url) {
    element = $(element);
    new Ajax.Request(url, {
      onSuccess: function(response) {
        var data = response.responseJSON;
        element.getElements().each(function(el) {
          el.setValue(data[el.readAttribute('name')])
        })
      }
    })
    return element;
  }

If your server-side script returns something like:

{
  "firstName": "Fluffy",
  "lastName": "Horse",
  "email": "far@far.away"
}

then invoking this method

$('myPrecious').populateFrom('myScript.php')

on a form with the same structure

<form action="foo.bar" id="myPrecious">
<input name="firstName" type="text" />
<input name="lastName" type="text" />
<input name="email" type="text" />
</form>

will populate JSON data into a form

2) Element#__extend

This one is a real gem and I find myself using it all the time. The idea is to be able to extend element with arbitrary number of methods/properties in a chain friendly manner.

  • Description: Extends element with a hash of properties
  • Usage: $$('input#firstName')[0].__extend({initialValue: 'John'}); $$('form').invoke('__extend', {counter: 0})
  • Invoked on: Any
__extend: function(element, hash) {
  return Object.extend($(element), hash);
}

As you can see this one is just a simple one-liner so we can easily skip explicit element assignment.
As an example of a real-life case, here’s how I used it in a Proto.Menu class:

new Element('a', {
      href: '#',
      title: item.name,
      className: item.className || ''})
  .observe('click', this.onClick.bind(this))
  .update(item.name)
  .__extend({
    _callback: item.callback,
    _disabled: item.disabled ? true : ''
  })
)

As you can see, it’s really convenient to store such things as callbacks, identifiers and boolean values as custom element properties. It’s also worth mentioning that Prototype 1.6+ extends observed elements with _eventID (preventing duplicate observers) in a similar manner.

Element#setUniqueClassName

Another common use case is when we need to assign a class to an element, removing it from its siblings at the same time. Most trivial example is setting “selected” or “active” class on navigation links

  • Description: Sets className, removing className from all siblings
  • Usage:$$('#nav li a')[0].setUniqueClassName('selected')
  • Invoked on: Any
setUniqueClassName: function(element, className) {
  var element = $(element),
  if (!element.hasClassName(className)) {
    collection = element.next() || element.previous() ? element.siblings() :
      $A(element.up(1).getElementsByTagName(element.tagName));
    collection.invoke('removeClassName', className);
    element.addClassName(className);
  }
  return element;
}

This method only starts iteration if current element does not have a specified className (in case already selected link was clicked). It’s also smart enough to iterate over child nodes of parent node siblings (since semantically-correct markup should contain links inside of list items). In this case, it will try to collect all <a> elements first and then iterate over them.

I hope you enjoyed this tutorial and are inspired by all the possibilities of prototyping : )

Categories: Element.addMethods, Script.aculo.us

Comments

  1. Gravatar

    RStankov said:

    setUniqueClassName is very interesting but I will offer you some small rewrite

    setUniqueClassName: function(element, className, up)
    {
    var element = $(element);

    if (!element.hasClassName(className))
    {
    $$(up ? element.up(up) : element.parentNode).getElementsBySelector('.' + className).invoke('removeClassName', className);

    element.addClassName(className);
    }

    return element;
    }

  2. Gravatar

    amal said:

    Hi people! I applied for credit cards for so many times. This is perhaps my last chance. Yesterday I found credit card applications at a website. Is it good to improve credit? Please consult. It’s called

    transfer a discover card balance

  3. Gravatar

    Maximus said:

    I would like to see a continuation of the topic

  4. Gravatar

    Diesel said:

  5. Gravatar

    Bob said:

    I disagree with you. Indeed, I’m not giving a ringing disagreement, but just sayin’ what I think. I have my opinion, you have yours.

Trackbacks

  1. Ajaxian » Ajaxian Featured Tutorial: Extending DOM elements Prototype’s said:

    [...] tutorial, Juriy Zaytsev, who authored the very popular Context Menu plugin for Prototype, discusses how to use Element.addMethods method of Prototype to extend DOM elements with custom methods and allow chaining: As an example, here’s [...]

  2. Javascript News » Blog Archive » Ajaxian Featured Tutorial: Extending DOM elements Prototype’s said:

    [...] tutorial, Juriy Zaytsev, who authored the very popular Context Menu plugin for Prototype, discusses how to use Element.addMethods method of Prototype to extend DOM elements with custom methods and allow chaining: As an example, here’s [...]

  3. GSIY … Ruby-Rails Portal said:

    [...] In my recent post I explain how to solve such common thing as iterating over menu items. Hope it helps Tutorails, RubyOnRails Home | | Login|Feed © 2007 GSIY … [...]

  4. GSIY … Ruby-Rails Portal said:

    [...] P.S. In my recent post I explain how to solve such common thing as iterating > over menu items.http://thinkweb2.com/projects/prototype/2007/08/28/extending-the-limits/ > Hope it helps > > – > View this message in [...]

  5. links for 2007-09-09 « Simply… A User said:

    [...] perfection kills » Prototype - Extending the limits (tags: javascript prototype programming tutorial dom oop web **) [...]

  6. Extendiendo Elementos DOM con Prototype « Quest’s Blog said:

    [...] Breve artículo inspirado por Extending the limits. [...]

  7. CSS, JavaScript, Rails, Ruby, Rails Plugins, TextMate « exceptionz said:

    [...] Extending the limits - exploring prototype by example [...]

  8. Ruby, JRuby, Rails, Rails Plugins, RSpec, JavaScript, CSS, SEO & Subversion « exceptionz said:

    [...] Extend DOM elements with custom methods in Prototype [...]

Leave a Comment

Allowed tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>