10 lutego 2007

requiredMessage i resource-bundle - udoskonalona kontrola komunikatów w JSF 1.2

Co nie dotknę, to coś nowego. Po prostu strach cokolwiek dotykać! ;-)

Po przeczytaniu sekcji 3.5 Encyjni obserwatorzy i metody przechwytujące specyfikacji JPA postanowiłem przygotować aplikację internetową, która pomogłaby mi utrwalić przeczytany materiał. Skorzystałem z pomocy NetBeans IDE 5.5.1 i GlassFish v2.

Rozpocząłem od stworzenia projektu i zabrałem się za tworzenie stron JSF. Pierwsza strona umożliwiała podanie nazwy użytkownika w polu h:inputText. Poszło gładko. Zdefiniowałem komponent zarządzany user, który miał przechowywać informację o wprowadzonym identyfikatorze użytkownika. Nie korzystałem z NetBeans Visual Web Pack 5.5, więc wszystko robiłem ręcznie, aż w pewnym momencie, edytując stworzony faces-config.xml dostrzegłem, że jestem w JSF 1.2 (!) Pomyślałem, że jest to dobra pora, aby zapoznać się z kilkoma dobrodziejstwami JSF 1.2, co zaraz przerodziło się w wykorzystanie gdzie się dało Unified EL, tj. tam, gdzie w poprzedniej wersji należało skorzystać z h:outputText teraz można zastąpić prostszymi konstrukcjami znanymi z JSP i JSTL, czyli ${nazwaKomponentuZarządzanego.atrybut}. Kiedy przyszło do przypisania komponentu zarządzanego user do h:inputText zauważyłem atrybut, którego z pewnością nie było w poprzedniej wersji JSF 1.1 - requiredMessage.

Zmiana komunikatów w JSF 1.1 związanych z obsługą błędów kontroli poprawności wprowadzonych danych nie była ani łatwa, ani zaawansowana (aż trudno uwierzyć, że twórcom JSF udało się to połączyć, zazwyczaj jest albo jedno, albo drugie). Zmiana komunikatów błędów polegała na dostarczeniu pliku komunikatów z odpowiednimi identyfikatorami, których wartości były zmodyfikowanymi komunikatami (w tym i tłumaczeniami). Bardziej zaawansowane modyfikacje komunikatów błędów wymagały zastosowania pewnych sztuczek (więcej w artykule Hansa Bergstena Designing and Implementing Web Application Interfaces).

W JSF 1.2 udostępniono atrybut requiredMessage. Wartość atrybutu requiredMessage jest komunikatem błędu, który będzie wyświetlany w przypadku niepodania przez użytkownika obowiązkowej wartości w polu typu UIInput (wymaganie nałożone przez zastosowanie atrybutu required dla kontrolek h:inputText, h:inputSecret, h:inputHidden i h:inputTextArea), bądź identyfikatorem komunikatu w aplikacyjnym pliku komunikatów. Komunikat (egzemplarz klasy FacesMessage) będzie umieszczony w FacesContext zamiast domyślnego.

<h:inputText id="name" value="#{user.name}" required="true" requiredMessage="Nie podano nazwy użytkownika" />

W powyższym przykładzie, jeśli użytkownik nie poda nazwy użytkownika wyświetlony zostanie komunikat Nie podano nazwy użytkownika (oczywiście samo wyświetlenie można zrealizować z pomocą h:message albo h:messages).

Na uwagę zasługuje jednak inna cecha requiredMessage - możliwość wskazania na właściwy komunikat w pliku komunikatów.

<h:inputText id="name" value="#{user.name}" required="true" requiredMessage="#{validationMessages.usernameMissing}" />

W ten sposób, w zależności od aktywnego ustawienia regionalnego (ang. locale), wyświetlony zostanie właściwy komunikat błędu w aktywnym języku. Ale co to jest validationMessages?

Poniżej znajduje się wycinek pliku konfiguracyjnego JSF - faces-config.xml:

<application>
<resource-bundle>
<base-name>pl.jaceklaskowski.ewt.faces.ValidationMessages</base-name>
<var>validationMessages</var>
</resource-bundle>
</application>
Zmienna validationMessages reprezentuje mapę komunikatów (z kluczami będącymi identyfikatorami komunikatów), a pl.jaceklaskowski.ewt.faces.ValidationMessages jest nazwą pliku komunikatów bez rozszerzenia .properties (w formacie java.util.Properties) o zawartości:

usernameMissing=Nie podano nazwy użytkownika!

Specyfikacja JSF 1.2 opisuje atrybut jako współpracujący z f:loadBundle, jednakże nie udało mi się tego powiązać. W dodatku znalazłem wiadomość na forum JavaServer Faces, która wskazywałaby, że taka możliwość nie ma prawa działać - requiredMessage and EL?.

Poza tym podobną funkcjonalność można wykorzystać do komunikatów związanych z mechanizmem konwersji i kontroli poprawności (walidacji) za pomocą atrybutów converterMessage oraz validatorMessage, odpowiednio.

Dobre wprowadzenie do zmian w JSF 1.2 znajduje się w artykule Web Tier to Go With Java EE 5: Summary of New Features in JavaServer Faces 1.2 Technology. Na lekturę specyfikacji JSF 1.2 przyjdzie jeszcze pora ;-)

3 komentarze:

  1. Może, ja coś źle robiłem, ale musze powiedzieć, ze mam bardzo złe wspomnienia z pracy z plikiem messages.properties w JSF ze względu na niemożność używania "."
    w kluczach komunikatów. Nie dosć ze zanim do tego doszedłem to straciłem sporo czasu to jeszcze substytut "." w postaci "_" (który wykorzystałem) nie podobał mi się.

    Czy w wersji 1.2 zostało to poprawione? czy może ja jednak źle coś robiłem i to zawsze można było używać kluczy w postaci "user.firstName", "user.lastName"?

    Pozdrawiam

    OdpowiedzUsuń
  2. Witaj Michał!

    Przyznaję, nie znałem początkowo odpowiedzi, ale ostatnio natrafiłem na kilka wiadomości dot. JSF, które wskazały właściwą drogę! (wniosek: warto śledzić grupy dyskusyjne). Pamiętam również, że temat był poruszany na p.c.l.java i nie potrafiłem wtedy odpowiedzieć na to pytanie. Tym razem, dla Ciebie, postanowiłem powalczyć i ostatecznie wyjaśnić temat.

    Zgodnie ze specyfikacją JSF 1.2 strona 272 mamy:

    <f:loadBundle>

    Load a resource bundle localized for the locale of the current view, and expose it (as a Map)
    in the request attributes for the current request.

    Co oznacza, że załadowanie komunikatów w JSF dowolnym sposobem - loadBundle (niezalecane) i resource-bundle (preferowane) - to sprowadzenie ich postaci identyfikator-komunikat w pliku properties do...typu Map.

    Co to oznacza? Załóżmy, że załadowaliśmy plik za pomocą konstrukcji:

    <resource-bundle>
    <base-name>pl.jaceklaskowski.et.faces.Messages</base-name>
    <var>msg</var>
    </resource-bundle>

    Dostęp do komunikatu o identyfikatorze firstName będzie za pomocą konstrukcji:

    ${msg.firstName}

    natomiast do komunikatu user.firstName za pomocą:

    ${msg['user.firstName']}

    Działa! Spradziłem. Dzięki Michał za zwrócenie na to uwagi, co zdopingowało mnie w końcu do sprawdzenia tego!

    Jacek

    OdpowiedzUsuń
  3. No tak, teraz wszystko jasne.

    To ja Ci dziękuje Jacku, nareszcie się wszystko wyjaśniło.

    A co do zdania że warto śledzić grupy dyskusyjne to zgadzam się w całej rozciągłości. Przekonuje się do tego zwłaszcza teraz gdy przygotowuje się do prezentacji GWT.

    OdpowiedzUsuń