Coming from jquery (be gentle)

6 views
Skip to first unread message

Kris Northfield

unread,
Oct 3, 2008, 5:11:38 AM10/3/08
to Prototype & script.aculo.us
Hi all,
Trying to get used to prtotype, I've written this code in jquery but
need a prototype equivalent. I've trawled the web for ages and read
pretty much a whole book (the Manning one with the turk on the front)
but I'm still none the wiser. Any help would be much appreciated. I've
got this html:

<ul class="bopCategories">
<li>
<h3 class="expanded">Heading One</h3>
<div class="bopCategoryDetails">
Lorem ipsum dolor sit amet.
</div>
</li>
<li>
<h3 class="expanded">Heading Two</h3>
<div class="bopCategoryDetails">
Lorem ipsum dolor sit amet.
</div>
</li>
</ul>

which I then toggle the showing/hiding of the <div>s when the <h3>s
are clicked with this jquery code:

$("ul.bopCategories li h3").click(function(){

$(this).siblings("div.bopCategoryDetails").toggle("fast");
$(this).toggleClass("expanded");

});

How on earth do I do this with Prototype? So far I've only managed to
add an onclick event to every <h3> and pass the id of the specific
<div>. Like so:

<h3 onclick="$('divvy2').toggle();">Event</h3>
<div id="divvy2">
Test test test test test test test test ets test test test test test
</div>

Obviously this is going to get cumbersome when I have 20 or so list
items.
Thanks.

T.J. Crowder

unread,
Oct 3, 2008, 5:34:22 AM10/3/08
to Prototype & script.aculo.us
Hello Kris, and welcome!

When I first started with Prototype, I sat down and read through the
API docs from front to back. It took about an hour (and I'm a slow
reader), and was hugely useful -- so useful, in fact, that when I
started reading this group (well, its predecessor) to gain further
knowledge, I was actually able to answer some of people's questions.

So as a first step, I would recommend doing that. It'll literally
take an hour for the first read-through, and I think you'll find that
you'll learn a huge amount from it.

I don't know jQuery at all, so I can't translate your jQuery to
Prototype reliably, but it would probably look something like this:

function toggleContent(evt)
{
var elm;
elm = evt.element();
elm.next('div.bopCategoryDetails').toggle();
elm.toggleClassName('expanded');
}
$$("ul.bopCategories li h3").each(function (elm) {
elm.observe('click', toggleContent);
});

E.g.:

1. Use $$ to select elements by CSS rule
http://prototypejs.org/api/utility/dollar-dollar
2. Use Enumerable.each to loop through the resulting array
http://prototypejs.org/api/enumerable/each
3. Hook the click event on an element with Event.observe
http://prototypejs.org/api/event/observe
4. Find a following sibling according to a CSS rule via Element.next
http://prototypejs.org/api/element/next
5. Toggle visibility with Element.toggle
http://prototypejs.org/api/element/toggle
6. Toggle classname with Element.toggleClassName
http://prototypejs.org/api/element/toggleClassName

Although this could be done with nested closures instead of named
function, I think a named function is both clearer and more efficient.

HTH,
--
T.J. Crowder
tj / crowder software / com

On Oct 3, 10:11 am, Kris Northfield <kris.northfi...@gmail.com>
wrote:

T.J. Crowder

unread,
Oct 3, 2008, 5:37:47 AM10/3/08
to Prototype & script.aculo.us
Actually, let me revise that:

function toggleContent(evt)
{
var elm;
elm = evt.element();
elm.next('div.bopCategoryDetails').toggle();
elm.toggleClassName('expanded');
}
function initPage()
{
$$("ul.bopCategories li h3").each(function (elm) {
elm.observe('click', toggleContent);
});
}
Event.observe(document, 'dom:loaded', initPage);

The "dom:loaded" event is fired when the DOM is ready (before
window.onload, usually).
--
T.J. Crowder
tj / crowder software / com

On Oct 3, 10:34 am, "T.J. Crowder" <t...@crowdersoftware.com> wrote:
> Hello Kris, and welcome!
>
> When I first started with Prototype, I sat down and read through the
> API docs from front to back.  It took about an hour (and I'm a slow
> reader), and was hugely useful -- so useful, in fact, that when I
> started reading this group (well, its predecessor) to gain further
> knowledge, I was actually able to answer some of people's questions.
>
> So as a first step, I would recommend doing that.  It'll literally
> take an hour for the first read-through, and I think you'll find that
> you'll learn a huge amount from it.
>
> I don't know jQuery at all, so I can't translate your jQuery to
> Prototype reliably, but it would probably look something like this:
>
>     function toggleContent(evt)
>     {
>         var elm;
>         elm = evt.element();
>         elm.next('div.bopCategoryDetails').toggle();
>         elm.toggleClassName('expanded');
>     }
>     $$("ul.bopCategories li h3").each(function (elm) {
>         elm.observe('click', toggleContent);
>     });
>
> E.g.:
>
> 1. Use $$ to select elements by CSS rulehttp://prototypejs.org/api/utility/dollar-dollar
> 2. Use Enumerable.each to loop through the resulting arrayhttp://prototypejs.org/api/enumerable/each
> 3. Hook the click event on an element with Event.observehttp://prototypejs.org/api/event/observe
> 4. Find a following sibling according to a CSS rule via Element.nexthttp://prototypejs.org/api/element/next
> 5. Toggle visibility with Element.togglehttp://prototypejs.org/api/element/toggle
> 6. Toggle classname with Element.toggleClassNamehttp://prototypejs.org/api/element/toggleClassName

bluezehn

unread,
Oct 3, 2008, 5:38:59 AM10/3/08
to Prototype & script.aculo.us
So get that javascript out of the html and use observers.

So the code you have so far is much better in prototype as this:

<h3>Event</h3>
<div id="divvy2">
Test test test test test test test test ets test test test
test test
</div>

<!-- IN JAVASCRIPT -->
document.observe('dom:loaded', function()
{
$$('h3').each(function(h3) {
h3.observe('click', function() {
this.down('div').toggle();
}.bindAsEventListener(h3));
});
});

So I've tried to use a really compact bit of code there not
necessarily to do exactly what you want, but to demonstrate loads of
cool prototype-ish features.

First, 'dom:loaded' is a custom event fired by prototype when the dom
has loaded BUT not necessarily all the images, flash files or whatever
else you may have on the page. Clearly this is better than onLoad for
the body or window. You can see that this event is observed by calling
the observe method on an object and specifying a function to run when
the event fires. Here the function is defined inline.

Moving down the code, $$ returns an array of everything that matches
that css definition. So in this case, all 'h3' tags in the dom. The
each method can be applied to all enumerables in prototype and is kind
of like a foreach language structure. So it's saying apply this
function to each h3 element.

You'll probably from jquery be familiar with the .down method, it's
kind of self explanatory, but then you'll notice as well the
bindAsEventListener function at the end there. What that effectively
does is make sure that the variable "this" inside the preceding
function refers to the parameter given in bindAsEventListener.

Any more qs let us know.


On Oct 3, 10:11 am, Kris Northfield <kris.northfi...@gmail.com>
wrote:

bluezehn

unread,
Oct 3, 2008, 5:40:04 AM10/3/08
to Prototype & script.aculo.us
Between me and T.J's post you should be able to get something decent
working now, plenty of code for you!

Kris Northfield

unread,
Oct 3, 2008, 6:00:59 AM10/3/08
to Prototype & script.aculo.us
Hi,
Brilliant. Both answered a lot of questions and expanded my prototype
knowledge ten fold.
Thanks very much.
Kris.

Jarkko Laine

unread,
Oct 3, 2008, 6:55:44 AM10/3/08
to prototype-s...@googlegroups.com
I'll throw in a Low Pro [1] version as well:

Event.addBehavior({
'ul.bopCategories .bopCategoryDetails' : function() {
this.hide();
},
'ul.bopCategories li h3:click' : function(e) {
elm = e.element();


elm.next('div.bopCategoryDetails').toggle();
elm.toggleClassName('expanded');
}

});

The first function will pre-hide the details. Remove it if it's not
something you want to happen.

//jarkko

[1] http://jlaine.net/2007/8/3/from-rails-ajax-helpers-to-low-pro-part-i

On 3.10.2008, at 12.37, T.J. Crowder wrote:

>
> Actually, let me revise that:
>
> function toggleContent(evt)
> {
> var elm;
> elm = evt.element();
> elm.next('div.bopCategoryDetails').toggle();
> elm.toggleClassName('expanded');
> }
> function initPage()
> {
> $$("ul.bopCategories li h3").each(function (elm) {
> elm.observe('click', toggleContent);
> });
> }
> Event.observe(document, 'dom:loaded', initPage);
>
> The "dom:loaded" event is fired when the DOM is ready (before
> window.onload, usually).
> --
> T.J. Crowder
> tj / crowder software / com

--
Jarkko Laine
http://jlaine.net
http://dotherightthing.com
http://www.railsecommerce.com
http://odesign.fi


kangax

unread,
Oct 3, 2008, 8:39:18 AM10/3/08
to Prototype & script.aculo.us
On Oct 3, 5:38 am, bluezehn <middlesh...@googlemail.com> wrote:
> So get that javascript out of the html and use observers.
>
> So the code you have so far is much better in prototype as this:
>
> <h3>Event</h3>
>         <div id="divvy2">
>         Test test test test test test test test ets test test test
> test test
>         </div>
>
> <!-- IN JAVASCRIPT -->
> document.observe('dom:loaded', function()
> {
>   $$('h3').each(function(h3) {
>     h3.observe('click', function() {
>       this.down('div').toggle();
>     }.bindAsEventListener(h3));

This is a common misunderstanding of `bindAsEventListener` : ) Plain
`bind` is actually sufficient unless you need to curry (prefill with
arguments) an event handler. Also, `h3` (which `bindAsEventListener`
uses) seems to be `undefined` here. Finally, invoking `down` without
arguments (i.e. when you only need to step one level down) is usually
faster than using an expression:

$$('h3').invoke('observe', 'click', function() {
this.down().toggle();
});

>   });
>
> });
>
> So I've tried to use a really compact bit of code there not
> necessarily to do exactly what you want, but to demonstrate loads of
> cool prototype-ish features.
>
> First, 'dom:loaded' is a custom event fired by prototype when the dom
> has loaded BUT not necessarily all the images, flash files or whatever
> else you may have on the page. Clearly this is better than onLoad for
> the body or window. You can see that this event is observed by calling
> the observe method on an object and specifying a function to run when
> the event fires. Here the function is defined inline.
>
> Moving down the code, $$ returns an array of everything that matches
> that css definition. So in this case, all 'h3' tags in the dom. The
> each method can be applied to all enumerables in prototype and is kind
> of like a foreach language structure. So it's saying apply this
> function to each h3 element.
>
> You'll probably from jquery be familiar with the .down method, it's
> kind of self explanatory, but then you'll notice as well the
> bindAsEventListener function at the end there. What that effectively
> does is make sure that the variable "this" inside the preceding
> function refers to the parameter given in bindAsEventListener.
>
> Any more qs let us know.

--
kangax

T.J. Crowder

unread,
Oct 3, 2008, 8:53:35 AM10/3/08
to Prototype & script.aculo.us
> This is a common misunderstanding of `bindAsEventListener`

Indeed, it's pretty rare to need to use bindAsEventListener.

> Finally, invoking `down` without
> arguments (i.e. when you only need to step one level down) is usually
> faster than using an expression:

It's not "down" anyway, is it? The div is a sibling of the h3 in the
OP's markup...

But I'm sure the OP's getting the idea. :-)
--
T.J. Crowder
tj / crowder software / com

Kris Northfield

unread,
Nov 10, 2008, 9:00:04 AM11/10/08
to Prototype & script.aculo.us
Yes, I get the idea. FYI this is what my final code is:

$$("ul.bopCategories li h3").each(function (elm) {
elm.observe('click', function(evt){
evt.element().next('div.bopCategoryDetails').toggle();
evt.element().toggleClassName('expanded');
});
});

Thanks everyone.

Tobie Langel

unread,
Nov 10, 2008, 9:16:19 AM11/10/08
to Prototype & script.aculo.us
You should be using event delegation like so:

document.observe('click', function(event) {
var element = event.findElement('ul.bopCategories li h3');
if (!element) return;
element.next('div.bopCategoryDetails').toggle();
element.toggleClassName('expanded');
});

You don't need to wait for the DOM to be loaded to set it up and it's
much more efficient (you're using a unique handler for the whole tree
instead of one per node)..

Best,

Tobie


On Nov 10, 3:00 pm, Kris Northfield <kris.northfi...@gmail.com>
wrote:

bluezehn

unread,
Nov 10, 2008, 12:10:37 PM11/10/08
to Prototype & script.aculo.us
Wow this thread should be summarized somewhere as an "intro to
prototype in 5 minutes" page!
Reply all
Reply to author
Forward
0 new messages