{"id":320,"date":"2025-05-15T17:15:13","date_gmt":"2025-05-15T15:15:13","guid":{"rendered":"https:\/\/www.schoenberg-solutions.de\/arndtblog\/?p=320"},"modified":"2025-05-15T17:15:15","modified_gmt":"2025-05-15T15:15:15","slug":"jsf-primefaces-ohne-javascipt-anpassen-columntoggler","status":"publish","type":"post","link":"https:\/\/www.schoenberg-solutions.de\/arndtblog\/?p=320","title":{"rendered":"JSF \/ Primefaces ohne JavaScipt anpassen &#8211; columnToggler"},"content":{"rendered":"\n<p>Es gab folgende Fragestellung: k\u00f6nnen die Informationen der Primfaces Column Toggler (siehe: <a href=\"https:\/\/www.primefaces.org\/showcase\/ui\/data\/datatable\/columnToggler.xhtml\">https:\/\/www.primefaces.org\/showcase\/ui\/data\/datatable\/columnToggler.xhtml<\/a>) Komponente in eine Jakarta EE Projekt in der Datenbank gespeichert und geladen werden? Die Komponente ist eine aus der reichhaltigen JSF Komponentenbibliothek PrimeFaces, die es dem Nutzer erlaubt Spalten einer Tabelle ein- und auszublenden. Ziel der Anforderung ist es, die Auswahl eines Nutzers in der Datenbank zu speichern und bei Aufruf der Seite wieder auszulesen, um die Anzeige vorzubef\u00fcllen.<\/p>\n\n\n\n<p>Diese Aufgabe zeigt deutlich, das sehr gute Konzept von JSF als komponentenbasiertes UI-Framework. Die meisten Java Entwickler sind eher keine Full-Stack-Entwickler. Der UI Teil weicht mit Angular und React in der Handhabung zu stark von den Konzepten von JAVA ab. Auch ist der Sprachwechsel zu JavaScript \/TypeScript und das abweichende Toolset (aus meiner Sicht h\u00e4ufig auch nicht so ausgereifte Set) ist problematisch.<\/p>\n\n\n\n<p>In JSF ben\u00f6tigt der Entwickler im Allgemeine keine oder nur minimale JacaScript Kenntnisse, da die Komponenten den JavaScript Teil kapseln. Wie kann man aber ohne JavaScript die Interaktion zwischen Front- und Backend realisieren, wen die Grundfunktionalit\u00e4t in der Komponente nicht gegeben ist? Sehen wir uns an, um welche Komponente es sich handelt:<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>&lt;p:dataTable id=\"products\" var=\"product\"\n\tvalue=\"#{dtBasicView.products}\"&gt;\n\t&lt;f:facet name=\"header\"&gt;\n\t\t&lt;div class=\"flex justify-content-between align-items-center\"&gt;\n\t\t\tList of Products\n\t\t\t&lt;div&gt;\n\t\t\t\t&lt;p:commandButton id=\"toggler\" type=\"button\" value=\"Columns\" icon=\"pi pi-align-justify\"\/&gt;\n\t\t\t\t&lt;p:columnToggler datasource=\"products\" trigger=\"toggler\"&gt;\n\t\t\t\t\t&lt;p:ajax event=\"toggle\" listener=\"#{dtBasicView.onToggle}\"\/&gt;\n\t\t\t\t&lt;\/p:columnToggler&gt;\n\t\t\t&lt;\/div&gt;\n\t\t&lt;\/div&gt;\n\t&lt;\/f:facet&gt;<\/code><\/pre>\n\n\n\n<p>Die Komponente wird im Header einer Tabelle eingebunden und kann wie alle Komponenten in JSF \u00fcber ein einfaches Tag bei Aktionen \u00fcber Ajax an das Backend angebunden werden (<code>p:ajax<\/code>). Mit einer guten IDE (ich verwende Eclipse) gibt Autovervollst\u00e4ndigung im xhtml-Editor und die M\u00f6glichkeit direkt \u00fcber die Namen der BackingBeans und Methoden in die jeweiligen Klassen zu springen.<\/p>\n\n\n\n<p>Soviel zu Setup. Wie realisiere ich nun das Speichern und Laden der Informationen des Nutzers? Der Nutzer soll die Auswahl ja nicht bei jedem Seitenaufbau erneut eingeben m\u00fcssen. Bei einem naiven Ansatz k\u00f6nnten in der <code>onToggle <\/code>Methode die Daten durch eine fixe BackingBean in der DB gespeichert werden. Beim erneuten Aufruf der Seite werden die Daten geeignet geladen und in der Komponente gesetzt. Das h\u00e4tte zur Folge, dass jede Tabelle, die diese Logik verwendet, eine ggf. abgeleitet ManagedBean ben\u00f6tigt, die h\u00e4ndisch angelegt werden muss. Bei mehreren Tabellen auf einer Seite m\u00fcssten mehrere Beans angelegt oder die Logik mehrfach in einer Bean existieren.<\/p>\n\n\n\n<p>Deutlich besser w\u00e4re es, wenn wir dies nur in der XHTML-Seite \u00fcber Tags steuern k\u00f6nnten &#8211; wie es bei den meisten JSF Komponenten der Fall ist.<\/p>\n\n\n\n<p>Mit Comopsite Componentes (CC) und dem richtigen Konzept ist dies m\u00f6glich &#8211; wie fast alles in JSF sauber umsetzbar ist. Die angelegte Komponente soll nicht auf eine fixe Bean zu greifen, die \u00fcbergeben wird, um die Aktionen im Backend auszuf\u00fchren. Dann m\u00fcssten wir ebenfalls im Vorfeld f\u00fcr jede Tabelle eine Bean anlegen. Die CC soll selbstst\u00e4ndig die notwendige Bean anlegen und an diese die relevanten Informationen \u00fcbergeben. Diese Bean muss allerdings im Toggler im Ajax-Tag \u00fcber ihren Namen angesprochen werden k\u00f6nnen.<\/p>\n\n\n\n<p>Eine Implementierung des <code>tableColToggleController<\/code> k\u00f6nnte wie folgt aussehen <\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>\n&lt;ui:composition xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\"\n      xmlns:cc=\"http:\/\/xmlns.jcp.org\/jsf\/composite\"\n      xmlns:ui=\"http:\/\/xmlns.jcp.org\/jsf\/facelets\"\n      xmlns:h=\"http:\/\/xmlns.jcp.org\/jsf\/html\"\n      xmlns:f=\"http:\/\/xmlns.jcp.org\/jsf\/core\"\n      xmlns:p=\"http:\/\/primefaces.org\/ui\"\n      >\n    &lt;cc:interface>\n        &lt;cc:attribute name=\"toggleControllerName\" required=\"true\" \/>\n        &lt;cc:attribute name=\"tableId\" required=\"true\" \/>\n    &lt;\/cc:interface>\n\n    &lt;cc:implementation>\n        &lt;f:event type=\"preRenderView\" listener=\"#{primeTableColToggleControllerFactoryAB.\n                   createOrFindToggleControllerBean(cc.attrs.toggleControllerName, cc.attrs.tableId).onPreRenderView}\" \/>\n    &lt;\/cc:implementation>\n&lt;\/ui:composition><\/code><\/pre>\n\n\n\n<p>An die CC wird der Name der Bean (<code>toggleControllerName<\/code>) und die ID der Tabelle aus der XHMTL Seite \u00fcbergeben. Eine Implementierung mit <code>&lt;ui:param><\/code> kann hier nicht verwendet werden, da bei Auswertung von <code>&lt;ui:param><\/code> die Parameter der CC noch nicht belegt sind.<\/p>\n\n\n\n<p>Die Nutzung der CC erfolgt \u00fcber das Tag <code>tableColToggleController<\/code> in der XHTML-Seite und den entsprechenden onToggle-Aufruf, der deb \u00fcbergebenen Namen verwendet.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>&lt;schoeso:tableColToggleController toggleControllerName=\"addressToglleBean\" tableId=\"tblAdressen\" \/&gt;\n&lt;p:dataTable id=\"tblAdressen\" value=\"#{primeTableTogglerUiVB.addressDataDtoList}\" var=\"item\" paginator=\"false\" emptyMessage=\"keine Daten vorhanden\" lazy=\"false\"&gt;\n\t&lt;f:facet name=\"header\"&gt;\n\t\t&lt;div class=\"flex justify-content-between align-items-center\"&gt;\n\t\t\tSpalten ausw\u00e4hlen\n\t\t\t&lt;div&gt;\n\t\t\t\t&lt;p:commandButton id=\"toggler\" type=\"button\" value=\"Columns\" icon=\"pi pi-align-justify\"\/&gt;\n\t\t\t\t&lt;p:columnToggler datasource=\"tblAdressen\" trigger=\"toggler\"&gt;\n\t\t\t\t\t&lt;p:ajax event=\"toggle\" listener=\"#{addressToglleBean.onToggle}\" update=\"@parent\"\/&gt;\n\t\t\t\t&lt;\/p:columnToggler&gt;\n\t\t\t&lt;\/div&gt;\n\t\t&lt;\/div&gt;\n\t&lt;\/f:facet&gt;<\/code><\/pre>\n\n\n\n<p>Was macht die Magie im Hintergrund? Da es nach meinem Wissen aktuell nicht m\u00f6glich ist, aus einer CC jeweils eine CDI-Bean der gleichen Klasse mit unterschiedlichen Namen zu erzeugen, verwenden wir eine Factory, die einmalig im Projekt existieren muss und gut in eine Basis-Bibliothek ausgelagert werden kann (<code>primeTableColToggleControllerFactoryAB<\/code>).<\/p>\n\n\n\n<p>Die Kernlogik sieht wie folgt aus<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>public PrimeTableColToggleController createOrFindToggleControllerBean(String beanName, String tableId) {\n\tFacesContext facesContext = FacesContext.getCurrentInstance();\n\tMap&lt;String, Object&gt; viewMap = facesContext.getViewRoot().getViewMap();\n\tif (beanName != null &amp;&amp; !viewMap.containsKey(beanName)) {\n\t\tString applicationName = \"TestAppl\"; \n\t\tPrimeTableColToggleController controller = new PrimeTableColToggleController(this.userDtoVal, applicationName, tableId, this.tableColStatusService);\n\t\tviewMap.put(beanName, controller);\n\t\treturn controller;\n\t} else {\n\t\treturn (PrimeTableColToggleController) viewMap.get(beanName);\n\t}\n}<\/code><\/pre>\n\n\n\n<p>Der entscheidende Punkt ist, dass die eigentlich von der UI angesprochene Bean vom Typ <code>PrimeTableColToggleController <\/code>hier bei Bedarf erzeugt wird und in die <code>ViewMap <\/code>gelegt wird. Die ViewMap wird von JSF verwendet, um \u00fcber die Keys die Objekte in den XHTML-Seiten \u00fcber die EL anzusprechen. Dies sind im Allgemeinen die CDI-Beans, die dort automatisch abgelegt werden. Wir erzeugen eine eigene Bean mit der notwendigen Logik und legen dies ebenfalls dort ab, damit wir sie \u00fcber den Namen ansprechen k\u00f6nnen. Wichtig ist, dass es sich nicht um eine CDI-Bean handelt &#8211; sie hat also keinen CDI-Lifecycle.<\/p>\n\n\n\n<p>Da die Bean aber in der Map liegt, wird sie mit dem View zerst\u00f6rt und kann \u00fcber ihren Namen angesprochen werden.<\/p>\n\n\n\n<p>Durch Parameter  oder Logik, kennt der PrimeTableColToggleController alle notwendigen Informationen f\u00fcr das Speichern der Nutzerauswahl<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Applikation (\u00fcbergeben)<\/li>\n\n\n\n<li>User (\u00fcbergeben)<\/li>\n\n\n\n<li>Webseite (ermittelt)<\/li>\n\n\n\n<li>ID der Tabelle (\u00fcbergeben)<\/li>\n\n\n\n<li>Spalte der Tabelle (ermittelt)<\/li>\n<\/ul>\n\n\n\n<p>Hier die relevanten Teile des Controllers. Der Controller h\u00e4lt keine CDI Annotationen und kann auch Dependency Injection nicht verwenden.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>public class PrimeTableColToggleController implements Serializable {\n...\npublic void onPreRenderView(ComponentSystemEvent event) throws AbortProcessingException {\n  if (!this.initialized) {\n    FacesContext context = FacesContext.getCurrentInstance();\n    UIComponent root = context.getViewRoot(); \/\/ Das ViewRoot ist der Startpunkt im Komponentenbaum\n    this.setPageName(this.getCurrentXhtmlFileName());\n    \/\/ Suche nach der DataTable anhand des letzten Teils ihrer ID - JSFUtils\n    DataTable dataTable = (DataTable) this.findComponentByIdEndingWith(root, this.getTableId());\n    LOG.info(\"Tabelle {}\", dataTable);\n    if (dataTable != null) {\n\tMap&lt;String, Boolean> colVisibleStatusMap = this.tableColStatusService.getColVisibleStatusMap();\n\tLOG.info(\"Map {}\", colVisibleStatusMap);\n\tfor (UIComponent child : dataTable.getChildren()) {\n\t\tif (child instanceof Column column) {\n\t\t\tString colNameInt = this.getColumnInternalName(column);\n\t\t\tif (colVisibleStatusMap.containsKey(colNameInt)) {\n\t\t\t\tcolumn.setVisible(colVisibleStatusMap.get(colNameInt));\n\t\t\t\tLOG.info(colNameInt + \" gesetzt \" + colVisibleStatusMap.get(colNameInt));\n\t\t\t\tcolVisibleStatusMap.remove(colNameInt);\n\t\t\t} else {\n\t\t\t\tLOG.info(colNameInt + \" nicht gefunden\");\n\t\t\t}\n\t\t}\n\t}\n\t\/\/ nicht mehr existierende Spalten l\u00f6schen\n\tfor (String item : colVisibleStatusMap.keySet()) {\n\t\tthis.tableColStatusService.removeColData(this.userDto, null, this.getPageName(), item);\n\t}\n    }\n    this.initialized = true;\n  }\n}<\/code><\/pre>\n\n\n\n<p>Die Methode, die bei jedem Aufbau der Seite aus dem erzeugten Controller aufgerufen wird hat einen Semaphor, um festzustellen, dass es der erste Aufbau ist. Ist dies der Fall, wird der Name der Seite bestimmt, die Tabelle gesucht und die entsprechenden Spalten auf nicht sichtbar gesetzt. Der Toggler bef\u00fcllt sich dann aus diesen Informationen und wird damit in der UI korrekt angezeigt. Einige Hilfsmethoden habe ich hier nicht abgebildet. Bei Fragen schickt mir gerne eine E-Mail.<\/p>\n\n\n\n<p>Die <code>onToggle <\/code>Methode muss dann nur noch die gespeicherten Daten aktualisieren<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>public void onToggle(ColumnToggleEvent event) {\n\tInteger index = (Integer) event.getData();\n\tUIColumn column = event.getColumn();\n\tColumn column2 = (Column) column;\n\tVisibility visibility = event.getVisibility();\n\tthis.tableColStatusService.saveColData(this.userDto, null, this.getPageName(), \n            this.getColumnInternalName(column2), Visibility.VISIBLE.equals(visibility));\n\n\tString text = \"Column: \" + index + \" AriaHeader: \" + column.getAriaHeaderText() + \n                      \" HeaderText\" + column.getHeaderText() + \" toggled: \" + visibility + \" \";\n\tFacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_INFO, text, null);\n\tFacesContext.getCurrentInstance().addMessage(null, msg);\n}<\/code><\/pre>\n\n\n\n<p><strong>Ergebnis<\/strong><\/p>\n\n\n\n<p>Durch ein einfaches Tag kann das gew\u00fcnschte Verhalten vollst\u00e4ndig gekapselt werden. In der Entwicklung muss nur noch das Tag <code>schoeso:tableColToggleController<\/code> verwendet und die Basis-Bibliothek mit der CC und der Implementierung eingebunden werden.<\/p>\n\n\n\n<p>Mit JSF kann effizient auch f\u00fcr komplexe Anforderungen eine L\u00f6sung gefunden werden, bei der man sich in der t\u00e4glichen Entwicklung auf die Fachlichkeit konzentrieren kann, ohne Boilerplate-Code zu erzeugen. Richtig angewendet schl\u00e4gt JSF fast alle L\u00f6sungen im Zeitaufwand f\u00fcr die L\u00f6sung und im Komfort bei der Nutzung &#8211; wenn man sich im Jakarta EE Kontext bewegt!<\/p>\n\n\n\n<p>Also: viel Spa\u00df mit effizienten L\u00f6sungen und zufriedenen Kunden.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Es gab folgende Fragestellung: k\u00f6nnen die Informationen der Primfaces Column Toggler (siehe: https:\/\/www.primefaces.org\/showcase\/ui\/data\/datatable\/columnToggler.xhtml) Komponente in eine Jakarta EE Projekt in der Datenbank gespeichert und geladen werden? Die Komponente ist eine aus der reichhaltigen JSF Komponentenbibliothek PrimeFaces, die es dem Nutzer erlaubt Spalten einer Tabelle ein- und auszublenden. Ziel der Anforderung ist es, die Auswahl eines [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"wp-custom-template-living-server-seite","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-320","post","type-post","status-publish","format-standard","hentry","category-jee"],"_links":{"self":[{"href":"https:\/\/www.schoenberg-solutions.de\/arndtblog\/index.php?rest_route=\/wp\/v2\/posts\/320","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.schoenberg-solutions.de\/arndtblog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.schoenberg-solutions.de\/arndtblog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.schoenberg-solutions.de\/arndtblog\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.schoenberg-solutions.de\/arndtblog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=320"}],"version-history":[{"count":18,"href":"https:\/\/www.schoenberg-solutions.de\/arndtblog\/index.php?rest_route=\/wp\/v2\/posts\/320\/revisions"}],"predecessor-version":[{"id":338,"href":"https:\/\/www.schoenberg-solutions.de\/arndtblog\/index.php?rest_route=\/wp\/v2\/posts\/320\/revisions\/338"}],"wp:attachment":[{"href":"https:\/\/www.schoenberg-solutions.de\/arndtblog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=320"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.schoenberg-solutions.de\/arndtblog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=320"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.schoenberg-solutions.de\/arndtblog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=320"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}