Um Daten in einer Java-Webanwendung dynamisch nachzuladen bietet sich das AJAX-Framework Direct Web Remoting [DWR] an. Mit diesem ist es möglich Java Methoden aus JavaScript aus aufzurufen und die Ergebnismenge zu empfangen. Da JavaScript mit den Objektstrukturen von Java nichts anfangen kann, müssen die Daten zuvor in JSON-Strings verpackt werden.
An einem praktischem Beispiel soll dies gezeigt werden. Wir haben 2 Dropdown-Boxen die in Abhängigkeit zueinander stehen. In der oberen Dropdownbox wählt man einen Zyklus (Täglich,Wöchentlich,Monatlich) aus und die unterer Dropdownbox soll ihre Inhalte entsprechend anpassen.
Als erstes werden die benötigten Javaklassen erzeugt.
Da es sich im Optionsfelder handelt, gibt es eine Basisklasse mit dem Name OptionElement. Diese Klasse hat alle Attribute die auch das Option-Element in HTML hat. Zusätzlich kommt noch das Attribut order hinzu, welches die Reihenfolge innerhalb des Select-Elementes repräsentiert. Da bei der Serialisierung (Java->JSON), die Objektreihenfolge verworfen wird.
1 2 3 4 5 6 7 8 9 10 11 |
public class OptionElement { private int order; private String id; private String name; private boolean disabled; private boolean selected; public OptionElement() { this.selected = false; this.disabled = false; } |
von dieser Klasse erben die 2 folgenden Klassen:
Die Klasse Zyklus entspricht der 1.Dropdownbox und hat als „Kindelemente“ alle zugehörigen Elemente der 2.Dropdownbox.
1 2 3 4 5 6 7 |
public class Zyklus extends OptionElement { private Collection<ZyklusDetails> zyklusDetailsList; public Zyklus() { super(); this.zyklusDetailsList = new ArrayList<ZyklusDetails>(); } } |
Die Klasse Zyklusdetails enspricht der 2.Dropdownbox
1 2 3 4 5 |
public class ZyklusDetails extends OptionElement { public ZyklusDetails() { super(); } } |
Nach dem eine konkrete Ausprägung der Zyklen erstellt wurde, werden diese in ein JSON-Objekt transferiert. Hierbei kommt die klassische JSON-Bibliothek zum Einsatz gekommen. Es gibt auch Alternativen, wie zum Beispiel FLEXJSON, welches „automatisch“ Objektreferenzen auflöst und serialisiert. Dies muss bei dieser Bibliothek händisch gelöst werden.
Wir haben natürlich mehrere Zyklusobjekte, die in einem Array gesammelt wurden. Dieses Array wird jetzt durchlaufen und in JSON-Objekte serialisert.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public JSONObject convertZyklenToJSON(Zyklen zyklen) { try { JSONObject zyklenJSON = new JSONObject(); for (Zyklus z : zyklen.getZyklusList()) { JSONObject zyklus = new JSONObject(z); for (ZyklusDetails zd : z.getZyklusDetailsList()) { zyklus.put(zd.getId(), new JSONObject(zd)); } zyklenJSON.put(z.getId(), zyklus); } return zyklenJSON; } catch (JSONException e) { e.printStackTrace(); } return null; } |
Anschließend werden die JSON-Objekte in ein JSON-String verpackt und an die aufrufende JavaScript-Funktion zurück gesendet.
1 2 3 4 5 6 7 8 9 10 |
public String getZyklus() { JSONObject data = new JSONObject(); try{ data.put("Zyklen", convertZyklenToJSON(getZyklen())); data.put("Format", convertFormatToJSON(getFormat())); }catch (JSONException e) { e.printStackTrace(); } return data.toString(); } |
Zusätzlich wird an dieser Stelle noch eine weitere Funktion (convertFormatToJSON) aufgerufen. Dies soll einfach nur zeigen das x-beliebig viele JSON-Objekte in ein JSON-String gepackt werden können und diese auch später wieder leicht auf der JavaScript-Seite aufgelöst werden können.
Folgende JavaScriptFunktion empfängt den JSON-String und schreibt die Objekte, je nach Objekttyp (Zyklus oder Format) in seperate Arrays, um diese im Anschluss weiterzuverarbeiten.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
getZyklusData: function (data){ //BerichtAboAccess.getZyklus(2, function(data) { zyklusDataDB = new Function("return " + data)( ); for(var prop in zyklusDataDB) { for(var propDetail in zyklusDataDB[prop]){ var className = zyklusDataDB[prop][propDetail].class; switch ((className.substring(className.lastIndexOf('$')+1,className.length))){ case "Format": formatData.push(zyklusDataDB[prop][propDetail]); break; case "Zyklus": zyklusData.push(zyklusDataDB[prop][propDetail]); break; } } } formatData.sort(this.Keysort); this.sortZyklusArray(); this.initialisiereZyklusChooser(); this.initialisiereFormatChooser(); } |
Die beiden Arrays formatData und zyklusData wurden außerhalb der Funktion, als globale Variablen angelegt.
Da bei der Serialisierung die Reihenfolge der Objekte komplett verworfen wurde, werden in der Funktion sortZyklusArray, die Elemente nach dem Attribute order sortiert. Dafür wurde die Standard Keysort-Methode überschrieben:
1 2 3 4 5 |
Keysort: function (a, b) { if (a.order<b.order) return -1; if (a.order>b.order) return 1; return 0; } |
Die folgende Methode durchläuft das Zyklusarray und nutzt die überschriebende Keysort-Methode zum sortieren:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
sortZyklusArray: function (){ zyklusData.sort(this.Keysort); for (var i = 0; i < zyklusData.length; ++i){ var zyklusDetails = new Array(); for(j in zyklusData[i]){ if(zyklusData[i][j].id != undefined){ zyklusDetails.push(zyklusData[i][j]); } } zyklusDetails.sort(this.Keysort); count = 0; for(k in zyklusData[i]){ if(zyklusData[i][k].id != undefined){ zyklusData[i][k] = zyklusDetails[count]; count++; } } } } |
Die folgende Methode, erzeugt die Optionsfelder in den Select-Elementen und setzt dabei auch die Attribute selected oder disabled:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
initialisiereZyklusChooser: function () { var selectedZyklus = 0; for (var i = 0; i < zyklusData.length; ++i){ var objOption = document.createElement("OPTION"); objOption.text= zyklusData[i].name; objOption.value= zyklusData[i].id; if(zyklusData[i].disabled == true){ objOption.disabled = true; } if(zyklusData[i].selected == true){ objOption.selected = true; DWRUtil.removeAllOptions(detailsChooser); selectedZyklus = zyklusData[i].id; } zyklusChooser.options.add(objOption); for(var j in zyklusData[i]) { //Standardmäßig ist Täglich ausgewählt if(zyklusData[i][j].id != undefined) { if((selectedZyklus == 0 && zyklusData[i].id == "z1") | (zyklusData[i].id == selectedZyklus)) var objOption = document.createElement("OPTION"); objOption.text= zyklusData[i][j].name; objOption.value= zyklusData[i][j].id; if(zyklusData[i][j].disabled == true){ objOption.disabled = true; } if(zyklusData[i][j].selected == true){ objOption.selected = true; } detailsChooser.options.add(objOption); } } } this.toogleChooser(zyklusChooser); this.toogleChooser(detailsChooser); } |
Wenn ein Select-Element nur ein Attribut hat, setzt die Methode toogleChooser, dieses Element auf disabled, da eh keine weiteren Elemente zur Auswahl stehen.
1 2 3 4 |
toogleChooser: function (chooser){ if(chooser.options.length == 1) chooser.disabled = true; else chooser.disabled = false; } |
Die folgende Methode wird aufgerufen, wenn das onChange-Event in der ersten Dropdownbox ausgelöst wird:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
onChangeZyklus: function (){ for (var i = 0; i < zyklusData.length; ++i){ if(zyklusData[i].id == $F(zyklusChooser)){ DWRUtil.removeAllOptions(detailsChooser); for(var j in zyklusData[i]){ if(zyklusData[i][j].id != undefined) { var objOption = document.createElement("OPTION"); objOption.text= zyklusData[i][j].name; objOption.value= zyklusData[i][j].id; if(zyklusData[i][j].disabled == true){ objOption.disabled = true; } detailsChooser.options.add(objOption); } } this.toggleDetailsSelectStatus("detailsChooser"); } } } |
Die Methode xxx ist nur eine Spielerei und verfärbt kurz die untere Dropdownbox, um die Veränderung zu verdeutlichen.
1 2 3 4 |
toggleDetailsSelectStatus: function (selectField){ new Effect.Highlight(selectField, {startcolor:'#FEF6DC', endcolor:'#FFFFFF', duration:1}); this.toogleChooser(detailsChooser); } |
Dazu wurde folgende Funktion aus der script.aculo.us-Bibliothek verwendet.
urls:
http://pragprog.com/titles/cppsu
http://scripteka.com/
http://prototype-graphic.xilinus.com/
http://blog.xilinus.com/2007/11/4/prototype-ui-beta-version-pwc-reloaded