Pregunta

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

¿Fue útil?

Solución

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 and conf/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 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;
  • 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 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>
}
  • 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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top