jrabary
jrabary

Reputation: 2441

Parse ajax query with play querystringbind

By default, when one has a route to an action that takes a list parameter

GET /list controllers.Application.index(ids: List[Long])

Play expects that the request uri looks like

/list?ids=1&ids=2&ids=3

But Jquery.ajax by default sends request as

/list?ids%5B%5D=1&ids%5B5D=2&ids%5B%5D=3

Play has something called QueryStringBindable that may be used to change this behavior. Isn't it ? If it can be used to solve this issue, how to implements may custom querybinders ? Or is there another solutions ?

Upvotes: 0

Views: 125

Answers (1)

Steve Chaloner
Steve Chaloner

Reputation: 8202

You would be better using the JavaScript routing support in Play.

In your controller, add the following method

def jsRoutes = Action { implicit request =>
  Ok(Routes.javascriptRouter("jsRoutes")(
    controllers.routes.javascript.Application.index))
  .as("text/javascript")
}

Expose it in your routes file, above the /assets/*file entry

GET    /assets/js/routes     controllers.Application.jsRoutes()
GET    /assets/*file         controllers.Assets.at(path="/public", file)

Add a tag to your view to get the JavaScript it generates

<script src="@controllers.routes.Application.jsRoutes()" type="text/javascript"></script>

Finally, instead of using jQuery to make the AJAX call, you can use an object that is aware of the routing requirements:

jsRoutes.controllers.Application.index(id1, id2, etc).ajax( {
      success : function (data, textStatus, jqXHR ) {
        // do something on success 
      },
      error: function (jqXHR, textStatus, errorThrown) {
        // do something on error
      },
      complete: function(jqXHR, textStatus) {
        // do something on complete
      }
    });

There's an additional benefit here in that if you change your index() route to use path parameters instead of query parameters, no change is required in the JavaScript - the routing object will handle this for you.

References

Scala: http://www.objectify.be/wordpress/?p=746

Java: http://www.objectify.be/wordpress/?p=734

Edit: For single-page apps

There are two approaches you can use for single-page applications.

1. Create a controller that exposes every route you want to access for AJAX. For example, for the routes file

GET   /foo     controllers.FooController.foo()
GET   /bar     controllers.BarController.bar()

you can have a (e.g.) JsRoutesController controller that has a function

def jsRoutes = Action { implicit request =>
  Ok(Routes.javascriptRouter("jsRoutes")(
    controllers.routes.javascript.FooController.foo)),
    controllers.routes.javascript.BarController.bar))
 .as("text/javascript")
}

Update your routes

GET    /assets/js/routes     controllers.JsRoutesController.jsRoutes()

and then access it using

<script src="/js/routes" type="text/javascript"></script>

or 2. The JS object name is specified in the jsRoutes function (Routes.javascriptRouter("jsRoutes") in the above example). You can have a jsRoutes function per controller which exposes a controller-specific JS routing object.

In FooController:

def jsRoutes = Action { implicit request =>
  Ok(Routes.javascriptRouter("fooRoutes")(
    controllers.routes.javascript.FooController.foo))
 .as("text/javascript")
}

In BarController:

def jsRoutes = Action { implicit request =>
  Ok(Routes.javascriptRouter("barRoutes")(
    controllers.routes.javascript.BarController.bar))
 .as("text/javascript")
}

Update your routes file

GET    /assets/js/foo/routes     controllers.FooController.jsRoutes()
GET    /assets/js/bar/routes     controllers.BarController.jsRoutes()

And in your page

<script src="/js/foo/routes" type="text/javascript"></script>
<script src="/js/bar/routes" type="text/javascript"></script>

Nothing in either approach requires Play 2's view layer.

Upvotes: 1

Related Questions