#10 – Spring mvc – formularze i widoki

By 25 października 2014Kurs Javy
Wpis-Header

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.

Lekcja

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=HttpMethod.GET)
	public String formularz() {
		return "widok.formularz";
	}

	@RequestMapping(value="/formularz", method=HttpMethod.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)
zip Pobierz rozwiązanie tego zadania

Licencja Creative Commons

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!

  •  
  •  
  •  
  •  
  •  
  • Michal

    Cześć,

    Czy jest możliwość wystawienia plików projektu do pobrania? Ucząc się z kursu często nie wiadomo do jakiego pliku w jakim miejscu w projekcie dany skrypt należy, jak również nie wiadomo jakie dependency są wrzucone. Jak również mam uwagę do if w kontrolerze na wejście warunku instrukcja haserrors zwraca false, więc od razu przechodzi do poFormularzu.

    • Cześć,
      pracujemy nad rozwiązaniami i ich publikacją, wiem, że zajmuje to zdecydowanie za długo, ale staramy się to pogodzić z innymi obowiązkami.
      Jeśli chodzi o formularz to faktycznie, w przypadku niewysłania formularza może wystąpić taki błąd. Lekcja zostanie za niedługo uzupełniona o poprawioną wersję :) Dzięki za zwrócenie uwagi!

  • mk

    Cześć, mam problem, że po dodaniu do applicationContext.xml (tak jak w zamieszczonym rozwiązaniu) tomcat zwraca błąd 404. Usunięcie tej linii rozwiązuje problem. Nie mam pojęcia, w czym jest problem…

  • Hubert

    Mam problem, którego nie mogę za nic w świecie rozwiązać. Może pomożecie? W waszym projekcie właściwie jest podobny problem. Otóż nie wiem dlaczego, ale request POST zamienia polskie znaki na jakiś dziwny format. Jeśli mam normalny formularz bez springowych tagów wszystko jest ok, odbieram dane przesłane metodą POST i są polskie znaki. Jednak gdy są przesyłane przez formularz z tagami polskie znaki zamieniają się na krzaczki niepodobne do żadnych znanych mi kodowań. Próbowałem już wszystkiego, nawet zmieniałem konfigurację Tomcata, żeby wszystkie requesty były przesyłane w kodowaniu UTF-8. Nie pomogło. Filtr kodowania UTF-8 w web.xml także nie pomógł. Ustawianie przed komentarzem request.setCharacterEncoding także nie pomaga. Nie mam już pomysłu. Podkreślam, że w normalnym formularzu jest ok. Możecie sprawdzić w projekcie koty, jest dokładnie ten sam problem.

  • Ok, postaramy się w tym tygodniu sprawdzić co i jak i podpowiedzieć ;)

    • Tom

      Dzięki wielkie! :) Czekam z niecierpliwością na info i walczę z tym dalej ;)

    • Tom

      Problem rozwiązałem! :) Okazało się, że muszę dodać w widokach JSP coś takiego

      I miałem złe kodowanie w bazie! Ale wszystko śmiga :)

      • Gratulujemy! Super, że sobie poradziłeś :)

  • Jachu

    A z kolei ostatnie wersje formularza zadziałały dopiero o dodaniu
    stworzenia instancji dla @ModelAttribute(„form”)
    tj.
    FormularzDTO

    @ModelAttribute(„form”)
    public UserDTO getUser() {
    return new UserDTO();
    }

  • Ola

    Cześć,
    mam pytanie. Co można zrobić, aby te błędy wyświetlały się po Polsku a nie po Angielsku?
    Pozdrawiam

    • Cześć,
      najprostszy sposób to ustawienie pola message dla adnotacji walidujących, np:
      @NotNull(message=”To pole nie może być puste”) String email;

      Minusem jest to, że komunikaty będą tylko w języku polskim. ‚Prawidłowy’ sposób, który pozwala na obsługę wielu języków jest poprzez bundle. Po pierwsze musisz w swojej aplikacji ustawić domyślny język na język polski, np poprzez taką konfigurację XML:

      Po drugie musisz przetłumaczyć komunikaty – ponieważ spring korzysta ze standardowego API do walidacji w Javie, wpisz w google „jsr303 i18n” albo „jsr303 localization”, kilka przykładowych stron:
      http://www.codebulb.ch/2015/05/jsf-validation-localization-i18n-by-example-part-1.html (nieco chaotyczna, ale z przykładami)
      http://stackoverflow.com/questions/12280800/how-does-it-work-with-bean-validation-messages-and-i18n-in-jsf2 (nie ma znaczenia, że dotyczy JSF – walidacja i tłumaczenia są częścią Javy i zarówno Spring jak i JSF korzystają z nich identycznie)

      • Ola

        Dziękuję za odpowiedź :)

  • Krzysztof Ambroziak

    Cześć
    Proszę o wytłumaczenie jednej adnotacji. W blogu napisaliście funkcję, która obsługuje formularz z walidacją tak

    public String obsluzFormularz(@ModelAttribute(„form”) @Valid FormularzDTO form, BindingResult result) {

    natomiast na stronie springa https://spring.io/guides/gs/validating-form-input/ tudzież innych tutoriali, na które zaglądam, metoda, która również obsługuje formularz z walidacją wygląda podobnie ale parametr metody pozbawiony jest adnotacji @ModelAttribute i wszystko działa:

    public String checkPersonInfo(@Valid PersonForm personForm, BindingResult bindingResult) {

    Proszę zatem czy moglibyście wytłumaczyć działanie adnotacji @ModelAttribute? Czy sama adnotacja @Valid to za mało?

    • Adnotacja ta pozwala nam określić, jak będzie się w modelu nazywał nasz formularz. Nie jest wymagana, ale wtedy zostanie przydzielona domyślna nazwa (nazwa parametru metody). Ta z kolei jest używana jeśli budujesz formularze na widokach – tag ma atrybut modelAttribute (lub commandName), w którym podajemy właśnie tą nazwę.

  • qvc

    Cześć – jedno pytanie co do przykładów – czy w konwencji ELa nie powinno się używać dla bezpieczeństwa lt, gt, eq zamiast , == (z tego co pamiętam przy xhtml strict jest taki wymóg)

    • Cześć, xhtml jest standardem związanym z tym, co trafia do przeglądarki – sposób, w jaki ta strona powstaje po stronie serwera (a tylko tam tagi EL są ‚widoczne’) nie jest przez standard określony. W zależności od IDE może zdarzyć się sytuacja, w której znaki większości i mniejszości będą interpretowane jako część html (czy xhtml) – jest to jednak raczej kwestia edytora a nie samego EL. Osobiście nie spotkałem się nigdy z tego rodzaju zaleceniami czy wręcz konwencją :)

  • gosc

    Cześć, jest jakaś możliwość logów czy czegoś w tym stylu? Nie wiem dlaczego mi nie działa aplikacja i teraz szukam co może być nie tak- czy może coś w widokach, czy może coś w controllerze, czy może coś w xml-ach.. W sumie przejrzałem wszystko ale wiadomo- człowiek bywa głupkowaty i pewnie coś przegapiłem.. Ale najbardziej mi brakuje możliwości typu : wrzucam link w przeglądarkę i teraz chciałbym zobaczyć w którym mniej więcej miejscu coś się krzaczy- czy w ogóle trafia do controllera, czy się sypie już na tłumaczeniu adresów…

    • gosc

      …jeszcze jedno pytanko- czy normalną sytuacją jest pracowanie z STS na zasadzie : świeci mi się 20 warningów na temat pierdół Javy, 8 errorów bo html nie ma jakichś powiązań do tagów.. Kilka osób mi wspominało, że nawet w profesjonalnej robocie słyszą od kogoś zaawansowanego (szczególnie gdy pracują na eclipse lub jakiejś jego mutacji) : ‚weź ten błąd olej i pracuj sobie spokojnie jakby go nie było’.
      Czy z STSem powinno się pracować na podobnych zasadach? Właśnie ściągnąłem wasz przykład i w sumie czerwono/pomarańczowych krzaków to cała masa, ale na serwerze się odpala i działa. Szczerze- doprowadza do trochę do obłędu, bo już nie wiadomo ostatecznie co powinno niepokoić a co jest w porządeczku..