I have the following two Java classes:
Person.java
@Entity
public class Person extends Model {
@Id
public Long id;
@ManyToOne
public Category category = new Category();
@ManyToMany
@JoinTable(joinColumns = {@JoinColumn(name = "person_id")}, inverseJoinColumns = {@JoinColumn(name = "category_id")}, name = "category_person")
public List<Category> categories = new ArrayList<>();
}
Category.java
@Entity
public class Category extends Model {
@Id
public Long id;
public String description;
@OneToMany
public Person person;
@ManyToMany(mappedBy = "categories")
public List<Person> persons = new ArrayList<>();
}
In the controller I have
public static Result blank() {
Form<Person> personForm = Form.form(Person.class);
return ok(blank.render(personForm));
}
How do I structure the form to reflect the ManyToMany relationship between Person and Category via checkboxes?
I've tried
@Category.find.order("description asc").findList().map { c =>
<input type="checkbox" name="categories[@c.id].id" value="@c.id"/> @c.description <br />
}
But when I try to save the Person it tells me
[PersistenceException: ManyToMany bean models.Category@11ac3fd does not have an Id value.]
If I try
@Category.find.order("description asc").findList().map { c =>
<input type="checkbox" name="categories[]" value="@c.id"/> @c.description <br />
}
The validation fails and it tells me that categories[0] has an Invalid Value
.
How do I fix this?
I've found the solution to the problem: I need to register a custom DataBinder. This will tell Play! (specifically the Form object) how to bind values entered into a form to an instance of a particular model.
First start by registering the custom DataBinder in a Global.java
file created in the app
folder. Mine contains this code:
public class Global extends GlobalSettings {
public void onStart(Application app) {
Formatters.register(Category.class, new Formatters.SimpleFormatter<Category>() {
@Override
public Category parse(String text, Locale locale) throws ParseException {
return Category.find.byId(Long.parseLong(text));
}
@Override
public String print(Category category, Locale locale) {
return category.id+"";
}
});
}
Next, in my form I name the ManyToMany Categories checkboxes as follows:
<input type="checkbox" name="categories[]" value="@category.id" /> @category.description <br />
However, that's not all. I had to cater for the instance where the form had to be re-rendered with filled information e.g. User editing his profile. This means that I need to pre-check the checkboxes that the user selected when signing up. This is the code I used:
@defining(personForm("categories").indexes.map { i => personForm("categories")("[" + i + "]").value }) { values =>
@Category.find.all().map { category =>
<input type="checkbox"
name="categories[]"
value="@category.id"
@if(values.contains(Some(category.id.toString()))) { checked } /> @category.description <br />
}
}
A bit complicated if you ask me, but this is the route I had to take as I wanted to go with passing a Form<>
to the template and not a POJO.
I hope this helps someone.