Afhankelijke listboxes met PHP, MySQLi en Prototype
Je kent ze wel, een tweetal listboxes waarvan de 2e listbox automagisch nieuwe waarden krijgt wanneer je een nieuwe waarde selecteert in de eerste listbox. Vandaag op Scriptorama een korte tutorial over hoe je deze zelf kunt maken met behulp de Protoype javascript library
Let op: deze voorbeelden zijn bedoeld voor PHP 5! Om alles kort en bondig te houden wordt er niet zoveel gecontroleerd op fouten. Dit houdt natuurlijk niet in dat jij dat ook niet moet doen!
Voorbereidingen
Maak een allereerst een simpele directory structuur:
-
library/
-
templates/
-
javascripts/
Download nu de Prototype Javascript library en plaats het bestand prototype.js in de javascripts directory.
Gegevens verzamelen
Voordat we de listboxes kunnen maken zullen we eerst even wat testdata moeten regelen. Maak een database in MySQL met de volgende 2 tabellen en vul deze met wat data:
-
CREATE TABLE categorie (
-
cat_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
-
naam VARCHAR(100) NOT NULL
-
);
-
-
CREATE TABLE onderwerp (
-
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
-
cat_id INTEGER UNSIGNED NOT NULL,
-
naam VARCHAR(100)
-
);
-
-
INSERT INTO categorie (naam) VALUES ('Browsers');
-
INSERT INTO categorie (naam) VALUES ('Script talen');
-
INSERT INTO categorie (naam) VALUES ('Database servers');
-
-
INSERT INTO onderwerp(cat_id, naam) VALUES (1, 'Mozilla FireFox');
-
INSERT INTO onderwerp(cat_id, naam) VALUES (1, 'Opera');
-
INSERT INTO onderwerp(cat_id, naam) VALUES (1, 'Safari');
-
-
INSERT INTO onderwerp(cat_id, naam) VALUES (2, 'PHP');
-
INSERT INTO onderwerp(cat_id, naam) VALUES (2, 'Python');
-
INSERT INTO onderwerp(cat_id, naam) VALUES (2, 'Ruby');
-
-
INSERT INTO onderwerp(cat_id, naam) VALUES (3, 'PostgreSQL');
-
INSERT INTO onderwerp(cat_id, naam) VALUES (3, 'MySQL');
-
INSERT INTO onderwerp(cat_id, naam) VALUES (3, 'Firebird');
Database toegang regelen
We zullen de MySQLi extensie gebruiken om de data uit de database te halen. Als je meer wilt lezen over MySQLi kun je terecht bij de volgende Scriptorama artikelen:
- MySQLi I - Een introductie tot MySQLi
- MySQLi II - Prepared Statements
- MySQLi III - Unbuffered queries
We maken een simpele klasse waarmee we de categorie en onderwerp gegevens kunnen ophalen:
library/categorie.class.php:
-
class CategorieGateway
-
{
-
private $_db = null;
-
-
function CategorieGateway($db)
-
{
-
$this->_db = $db;
-
}
-
-
public function haalCategorien()
-
{
-
$res = $this->_db->query (
-
"SELECT id,naam FROM categorie"
-
);
-
-
-
while ( $row = $res->fetch_assoc() )
-
$resArray[$row['id']] = $row['naam'];
-
-
$res->free();
-
-
return $resArray;
-
}
-
-
public function haalOnderwerp($catId)
-
{
-
$catId = (int) $catId;
-
$res = $this->_db->query (
-
"SELECT id,naam FROM onderwerp WHERE cat_id = $catId"
-
);
-
-
while ( $row = $res->fetch_assoc() )
-
$resArray[$row['id']] = $row['naam'];
-
-
$res->free();
-
-
return $resArray;
-
}
-
}
Doordat we zeker weten dat we de onderdeel en categorie gegevens op 1 manier gaan gebruiken retourneren we de gegevens als een associatieve array, geindexeerd op database ID.
Deze associatieve arrays zijn handig als om snel een listbox mee te genereren maar dan hebben we nog wel een functie nodig die dat voor ons kan regelen:
library/ToListbox.php:
-
function toListbox($name, $data)
-
{
-
$res = '<select name="' . $name . '" id="'. $name . '">';
-
-
foreach ($data as $id => $naam)
-
{
-
$res .= '<option value="' . $id . '">';
-
$res .= $naam;
-
$res .= '</option>';
-
}
-
$res .= "</select>";
-
-
return $res;
-
}
Deze functie lijkt me wel duidelijk. Hij zorgt er nog wel even voor dat de sleutels van de associatieve array gesorteerd zijn. Dat is handig voor als we met de hand nog iets toe willen voegen aan de data array. Dit zullen we later nog zien.
Deze twee bestanden en natuurlijk de connectie naar de database maken we vervolgens in een derde bestand:
init.php:
De pagina met listboxes
De pagina haalt alleen de categorie gegevens op en maakt deze beschikbaar aan de template via de $template array. Dit is de enige variabele die de template zal proberen uit te lezen. Je ziet ook dat we een extra element toevoegen aan de data array. Dit is een kleine usability toevoeging en had dus geen plek in de database. Doordat we ksort() gebruiken in de toListbox() functie komt deze nieuwe waarde bovenaan te staan.
index.php
-
require './init.php';
-
-
$catGateway = new CategorieGateway($db);
-
$categorie = $catGateway->haalCategorien();
-
-
$categorie[0] = "Selecteer categorie";
-
-
'lbxCategorie' => ToListbox('categorie', $categorie)
-
);
-
-
require './templates/index.tpl.php';
De template is simpel. We plaatsen de 2 listboxes allebei in een aparte div zodat we deze straks makkelijk kunnen manipuleren:
templates/index.tpl.php
-
<html>
-
<head>
-
<title>Scriptorama.nl - Afhankelijke listboxes</title>
-
<script src="javascripts/prototype.js"></script>
-
<script src="javascripts/listbox.js"></script>
-
</head>
-
<body>
-
<div id="lbx1">
-
</div>
-
-
<div id="lbx2">
-
<select id="onderwerp" name="onderwerp">
-
<option>Selecteer een onderwerp</option>
-
</select>
-
</div>
-
</body>
-
</html>
Javascript, please!
Vervolgens zullen we een stukje javascript moeten schrijven dat er voor zorgt dat onze listbox geupdate wordt. We maken daarvoor een nieuw javascript bestand waarin we wat handigheden van Prototype zullen toepassen:
javascripts/listbox.js
-
function verversListbox(resp)
-
{
-
if (resp.responseText != "-1")
-
Element.update($('lbx2'), resp.responseText);
-
}
-
-
function verversListboxEvent()
-
{
-
var catId = $F('categorie');
-
if (catId == 0) return;
-
-
new Ajax.Request(
-
"onderwerp.php",
-
{
-
method: 'get',
-
parameters: "id=" + catId,
-
onComplete: verversListbox
-
}
-
);
-
}
-
-
function attachHandlers()
-
{
-
Event.observe (
-
$('categorie'),
-
'change',
-
verversListboxEvent,
-
false
-
);
-
}
-
-
Event.observe(
-
window,
-
'load',
-
attachHandlers,
-
false
-
);
Dit script definieert een tweetal functies en hangt de eventhandler aan de listbox. Op het moment dat er een andere waarde wordt geselecteerd in de listbox wordt de javascript functie verversListboxEvent() aangeroepen. Deze doet vervolgens een XMLHttpRequest naar onderwerp.php en geeft het geselecteerde categorie ID mee.
Wanneer dit XMLHttpRequest afgerond is wordt de functie verversListbox() aangeroepen welke ons tweede divje een nieuwe inhoud geeft: namelijk dat wat terug kwam van onderwerp.php! Tenzij de waarde -1 is, in welk geval er iets mis is gegaan in onderwerp.php.
Dit alles realiseren we met enkele features van Prototype:
- De functies $F() en $() zijn shorthand notaties voor alledaagse dingen in Javascript: $F('id') retourneert de waarde voor het form control met id 'id' en $('id') retourneert het HTML elementen met het id 'id'.
- Event.observe() laat je op simpele wijze een eventhandler aan een control of element hangen.
- Ajax.Request() voert een XMLHttpRequest uit naar het opgegeven bestand en met de opgegeven parameters
- Element.update() vervangt de HTML (innerHTML) van het opgeven HTML element met de nieuwe opgegeven HTML
Het enige wat nu nog mist is een script dat voor het opgegeven categorie ID de juiste lijst van onderwerpen ophaalt.
onderwerp.php:
Conclusie
Et voila, een simpele implementatie van afhankelijke listboxes. Er zijn nog verschillende andere methodes waarop dit gerealiseerd had kunnen worden. In plaats van de hele content van een div te vervangen hadden we ook JSON kunnen gebruiken of juist XML maar het is maar net wat je nodig hebt. In ons geval was dit de snelste en simpelste manier.
Prototype maakt het leven van de webdeveloper die met javascript werkt aanzienlijk makkelijker, bekijk ook de onofficiele handleiding eens!
Volg Scriptorama via RSS!
Reageer ook!
Waarom de inhoud van de div vervangen?
Waarom uberhaupt een div rond de selects? Veel mooier is natuurlijk om de options array te legen en te vullen met de nieuwe options via DOM.
Door Vincent
op 03.29.06 @ 2:20 pm | Permalink
Omdat ik juist even snel (okay, okay, quick and enigzins dirty ;-) ) wilde laten zien wat je met prototype kunt doen. Het is inderdaad niet de mooiste manier.
Ik zal binnenkort nog een andere manier belichten waarbij JSON als data "protocol" gebruikt wordt. Deze methode zal dan inderdaad enkel de options van een listbox wijzigen.
Door Mathieu
op 03.29.06 @ 2:41 pm | Permalink
Mathieu: heb je deze code ook getest onder Internet Explorer? Ik ben de afgelopen tijd ook bezig geweest met een soortgelijk iets, maar het lijkt erop dat 'change' op een selectbox niet werkt onder IE.. Onder FF uiteraard wel.
Door berry__
op 05.12.06 @ 10:53 am | Permalink
Berry: Ik geloof dat ik dit artikel wel getest heb onder Windows/MSIE. Ik maak ook gebruik van de prototype library die juist het verschil tussen de 2 browsers moet abstraheren.
Hoe dan ook, ik heb nog even een testje gemaakt en getest met MSIE 6 en dat werkt goed:
Let op dat je als event 'change' gebruikt en niet 'onchange' ofzoiets.
Mocht het nu nog niet werken, post dan even je code in het forum, dan kan ik er verder naar kijken :)
Door Mathieu Kooiman
op 05.12.06 @ 12:18 pm | Permalink
Hallo,
ik ben net begonnen met Prototype en heb hierbij een paar vraagjes.
Waarom gebruik je niet
Dat is toch veel handiger, of ben ik mis?
Tweede punt, de Ajax.Request roept onderwerp.php aan. Kan hier ook een php functie aangeroepen worden ipv een php file. Zo blijft het wat overzichtelijk ipv al die kleine bestandjes.
Groeten, Stijn
Door Stijn
op 07.29.07 @ 1:34 pm | Permalink
Ik mis eigenlijk een sample wat uitlegt wat het nu eigenlijk is. Ben zelf meer van jQuery, dus kijk even verder. Duidelijk wel deze manier van uitleggen!
Door Balloon
op 01.12.08 @ 9:22 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>