#10 — Spring mvc — formularze i widoki

By 25 October 2014 March 1st, 2017 Kurs Javy

Dzisiejszą lekcję poświęcimy, żeby szerzej omówić wido­ki — a dokład­niej uży­cie tagów i JSTL — oraz praw­idłową obsługę for­mu­la­rzy (wraz z wal­i­dacją). JSTL (Java Stan­dard Tag Library) oraz ogól­nie taglibs (bib­liote­ki tagów) są wyko­rzysty­wane do automatyza­cji na poziomie widoków. Dzisi­aj poz­namy pod­sta­wowe — te do obsłu­gi pętli i warunk­ów. For­mu­la­rze z kolei są uży­wane na stronach do różnorod­nych ele­men­tów — od wprowadza­nia danych, edy­cji np. pro­filu po składanie zamówień. Ich obsłu­ga jest jed­nym z ważniejszych ele­men­tów z punk­tu widzenia inter­fe­j­su użytkownika.

Lekcja

TagLib

TagLib, czyli bib­liote­ki tagów to arte­fak­ty, które zami­ast stan­dar­d­owych klas udostęp­ni­a­ją tagi, które może­my uży­wać w widokach. Tagi takie mogą udostęp­ni­ać roz­maite funkcjon­al­noś­ci — od instrukcji warunk­owych i pętli, poprzez kon­struowanie adresów URL aż po umieszczanie gotowych kom­po­nen­tów HTML na stron­ie. Może­my oczy­wiś­cie samodziel­nie napisać taką bib­liotekę, ale nie będziemy tego omaw­iać w ramach kur­su — ilość dostęp­nych funkcji i bib­liotek jest tak duża, że najczęś­ciej nie ma takiej potrze­by, oso­biś­cie nie spotkałem się jeszcze w pra­cy zawodowej żeby pisać takie bib­liote­ki samodziel­nie. Dodawanie TagLibów do widoku Aby dodać do naszego widoku konkret­ny taglib, na początku strony umieszcza­my deklarację, np:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

Ta deklarac­ja zaw­iera dwa ele­men­ty, pier­wszy to pre­fix — ponieważ nazwy tagów mogą się pow­tarzać, każdy taglib którego uży­wamy musi mieć unikalny pre­fiks. I tak np. tag o nazwie abc z powyższej bib­liote­ki przy tak skon­struowanej deklaracji uży­wal­ibyśmy w postaci <c:abc … /> Dru­gi ele­ment to URI — uri jed­noz­nacznie iden­ty­fiku­je, której bib­liote­ki chce­my użyć. Jeden arte­fakt (np. JSTL) może udostęp­ni­ać wiele taglibów pod różny­mi URI (/core , /fmt).

JSTL

JSTL to skrót od Java Stan­dard Tag Library — początkowo nie było bib­liote­ki stan­dar­d­owej, co powodowało sytu­ację, w której każdy dewelop­er tworzył włas­ną wer­sję pod­sta­wowych ele­men­tów — warunk­ów, pętli itp. Postanowiono to ujed­no­li­cić i tak nar­o­dz­ił się JSTL — zbiór tagów wspier­a­ją­cych najczęst­sze oper­ac­je, pogrupowane wg ofer­owanej funkcjon­al­noś­ci. W tej lekcji skupimy się na JSTL core dostęp­nej pod przestrzenią nazw http://java.sun.com/jsp/jstl/core W poniższych przykładach zakładamy, że użyliśmy pre­fik­su ‘c’, a więc deklarac­ja wyglą­da następująco:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
pętla — c:forEach

Pęt­la ta pozwala nam prze­chodz­ić po całej kolekcji ele­men­tów (podob­nie jak uproszc­zona skład­nia pętli for), przy czym zbiorem ele­men­tów może być np . ciąg znaków (zbiór literek) lub zbiór liczb. Tag ten posi­a­da kil­ka atry­butów, których może­my uży­wać w zależnoś­ci od sytu­acji, najważniejsze z nich to:

  • items — kolekc­ja obiek­tów, jeśli chce­my prze­jść przez wszys­tkie obiekty
  • var — nazwa zmi­en­nej, która będzie widocz­na wewnątrz tego tagu i dostęp­na w ramach EL
  • step — krok, czyli co ile ele­men­tów się przemieszcza­my (domyśl­nie: 1 — idziemy do przo­du po każdym ele­men­cie, może­my też iść do tyłu, przyp­isu­jąc tej zmi­en­nej wartość ‑1)
  • begin — początkowy ele­ment (indeks elementu)
  • end — koń­cowy ele­ment (indeks elementu)

Poniżej kil­ka 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 podob­ne zas­tosowanie, co instrukc­ja if w języku Java, ale uwa­ga — za jej pomocą nie moż­na zbu­dować kon­strukcji if-else! Jeśli potrze­bu­je­my funkcjon­al­ność if-else, mamy dwie możli­woś­ci — dwukrot­nie użyć tego tagu (raz negu­jąc warunek) lub sko­rzys­tać z c:choose (opis poniżej). Tag ten ma tylko jeden para­metr o nazwie test, w którym przekazu­je­my test log­iczny (najczęś­ciej z uży­ciem ELa) Kil­ka 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ć jed­ną z kilku opcji. Jest on podob­ny do kon­strukcji Switch w Javie, z tym wyjątkiem, że warun­ki dla poszczegól­nych ele­men­tów mogą być bard­zo różnie zdefin­iowane. Funkcjon­al­nie może­my więc myśleć o nim bardziej jak o kon­strukc­jach if-elseif-else. Wewnątrz tego tagu może­my użyć dwóch innych: c:when — ma jeden atry­but test, w którym poda­je­my test log­iczny c:otherwise — jeśli żaden z powyższych warunk­ów nie będzie prawdą wykony­wana jest zawartość tego tagu. Tag ten jest opcjon­al­ny. 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 miejs­cach powyżej pojaw­ia się odniesie­nie do EL — nieprzy­pad­kowo, ponieważ jest to stan­dard jeśli chodzi o wyraże­nia i skryp­ty w ramach widoków. Z ELem mieliśmy do czynienia już w poprzed­niej lekcji, kiedy wyp­isy­wal­iśmy dane z mod­elu na widoku. W tej lekcji powiemy sobie o kole­jnych 2 rzeczach — porów­na­ni­ach, funkc­jach oraz oper­a­torach. EL może­my użyć w praw­ie dowol­nym miejs­cu naszego widoku, zawsze uży­wamy go rozpoczy­na­jąc ${ i kończąc }. W razie potrze­by EL moż­na deza­k­ty­wować w ramach danego widoku — uży­wamy do tego dyrek­ty­wy isELIgnored:

<%@ page isELIgnored ="true|false" %>
Porównania

Do porówny­wa­nia w EL może­my użyć właś­ci­wie iden­ty­cznych oper­a­torów jak w przy­pad­ku języ­ka Java — mamy więc zna­ki mniejs­zoś­ci, więk­szoś­ci, równoś­ci (także w EL uży­wamy pod­wójnego ‘=’ !) oraz log­iczne && i || (w przy­pad­ku EL nie ma oper­a­torów bina­rnych — & i | !)

funkcje — fn:

Może­my też uży­wać funkcji, które rozsz­erza­ją możli­woś­ci ELa. Funkc­je są udostęp­ni­ane także w postaci taglibów, jed­ną z pod­sta­wowych grup ofer­u­je także JSTL. Może­my z nich sko­rzys­tać doda­jąc taglib ana­log­icznie jak powyżej:

<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

Dzię­ki temu może­my następ­nie uży­wać kon­strukcji np.:

${fn:length(kolekcja)}

do wyp­isy­wa­nia lub sprawdza­nia warunków.

empty i inne operatory

Język EL ofer­u­je oczy­wiś­cie stan­dar­d­owe oper­a­to­ry takie jak dodawanie, mnoże­nie, dzie­le­nie, oper­a­to­ry log­iczne and i or itp, ale udostęp­nia także dodatkowy oper­a­tor — emp­ty. Uży­wamy go przed zmi­en­ną (np. ’emp­ty lista’) do sprawdzenia czy dana zmi­en­na jest pus­ta. Przy czym pus­ta, defini­u­je­my jako null, nie posi­ada­ją­ca ele­men­tów (w przy­pad­ku kolekcji) lub ciąg znaków o zerowej dłu­goś­ci.

Obsługa formularzy — obiekty transferowe

W poprzed­niej lekcji obsługi­wal­iśmy for­mu­la­rze za pomocą adno­tacji @RequestParam . Miało to swo­je zale­ty (głównie pros­to­ta uży­cia) ale tech­ni­ka ta zde­cy­dowanie utrud­nia prace kiedy mamy rozbu­dowany for­mu­la­rz z dużą iloś­cią pól albo kiedy chce­my wprowadz­ić wal­i­dację. Dzisi­aj poz­namy lep­szy sposób obsłu­gi for­mu­la­rzy — za pomocą obiek­tów trans­fer­owych. Wcześniej poz­namy jed­nak wzorzec, czy bardziej kon­wencję, obsłu­gi for­mu­la­rzy w aplikac­jach webowych.

GET-POST-REDIRECT

Nazw odnosi się do ‘zachowań’ które real­izu­je­my przy uży­ciu pro­tokołu HTTP. Pier­wszy krok to zapy­tanie typu GET, które powodu­je wyświ­etle­nie się strony z for­mu­la­rzem. Dru­gi krok to przesłanie for­mu­la­rza za pomocą metody POST. Po przesła­niu for­mu­la­rza jest on wali­d­owany i w zależnoś­ci od wyniku wal­i­dacji albo wyświ­et­lamy for­mu­la­rz ponown­ie (w przy­pad­ku błędów) albo wysyłamy do przeglą­dar­ki infor­ma­cję o przekierowa­niu (wtedy strona, na którą kieru­je­my, zostanie wyświ­et­lona już z uży­ciem metody GET). Takie pode­jś­cie ma kil­ka zalet, przede wszys­tkim stan­dar­d­owe pode­jś­cie z uży­ciem pod­sta­wowych mech­a­nizmów, co ułatwia imple­men­tację na poziomie kodu, ale także zabez­piecza przed przy­pad­kowym ponownym wysłaniem for­mu­la­rza (mogło­by się tak zdarzyć, gdy­by użytkown­ik np. kliknął przy­cisk wstecz w przeglą­darce a nasz ostat­ni krok nie wykony­wał­by przekierowa­nia). W Spring MVC przekierowanie obsługu­je­my zwraca­jąc z metody zami­ast nazwy widoku adres URL poprzed­zony frazą “redi­rect:”. Zobaczmy przykład­owy kon­trol­er zre­al­i­zowany w ten sposób (o adno­tacji @Valid i klasie Bind­in­gRe­sult 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 wal­i­dac­ja była możli­wa, musimy dodać jed­ną lin­ijkę do naszego pliku applicationContext.xml:

<mvc:annotation-driven />

Pozwala ona m.in. na automaty­czną obsługę adno­tacji służą­cych do wal­i­dacji pól.

Do obsłu­gi for­mu­la­rza uży­je­my więc tzw. DTO (ang. data trans­fer object) — obiek­tu stwor­zonego tylko i wyłącznie w celu przesła­nia danych z jed­nego miejs­ca w drugie (tzn. nie zapisu­je­my jego stanu w bazie danych np. ). Dla każdego for­mu­la­rza utworzymy więc obiekt, który będzie miał pola odpowiada­jące polom naszego for­mu­la­rza. Dla przykładu for­mu­la­rz rejes­tracji (o polach login, email i wiek) będzie reprezen­towany 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ęp­nie doda­je­my obiekt tej klasy jako atry­but naszej metody z adno­tacją @Valid oraz doda­je­my także jako atry­but obiekt typu Bind­in­gRe­sult. Adno­tac­ja @Valid powodu­je, że po pier­wsze obiekt ten będzie pow­iązany przez Springa z otrzy­many­mi z for­mu­la­rza dany­mi, a po drugie zostaną użyte wal­ida­to­ry pow­iązane z adno­tac­ja­mi pól. Aby korzys­tać z wal­ida­torów doda­jmy do naszego pro­jek­tu zależność do org.hibernate:hibernate-validator Na powyższym przykładzie użyliśmy tylko pod­sta­wowych wal­ida­torów — @NotEmpty, który sprawdza czy otrzy­many ciąg znaków nie jest pusty. Przykłady innych wal­ida­torów dostęp­nych w tej bib­liotece to np:

  • @Range(min=x, max=y) — sprawdza czy wartość jest z przedzi­ału x, y
  • @Size(min=x, max=y) — sprawdza dłu­gość ciągu znaków
  • @DateTimeFormat(pattern=“MM/dd/yyyy”) — opisu­je for­mat daty, jaki chce­my akceptować
  • @Past — sprawdza, czy data jest w przeszłości

więcej wal­ida­torów zna­jdziesz w doku­men­tacji paki­etu. Obiek­tu typu Bind­in­gRe­sult wyko­rzys­tamy z kolei do sprawdzenia, czy for­mu­la­rz został praw­idłowo zwery­fikowany. Na tej pod­staw­ie zade­cy­du­je­my, czy odesłać użytkown­ikowi przekierowanie do innej strony czy też ponown­ie wyświ­etlić for­mu­la­rz z infor­ma­c­ja­mi 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ś, sprawdza­my tam także czy zapy­tanie zostało wysłane metodą GET. Takie pode­jś­cie jest prost­sze i krót­sze, ale mało czytelne w bardziej skom­p­likowanych przy­pad­kach. Drugie rozwiązanie (bardziej czytelne) rozbi­ja obsługę for­mu­la­rza na dwie metody, tak jak na przykładzie poniżej, jed­ną do wyświ­et­la­nia for­mu­la­rza, 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 ter­az jak wyglą­da to od strony widoku. W nor­mal­nym HTM­Lu uży­wamy tagów <form>, <input>, <select> etc. Aby poprawnie obsługi­wać dane z for­mu­la­rza (tzn. w przy­pad­ku błedów żeby dane się nie traciły itp) zamien­imy te tagi na odpowied­ni­ki z tagli­ba Spring forms (<%@ taglib prefix=“form” uri=“http://www.springframework.org/tags/form”%>). Jedyną różnicą jest dodanie atry­bu­tu path, który określa nam ścieżkę do pola (w klasie), które reprezen­tu­je to pole (for­mu­la­rza). I tak np. pole typu input, które ma reprezen­tować pole o nazwie imie, będzie miało path po pros­tu imie. Dodatkowo mamy tag <form:error path=”..”>…</form:error> — ten tag powodu­je wyświ­etle­nie komu­nikatów błędów, które spowodowały niepoprawną wal­i­dację danego pola. Jest to bard­zo przy­datne do wyświ­et­la­nia komu­nikatów o błę­dach przy poszczegól­nych polach. Komu­nikaty te może­my w prosty sposób przetłu­maczyć też na język naszej aplikacji (o tym powiemy sobie w przyszłych lekc­jach). Pon­ad to może­my użyć ścież­ki *, która dopa­sowu­je wszys­tkie błędy wal­i­dacji, co pozwala nam np. wyświ­etlić ramkę u góry strony z błę­da­mi for­mu­la­rza. 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 for­mu­la­rz wyświ­etli jed­nak błędy także w sytu­acji, kiedy będziemy chcieli go wyświ­etlić po raz pier­wszy. Aby temu zapo­biec, sprawdz­imy, czy zapy­tanie zostało wysłane metodą POST i tylko wtedy wyświ­etlimy komu­nikaty 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

Zmody­fikuj ist­nieją­cy pro­jekt tak, aby uży­wał opisanych wyżej sposobów do dodawa­nia kota do bazy danych, w szczególności:

  • użyj JSTL i EL w utwor­zonych wcześniej widokach statycznych
  • dodaj obiekt DTO i jego walidację
  • dodaj kod obsłu­gi for­mu­la­rza korzys­ta­ją­cy z utwor­zonego wcześniej DAO
  • dodaj wyma­gane zależnoś­ci do pom.xml (adno­tac­je walidu­jące, jstl)
zip Pobierz rozwiązanie tego zadania

Licencja Creative Commons

Jeśli uważasz powyższą lekcję za przy­dat­ną, mamy małą prośbę: pol­ub nasz fan­page. Dzię­ki temu będziesz zawsze na bieżą­co z nowy­mi treś­ci­a­mi na blogu ( i oczy­wiś­cie, z nowy­mi częś­ci­a­mi kur­su Javy). Dzięki!