Design & Evolution of include.js

Nov 13, 2010 21:42

Разработка AJAX Web UI c include.js
===================================

UPD: Теперь это типо плагин к jQuery. http://plugins.jquery.com/project/include_js

Кстати, вот похожая работа - тоже плагин к jQuery, с аналогичным названием. Ее я раньше не находил. А жаль.

http://www.arashkarimzadeh.com/jquery/17-includemany-jquery-include-many.html

/UPD

Основные проблемы разработки ajax-приложений
--------------------------------------------
Ajax-приложение отличается от обычных веб-приложний. Пользователь видит это отличие - он замечает, что страница его приложения обновляется не целиком, а частями. Также, пользователь видит большую отзывчивость таких приложений, и ему это нравится.

С точки зрения разработчика отличий гораздо больше. Взаимодействие с сервером происходит не так, как раньше. И, если разработчик хочет не просто "добавить в страницы немного динамики", а построить вокруг принципов ajax все свое приложение, то довольно быстро он понимает, что происходит не так, как раньше, не только взаимодействие с сервером, а буквально все.

(А на самом деле - я уверен, что нет никаких серьезных причин, чтобы ajax программирование было сложнее обычного. Я уверен, что все должно быть наоборот. Оно проще. Почему?)

- Если раньше состояние сессии было на сервере, то теперь оно переползает в браузер.
- Серверная часть, соответственно, сильно упрощается. Это хорошая новость. Но - приходится отдельно проектировать клиент-серверный прикладной протокол. С этим, впрочем, сложностей нет, если разработчик вовремя узнал про RESTful интерфейс.
- Серверные шаблоны использовать нельзя - приходится генерировать HTML динамически в браузере. Это тоже достаточно быстро решается - сейчас нет недостатка в клиентских шаблонных движках.
- Традиционные средства разбиения приложения на компоненты, где приложение нарезано на HTML-страницы с модулями скриптов, перестали работать. Все приложение живет в одной странице.

Последнее и представляет собой наибольшую проблему. HTML-файл стремительно раздувается. То же самое происходит с джаваскриптом. Разработчик быстро начинает бить джаваскрипт на модули, и загружать их тегом script.

Начиная с некоторого момента - программист путается в зависимостях своих .js файлов, и задает на форумах вопрос: "how can I include .js in other js" (погуглите по include js)? И - одновременно его мучает вопрос, что же делать с HTML-шаблонами и раздувшимся HTML-файлом, в котором также черт ногу сломит.

Знакомо? Значит, читайте дальше.

Разработчик дальше находит, что решаются эти проблемы следующими способами:
1. Google Closure Compiler. Это компилятор из JS в JS, который умеет модули, и умеет минифицировать код.
2. В Dojo Toolkit, оказывается есть система модулей. Но давайте на минуточку предположим, что разработчик не хотел бы ради модулей менять свой веб-фреймворк.
3. Require.js - дает ровно то, что нужно. Нейтральная к фреймворку, позволяет делать клиентский include (точнее, require) для скриптов, и - умеет делать статическую сборку при помощи Closure Compiler. Его написал автор системы модулей Dojo Toolkit. И - он скоро переведет Dojo а эту штуку. Таки да - это оно. Наш выбор.
(для пытливых умов. Вы обзязательно найдете еще и инициативу CommonJS. И удивитесь, что именно они там курят - ибо их основной API синхронный, и для браузера не годится. Правда, там не так давно появился и асинхронный вариант API. Думаю, вы не удивитесь, что require.js с ним совместим)

Итак, взяв require.js - мы можем делать клиентский include одного js в другой, независимо от фреймворка. Однако, поделав немного этот include, мы быстро выясняем, что одной этой возможности мало. require.js позволяет устранить бардак в логике, но не решает проблемы гуя. Проблемы остается как минимум две:
- До сих пор непонятно, что делать с шаблонами, живущими плотной кучкой внутри HTML. Можно их разнести по файлам при помощи SSI (Server-Side-Include), и это, в принципе, поможет. Но не до конца. Ибо для динамического гуя одного HTML-шаблона мало. К нему надо добавить логику обработки событий и управления отрисовкой на JavaScript, которую в шаблоне изображать крайне неудобно.
- Второе. API require.js выглядит реально страшно. Определения модуля и инклуды меньше всего похожи на что? Но определения модулей, и инклуды.
- Ну, и третье. Не смотря на появившуюся техническую возможность разнести все по модулям-файлам, остается непонятным _принцип_ этого разнесения. То есть, главный вопрос - каким образом структурировать ajax-приложение, до сих пор не отвечен.

Include.js - что и зачем?
=========================
(Если еще не сделали - идем на сайт requirejs.org, читаем, что это за хрень, потом - на https://github.com/gaperton/include.js, и читаем там README. После этого - продолжаем)

Первое, что мы делаем - оборачиваемся вокруг require.js, заставляя его API выглядеть по человечески. И у нас появляется нормальный include.

Второе, что мы делаем - это замечаем, что проблема с тем, что помимо HTML-шаблона нужна логика обработки событий и управления отрисовкой, решается паттерном Model-View-Controller. Как он выглядит в нашем случае?

1. Мы пишем HTML-шаблон, который умеет разворачиваться в текст. В паре с CSS - это самый настоящий View.
2. Мы берем этот текст, превращаем его в DOM, навешиваем обработку событий, и вставляем в документ, заменяя новым поддеревом уже существующее. Этот код - это у нас типичнейший контроллер. Обратите внимание - в данном случае контроллеру получается удобно разворачивать view-шаблон. Значит, что? Значит, контроллер будет у нас завернут в модуль. И этот модуль-контроллер должен уметь включать в себя шаблон, который будет тоже модулем - view.
3. И, при разворачивании шаблона, мы передаем ему некий контекст. Этот контекст, и место, откуда он берется - это Model. Этот код без проблем нарезается на модули, которые будут включаться include-ом в контроллер.

Таким образом, все приложение будет состоять из иерархии модулей-контроллеров, которые будут в себя включать:
- модуль со своими HTML-шаблонами (View).
- модули модели.
- другие модули-контроллеры.

Идея понятна? Теперь, мы делаем второе, и самое важное. Придаем этой идее человечный вид, используя в качестве базиса нашу систему модулей. С этого момента предполагается, что для навеса событий и работы с DOM используется jQuery. Не мазохисты же мы, делать это врукопашную? А jQuery как раз в этом силен.

Начнем с простого. Чего именно в системе модулей для этого не хватает? Ну конечно же поддержки модулей-шаблонов. Модуль-шаблон - это отдельный файл с HTML и парой тегов для внедрения в него вставок на JS. При включении такого модуля, он должен скомпилироваться в функцию на JS, вызвав которую с параметром-контекстом, мы получим чистый, как слеза младенца, HTML.

Теперь, самое главное. Как оно, включение и вызов шаблона, выглядеть-то будет? Не слишком-ли страшно? Смотрите, вот так - устроит?

$.include({
html: "template.html"
model : "model"
})
.define( function( _ ){
...
var plain_html = _.html( _.model.query_data() );
...
});

Неплохо, не так ли? Обратите внимание - сейчас, для простоты, я считаю, что данные модели доступны синхронно. И это, на самом деле, именно мой случай - я их вынимаю из SQL-базы Google Gears. А там API синхронный.

Но это еще не все. Это мы развернули шаблон. Но - мы ничего не сделали с результатом, и, таким образом, наш модуль - никакой пока не контроллер. Наш модуль даже пока ничего не экспортирует. Теперь у нас стоит второй вопрос - а что он должен экспортировать, или - как именно выглядит снаружи контроллер? Что это за штука такая, как его вызывать?

Здесь возможны самые разные варианты, но я рекомендую вам следующий. Контроллер - это _функция_, следующего вида:
function( $this : DOM-node, data_or_paramenets : any, callbacks : { function } ) : controller_obj

Первый аргумент - это узел DOM-дерева, куда контроллер должен вставится. По выходу из вызова - контроллер обязан произвести все необходимые манипуляции с DOM.

Второй аргумент - объект произвольного вида, в котором контроллеру передаются его параметры. Он, контроллер, знает, как их обрабатывать.

Третий - объект с набором коллбэков. Часть событий контроллер знает как обрабатывать, и обработает сам. А часть - не знает, и просигнализирует о них наверх.

И возвращаемый объект - это интерфейс экземпляра контроллера. Он может содержать методы для управления поведением контроллера, после того, как он создан. Эта штука не всегда требуется, но вообще - наличие такой возможности важно. Итак, что мы получили:

template.html:

{{ message }}




view.js:

$.include({
html: "template.html"
model : "model"
})
.define( function( _ ){
_.exports = function( $this, a_data, a_events ){
function update(){
// готовим данные из модели...
var context = _.model.query_data( a_data );

// перегенерируем View, вставляя результат в DOM...
_.html.renderTo( $this, context );

// навешиваем обработчики событий...
$this.find( '#ok' ).click( a_events.on_ok ); // ok - пробрасываем наверх.
$this.find( '#refresh ).click( update ); // это - обрабатываем сами, обновляясь.
}

update();

return {
force_update : update; // и - даем вызывающему "хвост", чтобы заставить нас обновиться.
};
}
});

Вот и все. Ничего лишнего. Каждая строчка - фукнциональна.

Кроме одного момента. На самом деле, здесь показана не лучшая практика - я выставил наружу полное обновление как управление. Дело в том, что для обновления такого View "сверху" мне достаточно просто его еще раз вызвать, пересоздав. Но так как простого и содержательного примера управления привести нелегко (это скорее редкость, когда оно требуется), и так как в ответ на вызов "управления" придется что-то обновлять, я сделал в примере так. Но, если бы я задавал его без управления, то это выглядело бы проще, и красивее. Имейте это в виду.

Короче говоря, мы определили view. Для того, чтобы использовать данный view, нам достаточно включить один файл view.js. И вызвать его. Примерно так:

include({
view: "view.js"
})
.define( function( _ ){
_.view( $("body"), { parm: "something" }, {
on_ok : function(){ alert( 'ok!' ); }
});
});

Это были базовые возможности библиотеки, и обзор проблем, ей решаемых. Продолжение следует.

ЗЫ: Обновлено описание проекта на github. Со слабой надеждой ищутся добровольцы, которые помогут мне в описании. Например, Маммут. :)
PPS: Необходимо в срочном порядке инициировать merge данной либы либо в require.js, либо - в jQuery. Для этого и нужны нормальные описания на английском. Я сам, боюсь, не осилю. Времени это требует не меньше, а больше, чем программирование. К сожалению, у меня времени нет. Мне надо делать тот проект, ради которого эта либа создана.

javascript, mvc, ajax, include.js

Previous post Next post
Up