Search code examples
javamvvmdata-bindingviewmodelzk

MVVM binding, get previous form values before saving


Problem

I have a Person object, that can be edited in a popup window.

I need to know, when the form is saved, if some values have changed.

I am using a global variable to store the initial state of the object but it seems that variable is updated too and I don't understand why.


Code

Take a look at the code, it is more explicit (I have removed irrelevant code so it is easier to read) :

Model (Person.java) :

@Entity
@Table(name = "person")
public class Person {

    @Column
    private String lastName;

    @Column
    private String firstName;

}

View model (PersonVm.java) :

@VariableResolver(org.zkoss.zkplus.spring.DelegatingVariableResolver.class)
public class PersonVm {

    private Person person;
    private static Person initialPerson;
    private PersonGroupingVm personGroupingModel;

    @Init
    public void init(@ExecutionArgParam("person") Person pers, @ContextParam(ContextType.VIEW) Component view) {
        Selectors.wireComponents(view, this, false);
        if (pers != null) 
            person = pers;
        setPersonGroupingModel(new PersonGroupingVm(personMgr.getPersons(), new PersonComparator(), false));
    }

    @Command
    @NotifyChange("person")
    public void openEditPersonDialog(@BindingParam("person") Person pers) {
        initialPerson = pers;
        Map<String, Object> args = new HashMap<String, Object>();
        args.put("person", pers);
        Executions.createComponents(".../editPerson.zul", null, args);
    }

    @Command
    @NotifyChange("person")
    public void savePerson() {
        Clients.alert(person.equals(initialPerson) + "");
        Clients.alert(person.getFirstName());
        Clients.alert(initialPerson.getFirstName());
        // person and initialPerson both contain new values !
    }

}

View (persons.zul) :

<vlayout apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('ch.myorg.vm.PersonVm')">
    <grid model="@load(vmp.personGroupingModel)">
        ...
        <template name="model" var="pers">
            <row>
                <label value="@load(pers.firstName)"/>
                <label value="@load(pers.lastName)"/>
                <button onClick="@command('openEditPersonDialog', person=pers)"/>
            </row>
        </template>
    </grid>
</vlayout>

View (editPersonDialog.zul) :

<window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('ch.myorg.vm.PersonVm')">
    <div form="@id('p') @load(vm.person) @save(vm.person, before='savePerson')">
        <textbox value="@bind(p.firstName)"/>
        <textbox value="@bind(p.lastName)"/>
        <button label="${labels.action.validate}" onClick="@command('savePerson')"/>
    </div>
</window>

Why did my 2 objects (person and initialPerson) contain the new values? Only the person object is bound.


Solution

  • Quick answer : Java threats Objects by reference, this means the 2 vars pointing to same memory location.

    Now up to some solutions.

    First solution is fetching a new Object from the DB as the original object.
    But, if you do this => this should be more code for the server and not for the GUI part.

    Second solution is to use a form.
    You are only intressted if some data has changed => the form has a dirtystatus witch you could enable/disable the save button with.
    So if the button is enabled you are sure that there are some changes.
    If you need to know the exact changes you need to "clone" your object so you can check that out.
    But I think the form object resolves your problem of not be able to press the button when no changes are done.

    Hope this could help you a little bit.