Reputation: 3747
All I am trying to achieve is this Sample app : ~\play-2.1.0\samples\java\forms
Updated Latest Code :
my question.scala.html
looks like :
@(questionForm: Form[Question])
@import helper._
@import helper.twitterBootstrap._
@answerField(field: Field, className: String = "answer") = {
<div class="twipsies well @className">
<table>
<tr>
<td> @checkbox(field("addRight"),'_label -> "Add")</td>
<td> @checkbox(field("editRight"),'_label -> "Edit")</td>
<td> @checkbox(field("delRight"),'_label -> "Delete")</td>
<td> @checkbox(field("viewRight"),'_label -> "View")</td>
</tr>
</table>
</div>
}
@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>
@inputText(
questionForm("name"),
'_label -> "Name of a Right"
)
@inputText(
questionForm("namex"),
'_label -> "Name of a Right"
)
<div class="answers">
@repeat(questionForm("answers"), min = 0) { answer =>
@answerField(answer)
}
@**
* 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"),
className = "answer_template"
)
<div class="clearfix">
<div class="input">
<a class="addAnswer btn success">Add </a>
</div>
</div>
</div>
</fieldset>
<div class="actions">
<input type="submit" class="btn primary" value="Insert">
</div>
}
<script type="text/javascript" charset="utf-8">
$('.removeAnswer').on('click', function(e) {
var answers = $(this).parents('.answers');
$(this).parents('.answer').remove();
renumber(answers);
});
$('.addAnswer').on('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>
....
Question Model :
package models;
import play.data.validation.Constraints;
import play.db.ebean.Model;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Question extends Model {
@Id
public Long id;
@Constraints.Required
public String name;
@OneToMany(cascade = CascadeType.PERSIST, mappedBy = "question")
public List<Answer> answers;
public Question() {
}
public Question(String name) {
this.name = name;
}
}
Answer Model :
@Entity
public class Answer extends Model {
@Id
public Long id;
public boolean addRight;
public boolean editRight;
public boolean delRight;
public boolean viewRight;
@ManyToOne
public Question question;
public Answer() {
}
public Answer(boolean addRight,boolean editRight,boolean delRight,boolean viewRight,
Question question) {
this.addRight = addRight;
this.editRight = editRight;
this.delRight = delRight;
this.viewRight = viewRight;
this.question = question;
}
}
And Finally Controller save part :
public static Result submit() {
Form<Question> filledForm = questionForm.bindFromRequest();
if(filledForm.hasErrors()) {
User user = User.findByUserName("samuel");
return badRequest(question.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
System.out.println("-->" + question.answers.size() );
if(question.answers != null) {
ArrayList<Answer> answersCopy = new ArrayList<Answer>();
Logger.trace("Size of Anwsers : " +answersCopy.size());
for(Answer answer : question.answers) {
answersCopy.add(new
Answer(answer.addRight,answer.editRight,answer.delRight,answer.viewRight,question));
System.out.println("##" + answer.addRight);
}
question.answers = answersCopy;
}
question.save();
return ok("Nice, all saved!");
}
}
With the above code I dont get any exceptions But .. Question part saves leaving Anwser behind.
Thanks
Upvotes: 2
Views: 1454
Reputation: 9788
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:
play-2.1.0/samples/java/forms
play clean .. play compile .. play eclipse
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
conf/evolutions/default
and conf/META-INF
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>
1.sql
to evolutions/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;
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); } } } } }
routes
file:# Question GET /questions controllers.Questions.blank() POST /questions controllers.Questions.submit()
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!"); } } }
views.question
name it as form.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> }
.phone_template, .profile_template, .answer_template { display: none; }
main.scala.html
this:<li class="@("active".when(nav == "question"))"> <a href="@routes.Questions.blank()">Questions</a> </li>
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
Upvotes: 2