Организация работы с jQuery
Каждому веб-разработчику, который в своей работе использует библиотеку jQuery, рано или поздно приходится столкнуться с ситуацией, что клиентские скрипты с увеличением объема превращаются в сложно поддерживаемое месиво.
Происходит это в том числе потому, что сам по себе jQuery не диктует какой-либо структуризации кода, а только упрощает написание "рутинных" скриптов, либо предлагает плагины, для решения каких-то задач. Кроме того, сам jQuery не событийно-ориентирован (кроме подписки элементов на события) и не диктует событийно-ориентированное проектирование клиенстких скриптов. В то время, как почти все взаимодействия в html основаны на событиях.
В качестве решения этой проблемы я написал коротенький скрипт (который при определенных допущениях позволяет назвать себя микрофреймворком), который решает следующие задачи: отделяет код бизнес-логики от скриптов представления (от самого jQuery-кода) путем использования механизма подписки и оповещения о событиях, плюс структурирует скрипты, непосредственно связанные с jQuery.
В общем виде этот скрипт позволяет сделать что-то подобное:
// Описываем объект бизнес-логики:
var mySiteObject = {
"getSomeDataFromServer": function() {
// ... тут мы получаем какие-то данные, либо выполняем другие действия
// возможно мы находимся в обработчике ответа с сервера:
this.emit("iveGotSomeData", data); // Передаем оповещение о событии.
}
};
// Расширяем объект методами работы с событиями:
Q.EventEmitter(mySiteObject);
// Описываем часть логики отображения:
Q.register({
".wowThisElementIsGood": { // Задаем CSS-селектор элемента
// Описываем метод, который выполняется автоматически
// в контексте соответствующего элемента:
"__init__": function() {
var self = this;
mySiteObject.on("iveGotSomeData", function(data) {
self.html(data);
});
}
// Описываем обработчик события "click":
"click": function() {
mySiteObject.getSomeDataFromServer();
}
}
});
// Выполняем описанные скрипты (отложено до document ready):
Q.exec();
Если вы после ознакомления с этим примером совсем не поняли, зачем это может понадобиться в принципе, то скорее всего вам этот скрипт будет неинтересен совсем.
Этот скрипт определяет следующие методы:
Q.EventEmitter(object) - Этот метод расширяет объект, который передан в качестве параметра, всеми методами по работе с событиями.
Q.addEventListener, Q.on(eventName, listener) - метод устанавливает обработчик определенного события. Эти методы копируются при расширении объекта методом Q.EventEmitter. В качестве параметров получает название события eventName и функцию-слушатель, которую и возвращает результатом, для её удобного хранения (что необходимо в случае удаления).
Q.removeEventListener(eventName, listener) - метод удаляет установленный обработчик события. Как и везде - listener должен быть тем же самым обработчиком, что был установлен.
Q.emit(eventName, [argument1[, argument2[, ...]]]) - метод вызывает цепочку обработчиков соответствующего события, передавая ему параметры, указанные аргументами метода.
Q.getEventListeners(eventName) - метод возвращает доступный для редактирования массив обработчиков определенного события.
Q.clearEventListeners(eventName) - метод удаляет все обработчики определенного события.
Q.register(scheme), Q.register(viewName, scheme) - этот метод обрабатывает переданный объект, рассматривая ключи, как css-селекторы, а объекты значений, как пары "событие => обработчик". При этом добавляет новое событие "__init__" которое выполняется по вызову Q.exec() или DOMReady, в зависимости от времени вызова, если соответствующий элемент существует на странице.
Если указан параметр viewName - блок обработчиков помещается под этим именем (или расширяет объект, если он уже существует). С помощью имен можно отделять обработчики друг от друга, например те, которые необходимы только на определенных страницах. По-умолчанию viewName имеет значение "global".
Q.exec(), Q.exec(viewName), Q.exec(viewName, element) - выполняет, или ставит обработчик выполнения на DOMReady, описанные в схеме обработчики. Если задан viewName, то будет выполнен только именованный блок, иначе выполнится блок "global".
Именованные блоки можно выполнять в контексте определенного элемента, если указать параметр element. При этом элемент element будет рассматриваться, как родительский элемент для всех обработчиков.
По сути этот скрипт не представляет какой-либо ценности и не вводит что-то принципиально новое. Однако, сам подход может заинтересовать тех, кто часто и много пишет с использованием jQuery и страдает от слабой структуризации собственных скриптов.
Лично я использую такой подход в 90 случаев из 100, хотя на первый взгляд это может показаться бессмысленным. Поскольку часто html-страница является "конечным видом" в приложении. Но все чаще и чаще это становится не совсем верно, поскольку со временем появляется все больше и больше клиентской логики.
Дело в том, что часто в клиентских скриптах нам необходимо не только привязать какие-то обработчики, но и хранить "состояние" в каком-то удобном виде. Это необходимо в тех случаях, когда от состояния объектов на странице зависит не только отображение, но и какая-то "клиентская логика". В качестве банального примера: выбранный элемент меню/табов, который удобнее хранить в самостоятельном объекте, чем получать класс элемента страницы и определять состояние по его идентификатору.
Кроме того, такой скипт может упростить написание обработчиков форм, либо может в разы сократить и упростить код, если приложение активно (или вообще полностью) использует асинхронную коммуникацию с сервером и зависит от множества параметров состояния. Ведь в этом случае просто необходимо отделить бизнес-логику от логики отображения.
Поскольку метод Q.exec() позволяет указать группу обработчиков и элемент-родитель, становится возможно использовать этот подход и для динамически создаваемого, или загружаемого с сервера, контента. Кроме того, именование групп обработчиков позволяет разделить обработчики на глобальные и необходимые только на одном виде страниц.
Конкретный пример использования такого метода организации работы с jQuery я покажу в одной из следующих записей, вместе с небольшой JavaScript HTML5 игрой, которую опубликую сюда же.
|
Проблемы вы правильно нашли, только я бы вам советовал посмотреть на уже существующие либы (что-то вроде KnockoutJS) и на паттерн MVVM
Меня интересовало решение проблемы с минимальным кодом и максимально прозрачным кодом. Согласитесь, KnockoutJS не является таким решением и устанавливает свои собственные правила на то, как должен писаться код клиентского скрипта. MVVM это тоже хорошо, но реализация его тоже нанесет свой большой отпечаток.
Так что я решил остановиться именно на таком "обертыше" для структуризации кода jQuery и на почти стандартном интерфейсе EventEmitter для связи с объектами "модели" на клиенте.
А я для себя решил через JavaScriptMVC
Жаль там нет модулей, но я написал свою реализацию.