0

Issue with form validation middle bean and list<Object> as attribut

asked 2015-08-10 14:14:51 +0800

WilliamB gravatar image WilliamB
1609 1 6

Greetings,

I've a bean:

public class ExempleBean{
    private String label;
    private Date dateStart;
    private List<SubBean> listSubBeans= new ArrayList<SubBean>();
}

It contains a list of SubBean :

public class SubBean {
    private String labelSub;
    private Date dateSub;
}

Now I want to map this to a form using a Middle Bean.

<div form="@id('exempleMiddleBean') @load(vm.exempleBean) @save(vm.exempleBean, before='validate') @validator(vm.exempleBeanValidator)">
    <textbox value="@bind(exempleBean.label)"/> 
    <datebox value="@bind(exempleBean.dateStart)"/>
    <grid model="@init(exempleBean.listSubBeans)  @template('subBeanTemplate')" >
        <template name="subBeanTemplate" var="subBean">
            <row>
                <cell>
                    <textbox value="@bind(subBean.labelSub)"/>  
                </cell>
                <cell>
                    <datebox value="@bind(subBean.dateSub)" />
                </cell>
            </row>
        </template>
    </grid>
</div>

When I try to get the list in my validator to check the value of each SubBean attribut I can't find it with the default way :

pCtx.getProperties("listSubBeans")[0].getValue()

It returns null. When I inspect i debug, i see that all 3 fields of ExempleBean are shown in _loadFieldNames but in _saveFieldNames I only see Label and dateStart. Probably because the list isn't directly impacted by the change only its element attributs are.

Tried to access the list in my validator with

Map<String, Property> beanProps = ctx.getProperties(pCtx.getProperty().getBase());
((SimpleForm) beanProps.get(".").getValue()).getField("listSubBeans");

And I got a list but I found out that the original ExempleBean also has his listSubBeans item with modified attribut before going through validation ...

Seems inappropriate that the middleware item modify the object from the original bean, no?

delete flag offensive retag edit

Comments

zk 8 should have resolved this issue. Can you try there?

chillworld ( 2015-08-11 10:11:00 +0800 )edit

Thanks Chill, nop can't atm as I'm nearing production phase. Did a work around by cloning my original object so I could compare the value during validation.

WilliamB ( 2015-08-12 08:36:55 +0800 )edit

Monday back at work, can test only then. Greetz chill.

chillworld ( 2015-08-12 11:29:49 +0800 )edit

I know I'm asking a lot but did you get a chance to check it out Chill?

WilliamB ( 2015-09-10 14:24:49 +0800 )edit

not yet, I was pretty sick (home for 3 weeks). Back @work on Monday.

chillworld ( 2015-09-10 15:59:05 +0800 )edit

3 Answers

Sort by ยป oldest newest most voted
0

answered 2015-09-15 07:46:43 +0800

chillworld gravatar image chillworld flag of Belgium
5322 4 9
https://github.com/chillw...

updated 2015-09-15 08:45:49 +0800

Oke, played around today and did have a lot of this fiddle of cor3000.

I didn't create a fiddle but here are the complete sources :

SubBean.java:

public class SubBean {
    private static int counter = 0;
    private String labelSub;
    private Date dateSub;
    private final int id = counter++;


    public SubBean(String labelSub, Date dateSub) {
        this.labelSub = labelSub;
        this.dateSub = dateSub;
    }

    public String getLabelSub() {
        return labelSub;
    }

    public void setLabelSub(String labelSub) {
        this.labelSub = labelSub;
    }

    public Date getDateSub() {
        return dateSub;
    }

    public void setDateSub(Date dateSub) {
        this.dateSub = dateSub;
    }

    public int getId() {
        return id;
    }

    public String getUniqueToken() {
        return "subBeanToken" + id;
    }

ExampleBean.java:

public class ExampleBean{
    private String label;
    private Date dateStart;
    private List<SubBean> listSubBeans= new ArrayList<SubBean>();

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public Date getDateStart() {
        return dateStart;
    }

    public void setDateStart(Date dateStart) {
        this.dateStart = dateStart;
    }

    public List<SubBean> getListSubBeans() {
        return listSubBeans;
    }

    public void setListSubBeans(List<SubBean> listSubBeans) {
        this.listSubBeans = listSubBeans;
    }

validation.zul:

<?xml version="1.0" encoding="UTF-8"?>
<window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('be.chillworld.web.ViewModel')" validationMessages="@id('vmsgs')">
    <div id="dv" form="@id('exampleBean') @load(vm.exampleBean) @save(vm.exampleBean, before='save') @validator(vm.exampleBeanValidator)">
        <textbox value="@bind(exampleBean.label)"/> 
        <datebox value="@bind(exampleBean.dateStart)"/>
        <grid model="@init(exampleBean.listSubBeans)  @template('subBeanTemplate')" >
            <template name="subBeanTemplate">
                <row form="@id('fx2') @load(each) @save(each, before='save') @validator(vm.exampleSubBeanValidator)">
                    <cell>
                        <textbox value="@bind(fx2.labelSub)"/>  
                    </cell>
                    <cell>
                        <datebox value="@bind(fx2.dateSub)" />
                    </cell>
                    <cell>
                        <label value="@load(vmsgs[fx2.uniqueToken])"/>
                    </cell>
                </row>
            </template>
        </grid>
    </div>
    <button label="save" onClick="@command('save')"/>
    <label value="@load(vmsgs[dv])"/>
</window>

ViewModel.java:

public class ViewModel {

    private ExampleBean exampleBean;

    private final Validator exampleBeanValidator = new AbstractValidator() {

        @Override
        public void validate(ValidationContext ctx) {
            String fault = "";
            String label = (String) ctx.getProperties("label")[0].getValue();
            if (label == null || "".equalsIgnoreCase(label.trim())) {
                fault += "Label may not be empty";
            }
            Date dateStart = (Date) ctx.getProperties("dateStart")[0].getValue();
            if (dateStart == null) {
                if (!"".equals(fault)) {
                    fault += "\n";
                }
                fault += "Date may not be empty";
            }
            if (!"".equals(fault)) {
                System.out.println("added invalidation");
                addInvalidMessage(ctx, fault);
            }
        }
    };

    private final Validator exampleSubBeanValidator = new AbstractValidator() {

        @Override
        public void validate(ValidationContext ctx) {
            String fault = "";
            Form form = (Form) ctx.getProperty().getValue();
            String label = (String) form.getField("labelSub");
            if (label == null || "".equalsIgnoreCase(label.trim())) {
                fault += "Label may not be empty";
            }
            Date dateStart = (Date)form.getField("dateSub");
            if (dateStart == null) {
                if (!"".equals(fault)) {
                    fault += "\n";
                }
                fault += "Date may not be empty";
            }
            if (!"".equals(fault)) {
                SubBean subBean = (SubBean) ctx.getProperty().getBase();
                addInvalidMessage(ctx,subBean.getUniqueToken(), fault);
            }
        }
    };

    @Init
    public void init() {
        exampleBean = new ExampleBean();
        exampleBean.setDateStart(new Date());
        exampleBean.setLabel("ExampleBean");
        List<SubBean> subBeans = new ArrayList<SubBean>();
        subBeans.add(new SubBean("first sub", new Date()));
        subBeans.add(new SubBean("second sub", new Date()));
        subBeans.add(new SubBean("third sub", new Date()));
        subBeans.add(new SubBean("fourth sub", new Date()));
        subBeans.add(new SubBean("fifth sub", new Date()));
        exampleBean.setListSubBeans(subBeans);
    }

    @Command
    public void save() {
        Clients.showNotification(exampleBean.getListSubBeans().get(0).getLabelSub());
    }

    public ExampleBean getExampleBean() {
        return exampleBean;
    }

    public void setExampleBean(ExampleBean exampleBean) {
        this.exampleBean = exampleBean;
    }

    public Validator getExampleBeanValidator() {
        return exampleBeanValidator;
    }

    public Validator getExampleSubBeanValidator() {
        return exampleSubBeanValidator;
    }

}

As you can see, I did create a token in the SubBean class.
This just need to be a unique string for each SubBean, so the validator can reference to the right validation message.

Greetz chill.

link publish delete flag offensive edit
0

answered 2015-09-15 08:02:25 +0800

WilliamB gravatar image WilliamB
1609 1 6

updated 2015-09-15 09:07:53 +0800

Hey Chill,

I already had the mechanism to display the error message on the correct line using the "Stats.index" of the template foreach loop as the unique identifier.

Until you pointed me to this Jira, I was just using a replicate of my initial list and working on it in the validator as the object were changed by reference.

The issue with working with the bean directly instead of the form is I cannot see if the field is in error before the user click validate. If that's the case, the value of the bean is NOT changed and so when I enter the validator I was working on the old value instead of the one shown on screen.

Playing with the subform seems like a good idea and I've been toying with it yesterday. But for one I need to do the validation in ONE validator, or at least find a way to have them communicate as I've cross data validation and I've to display specific message if nothing was changed.

At first I put in both form the validator, but the result is I'm going through my validator both time. So I removed the validator from the subform.

Now I'm getting my "datesub" field but it's in there twice for each line, once with a Simpleform base and once with an ExempleBean base. The order seems weird too, not matchhing the order of my list.

Anyway that's where I'm at, trying to find a pattern to "hide" this behind the curtain ;)

Sorry for the wall of text and thanks for putting me on the subform path.

EDIT:

Well I just tried something but it doesn't work.

for (Property prop : ctx.getProperties("dateStart"") {
    if (prop.getBase() instanceof ExempleBean ) {
        mapDateDebutProperties.put(((ExempleBean ) prop.getBase()).getId(), prop);
    }
}

I'm able to match my bean with its id, but the Property with base ExempleBean is never showing WrongValuePropertyImpl if the field is in error before the validation.

So I've to use the simpleForm and find a way to tie it even if the Id is not really a field.

Lol this is Hell, would have been so much easier if the order matched but it changes everytime...

link publish delete flag offensive edit

Comments

I will play a little further

chillworld ( 2015-09-15 08:47:51 +0800 )edit

Just updated my prev message with the first try this morning lol ...

WilliamB ( 2015-09-15 09:08:15 +0800 )edit

2 validators where 1 validator can acces first one is also good?

chillworld ( 2015-09-15 09:21:51 +0800 )edit

Depends how, the main form (outside of the template) need to know what's all been modified to do cross validation. I'm guessing by going 2 validators, I would have to make a specific one, problem is, I've the same use case on almost ALL my form ...

WilliamB ( 2015-09-15 09:54:18 +0800 )edit

I'm about to give in and use this http://books.zkoss.org/wiki/ZKDeveloper'sReference/MVVM/DataBinding/FormBinding#InitializewithForm_Object just so i can know which field matches which object. But damn that's gonna be quite ugly ... Thanks again for all your help man ...

WilliamB ( 2015-09-15 09:55:12 +0800 )edit
0

answered 2015-09-15 10:13:19 +0800

chillworld gravatar image chillworld flag of Belgium
5322 4 9
https://github.com/chillw...

updated 2015-09-15 10:15:24 +0800

New answer because the first one could help other people who can use 2 validators.

SubBean and ExampleBean and the zul page stays the same.

MineAbstractValidator.java:

public abstract class MineAbstractValidator extends AbstractValidator{

    @Override
    public void addInvalidMessage(ValidationContext ctx, String message) {
        super.addInvalidMessage(ctx, message); 
    }
}

This is just making the protected method public so we can acces it later.

MineValidator.java:

public abstract class MineValidator extends AbstractValidator {

    private ValidationContext mainCtx;

    public ValidationContext getMainCtx() {
        return mainCtx;
    }

    public void setMainCtx(ValidationContext mainCtx) {
        this.mainCtx = mainCtx;
    }
}

ViewModel.java:

public class ViewModel {

    private ExampleBean exampleBean;

    private final MineValidator exampleSubBeanValidator = new MineValidator() {

        @Override
        public void validate(ValidationContext ctx) {
            // check main.
            String fault = "";
            String label = (String) getMainCtx().getProperties("label")[0].getValue();
            if (label == null || "".equalsIgnoreCase(label.trim())) {
                fault += "Label may not be empty";
            }
            Date dateStart = (Date) getMainCtx().getProperties("dateStart")[0].getValue();
            if (dateStart == null) {
                if (!"".equals(fault)) {
                    fault += "\n";
                }
                fault += "Date may not be empty";
            }
            if (!"".equals(fault)) {
                exampleBeanValidator.addInvalidMessage(getMainCtx(), fault);
            }

            // check sub.
            fault = "";
            Form form = (Form) ctx.getProperty().getValue();
            label = (String) form.getField("labelSub");
            if (label == null || "".equalsIgnoreCase(label.trim())) {
                fault += "Label may not be empty";
            }
            dateStart = (Date) form.getField("dateSub");
            if (dateStart == null) {
                if (!"".equals(fault)) {
                    fault += "\n";
                }
                fault += "Date may not be empty";
            }
            if (!"".equals(fault)) {
                SubBean subBean = (SubBean) ctx.getProperty().getBase();
                addInvalidMessage(ctx, subBean.getUniqueToken(), fault);
            }
        }
    };

    private final MineAbstractValidator exampleBeanValidator = new MineAbstractValidator() {

        @Override
        public void validate(ValidationContext ctx) {
            exampleSubBeanValidator.setMainCtx(ctx);
        }
    };

    @Init
    public void init() {
        exampleBean = new ExampleBean();
        exampleBean.setDateStart(new Date());
        exampleBean.setLabel("ExampleBean");
        List<SubBean> subBeans = new ArrayList<SubBean>();
        subBeans.add(new SubBean("first sub", new Date()));
        subBeans.add(new SubBean("second sub", new Date()));
        subBeans.add(new SubBean("third sub", new Date()));
        subBeans.add(new SubBean("fourth sub", new Date()));
        subBeans.add(new SubBean("fifth sub", new Date()));
        exampleBean.setListSubBeans(subBeans);
    }

    @Command
    public void save() {
        Clients.showNotification(exampleBean.getListSubBeans().get(0).getLabelSub());
    }

    public ExampleBean getExampleBean() {
        return exampleBean;
    }

    public void setExampleBean(ExampleBean exampleBean) {
        this.exampleBean = exampleBean;
    }

    public Validator getExampleBeanValidator() {
        return exampleBeanValidator;
    }

    public Validator getExampleSubBeanValidator() {
        return exampleSubBeanValidator;
    }
}

In this case I set the invalidation message of the main one multiple times if it's empty.
I don't know exactly what you need to check, but normally the latest addinvalidmessage will be shown. (if you don't add one in a specific case, also no problem)

Greetz chill.

link publish delete flag offensive edit

Comments

Thanks Chill. I've to do database validation etc while cross checking the data from subbean and the data from main bean (and other subbean). So I don't only need to get the error message. I'll try and see if i can apply your exemple to mine but I'm not sure it's enough. Either way thanks a lot!

WilliamB ( 2015-09-15 10:24:51 +0800 )edit

your welcome

chillworld ( 2015-09-15 10:58:21 +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
1 follower

RSS

Stats

Asked: 2015-08-10 14:14:51 +0800

Seen: 76 times

Last updated: Sep 15 '15

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