# Invoking actions from Javascript In the last the last chapter of the tutorial, we implemented a number of new actions that are going to be the backend for the navigation drawer. In this chapter, we'll add the client side code necessary to complete the behavior for the navigation drawer. ## Javascript routes The first thing we need to do is implement a Javascript router. While you could always just make AJAX calls using hard coded URLs, Play provides a client side router that will build these URLs and make the AJAX requests for you. Building URLs to make AJAX calls can be quite fragile, and if you change your URL structure or parameter names at all, it can be easy to miss things when you update your Javascript code. For this reason, Play has a Javascript router, that lets us call actions on the server, from Javascript, as if we were invoking them directly. A Javascript router needs to be generated from our code, to say what actions it should include. It can be implemented as a regular action that your client side code can download using a script tag. Alternatively Play has support for embedding the router in a template, but for now we'll just use the action method. Write a Javascript router action in `app/controllers/Application.java`: ```java public static Result javascriptRoutes() { response().setContentType("text/javascript"); return ok( Routes.javascriptRouter("jsRoutes", controllers.routes.javascript.Projects.add(), controllers.routes.javascript.Projects.delete(), controllers.routes.javascript.Projects.rename(), controllers.routes.javascript.Projects.addGroup() ) ); } ``` We've set the response content type to be `text/javascript`, because the router will be a Javascript file. Then we've used `Routes.javascriptRouter` to generate the routes. The first parameter that we've passed to it is `jsRoutes`, this means the router will be bound to the global variable by that name, so in our Javascript/CoffeeScript code, we'll be able to access the router using that variable name. Then we've passed the list of actions that we want in the router. Of course, we need to add a route for that in the `conf/routes` file: GET /assets/javascripts/routes controllers.Application.javascriptRoutes() Now before we implement the client side code, we need to source all the javascript dependencies that we're going to need in the `app/views/main.scala.html`: ```html ``` ## CoffeeScript We are going to implement the client side code using CoffeeScript. CoffeeScript is a nicer to use Javascript, it compiles to Javascript and is fully interoperable with Javascript, so we can use our Javascript router for example from it. We could use Javascript, but since Play comes built in with a CoffeeScript compiler, we're going to use that instead. When we added the Javascript dependencies to `main.scala.html`, we added a dependency on `main.js`. This doesn't exist yet, it is going to be the artifact that will be produced from the CoffeeScript compilation. Let's implement it now, open `app/assets/javascripts/main.coffee`: ```coffeescript $(".options dt, .users dt").live "click", (e) -> e.preventDefault() if $(e.target).parent().hasClass("opened") $(e.target).parent().removeClass("opened") else $(e.target).parent().addClass("opened") $(document).one "click", -> $(e.target).parent().removeClass("opened") false $.fn.editInPlace = (method, options...) -> this.each -> methods = # public methods init: (options) -> valid = (e) => newValue = @input.val() options.onChange.call(options.context, newValue) cancel = (e) => @el.show() @input.hide() @el = $(this).dblclick(methods.edit) @input = $("") .insertBefore(@el) .keyup (e) -> switch(e.keyCode) # Enter key when 13 then $(this).blur() # Escape key when 27 then cancel(e) .blur(valid) .hide() edit: -> @input .val(@el.text()) .show() .focus() .select() @el.hide() close: (newName) -> @el.text(newName).show() @input.hide() # jQuery approach: http://docs.jquery.com/Plugins/Authoring if ( methods[method] ) return methods[ method ].apply(this, options) else if (typeof method == 'object') return methods.init.call(this, method) else $.error("Method " + method + " does not exist.") ``` Now the code that you see above may be a little overwhelming to you. The first block of code activates all the option icons in the page, it's straight forward jquery. The second is an extension to jquery that we'll use a bit later, that turns a span into one that can be edited in place. These are just some utility methods that we are going to need to help with writing the rest of the logic. Let's start to write our Backbone views: ```coffeescript class Drawer extends Backbone.View $ -> drawer = new Drawer el: $("#projects") ``` We've now bound our drawer, which has an id of `projects`, to a Backbone view called `Drawer`. But we haven't done anything useful yet. In the initialize function of the drawer, let's bind all the groups to a new `Group` class, and all the projects in each group to a new `Project` class: ```coffeescript class Drawer extends Backbone.View initialize: -> @el.children("li").each (i,group) -> new Group el: $(group) $("li",group).each (i,project) -> new Project el: $(project) class Group extends Backbone.View class Project extends Backbone.View ``` Now we'll add some behavior to the groups. Let's first add a toggle function to the group, so that we can hide and display the group: ```coffeescript class Group extends Backbone.View events: "click .toggle" : "toggle" toggle: (e) -> e.preventDefault() @el.toggleClass("closed") false ``` Earlier when we created our groups template, we added some buttons, including a new project button. Let's bind a click event to that: ```coffeescript class Group extends Backbone.View events: "click .toggle" : "toggle" "click .newProject" : "newProject" newProject: (e) -> @el.removeClass("closed") jsRoutes.controllers.Projects.add().ajax context: this data: group: @el.attr("data-group") success: (tpl) -> _list = $("ul",@el) _view = new Project el: $(tpl).appendTo(_list) _view.el.find(".name").editInPlace("edit") error: (err) -> $.error("Error: " + err) ``` Now you can see that are are using the `jsRoutes` Javascript router that we created before. It almast looks like we are just making an ordinary call to the `Projects.add` action. Invoking this actually returns an object that gives us a method for making ajax requests, as well as the ability to get the URL and method for the action. But, this time you can see we are invoking the `ajax` method, passing in the group name as part of the `data`, and then passing `success` and `error` callbacks. In fact, the `ajax` method just delegates straight to jQuery's `ajax` method, supplying the URL and the method along the way, so anything you can do with jQuery, you can do here. > You don't have to use jQuery with the Javascript router, it's just the default implementation that Play provides. You could use anything, by supplying your own ajax function name to call to the Javascript router when you generate it. Now if you refresh the page, you should be able to create a new project. However, the new projects name is "New Project", not really what we want. Let's implement the functionality to rename it: ```coffeescript class Project extends Backbone.View initialize: -> @id = @el.attr("data-project") @name = $(".name", @el).editInPlace context: this onChange: @renameProject renameProject: (name) -> @loading(true) jsRoutes.controllers.Projects.rename(@id).ajax context: this data: name: name success: (data) -> @loading(false) @name.editInPlace("close", data) error: (err) -> @loading(false) $.error("Error: " + err) loading: (display) -> if (display) @el.children(".delete").hide() @el.children(".loader").show() else @el.children(".delete").show() @el.children(".loader").hide() ``` First we've declared the name of our project to be editable in place, using the helper function we added earlier, and passing the `renameProject` method as the callback. In our `renameProject` method, we've again used the Javascript router, this time passing a parameter, the id of the project that we are to rename. Try it out now to see if you can rename a project, by double clicking on the project. The last thing we want to implement for projects is the remove method, binding to the remove button. Add the following CoffeeScript to the `Project` backbone class: ```coffeescript events: "click .delete" : "deleteProject" deleteProject: (e) -> e.preventDefault() @loading(true) jsRoutes.controllers.Projects.delete(@id).ajax context: this success: -> @el.remove() @loading(false) error: (err) -> @loading(false) $.error("Error: " + err) false ``` Once again, we are using the Javascript router to invoke the delete method on the `Projects` controller. As one final task that we'll do is add a new group button to the main template, and implement the logic for it. So let's add a new group button to the `app/views/main.scala.html` template, just before the closing `` tag: ```html ``` Now add an `addGroup` method to the `Drawer` class, and some code to the `initialize` method that binds clicking the `newGroup` button to it: ```coffeescript class Drawer extends Backbone.View initialize: -> ... $("#newGroup").click @addGroup addGroup: -> jsRoutes.controllers.Projects.addGroup().ajax success: (data) -> _view = new Group el: $(data).appendTo("#projects") _view.el.find(".groupName").editInPlace("edit") ``` Try it out, you can now create a new group. ## Testing our client side code Although we've tested manually that things are working, Javascript apps can be quite fragile, and easy to break in future. Play provides a very simple mechanism for testing client side code using [FluentLenium](https://github.com/FluentLenium/FluentLenium). FluentLenium provides a simple way to represent your pages and the components on them in a way that is reusable, and let's you interact with them and make assertions on them. ### Implementing page objects Let's start by creating a page that represents our login page. Open `test/pages/Login.java`: ```java package pages; import org.fluentlenium.core.*; import static org.fest.assertions.Assertions.assertThat; import static org.fest.assertions.fluentlenium.FluentLeniumAssertions.assertThat; import static org.fluentlenium.core.filter.FilterConstructor.*; import components.*; import controllers.*; public class Login extends FluentPage { public String getUrl() { return routes.Application.login().url(); } public void isAt() { assertThat(find("h1", withText("Sign in"))).hasSize(1); } public void login(String email, String password) { fill("input").with(email, password); click("button"); } } ``` You can ee three methods here. Firstly, we've declared the URL of our page, conveniently using the reverse router to get this. Then we've implemented an `isAt` method, this runs some assertions on the page to make sure that we are at this page. FluentLenium will use this when we go to the page to make sure everything is as expected. We've written a simple assertion here to ensure that the heading is the login page heading. Finally, we've implemented an action on the page, which fills the login form with the users email and password, and then clicks the login button. > You can read more about FluentLenium and the APIs it provides [here](https://github.com/FluentLenium/FluentLenium). We won't go into any more details in this tutorial. Now that we can log in, let's create a page that represents the dashboard in `test/pages/Dashboard.java`: ```java package pages; import org.fluentlenium.core.*; import static org.fest.assertions.Assertions.assertThat; import static org.fest.assertions.fluentlenium.FluentLeniumAssertions.assertThat; import static org.fluentlenium.core.filter.FilterConstructor.*; import components.*; public class Dashboard extends FluentPage { public String getUrl() { return "/"; } public void isAt() { assertThat(find("h1", withText("Dashboard"))).hasSize(1); } public Drawer drawer() { return Drawer.from(this); } } ``` It is similarly simple, like the login page. Eventually we will add more functionality to this page, but for now since we're only testing the drawer, we just provide a method to get the drawer. Let's see how implementation of the drawer, in `test/components/Drawer.java`: ```java ackage components; import java.util.*; import org.fluentlenium.core.*; import org.fluentlenium.core.domain.*; import static org.fest.assertions.Assertions.assertThat; import static org.fest.assertions.fluentlenium.FluentLeniumAssertions.assertThat; import static org.fluentlenium.core.filter.FilterConstructor.*; public class Drawer { private final FluentWebElement element; public Drawer(FluentWebElement element) { this.element = element; } public static Drawer from(Fluent fluent) { return new Drawer(fluent.findFirst("nav")); } public DrawerGroup group(String name) { return new DrawerGroup(element.findFirst("#projects > li[data-group=" + name + "]")); } } ``` Since our drawer is not a page, but rather is a component of a page, we haven't extended `FluentPage` this time, rather we are simply wrapping a `FluentWebElement`, this is the `