Статьи

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

В моем предыдущем посте я рассмотрел использование JavaScript-маршрутизации в Java-приложениях Play 2. Вот версия Scala. Это в значительной степени копия предыдущей статьи, чтобы сделать ее независимой.

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

обзор

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

1
GET    /foo    controllers.Application.getAll()

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

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

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

1
2
3
4
5
6
7
8
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, сгенерированные серверной частью, и поэтому мы можем заменить приведенный выше код на

1
2
3
4
5
6
7
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 в качестве параметров запроса.

1
GET /foo?personId=personId&taskId=taskId

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

1
GET /foo/personId/taskId

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

Основы

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

1
play new jsRoutingScala

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
_ __ | | __ _ _  _| |
| '_ \| |/ _' | || |_|
|  __/|_|\____|\__ (_)
|_|            |__/
 
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/jsRoutingScala
 
What is the application name? [jsRoutingScala]
>
 
Which template do you want to use for this new application?
 
  1             - Create a simple Scala application
  2             - Create a simple Java application
 
> 1
OK, application jsRoutingScala is created.
 
Have fun!

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package models
 
import anorm._
import anorm.SqlParser._
import play.api.Play.current
import play.api.db.DB
import play.api.libs.json._
import anorm.~
 
case class Person(id: Pk[Long] = NotAssigned, name: String)
 
object Person {
 
  val simple = {
    get[Pk[Long]]("person.id") ~
      get[String]("person.name") map {
      case id~name => Person(id, name)
    }
  }
 
  def insert(person: Person) = {
    DB.withConnection { implicit connection =>
      SQL(
        """
          insert into person values (
            (select next value for person_seq),
            {name}
          )
        """
      ).on(
        'name -> person.name
      ).executeInsert()
    } match {
      case Some(long) => long
      case None       => -1
    }
  }
 
  def delete(id: Long) = {
    DB.withConnection { implicit connection =>
      SQL("delete from person where id = {id}").on('id -> id).executeUpdate()
    }
  }
 
  def getAll: Seq[Person] = {
    DB.withConnection { implicit connection =>
      SQL("select * from person").as(Person.simple *)
    }
  }
 
  implicit object PersonReads extends Reads[Person] {
    def reads(json: JsValue): JsResult[Person] =
      JsSuccess[Person](Person(NotAssigned, (json \ "name").as[String]), JsPath())
  }
 
  implicit object PersonWrites extends Writes[Person] {
    def writes(person: Person) = Json.obj(
      "id" -> Json.toJson(person.id.get),
      "name" -> Json.toJson(person.name)
    )
  }
}

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

1
2
db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"

Поскольку в этом примере используется Anorm, нам нужно настроить базу данных самостоятельно. В папке conf создайте папку с именем evolutions, которая содержит другую папку по умолчанию . Создайте здесь файл с именем 1.sql и скопируйте в него следующее:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
# --- !Ups
create table person (
  id bigint not null,
  name varchar(255),
  constraint pk_person primary key (id))
;
 
create sequence person_seq;
 
# --- !Downs
SET REFERENTIAL_INTEGRITY FALSE;
 
drop table if exists person;
 
SET REFERENTIAL_INTEGRITY TRUE;
 
drop sequence if exists person_seq;

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

Контроллер

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def getAll() = Action {
  Ok(Json.toJson(Person.getAll))
}
 
def delete(id: Long) = Action {
  Person.delete(id)
  Ok("Deleted " + id)
}
 
def create = Action { implicit request =>
  request.body.asJson match {
    case None => BadRequest
    case Some(json: JsValue) => {
      val person: Person = json.as[Person]
      val id = Person.insert(person)
      Ok(Json.obj(
        "id" -> id,
        "name" -> person.name
      ))
    }
  }
}
 
def jsRoutes = Action { implicit request =>
  Ok(Routes.javascriptRouter("appRoutes")(
    controllers.routes.javascript.Application.create,
    controllers.routes.javascript.Application.delete,
    controllers.routes.javascript.Application.getAll))
    .as("text/javascript")
}

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

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

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

Пока все нормально. У нас есть бизнес-логика, к которой можно получить доступ через HTTP-вызовы, но нет специальной поддержки JS. Нам нужно добавить еще один метод в контроллер, который указывает, какие маршруты мы хотим быть доступными в объекте маршрутизации JS.

1
// jsRoutes

Этот метод создаст файл JavaScript, который можно загрузить в клиент. Мы увидим это, когда доберемся до вида. Поскольку это то, что вызывается клиентом, нам нужно добавить еще одну запись в маршруты. Крайне важно отметить расположение маршрута — он должен предшествовать существующей записи «/ assets / * file», иначе этот маршрут будет использовать запрос.

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

Вид

Теперь мы добрались до места действия. Первым шагом является ознакомление нашего представления с кодом маршрутизации JS, и мы делаем это, просто добавляя тег script в views / main.scala.html

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

Мы наконец готовы использовать наш объект маршрутизации JS. Следующий код представляет собой базовое одностраничное приложение, которое подключается к функциям получения, создания и удаления серверной части. Скопируйте и вставьте этот код в существующий файл views / index.scala.html, а затем посмотрите, что он делает. Вы можете заметить, что этот код идентичен коду в примере с Java.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@(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>
}

Здесь есть три строки, которые будут генерировать звонки на сервер, а именно

  • appRoutes.controllers.Application.delete (ID) .ajax
  • appRoutes.controllers.Application.create (). Ajax
  • appRoutes.controllers.Application.getAll (). Ajax

Давайте возьмем delete в качестве примера, так как он принимает параметр.

  • appRoutes
    • Взгляните на свой класс Application, и вы увидите имя appRoutes, которое дается JS-маршрутизатору. Это формирует основу пространства имен объекта маршрутизации JS и означает, что вы можете иметь несколько объектов маршрутизации JS с разных контроллеров, импортированных в одно и то же представление, поскольку вы можете сохранять имена каждого из них уникальными.
  • controllers.Application
    • Это полное имя целевого контроллера. Обратите внимание, что он соответствует FQN объекта Scala.
  • удаление (ID)
    • Вызов метода, включая параметры. Эта функция возвращает объект с именем ajax, который можно использовать для управления поведением HTTP-вызова.
  • Аякса
    • Эта функция делает звонок на сервер. Именно здесь вы добавляете обратные вызовы об успешном завершении и об ошибке, меняете тип содержимого запроса в соответствии с вашими внутренними требованиями, добавляете данные PUT или POST и т. Д.

Резюме

Это казалось большой работой, но большая часть работ создавала проект. Фактическая маршрутизация JS представляет собой очень небольшие временные вложения, но с довольно хорошей отдачей. Очень легко модифицировать существующий код, чтобы использовать этот подход, и очень просто продолжать в этом стиле, как только вы почувствуете преимущества.

Пример приложения

Ява

В этой статье рассказывается, как использовать эту функцию с точки зрения Scala. В одной статье также продемонстрирована поддержка Java. Различия между ними минимальны и отсутствуют с точки зрения клиента. Вы можете прочитать эту статью здесь .

Ссылка: JavaScript-маршрутизация в Play 2 (издание Scala) от нашего партнера по JCG Стива Чалонера из блога Objectify .