Play 2.x: How to make an AJAX request with a common button
Asked Answered
T

5

60

So I have successfully gotten AJAX requests to work before but I have always had to use a form, and then at the end of the submit do return false so that it doesn't refresh the page.

I have also just recently moved my JavaScript into a separate file this has caused my @ commands to fail. Because of this I do not no how to set my URL to my route?

HTML:

<button id="saveAsDefaultButton">Save as default</button>

Playframework Java code:

public static Result saveDefaultPhoneForUser(String handset) {
    User currentUser = User.findByName(session("name"));
    currentUser.lastControlledHandset = theHandset;
    currentUser.save();
    return ok();
}

routes:

POST    /                           controllers.Application.saveDefaultPhoneForUser(handset : String)

javascript:

$('#saveAsDefaultButton').click(function(evt) {
        $('#errors').hide();
        $.ajax({
            type : 'POST',
            url : "controllers.Application.saveDefaultPhoneForUser",
            data : $('#controlledPhone option:selected').text(),
            dataType : "text",
            success : function(data) {
                //setError('Call succedded');
                //$('#test1').attr("src", data)
            },
            error : function(data) {
                setError('Make call failed');
            }
        });
        return false;
    });

I'm sure there is a way to do this but I am just having no luck finding anything.

Tiphanie answered 21/6, 2012 at 6:58 Comment(0)
D
124

For this job you should go with javascriptRoutes as it generates correct JS paths based on your routes.conf. You'll find usage sample in Zentask sample

Anyway, for now you can fix your AJAX call by changing the url to

url : '@routes.Application.saveDefaultPhoneForUser()',

This way requires it to place the whole JS in template, which is wrong. It can or even should be moved to separate JS file and to make it possible you need to use javascriptRoutes.

More...

javascriptRoutes are not described yet in official documentation, but here's step-by-step introduction to it. Although the description looks sophisticated de facto using this way brings a lot of benefits.

1. Create the common routes

First you need to create common routes in conf/routes file:

GET     /item/:id     controllers.Application.getItem(id: Long)
POST    /item/new     controllers.Application.newItem
PUT     /item/:id     controllers.Application.updateItem(id: Long)

Of course, you need to create at least these three actions in Application controller:

  • getItem(Long id){ ... }
  • newItem() { ... }
  • updateItem(Long id) { ... }

2. Create an action translating common routes to JS

  • place it somewhere, ie. in your Application controller
  • Let's call it javascriptRoutes()

In that action you'll point the existing routes from the conf/routes file

public static Result javascriptRoutes() {
    response().setContentType("text/javascript");
    return ok(
        Routes.javascriptRouter("myJsRoutes",
            routes.javascript.Application.getItem(),
            routes.javascript.Application.newItem(),
            routes.javascript.Application.updateItem(),
            //inside somepackage
            controllers.somepackage.routes.javascript.Application.updateItem()
        )
    );
}

Note: Don't set any params in brackets.

3. Create a route for javascriptRoutes action and include it in your template

Route conf/routes

GET     /javascriptRoutes     controllers.Application.javascriptRoutes

View in <head> part of /views/main.scala.html

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

4. Use javascriptRoutes where you want

Up from now you can use routes in JS to get the correct path without need to specify the url and type. For an example instead of:

 $('.getAjaxForThisContainer').click(function(e) {
    var idToGet = $("#someField").val();
    $.ajax({
        type : 'GET',
        url : '@routes.Application.getItem()',
        data : {
            id: idToGet
        },
        success : function(data) {
            // ... some code on success
        }
    });
    return false;
});

you can use simplified version (myJsRoutes from point 2):

myJsRoutes.controllers.Application.getItem(idToGet).ajax({
    success : function(data) { ... some code ... }
});

or

myJsRoutes.controllers.Application.newItem().ajax({
    success : function(data) { ... some code ... }
});

etc...

  • you don't need to specify type: "POST" - JS router will use correct method according to conf/routes rule
  • you can set id of the record (or other params) to GET or PUT (or other methods) using routes-like syntax in pure JS
  • If your route rule contains all required params you can really minimize yours JS:

for route:

GET   /some/:a/:b/:c    controllers.Application.getABC(a: String, b: Integer, c: String)

JS:

myJsRoutes.controllers.Application.getABC("a", 1, "b" ).ajax({});
Dirkdirks answered 21/6, 2012 at 7:36 Comment(5)
Thank you very much Marcus. I did have a look at the zen task example but as I am just getting back into web dev, so refreshing my html, css, javascript knowledge. When that example was done in coffee script it put me off a fair bit. And when I tried it, I couldn't get it to work.Tiphanie
Also marcus just a quick question if in my action I do some server side validation and then return badRequest("Incorrect data sent"); I also put error catching in my javascript after the success: error : function(data) { setError('Make call failed'); } How do I get access to the String that I passed. When I put the error message into a receive i get the result 'object [Object]'. Or do you know of a way for me to debug/inspect this information?Tiphanie
javascript routes in official documents: playframework.com/documentation/2.1.1/JavaGuide6Dillman
Not everyone may would like to use CoffeScript.. and this is what this official doc is coming with.Spates
@Dirkdirks one thing I did not understand from the answer was if the ajax call with url : '@routes.Application.getItem()', should in theory work. I tried here and it didn't, play accuses that the argument is missing. The reason why I ask is because I was thinking of combining it with jqGrid in a similar fashion to what is done hereIndignity
S
9

I like "old good" simple way:

1 In the page, with JQuery,

inside some even-handler

 $.get('/giveme', {'arg': value}, function(data) {

                                    window.alert(data);

                                  }
 );

2 On the server/play side

2.1 Routes: GET /giveme controllers.Application.giveme(arg)

2.2 scala action:

object Application extends Controller { 

      def giveme(arg:String) = Action {
        Ok(Json.toJson("hello," + arg ))
      }

    }
Spates answered 18/8, 2013 at 3:27 Comment(1)
can i use multiple argument in function(data). like function(data, data1).Tramel
S
7

Something to watch out for... when calling

Routes.javascriptRouter("myJsRoutes",

Routes is imported from play.Routes, not play.api.Routes

Had an error when I implemented this code, because I assumed it would came from api (clearly I was wrong). Other than that, everything works great~!!

Surakarta answered 30/1, 2013 at 4:33 Comment(0)
I
2

There is a simpler solution, use Embedded router to generating Javascript router. You can generate Javascript router using @javascriptRouter helper directive, place the following code inside your html page head tag (maybe inside the main decorating template)

<head>
    <title>Saeed Title</title>
    <link rel="shortcut icon" type="image/png" href="@routes.Assets.at("images/favicon.png")">
    <link rel="stylesheet" media="screen" href="@routes.Assets.at("stylesheets/main.css")">
    <script src="@routes.Assets.at("javascripts/jquery/v1.12.4/jquery.min.js")" type="text/javascript"></script>
    @helper.javascriptRouter("jsRoutes")(
        routes.javascript.MyController.getById,
        routes.javascript.MyController.getByName
    )
</head>

Don't worry about syntax error (in Intellij) and don't use parenthesis after your action name. Now you can use jsRoutes.controllers.MyController.getById(id) in your ajax call code.

$.ajax(jsRoutes.controllers.MyController.getById(id))
   .done( /*...*/ )
   .fail( /*...*/ );

or use jsRoutes.controllers.MyController.getById(id).url to get url. more information.

Iyre answered 19/7, 2016 at 10:3 Comment(0)
T
1

Marcus' answer is very good, so anyone else having this issue should be able to use this.

I did have one issue though when trying to get this to work which took me a little while to get working. When I was making my ajax request it was routing to the wrong method.

This was because in my routes file I had the following:

POST    /                           controllers.Application.makeCall()
POST    /                           controllers.Application.saveDefaultPhoneForUser(handset : String)

By having two post methods with the same location /. It seemed to always go to make call. So just a tip for anyone don't do this otherwise it wont work.

I just needed to change it to:

POST    /                           controllers.Application.makeCall()
POST    /saveDefaultPhoneForUser    controllers.Application.saveDefaultPhoneForUser(handset : String)

Hope this helps someone.

Tiphanie answered 21/6, 2012 at 23:42 Comment(1)
That's normal, if routes file contains two rules with the same method and path only first will be used.Dirkdirks

© 2022 - 2024 — McMap. All rights reserved.