Jan Chabik
Jan Chabik

Reputation: 99

Play FormFactory Type Mismatch

I am struggling with Scala & Play a bit. I have this book class in my model

class Book(identifier: Int, title1: String, price1: Int, author1: String) {

  val id: Int = identifier
  val title: String = title1
  var price: Int = price1
  val author: String = author1

  override def toString: String = s" $title written by $author"
}

In my BooksController class I am defining Basic CRUD operations. I wanted to do some magic using the FormFactory class which I am injecting.

import javax.inject._
import play.api.mvc._
import model.Book
import model.BookSet
import play.data._
import scala.collection.mutable.HashMap


@Singleton
class BooksController @Inject()(cc: ControllerComponents, formFactory: FormFactory)  (implicit assetsFinder: AssetsFinder)
    extends AbstractController(cc) {

  //show a form
  def create = {
    val bookform: Form[Book] = formFactory.form(classOf[Book])
    Ok(views.html.bookstore.create(bookform))

  }

  //show all books
  def index = Action {
    val map: HashMap[Int, Book] = BookSet.bookMap
    Ok(views.html.bookstore.bookindex(map))
  }

This is my bookstore.create scala.html file

@import model.Book
@import helper._

@(bookForm: Form[Book])

<html>
    <head>
        <title>Create Book </title>
    </head>
    <body>
        <h1> Create Book</h1>
        @helper.form( action = routes.BooksController.save() ){
            @helper.inputText(bookForm("id"))
            @helper.inputText(bookForm("title"))
            @helper.inputText(bookForm("price"))
            @helper.inputText(bookForm("author"))

            <input type="submit" value="Create Book">

        }
    </body>

</html>

These are troublesome lines of code:

val bookform: Form[Book] = formFactory.form(classOf[Book])
Ok(views.html.bookstore.create(bookform))

On the second line IntelliJ informs me of Type mismatch, expected play.api.data.Form[model.Book], actual play.data.Form[model.Book]

I've tried fixing this by adding the follwing line to html but it results in an error (probably caused by the helper class)

@import play.data.Form

I've also tried passing the play.api.data.Form from the factory using asInstanceOf[]:

val bookform: Form[Book] = formFactory.form(classOf[Book])

Ok(views.html.bookstore.create(bookform.asInstanceOf[play.api.data.Form[Book]]))

After trying this i got the error during compilation: An implicit MessagesProvider instance was not found. P lease see https://www.playframework.com/documentation/2.6.x/ScalaForms#Passing-MessagesProvider-to-Form-Helpers [error] @helper.inputText(bookForm("title"))

It basically repeats for every field. So I've followed the scala docs Option 2 and added an Injection and implicit Request to the Controller as well as the implicit Request to html file. Still getting the same error

At this point I feel helpless so thats why I am asking the question. Is it acctually possible to use use play.data.FormFactory with a helper class? Unfourtunately play.api.data package doesn't provide it's own FormFactory. I feel like there is an obvious solution I am just missing. Thanks for patience

Upvotes: 0

Views: 270

Answers (1)

Daniel Hinojosa
Daniel Hinojosa

Reputation: 992

I think at first glance, FormFactory was never intended for Scala, only Java. I could be wrong about that, but I have never used it in Scala.

If you are willing to try the following you might have an easier time:

Change your Book simply to:

case class Book(identifier: Int, title: String, price: Int, author: String)

Create a Form that looks like this:

val bookForm = Form(
   mapping(
     "id" -> number,
     "title" -> nonEmptyText,
     "price" -> number,
     "author" -> nonEmptyText
   )(Book.apply)(Book.unapply)
 )

Then your Controller can look something like the following:

@Singleton
class BooksController @Inject()(cc: ControllerComponents)  
  (implicit assetsFinder: AssetsFinder)
  extends AbstractController(cc) {

  //show a form
  def create = {
    Ok(views.html.bookstore.create(bookForm))
  }

  //show all books
  def index = Action {
     val map: HashMap[Int, Book] = BookSet.bookMap
     Ok(views.html.bookstore.bookindex(map))
  }
}

Unsure about AssetsFinder here, but you may have a reason, so I left it in the example. ;)

Upvotes: 0

Related Questions