Lift — современный фреймворк на языке Scala, предназначенный для создания веб-приложений и предлагающий нестандартные подходы для решения некоторых задач. В данной заметке будет рассмотрен простейший пример (до)загрузки данных с сервера при помощи AJAX-запросов. Пост написан в надежде, что он может быть кому-то полезен и сократить время поиска решения на часик-другой, да и просто в качестве демонстрации возможностей отличного современного, но пока не очень популярного фреймворка. Но я предполагаю, что у читателя уже есть некоторые базовые представления о Lift и о некоторых концепциях, в нем применяемых.
В качестве примера возьмем приложение, которое должно отображать «комментарии» — при загрузке страницы первые пять, а загрузку остальных — по запросу, «порциями» по пять штук, без перезагрузки страницы, конечно же.
Итак, начнем. Пускай, у нас есть модель Post, представляющая единичный комментарий:
и соответствующий companion object:
в котором уже объявлены две функции — получение общего количества объектов и получение некоего настраиваемого диапазона комментариев. В нашем случае можно было обойтись и без этих методов и строить запросы «по-месту» — прямо в коде сниппетов, но, по моему скромному мнению, это затруднит тестирование и перегрузит код.
Теперь надо заняться первоначальным отображением комментариев. Для этого создаем следующий сниппет:
И соответствующий embeded-шаблон, который располагается в /templates/__post.html (да, нижние подчеркивания даже в Sсala/Lifft иногда бывают нужны):
и который подключается на требуемой странице с помощью:
Хочу заметить, что в данном случае я использую «устаревшую» разметку шаблонов, основанную на спец. xml-тегах, которая в данном случае мне кажется более удобной и наглядной.
В результате, после добавления модели в схему БД, заполнения записей и т.д, получаем примерно такой результат:
всего 5 объектов, как и требовалось. Теперь надо организовать загрузку остальных комментариев.
Перед тем, как мы продолжим, стоит несколько слов сказать о JsCmd — фактически, подсистеме в Lift, которая предназначена для генерации клиентского JavaScript на стороне сервера, в коде сниппетов и с последующей вставкой их в ответ. Существуют различные способы инжектирования JS-кода — от метода JsRaw, позволяющего вставлять «сырой» JS, до прокси-методов для JS-фреймворков. В первую очередь, JsCmd предназначен для реализации небольших по объему частей JS-кода.
Модифицируем вставку комментариев, добавив туда вызов сниппета для отображения кнопки загрузки дополнительных объектов:
и класс сниппета:
В класс были введены значения для представления загружаемых «страниц» с комментариями — метод renderPosts использует их первоначальные значения для отображения первых 5 записей, а так же новый метод renderLoadMoreControl, который добавляет кнопку для загрузки следующей «порции» комментариев и связывает ее с внутренней функцией loadPosts, которая и представляет собой реализацию AJAX-ответа. Рассмотрим ее подробнее.
Первым делом мы инкрементируем счетчик индекса записей для загрузки — это очевидно. Далее мы вызываем метод AppendHtml из пакета net.liftweb.http.js.jquery.JqJsCmds, который предоставляет обертки для функций библиотеки jQuery (которая встроена по-умолчанию в Lift). AppendHtml принимает два параметра — id объекта DOM-дерева и объект типа NodeSeq — собственно, тот код, который и будет добавлен. Думаю, теперь становится понятно, почему шаблон комментария был вынесен в отдельный файл — теперь его можно передать в метод AppendHtml в качестве AJAX-ответа, и более того — этот шаблон будет обработан методом renderPosts! Функции, которые возвращают тип JsCmd можно конкатенировать с помощью метода & — в нашем случае, при достижении последней «страницы», прячется кнопка загрузки комментариев. Собственно говоря — это все, весь код :)
На стороне клиента, кнопка превратится в примерно такой код:
Конечно же, приведенный пример несколько искусственный, но он может продемонстрировать некоторые особенности Lift: встроенные средства для создания AJAX-запросов, обертки и прокси-методы для создания клиентского JavaScript, сквозная обработка шаблонов. Так же, явно не было написано никакого клиентского JS-кода вовсе — все это берет на себя фреймфорк, за пределы которого мы не выходили в этом примере. Впрочем, это не единственный подход, и если надо, то конечно же нет никаких препятствий к «ручному управлению». Код приложения можно получить тут (используется Maven).
В качестве примера возьмем приложение, которое должно отображать «комментарии» — при загрузке страницы первые пять, а загрузку остальных — по запросу, «порциями» по пять штук, без перезагрузки страницы, конечно же.
Итак, начнем. Пускай, у нас есть модель Post, представляющая единичный комментарий:
class Post extends LongKeyedMapper[Post] with IdPK {
def getSingleton = Post
object title extends MappedText(this)
object text extends MappedText(this)
object date extends MappedDate(this)
}
и соответствующий companion object:
object Post extends Post with LongKeyedMetaMapper[Post] {
def getPosts(startAt: Int, count: Int) = {
Post.findAll(OrderBy(Post.date, Descending), StartAt(startAt), MaxRows(count))
}
def getPostsCount = Post.count
}
в котором уже объявлены две функции — получение общего количества объектов и получение некоего настраиваемого диапазона комментариев. В нашем случае можно было обойтись и без этих методов и строить запросы «по-месту» — прямо в коде сниппетов, но, по моему скромному мнению, это затруднит тестирование и перегрузит код.
Теперь надо заняться первоначальным отображением комментариев. Для этого создаем следующий сниппет:
class PostRender {
def renderPosts(in: NodeSeq): NodeSeq = {
Post.getPosts(0, 5).flatMap(item =>
bind("post", in,
"title" -> Text(item.title),
"text" -> Text(item.text)
))
}
}
И соответствующий embeded-шаблон, который располагается в /templates/__post.html (да, нижние подчеркивания даже в Sсala/Lifft иногда бывают нужны):
<lift:PostRender.renderPosts>
<p>
Title: <post:title />
<br />
<post:text />
</p>
</lift:PostRender.renderPosts>
и который подключается на требуемой странице с помощью:
<div id="posts">
<lift:embed what="/templates/__post" />
</div>
Хочу заметить, что в данном случае я использую «устаревшую» разметку шаблонов, основанную на спец. xml-тегах, которая в данном случае мне кажется более удобной и наглядной.
В результате, после добавления модели в схему БД, заполнения записей и т.д, получаем примерно такой результат:
всего 5 объектов, как и требовалось. Теперь надо организовать загрузку остальных комментариев.
AJAX-загрузка
Перед тем, как мы продолжим, стоит несколько слов сказать о JsCmd — фактически, подсистеме в Lift, которая предназначена для генерации клиентского JavaScript на стороне сервера, в коде сниппетов и с последующей вставкой их в ответ. Существуют различные способы инжектирования JS-кода — от метода JsRaw, позволяющего вставлять «сырой» JS, до прокси-методов для JS-фреймворков. В первую очередь, JsCmd предназначен для реализации небольших по объему частей JS-кода.
Модифицируем вставку комментариев, добавив туда вызов сниппета для отображения кнопки загрузки дополнительных объектов:
<div id="posts">
<lift:embed what="/templates/__post" />
</div>
<lift:PostRender.renderLoadMoreControl />
и класс сниппета:
class PostRender {
val loadSize = 5
val lastLoadIndex = Post.getPostsCount - loadSize
var loadStartIndex = 0
def renderPosts(in: NodeSeq): NodeSeq = {
Post.getPosts(loadStartIndex, loadSize).flatMap(item =>
bind("post", in,
"title" -> Text(item.title),
"text" -> Text(item.text)
))
}
def renderLoadMoreControl: NodeSeq = {
def loadPosts: JsCmd = {
loadStartIndex += loadSize
JqJsCmds.AppendHtml("posts", <lift:embed what="/templates/__post" />) & {
if (lastLoadIndex < loadStartIndex)
JsCmds.Replace("loadButton", NodeSeq.Empty)
}
}
SHtml.ajaxButton("Load More", loadPosts _, "id" -> "loadButton")
}
}
В класс были введены значения для представления загружаемых «страниц» с комментариями — метод renderPosts использует их первоначальные значения для отображения первых 5 записей, а так же новый метод renderLoadMoreControl, который добавляет кнопку для загрузки следующей «порции» комментариев и связывает ее с внутренней функцией loadPosts, которая и представляет собой реализацию AJAX-ответа. Рассмотрим ее подробнее.
Первым делом мы инкрементируем счетчик индекса записей для загрузки — это очевидно. Далее мы вызываем метод AppendHtml из пакета net.liftweb.http.js.jquery.JqJsCmds, который предоставляет обертки для функций библиотеки jQuery (которая встроена по-умолчанию в Lift). AppendHtml принимает два параметра — id объекта DOM-дерева и объект типа NodeSeq — собственно, тот код, который и будет добавлен. Думаю, теперь становится понятно, почему шаблон комментария был вынесен в отдельный файл — теперь его можно передать в метод AppendHtml в качестве AJAX-ответа, и более того — этот шаблон будет обработан методом renderPosts! Функции, которые возвращают тип JsCmd можно конкатенировать с помощью метода & — в нашем случае, при достижении последней «страницы», прячется кнопка загрузки комментариев. Собственно говоря — это все, весь код :)
На стороне клиента, кнопка превратится в примерно такой код:
<button onclick="liftAjax.lift_ajaxHandler("F167386167581RGRLAG=true", null, null, null); return false;" id="loadButton">Load More</button>
, а код ответа будет содержать команды jQuery, примерно так:jQuery('#'+"posts").append("\u000a <p>\u000a Title: Post #11 ... ");
В качестве заключения
Конечно же, приведенный пример несколько искусственный, но он может продемонстрировать некоторые особенности Lift: встроенные средства для создания AJAX-запросов, обертки и прокси-методы для создания клиентского JavaScript, сквозная обработка шаблонов. Так же, явно не было написано никакого клиентского JS-кода вовсе — все это берет на себя фреймфорк, за пределы которого мы не выходили в этом примере. Впрочем, это не единственный подход, и если надо, то конечно же нет никаких препятствий к «ручному управлению». Код приложения можно получить тут (используется Maven).