Maintaining request parameters across controller and view in Play Framework - java

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.

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

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.

Related

Form with field options loaded from DB and validation

The situation is the following:
I have a JSP page with a form.
This form contains various <select> tags with options loaded from DB.
I want to use validation with an XML file.
The problem is the following: if I use an XML file and there are some errors in the form fields, the struts framework doesn't pass through the class method I laid out, but it will directly return the input result. So what's the point? That in this way I can't load the options for the various <select> tags I mentioned above.
So I thought to do something like this:
<result name="input" type="chain">
<param name="actionName">Class_method</param>
</result>
but with this trick I lose all the error messages, i.e. hasFieldErrors() returns always false.
How can I solve that?
Many questions, all good though.
Conversion and validation errors forces the Workflow interceptor to trigger the INPUT result, and the workflow will execute the INPUT result instead of reaching the action method (execute() or whatever).
If you need to populate some static data, like selectboxes sources, that must be available also in case of INPUT result, you should put that loading in a prepare() method, and make your action implement the Preparable interface. This method is run by an Interceptor before the INPUT result is returned, as described in the official docs.
Avoid using the chain result. It is officially discouraged since many years.
If you want to prevent double submits (by pressing F5 after a page has been submitted and the result rendered), you can use the PRG pattern with the redirectAction result. This way, however, you'd encounter the same problem of the chain result: the messages (and the parameters) will be lost.
To preserve the error messages, action errors and field errors across the redirections, you can use a predefined interceptor called Message Store Interceptor, that you must include in your stack because the defaultStack doesn't include it. I've described how it works in this answer.
If you decide to use the Message Store along with PRG there are more considerations, too long to be written here, but that could be explained in the future, about preventing infinite recursion due to Field Error -> INPUT -> PRG -> Retrieve Field Error -> INPUT -> etc... that will be blocked by the browser near the 10th recursion... but that's another story.
One option:
public class Foo extends ActionSupport {
public string myAction() { return SUCCESS; }
public void validateMyAction() { // executed after XML validation
// other complex validation here if needed
if (hasErrors()) {
// repopulate form data from DB here
}
}
}
hasErrors() method comes from the ValidationAware interface which ActionSupport implements.
Another option is to do a redirect on input result and use the message store interceptor to keep action messages

Spring web flow how to add flash attribute when exiting from flow to external redirect

I'm new to Spring Web Flow (2.3.1) and this is what I'm trying to do:
I have a JSP page which lists a paginated table of Books at the bottom of the page for the Author selected at the top of the page. I have a button/link at the top 'Add Book' (just below the Author drop-down) clicking which launches a Spring web flow that takes to a page where user can enter details of the Book in steps 1, 2 & 3 (in 3 different views/pages). Clicking Save creates the new Book and should take the user back to the view with paginated list of books.
Now, I want to add a flash attribute (success message) after the Save action and take user back to Books page (paginated), have the 'previous' author pre-selected and show the success message.
I have the following the web flow XML for the end state:
<end-state id="home" view="externalRedirect:/books/" >
<output name="author" value="book.author" />
</end-state>
The reason I'm doing the externalRedirect is that I want the URL to read as if the user just clicked on the Books listing page after adding a new Book. If I don't do the redirect but instead point to the view name from tiles.xml I see the flash message correctly but the URL still shows the web flow e.g., ?execution=e1s1. In both cases the author is not automatically selected.
How do I preserve the flash success message AND the Author selection after a redirect?
Does output variable have any meaning in an external redirect?
I'm also setting the following in the Save action:
requestContext.getFlowScope().put("authorId", book.getAuthorId());
the 'output' tag is meant to be used as a container to transfer pojos between subflows flows and the parent flow callers. However, the following enhancement request:
https://jira.spring.io/browse/SWF-1561
expanded the role of the 'output' tag to allow the transfer of flash variables from the 'end-state' of a webflow to a spring MVC controller's flashMap.
Unfortunately, this enhancement did NOT include what you want to achieve which is passing variables via an externalRedirect from flow A -> new Flow A. So as with any 3rd party lib that doesn't have the desired functionality... we're going to have to 'hack' it.
1. First you need to have your FlowHandlerAdapter configured like this to utilize the enhancement above:
the "saveOutputToFlashScopeOnRedirect" set to true is the important part for this discussion (set to false by default).
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
<property name="flowExecutor" ref="flowExecutor"/>
<property name="saveOutputToFlashScopeOnRedirect" value="true"/>
</bean>
2. You will need to create (extend) and configure a FlowExecutionListenerAdapter.
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.core.collection.SharedAttributeMap;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.execution.FlowExecutionListenerAdapter;
import org.springframework.webflow.execution.RequestContext;
public class TestFlowExecutionListenerAdapter extends FlowExecutionListenerAdapter{
#Override
public void sessionCreating(RequestContext context, FlowDefinition definition) {
MutableAttributeMap<Object> flashScopeWebFlow = context.getFlashScope();
MutableAttributeMap<Object> flashMapWebFlow = context.getFlowScope();
SharedAttributeMap<Object> sessionMap = context.getExternalContext().getSessionMap();
MutableAttributeMap<Object> requestMap = context.getExternalContext().getRequestMap();
HttpServletRequest request = (HttpServletRequest)context.getExternalContext().getNativeRequest();
Map<String, ?> flashMapMvc = RequestContextUtils.getInputFlashMap(request);
if(flashMapMvc != null)
putAllFlashMapToFlashScope(flashMapMvc,flashScopeWebFlow);
System.out.println("here");
}
public static void putAllFlashMapToFlashScope(Map<String, ?> map, MutableAttributeMap<Object> mutableAttributeMap) {
for( Entry<String, ?> entry : map.entrySet()) {
mutableAttributeMap.put(entry.getKey(), entry.getValue());
}
}
}
And init the bean like this:
<webflow:flow-executor id="flowExecutor" flow-registry="flowRegistry">
<webflow:flow-execution-listeners>
<webflow:listener ref="testFlowExecutionListenerAdapter" />
</webflow:flow-execution-listeners>
</webflow:flow-executor>
<bean id="testFlowExecutionListenerAdapter" class="com.foo.bar.flowexeclisteners.TestFlowExecutionListenerAdapter"/>
Note: Change "com.foo.bar.flowexeclisteners" to your actual package path.
3. The FlowExecutionListenerAdapter above allows to to monitor life cycle events of Spring Webflow framework by #Overriding certain methods. In our case, I chose to #Override the sessionCreating() method but you can use from many different methods to meet your use case or to increase efficiency.
http://docs.spring.io/spring-webflow/docs/current/api/org/springframework/webflow/execution/FlowExecutionListenerAdapter.html
So given the above configuration we can now begin passing back variables in 2 ways. One via sessionMap or Two via the 'output' tag which is now configured to be stored in the flashMap (of Spring MVC not flashScope). Here is an example 'end-state' demonstrating this.
<end-state id="home" view="externalRedirect:/books/">
<on-entry>
<evaluate expression="externalContext.sessionMap.put('author', 'William Brian Jennings')"/>
</on-entry>
<output name="responseMsg" value="'Added author to the sessionMap'"/>
</end-state>
4. When this 'end-state' is triggered (if you place a break point inside our FlowExecutionListenerAdapter) you will see that flashMapMvc will hold your 'responseMsg' variable and that 'author' variable will be in the sessionMap. The static method 'putAllFlashMapToFlashScope' will automatically make your 'responseMsg' avaliable for consumption by your view but for the sessionMap you will need to explicitly extract your variable out in your receiving flow like so:
<set name="flowScope.author" value="externalContext.sessionMap.get('author')"/>
A few notes here:
The hacky part of all this is we have to convert the flashMapMvc to flashScopeWebFlow because they are not compatible (2 different containers). I used a static method 'putAllFlashMapToFlashScope()' within the FlowExecutionListenerAdapater class to demonstrate this. I don't think this is a good practice but I just did it here for brevity so you can see what exactly what the issues are and how to solve them.
sessionMap variables will stay during the entire session and across all flows. If you use this map becareful of this point.
I included other (unused) maps in the FlowExecutionListenerAdapter to demonstrate what you have access to.
Obviously this is a hack solution and what really needs to happen is an enhancement request to achieve what your trying to do (flow A ends) -> pass output back to -> (new flow A) via external redirect.
Because of issues like this I've personally stopped using WebFlow for all use cases and have limited it only to simple cases (i.e page A -> Page B -> page C) and now I use Spring MVC exclusively for all other use cases.
Sorry for the long answer but this is not a trivial issue :)

Playframework 2.2 : How to pass argument directly to another method inside of view file?

This code below has been extracted from http://www.playframework.com/documentation/2.1-RC2/JavaFormHelpers
#(myForm: Form[User])
#helper.form(action = routes.Application.submit()) {
#helper.inputText(myForm("username"))
#helper.inputPassword(myForm("password"))
}
I have two questions
How can I pass two values("username", "password") directly to the
"submit()" method? Like,
submit(inputText("username"), inputPasword("password")) ?
I've followed some tutorials then I've realized that "form
variables" were always declared as global in the controller. Just
like the code I included.
Why doesn't just binds to form locally and passes it directly to submit()?
Why should exist a global variable?
Is declaring the variable "myForm" as global in the controller is kind of good practice?
Thank you!
routes.Application.submit() in this example defines the URI where this form will send its contents upon clicking 'submit' button. inputText and inputPassword are form elements. When you post the form, all its contents are sent along already (in the request body) - you will be able to get their values via binding the form with the same structure to the request in routes.Application.submit() method.

Play 2.0/Java - Is there a way to get the validation done post request data binding?

In play 2.0 you can get the request binding with validation done (via annotations) by :
ABCForm abcForm=(ABCForm)form(ABCForm.class).bindFromRequest().get();
The problem I have is , I want to get the validation done after trimming the form values.
So is there a way to either defer or call the validation stuff post binding in play 2.0 ?
Binding and validation are combined. So validation after the binding is not possible, as far as I know. However you can create a validate() method, in which you trim your values before validating them. For example:
public class User {
public String name;
public String validate() {
name.trim
if(name == "") {
return "Name is required";
}
return null;
}
}
The validate() method will be invoked when you bind a form. So you can make sure your data is valid, but errors won't be automatically added to the Form.Field objects. So it is certainly a nice solution.
There are also pretty much discussions about Form validation in Play's Google Group, so if you want to know more about the binding/validation problems I recommend reading them: https://groups.google.com/forum/#!searchin/play-framework/%5B2.0%5D$20validation.
If you need to modify your values before validation. You can create a setter for your field and do your trims there.

Modifying FormInjector context information in Tapestry 5 dynamically

My current problem regards updating context information dynamically in FormInjector, my previous question Updating a zone inside a form in Tapestry 5 probably contains useful background information.
I added the following in my template.
<div t:type="FormInjector" t:id="injector" t:context="item.id"/>
And the following in my component class.
#OnEvent(component = "injector")
Block loadItemFields(String id) {
item = itemRepository.find(id);
return itemFieldsBlock;
}
Everything is working fine, new form fields appear, but the search is always done with the same id. I would like to change the id with JavaScript before triggering the event, but I don't know how to achieve this.
If there is additional information required I am happy to supply it.
Using the context parameter to pass a dynamic value wouldn't be my first option. (The FormInjector component generates a URL to trigger the event handler, which then includes the context - however, this is done when the component renders, and is not meant to be dynamic.)
I'd get rid of the context parameter and find a different way to submit the value. One possibility would be to submit the form via AJAX and trigger the injection in the callback:
this.myFormElement.observe('change', this.onChange.bindAsEventListener(this));
...
onChange: function(event) {
this.myFormElement.form.request({
onSuccess: this.afterFormSubmitted.bind(this)
});
},
afterFormSubmitted: function() {
this.formInjector.trigger();
}
That way, the value of the form element has been set on the server side when you trigger the form injection, and you can use it in your injection event handler.

Categories

Resources