Nach all der Theorie
über WDDX soll nun in diesem Artikel ein praktisches Beispiel folgen.
Dazu wollen wir zwei Selectboxen dynamisch mit Werten aus einem Datenbank-Query
füllen, wobei eine Auswahl in der ersten Selectbox eine Änderung der
2. Selectbox zur Folge hat.
Dieser Technik eröffnet dem aufstrebenden Webentwickler einige interessante
Möglichkeiten für die Bedienung von Webapplikationen. Man kann z.B.
in der ersten Selectbox mehrere Sprachen zur Auswahl stellen und bei Selektion
in der 2. Selectbox ein Auswahlmenü in der entsprechenden Sprache erzeugen,
oder in der ersten Box wählt man in einem Shopsystem die Produktgruppe
und in der 2. Selectbox das Produkt.
In einer normalen
Applikation würde die Selektion eines Elementes in der ersten Selectbox
normalerweise eine Datenbankabfrage mit dem ausgewählten Element als Parameter
nachsich ziehen um mit dem Ergebnis dann die 2. Selectbox zu füllen. Man
könnte dieses Verhalten natürlich auch simulieren indem man nach der
ersten Selektion zu einer 2. Seite weiterschalten und dort die Datenbankabfrage
durchführt. Jeder kann sich sicherlich denken, dass das nicht gerade sehr
schnell und komfortabel ist.
Mit etwas
Javascript, ColdFusion und WDDX kann man dieses dynamische Selectbox-Set viel
userfreundlicher erzeugen. Es reagiert ohne merkliche Verzögerung auf Usereingaben
und läuft auch Offline.
Fangen wir also an. Ziel soll es sein, 2 Selectboxen zu erzeugen, wobei die 1. eine Liste von Autohersteller enthält und die 2. Selectbox eine Auswahl von Autotypen, die vom jeweiligen selektierten Hersteller abhängt. Als Basis unserer Daten ist eine Datenbank mit der Tabelle autos und den Spalten hersteller und typ vorhanden. Im Beispielscript wird folgender Tabelleninhalt benutzt:
hersteller | typ | Fiat | Croma | Fiat | Panda | Fiat | Punto | Fiat | Uno | Opel | Corsa | Opel | Kadett | Opel | Omega | VW | Golf | VW | Passat | VW | Polo | VW | Vento |
Das daraus resultierende Ergebnis sieht dann so aus (das Textinput-Element dient nur zur Anzeige des gewählten Eintrags) und wird in wenigen Minuten jedem in der Funktion klar sein :-):
Die folgenden Schritte sind notwendig:
Schritt 1
Wir führen eine normale Datenbankabfrage durch. Dieser Code wird auf dem
Server von ColdFusion ausgeführt!
<cfquery name="autos" datasource="myDSN">
select hersteller,typ from autos
</cfquery>
Schritt 2
Nun muß das Queryset autos nach Javascript konvertiert werden.
Dazu benutzen wir das schon beschriebene ColdFusion-Tag
CFWDDX (die JS-Script-Tags nicht vergessen, denn es wird Javascripcode
durch CFWDDX erzeugt!):
<SCRIPT LANGUAGE="JavaScript" TYPE="text/javascript">
<cfwddx action="cfml2js" input="#autos#"
toplevelvariable="js_autos">
</SCRIPT>
Das Ergebnis dieser
Zeilen ist ein WddxRecordset-Objekt mit dem Namen js_autos, dessen Elemente
der des ColdFusion-Querysets autos entsprechen.
Die Struktur und Methoden des WddxRecordsets sind in der Javascriptlibrary Wddx.js
definiert, die man mit diesen Zeilen in die Seite einbinden muss:
<script SRC="/cfide/scripts/wddx.js"></script>
Das SRC-Attribut
muss mit dem Pfad zu diesem Script gesetzt werden. Man kann die Datei wddx.js
aber auch in ein beliebiges zur Site gehörendes Verzeichnis kopieren und
den Pfad entsprechend anpassen.
Nach dem Konvertieren sind noch einige Vorbereitungsarbeiten mit Javascript nötig. Zuerst werden die benötigten globalen Javascript-Variablen angelegt. Die wichtigen davon sind:
autoArray=new Array();
box1=null;
box2=null;
Die Variablen box1 und box2 werden später die Referenz auf die beiden Selectboxen aufnehmen. Die Variable autoArray soll die Query-Daten als zweidimensionales Array aufnehmen. Da man in Javascript nicht direkt mehrdimensionale Arrays erzeugen kann gehen wir hier einen Umweg, indem zuerst ein einfaches Array erzeugt wird, dessen Elemente dann wiederum Arrays werden. Das Ziel ist ein zweidimensionales Array, dessen Zeilen jeweils einen Eintrag eines Herstellers inkl. Typen erhalten. In der ersten Spalte stehen dann die Hersteller und in den folgenden Spalten die zugehörigne Typen:
Fiat | Croma | Panda | Punto | Uno |
Opel | Corsa | Kadett | Omega | |
VW | Golf | Passat | Polo | Vento |
Diese etwas komplizierte
Struktur ermöglich aber einen sehrt einfachen und schnellen Zugriff auf
die Typen eines Herstellers, denn man muss nur bei gegebenen Hersteller den
Index der entsprechende Zeile finden und kann dann alle Werte der Zeile bequem
über den ermittelten Index ansprechen.
Fiat befindet sich also auf dem Feld autoArray[0][0], Opel auf autoArray[1][0]
usw. Alle Typen von Opel lassen sich also über autoArray[1][x] auslesen.
Man muss nicht unbedingt dieses Prinzip anwenden um dynamische Selectboxen zu
erzeugen. Es bleibt jeden selbst überlassen, die für seine Zwecke
am besten geeignetste Datenstruktur zu finden.
Das Array js_autos wird gleich beim Laden der Webseite aus dem WddxRecordset mit diesem Code erstellt:
for (var i=0; i<js_autos.getRowCount(); i++) {
if (last == js_autos.hersteller[i]) {
//zeile fuellen
autoArray[rowIdx][colIdx]=js_autos.typ[i];
colIdx++;
}
else {
//neue zeile anlegen
rowIdx++;
autoArray[rowIdx]=new Array();//neues Array fuer typen erzeugen
colIdx=2;
autoArray[rowIdx][0]=js_autos.hersteller[i];//1.spalte=hersteller
autoArray[rowIdx][1]=js_autos.typ[i];
last=js_autos.hersteller[i];
}
}
getRowCount() ist eine Methode (definiert in der Datei wddx.js) des WddxRecordsets, mit dem wir die Anzahl der Recordset-Zeilen ermitteln können (ähnlich Query.Recordcount in CF).
Beim Ansprechen der Spalten eines WddxRecordsets muss man beachten dass Tabellenspalten des Querysets in lowercase umgewandelt werden. Würde also die Originalspalte der Datenbank Hersteller oder herSteller lauten, wäre der Spaltenname imWddxRecordset in beiden Fällen hersteller und nur über js_autos.hersteller[i] ansprechbar. Der Ausdruck js_autos.Hersteller[i] würde einen Fehler auslösen.
Schritt
3
Wir haben jetzt die Daten in Javascript (autoArray[][] )verfügbar.
Nun müssen die Selectboxen gefüllt werden. Dazu erzeugen wir zuerst
die Selectboxen im HTML-Code:
<form NAME="f1">
<select name="hersteller" size="5"
onChange="hersteller_onChange()">
<OPTION VALUE="1">=====(loading)=====</OPTION>
</select>
<select name="typ" size="5" onChange="typ_onChange()">
<OPTION VALUE="1">=====(loading)=====</OPTION>
</select>
<br>
<input type="text" name="auswahl">
</form>
Die OPTION-Elemente
müssen mit einem Wert vorbelegt werden, der für den Browser die Breite
der Selectbox beim Laden der Seite angibt. Nötig ist das allerding nur
für den Netscape-Browser, der Internet Explorer passt die Breite der Selectboxen
dynamisch den darzustellenden Inhalten an.
Jeder Selectbox wird eine Javascript-Funktion als Eventhandler zugeordnet, die
aufgerufen wird, wenn sich der Selektionszustand ändert. Wichtig ist dabei
der Eventhandler der ersten Selectbox, denn in dieser Funktion müssen wir
in Abhängigkeit der Auswahl die 2. Box füllen.
Die Boxen müssen beim Aufruf der Seite natürlich zuerst einmal mit
Daten gefüllt werden. Wir setzen dazu in das Body-Tag der Seite einen onLoad-Eventhandler
(wird aufgerufen, wenn die Seite vollständig geladen wurde).
<body onLoad="init()">
Der zur init()-Funktion gehörende Code sieht in diesem Beispiel so aus:
function init(){
box1=document.f1.hersteller; //referenzen speichern
box2=document.f1.typ;
box1.options.length=0; //selectboxen loeschen
box2.options.length=0;
for (i=0; i<autoArray.length; i++) {
//neuen eintrag in selectbox anlegen
box1.options[box1.options.length]=new Option(autoArray[i][0],autoArray[i][0]);
}
box1.selectedIndex=0; //1. eintrag selecktieren
hersteller_onChange();
}
Zuerst werden die
Referenzen der beiden Selectboxen zur Einsparung von Schreibarbeit in den globalen
Variablen box1 und box2 gespeichert.
Dann wird die Selectbox gelöscht, indem man die Länge des Option-Objekt-Arrays
auf Null setzt.
Um den Javascriptcode
besser verstehen zu können, sollte man wissen, dass die Selectbox- und
Option-Objekte sich in einigen Punkten von anderen Formularelementen unterscheiden.
Das Option-Objekt ist z.B. selbst kein Formularelement, sondern ein Objekt,
das Teil des Select-Objektes (Selectbox) ist. Das Select-Objekt ist das einzige
Formularelement, das andere Objekte enthält. Diese Objekte werden in dem
Array options[] gespeichert und lassen sich manipulieren wie ganz normale
Array-Elemente.
Das Besondere an den Option-Objekten ist, dass wir sie zur Laufzeit (ab NS3.0)
durch den Konstruktor Option() dynamisch erzeugen und dem Array options[]
zuweisen können (dadurch erscheinen sie in der Selectbox). Durch Setzen
der Eigenschaft length des Arrays options[] löschen wir alle
Eingträge. Aber auch Anhängen von neuen Eingrägen ist problemlos
über das options[]-Array möglich.
Das versetzt uns erst in die Lage, die Werte der 2. Selectbox dynamisch auszutauschen.
Zur Initzialisierung loopen wir durch autoArray und lesen die erste Spalte
(i=0) und damit die Werte der Hersteller aus. Wir erzeugen dann jeweils ein
Option-Objekt.
Am Schluss selektieren wir den ersten Eintrag der 1. Selectbox, indem wir deren
Eigenschaft selectedIndex auf Null setzen. Soll kein Eintrag vorselektiert
werden, kann man diese Zeile auch weglassen bzw. explizit auf -1 setzen.
Um jetzt auch die 2. Selectbox vorzufüllen, rufen wir einfach den Eventhandler
der 2. Selectbox direkt auf (schliesslich ist in der 1. Box der erste Eintrag
ausgewählt).
Jetzt müssen nur noch die zu den Selectboxen gehörenden Eventhandler geschrieben werden, die die Einträge der Selectboxen auswerten können:
function hersteller_onChange(){
for (i=0; i<autoArray.length; i++) {
if (box1.options[box1.selectedIndex].value==autoArray[i][0]){
index=i;
break;
}
}
box2.options.length=0;
for (i=1; i<autoArray[index].length; i++) {
if (autoArray[index][i] !=null)
box2.options[box2.options.length]=new
Option(autoArray[index][i],autoArray[index][i]);
}
box2.selectedIndex=0;
typ_onChange();
}
}
function typ_onChange(){
document.f1.auswahl.value=box2.options[box2.selectedIndex].value;
}
Der Eventhandler
der 1. Selectbox muss den selektierten Eintrag ermitteln. Mit dem Ausdruck box1.selectedIndex
erhält man den Index des selektierten Option-Objekts innerhalb des Select-Objekts
(Selectbox). Damit kann man dann über box1.options[box1.selectedIndex].value
den selektierten Wert (Hersteller als String) ermitteln.
Mit diesem Wert wird in der 1. for-Schleife der Index der Typen des Herstellers
ermittelt (Loop über autoArray). Ist dieser gefunden kann in einer
2. for-Schleife die 2. Selectbox mit den zum Hersteller gehörigen Typen
gefüllt werden. Dabei wird aber mit autoArray[index][i] !=null vor
jedem Anlegen eines neuen Options-Objektes geprüft, ob auch ein Wert in
autoArray vorhanden ist. Dadurch sind wir bei dem Array nicht darauf
angewiesen, das alle Obereinträge (Hersteller) die gleiche Anzahl Untereinträge
(Typ) haben.
Zuletzt wird der erste Eintrag der Typen-Selectbox gesetzt und der Eventhandler
für die Typen-Selectbox aufgerufen. Der schreibt zum Test den Endwert in
die Eingabebox.
Der komplette Code kann hier downgeloadet werden.