Javascript.RU

Организация работы с 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 игрой, которую опубликую сюда же.

Прикрепленный файлРазмер
Код объекта Q2.53 кб
+5

Автор: Dzyanis Kuzmenka (не зарегистрирован), дата: 3 сентября, 2011 - 15:18
#permalink

Проблемы вы правильно нашли, только я бы вам советовал посмотреть на уже существующие либы (что-то вроде KnockoutJS) и на паттерн MVVM


Автор: Андрей Параничев, дата: 4 сентября, 2011 - 11:21
#permalink

Меня интересовало решение проблемы с минимальным кодом и максимально прозрачным кодом. Согласитесь, KnockoutJS не является таким решением и устанавливает свои собственные правила на то, как должен писаться код клиентского скрипта. MVVM это тоже хорошо, но реализация его тоже нанесет свой большой отпечаток.

Так что я решил остановиться именно на таком "обертыше" для структуризации кода jQuery и на почти стандартном интерфейсе EventEmitter для связи с объектами "модели" на клиенте.


Автор: DjDiablo, дата: 5 сентября, 2011 - 22:45
#permalink

А я для себя решил через JavaScriptMVC
Жаль там нет модулей, но я написал свою реализацию.


 
Поиск по сайту
Другие записи этого автора
Андрей Параничев
Содержание

Учебник javascript

Основные элементы языка

Сундучок с инструментами

Интерфейсы

Все об AJAX

Оптимизация

Разное

Дерево всех статей

Популярные таги
Последние темы на форуме
Forum