2

I am using Play Framework with Java and with no prior experience with Scala. What I'm trying to achieve is to maintain request parameters (typically GET) across controller and view. More specifically, I need the view to pass the parameters sent to it by the controller (via the query string) back to the controller once it hands over control. A form is generated in the template using the form helper:

@form(routes.Application.authenticate()) 

I know I can access the current request with play.mvc.Controller.request(). I need to append the data submitted by the form to the current query string and pass it all via the URL, or, in case the form method is POST, either append the current query string to the action URL or store the parameters in hidden fields and pass them all through POST.

Is there a straightforward and clean way to ahieve this? At first I tried to pass everything via an object, but then I ran into trouble with the router, plus I couldn't figure out how to pass the data back.

Lord Zuthulu
  • 143
  • 1
  • 7
  • Have you considered using the flash or session scope instead? Should be much easier. – dres Oct 24 '13 at 20:49
  • I did, I had trouble implementing it and found the form approach more convenient. But I may have been doing something wrong. – Lord Zuthulu Nov 03 '13 at 00:23
  • I'm pretty sure that would be the right way to go about it. Check the docs again and post a question on flash scope if you can't figure it out. PM me if you want me to have a look. You might also want to look at other posts on the topic: http://stackoverflow.com/search?q=play+flash+scope – dres Nov 03 '13 at 04:07

2 Answers2

1

UPDATE

This is usually can be done via hidden input field. According to this, there are 2 ways of rendering hidden input, but I prefer simplest one:

<input type="hidden" name="hiddenData" value="@data" /> 

On server side, you may get data from HashMap filledForm.data().get("hiddenData"), or via regular databind mechanism. Since your data is undeterministic, you may change name and value on input control or pass HashMap as data. It will be rendered like this: value="{A=B, B=B}" and on server side you will receive HashMap.

To send request via GET you need to use controller accessed via GET at routes file like this:

@helper.form(routes.Application.doLoginGetMethod)

Generic forms

Play framework often lacks documentation for many important features, but at least it have examples in installation folder. %PLAYINSTALLFODLER%\samples\java\forms is what you need.

Documentation is here: Play framework Forms (Scala) Server side(Java), Templates

Anyway, idea is simple - you can use same form API for working with forms on client side and server side.

First create pojo to store data. Attributes needed for validation and visual tuning:

public static class Login
    @Required
    @Email
    public String email;

    @Required
    @MinLength(5)
    public String password;
 }

Second, you need to create your form - it stateless, so can be reused. Pass it to your view:

public static final Form<Login> LOGIN_FORM = form(Login.class);
...
public static Result login() {
    return ok(loginView.render(LOGIN_FORM));
}

On your template use helpers api from views.html.helper to render form and controls. There are plenty of them: checkbox, select, options, textarea and others.

@(loginForm: Form[_])
@import helper._
...
@helper.form(routes.Application.doLogin) { // this differ from original login method

     @if(loginForm.hasGlobalErrors) { // example of validation and form data
         <p class="error">
             @loginForm.globalError.message</span>
         </p>
     }

    @inputText(  // Notice the helper
        loginForm("email"),
        '_showConstraints -> false,
        '_label -> "Your email"
    )

     @inputPassword( // Another helper
         loginForm("password"),
         '_showConstraints -> true,
         '_label -> "Password"
     )
     <input type="submit" value="Login"> // submit button
}

And then on server side receive form:

public static Result doLogin() {
    final Form<Login> filledForm = LOGIN_FORM.bindFromRequest();
// User did not fill everything properly
    if (filledForm.hasErrors()) return badRequest(login.render(filledForm));
return Controller.redirect("Hey!");
}

Of course you will need routes for this:

GET     /login                      controllers.Application.login
POST    /login                      controllers.Application.doLogin
Community
  • 1
  • 1
Viktor Aseev
  • 698
  • 4
  • 9
  • I've already gone through the documentation. I know how to generate a from from a model, so the part of the code you just demonstrated is already there. The problem is there will be a number of non-deterministic query parameters in the request which I need the view to pass back to the controller ALONG WITH the form data when it's submitted. Note the parameters are non-deterministic, so I can't create a model/POJO for them to pass to the form. I just need to preserve them throughout the communication between controller and view. – Lord Zuthulu Oct 21 '13 at 00:14
  • Thanks, that makes things clearer, although it still doesn't answer an important part of my question. Suppose I want to iterate over the key-value pairs in the request, generating a hidden input from each pair with name="key" and value="value". What would be the Scala syntax for that? How exactly should I utilize the Request object (retrieved via play.mvc.Controller.request()) for this purpose? Since I lack familiarity with the FP paradigm, I'm having difficulty figuring this out and the Scala documentation looks archaic to me. – Lord Zuthulu Oct 22 '13 at 12:12
-1

With help from this and this I finally figured out how to generate the hidden input fields. Either of the following approaches does the job:

@for((key, value) <- request.queryString) {
  <input type="hidden" name="@key" value="@value" />
}

Or:

@request.queryString.map { case (key,value) =>          
  <input type="hidden" name="@key" value="@value" />
}

In case of POST, @request.queryString can be simply replaced with @request.body.asFormUrlEncoded. Since both methods return Map[String, Seq[String]], one might want to flatten the values (using @value.mkString); however, in my case the code seems to work fine as is. My ignorance about Scala prevents me from delving deeper into what's happening under the hood, but I'm guessing that in each iteration, the first element from the array is returned, which should work as far as HTTP request parameters in my application are concerned. If I ever test this with edge cases, I will update this post.

Community
  • 1
  • 1
Lord Zuthulu
  • 143
  • 1
  • 7
  • With this code you are just iterate over the map and generate _many_ hidden inputs. You generate hidden inputs for all request parameters _including_ visible ones, effectively duplicating them. Depending on browser and position of this code, hidden param may overwrite user input, resulting incorrect app behavior. From my perspective, it is much better to use single hidden input to store all undermenistic data inside. I shown in my answer how to generate it. – Viktor Aseev Oct 22 '13 at 22:14
  • I account for the dupplication issue by excluding visible form inputs with when generating the hidden inputs. But you have a point and this is not a very clean solution. However, like I said, your answer still doesn't address my main problem. I can't simply pass request.queryString as @data, since it returns a Map (having the string representation of String[] all over the place wouldn't be very helpful). I would have to convert the Map to String using a custom utility method and perform the reverse on the server side, which does not look loke a cleaner solution to me. – Lord Zuthulu Oct 24 '13 at 17:19