Традиционные шаблоны
Хорошо известная концепция разделения логики приложения и логики представления с помощью шаблонов не работает для AJAX-приложений. Проблема заключается в том, что использование JavaScript для модификации страницы имеет следующие последствия:- Код шаблонов должен учитывать, что страница будет модифицироваться с помощью JavaScript, т.е. вы больше не можете отдать шаблоны полностью на откуп дизайнерам, а должны также привлекать и JavaScript-программиста в процессе их разработки
- JavaScript код должен обладать высокой степенью информированности о структуре страницы
- JavaScript код должен находиться в полностью согласованном состоянии с кодом шаблонов
- В случае сложного дизайна, его (JavaScript код) просто сложно писать и каждое изменение в дизайне приложение связано с двойной работой и будет вызывать боль
XSLT
Данные проблемы могла бы решить технология, позволяющая использовать один и тот же код генерации контента как на стороне сервера, так и на стороне клиента. Кроме того, необходима простая возможность генерации как целых страниц, так и её отдельных фрагментов. И такая технология есть уже достаточно давно - это XSLT. Я имею очень хороший (и успешный) многолетний опыт использования XSLT для решения различных задач. И я использую эту технологию в своем основном приложения именно для распределения логики представления между серверной и клиентской частью. И по началу всё шло просто чудесно, но недавно я стал понимать, что имею серьёзные проблемы в этой части. И это при том, что основой приложения является XML REST служба и все данные изначально представлены в XML, а в качестве клиента используется исключительно Firefox: для отображения контента используется XUL и SVG. Т.е. среда для использования XSLT очень даже благоприятная. Кроме того, моя попытки использовать XSLT в более общих web-приложениях не увенчалась успехом: на практике проще обойтись без XSLT. В общем, факт в том, что XSLT не получил действительно широкого распространения в той области, для которой он, собственно, и предназначался. Я вижу следующие причины этого:- Тяжеловесность технологии XML. Я имею ввиду, в том числе, что всегда нужно написать достаточно много строчек кода, что бы использовать его даже для простейших операций. Если обмен данными идёт большими кусками, то с этим ещё можно мириться, но когда есть множество функций, оперирующих крохотными кусочками данных, - overhead становится просто невыносим.
- Ограниченные возможности самого языка преобразований. Он идеально подходит именно для трансформации данных, но на практике часто нужно делать что-то ещё. При использовании XSLT на стороне сервера возможно применения собственных функций расширения, но это не работает на клиенте, что исключает возможность использования расширений в логике представления, работающей как на сервере, так и на клиенте.
- Различный уровень поддержки технологии со стороны браузеров
- Проблема дизайна XML-данных. Если он плох (а как показывает практика, далеко не все могут его готовить), то система очень скоро начнёт напоминать монстра, и XML-ненавистники обязательно обратят на это внимание :)
Closure Templates
В последнее время, ввиду накопившихся у меня проблем я был занят поиском подходящего решения в это области, и не мог ничего найти, мне ничего не нравилось, не решало моих принципиальных проблем. В сети встречаются описания схем, ориентированных на решение описанных проблем, но они типично сложны в понимании, сложны в технической реализации, плохо масштабируемые, накладывают различные ограничения на структуру приложения т.п.. И тут на помощь пришёл Google, опубликовавший свою библиотеку Closure Templates, которая показалась мне глотком свежего воздуха. Это система шаблонов, которая, как мне представляется, лишена описанных выше недостатков и обладает следующими свойствами:- Удивительно простое решение, простое для понимания, простое для использования, не накладывающее никаких ограничений на структуру приложения
- Один и тот же код, может быть использован как на клиенте, так и на сервере
- Для использования данной системы нет необходимости в поддержке браузером каких-либо специфичных технологий, поскольку шаблоны компилируются в эффективный JavaScript-код
- Шаблоны можно разбивать на настолько маленькие компоненты, насколько это необходимо, что даёт простую возможность как генерации больших кусков контента, так и обновления самых небольших фрагментов
- Входным форматов для шаблонов на стороне клиента, фактически, является JSON
- Результатом вызова шаблона, в несколько упрощённом виде, является строка
Данная система имеет только один серьёзный недостаток - язык разработки Java :( Но теперь это не проблема, поскольку доступна cl-closure-template - Common Lisp реализация этой идеи (см. мои предыдущие сообщения).
Пример использования
Для cl-closure-template я написал очень небольшое демонстрационное приложение (которое включил в состав исходного кода), этакую "доску сообщений". Пользователи могут заходить и создавать свои сообщения. Сообщения включают в себя информацию об авторе, заголовок и тело. Пользователь может увидеть список всех сообщений, которые при этом отображаются в сокращённом виде (без тела), а также получить "расширенную" версию выбранного сообщения. Все действия производятся в рамках одной страницы, без перезагрузки. Собственно, программа обладает следующим поведением:- При загрузке страницы пользователю показывается списком сообщений (для простоты они хранятся в памяти сервера): заголовок и автор
- Заголовок сообщения обозначается как ссылка, при клике мышкой по нему с сервера асинхронно запрашивается более подробная информация о сообщении, которая преобразуется в html-разметку и происходит замена оригинального html-фрагмента (с сокращенным описание) на сгенерированный
- Внизу страницы отображается в виде ссылки команда "New message", при клике на которую создаётся (не показывается, а именно создаётся) форма для ввода нового сообщения
- Пользователь вводит данные и нажимает "Создать": данные формы асинхронно отправляются на сервер, на котором создаётся новый объект и возвращается его описание в формате JSON. На основе возвращённых данных создаётся html-фрагмент для нового сообщения и помещается в начале списка
мне поначалу показалось, что это перевод (хорошо еще не машинный):
ОтветитьУдалить«Я имею очень хороший (и успешный) многолетний опыт использования XSLT для решения различных задач. И я использую эту технологию в своем основном приложения именно для распределения логики представления между серверной и клиентской частью. И по началу всё шло просто чудесно, но недавно я стал понимать, что имею серьёзные проблемы в этой части.»
@banan
ОтветитьУдалитьДа, слог сегодня не тот :(, что не удивительно, учитывая в каком графике я работал последнюю неделю, но мне было важно написать этот пост сегодня, что бы можно было спокойно сегодня выспаться и уже завтра переключиться на другую задачу.
Спасибо за описание.
ОтветитьУдалитьОдин вопрос: а почему с сервера возвращается уже готовый HTML, а не JSON-данные, которые можно подставить в шаблон на клиенте?
@Vsevolod
ОтветитьУдалитьБрр... Запутался, не понял вопроса. HTML может генериться на сервере. Либо на сервере можно сгенерить JavaScript код, которому на клиенте можно перед JSON-данные и уже на клиенте сгенерить HTML.
Я имел в виду: "Заголовок сообщения обозначается как ссылка, при клике мышкой по нему с сервера асинхронно запрашивается более подробная информация о сообщении, которая преобразуется в html-разметку и происходит замена оригинального html-фрагмента (с сокращенным описание) на сгенерированный"
ОтветитьУдалитьможет, сам не допонял: на сервере генерируется уже готовый html-фрагмент, который росто вставляется в страницу (obj.replaceWith(messages.showMessageDetail(data)))? Или передаеются JSON-данные, которые обрабатываются Javascript'ом на клиенте с помощью тех же closure-шаблонов и вставляются на страницу? Т.е. я не вижу никаких проблем так сделать, но в твоем варианте не так?
Всё так. Вот кода:
ОтветитьУдалитьfunction showDetailMsg () {
$.getJSON($(this).attr("href"), function (obj) {
return function (data) {
obj.replaceWith(messages.showMessageDetail(data));
}
}($(this).parent()));
}
$.getJSON - это функция из JQuery, она принимает два аргемента: ссылку и функцию обработчик JSON-данных. replaceWith - это тоже функция из JQuery.
messages.showMessageDetail - это функция, сгенерированная на основе шаблона. Она принимает на вход данные (распарсенный JSON) и на основе этих данных генерит разметку. Т.е. для обмена с сервером используется только JSON.