Статьи

JavaScript маршрутизация в Play 2 (издание Java)

Одной из самых приятных функций в Play 2, которая пока не получила широкого распространения, является маршрутизация JavaScript, которая может быть сгенерирована платформой для повышения удобства сопровождения клиентского кода на основе AJAX. Здесь я расскажу об использовании этой функции в приложении Play 2 на основе Java.

обзор

В JavaScript часто встречаются вызовы, выполняющие запросы AJAX. Часто для поддержки используется библиотека, такая как jQuery. Например, учитывая маршрут

GET    /foo    controllers.Application.getAll()

запрос GET может быть сделан с использованием сокращенного метода $ .get.

$.get("/foo", function( data ) {
  // do something with the response
});

Вариант, в случае, когда требуются параметры, требует немного больше работы.

GET    /foo    controllers.Application.get(personId: Long, taskId: Long)

var personId = $('#person').val();
var taskId = $('#item').val();
$.get("/foo?person=" + personId + '&task=' + taskId, 
      function( data ) {
        // do something with the response
      });

Все это кажется далеко от простого стиля приложений Play, где взаимодействие является идиоматическим и безопасным. К счастью, Play предлагает функцию маршрутизации JavaScript. Это позволяет нам использовать объекты JS, сгенерированные серверной частью, и поэтому мы можем заменить код выше

var personId = $('#person').val();
var taskId = $('#item').val();
appRoutes.controllers.Application.get(personId, taskId).ajax({
  success: function( data ) {
    // do something with the response
  }
})

Это приведет к GET-запросу к / foo с personId и taskId в качестве параметров запроса.

GET /foo?personId=personId&taskId=taskId

Изменение файла маршрута для использования URL-адреса в стиле RESTful  / foo /: personId /: taskId  приведет к следующему вызову без изменений в вашем коде JS:

GET /foo/personId/taskId

Давайте посмотрим, что нам нужно сделать, чтобы достичь этого.

Основы

Время создать наше основное приложение. С помощью командной строки создайте новое приложение Play

play new jsRoutingJava

Примите имя по умолчанию jsRoutingJava и выберите опцию для приложения Java.

 _ __ | | __ _ _  _| |
| '_ \| |/ _' | || |_|
|  __/|_|\____|\__ (_)
|_|            |__/

play! 2.1.5 (using Java 1.7.0_17 and Scala 2.10.0), http://www.playframework.org

The new application will be created in /tmp/jsRoutingJava

What is the application name? [jsRoutingJava]
> 

Which template do you want to use for this new application? 

  1             - Create a simple Scala application
  2             - Create a simple Java application

> 2
OK, application jsRoutingJava is created.

Have fun!

Это даст нам базовый контроллер под названием Application и пару представлений. Нам все еще нужно создать модельный класс, поэтому давайте сделаем это сейчас. В   каталоге приложения создайте пакет с именем  models . В пакете моделей создайте класс с именем Person.

package models;

import play.db.ebean.Model;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.List;

@Entity
public class Person extends Model
{
    private static Finder FIND = new Finder<>(Long.class, Person.class);
															
    @Id
    public Long id;
	
    public String name;
	
    public static List getAll()
    {
        return FIND.all();
    }
	
    public static Person getById(Long id)
    {
        return FIND.byId(id);
    }
}

Нам понадобится база данных, поэтому отредактируйте  файл conf / application.conf  и раскомментируйте следующие строки:

db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"
ebean.default="models.*"

Теперь мы готовы построить наш контроллер.

Контроллер

В   пакете контроллеров откройте класс Application. Уже есть метод индекса, но нам не нужно его менять.

Приложение позволит нам создавать, удалять и получать экземпляры Person, поэтому нам нужны методы для поддержки этих операций.

public static Result getAll()
{
    return ok(Json.toJson(Person.getAll()));
}

public static Result delete(Long id)
{
    Result result;
    Person person = Person.getById(id);
    if (person == null)
    {
        person.delete();
        result = ok(person.name + " deleted");
    }
    else
    {
        result = notFound(String.format("Person with ID [%d] not found", id));
    }
    return result;
}

public static Result create()
{
    JsonNode json = request().body().asJson();
    Person person = Json.fromJson(json, Person.class);
    person.save();
    return ok(Json.toJson(person));
}

Эти методы охватывают нашу бизнес-логику, поэтому мы можем добавить их в файл маршрутов

GET     /person       controllers.Application.getAll()
DELETE  /person/:id   controllers.Application.delete(id: Long)
POST    /person       controllers.Application.create()

Добавить поддержку маршрутизации JS

So far, so normal. We have business logic that can be accessed via HTTP calls, but no specialised JS support. We need to add another method to the controller that specifies which routes we want to be available in the JS routing object.

public static Result jsRoutes()
{
    response().setContentType("text/javascript");
    return ok(Routes.javascriptRouter("appRoutes", //appRoutes will be the JS object available in our view
                                      routes.javascript.Application.getAll(),
                                      routes.javascript.Application.delete(),
                                      routes.javascript.Application.create()));
}

This method will generate a JavaScript file that can be loaded into the client. We’ll see this when we get to the view. Because it’s something called from the client, we need to add another entry to the routes. It’s extremely important to note the placement of the route – it must precede the existing “/assets/*file” entry, otherwise that route will consume the request.

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

The view

Now we get to where the action is. The first step is to make our view aware of the JS routing code, and we do this by simply adding a script tag to views/main.scala.html

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

We’re finally ready to use our JS routing object. The following code is an utterly basic single-page app that hooks into the get, create and delete functionality of the back-end. Copy and paste this code into your existing views/index.scala.html file, and then take a look at what it’s doing.

@(message: String)

@main("Play 2 JavaScript Routing") {

    <fieldset>
        <form>
            <label for="personName">Name:
            <input type="text" name="personName" id="personName"/>
            <input type="button" value="Create" id="createPerson"/>
        </form>
    </fieldset>
    <ul id="peopleList">

    <script>
    var doc = $ (document);
    doc.ready (function() {
      // Delete a person
      doc.on ('click', '.deletePerson', function(e) {
        var target = $(e.target);
        var id = target.data('id');
        appRoutes.controllers.Application.delete(id).ajax( {
          success : function ( data ) {
            target.closest('li').remove();
          }
        });
      });

      // Create a new person
      $('#createPerson').click(function() {
        var personNameInput = $('#personName');
        var personName = personNameInput.val();
        if(personName && personName.length > 0) {
          var data = {
            'name' : personName
          };
          appRoutes.controllers.Application.create().ajax({
            data : JSON.stringify(data),
            contentType : 'application/json',
            success : function (person) {
              $('#peopleList').append('<li>' + person.name + ' <a href="#" data-id="' + person.id + '" class="deletePerson">Delete</a></li>');
                personNameInput.val('');
            }
          });
        }
      });

      // Load existing data from the server
      appRoutes.controllers.Application.getAll().ajax({
        success : function(data) {
          var peopleList = $('#peopleList');
          $(data).each(function(index, person) {
            peopleList.append('<li>' + person.name + ' <a href="#" data-id="' + person.id + '" class="deletePerson">Delete</a></li>');
          });
        }
      });
    }) ;
    </script>
}

There are three lines here that will generate calls to the server, namely

  • appRoutes.controllers.Application.delete(id).ajax
  • appRoutes.controllers.Application.create().ajax
  • appRoutes.controllers.Application.getAll().ajax

Let’s take delete as the example, since it takes a parameter.

  • appRoutes
    • Take a look at your Application class, and you’ll see the name appRoutes being given to the JS router. This forms the base of the namespace of the JS routing object, and means you can have multiple JS routing objects from different controllers imported into the same view because you can keep the names of each unique.
  • controllers.Application
    • This is the fully qualified name of the target controller. Note that it matches the FQN of the Java class.
  • delete(id)
    • The method call, including parameters. This function will return an object called ajaxwhich can be used to control the behaviour of the HTTP call.
  • ajax
    • This function makes the call to the server. It’s here that you add success and error callbacks, change the content-type of the request to match your back-end requirements, add PUT or POST data, etc.

Summary

That seemed like a lot of work, but most of it was creating the project. The actual JS routing represents a very small time investment, for a pretty good pay-off. It’s very easy to retrofit existing code to use this approach, and very simple to continue in this style once you experience the benefits.

Happy new year.

Example application

You can find the example code from this article on GitHub: https://github.com/schaloner/jsRoutingJava

Scala

This article covered how to use this feature from a Java viewpoint. In a sibling article, Scala support is also demonstrated. The differences between the two are minimal, and non-existent from a client-side perspective. The Scala article will be available soon.