Binding an html form action to a controller method

2019-08-10 07:27发布

问题:

In my Find controller I have a method like:

public Result findLatest(String repoStr) {
    ............
}

Which is linked through a route:

GET     /latest                     controllers.Find.findLatest(repo: String)

Then, I have a form in a view like:

<form action="@routes.Find.findLatest()" method="get">
    ....
    <select name="repo">....</select>
</form>

But obviously that is failing, because it is expecting some parameters that I do not fulfill in the action. What is the correct way to do this without having to end up leaving the findLatest method taking no parameters in my controller?

回答1:

You could change the routes to accept an empty string:

GET     /latest/:repo          controllers.Find.findLatest(repo: String = "")

Then configure your controller function to handle empty string.

That way,

<form action="@routes.Find.findLatest()" method="get">
....
<select name="repo">....</select>

will evaluate repo as an empty string at the controller level.



回答2:

Edit: Support for this implementation was dropped in Play v 2.1

You may be interested in Play's Optional parameters e.g. play.libs.F.Option[String]

Example: How to handle optional query parameters in Play framework

GET     /latest/:repo/:artifact     controllers.Find.findLatestArtifact(repo: play.libs.F.Option[String], artifact: play.libs.F.Option[String])

This will allow you flexibility in which arguments need to be provided.

Not sure which language you're using but the link above contains an example for scala and the method declaration in java would look something like:

import play.libs.F.Option;
public static Result findLatestArtifact(Option<String> repo, Option<String> artifact){ ... }

and updated implementation 2.1 Routes with optional parameter - Play 2.1 Scala

EDIT: play 2.1+ Support : Props to @RobertUdah below

Initializing to null:

GET     /latest/               controllers.Find.findLatest(repo: String = null)
GET     /latest/:repo          controllers.Find.findLatest(repo: String)

<form action="@routes.Find.findLatest()" method="get">


回答3:

Normally all form data go in the body and you can retrieve them in your action method with bindFromRequest() (see docs).

If you really want to pass one form element as a part of the URL then you have to dynamically compose your URL in JavaScript and change your route.

Your route could look like:

GET     /latest/:repo                     controllers.Find.findLatest(repo: String)

And the JavaScript part like (I didn't actually test the code):

<form name="myform" action="javascript:composeUrl();" method="get">
   ....
   <select name="repo">....</select>
</form>

<script>
  function submitform() {
    var formElement = document.getElementsByName("myform");
    var repo = formElement.options[e.selectedIndex].text;
    formElement.action = "/lastest/" + repo;
    formElement.submit();
  }
</script>


回答4:

Cavice suggested something close to what I consider the best solution for this (since F.Option are not supported anymore with the default binders in Play 2.1 ).

I ended up leaving the route like:

GET     /latest                     controllers.Find.findLatest(repo=null)

and the view like:

<form action="@routes.Find.findLatest(null)" method="get">
    <select name="repo"> .... </select>
....
</form>

and in the controller:

public Result findLatest(String repoStr) {
    if(repoStr==null) {
        repoStr=Form.form().bindFromRequest().get("repo");
.....

This allows me to have a second route like:

GET     /latest/:repo                     controllers.Find.findLatest(repo: String)