Dwie różne walidacje na jednym formie

0

Cześć,

Mam taki formularz, który może mieć dwa stany. Zanim dojdzie do pełnego wysłania dokumentu można go zapisać. Coś na wzór maili roboczych :)

I do zapisu dokumentu roboczego potrzebne są pola np.:
Imię
Nazwisko
PESEL

ale, zeby kliknac drugi przycisk i wyslac dokument trzeba juz miec wypełnione:
Imie
Nazwisko
PESEL,
dokument tożsamości
adres

Jak to rozwiązać za pomocą jsf, primefaces?

0

Spring boot używa JSR-303 do walidacji. Wystarczy zatem oznaczyć pola by były walidowane w ramach odpowiednich grup.

0

To można zrobić na kilka sposobów. Jednym z nich jest if'ek walidujący w akcji (na początku metody). I wysłanie message do danego przycisku (musisz zrobić binding dla UIComponent, aby wiedzieć przy którym komponencie wyświetlić message) informujący, co poszło nie tak. Po wysłaniu message przerywasz akcje: nie są spełnione warunki. Jak walidacja może być przeprowadzona w czasie akcji (czyli po kliknięciu w przycisk) to może być najprostszy sposób. Poza tym taka metoda się przydaje jak czasem trzeba zawołać jakiś WebService albo zrobić zapytanie do bazy: standardowe walidatory słabo się tutaj sprawdzają.

Podany "walidator" zadziała dopiero, gdy standardowe walidatory (np. długości pól / Bean Validation) nie wykryją błędów (cykl życia JSF).

Po if'kach wypada dopisać unit-testy, ale to nie jest problem.

0

@margor90 ok pokombinuje.

jeszcze potrzebuje pomysłu jak zrobić walidację dwóch przycisków.

np. oba są wymagane, gdy drugie nie jest wypełnione.

Czyli jeśli A wypełnione to B niewymagane. I odwrotnie :)

0

@margor90 @Koziołek
Fragment widoku (id forma i tabów)

    <h:form id="wniosekForm">
            <p:tabView id="tabView">
                <p:tab title="Wniosek" id="wniosekTab">

Fragment widoku (blok messages)

                        <p:messages showDetail="true" showSummary="true"/>

public void zatwierdz(WniosekDTO wniosekDTO) {
        Set<ConstraintViolation<WniosekDTO>> violations = validator.validate(wniosekDTO, ZatwierdzWniosekGroup.class);
        for (ConstraintViolation<WniosekDTO> violation : violations) {
            addErrorMessage(formIdPrefix + violation.getPropertyPath(), violation.getMessage());
            System.out.println(formIdPrefix + " " + violation.getPropertyPath() + " " + violation.getMessage());
        }
        if (violations.isEmpty()) {
            //save czegos tam
            addInfoMessage("", "success");
        } else {
            addErrorMessage("", violations.size() + " error(s)");
        }

    }

    private void addErrorMessage(String id, String msg) {
        addMessage(id, FacesMessage.SEVERITY_ERROR, "Error", msg);
    }

    private void addInfoMessage(String id, String msg) {
        addMessage(id, FacesMessage.SEVERITY_INFO, "Info", msg);
    }

    private void addMessage(String clientId, FacesMessage.Severity severity, String summary, String detail) {
        FacesMessage message = new FacesMessage(severity, summary, detail);
        FacesContext.getCurrentInstance().addMessage(clientId, message);
    }


Pola oznaczane mam mniej więcej tak:

@NotNull(groups = ZatwierdzWniosekGroup.class)
    @NotBlank(groups = ZatwierdzWniosekGroup.class)
    private String nazwa= "Przykładowa wartość nazwy";

I wygląda to tak, że pojawia mi się wszystko w bloku messages, ale nie podświetla mi pól na czerwono i w bloku messages nie ma nazw pól tylko jest tak:

    Errormay not be null
    Errormay not be null
    Errormust be true
    Errormust be true
    Errormust be true
    Errormay not be null
    ErrorMusi być zaznaczone
    Errormay not be empty
    Errormust be true
    Error9 error(s)

Co mam nie tak?

Jak zrobię tak:

private void addMessage(String clientId, FacesMessage.Severity severity, String summary, String detail) {
        FacesMessage message = new FacesMessage(severity, summary, detail);
        FacesContext.getCurrentInstance().addMessage(clientId, message);
        UIViewRoot uiViewRoot = FacesContext.getCurrentInstance().getViewRoot();
        if (!clientId.isEmpty()) {

            UIComponent component = uiViewRoot.findComponent(clientId);
            if (component instanceof UIInput) {
                UIInput uiInput = (UIInput) component;
                uiInput.setValid(false);

            }
        }
    }

To nie działa mi potem button wyczyść :) I zostają errory.

0

@margor90 może łatwiej będzie mi wytłumaczyć jaki efekt chcę uzyskać.

Otóż mam jeden formularz. I chcę zrobić dwa rodzaje walidacji:

  • podstawowy
  • rozszerzony

Jeśli kliknę commandButton "Zapisz" to sprawdza mi podstawową walidację.
Jeśli kliknę commandButton "Wyślij" to sprawdza mi walidację rozszerzoną.

Dodatkowo niektóre pola prócz wymagany/niewymagany mają takie dane jak:
PESEL, które ma być walidowany jak to PESEL :)

Jest jeszcze taka kwestia. Załóżmy, że w formularzu mamy nadawcę i odbiorcę.

Każdy z nich korzysta z modelu daneIdentyfikacyjne.
I w podstawowej walidacji wymagany jest pesel tylko dla nadawcy, a dla odbiorcy już nie. I wtedy jest lipa z adnotacjami na modelu, bo musiałbym rozdzielić modele na np. OdbiorcaDTO i NadawcaDTO i jednemu dać anotację, a drugiemu nie.

Każde odpalenie walidacji ma skutkować pojawieniem się zbiorczego podsumowania w <p:messages> i dodatkowo poszczególne pole ma być podświetlone na czerwono, ale już bez wiadomości przy polu.

Używam SpringBoot i JSF i Primefaces.

0

Rozdzielenie modelu jest w tym przypadku jak najbardziej OK. W swojej domenie masz byty:

  • Nadawca
  • Odbiorca

Warto zamodelować je oddzielnie ponieważ reprezentują dwie osobne rzeczy. Ma to dwie podstawowe zalety

Po pierwsze możesz traktować takie klasy jako zupełnie niezwiązane ze sobą, bo takimi są, dzięki czemu możesz swobodnie zmieniać np. walidację w jednej bez wpływu na drugą.
Po drugie otrzymujesz możliwość pisania kodu w oparciu o typy. W ten sposób już na etapie kompilacji sprawdzana jest poprawność całych przepływów i można bardzo łatwo uniknąć sytuacji gdzie np. pod adres zamieszkania wstawiasz adres zameldowania (patrz http://niebezpiecznik.pl/post/pko-wyslalo-karty-platnicze-na-zle-adresy/ )

Oczywiście pojawia się problem powtarzanego kodu. Tu z pomocą przychodzi dziedziczenie, które pozwala wynieść do nadklasy części wspólne, a w podklasach przechowywać znaczenie (nadawca, odbiorca) oraz meta konfigurację .

0

@Koziołek
To może tylko rozjaśnię o co chodzi z tym rozdzieleniem.

Załóżmy, że Odbiorca nie musi mieć wymaganego peselu. I teraz, moje modele wygladaja mniej więcej tak:

OdbiorcaDTO:

private String nazwaFirmy;
private DaneTozsamosciDTO daneTozsamosciDTO;

NadwacaDTO:

private String nazwaFirmy;
private DaneTozsamosciDTO daneTozsamosciDTO;

I załóżmy, że DaneTozsamoscieDTO zawieraja pole: PESEL, nrDokumentToz, Imie, Nazwisko i jeśli jeden z nich ma mieć pole PESEL wymagane a drugi nie. To muszę to rozdzielić też na dwa modele DaneTozsamosciNadawcyDTO i DaneTozsamosciOdbiorcyDTO i w jednym dam adnotację @NotNull a w drugim nie.

No i pozostaje reszta pytań. Jak zrobić takie walidację JSR303 - dwie różne na jednym formularzu. Tak by podsuowanie wyświetlało się w <p:messages/> a pola na formularzu podświetliły się na czerwono tak jak w <p:message> ale bez labelki.

0

Użyj grup.

class DaneTozsamosciDTO {

    @NotNull(groups=Required.class)
    Pesel pieseł;
}
class NadwacaDTO{

    private String nazwaFirmy;

    @Valid(groups=Required.class)
     private DaneTozsamosciDTO daneTozsamosciDTO;
}

i następnie uruchamiasz walidator z odpowiednią grupą.

W ten sposób, warunek zostanie sprawdzony tylko w ramach grupy do której jest przypisany.

0

@Koziołek
Właśnie uruchamiałem w ten sposób i nie do końca mi to działało. Przede wszystkim pola nie podświetlają się na czerwono i w <p:messages> traci kontakt z jakie pole pod jaką walidacją jest.

0

To tu się poddam, bo za słabo u mnie z JSyFami...

0

Czy ten print:

System.out.println(formIdPrefix + " " + violation.getPropertyPath() + " " + violation.getMessage());

w metodzie

sprawdź

wyświetla Ci poprawne ścieżki?
Pokaż jeszcze kod formularza (szablon JSF).

0
student pro napisał(a):

(...)
w metodzie

sprawdź

</del> (...)

sorry, w metodzie

zatwierdź
0

Poradziłem sobie, ale pozostał mi jeden problem:

Mam na tym formularzu listę i używam ui:repeat i nie może mi znaleźć komponentu na stronie.

Przykład na działającym polu:
9d5ab3b7ee.png
i w kodzie:

            UIViewRoot viewRoot = FacesContext.getCurrentInstance().getViewRoot();
            LOG.debug("PATH: " + formIdPrefix + constraintViolation.getPropertyPath());
            String path = formIdPrefix + constraintViolation.getPropertyPath();
            path = path.replace(".", ":").replace("[",":").replace("]","");
            LOG.debug(path);
            UIComponent component = viewRoot.findComponent(path);

i findComponent z tym pathem co zaznaczyłem na obrazku znajduje, ale dla listy to już nie działa:

0f5c06d333.png

Jakieś pomysły?!

0

Pomógł stackoverflow:
http://stackoverflow.com/questions/18358604/how-to-use-el-with-uirepeat-var-in-id-attribute-of-a-jsf-component

You can use EL in the id attribute of a JSF component, but the EL variable has to be available during view build time, while the JSF component tree is to be built. However, the ui:repeat runs during view render time, while the HTML output is to be generated based on JSF component tree. The <ui:repeat var> is not available during view build time and #{class2.name} evaluates to null which totally explains the error you got. That it works in <h:outputText> is because it runs during view render time.

Zamieniłem ui:repeat na c:forEach ponieważ mają inny cykl życia.

1 użytkowników online, w tym zalogowanych: 0, gości: 1