jQuery для начинающих. Часть 7. Пишем плагины

jQuery Logo

Если вы читали предыдущие статьи из этой серии, то вы наверное уже пробовали разрабатывать свой плагин, если нет, то можем начать вместе…

Материалы данной статьи включены в учебник «jQuery для начинающих». Учебник распространяется бесплатно, и сопровождается интерактивными примерами.

Для начала вспомним, для чего нам нужны плагины? Мой ответ — создание повторно используемого кода, и да — с удобным интерфейсом. Давайте напишем такой код, вот простая задачка: «По клику на параграф, текст должен измениться на красный»

Javascript и даже не jQuery

Дабы не забывать истоков — начнем с реализации на нативном javascript’е:

var loader = function () {
    // находим все параграфы
    var para = document.getElementsByTagName('P');
    // перебираем все, и вешаем обработчик
    for (var i=0,size=para.length;i<size;i++) {
        // обработчик
        para[i].onclick = function() {
             this.style.color = "#FF0000";
        }
    }
}
// естественно, весь код должен работать после загрузки всей страницы
document.addEventListener("DOMContentLoaded", loader, false);

Данный код не является кроссбраузерным, и написан с целью лишний раз подчеркнуть удобство использования фреймворка

jQuery, но еще не плагин

Теперь можно этот код упростить, подключаем jQuery и получаем следующий вариант:

$(document).ready(function(){
    $('p').click(function(){
        $(this).css('color', '#ff0000');
    })
});

Таки jQuery плагин

С поставленной задачей мы справились, но где тут повторное использование кода? Или если нам надо не в красный, а в зеленый перекрасить? Вот тут начинается самое интересное, чтобы написать простой плагин достаточно расширить объект $.fn:

$.fn.mySimplePlugin = function () {
    $(this).click(function(){
        $(this).css('color', '#ff0000');
    })
}

Если же писать более грамотно, то нам необходимо ограничить переменную $ только нашим плагином, а так же возвращать this, чтобы можно было использовать цепочки вызовов (т.н. chaining) , делается это следующим образом:

(function($) {
  $.fn.mySimplePlugin = function(){
     // код плагина ...
     return this;
  };
})(jQuery);

Внесу небольшое пояснение о происходящем, код (function($){…})(jQuery) создает анонимную функцию, и тут же вызывает ее, передавая в качестве параметра объект jQuery, таким образом внутри анонимной функции мы можем использовать алиас $ не боясь за конфликты с другими библиотеками — так как теперь $ находится лишь в области видимости нашей функции, и мы имеем полный контроль над ней

Добавим опцию по выбору цвета и получим рабочий плагин:

(function($) { 
    // значение по умолчанию - ЗЕЛЁНЫЙ
    var defaults = { color:'green' };
    
    // актуальные настройки, глобальные
    var options;
    
    $.fn.mySimplePlugin = function(params){
        // при многократном вызове функции настройки будут сохранятся, и замещаться при необходимости
        options = $.extend({}, defaults, options, params);
        
        $(this).click(function(){
            $(this).css('color', options.color);
        });

        return this;
    };
})(jQuery);

Вызов:

// первый вызов
$('p:first,p:last').mySimplePlugin();
// второй вызов
$('p:eq(1)').mySimplePlugin({ color: 'red' });

В результате работы данного плагина, каждый клик будет изменять цвет параграфа на красный, т.к. мы используем глобальную переменную для хранения настроек, то второй вызов плагина изменят значение для всех элементов. Можно внести небольшие изменения, и разделить настройки для каждого вызова (пример):

// актуальные настройки, будут индивидуальными при каждом запуске
var options = $.extend({}, defaults, params);

Работаем с коллекциями объектов

Тут все просто, достаточно запомнить — this содержит jQuery объект с коллекцией всех элементов, т.е. :

$.fn.mySimplePlugin = function(){
     console.log(this); // jQuery
     console.log(this.length); // число элементов
     return this;
};

Если мы хотим обрабатывать каждый элемент то соорудим следующую конструкцию:

// необходимо обработать каждый элемент в коллекции
return this.each(function(){
    $(this).click(function(){
        $(this).css('color', options.color);
    });
});

// предыдущий вариант немного избыточен,
// т.к. внутри функции click и так есть перебор элементов
return this.click(function(){
        $(this).css('color', options.color);
    });

Опять же напомню, если ваш плагин не должен что-то возвращать по вашей задумке — возвращайте this — цепочки вызовов в jQuery это часть магии, не стоит её ломать

Публичные методы

Так, у нас написан крутой плагин, надо бы ему еще докрутить функционала, пусть цвет регулируется несколькими кнопками на сайте. Для этого нам понадобится некий метод «color», который и будет в ответе за всё. Сейчас приведу пример кода готового плагина — будем курить вместе:

// значение по умолчанию
var defaults = { color:'green' };

// наши публичные методы
var methods = {
    // инициализация плагина
    init:function(params) {
        // актуальные настройки, будут индивидуальными при каждом запуске
        var options = $.extend({}, defaults, params);

        return this.click(function(){
           $(this).css('color', options.color);
        });
    },
    // изменение цвета
    color:function(color) {
        $(this).css('color', color);
    },
    // сброс цвета
    reset:function() {
        $(this).css('color', 'black');
    }
};

$.fn.mySimplePlugin = function(method){

    // немного магии
    if ( methods[method] ) {
        // если запрашиваемый метод существует, мы его вызываем
        // все параметры, кроме имени метода прийдут в метод
        // this так же перекочует в метод
        return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
    } else if ( typeof method === 'object' || ! method ) {
        // если первым параметром идет объект, либо совсем пусто
        // выполняем метод init
        return methods.init.apply( this, arguments );
    } else {
        // если ничего не получилось
        $.error( 'Метод "' +  method + '" не найден в плагине jQuery.mySimplePlugin' );
    }
};

Теперь еще небольшой пример использование данных методов:

// вызов без параметров - будет вызван init
$('p').mySimplePlugin();
// вызов метода color и передача цвета в качестве параметров
$('p').mySimplePlugin('color', '#FFFF00');
// вызов метода reset
$('p').mySimplePlugin('reset');

Для понимания данного кусочка кода, вы должны разобраться лишь с переменной arguments, и с методом apply (тут им целые статьи посвятили — дерзайте)

О обработчиках событий

Если ваш плагин вешает какой-либо обработчик, то лучше всего (читай всегда) данный обработчик повесить в своём собственном namespace:

return this.bind("click.mySimplePlugin",function(){
    $(this).css('color', options.color);
});

Данный финт позволит в любой момент убрать все ваши обработчики, или вызвать только ваш, что очень удобно:

// вызовем лишь наш обработчик
$('p').trigger("click.mySimplePlugin");

// убираем все наши обработчики
$('p').unbind(".mySimplePlugin");

Использование data

Если по какой-то причине вы еще не знакомы с data — то советую прочитать и усвоить незамедлительно. Если же в двух словах — это реестр данных, и все данные привязанные к какому-либо элементу лучше хранить в нем, это же правило касается и плагинов. Если вам надо сохранить состояние плагина — используйте data, если необходим кеш — используйте data, если вам необходимо сохранить … ну думаю понятно. Приведу еще примерчик связанный с инициализацией:

// функция init
function() {
  var init = $(this).data('mySimplePlugin');

  if (init) {
    return this;
  } else {
    $(this).data('mySimplePlugin', true);
    return this.bind("click.mySimplePlugin",function(){
       $(this).css('color', options.color);
    });
  }
}

Источники

Цикл статей

  1. jQuery для начинающих
  2. jQuery для начинающих. Часть 2. JavaScript Меню
  3. jQuery для начинающих. Часть 3. AJAX
  4. jQuery для начинающих. Часть 4. Селекторы
  5. jQuery для начинающих. Часть 5. Эффекты
  6. jQuery для начинающих. Часть 6. События
  7. jQuery для начинающих. Часть 7. Пишем плагины
  8. jQuery для начинающих. Часть 8. Расширяем фильтры
  9. jQuery для начинающих. Часть 9. Пишем плагины анимации

37 thoughts on “jQuery для начинающих. Часть 7. Пишем плагины”

  1. Благодарствую за сей тяжкий труд, давно хотелось написать какой-нить маленький плагин ради развлечения, но лень было копошиться по соответствующей документации, а тут… Весь материал скомпилирован в одну статью, в общем теперь-то что-то наворочу. Еще раз спасибо!

  2. Давно хотел всё оформить в отдельные плагины, но всё как то не было толкового обзора.
    Спасибо, за качественный материал!

  3. Спасибо за материал. Ковыряюсь вот с одной идейкой динамического использования географических карт совместно с блогом. И обнаружил, что писать на чистом JS тяжело невероятно (вообще, по малоопытности, JS производит жуткое впечатление). Лучше – с фреймворком jquery(). Но еще обнаружил, что плагинов для jquery, которые использовали третью версию API google maps просто не существует. Все что есть – для второй GMAP2.
    Между тем – http://code.google.com/intl/ru-RU/apis/maps/documentation/javascript/v2/reference.html – Google настоятельно рекомендует начать юзать третью версию. Такая вот беда.

    Как бы осилить?.. Кто бы помог? :)

  4. Огромное спасибо за эту серию статей! Антон, когда вы находите время для написания статей? 0_о

  5. Спасибо отличная статья, особенно про магию с публичными методами, об этом к сожалению почти не пишут, спасибо.

  6. Огромный респектище автору!
    Убедительная просьба: Может ли автор нас поближе познакомить с неким чудом как $.widget от jQuery.UI ? Да и вообще с внутренностями УЯ. Да! И еще про namespace практически нигде нет ничего вменяемого.
    Респектам не будет предела!

  7. вот никак не могу понять смысл Array.prototype.slice.call( arguments, 1 ),
    по отдельности понимаю что значит каждая из этих ф-й, но именно вот такую конструкцию не понимаю, обьясните пожалуйста. и почему не сделать вызов таким образом
    return methods[ method ].call( this, Array.prototype.slice.call( arguments ));

      1. Потому что в случае:

        return methods[ method ].call(this, arguments);

        в функцию передадутся все аргументы, а нужны все кроме первого (имени метода). Можно было бы

        Array.prototype.slice.call( arguments, 1 )

        заменить не

        arguments.slice(1)

        , НО! arguments это не массив, у него нет метода slice, поэтому так страшно на первый взгляд и получается :)

  8. Прекрасная статья.
    Уже несколько раз к ней обращался при написании плагинов.
    Большое спасибо.

  9. Супер! Дает понимание, и не отвращает чрезмерной сложностью!
    А то, бывает, начнешь читать, и желание изучать технологию пропадает..
    Спасибо!!!
    Дизайн на сайтах в примерах суперовый!

  10. А как можно проверить – вызывался ли плагин хотя бы единожды?(метод с переменными var called = false – не надо рассматривать )

  11. Объясните, пожалуйста, или дайте ссылки по следующему вопросу.
    Первый вариант написания плагина учитывает передачу пользователем свойств

    defaults={color:'green'};
    var options;
    $.fn.myPlugin = function(param){
    	options=$.extend({},defaults,options,params);
    }
    $('.selector').myPlugin({color,'red'});

    Второй вариант учитывает передачу пользователем методов

     $.fn.myPlugin = function(method){/*проверка существования методов*/}
    $('.selector').myPlugin('color','#ffff00');

    —>Во втором примере нет $.extend();
    Как правильно написать код плагина, чтобы можна было передавать и пользовательские свойства, и методы.

  12. Спасибо за статьи! Очень доходчиво и ясно.
    Из интереса чуть дописал Ваш скрипт.

    (function($) {
        
        var defaults = { color:'green' };
         
        var options;
        var limit=1;
        var count=0;	
        
    	$.fn.mySimplePlugin = function(params,num){
            
    		if(params) defaults.color=params;
            if(num) limit=num;   
    		    
    			$(this).click(function(){
                count++;
    			if(count==limit) $(this).css('color', defaults.color);
    			
            });
     
            return this;
        };
    })(jQuery);

    Здесь реализован простой счетчик кликов для изменения цвета текса.
    $(“#point”).mySimplePlugin(“yellow”,3);

  13. Добавьте на сайт возможность выделения текста с последующим указанием на ошибку ибо такого количество орфографических и пунктационных ошибок я давно не встречал. Без обид, но глаз режет, тем более, что вы претендуете на звание учащего.

  14. Объясните кто-то, что насчет плагинов вида “$.cookie”. Что если мне не нужно привязываться к DOM? Дайте ссылку по разработке таких плагинов.

  15. (function( $ ){
    
      $.fn.JClock = function() {
      
          var CBlock = this;/*блок в котором часы*/
        
          CBlock.append("<div class='clockCifra'></div>");
    	   
    	  CBlock = $(".clockCifra");
    	  
    	   this.hour=null;
    	   this.min=null;
    	   this.sec=null;
    	  
    	   this.day=null;
    	   this.dayned=null;
    	   this.month=null;
    	   this.year=null;
    	  
    	   this.data('interValId',null);
    	  
    	  var arrayDayNed=new Array('','понедельник','вторник','среда','четверг','пятница','суббота','воскресенье');
    	  var arrayManth= new Array('','январь','февраль','март','апрель','май','июнь','июль','август','сентябрь','октябрь','ноябрь','декабрь');
    	  
    	  /*поехали*/
    	  getTime();
    	   
    	   
    	 
    	    function startClock()
    	  {
    	  	   alert(this.sec); /*!!! выдает undefined  */
    	  }
    	 
    	  
    	  
    	  function getTime()
    	  {
    	  	 $.post( "http://"+document.domain+"/time.php", function( xml )
    	    {
               
    		   this.day=$(xml).find("day").text();
    		    
               this.dayned=$(xml).find("deayned").text();
    		   this.month=$(xml).find("month").text();
    		   this.year=$(xml).find("year").text();
    		   this.hour=$(xml).find("hour").text();
    		   this.min=$(xml).find("min").text();
    		   this.sec=$(xml).find("sec").text();
                
    		   CBlock.html('');
    		   
    		   CBlock.append("<span class='clLabel preday'>&nbsp;</span>"); 
    		   CBlock.append("<span class='day'>"+this.day+"</span>");
    		   CBlock.append("<span class='clLabel postday'></span>");
    		    
    		   CBlock.append("<span class='clLabel premonth'>.</span>");
    		   CBlock.append("<span class='month'>"+this.month+"</span>");
    		   CBlock.append("<span class='clLabel postmonth'></span>");
    		   
    		   CBlock.append("<span class='clLabel preyear'>.</span>");
    		   CBlock.append("<span class='year'>"+this.year+"</span>");
    		   CBlock.append("<span class='clLabel postyear'></span>");
    		   
    		   
    		   CBlock.append("<span class='clLabel premonrus'>&nbsp;</span>");
    		   CBlock.append("<span class='monrus'>"+arrayManth[this.month]+"</span>"); 
    		   CBlock.append("<span class='clLabel posmonrus'>,</span>");
    		   
    		   CBlock.append("<span class='clLabel predayned'>&nbsp;</span>");
    		   CBlock.append("<span class='dayned'>"+arrayDayNed[this.dayned]+"</span>"); 
    		   CBlock.append("<span class='clLabel posdayned'>&nbsp;</span>");
    		   
    		   CBlock.append("<span class='clLabel prehour'>&nbsp;</span>");
    		   CBlock.append("<span data='"+this.hour+"' class='hour'>"+this.hour+"</span>");
    		   CBlock.append("<span class='clLabel posthour'></span>");
    		   
    		   CBlock.append("<span class='clLabel premin'>:</span>");
    		   CBlock.append("<span data='"+this.min+"' class='min'>"+this.min+"</span>");
    		   CBlock.append("<span class='clLabel postmin'></span>");
    		   
    		   CBlock.append("<span class='clLabel presec'>:</span>");
    		   CBlock.append("<span data='"+this.sec+"' class='sec'>"+this.sec+"</span>");
    		   CBlock.append("<span class='clLabel postsec'></span>");
    		   
    		   clearInterval(this.interValId);
    		   
    		   this.interValId=setInterval(startClock,1000);
    		   
    		   
            },'xml'); 
    		
    		
    	  }
    	  
    	  
    	  return this;
         
    
      };
    })( jQuery );
    
    
    
    

    Здравствуйте, решил написать плагин уже весь изматерился, не понимаю, почему внутри плагина не сохраняется переменная (this.sec) после присваивания ей значения с ajax запроса

  16. Спасибо за интересную полезную статью. Но у меня возник вопрос: как быть, если нужно иметь в плагине и методы, и настройки для каждого экземпляра плагина индивидуально?
    В Вашем примере индивидуальные настройки доступны только в методе init? а как быть, если он мне нужны и в других методах тоже?

    1. Тоже интересен этот вопрос. При вызове плагина еще раз опции не перезаписываются.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.