How to pass flash data from controller to view with Play! framework

14,605

Solution 1

If you look in the documentation for Session and Flash scopes you'll see this code snippet:

def save = Action {
  Redirect("/home").flashing(
    "success" -> "The item has been created"
  )
}

Now, compare that to your use of the flash scope:

flash + ("message" -> "Login successful") + ("state" -> "success")

The issue with this usage is that flash is immutable, you can't reassign it. Moreover, with your usage here you're actually creating a new flash variable, it just isn't being used.

If you had modified that slightly to become:

implicit val newFlash = flash + ("message" -> "Login successful") + ("state" -> "success")
Redirect(...)

It would've worked. However, the preferred usage is to use the .flashing() method on your result. This method comes from play.api.mvc.WithHeaders, a trait that is mixed in to play.api.mvc.PlainResult which the various result methods (Ok, Redirect, etc.) inherit from.

Then, as shown in the documentation, you can access the flash scope in your template:

@()(implicit flash: Flash) ... 
@flash.get("success").getOrElse("Welcome!") ...

edit: Ah, okay. I've reviewed your sample code and now I see what you're trying to do. I think what you're really looking for is the canonical way of handling form submissions. Review the constraint definitions here in the documentation and I think you'll see there's a better way to accomplish this. Essentially you'll want to use the verifying method on the tuple backing your form so that bindFromRequest will fail to bind and the validation errors can be passed back to the view:

loginForm.bindFromRequest.fold(
  formWithErrors => // binding failure, you retrieve the form containing errors,
    BadRequest(views.html.login(formWithErrors)),
  value => // binding success, you get the actual value 
    Redirect(routes.HomeController.home).flashing("message" -> "Welcome!" + value.firstName)
)

Solution 2

Wanted to add one more thing to this discussion, to help people avoid this error:

could not find implicit value for parameter flash: play.api.mvc.Flash

I know a lot of this is redundant, but there's a technicality at the end that stole half of my workday and I feel I can help people out with. Appending .flashing(/* your flash scoped info */), such as:

Redirect(routes.ImageEditApp.renderFormAction(assetId)).
  flashing("error" -> "New filename must be unused and cannot be empty.")

... does defines the implicit "flash" variable that you can use in your template, if you have a "base" template that you want to handle flash scope with (such as errors), single usage of the base template is easy, since you already have the implicit flash variable defined via .flashing() ... This is an example of one of my "includes" of a base template.

@views.html.base("Editing: "+asset.id, scripts = Some(scripts),
  extraNav = Some(nav))

You do not have to pass the "flash" variable to the base template. It's an implicit. The base template still has to define it. The parameters for my base template is this:

@(title: String, stylesheets: Option[Html] = None,
  scripts: Option[Html] = None,
  extraNav: Option[Html] = None)(content: Html)(implicit flash: Flash)

Yeah, I know a lot of that is unnecessary, but this is a real world example I'm copy n' pasting from. Anyway, it's likely you need to have other templates that use your base template, and you do not always use .flashing() to load them. Since you're surely loading these using Controllers, if you forget to start your Action for each with implicit request => such as:

def controllerMethodName() = Action { implicit request =>

then the "flash" implicit will not be defined. Then when that template tries to include your base template, you'll be flummoxed because you don't have the default flash implicit variable defined, while the base template requires it. Hence that error.

So again, the fix.. go to all of your controller methods, and make sure you put in that implicit request => !

Share:
14,605
Ron
Author by

Ron

co-founder & cto @ fleetbase.io, software engineer, acted in foreign films, former u.s. marine

Updated on July 19, 2022

Comments

  • Ron
    Ron almost 2 years

    I've been plowing through play! so far with a few bumps in the learning curve. Right now I am unable to pass flash data from the controller to the view, which at first I thought was a trivial task, or at least it should be.

    Here's what I have right now:

    I have a main layout: application.scala.html

    I have a view that goes in the layout: login.scala.html

    and I have my controller and method: UX.authenticate() - I want this to provide flash data to the view depending on the outcome of the login attempt (successful vs fail)

    This is my code in my controller method:

    def authenticate = Action { implicit request =>
            val (email, password) = User.login.bindFromRequest.get
            // Validation
            // -- Make sure nothing is empty
            if(email.isEmpty || password.isEmpty) {
                flash + ("message" -> "Fields cannot be empty") + ("state" -> "error")
                Redirect(routes.UX.login())
            }
            // -- Make sure email address entered is a service email
            val domain = email.split("@")
            if(domain(1) != "example.com" || !"""(\w+)@([\w\.]+)""".r.unapplySeq(email).isDefined) {
                flash + ("message" -> "You are not permitted to access this service") + ("state" -> "error")
                Redirect(routes.UX.login())
            } else {
                // Attempt login
                if(AuthHelper.login(email, password)) {
                    // Login successful
                    val user = User.findByEmail(email)
                    flash + ("message" -> "Login successful") + ("state" -> "success")
                    Redirect(routes.UX.manager()).withSession(
                      session + (
                        "user"      -> user.id.toString
                      )
                    )
                } else {
                    // Bad login
                    flash + ("message" -> "Login failed") + ("state" -> "error")
                    Redirect(routes.UX.login())
                }
            }
        }
    

    In my login view I have a parameter: @(implicit flash: Flash)

    When I try to use flash nothing appears using @flash.get("message")

    Ideally I would want to set @(implicit flash: Flash) in the layout, so that I can flash data from any controller and it will reach my view. But whenever I do that, login view throws errors.

    In my login view right now I have this:

    def login = Action { implicit request =>
            flash + ("message" -> "test")
            Ok(views.html.ux.login(flash))
        }
    

    What is the ideal way of passing flash data to the view, and are there examples anywhere? The examples on the Play! framework docs do not help whatsoever and are limited to two examples that show no interaction with the view at all (found here at the bottom: http://www.playframework.com/documentation/2.0/ScalaSessionFlash).

    Is there an easier alternative? What am i doing wrong? How can I pass flash data directly to my layout view?

  • Ron
    Ron about 11 years
    The flashing() method worked, but how would I get flash data into my layout without going having to pass it from controller to my view then to my layout?
  • Ryan
    Ryan about 11 years
    Let's assume you have two views here: login.scala.html and layout.scala.html. You're trying to call views.html.login(...) from your controller which is then going to look like @layout { form stuff } . If you also declare flash as an implicit variable on your layout view, it will automatically be in scope in your layout. Is that what you're looking for?
  • Ron
    Ron about 11 years
    With that almost exact scenario I am getting this error '=>' expected but ')' found. - the exception from your example and my setup is my layout has parameters so from login.scala.html it looks like this: @layout(param) { content } so in my layout.scala.html I have this: @(title: String)(content: Html) \n @(implicit flash: Flash) - what am i doing wrong here?
  • Ron
    Ron about 11 years
    Removed the parameters and set it up just how you have it in the example now I'm getting this error: could not find implicit value for parameter flash: play.api.mvc.Flash
  • Ryan
    Ryan about 11 years
    The parameters for layout.scala.html should be @(title: String)(content: Html)(implicit flash: Flash). No line break between them.
  • Ron
    Ron about 11 years
    I did that, and that gives me the error: could not find implicit value for parameter flash: play.api.mvc.Flash from login.scala.html at @layout("Login") {
  • Ryan
    Ryan about 11 years
    Can you post your controller and views at gists and post the links here? It's hard to get the full picture like this.
  • Ryan
    Ryan about 11 years
  • Leo
    Leo over 8 years
    You've just saved me an hour :) Thank you!