Play!> framework How-To : binding @ManyToMany to views (without CRUD) and differents UI

This is a little How-To for binding @ManyToMany relationship in Play!> views. The model for this very simple : a Domain model , and a User model. Users can belong to many domains and domains can have many models.

So, the Models are simple :

@Entity
public class Domain extends Model {
    public String name;


    public String toString() {
        return name;
    }

}
@Entity
public class User extends Model {
    @Required
    public String firstname;
    @Required
    public String lastname;
    @ManyToMany
    public List<Domain> domains = new ArrayList<Domain>();

    public String toString() {
        return firstname + " " + lastname;
    }

}

The relationship will be handle by the User view : the deafult component to handle this is the <select> form tag. So here is the basic code to make the binding.

    #{form @Users.save(), id:'userCreationForm', enctype:'multipart/form-data'}
        <input name='user.id' id='id-${user?.id}' type='hidden' value="${user?.id}"/>
        #{input for:'user.firstname', id:'firstname-${user.id}' /}
        #{input for:'user.lastname', id:'lastname-${user.id}' /}

        <select id="user_domains" name="user.domains.id" value="" size="1" id="select" multiple>
        #{list domains, as:'domain'}
            <option value="${domain.id}" ${user.domains.contains(domain) ? 'selected' : ''} >${domain.name}</option>
        #{/list}
        </select>

        <input type="submit" value="Save">
    #{/form }

This will reproduce the same UI as in CRUDSs views. Note this two importants things :

  • the name of the select tag is : user.domains.id

  • to bind selected domains in the options, use the ternary operator and the contains method : $\{user.domains.contains(domain) ? 'selected' : ''}

Changing the UI

I don’t really like this UI choice. As in the Yabe blog demo, we can change the UI of this view : we will user the same design as on the demo. We will use the same styles and javascript :

<style type="text/css">
       .domains-list .domain {
           cursor: pointer;
           padding: 1px 4px;
       }
       .domains-list .selected {
           background: #222;
           color: #fff;
       }
</style>
<script type="text/javascript">
       var toggle = function(domainEl) {
           var input = document.getElementById('h'+domainEl.id);
           if(domainEl.className.indexOf('selected') > -1) {
               domainEl.className = 'domain';
               input.value = '';
           } else {
               domainEl.className = 'domain selected';
               input.value = domainEl.id;
           }
       }
</script>

And then adapt the form components :

    <div class="domains-list">
       #{list domains, as:'domain'}
           <span id="${domain.id}" onclick="toggle(this)"
                class="domain ${user.domains.contains(domain) ? 'selected' : ''}">
               ${domain}
           </span>
           <input id="h${domain.id}" type="hidden" name="user.domains.id"
                    value="${user.domains.contains(domain) ? domain.id : ''}" />
        #{/list}
    </div>

USing checkboxes

Another choice is to use checkboxes. The use is really simple. The important thing is the same, use the good name on the form component : name="user.domains.id"

    #{list domains, as:'domain'}
    <input id="${domain.id}" type="checkbox" name="user.domains.id"
             ${user.domains.contains(domain) ? 'checked' : ''} value=${domain.id}>${domain}</input>
    #{/list}
    <input type="hidden" name="user.domains.id" value="" />

Updated : It’s important to not forgot the last <input /> to handle when no checkbox is selected, otherwise no changes will be made.

comments powered by Disqus