Еще раз о создании jQuery плагина или применяем на практике

в 11:54, , рубрики: jquery, jquery plugins, Веб-разработка, Песочница, метки: , ,

Доброго времени суток, читатель!

Предисловие

Я работаю программистом и пишу проекты для внутренних нужд компании. Проекты попадаются разнообразные и интересные.
До недавнего времени многие «красивости» я делал при помощи небезызвестного jQuery UI. В наборе есть практически все необходимые виджеты и т.п., использовать его просто и удобно. И даже если возникли проблемы, ответы на вопросы можно без проблем найти в сети.
И все бы было хорошо в датском королевстве, если не одно НО

Проблематика

Несмотря на все великолепие jQuery UI, я пришел к выводу, что его использование не оправдано в ситуация, когда нужен всего один виджет.
Да и простое изменение стиля для одного виджета в крупном проекте может стать большой головной болью.
Недавно я закончил один из проектов в котором не предполагалось использование jQuery UI. Но в одном из блоков, я встретился с необходимостью встраивания такого виджета как спойлер (панелька, которая при клике открывает/скрывает блок с контентом). Поискав на просторах интернета решение, я к своему удивлению не нашел ни одного подходящего плагина.

Лирическое отступление

Предвосхищая бурную дискуссию по поводу

не нашел ни одного подходящего плагина

хочу сказать следующее.
В сети действительно есть много плагинов для создания спойлеров, но:
1. Они или совсем сырые или заброшены больше года.
2. Имею слишком «навароченый» функционал.
3. Не умеют работать с теми типами тэгов, которые необходимы.
4. etc.
В составе jQuery UI также есть виджет, который можно использовать в качестве спойлера. Это виджет аккордиона. Но тащить за собой даже минимальный набор из пакета jQuery UI ради одного виджета, на мой взгляд, глупо.
Ну и последний факт ЗА написание плагина — повышение самооценки после написания.

Постановка задачи

Итак, есть желание, необходимость и время для написания плагина.
Недолго думая, я открыл гугл и набрал в поиске что-то вроде «плагин для jQuery». В первых результатах увидел статью на любимом хабре Пишем плагин для jQuery. Автору отдельное спасибо за статью, так как она очень помогла в освоении базовых принципов написания плагинов.
Далее я начал составлять список требований к плагину.
Во-первых, он должен уметь скрывать/раскрывать блоки с информацией.
Во-вторых, он должен уметь генерировать кликабельную полосу (блок) для управления спойлером (ну а какой же спойлер обойдется без этого).
В-третьих, должна быть возможность писать текст на кликабельном блоке (что совсем по взрослому все было).
В-четвертых, должна быть возможность управлять изначальным состоянием открыть/закрыт при инициализации плагина.
Ну и на вкусненькое, пусть будет анимация открытия/закрытия с возможностью отключения.

Приступая к работе

За основу взят блок кода из топика читателя Rafael.
Создадим файл плагина и назовем его jquery.bestSpoiler.js (+5 к самооценке) и поместим в него заготовку:

(function ($) {
    var methods = {
        init: function (options) {
            settings = $.extend({}, options);

            return this.each(function () { });

    $.fn.spoiler = function (method) {
        if (methods[method]) {
            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
        } else if (typeof method === 'object' || !method) {
            return methods.init.apply(this, arguments);
        } else {
            $.error('Метод с именем ' + method + ' не существует для jQuery.spoiler');
        }

    };
})(jQuery);

Обратите внимание на строчку return this.each(function () { });. Она указывает на то, что плагин должен быть инициализирован для каждого блока, который соответствует селектору, указанному при инициализации плагина.

Заготовка готова. Пока что это голый скелет нашего будущего плагина. Поэтому пора начать обвешивать его мышцами и другими составляющими.
В требованиях к плагину были указаны параметры, которые мы хотим назначать в зависимости от задачи.
Давайте добавим пару параметров для нашего плагина и добавим следующие строки:

init: function (options) {
            settings = $.extend({
                //Этот параметр принимает текст, который будет отображаться в кликабельном блоке. По умолчанию будет писать просто слово "Спойлер"
                text: 'Спойлер',
                //Это флаг, указывающий начальное состояние спойлера. По умолчанию свойлер будет свернутым
                collapsed: true,
                //Наша плюшка. Здесь укажем, сколько времени будет занимать анимация открытия/закрытия спойлера
                animationTimeout: 1000
            }, options);

В этом блоке мы определили параметры нашего плагина, которые сможем поменять в зависимости от задачи и места использования плагина.
Надеюсь из комментариев в коде все понятно, поэтому двинемся дальше. Мы определили основные параметры плагина и пора заставить его делать магию. Но сначала маленькое отступление.
Идея заставлять программиста рисовать все необходимые для плагина блоки эгоистична. Это конечно облегчит создание плагина, но его использование будет неудобным. Поэтому перед началом работы над логикой плагина пришлось подумать и провести несколько испытательных тестов. В итоге я пришел к выводу, что основным контейнером для спойлера станет элемент <div>, внутри которого мы нарисуем все необходимое. Хотя работать плагин будет практически с любым простым контейнером, таким как <span>, <p> и т.д.. Исходя из этого, наш плагин будет рисовать все необходимые контролы внутри указанного контейнера самостоятельно. Итак, обратно к коду.
Напишем следующее в блоке return this.each(function () {});:

              //Определяем разметку для кликабельного блока
               var spoilerControl = '<div class="spoilerControl">' + settings['text'] + '</div>';
		
                //Пишем в объект data нашего спойлера флаг инициализации
		$(this).data('hasSpoiler',true);
                //Добавляем первым элементом контейнера наш кликабельный блок
                $(this).prepend(spoilerControl);
                //Запоминаем высоту контейнера
                $(this).data('defaultHeight', $(this).height());
                //Пишем флаг состояния открытзакрыт
		$(this).data('collapsed',settings['collapsed']);
                //Добавляем класс к блоку
                $(this).addClass('spoilerContainer');
                var Control = $(this).find('.spoilerControl');
                $(this).css('height', $(Control).css('height'));

                //Проверяем параметр состояния и закрываем спойлер, если флаг установлен в true
                if ($(this).data('collapsed') == true) {
                   //Задаем высоту контейнера равную высоте кликабельного блока
                    $(this).css('height', $(Control).css('height'));
    		    //Меняем иконку на значок плюса
                    $(Control).addClass('plus');
                }
                else {
                    //Задаем высоту контейнера равную начальной высоте блока 
                    $(this).css('height',$(this).data('defaultHeight'));
                    //Меняем иконку на значок минуса
                    $(this).addClass('minus');
                }

                //Вешаем обработчик на событие клика мышки по кликабельному блоку
                $(Control).click(function () {
                    var spoiler = $(this).parent();
		    var animation;
		    //Проверяем была ли завершена предыдущая анимация открытиязакрытия
                    if($(spoiler).data('currentAnimation')!=undefined){
                          //Если нет, останавливаем анимацию
			  $(spoiler).data('currentAnimation').stop();
		    }
		    //Проверяем состояние флага состояния
    		    if ($(spoiler).data('collapsed') != true)
                    {
                        //Если спойлер открыт меняем значек на плюс
		        $(this).removeClass('minus');
			$(this).addClass('plus');
                        //Создаем новую анимацию и присваиваем ее переменной animation для проверки
                        animation=$(spoiler).animate({
                            //Меняем высоту спойлера на высоту кликабельного блока
                            height: $(this).css('height')
                        }, 
                        //Задаем таймаут анимации
                        settings['animationTimeout'],
                        //По завершении выполнения обнуляем объект animation, указывая, что анимация завершилась
			function(){
					  $(spoiler).data('currentAnimation',undefined)
			                });
			//Устанавливаем состояние спойлера в "закрыт"
                       $(spoiler).data('collapsed',true);
                    }
                    else {
                         //Если спойлер закрыт меняем значек на минус
			$(this).removeClass('plus');
			$(this).addClass('minus');
                        //Создаем новую анимацию и присваиваем ее переменной animation для проверки
			animation=$(spoiler).animate({
                                   //Меняем высоту спойлера на изначальную высоту спойлера
                                   height: $(spoiler).data('defaultHeight')
                        }, 
                         //Задаем таймаут анимации
                         settings['animationTimeout'],
                         //По завершении выполнения обнуляем объект animation, указывая, что анимация завершилась
			 function(){
					  $(spoiler).data('currentAnimation',undefined)
	        			  });
                         //Устанавливаем состояние спойлера в "открыт"
                        $(spoiler).data('collapsed',false);
                      }
			//Записываем анимацию в объект data спойлера, чтобы отследить состояние после повторного нажатия
			$(spoiler).data('currentAnimation',animation);
                });

Кода не много и, надеюсь, он вполне понятен. Для каждого селектора блока, указанного при инициализации плагина мы добавили кликабельный блок, который станет основным элементом управления и определили обработчик события клика по этому блоку. В обработчике мы меняем флаг состояния спойлера, изменяем его высоту, в зависимости от флага и запускаем анимацию.

В общем плагин готов и работает. Но не по джедайски будет бросить все так как есть.

Идем дальше

Первая проблема, с которой мы столкнемся при использовании плагина в таком виде — бесконтрольность создания новых экземпляров. То есть мы можем сколько угодно раз вызвать плагин и он каждый раз выполнит инициализацию, нарисовав любое количество кликабельных блоков и т.д. Это совсем ни в какие ворота. Поэтому добавим в код следующий блок сразу после строки var spoilerControl = '<div class="spoilerControl">' + settings['text'] + '</div>';

//Проверяем флаг инициализации плагина
if($(this).data('hasSpoiler')!=undefined)
    return false;

Данный блок выполнит проверку инициализации плагина. Если спойлер уже был инициализирован на данном блоке, плагин завершает свою работу ничего не меняя. Если же блок «нетронут», плагин выполнит все необходимые действия.

Итак, плагин почти готов. Почему почти? Нам не хватает одного маленького метода, который удалит «следы деятельности плагина».
Добавим в код следующий блок:

 init: function (options) {
   //Код инициализации плагина вырезан для экономии места
   ....
},
destroy: function(){
            //Вызываем действие для каждого плагина, соответствующего селектору
	    return this.each(function () {
                //Удаляем все из объекта data спойлера
		$(this).removeData();
                //Ищем кликабельный блок
                var control = $(this).find('.spoilerControl');
		var defaultHeight = $(control).css('defaultHeight');
		$(this).removeClass('spoilerContainer');
                //Удаляем кликабельный блок
		$(control).remove();
                //Устанавливаем изначальную высоту контенера
                $(this).css('height', defaultHeight);
    });
}

Вот в общем то и все, что потребовалось для создания плагина (за исключением картинок и одной таблицы стилей).

Заключени

Написать свой плагин оказалось не так сложно. Как я уже говорил, тащить с собой jQuery UI ради одного маленького плагина не всегда оправдано. Поэтому опыт в написании своего собственного плагина не только увлекательно, интересно и полезно, но и добавляет +10 к самооценке и +5 к опыту.
Надеюсь, кому-то мой пост оказался полезным и сподвигнет на создание своего собственного плагина.

Материалы и ссылки

По этой ссылке лежит архив с скриптом и всем необходимым для работы.
Здесь можно прочитать про объект data, который был использован в скрипте.

Автор: ParaPilot

Источник

Поделиться

* - обязательные к заполнению поля