Well I think i got it working using JPA, I was a bit unsure whether you wanted to make it with JPA or just Ebean, however I think you and others are able to port it over Ebean too. (I can also make it if you require, i guess ;)
Here goes something I tried with JPA:
- Open up terminal (or cmd) and navigate to project called
play-2.1.0/samples/java/forms
- Then (Just in case) run these commands:
play clean .. play compile .. play eclipse
- Go eclipse and import it there (right click to package explorer -> existing project..)
- Open up
conf/application.conf
and add (or uncomment) these rows:
db.default.driver=org.h2.Driver db.default.url="jdbc:h2:mem:play" db.default.jndiName=DefaultDS jpa.default=defaultPersistenceUnit
- Create folder structure of
conf/evolutions/default
andconf/META-INF
- Add file named
persistence.xml
with following contents:
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0">
<persistence-unit name="defaultPersistenceUnit" transaction-type="RESOURCE_LOCAL"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <non-jta-data-source>DefaultDS</non-jta-data-source> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/> </properties> </persistence-unit> </persistence>
- Now lets create tables for your Answer and Question models, create file
1.sql
toevolutions/default
with following contents:
# --- !Ups create table question ( id bigint not null, name varchar(255), constraint pk_question primary key (id)) ; create table answer ( id bigint not null, name varchar(255), question_id bigint, constraint pk_answer primary key (id)) ; create sequence answer_seq start with 1000; create sequence question_seq start with 1000; alter table answer add constraint fk_answer_question_1 foreign key (question_id) references question (id) on delete restrict on update restrict; # --- !Downs SET REFERENTIAL_INTEGRITY FALSE; drop table if exists question; drop table if exists answer; SET REFERENTIAL_INTEGRITY TRUE; drop sequence if exists question_seq; drop sequence if exists answer_seq;
- Modify or add following models:
package models; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.SequenceGenerator; import javax.persistence.ManyToOne; import play.data.validation.Constraints.Required; @Entity @SequenceGenerator(name = "answer_seq", sequenceName = "answer_seq") public class Answer { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "answer_seq") public Long id; @Required public String name; @ManyToOne public Question question; public Answer() { } public Answer(String name, Question question) { this.name = name; this.question = question; } }
...
package models; import java.util.List; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.SequenceGenerator; import play.data.validation.Constraints.Required; import play.db.jpa.*; @Entity @SequenceGenerator(name = "question_seq", sequenceName = "question_seq") public class Question { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "question_seq") public Long id; @Required public String name; @Valid @OneToMany(cascade = CascadeType.PERSIST, mappedBy = "question") public List<Answer> answers; public Question() { } public Question(String name) { this.name = name; } public void save() { JPA.em().persist(this); List<Question> allQuestions = JPA.em().createQuery("from Question order by name").getResultList(); System.out.println("Number of questions: " + allQuestions.size()); for(Question q : allQuestions) { System.out.println("Question --- id: " + q.id + ", name: " + q.name); if(q.answers != null) { for(Answer a : q.answers) { System.out.println("Answer --- id: " + a.id + ", name: " + a.name + " question_id: " + a.question.id); } } } } }
- Ok, now that models OK lets create still controller and view to test this out, starting from
routes
file:
# Question GET /questions controllers.Questions.blank() POST /questions controllers.Questions.submit()
- Then add following controller:
package controllers; import static play.data.Form.form; import java.util.ArrayList; import models.Answer; import models.Question; import play.data.Form; import play.db.jpa.Transactional; import play.mvc.Controller; import play.mvc.Result; import views.html.question.*; public class Questions extends Controller { /** * Defines a form wrapping the Question class. */ final static Form<Question> questionForm = form(Question.class); /** * Display a blank form. */ public static Result blank() { return ok(form.render(questionForm)); } @Transactional public static Result submit() { Form<Question> filledForm = questionForm.bindFromRequest(); if(filledForm.hasErrors()) { return badRequest(form.render(filledForm)); } else { // If we dont have any errors, we should be around here :) Question question = filledForm.get(); // Since Answer needs reference to Question and with new Question // it cant get it loaded from DB we need to do little dirty trick here // in order to save new question id instantly to answers foreign_key // as question_id, otherwise it will be null if(question.answers != null) { ArrayList<Answer> answersCopy = new ArrayList<Answer>(); for(Answer answer : question.answers) { answersCopy.add(new Answer(answer.name, question)); } question.answers = answersCopy; } question.save(); // You can also use this test code to save data // Question question = new Question("What is your favorite color?"); // question.answers = new ArrayList<Answer>(); // question.answers.add(new Answer("Blue", question)); // question.answers.add(new Answer("Red", question)); // question.save(); return ok("Nice, all saved!"); } } }
- Ok, still one view to go, create it at
views.question
name it asform.scala.html
(btw, i reused similar code the application is already using for contacts but made it to work with your models.)
@(questionForm: Form[Question]) @import helper._ @import helper.twitterBootstrap._ @title = { Add a new question } @answerField(field: Field, className: String = "answer") = { @input(field, '_label -> "Answer", '_class -> className) { (id, name, value, _) => <input type="text" name="@name" value="@value"> <a class="removeAnswer btn danger">Remove</a> } } @main(title, nav = "question") { @if(questionForm.hasErrors) { <div class="alert-message error"> <p><strong>Oops</strong> Please fix all errors</p> </div> } @helper.form(action = routes.Questions.submit, 'id -> "form") { <fieldset> <legend>Fill a question with answers</legend> @inputText( questionForm("name"), '_label -> "Name of a question" ) <div class="answers"> @repeat(questionForm("answers"), min = 0) { answer => @answerField(answer("name")) } @** * Keep an hidden block that will be used as template for Javascript copy code * answer_template is only css style to make it hidden (look main.css and declare your own answer_template at bottom) **@ @answerField( questionForm("answers[x].name"), className = "answer_template" ) <div class="clearfix"> <div class="input"> <a class="addAnswer btn success">Add answer</a> </div> </div> </div> </fieldset> <div class="actions"> <input type="submit" class="btn primary" value="Insert"> <a href="@routes.Application.index" class="btn">Cancel</a> </div> } <script type="text/javascript" charset="utf-8"> $('.removeAnswer').live('click', function(e) { var answers = $(this).parents('.answers'); $(this).parents('.answer').remove(); renumber(answers); }); $('.addAnswer').live('click', function(e) { var answers = $(this).parents('.answers'); var template = $('.answer_template', answers); template.before('<div class="clearfix answer">' + template.html() + '</div>'); renumber(answers); }); $('#form').submit(function() { $('.answer_template').remove() }); // -- renumber fields // This is probably not the easiest way to do it. A jQuery plugin would help. var renumber = function(answers) { $('.answer').each(function(i) { $('input', this).each(function() { $(this).attr('name', $(this).attr('name').replace(/answers\[.+?\]/g, 'answers[' + i + ']')) }); }); } </script> }
- As final tuning, check main.css file last line that you remember to edit it to:
.phone_template, .profile_template, .answer_template { display: none; }
- and add
main.scala.html
this:
<li class="@("active".when(nav == "question"))"> <a href="@routes.Questions.blank()">Questions</a> </li>
- Now if you haven't already done so, go to your browser, confirm evolutions to be run and navigate to questions view and try submitting your form with different questions and values.
You should see results in terminal window of play (similar to mine which is below), and if thats the case you have just saved successfully new question with few answers, you can repeat it as you like - should work since new id's are always generated. If you want your form to contain also edit etc, I recommend checking how JPA is used inside play samples folder (computer-database-jpa) since this example already turned out to be too huge; I might throw it to github later, good night & cheers.
Number of questions: 1
Question --- id: 50000, name: What is your favorite color?
Answer --- id: 50000, name: Blue question_id: 50000
Answer --- id: 50001, name: Red question_id: 50000