Dzisiejszą lekcję poświęcimy, żeby szerzej omówić widoki — a dokładniej użycie tagów i JSTL — oraz prawidłową obsługę formularzy (wraz z walidacją). JSTL (Java Standard Tag Library) oraz ogólnie taglibs (biblioteki tagów) są wykorzystywane do automatyzacji na poziomie widoków. Dzisiaj poznamy podstawowe — te do obsługi pętli i warunków. Formularze z kolei są używane na stronach do różnorodnych elementów — od wprowadzania danych, edycji np. profilu po składanie zamówień. Ich obsługa jest jednym z ważniejszych elementów z punktu widzenia interfejsu użytkownika.
TagLib
TagLib, czyli biblioteki tagów to artefakty, które zamiast standardowych klas udostępniają tagi, które możemy używać w widokach. Tagi takie mogą udostępniać rozmaite funkcjonalności — od instrukcji warunkowych i pętli, poprzez konstruowanie adresów URL aż po umieszczanie gotowych komponentów HTML na stronie. Możemy oczywiście samodzielnie napisać taką bibliotekę, ale nie będziemy tego omawiać w ramach kursu — ilość dostępnych funkcji i bibliotek jest tak duża, że najczęściej nie ma takiej potrzeby, osobiście nie spotkałem się jeszcze w pracy zawodowej żeby pisać takie biblioteki samodzielnie. Dodawanie TagLibów do widoku Aby dodać do naszego widoku konkretny taglib, na początku strony umieszczamy deklarację, np:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
Ta deklaracja zawiera dwa elementy, pierwszy to prefix — ponieważ nazwy tagów mogą się powtarzać, każdy taglib którego używamy musi mieć unikalny prefiks. I tak np. tag o nazwie abc z powyższej biblioteki przy tak skonstruowanej deklaracji używalibyśmy w postaci <c:abc … /> Drugi element to URI — uri jednoznacznie identyfikuje, której biblioteki chcemy użyć. Jeden artefakt (np. JSTL) może udostępniać wiele taglibów pod różnymi URI (/core , /fmt).
JSTL
JSTL to skrót od Java Standard Tag Library — początkowo nie było biblioteki standardowej, co powodowało sytuację, w której każdy deweloper tworzył własną wersję podstawowych elementów — warunków, pętli itp. Postanowiono to ujednolicić i tak narodził się JSTL — zbiór tagów wspierających najczęstsze operacje, pogrupowane wg oferowanej funkcjonalności. W tej lekcji skupimy się na JSTL core dostępnej pod przestrzenią nazw http://java.sun.com/jsp/jstl/core W poniższych przykładach zakładamy, że użyliśmy prefiksu ‘c’, a więc deklaracja wygląda następująco:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
pętla — c:forEach
Pętla ta pozwala nam przechodzić po całej kolekcji elementów (podobnie jak uproszczona składnia pętli for), przy czym zbiorem elementów może być np . ciąg znaków (zbiór literek) lub zbiór liczb. Tag ten posiada kilka atrybutów, których możemy używać w zależności od sytuacji, najważniejsze z nich to:
- items — kolekcja obiektów, jeśli chcemy przejść przez wszystkie obiekty
- var — nazwa zmiennej, która będzie widoczna wewnątrz tego tagu i dostępna w ramach EL
- step — krok, czyli co ile elementów się przemieszczamy (domyślnie: 1 — idziemy do przodu po każdym elemencie, możemy też iść do tyłu, przypisując tej zmiennej wartość ‑1)
- begin — początkowy element (indeks elementu)
- end — końcowy element (indeks elementu)
Poniżej kilka przykładów użycia:
<c:forEach var="i" begin="1" end="5">
Element ${i}<br />
</c:forEach>
<c:forEach var="element" items="${kolekcja}">
Element ${element}<br />
</c:forEach>
<c:forEach varStatus="status" var="element" items="${kolekcja}">
Element #${varstatus.index}: ${element}<br />
</c:forEach>
warunek — c:if
Tag ten ma podobne zastosowanie, co instrukcja if w języku Java, ale uwaga — za jej pomocą nie można zbudować konstrukcji if-else! Jeśli potrzebujemy funkcjonalność if-else, mamy dwie możliwości — dwukrotnie użyć tego tagu (raz negując warunek) lub skorzystać z c:choose (opis poniżej). Tag ten ma tylko jeden parametr o nazwie test, w którym przekazujemy test logiczny (najczęściej z użyciem ELa) Kilka przykładów użycia :
<c:if test="${pensja > 1000}">
Zarabaiam powyżej 1000!
</c:if>
<c:if test="${pensja > 2000}">
Zarabaiam powyżej 2000!
</c:if>
<c:if test="${!(pensja > 2000)}">
Zarabaiam nie więcej niż 2000!
</c:if>
wybór (wiele warunków) — c:choose
Tag choose pozwala wybrać jedną z kilku opcji. Jest on podobny do konstrukcji Switch w Javie, z tym wyjątkiem, że warunki dla poszczególnych elementów mogą być bardzo różnie zdefiniowane. Funkcjonalnie możemy więc myśleć o nim bardziej jak o konstrukcjach if-elseif-else. Wewnątrz tego tagu możemy użyć dwóch innych: c:when — ma jeden atrybut test, w którym podajemy test logiczny c:otherwise — jeśli żaden z powyższych warunków nie będzie prawdą wykonywana jest zawartość tego tagu. Tag ten jest opcjonalny. Przykład użycia:
<c:choose>
<c:when test="${pensja <= 2000}">
Zarabiasz bardzo mało
</c:when>
<c:when test="${pensja > 10000}">
Zarabiasz dużo
</c:when>
<c:otherwise>
Zarabiasz umiarkowanie
</c:otherwise>
</c:choose>
Podstawy ELa
Jak zapewne zauważyłaś, w wielu miejscach powyżej pojawia się odniesienie do EL — nieprzypadkowo, ponieważ jest to standard jeśli chodzi o wyrażenia i skrypty w ramach widoków. Z ELem mieliśmy do czynienia już w poprzedniej lekcji, kiedy wypisywaliśmy dane z modelu na widoku. W tej lekcji powiemy sobie o kolejnych 2 rzeczach — porównaniach, funkcjach oraz operatorach. EL możemy użyć w prawie dowolnym miejscu naszego widoku, zawsze używamy go rozpoczynając ${ i kończąc }. W razie potrzeby EL można dezaktywować w ramach danego widoku — używamy do tego dyrektywy isELIgnored:
<%@ page isELIgnored ="true|false" %>
Porównania
Do porównywania w EL możemy użyć właściwie identycznych operatorów jak w przypadku języka Java — mamy więc znaki mniejszości, większości, równości (także w EL używamy podwójnego ‘=’ !) oraz logiczne && i || (w przypadku EL nie ma operatorów binarnych — & i | !)
funkcje — fn:
Możemy też używać funkcji, które rozszerzają możliwości ELa. Funkcje są udostępniane także w postaci taglibów, jedną z podstawowych grup oferuje także JSTL. Możemy z nich skorzystać dodając taglib analogicznie jak powyżej:
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
Dzięki temu możemy następnie używać konstrukcji np.:
${fn:length(kolekcja)}
do wypisywania lub sprawdzania warunków.
empty i inne operatory
Język EL oferuje oczywiście standardowe operatory takie jak dodawanie, mnożenie, dzielenie, operatory logiczne and i or itp, ale udostępnia także dodatkowy operator — empty. Używamy go przed zmienną (np. ’empty lista’) do sprawdzenia czy dana zmienna jest pusta. Przy czym pusta, definiujemy jako null, nie posiadająca elementów (w przypadku kolekcji) lub ciąg znaków o zerowej długości.
Obsługa formularzy — obiekty transferowe
W poprzedniej lekcji obsługiwaliśmy formularze za pomocą adnotacji @RequestParam . Miało to swoje zalety (głównie prostota użycia) ale technika ta zdecydowanie utrudnia prace kiedy mamy rozbudowany formularz z dużą ilością pól albo kiedy chcemy wprowadzić walidację. Dzisiaj poznamy lepszy sposób obsługi formularzy — za pomocą obiektów transferowych. Wcześniej poznamy jednak wzorzec, czy bardziej konwencję, obsługi formularzy w aplikacjach webowych.
GET-POST-REDIRECT
Nazw odnosi się do ‘zachowań’ które realizujemy przy użyciu protokołu HTTP. Pierwszy krok to zapytanie typu GET, które powoduje wyświetlenie się strony z formularzem. Drugi krok to przesłanie formularza za pomocą metody POST. Po przesłaniu formularza jest on walidowany i w zależności od wyniku walidacji albo wyświetlamy formularz ponownie (w przypadku błędów) albo wysyłamy do przeglądarki informację o przekierowaniu (wtedy strona, na którą kierujemy, zostanie wyświetlona już z użyciem metody GET). Takie podejście ma kilka zalet, przede wszystkim standardowe podejście z użyciem podstawowych mechanizmów, co ułatwia implementację na poziomie kodu, ale także zabezpiecza przed przypadkowym ponownym wysłaniem formularza (mogłoby się tak zdarzyć, gdyby użytkownik np. kliknął przycisk wstecz w przeglądarce a nasz ostatni krok nie wykonywałby przekierowania). W Spring MVC przekierowanie obsługujemy zwracając z metody zamiast nazwy widoku adres URL poprzedzony frazą “redirect:”. Zobaczmy przykładowy kontroler zrealizowany w ten sposób (o adnotacji @Valid i klasie BindingResult powiemy sobie za moment).
@Controller
public class SampleController {
@RequestMapping("/formularz")
public String formularz(@ModelAttribute("form") @Valid FormularzDTO form, BindingResult result) {
if (result.hasErrors()) {
//formularz nie jest uzupełniony prawidłowo
return "widok.formularz";
} else {
//formularz wypełniony prawidłowo
return "redirect:/poFormularzu";
}
}
}
@Valid oraz BindingResult
Aby walidacja była możliwa, musimy dodać jedną linijkę do naszego pliku applicationContext.xml:
<mvc:annotation-driven />
Pozwala ona m.in. na automatyczną obsługę adnotacji służących do walidacji pól.
Do obsługi formularza użyjemy więc tzw. DTO (ang. data transfer object) — obiektu stworzonego tylko i wyłącznie w celu przesłania danych z jednego miejsca w drugie (tzn. nie zapisujemy jego stanu w bazie danych np. ). Dla każdego formularza utworzymy więc obiekt, który będzie miał pola odpowiadające polom naszego formularza. Dla przykładu formularz rejestracji (o polach login, email i wiek) będzie reprezentowany przez poniższą klasę:
public class FormularzDTO {
@NotEmpty
@Size(min=3)
private String imie;
@NotEmpty
@Email
private String email;
@Min(18)
private Integer wiek;
//gettery i settery
}
Następnie dodajemy obiekt tej klasy jako atrybut naszej metody z adnotacją @Valid oraz dodajemy także jako atrybut obiekt typu BindingResult. Adnotacja @Valid powoduje, że po pierwsze obiekt ten będzie powiązany przez Springa z otrzymanymi z formularza danymi, a po drugie zostaną użyte walidatory powiązane z adnotacjami pól. Aby korzystać z walidatorów dodajmy do naszego projektu zależność do org.hibernate:hibernate-validator Na powyższym przykładzie użyliśmy tylko podstawowych walidatorów — @NotEmpty, który sprawdza czy otrzymany ciąg znaków nie jest pusty. Przykłady innych walidatorów dostępnych w tej bibliotece to np:
- @Range(min=x, max=y) — sprawdza czy wartość jest z przedziału x, y
- @Size(min=x, max=y) — sprawdza długość ciągu znaków
- @DateTimeFormat(pattern=“MM/dd/yyyy”) — opisuje format daty, jaki chcemy akceptować
- @Past — sprawdza, czy data jest w przeszłości
więcej walidatorów znajdziesz w dokumentacji pakietu. Obiektu typu BindingResult wykorzystamy z kolei do sprawdzenia, czy formularz został prawidłowo zweryfikowany. Na tej podstawie zadecydujemy, czy odesłać użytkownikowi przekierowanie do innej strony czy też ponownie wyświetlić formularz z informacjami o błędach, jak na przykład w poniższej metodzie:
@Controller
public class SampleController {
@RequestMapping("/formularz")
public String formularz(HttpServletRequest request, @ModelAttribute("form") @Valid FormularzDTO form, BindingResult result) {
if (request.getMethod().equalsIgnoreCase("get") || result.hasErrors()) {
//formularz nie jest uzupełniony prawidłowo lub zapytanie GET
return "widok.formularz";
} else {
//formularz wypełniony prawidłowo
return "redirect:/poFormularzu";
}
}
}
Jak pewnie zauważyłaś, sprawdzamy tam także czy zapytanie zostało wysłane metodą GET. Takie podejście jest prostsze i krótsze, ale mało czytelne w bardziej skomplikowanych przypadkach. Drugie rozwiązanie (bardziej czytelne) rozbija obsługę formularza na dwie metody, tak jak na przykładzie poniżej, jedną do wyświetlania formularza, a drugą do do jego obsługi.
@Controller
public class SampleController {
@RequestMapping(value="/formularz", method=RequestMethod.GET)
public String formularz() {
return "widok.formularz";
}
@RequestMapping(value="/formularz", method=RequestMethod.POST)
public String obsluzFormularz(@ModelAttribute("form") @Valid FormularzDTO form, BindingResult result) {
if (result.hasErrors()) {
//formularz nie jest uzupełniony prawidłowo
return "widok.formularz";
} else {
//formularz wypełniony prawidłowo
return "redirect:/poFormularzu";
}
}
}
<form:form />
Zobaczmy teraz jak wygląda to od strony widoku. W normalnym HTMLu używamy tagów <form>, <input>, <select> etc. Aby poprawnie obsługiwać dane z formularza (tzn. w przypadku błedów żeby dane się nie traciły itp) zamienimy te tagi na odpowiedniki z tagliba Spring forms (<%@ taglib prefix=“form” uri=“http://www.springframework.org/tags/form”%>). Jedyną różnicą jest dodanie atrybutu path, który określa nam ścieżkę do pola (w klasie), które reprezentuje to pole (formularza). I tak np. pole typu input, które ma reprezentować pole o nazwie imie, będzie miało path po prostu imie. Dodatkowo mamy tag <form:error path=”..”>…</form:error> — ten tag powoduje wyświetlenie komunikatów błędów, które spowodowały niepoprawną walidację danego pola. Jest to bardzo przydatne do wyświetlania komunikatów o błędach przy poszczególnych polach. Komunikaty te możemy w prosty sposób przetłumaczyć też na język naszej aplikacji (o tym powiemy sobie w przyszłych lekcjach). Ponad to możemy użyć ścieżki *, która dopasowuje wszystkie błędy walidacji, co pozwala nam np. wyświetlić ramkę u góry strony z błędami formularza. Zobaczmy na przykładzie jak może wyglądać nasz formularz:
<form:form action="/formularz" modelAttribute="form" method="post">
Imię:
<form:input path="imie" id="imie"></form:input>
<form:errors path="imie" cssclass="error" />
<br />
Adres email:
<form:input path="email" id="email"></form:input>
<form:errors path="email" cssclass="error" />
<br />
Wiek:
<form:input path="wiek" id="wiek"></form:input>
<form:errors path="wiek" cssclass="error" />
<br />
<input type="submit" value="Wyślij formularz" />
</form:form>
Powyższy formularz wyświetli jednak błędy także w sytuacji, kiedy będziemy chcieli go wyświetlić po raz pierwszy. Aby temu zapobiec, sprawdzimy, czy zapytanie zostało wysłane metodą POST i tylko wtedy wyświetlimy komunikaty o błedach:
<form:form action="/formularz" modelAttribute="form" method="post">
Imię:
<form:input path="imie" id="imie"></form:input>
<c:if test="${pageContext.request.method=='POST'}"><form:errors path="imie" /></c:if>
<br />
Adres email:
<form:input path="email" id="email"></form:input>
<c:if test="${pageContext.request.method=='POST'}"><form:errors path="email" /></c:if>
<br />
Wiek:
<form:input path="wiek" id="wiek"></form:input>
<c:if test="${pageContext.request.method=='POST'}"><form:errors path="wiek" /></c:if>
<br />
<input type="submit" value="Wyślij formularz" />
</form:form>
Zadanie
Zmodyfikuj istniejący projekt tak, aby używał opisanych wyżej sposobów do dodawania kota do bazy danych, w szczególności:
- użyj JSTL i EL w utworzonych wcześniej widokach statycznych
- dodaj obiekt DTO i jego walidację
- dodaj kod obsługi formularza korzystający z utworzonego wcześniej DAO
- dodaj wymagane zależności do pom.xml (adnotacje walidujące, jstl)
Jeśli uważasz powyższą lekcję za przydatną, mamy małą prośbę: polub nasz fanpage. Dzięki temu będziesz zawsze na bieżąco z nowymi treściami na blogu ( i oczywiście, z nowymi częściami kursu Javy). Dzięki!