Przekazywanie akcji do komponentu composite w Facelets dla JSF 2

Specyfikacja JavaServer Faces 2.0 (JSR 314) obejmuje język definicji widoku Facelets. Udostępnia mechanizmy ułatwiające trzymanie się reguły DRY (Don’t Repeat Yourself) czyli unikanie powtarzania własnego kodu. Templates i composite components, bo o nich mowa umożliwiają dekompozycję definicji widoku na mniejsze framenty. Często w przypadku komponentów zawierających na przykład przyciski przydaje się możliwość przekazywania jako parametru akcji przez nie wywoływanej. W specyfikacji znalazłem informację o dwóch sposobach, w tym jednym podanym w przykładzie. Niemniej jednak podczas korzystania z referencyjnej implementacji okazało się, że jeszcze nie wszystko jest spójne. Korzystałem z implementacji JSF 2.0 Mojarra będącej referencyjną implementacją dla JSR 314. Jako serwer aplikacyjny użyłem Google App Engine opartego na Jetty.

W sekcji specyfikacji można znaleźć jeden przykład definicji komponentu:

<composite:interface>
<composite:actionSource name="loginEvent" />
</composite:interface>
<composite:implementation>
<p>Username: <h:inputText id="usernameInput" /></p>
<p>Password: <h:inputSecret id="passwordInput" /></p>
<p><h:commandButton id="loginEvent" value="login"/>
</composite:implementation>

repozytorium ze źrółami Mojarra znalazłem nawet mniej więcej podobny przykład. Definicja komponentu:

 <composite:interface>
  <composite:actionSource name="abutton" />
</composite:interface>
 
<composite:implementation>
      <h:commandButton id="abutton" value="Click Me" />
</composite:implementation>

i jego użycie:

    <ez:actionButton id="loginPanelInConsumingPage" model="#{bean}">
      <f:actionListener for="abutton" binding="#{actionBean.listener}"/>
    </ez:actionButton>

Zgodnie z dokumentacją tagu f:actionListener opcjonalny atrybut for odnosi się do źródła akcji wewnątrz komponentu. Przykład wydaje się być poprawny, ale niestety uruchomienie go powoduje wyświetlenie błędu typu: /actionListener.xhtml @60,73 <f:actionListener> Parent is not of type ActionSource, type is: javax.faces.component.html.HtmlForm@1248f2b Okazuje się, że w Mojarra jeszcze nie został zaimplementowany ten mechanizm. Przykład umieściłem na serwerze tutaj (przy okazji można zobaczyć ulepszone wyświetlanie błędów w JSF 2).  Podobne przykłady można znaleźć w dokumentacji tagów dla Facelets. Na szczęście nie jest to jedyny sposób przekazywania metod.

W sekcji 10.3.3.3 specyfikacji znajduje się opis przetwarzania atrybutów dla elementów zawartych w composite component. Jeśli element zawiera atrybut, którego wartość zaczyna się od #{cc.attr. i jest on typu MethodExpression, to jest on kojarzony z odpowiednim atrybutem interfejsu dla zawierającego go composite component. Nie jest to zilustrowane przykładem w specyfikacji, ale w repozytorium można znaleźć działający przykład. Definicja komponentu:

<composite:interface>
    <composite:attribute name="action" targets="navbutton" required="true" method-signature="String f1()" />
</composite:interface>
<composite:implementation>
   <h:commandButton id="navbutton" action="#{cc.attrs.action}" value="NavButton" type="submit" />
</composite:implementation>

Użycie komponentu:

<ez:nav action="#{navigation.goNav2}"/>

W specyfikacji nie znalazłem wymagania odnoszącego się do atrybutu targets w interfejsie komponentu. Okazuje się, że przykład ze zmodyfikowaną linią

<composite:attribute name="action" required="true" method-signature="String f1()" />

działa poprawnie. Nie ma też wymagania co do nazwy elementu definiującego atrybut akcji w interfejsie. Tak więc komponent

<composite:interface>
    <composite:attribute name="action_1" required="true" method-signature="String f1()" />
</composite:interface>
<composite:implementation>
   <h:commandButton id="navbutton" action="#{cc.attrs.action_1}" value="NavButton" type="submit" />
</composite:implementation>

użyty odpowiednio

<ez:nav action_1="#{navigation.goNav2}"/>

również działa poprawnie. Można także skorzystać z atrybutu targets w interfejsie nie definiując akcji dla elementu ją wywołującego:

<composite:interface>
    <composite:attribute name="action" targets="navbutton" required="true" method-signature="String f1()" />
</composite:interface>
<composite:implementation>
   <h:commandButton id="navbutton" value="NavButton" type="submit" />
</composite:implementation>

W sekcji 3.6.2.1 specyfikacji można znaleźć jedynie, że atrybut targets zawiera listę elementów typu AttachedObjectTargets które mogą się odnosić do różnych rzeczy jak listenery, konwertery czy walidatory. Ich typy muszą być rzutowalne na EditableValueHolderAttachedObjectTarget lub ActionSource2AttachedObjectTarget. Wydawałoby się, że można zdefiniować komponent w ten sposób:

<composite:interface>
    <composite:attribute name="action_1" targets="navbutton" required="true" method-signature="String f1()" />
</composite:interface>
<composite:implementation>
   <h:commandButton id="navbutton" value="NavButton" type="submit" />
</composite:implementation>

Niemniej jednak w implementacji Mojarra tak zdefiniowany komponent nie działa poprawnie. Przycisk jest wtedy renderowany ale jego naciśnięcie nie wywołuje żadnej akcji a w logach pojawia się ostrzeżenie: WARNING: JSF1092: Composite Component: nav.xhtml, library: navbutton : Unnecessary specification of the 'targets' attribute for composite attribute 'action_1'.  The 'targets' attribute is only necessary when the composite attribute is named 'action', 'actionListener', 'validator', or 'valueChangeListener'.

Na powyższych przykładach widać, że implementacja referencyjna nie jest jeszcze spójna z finalną wersją specyfikacji a co istotne przykłady do niej dołączone niekoniecznie muszą działać.

UAKTUALNIENIE: Niedziałający actionListener dla composite component jest znanym błędem podobno naprawionym w ciągu ostatnich dni, ale jeszcze nie zamkniętym.

Zacheusz Siedlecki

Komentarze

Chcesz coś napisać?





*