Baby Steps With Scriptaculous

by David O'Meara

I almost missed the boat with this one, heck Bear and company managed to get a book out before I decided to give it a serious look, but finally I have looked.

Introduction

As a quick introduction, Prototype is a JavaScript library that has become quite popular due to the things others have built on top of it. I like to think of Prototype as an extension to JavaScript and inclusion of various best practices. Through it code becomes less cumbersome and unwieldy, and therefore easier to manage. This doesn't sound like much, but I have developed an aversion to JavaScript over my eight years of casual development and Prototype made me much happier. The only Prototype functionality used directly in the following is the $() function, which is a sort of shorthand for document.getElementById(), the rest of Prototype is used under the covers by Scriptaculous. Two additional resources I liked were the online API book and the article How well do you know prototype.

Finishing the introduction, Scriptaculous or script.aculo.us, as it is commonly written to match the URL, is written as an additional layer on Prototype and "provides you with easy-to-use, cross-browser user interface JavaScript libraries to make your web sites and web applications fly". There are few different areas that are enhanced by Scriptaculous, but we're just looking at the animation effects, and the smallest subset, at that.

The Scriptaculous Demos

The demos provided by Scriptaculous show some useful effects, but I will just be looking at two. To get started you need the libraries, but you can get scriptaculous.js and effects.js in the Scriptaculous download, and it contains prototype.js too. It contains some other files too, but we just need the ones listed.

Time for some code. Create a file called test1.html with the following content, make sure the javascript files are in a folder called javascript in the same location.

<html>
<head>
<script type="application/x-javascript" src="javascript/prototype.js"></script>
<script type="application/x-javascript" src="javascript/scriptaculous.js"></script>
</head>
<body>

<div class="space">
<div class="example" id="demo-effect-blinddown" onclick="new Effect.BlindDown(this)">
<div style="width:200px;">
<span>Click for Effect.BlindDown demo</span>
</div>
</div>

</div>
<div class="space">
<div class="example" id="demo-effect-blindup"
    onclick="new Effect.BlindUp(this);
            window.setTimeout('Effect.Appear(\'demo-effect-blindup\', {duration:.3})',2500);">
<span>Click for Effect.BlindUp demo</span>
</div>
</div>

</body>
</html>

Gives us this:

Click for Effect.BlindDown demo
Click for Effect.BlindUp demo

Clicking on the blind-down demo creates a 'BlindDown' effect on that div, same with the blind-up except that it also has a little event that fires off afterwards to show the div again, otherwise we lose it. The code is pretty simple: in each there is an outer div which can be ignored, and an inner div of interest. The two active div's respond to an onclick event, the onclick calls a Scriptaculous effect and passes a reference for the div to Scriptaculous to provide the effect. Hence: onclick, Scriptaculous, 'this', done.

Extending the Demo

Hopefully the example above is pretty self explainatory. To add our own functionality we will add the BlindUp and BlindDown effects to a single div, allowing us to open and close it. Rather than applying the onclick to that div, we'll add it to a second div so that we always have something to click on, even when our 'blind' div is not available. It will start out something like this:

<div class="header">
    Header
</div>
<div class="bodyOpen">
    This is the body part,
    which will do the
    blind-up and blind-down
    effects
</div>
Header
This is the body part, which will do the blind-up and blind-down effects

Unlike the previous example, where the click on the div caused an action on the div, we are going to make a few changes...

Naming the Head and Body

Element IDs must be unique. This isn't my rule, it's an XHTML rule. Since you're not allowed to reuse an ID after using it once, it makes them handy for identifying and finding specific items on our page. On our page, we are going to assume that whatever ID the header has, the associated body's ID will have 'Body' added to the end. Hence the associated body for header 'itemOne' is 'itemOneBody'.

Tracking the Body State

The Prototype $() function, mentioned earlier, accepts either an Element or the name of an Element ID, so starting from a reference to the header element, the header ID is header.id, the therefore the ID for the associated body is header.id+'Body' and to get a refernce to that Element we call $(header.id+'Body'). Now we want to know if it has the CSS class 'bodyOpen', and again Prototype comes to our aid with hasClassName('bodyOpen'). There is also a function to add a class name addClassName but I'm going to keep the name of the function used to remove a class name secret for now.

Putting these together, we can write a function which takes the reference to a header as its argument, detects the state of the body and then performs a switch on the class state.

function toggle(header)
{
    if($(header.id+'Body').hasClassName('bodyOpen'))
    {
        // open
        $(header.id+'Body').addClassName('bodyClosed');
        $(header.id+'Body').removeClassName('bodyClosed');
    }
    else
    {
        // close
        $(header.id+'Body').addClassName('bodyClosed');
        $(header.id+'Body').removeClassName('bodyOpen');
    }
}

Adding the Effect

As we saw in the Scriptaculous demos, adding an effect is easy.

function toggle(header)
{
    if($(header.id+'Body').hasClassName('bodyClose'))
    {
        // open
        new Effect.BlindDown($(header.id+'Body'));
        $(header.id+'Body').addClassName('bodyOpen');
        $(header.id+'Body').removeClassName('bodyClosed');
    }
    else
    {
        // close
        new Effect.BlindUp($(header.id+'Body'));
        $(header.id+'Body').addClassName('bodyClosed');
        $(header.id+'Body').removeClassName('bodyOpen');
    }
}

Trying it Out

All that remains now is to add out IDs, plug the function in to the div and give it a try:

<div class="header"
onclick="toggle(this);">
Header
</div>
<div class="bodyOpen">
This is the body part,
which will do the
blind-up and blind-down
effects
</div>
Header
This is the body part, which will do the blind-up and blind-down effects

Now To Make Something Useful

So maybe you're not too impressed so far, but if you put a few in a row, add some nicer styles and realistic content, and you have an easy to maintain accordion panel.

Header
script.aculo.us
JavaRanch
David O'Meara
The JavaRanch Volunteers

I did cheat a little here, if you look at the source code. Firstly the body parts start with the 'bodyClosed' rather than 'bodyOpen' class, and they have an initial style that makes them hidden. Also, the name of the header and body don't have to be numbers, they just need to be unique. It makes life easier if they are also meaningful, as long as you copy and paste them correctly.

Where Do We Go From Here?

If you thought that was too easy, that would be my point! Play with styles, play with storing the open/closed position in a cookie so it is retained between visits, look at rollover effects or different styles for open and closed headers. Keep in mind that now we are finished, the page content, CSS and script are about the same size, and the code knows nothing about the page content and page content just follows some easy class name and ID naming rules.