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 можно конкатенировать с помощью метода & — в нашем случае, при достижении последней «страницы», прячется кнопка загрузки комментариев. Собственно говоря — это все, весь код :)
На стороне клиента, кнопка превратится в примерно такой код:
, а код ответа будет содержать команды jQuery, примерно так:<button onclick="liftAjax.lift_ajaxHandler("F167386167581RGRLAG=true", null, null, null); return false;" id="loadButton">Load More</button>
jQuery('#'+"posts").append("\u000a <p>\u000a Title: Post #11 ... ");
В качестве заключения
Конечно же, приведенный пример несколько искусственный, но он может продемонстрировать некоторые особенности Lift: встроенные средства для создания AJAX-запросов, обертки и прокси-методы для создания клиентского JavaScript, сквозная обработка шаблонов. Так же, явно не было написано никакого клиентского JS-кода вовсе — все это берет на себя фреймфорк, за пределы которого мы не выходили в этом примере. Впрочем, это не единственный подход, и если надо, то конечно же нет никаких препятствий к «ручному управлению». Код приложения можно получить тут (используется Maven).
