Removing items from Spring's AutoPopulatingList

I have created a dynamic form using Spring AutoPopulatingList and JQuery. Addition works like a charm, new items are created and persisted to the database. The issue is with deletion: my update method always gets the full list, regardless of deletion of the elements on the browser side.

Controller's update method is simple as that

@RequestMapping(value = "/user/{id}", method = RequestMethod.POST)
@ResponseBody
public String updateUser(@PathVariable("id") int id, @ModelAttribute("user") User user, HttpServletRequest request) {
    userService.update(user);
    return messageSource.getMessage("user.data_updated", null, request.getLocale());
}

User POJO implementation as follows

@Entity
public class User implements Serializable {
...

@OneToMany(targetEntity = Language.class, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<Language> languages = new AutoPopulatingList(Language.class);
...
}

The POST request which goes to my controller looks like that (addition, language 2 added):

languages[0].code:pl
languages[0].level:Fluent
languages[1].code:de
languages[1].level:Native
languages[2].code:cc
languages[2].level:Intermediate

and removal(language 1 removed using JQuery .remove() method):

languages[0].code:pl
languages[0].level:Fluent
languages[2].code:cc
languages[2].level:Intermediate

so from the communication side looks OK, but the user retrieved from @ModelAttribute("user") in updateUser method still has three language elements, all valid (that is not null). Any suggestions? I'm using Spring 3.1.1 and JQuery 1.7.2 if that's relevant.

EDIT: The client side code for adding/removal of fields is as follows:

$.addLanguage = function()  {
            var newLanguage = $('<input type="text" id="languages' + languagesCounter + '.code" name="languages[' + languagesCounter + '].code" class="langIdentifier"/>' +
                                '<select id="languages' + languagesCounter + '.level" name="languages[' + languagesCounter + '].level" class="langSelect">'  +
                                    '<option value="Basic">Basic</option>' +
                                    '<option value="Intermediate">Intermediate</option>' +
                                    '<option value="Fluent">Fluent</option>' +
                                    '<option value="Native">Native</option>' +
                                '</select>' +
                                '<input type="button" id="remove' + languagesCounter + '" onclick="$.removeLanguage(' + languagesCounter + ')"' +
                                        'name="Remove" value="Remove" class="removeButton"/>' +
                                '<br/>');
            languagesCounter++;
            newLanguage.insertBefore($("#add"));
        }

and for removal:

$.removeLanguage = function(languageId) {
    var languageField = '#languages' + languageId + '\\.code';
    var levelField = '#languages' + languageId + '\\.level';
    var removeButton = '#remove' + languageId;
    $(languageField).fadeOut(250, function() { $(this).remove(); });
    $(levelField).fadeOut(250, function() { $(this).remove(); });
    $(removeButton).fadeOut(250, function() { $(this).remove(); });
};

Answers


Try using LazyList instead of AutoPopulatingList. I am using it to add dynamic content to the html and get the data back in the model attribute object. Below is the code I am using to create the list.

    private List<OperationParameters> operationParameterses = LazyList.decorate(new ArrayList<OperationParameters>(), FactoryUtils.instantiateFactory(OperationParameters.class));

    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="operations")
    public List<OperationParameters> getOperationParameterses() {
        return this.operationParameterses;
    }

    public void setOperationParameterses(List<OperationParameters> operationParameterses) {
        this.operationParameterses = operationParameterses;
    }

And in controller when I get the list populated in the model attribute I first check it for empty data in the list and removes them with following code and then persist in the database. Otherwise it tries to insert null data in the database.

operations.getOperationParameterses().removeAll(Collections.singleton(null));

Hope this helps you. Cheers.


Answering my own question (perhaps save someone some headaches):

I've had

@ModelAttribute("user")
public User getUserById(@PathVariable("id") int id) {
    logger.fine("Getting user for id: " + id);
    return userService.findById(id);
}

to populate the model in GET, and

@RequestMapping(value = "/user/{id}", method = RequestMethod.POST)
@ResponseBody
public String updateUser(@PathVariable("id") int id, @ModelAttribute("user") User user, HttpServletRequest request) {
    userService.update(user);
    return messageSource.getMessage("user.data_updated", null, request.getLocale());
}

to update the user. Now the whole problem was in @ModelAttribute in POST handler - it cannot be the same name for a key as in pre-population code. Changing it to say @ModelAttribute("updated") solved the issue. Now deleted objects from form are indeed null's.

Now I'm struggling with removing them from the list before persisting, because

removeAll(Collections.singleton(null));

doesn't seem to work.


When you are deleting the list element tag from view using jquery, its only stopping spring from binding new value to that elements. But it wont remove the element from the list in your controller.

For clarity here is how jsp + spring will bind data to list element. for binding i th element of the list it will call list.get(i).set(newVal). So when you remove i th element from view then nothing gets bind but at the same time i th element doesn't get removed either.

For deleting you might send ajax request every time you remove an element and delete the element from list in controller.


Soluton presented by user "Japs T" doesn't work for me either:

operations.getOperationParameterses().removeAll(Collections.singleton(null));

I red about possible solutions. Here is the list:

  • remove all items on server side before binding values from request
  • update server state via AJAX

The "not transparent" solution I would try is hiding HTML element instead of removing and clearing values in inputs, then on server side you can implement functionality of ignoring "empty" records. For example:

interface Removeable {
boolean isEmpty();
}

(Collection<Removeable>)myCollection.removeIf(e -> e.isEmpty())

Need Your Help

R - cox hazard model not including levels of a factor

r statistics survival-analysis cox-regression

I am fitting a cox model to some data that is structured as such:

How to enforce 80 columns in latex using emacs

emacs latex indentation

I see that fill column shows a nice bar at the point that I want.