В моем предыдущем посте я рассмотрел использование 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.orgThe new application will be created in /tmp/jsRoutingScalaWhat 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> 1OK, 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 modelsimport anorm._import anorm.SqlParser._import play.api.Play.currentimport play.api.db.DBimport 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.Driverdb.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
|
# --- !Upscreate table person ( id bigint not null, name varchar(255), constraint pk_person primary key (id));create sequence person_seq;# --- !DownsSET 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 представляет собой очень небольшие временные вложения, но с довольно хорошей отдачей. Очень легко модифицировать существующий код, чтобы использовать этот подход, и очень просто продолжать в этом стиле, как только вы почувствуете преимущества.
Пример приложения
- Вы можете найти пример кода из этой статьи на GitHub: https://github.com/schaloner/jsRoutingScala
Ява
В этой статье рассказывается, как использовать эту функцию с точки зрения Scala. В одной статье также продемонстрирована поддержка Java. Различия между ними минимальны и отсутствуют с точки зрения клиента. Вы можете прочитать эту статью здесь .