45

I am new to Play framework and tried to mimic the helloworld sample in my local machine but I encountered an error:

enter image description here

routes:

# Home page
GET        /                    controllers.Application.index

# Hello action
GET        /hello               controllers.Application.sayHello


# Map static resources from the /public folder to the /assets URL path
GET        /assets/*file        controllers.Assets.versioned(path="/public", file: Asset)

controller:

package controllers

import play.api.mvc._
import play.api.data._
import play.api.data.Forms._

import views._

class Application extends Controller {

  val helloForm = Form(
    tuple(
      "name" -> nonEmptyText,
      "repeat" -> number(min = 1, max = 100),
      "color" -> optional(text)
    )
  )

  def index = Action {
    Ok(html.index(helloForm))
  }

  def sayHello = Action { implicit request =>
      helloForm.bindFromRequest.fold(
      formWithErrors => BadRequest(html.index(formWithErrors)),
      {case (name, repeat, color) => Ok(html.hello(name, repeat.toInt, color))}
    )
  }
}

view:

@(helloForm: Form[(String,Int,Option[String])])

@import helper._

@main(title = "The 'helloworld' application") { 
    <h1>Configure your 'Hello world':</h1> 
    @form(action = routes.Application.sayHello, args = 'id -> "helloform") {
        @inputText(
            field = helloForm("name"),
            args = '_label -> "What's your name?", 'placeholder -> "World"
        )

        @inputText(
            field = helloForm("repeat"),
            args = '_label -> "How many times?", 'size -> 3, 'placeholder -> 10
        ) 
        @select(
            field = helloForm("color"),
            options = options(
                "" -> "Default",
                "red" -> "Red",
                "green" -> "Green",
                "blue" -> "Blue"
            ),
            args = '_label -> "Choose a color"
        ) 
        <p class="buttons">
            <input type="submit" id="submit">
        <p> 
    } 
}

I have Play 2.4 installed and created the project using IntelliJ Idea 14 via activator template.

arjayads
  • 501
  • 1
  • 5
  • 14

3 Answers3

65

After adding implicit messages parameters to views you can just add the following imports and use the old controller classes or even objects without any additional changes:

import play.api.Play.current
import play.api.i18n.Messages.Implicits._
ps_ttf
  • 1,096
  • 7
  • 15
  • 3
    Why is this not the documentation's recommended approach? This seems so much simpler than extending `I18Support` and injecting the `MessagesApi` – mplis Sep 06 '15 at 13:26
  • 2
    I suppose that Play Framework developers try to popularize the new approach which is more customisable. Importing the `play.api.i18n.Messages.Implicits._` values introduces a hard-coded dependency on Play messaging provider. The dependency is not parameterized which is not convenient in some situations, e.g. for writing isolated unit tests. Thus the approach that uses controller classes with parameterized dependencies is more clean. Nevertheless, in my applications it is usually sufficient to use the oldschool controller *objects* with hardcoded dependencies. I prefer to use simpler solutions. – ps_ttf Sep 06 '15 at 18:25
  • 7
    It's probably worth to mention that this approach is going to be deprecated with the removal of play's global state. See [Migration Guide / Dependency Injection](https://www.playframework.com/documentation/2.4.x/Migration24#Dependency-Injection). – Roman Sep 30 '15 at 13:45
  • 1
    Thank you! This is a good reason to move to the new approach. – ps_ttf Oct 02 '15 at 14:17
44

Using view form helpers (such as @inputText) requires you to pass an implicit play.api.i18n.Messages parameter to your view. You can do this adding (implicit messages: Messages) to the signature in your view. Your view becomes this:

@(helloForm: Form[(String,Int,Option[String])])(implicit messages: Messages)

@import helper._

@main(title = "The 'helloworld' application") { 
  <h1>Configure your 'Hello world':</h1> 
  ...

Then in your application controller you must make this parameter implicitly available in your scope. The simplest way to do this is to implement play's I18nSupport trait.

In your example, this would look like this:

package controllers

import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
import javax.inject.Inject
import play.api.i18n.I18nSupport
import play.api.i18n.MessagesApi

import views._

class Application @Inject()(val messagesApi: MessagesApi) extends Controller with I18nSupport {

  val helloForm = Form(
    tuple(
      "name" -> nonEmptyText,
      "repeat" -> number(min = 1, max = 100),
      "color" -> optional(text)
    )
  )

  def index = Action {
    Ok(html.index(helloForm))
  }

  def sayHello = Action { implicit request =>
    helloForm.bindFromRequest.fold(
      formWithErrors => BadRequest(html.index(formWithErrors)),
      {case (name, repeat, color) => Ok(html.hello(name, repeat.toInt, color))}
    )
  }
}

In your controller you can of course use your own implementation of MessagesApi. Since play knows out of the box how to inject a MessagesApi you can simply annotate your controller with @Inject and let play do the work for you.

As Matthias Braun mentioned, you also have to set

routesGenerator := InjectedRoutesGenerator

in your build.sbt

See https://www.playframework.com/documentation/2.4.x/ScalaI18N for more information about I18n.

Roman
  • 5,323
  • 1
  • 27
  • 39
  • you save my day! How can we disable of I18nSupport to avoid **(implicit messages: Messages)** in my views? – arjayads Jun 12 '15 at 14:09
  • AFAIK it is not possible to disable i18n support using forms. The only way I can think of would be to write your own form helpers without i18n support. See https://playframework.com/documentation/2.4.x/api/scala/index.html#views.html.helper.package. But I don't think this is worth the effort. – Roman Jun 12 '15 at 14:17
  • When I do this, I get a "Controller needs to be abstract, since method messagesApi in trait I18nSupport of type => play.api.i18n.MessagesApi is not defined". Any idea what I might be missing? – Michael A. Jun 26 '15 at 07:56
  • @MichaelA. you have to implement `messagesApi` in your controller. Either you do it your own by overriding `messagesApi` or you let Play do the work for you by simply injecting a `MessagesApi`. See the `@Inject` annotation in my answer. – Roman Jun 26 '15 at 08:03
  • 2
    I already have: `class AppController @Inject() (val messages: MessagesApi) extends Controller with I18nSupport { ... }`. If I understood the answer correctly, that should be enough - but I still get that message. – Michael A. Jun 26 '15 at 08:24
  • 3
    @MichaelA. Change `val messages: MessagesApi` to `val messagesApi: MessagesApi`. This will automatically override `abstract def messagesApi` defined in `I18nSupport`. – Roman Jun 26 '15 at 08:30
  • 2
    Doh. Works now - a thousand thanks. One of those type of errors one can spend hours staring at... – Michael A. Jun 26 '15 at 08:37
  • 2
    To get this to work, I had to add `routesGenerator := InjectedRoutesGenerator` to my `build.sbt` and prefix the routes to my controllers with `@`: `GET /hello @controllers.Application.sayHello`. – Matthias Braun Nov 14 '15 at 15:28
  • Thanks @MatthiasBraun for pointing this out. Indeed the method described above requires dependency injected routing. However, including `routesGenerator := InjectedRoutesGenerator` in `build.sbt` should be enough. Prefixing routes with `@` is not needed. See https://www.playframework.com/documentation/2.4.x/ScalaRouting#Dependency-Injection for details. – Roman Nov 18 '15 at 15:52
  • 2
    I also had to add `import play.api.i18n.I18nSupport` and `import play.api.i18n.MessagesApi` to get it to work. – vextorspace Dec 05 '15 at 16:48
  • I had to add (implicit messagesProvider: MessagesProvider), rather than (implicit messages: Messages), to get mine to work – Anomaly Oct 25 '17 at 01:29
1

Using form helpers requires you to pass an implicit play.api.i18n.Messages parameter to your view. You can do this adding (implicit messages: Messages) to in your view. Your view becomes this:

@(contacts: List[models.Contact], 
  form: Form[models.Contact])(implicit messages: Messages)

Then manually inject into your controllers

import play.api.data.Forms._

import javax.inject.Inject

import play.api.i18n.I18nSupport

import play.api.i18n.MessagesApi 

then finally add on to your main index controller class

class Application @Inject()(val messagesApi: MessagesApi) extends
                                           Controller with I18nSupport {
ρяσѕρєя K
  • 127,886
  • 50
  • 184
  • 206
arvind grey
  • 150
  • 12