Toepassing van Prototype: een foto album
Sinds kort is Prototype 1.5 RC2 beschikbaar. Een nieuwe feature in deze versie is de mogelijkheid om door HTML nodes te bladeren via up, down, next en previous. Om de mogelijkheden van deze functies te laten zien, zal ik met behulp van Prototype een simpel foto album maken.
Uiteraard doen we dit helemaal xHTML valid en zorgen we voor Unobtrusive JavaScript. Gebruikers zonder JavaScript moeten dus zonder problemen de foto's kunnen bekijken.
De server-side code voor dit foto album zal ik buiten beschouwing laten. Ik ga er vanuit dat je in je html het volgende hebt staan:
Zonder CSS en JavaScript zal dit dus een lijst met foto's en de bijbehorende omschrijvingen opleveren. Prima bruikbaar als de gebruiker geen CSS of JavaScript heeft, maar we willen liever maar 1 foto op het scherm weergeven en de mogelijkheid bieden naar de volgende en vorige foto te gaan. Dit gaan we realiseren door een behaviour te koppelen aan de lijst, door middel van JavaScript.
Het koppelen van een behaviour kan via Prototype simpel via de $$(css-selector) functie. Deze functie zal een lijst met elementen opleveren die voldoen aan de meegegeven CSS selector. Deze manier van werken is dus vergelijkbaar met CSS, alleen definieer je in dit geval geen stijl voor de elementen, maar een bepaald gedrag.
De onderstaande code selecteert alle lijst elementen (li tags) in de tag met de klasse 'foto-album'.
-
$$('.foto-album li').each(function(element){
-
// Plaats hier de behaviour voor deze foto
-
});
Het gedrag dat we willen toekennen aan elke foto is het volgende:
- Een foto is verborgen, tenzij het de huidige foto is
- In het begin is de eerste foto in de lijst de huidige foto
- Elke foto bevat 2 links naar de respectievelijk volgende en voorgaande foto
De JavaScript voor dit gedrag ziet er als volgt uit:
-
$$('#foto-album li').each(function(element){
-
// Verberg de foto
-
element.hide();
-
// Haal het <em> element op
-
emChild = element.immediateDescendants().last();
-
// Voeg links en rechts van de omschrijving een link toe
-
emChild.update('<a href="#" class="prev-photo">«</a>'+emChild.innerHTML+'<a href="#" class="next-photo">»</a>');
-
});
-
-
-
// De eerste foto is de huidige foto
-
currentPhoto = $('foto-album').immediateDescendants().first();
-
currentPhoto.show();
Merk op dat ik gebruik maak van innerHTML voor het toevoegen van de links en niet van de DOM methoden. Benchmarks hebben uitgewezen dat de innerHTML methode in alle browsers het snelste is. Daarnaast scheelt het ook veel code.
Het tweede punt wat enige uitleg nodig heeft, is het gebruik van immediateDescendants().first() in plaats van firstChild. Met firstChild kan je namelijk ook het eerste kind van een element ophalen. In Internet Explorer worden echter newlines en spaties ook als een kind gezien. firstChild in Internet Explorer zal dus een #text element opleveren en niet het gewenste LI element. De immediateDescendants() methode levert alleen de echte html nodes op die kinderen zijn van het huidige element.
Het resultaat van de code is dat we één foto op het scherm hebben met een omschrijving en hyperlinks naar de volgende en vorige foto. De volgende stap is het toekennen van een behaviour voor deze links. Het gedrag voor de 'volgende' link ziet er als volgt uit:
- Ga naar de volgende foto
- Ga naar de eerste foto, als de huidige foto de laatste foto is
Dit gedrag wil je aan een event op het link element hangen, in dit geval aan het 'onclick' event. Hiervoor maak je gebruik van de observe methode.
-
$$('#foto-album a.next-photo').each(function(element){
-
// Registreer het gedrag voor de
-
// event 'onclick' op het link element
-
element.observe('click', function(elementClick){
-
// Verberg de huidige foto
-
currentPhoto.hide();
-
// Als er een volgende foto is
-
if(currentPhoto.next() != undefined){
-
// Selecteer het volgende html element
-
// als deze niet undefined is
-
currentPhoto = currentPhoto.next();
-
} else {
-
// Selecteer het eerste html element van
-
// bovenliggende html node, in dit geval de
-
// eerste foto
-
currentPhoto = currentPhoto.parentNode.immediateDescendants().first();
-
}
-
// Laat de volgende foto zien
-
currentPhoto.show();
-
});
-
});
Hierbij maken we gebruik van de next methode die elk object heeft gekregen van Prototype. De next methode geeft het volgende element in de DOM terug. In het geval van een lijst is dat dus het volgende lijst element. De methode immediateDescendants geeft een lijst met kind elementen terug. Door hier het eerste element van te pakken middels first, wordt de eerste foto in de reeks weergegeven.
De JavaScript code voor de previous link is grotendeels gelijk aan die van de next link.
Om het gedrag toe te kennen aan de html elementen in de pagina, dient de pagina geladen te zijn. Daarom willen we de bovenstaande code pas uitvoeren zodra de pagina geladen is. Dit realiseren we wederom met de Event.observe methode in Prototype. De volledige JavaScript code voor het foto album ziet er als volgt uit:
-
var currentPhoto = null;
-
-
Event.observe(window, 'load', function(){
-
$$('#foto-album li').each(function(element){
-
// Verberg de foto
-
element.hide();
-
// Haal het <em> element op
-
emChild = element.immediateDescendants().last();
-
// Voeg links en rechts van de omschrijving een link toe
-
emChild.update('<a href="#" class="prev-photo">«</a>'+emChild.innerHTML+'<a href="#" class="next-photo">»</a>');
-
});
-
-
// De eerste foto is de huidige foto
-
currentPhoto = $('foto-album').immediateDescendants().first();
-
currentPhoto.show();
-
-
// Selecteer de link
-
$$('#foto-album a.next-photo').each(function(element){
-
// Registreer het gedrag voor de
-
// event 'onclick' op het link element
-
element.observe('click', function(){
-
// Verberg de huidige foto
-
currentPhoto.hide();
-
// Als er een volgende foto is
-
if(currentPhoto.next() != undefined){
-
// Selecteer het volgende html element
-
// als deze niet undefined is
-
currentPhoto = currentPhoto.next();
-
} else {
-
// Selecteer het eerste html element van
-
// bovenliggende html node, in dit geval de
-
// eerste foto
-
currentPhoto = currentPhoto.parentNode.immediateDescendants().first();
-
}
-
// Laat de volgende foto zien
-
currentPhoto.show();
-
});
-
});
-
-
$$('#foto-album a.prev-photo').each(function(element){
-
element.observe('click', function(){
-
currentPhoto.hide();
-
if(currentPhoto.previous() != undefined){
-
currentPhoto = currentPhoto.previous();
-
} else {
-
currentPhoto = currentPhoto.parentNode.immediateDescendants().last();
-
}
-
currentPhoto.show();
-
});
-
});
-
}
Zoals je ziet hebben we op een hele leesbare manier gedrag toegekend aan de pagina. Prototype zorgt voor duidelijke en leesbare code. Het grootste nadeel van Prototype is de documentatie. Nog lang niet alle mogelijkheden zijn goed gedocumenteerd, daardoor is het regelmatig nodig om in de Prototype code te duiken.
De bovenstaande code kan natuurlijk nog uitgebreid worden met extra behaviour zoals AJAX requests om extra foto's op te halen of grafische overgangen tussen de foto's. Met de behaviour opbouw is het zonder veel extra moeite mogelijk de code uit te breiden.
Een voorbeeld van het foto album is te vinden op de project pagina's van mijn website.
Volg Scriptorama via RSS!
Reageer ook!
werkend voorbeeldje? ben te lui om te lezen ;)
Door Joost
op 12.21.06 @ 3:23 pm | Permalink
Voorbeeldje zou leuk zijn ja! Verder interessant artikel.
Ik gebruik prototype een tijdje. Het werkt ideaal.
Door Ruud
op 12.21.06 @ 5:12 pm | Permalink
Heel mooie tutorial, Edwin!
Ik vroeg me alleen even iets af; je hebt ergens in de code dit staan:
currentPhoto = $('#foto-album').immediateDescendants().first();
Moeten dat dan niet ook 2 $ zijn ($$('#foto-album')) omdat je het op de css manier doet?
Door Ivo Jansch
op 12.21.06 @ 8:55 pm | Permalink
Ik heb een link naar een voorbeeld toegevoegd aan het artikel.
Verder is de enkele dollar syntax een functie om een enkel element met het meegegeven ID op te halen. Deze functie is vergelijkbaar met getElementById. Hier is uiteraard geen CSS syntax voor nodig, enkel 'foto-album' zou hier moeten volstaan.
Door Edwin V.
op 12.22.06 @ 12:03 am | Permalink
Leuk script. Wel twee opmerkingen: De onclick hoort false te returnen, of simpelweg niet op een anchor.
De initialisatie duurt te lang. Je ziet nu een rij foto's die pas na laden omgezet wordt in het album.
Door Vincent
op 12.22.06 @ 10:44 am | Permalink
Puik, hoe meer promotie van best practices hoe beter :) Nadeel van window.onload is nu wel dat je pas iets te zien krijgt als alle afbeeldingen geladen zijn. Dan toch misschien maar aparte noscript markup.
Door Michel de Lange
op 12.22.06 @ 11:09 am | Permalink
Goed artikel!
Door Sebastiaan Stok
op 12.22.06 @ 8:01 pm | Permalink
Line and paragraph breaks automatic, e-mail address never displayed, HTML allowed:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>