Kontrollelement

TreeListControl

Baum und Tabelle kombiniert: Hierarchie in der ersten Spalte, Daten daneben.

Guided Tour

TreeListControl in 8 Schritten einbauen

Die Tour zeigt, wie sich das Kontrollelement auch nachträglich in eine bestehende Anwendung integrieren lässt.

TreeListControl - Gegenstand

Diese Übung demonstriert den Einsatz des TreeListControls. Es kombiniert einen Baum mit einer Liste, dessen Knoten sich auf- und zugeklappen lassen. Der Programmierer stellt hierfür lediglich die Anzeigedaten (das Datenmodell) durch Implementierung eines einfachen Interfaces bereit.

Das TreeListControl bietet die folgenden Features:

  • Die Linien auf der obersten Ebene können ein oder ausgeblendet werden. Zu den Knoten und den Blättern lassen sich unterschiedliche Bilder in einer ImageMap hinterlegen. Dabei wird die Zuordnung der Bilder zu dem jeweiligen Baumknoten mit Hilfe von Regulären Ausdrücken vorgenommen.
  • Das Kontrollelement verwaltet selbstständig alle notwendigen Zustandsdaten über mehrere Server Roundtrips hinweg. Dazu zählt beispielsweise der auf- oder zugeklappte Status der Baumknoten.
  • Vor den Baumeinträgen können Checkboxen ein oder ausgeblendet werden. Bei der Auswahl eines Knotens auf einer unteren Ebene, werden automatisch alle übergeordneten Knoten markiert.
Abbildung: TreeListControl - Gegenstand

Zum Einsatz des TreeListControls sind folgende Schritte notwendig:

  1. Auswahl des Layouts der Benutzeroberfläche
  2. Ableitung einer Actionklasse
  3. Instanziierung eines TreeListControls
  4. Bereitstellung der Anzeigedaten
  5. Konfiguration des Kontrollelementes innerhalb der JSP-Seite

1. Registrierung der Painterfactory

Zuerst erfolgt die Registrierung der Painterfactory. Sie legt fest, welches Design die Benutzeroberfläche erhält. Dies kann in der init()-Methode des Frontcontroler-Servlets applikationsweit geschehen.1 Wir wählen hier das Standarddesign, welches uns der DefaultPainter bereitstellt.2

Java
import javax.servlet.ServletExceptionimport org.apache.struts.action.ActionServlet;import com.cc.framework.ui.painter.PainterFactoryimport com.cc.framework.ui.painter.def.DefPainterFactory;import com.cc.framework.ui.painter.html.HtmlPainterFactory;public class MyFrontController extends ActionServlet {    public void init() throws ServletException {        super.init();        // Register all Painter Factories        // with the preferred GUI-Layout        // In this case we only use the Default-Layout.        PainterFactory.registerApplicationPainter (            getServletContext(), DefPainterFactory.instance());        PainterFactory.registerApplicationPainter (            getServletContext(), HtmlPainterFactory.instance());    }}

*1) Wenn der einzelne Benutzer zwischen verschiedenen Oberflächendesigns wählen können soll, dann werden zusätzliche PainterFactorys in der Benutzersession registriert. Dies erfolgt meist in der LoginAction mit PainterFactory.registerSessionPainter() im Session Scope.

*2) Weitere Designs (PainterFactories) sind im Lieferumfang der Professional Edition enthalten, oder können selbst entwickelt werden.

2. Ableitung der Action Klasse

In unserem TreeListControl wollen wir Regionen und Länder anzeigen. Daher soll die Action-Klasse, welche das Laden und Befüllen des TreeListControls übernimmt, die Bezeichnung "RegionBrowseAction" tragen. Die Actionklasse wird von der Klasse FWAction abgeleitet, welche die Struts-Action Klasse kapselt und um Funktionalitäten des Präsentationsframeworks erweitert. Dabei wird anstelle der execute()-Methode die doExecute()-Methode aufgerufen. [FWAction ist von org.apache.struts.Action abgeleitet] Sie erhält beim Aufruf den ActionContext, über den der Zugriff auf weitere Objekte, wie das Request- und Response-Objekt gekapselt ist.

Java
import java.io.IOException;import javax.servlet.ServletException;import com.cc.framework.adapter.struts.FWActionimport com.cc.framework.adapter.struts.ActionContextpublic class RegionBrowseAction extends FWAction {    /**     * @see com.cc.framework.adapter.struts.FWAction#doExecute(ActionContext)     */    public void doExecute(ActionContext ctx)        throws IOException, ServletException {        // In the next chapter, we will instantiate        // our TreeListControls with the DisplayData    }}

3. Instanziierung des TreeListControls

Nun wird das TreeListControl innerhalb unserer Action instanziiert und mit den Anzeigedaten gefüllt. Das Datenmodell wird dem Kontrollelement über die setDataModel()-Methode zugeordnet. Die Methode nimmt als Argument ein Objekt vom Typ TreeGroupDataModel entgegen. Dabei handelt es sich um ein Interface welches den Zugriff auf die Anzeigedaten des Baumes bereitstellt. Es ist die Aufgabe des Anwendungsentwicklers eine entsprechende Implementierung zur Verfügung zu stellen.

Java
import java.io.IOException;import javax.servlet.ServletException;import com.cc.framework.adapter.struts.ActionContext;import com.cc.framework.adapter.struts.FWAction;import com.cc.framework.ui.control.TreeListControl;public class RegionBrowseAction extends FWAction {    /**     * @see com.cc.framework.adapter.struts.FWAction#doExecute(ActionContext)     */    public void doExecute(ActionContext ctx)         throws IOException, ServletException {            try {                // first get the Displaydata for our TreeList                RegionGroupDsp dspData = DBRegion.fetchDspOutline();                // Create the TreeListControl an populate it                // withe the Data to display                TreelistControl regionList = new TreelistControl();                regionList.setDataModel(dspData);                // third put the TreeListControl into the Session-Object.                // Our TreeListControl is a statefull Object.                // Normaly  you can use an Objectmanager or an other                // workflow Component that manage the Livecyle of the Object                ctx.session().setAttribute("regions", regionList);            }        catch (Throwable t) {            ctx.addGlobalError("Error: ", t);        }        // Display the Page with the TreeList        ctx.forwardToInput();    }}

4. Bereitstellen der Anzeigedaten

Der Baum besteht aus Gruppen- und Blattknoten. Gruppenknoten können wieder weitere Knoten enthalten (Composite Pattern). Entsprechend stehen für die beiden Knotentypen die Interfaces TreeGroupDataModel und TreeNodeDataModel bereit (TreeGroupDataModel erweitert dabei TreeNodeDatamodel). Mit ihrer Hilfe lässt sich die Baumstruktur einfach erzeugen.

Dabei wird zuerst der Wurzelknoten erzeugt, unter dem dann weitere Gruppen oder Blätter eingehängt werden. Der Wurzelknoten wird dem TreeListControl als Datenmodell übergeben.

Die Vorgehensweise entspricht der Bereitstellung der Anzeigedaten für das TreeControl. Im Unterschied zu dem TreeControl soll das TreeListControl jedoch noch weitere Spalten präsentieren. Dazu muss unsere Bean, welche die Anzeigedaten bereitstellt, lediglich weitere Properties für die entsprechenden Spalten implementieren. In unserem Beispiel handelt es sich dabei um die Klasse RegionDsp, von der Gruppen- und Blattknoten abgeleitet sind.

Abbildung: 4. Bereitstellen der Anzeigedaten

Ein ausführliches Code-Beispiel wird Ihnen mit der Trialversion bereitgestellt, die Sie kostenlos herunterladen können.

5. Konfiguration des TreeListControls innerhalb der JSP-Seite

Um das TreeListControl-Tag auf einer JSP Seite einzusetzen, muss am Anfang der Seite die entsprechende Tag Library deklariert werden. Anschließend können die Common-Controls mit dem Präfix <ctrl:tagname /> referenziert werden. [Zudem muss die Aufnahme der Tag Bibliotheken im Deployment-Deskriptor, der WEB-INF/web.xml Datei, erfolgen]

JSP
<%@ taglib uri="/WEB-INF/tlds/cc-controls.tld" prefix="ctrl" %><ctrl:treelist    id="tl1"    name="regions"    action="sample301/regionBrowse"    title="Regions Structure"    rows="15"    refreshButton="true"    expandMode="multiple"    root="true">    <ctrl:columntree        title="Region"        property="region"        width="180"        imageProperty="type"/>    <ctrl:columntext        title="Name"        property="name"        width="250"/>    <ctrl:columnadd        title="Add"        property="add"/>    <ctrl:columnedit        title="Edit"        property="editable"/>    <ctrl:columndelete        title="Delete"        property="editable"/></ctrl:treelist>

Da wir das TreeListControl in der Session abgelegt haben, wird der Name der Bean über das name-Attribut angegeben. Zudem wird über das action-Attribut die Action spezifiziert, an die Aktionen aus unserem TreeListControl (onEdit, onDelete, etc... ) delegiert werden.

Wird das TreeListControl in einer FormBean abgelegt, reicht die Angabe des property-Attributes aus. Der Scope der Formbean muss in diesem Fall auf "session" eingestellt werden, damit das Kontrollelement über Server Roundtrips hinweg seinen Zustand behalten kann.

Bei Einsatz einer Workflowsteuerung kann das Kontrollelement über andere Komponenten erzeugt und später wieder aus der Session gelöscht werden.

Damit sind alle notwendigen Schritte zur Nutzung des TreeListControls abgeschlossen. Das Auf- und Zuklappverhalten muß nicht selbst implementiert werden. Es wird durch das Kontrollelement selbst verwaltet. Zur Navigation stellt das Kontrollelement Blätterbuttons zur Verfügung, die aktiviert werden, sobald die vorgegebene Zeilenanzahl überschritten wird.

Tour Ende

Das TreeListControl lässt sich schnell und einfach integrieren. Sein Standardverhalten lässt sich bei Bedarf überschreiben. So lassen sich auch spezielle TreeListControl-Objekte erstellen, die bereits den Zugriff auf bestimmte fachliche Daten kapseln und wiederkehrend innerhalb eines Anwendungsprojektes verwendet werden können.

Durch die Konfigurationsmöglichkeiten in der JSP-Seite kann das Verhalten des TreeListControls schnell geändert werden. Alternative Designs lassen sich durch eine Anpassung der bestehenden Painter einfach integrieren. Dabei werden unterschiedliche Designs auch parallel unterstützt. Der Programmierer kann sich auf die fachlichen Abläufe und die Bereitstellung der Anzeigedaten konzentrieren.

Features des TreeListControls:

  • Implementiert ein automatisches Auf- und Zuklappen von Knoten.
  • Verwaltet den Zustand von optionalen Checkboxen.
  • Verschiedene Konfigurationsmöglichkeiten (Ein- oder Ausblenden des Wurzelknotens, Änderung des Auf- und Zuklappverhalten einstellbar, Verbindungslinien auf oberster Ebene ausblendbar)
  • Daten unterhalb eines Gruppenknotens können auch erst bei Öffnen der Gruppe geladen werden. Der Baum muss also nicht von Anfang an vollständig bekannt sein. Dies ist beispielsweise in Verbindung mit Datenbanken hilfreich. Beim ersten Aufklappen eines Knoten mit unbekannter Kinderzahl wird der Anwendung ein onExpandEx Ereignis gesendet.
  • Design des TreeListControls in der JSP oder auch serverseitig definierbar!
  • Bildet Aktion, die auf dem Baum ausgeführt werden auf CallBack-Methoden in der Action-Klasse ab (Beispiele: onCheck, onExpand, onCollapse, onExpandEx).
  • Bilder vor den Knoten/Blättern über Reguläre Ausdrücke zuordbar.
  • Berechtigungsprüfung auf Knotenebene. Knoten können damit automatisch für unberechtigte Anwender ausgeblendet werden (siehe Security-Dokumentation).
  • Design durch Painterfactory an eigenen StyleGuide (Corporate Identity) anpassbar.
  • Optimierter HTML-Code.
  • Gleiches Look and Feel in Microsoft InternetExplorer > 5.x und Netscape Navigator > 7.x

Exkurs: Implementierung von Callback-Methoden

Das TreeListControl generiert bei einem Klick auf ein Label automatisch ein onDrilldown-Event auf das der Programmierer innerhalb der Action-Klasse reagieren kann.

Um in unserem Beispiel auf dieses Ereignis zu reagieren, nehmen wir eine entsprechende Callback-Methode in der RegionBrowseAction auf. Da wir die Businesslogik nicht an dieser Stelle implementieren möchten, leiten wir das Ereignis an eine andere Action - RegionDisplayAction - weiter.

Java
import java.io.IOException;import javax.servlet.ServletException;import com.cc.framework.adapter.struts.ActionContext;import com.cc.framework.adapter.struts.FWAction;import com.cc.framework.ui.control.TreeListControl;public class RegionBrowseAction extends FWAction {    /**     * @see com.cc.framework.adapter.struts.FWAction#doExecute(ActionContext)     */    public void doExecute(ActionContext ctx)        throws IOException, ServletException {            try {                RegionGroupDsp dspData = DBRegion.fetchDspOutline();                TreelistControl regionList = new TreelistControl();                regionList.setDataModel(dspData);                ctx.session().setAttribute("regions", regionList);            }        catch (Throwable t) {            ctx.addGlobalError("Error: ", t);        }        // Display the Page with the TreeList        ctx.forwardToInput();    }    // ------------------------------------------------    //          TreeList-Control Event Handler    // ------------------------------------------------    /**     * This Method is called when the TreeLabel is clicked     * In our Example we switch to the DetailView, which shows     * more Information about the node.     * @param   ctx ControlActionContext     * @param   key     UniqueKey, as created in the Datamodel     */    public void regions_onDrilldown(ControlActionContext ctx, String key) {        ctx.forwardByName(Forwards.DRILLDOWN, key);    }}

Der Name der CallBack-Methode setzt sich dabei aus dem Property-Namen des TreeListControls - dem Namen der Bean - und dem eingetretenen Event zusammen. Da das TreeListControl unter dem Namen "region" in der Session abgelegt wurde, lautet der Name der CallBack-Methode regions_onDrilldown.

Konfigurationsbeispiele

Fertige Konfigurationen zum Übernehmen

Screenshot, Konfiguration und der zugehörige JSP-Code.

Screenshot: TreeListControl, Beispiel 1

Konfiguration

  • Anzeige des Wurzelknotens (root="true").
  • Anzeige des Refresh-Buttons (zum Aktualisieren der Liste)
  • Anzeige von maximal 15 Zeilen pro Seite
  • Durch die Angabe von expandMode="multiple" werden geöffnete Knoten beim Aufklappen weitere Knoten nicht mehr geschlossen. Mit der Einstellung expandMode="single" wird immer nur ein Knoten aufgekplappt gezeichnet, d.h. alle andere Knoten werden immer automatisch geschlossen.
  • Die Instanz des TreeControls wird unter seinem Namen im Scope (Session/Request) gesucht. Bei Verwendung des Property-Attributs wird das Control aus der FormBean ermittelt.
  • Tree-Spalte zur Anzeige des Baums
  • Text-Spalte
  • Add-Spalte zur Neuanlage von Datensätzen unterhalb eines Knotens. Dabei lässt sich über das DataModel steuern, unter welcher Bedingung der Button erscheinen soll. Durch Aufnahme des Permission-Attributes kann die Spalte zudem rollenabhängig integriert werden.
  • Edit-Spalte zur Bearbeitung des Datensatzes. Dabei lässt sich über das DataModel steuern, unter welcher Bedingung der Button erscheinen soll. Durch Aufnahme des Permission-Attributes kann die Spalte zudem rollenabhängig integriert werden.
  • Delete-Spalte zur Bearbeitung des Datensatzes. Dabei lässt sich über das DataModel steuern, unter welcher Bedingung der Button erscheinen soll. Durch Aufnahme des Permission-Attributes kann die Spalte zudem rollenabhängig integriert werden.
JSP
<ctrl:treelist        id="tl1"        name="regions"        action="sample301/regionBrowse"        title="Regions Structure"        rows="15"        refreshButton="true"        createButton="true"        expandMode="multiple"        root="true">        <ctrl:columntree                title="Region"                property="region"                width="180"                imageProperty="type"/>        <ctrl:columntext                title="Name"                property="name"                width="250"/>        <ctrl:columnadd                title="Add"                property="add"/>        <ctrl:columnedit                title="Edit"                property="editable"/>        <ctrl:columndelete                title="Delete"        property="editable"/></ctrl:treelist>
Screenshot: TreeListControl, Beispiel 2

Konfiguration

  • Deklaration einer ImageMap mit benutzerspezifischen Bildern für Gruppen und Blätter. Hierzu wird die ImageMap innerhalb des Tree Spalte über das Attribut imagemap referenziert (imagemap="im_products). Jeder Eintrag im Baum liefert über die in der imageProperty spezifizierten Methode einen Ausdruck zurück, der mit der ImageMap verglichen wird. Bei Übereinstimmung wird das entsprechende Image verwendet.
  • Weitere Einstellungen vergleiche Konfigurationsbeispiel A.
JSP
  <util:imagemap name="im_product">        <util:imagemapping                rule="group.open"                src="app/images/imgBoxOpen.gif"                width="16"                height="16"/>        <util:imagemapping                rule="group.closed"                src="app/images/imgBoxClosed.gif"                width="16"                height="16"/>        <util:imagemapping                rule="country"                src="app/images/imgItem.gif"                width="16"                height="16"/></util:imagemap><ctrl:treelist        id="tl1"        name="regions"        action="sample302/regionBrowse"        title="Regions Structure"        rows="15"        refreshButton="true"        expandMode="multiple"        root="true">        <ctrl:columntree                title="Region"                property="region"                width="180"                imageProperty="imgType"                imagemap="im_product"/>        <ctrl:columntext                title="Name"                property="name"                width="250"/>        <ctrl:columnadd                title="Add"                property="add"/>        <ctrl:columnedit                title="Edit"                property="editable"/>        <ctrl:columndelete                title="Delete"                property="editable"                onclick="return message();"/>        <ctrl:columnbutton                title="Info"                property="print"                width="35"                image="app/images/imgPDF.gif"                align="center"/></ctrl:treelist>