Event.Behavior

Natural Language Event Programming for Prototype.js.

Introduction

This library is inspired by Adam McCrea's MetaProgramming Javascript Presentation. It is a work in progres and is being used in the development of several real world applications. Based on the use cases and problems encountered it will evolve and the API will change in the coming weeks.

Requirements

This script uses the Prototype javascript framework. You must include version 1.5 in your pages before including this script. This library is known to work in recent versions of all major browsers (IE 6/7,FireFox,Safari,Opera).

In Action

with(Event.Behavior){
    set_style(styles).on(paragraphs).when(selects).change();
}
Size
Color
Weight
Font

By changing any of the select boxes above, the styles on these paragraphs will change.

The code above is real, and if you read on, you'll learn why it works.

Starting Simple

with(Event.Behavior){
    show('state_field').when('country').is('United States');
    show('province_field').when('country').is('Canada');
}
Country
State
Province

Basic Sentance Construction

The basic rule is: one verb construct, followed by when(), followed by at least one event or condition. If no events are specified, changes() is implicit.

VerbsNounsEventsConditions
show(element)when(element)changes()is(value)
hide(element) loses_focus()is_not(value)
add_class_name(class_name).on(element) gains_focus()contains(value)
remove_class_name(class_name).on(element) is_clicked() 
set_class_name(class_name).on(element) is_double_clicked() 
set_style(styles).on(element) key_pressed() 
remove(element)   
update(element)._with(content)   
replace(element)._with(content)   
call(function)   

You can use any of the constructs in any of the columns above to construct a sentance. Below is a simpler version of the font picker.

with(Event.Behavior){
    add_class_name('black').to('paragraph').when('color_select').is('black');
    add_class_name('red').to('paragraph').when('color_select').is('red');
    add_class_name('green').to('paragraph').when('color_select').is('green');
    add_class_name('blue').to('paragraph').when('color_select').is('blue');
    add_class_name('bold').to('paragraph').when('weight_select').is('bold');
    add_class_name('normal').to('paragraph').when('weight_select').is('normal');
}
Color
Weight

When you change one of the selects above, the styles of this paragraph will change.

Construcing Sentances With Functions

The above example describes what is actually happening very well, but it won't scale as we add selects and paragraphs. To accommodate this, virtually very word can accept a function instead of a scalar argument. The table below shows the return type from any functions you define.

VerbsNounsEventsConditions
show(array)when(array)changes()is(boolean)
hide(array) loses_focus()is_not(boolean)
add_class_name(string).on(array) gains_focus()contains(boolean)
remove_class_name(string).on(array) is_clicked() 
set_class_name(string).on(array) is_double_clicked() 
set_style(hash).on(array) key_pressed() 
remove(array)   
update(array)._with(string)   
replace(array)._with(string)   
call(null)   

Note that call() always expects a function, and will call it. The function you pass in doesn't need to return anything.

To demonstrate the construction of sentances with functions, the full code behind the example at the top of the page was:

function selects(){
    return $$('#font_picker select');
}
function paragraphs(){
    return $$('#font_picker p');
}
//this builds a hash of styles that we can pass to Element.setStyle()
function styles(){
    return selects().inject({},function(styles,select){
        styles[select.getAttribute('name')] = select.options[select.options.selectedIndex].value;
        return styles;
    });
}
with(Event.Behavior){
    set_style(styles).on(paragraphs).when(selects).change();
}

and(), or() - Multiple Events & Conditions

with(Event.Behavior){
    show('postal_code_field').when('country_select').is('United States').or('Canada');
}
Country
Postal Code

You can join together multiple events and conditions using and(), or(). Verbs cannot be chained together, use call() for complex functionality. When joining together conditions and(), or() also act as the last condition you specified. The following are just some ideas:

  • call(function).when(element).is('a').or().is('b')
  • call(function).when(element).is('a').or('b').and().is_not('c')
  • call(function).when(element).changes().and().is('red')
  • remove_class_name(class_name).from(element).when(other_element).is_clicked().or().is_double_clicked()

Method Aliases

To help make things look a little more natural, several methods are aliased. Public methods that contain multiple words exist in both camelCased and under_score form.

  • includecontains
  • includescontains
  • areis
  • are_notis_not
  • changechanges

A few methods require two words to construct the verb portion of the sentence, "set_class_name(name).on(element)" for example. The following are all interchangeable:

  • to
  • from
  • on
  • of
  • _with

So add_class_name(class_name).to(element) and remove_class_name(class_name).from(element) both work.

Connecting Custom Objects & Classes - Add Your Own Verbs

The example below uses Control.Tabs, and Object.Event. Below a tabs instance is linked up to a select box, and visa versa. Although the example given is fairly verbose and complex, in practical usage, much of the code is highly reusable.Note that this example contains legacy Object.Event code, see the Object.Event project page for updated examples.

Tab Select

I am tab one. I belong to group one.

I am tab two. I belong to group one.

I am tab three. I belong to group one.

//define your application specific functions
function active_tab(){
    return $('tab_select').options[$('tab_select').options.selectedIndex].value;
}
function set_tab_select(){
    $A($('tab_select')).each(function(option,i){
        if(option.value == tabs.activeContainer.id)
            $('tab_select').options.selectedIndex = i;
    });
}
//Event.Behavior exepects an object to respond to getValue() if it is not a DOM element
Object.extend(Control.Tabs.prototype,{
    getValue: function(){
        return this.activeContainer.id;
    }
});
//Event.Behavior also expects it to respond to observe(), Object.Event takes care of this
Object.extend(Control.Tabs.prototype,Object.Event);
//then have each tabs instance fire a 'change' event, just like any form field
Control.Tabs.addResponder({
    afterChange: function(tabs){
        tabs.fireEvent('change');
    }
});
//add a verb "setActiveTab" which will also become available as set_active_tab
Event.Behavior.addVerbs({
    setActiveTab: function(tabs,tab){
        return tabs.setActiveTab((typeof(tab) == 'function' ? tab() : tab));
    }
});
//create the tabs
var tabs = new Control.Tabs('tabs');
//and attach the behaviors
with(Event.Behavior){
    set_active_tab(active_tab).on(tabs).when('tab_select').changes();
    call(set_tab_select).when(tabs).change();
}

Adding Custom Events

Event.Behavior.addEvents({
    isReset: 'reset' //available as is_reset()
});