0

validate input before and after @save

asked 2021-06-05 06:23:16 +0800

Jtt gravatar image Jtt
107 4

updated 2021-06-08 22:44:42 +0800

As per design, I need to validate if some fields are complete or not in two separate methods, one to move to another part of the form and another when trying to finish.

I wanted to use ZK as much as possible since the code can get very verbose for this type of forms in Java. So, I used validators for my required fields, getting the expected result when trying to move the next part of the form with invalid inputs.

When trying to finish the form, I also need to check other fields in the backend and display a showNotification popup alongside the errorMessage in my inputs, but so far I'm not able to do so. My first idea was to "time" differently the validators depending on what action is taking, as follows (there are textbox too, with value instead of selectedItem)

<listbox selectedItem="@load(vm.list.item) @save(vm.list.item, before={'nextPage'}, after={'finish'}) @validator(vm.validator)" >

But the result wasn't the one desired.

I can do all these validations without* Validator, but that's exactly what I'm trying to avoid, and that's why I ask now, is there a way to let a command run BEFORE the validators or something like that?

I've tried using the snippet above, both commands on before, and 'finish' before 'nextPage' with the same not desired result: All the errorMessages but not the showNotification.

Any help would be appreciated! Thank you and have a great weekend!

EDIT

Further details:

When the validator triggers for the 'nextPage' command, all the invalid inputs should show the corresponding error message. This is working as intended.

When the validator triggers for the 'finish' command, an aditional validation takes place and, if said validation fails, a showNotification popup is created, along with the behaviour of the 'nextPage' command. I also need to add, this showNotification only happens when the 'finish' command is called and the 'nextPage' hasn't been triggered yet. Inside this so called 'nextPage' form, users can't leave until completition (All this events are independent and working as intenteded already), meaning that there's only one case when this behaviour differs. This is the one I'm having problems with. I thought of adding the additional behaviour on every validator, but that would mean having the showNotification triggered up to 6 times, and other methods seem too hacky to my liking.

Aditional code (I'm omiting divs and other attributes not really important):

<textbox name="required" value="@load(vm.uF.nombre ne null ? vm.uF.nombre : '') @save(vm.uF.nombre, before={'finish', 'nextPage'}) @validator(vm.validatorNombre)"/>
<textbox value="@load(vm.uF.nombres ne null ? vm.uF.nombres : '') @save(vm.uF.nombres)"/>
<textbox name="required" value="@load(vm.uF.primerApellido ne null ? vm.uF.primerApellido : '') @save(vm.uF.primerApellido, before={'finish', 'nextPage'}) @validator(vm.validatorApellido)"/>
<textbox placeholder="Segundo apellido" value="@load(vm.uF.segundoApellido ne null ? vm.uF.segundoApellido : '') @save(vm.uF.segundoApellido)"/>
<textbox name="required" value="@load(vm.uC.numNomina ne null ? vm.uC.numNomina : '') @save(vm.uC.numNomina, before={'finish', 'nextPage'}) @validator(vm.validatorNomina)"/>
<textbox placeholder="RFC *" name="required" value="@load(vm.uP.rfc ne null ? vm.uP.rfc : '') @save(vm.uP.rfc, before={'finish', 'nextPage'}) @validator(vm.validatorRfc)"/>
<bandbox id="bDepto" name="required" value="@load(vm.listaDeptos.itemSeleccionado.nombreTipo)">
    <bandpopup>
        <div>
            <listbox model="@load(vm.listaDeptos.items)" selectedItem="@load(vm.listaDeptos.itemSeleccionado) @save(vm.listaDeptos.itemSeleccionado, before={'finish', 'nextPage'}) @validator(vm.validatorDepto)">
                <template name="model">
                    <listitem>
                        <listcell>
                            <label/>
                        </listcell>
                    </listitem>
                </template>
            </listbox>
        </div>
    </bandpopup>
</bandbox>
<bandbox id="bPuesto" name="required" value="@load(vm.listaPuestos.itemSeleccionado.nombreTipo)">
    <bandpopup>
        <div>
            <listbox model="@load(vm.listaPuestos.items)" selectedItem="@load(vm.listaPuestos.itemSeleccionado) @save(vm.listaPuestos.itemSeleccionado, before={'finish', 'nextPage'}) @validator(vm.validatorPuestos)">
                <template name="model">
                    <listitem>
                        <listcell>
                            <label/>
                        </listcell>
                    </listitem>
                </template>
            </listbox>
        </div>
    </bandpopup>
</bandbox>

And the validators are:

public void validatorNombre(ValidationContext ctx) {
        Textbox input = (Textbox) ctx.getBindContext().getComponent();
        if (input.getValue() == null || input.getValue().isEmpty()) {
            input.setErrorMessage(REQUERIDO);
            ctx.setInvalid();
        }
        else if (!validaciones.unaPalabra(sn.normalize(input.getValue()))) {
            input.setErrorMessage(FORMATO);
            ctx.setInvalid();
        }
    }

    public void validatorApellido(ValidationContext ctx) {
        Textbox input = (Textbox) ctx.getBindContext().getComponent();
        if (input.getValue() == null || input.getValue().isEmpty()) {
            input.setErrorMessage(REQUERIDO);
            ctx.setInvalid();
        }
        else if (!validaciones.dosOMasPalabras(sn.normalize(input.getValue()))) {
            input.setErrorMessage(FORMATO);
            ctx.setInvalid();
        }
    }

    public void validatorRfc(ValidationContext ctx) {
        Textbox input = (Textbox) ctx.getBindContext().getComponent();
        if (input.getValue() == null || input.getValue().isEmpty()) {
            input.setErrorMessage(REQUERIDO);
            ctx.setInvalid();
        }
        else if (!validaciones.validarRFCFisica(sn.normalize(input.getValue()))) {
            input.setErrorMessage(FORMATO);
            ctx.setInvalid();
        }
        else if (personaRepository.findThisRfc(input.getValue().toUpperCase()).isPresent()) {
            Clients.showNotification(EXISTENTE, "warning", input, "end_center", 0, false);
            ctx.setInvalid();
        }
    }

    public void validatorNomina(ValidationContext ctx) {
        Textbox input = (Textbox) ctx.getBindContext().getComponent();
        String num = input.getValue();
        if (num == null || num.isEmpty()) {
            input.setErrorMessage(REQUERIDO);
            ctx.setInvalid();
        }
        else if (!num.matches("\\d+")) {
            input.setErrorMessage(FORMATO);
            ctx.setInvalid();
        }
        else if (colaboradoresRepository.findNumeroNomina(num.trim()).isPresent()) {
            Clients.showNotification(EXISTENTE, "warning", input, "end_center", 0, false);
            ctx.setInvalid();
        }
    }

    public void validatorDepto() {
        if(uC.getTiposDepartamentos() == null) {
            bDepto.setErrorMessage(REQUERIDO);
            bDepto.invalidate();
        }
    }

    public void validatorPuesto() {
        if(uC.getTiposPuestos() == null) {
            bPuesto.setErrorMessage(REQUERIDO);
            bPuesto.invalidate();
        }
    }

Note: I currently have them as void because I kept experimenting, but all these methods started as Validators as per the documentation.

Aditionally, the added behaviour for the 'finish' command is validated with the following code, that checks on the fields of the next page of the form.

if(ctx.getCommand().equals("finish") && !isSecondPageSet()) 
                    Clients.showNotification(INCOMPLETO, "error", null, "middle_center", 0, false);

EDIT 2:

This form is a popup window. In it, users have access to two pages. In the first one, users have 6 required inputs (Everyone with an independent validator). To access the second page, users must have filled the required inputs with no errors. They just can't finish at this point as the data in the second page is required.

In the second page, users have 4 fields to select nationality and current residence (one for national and one for foreigner for each pair). If neither nationality nor residence is selected, users can't go back to the first page nor finish the form.

The problem/complicated bit about 'finish' in the first page:

The finish button is always present in this window due a design choise upon wich I have no control over (Not to mention that at this point this template is standard in our webapp and changing it would be a lot of work for our front-end team). Because of this, users can use the 'finish' button at any given point, but we will prevent it working depending of the nature of each window.

In the case of the one I'm working on, the 'finish' button checks the completition of both pages of the form. If the first page is ready but the second page is not, then the showNotification shall pop up. That's the only case where that should happen. If the user tries to change pages without filling the required fields, the validators will do their work. As you mentioned, thanks to ctx.getCommand() I can control if the showNotification pops up or not for this case.

As a summary:

If user:

'finish' -> With filled form (Both pages) -> Proceeds
         -> With filled form (First page) -> showNotification
         -> Incorrectly filled form       -> invalidate + showNotification *
  • Since the validation for the Notification is handled by a command, the execution stops before it taking place, now I understand.

One workaround (the easiest one, at least) is to add my needed validation in every validator, meaning that it would be triggered up to 6 times, which while I doubt is particulary bad for performance, is not really an ideal solution.

This is how this window should work. As of right now, I used the workaround but I will change it as soon I find a better way to do it. It does the job since I check along the validators, is just the amount of time it has to be called what's bugging me.

By the way, thank you very much for the time you're putting helping me out. I really appreciate it.

delete flag offensive retag edit

3 Answers

Sort by ยป oldest newest most voted
1

answered 2021-06-09 11:05:26 +0800

hawk gravatar image hawk
3205 1 5
http://hawkphoenix.blogsp... ZK Team

According to this:

the 'finish' button checks the completion of both pages of the form

I think the requirements are:

  1. ensure each field passes validation
  2. validate 2nd page's fields

ensure each field pass validation

By specifying a validator and save-before on each field, you can ensure each field passes validation before executing "finish". <textbox name="required" value="@load(vm.uF.nombre ne null ? vm.uF.nombre : '') @save(vm.uF.nombre, before={'finish', 'nextPage'}) @validator(vm.validatorNombre)"/>

Each validaor applied on a textbox e.g. vm.validatorNombre just validate one value only, no need to validate 2nd page's fields.

validate 2nd page's fields

Validate the 2nd page's field in vm.completeValidator. So you don't repeat the validating in a validator for each field.

<listbox selectedItem="@load(vm.list.item) @save(vm.list.item, before={'nextPage', 'finish'}) @validator(vm.completeValidator)" >

Summary

  • If a user invokes "finish" with invalid fields, before={'finish', 'nextPage'} and each validator will stop him.
  • If a user invokes "finish" with all correct fields, he will invoke vm.completeValidator to validate the 2nd page's field.

If this still doesn't solve the problem, then I think I need a running example. Since I think it's hard to describe the whole thing with natural language.

link publish delete flag offensive edit

Comments

Yes! It never occured to me I could just ignore the validation context and just add a dummy save with the completeValidator if you can't use another field. In my case, a read-only bandbox does the trick. It's a bit different than your answer, but it did point me in the right track. Thank you!

Jtt ( 2021-06-09 23:31:26 +0800 )edit
1

answered 2021-06-07 14:08:52 +0800

hawk gravatar image hawk
3205 1 5
http://hawkphoenix.blogsp... ZK Team

Validation is always performed before executing a command, see http://books.zkoss.org/zk-mvvm-book/9.5/databinding/commandbinding.html

ZK invokes a validator before saving a value into a ViewModel, you can control the timing of saving by before or after (one of them). So you can't specify before and after at one save binding expression.

Your case sounds complicate. I'm not sure I understand you correctly. According to your description, what I understand are:

a. validate upon 2 commands 'nextPage" and 'finish'

b. you already validate some required fields with validators for 'nextPage' like:

<textbox value="@bind(vm.list.item.oneProperty) @validator(vm.fieldValidator)
"/>

c. you want to validate other fields for command 'finish'

I guess vm.validator is the validator to validate multiple fields. So you can write:

<listbox selectedItem="@load(vm.list.item) @save(vm.list.item, before={'nextPage', 'finish'}) @validator(vm.validator)" >

So when a users click 'finish', it still invokes vm.validator.

d. display a showNotification popup alongside the errorMessage in my inputs

A validator can store an error message in the message holder with a key and show it with the key in a zul.

See "Validation Message Holder" at http://books.zkoss.org/zk-mvvm-book/9.5/data_binding/validator.html to show a validation message with a key.

If my understanding is wrong, please share us more details including more zul or java source.

link publish delete flag offensive edit

Comments

Hello! Thanks for your insight. Your understanding is mostly right, but I see I wasn't clear enough. I will add more details and a bit more code. Overall, I need the same validations when triggering 'finish' and 'nextPage', but I need added behaviour in the particular case of 'finish'

Jtt ( 2021-06-07 21:54:36 +0800 )edit
1

answered 2021-06-08 15:43:09 +0800

hawk gravatar image hawk
3205 1 5
http://hawkphoenix.blogsp... ZK Team

updated 2021-06-08 15:59:38 +0800

What I understand

...When the validator triggers for the 'finish' command, an additional validation takes place

It looks like you already know how to determine which command is invoked in a validator

ctx.getCommand().equals("finish")

Your key requirement

...this showNotification only happens when the 'finish' command is called and the 'nextPage' hasn't been triggered yet.

I don't understand this, I suppose the whole process is: a user will invoke "nextPage" several time and invoke "finish" in the last time:

"nextPage" -> "nextPage" -> "finish"

Do you mean a user can invoke "finish" directly without invoking "nextPage" first?

Maybe let me understand the overall flow about "nextPage" and "finish" will be helpful. Your case is quite complicated.

Do you mean you want to know the status the 'finish' command is called and the 'nextPage' hasn't been triggered yet inside a validator?

This kind of status is an application context that a validator doesn't know by default. So you have to set a flag isNextPageTriggered=true each time when users invoke "nextPage" and pass the flag isNextPageTriggered into the validator as an parameter.

link publish delete flag offensive edit

Comments

Hello! Thank one more time for your time! i added more info about how the form works. Yes, users can invoke 'finish' before 'nextPage' due a design/UI choice. 'Finish' validates the same as 'nextPage' and more. That 'more' is what should invoke the showNotification I need.

Jtt ( 2021-06-08 22:48:58 +0800 )edit
Your answer
Please start posting your answer anonymously - your answer will be saved within the current session and published after you log in or create a new account. Please try to give a substantial answer, for discussions, please use comments and please do remember to vote (after you log in)!

[hide preview]

Question tools

Follow

RSS

Stats

Asked: 2021-06-05 06:23:16 +0800

Seen: 20 times

Last updated: Jun 09 '21

Support Options
  • Email Support
  • Training
  • Consulting
  • Outsourcing
Learn More