jQuery plugin - własna wtyczka od podstaw


W tym krótkim wpisie, przedstawię jak szybko można napisać własny plugin do jQuery. Z racji tego, iż jest to obecnie jeden z najbardziej popularnych frameworków JavaScriptowych, znajomość tworzenia rozszerzeń jest bardzo przydatna.

Nasz plugin

W celu uniknięcia tworzenia sztuki dla sztuki, stworzona wtyczka będzie coś robić. Jej zadaniem będzie zmiana tła w środku elementu, po kliknięciu na tym obiekcie. Jako parametry przyjmować będzie dwa kolory, które co kliknięcie będą się zmieniać.

Baza

Każdy plugin dla jQuery składa się z bazy, która zawsze jest identyczna dla każdej wtyczki, bez względu na to w jaki sposób będzie dalej tworzona.

Pierwszym krokiem jest dostarczenie naszemu pluginowi dostępu do $ oraz zapewnienie unikalności nazw i niekolidowania z innymi wtyczkami:


(function($) {
	//baza pod plugin
})(jQuery	

Teraz przyszedł moment aby dodać do kodu funkcję, w której nasz plugin będzie działał:


(function($) {
	$.fn.changeColor = function() {
		//ciało naszego pluginu
	}
})(jQuery);

Na tym etapie nazywamy nasz plugin. W tym przypadku będzie to changeColor. Już w tym momencie nasz plugin istnieje, mimo, iż nic nie robi można go wywołać:

$("div").changeColor();

Łańcuchowość

Pluginy jQuery opierają się na zasadzie, iż każda wtyczka zwraca przetworzony/wybrany łańcuch obiektów dalej. Dzięki czemu można pisać ciąg poleceń np.:


$("div").attr("data-type","slider").addClass("slider");

W powyższym przykładzie, ze strony wybieramy wszystkie elementy div i ustawiamy im atrybut data-type na slider . Dzięki temu, iż attr zwraca łańcuch divów z dodanym atrybutem, możemy ponownie przetworzyć dane i np. dodać do każdego diva klasę slider .

Łańcuchowośc zapewnimy, dzięki temu, iż plugin zwracać będzie element this , czyli dane które otrzymał do przetworzenia:


(function($) {
	$.fn.changeColor = function() {
		return this.each(function() {
		  //ciało naszego pluginu
		});	
	}
})(jQuery);

Parametry wejściowe

Nasz plugin powinien przyjmować parametry wejściowe, zatem należy zapewnić taką możliwość:


(function($) {
	$.fn.changeColor = function(options) {
	
		//parametry wejściowe
		var settings = $.extend( {
		  colorFirst : 'Red',
		  colorSecond : 'Green'
		}, options);
		
		return this.each(function() {
		  //ciało naszego pluginu
		});	
	}
})(jQuery);

Dzięki temu szybkiemu zabiegowi, uzyskaliśmy kilka ważnych rzeczy. Nasz plugin uzyskał możliwość inicjalizacji z parametrami wejściowymi. colorFirst oraz colorSecond mają odpowiadające wartości domyślne, które mogę być zmienione na te które poda użytkownik. Możliwe wywołania:


$("div").changeColor({ colorFirst : 'Blue', colorSecond : 'Black' });
//parametry domyślne
$("div").changeColor();
//ustawienie tylko jednego parametru
$("div").changeColor({ colorSecond : 'Yellow' });

Metody publiczne i prywatne

Kolejny krok to dodanie metod. Jeśli nasza wtyczka ma być prosta można spokojnie zacząć kodować w ciele głównej metody pluginu. Jednakże, jeśli kod ma być bardziej złożony i dodatkowo udostępniać na zewnątrz metody, warto jeszcze chwilę poczekać.

Poniższy schemat jest zalecany, w przypadku pluginów posiadających publiczne metody. Dzięki takiemu stworzeniu metod, nie marnujemy niepotrzebnie nazw w obrębie jQuery.


(function($) {
	$.fn.changeColor = function(options) {
		
		//metoda prywatna
		function onClick(event) {
			//ciało metody prywatnej
		}
		
		//metody publiczne
		var methods = {
			swapColors: function() {
				//wystawiona metoda
			},
			destroy: function() {
				//destruktor 
			}
		};
		
		return this.each(function() {
			//ciało naszego pluginu
			if(methods[options]){	
				//wywołana metoda publiczna
				return methods[options].apply( this, arguments );
			}
			else if (typeof options === 'object' || ! options ){
				//wywołany konstruktor
				var settings = $.extend( {
				  colorFirst : 'Red',
				  colorSecond: 'Green'
				}, options);
				//inicjalizacja pluginu
				return;
			}
			else{
				//bład
				$.error('changeColor: no method: '+ options);
			} 
		});	
	}
})(jQuery);					
	

Powyżej stworzono obiekt methods , zawierający metody publiczne. SwapColors jest publiczną metodą wystawioną na zewnątrz. Taki zabieg powoduje, iż do metody odwołujemy się podobnie jak ma to miejsce w pluginie jQuery UI:


$("div").changeColor('swapColors');

Dodatkowo prywatna metoda onClick będzie użyta jedynie wewnątrz wtyczki.

Destruktory

Pisząc plugin do jQuery, warto zadbać o to by można było nasz plugin “odinstalować”. Należy zatem dodać metodę która będzie sprzątała po naszej wtyczce. W takim destruktorze warto zadbać o to, by znalazło się w nim usunięcie np. eventów, które podczepiliśmy podczas inicjalizacji lub innych danych wrzuconych do htmla. Do powyższego kodu dołączono metodę destroy , wywołuje się ją podobnie jak inne publiczne metody.

Dodawanie funkcjonalności

Możemy zatem przystąpić do dodawani funkcjonalności do naszego pluginu. Na początek dodamy inicjalizację wtyczki:


//inicjalizacja pluginu
$this = $(this);
data = $this.data("changeColor");
if(!data)
{
	data = $this.data("changeColor", settings);
	$this.bind("click", onClick);
}
$this.css("background-color", settings.colorFirst);
data.first = true;

Powyższy kod sprawdza, czy przypadkiem plugin dla danego elementu nie jest już jest dodany. W tym przypadku sprawdzić można, czy element posiada dane pod wybranym kluczem: changeColor . W prosty sposób unikamy podwójnego inicjalizowania tego samego elementu, a także jest to świetny sposób na przechowanie parametrów wejściowych. Do elementu, podczepiliśmy metodę onClick , która będzie wywoływana dla eventu odpowiadającego za kliknięcie myszką. Zmienna *first * będzie informować, o tym który kolor mamy ustawić jako tło.

Przyszła kolej na dodanie ciała dla funkcji onClick , która podmienia tło dla klikniętego elementu;


//ciało metody prywatnej
$this = $(this);
data = $this.data("changeColor");
data.first = !data.first;
$this.css("background-color", 
	data.first ? data.colorSecond : data.colorFirst);
return true;

Dodajmy jeszcze kod dla publicznej metody SwapColors , która zamienia kolejnością tła:


//wystawiona metoda
$this = $(this);
data = $this.data("changeColor");
tmp = data.colorSecond;
data.colorSecond = data.colorFirst;
data.colorFirst = tmp;

Ostatni krok to uzupełnienie metody dla destruktora:


//destruktor
$this = $(this);
$this.unbind("click");
$this.css("background-color","");
$this.removeData("changeColor");

Gdy zechcemy usunąć plugin z elementu wywołujemy metodę destroy . Pozbywamy się eventa dla kliknięcia na element i czyścimy wszelkie dane.

Nasz gotowy plugin


(function($) {
	$.fn.changeColor = function(options) {
		
		//metoda prywatna
		function onClick(event) {
			//ciało metody prywatnej
			$this = $(this);
			data = $this.data("changeColor");
			data.first = !data.first;
			$this.css("background-color", 
				data.first ? data.colorSecond : data.colorFirst);
			return true;
		}
		
		//metody publiczne
		var methods = {
			swapColors: function() {
				//wystawiona metoda
				$this = $(this);
				data = $this.data("changeColor");
				tmp = data.colorSecond;
				data.colorSecond = data.colorFirst;
				data.colorFirst = tmp;
			},
			destroy: function() {
				//destruktor
				$this = $(this);
				$this.unbind("click");
				$this.css("background-color","");
				$this.removeData("changeColor");	
			}
		};
		
		return this.each(function() {
			//ciało naszego pluginu
			if(methods[options]){	
				//wywołana metoda publiczna
				return methods[options].apply( this, arguments );
			}
			else if (typeof options === 'object' || ! options ){
				//wywołany konstruktor
				var settings = $.extend( {
				  colorFirst : 'Red',
				  colorSecond: 'Green'
				}, options);
				//inicjalizacja pluginu
				$this = $(this);
				data = $this.data("changeColor");
				if(!data)
				{
					data = $this.data("changeColor", settings);
					$this.bind("click", onClick);
				}
				$this.css("background-color", settings.colorFirst);
				data.first = true;
				return;
			}
			else{
				//bład
				$.error('changeColor: no method: '+ options);
			} 
		});	
	}
})(jQuery); 


Jak widać tworzenie pluginów do jQuery nie jest trudne. A zatem, do dzieła! :)