Artykuł

paź 14 2012
0

Jak tworzyć zaawansowane wtyczki w jQuery

Dokładnie rok temu i dzień, popełniłem wpis mówiący o tym jak stworzyć prostą wtyczkę jQuery. W poście tym opisałem w miarę dokładnie (tak przynajmniej mi się wydaje) podstawowe aspekty związane z procesem tworzenia własnych rozszerzeń. Przez kolejny rok, dalej jednak pracowałem zawodowo jako developer i ścierałem się również z jQuery oraz koniecznością tworzenia wtyczek właśnie w tej technologii. Przez ten okres czasu, pojawiło się kilka problematycznych kwestii związanych z aspektem projektowania skalowalnych rozszerzeń.

W dzisiejszym poście chciałbym się nimi z Wami podzielić oraz zaproponować potencjalne rozwiązania nurtujących problemów.

Testowy kod

Zanim przejdziemy do konkretnych problemów, przedstawię kod wtyczki, która choć w teorii nie robi niczego sensownego, ale w praktyce rozwiązuje wiele istotnych kwestii, jakie mogą stanąć na drodze każdego dewelopera.

Nie przejmuj się jeżeli w tym momencie nie do końca rozumiesz sens zamieszczonego tu kodu, poszczególne jego fragmenty omówię szerzej w kolejnych akapitach.

(function($) {
	var m_oDefaults = {
		nTestSetting: 0,
		sTestSetting: 'test',
		ExtraInitFunction: null
	};
	
	var initExampleHelperFunction = function(){
		alert('Do something!');
	};
	
	var m_aoFunctions = {
		init: function(oOptions){
			oOptions = $.extend({}, m_oDefaults, oOptions);
			return this.each(function() {
				var oElement = $(this);
				oElement.data('myplugin', {
					pluginoptions: oOptions
				});
				// init operations....
				initExampleHelperFunction();
				if(null != oElement.data('myplugin')
					.pluginoptions.ExtraInitFunction){
					oElement.data('myplugin').pluginoptions.ExtraInitFunction();
				}
			});
		},
		option: function(oOptions){
			$.extend($(this).data('myplugin').pluginoptions, oOptions);
		},
		getTestSettingText: function(){
			return $(this).data('myplugin').pluginoptions.sTestSetting;
		}
	};
	
	$.fn.myplugin = function(oFunction) {
		if (m_aoFunctions[oFunction]) {
			return m_aoFunctions[oFunction].apply(this, 
				Array.prototype.slice.call(arguments, 1));
		} else if ((typeof oFunction === 'object') || !oFunction) {
			return m_aoFunctions.init.apply(this, arguments);
		} else {
			$.error('Funkcja ' + oFunction + 
				' nie istnieje w przestrzenii jQuery.myplugin');
		}    
	};	
})(jQuery);

Punkt wejścia

Naszą analizę zaczniemy trochę przewrotnie bo od końca (36-46). W tym miejscu znajduje się jawna deklaracja naszego rozszerzenia. Na wstępie określamy jego nazwę (36), a jako parametr tworzonej pseudo-funkcji wskazujemy nazwę jednej z funkcji z tablicy m_aoFunctions. W tej części kodu znajduje się zatem tak naprawdę punkt wejścia do naszego rozszerzenia.

Za pomocą prostej instrukcji if wybieramy:

  • Funkcję przekazaną przez użytkownika
  • Funkcję inicjalizacji, w przypadku przekazania tablicy JSON z opcjami
  • Zgłaszamy wyjątek w sytuacji gdy przekazana przez użytkownika funkcja nie została rozpoznana w przestrzeni nazw naszego rozszerzenia

Dzięki takiej konstrukcji, jesteśmy w stanie wykonywać wybrane przez użytkownika funkcje, już po inicjalizacji konkretnej instancji naszego rozszerzenia.

Tablica wartości domyślnych

W przypadku tablicy wartości domyślnych (2-6), nie bardzo da się wymyślić coś nowego, aczkolwiek można zastosować tu pewien ciekawy trick. Możemy w tym miejscu stworzyć punkt zaczepiania do funkcji użytkowników, których zawartość zostanie określona w momencie deklaracji tablicy opcji.

W ten sposób można utworzyć funkcję inicializacji (5), która zostanie wykonana w trakcie procesu uruchamiania rozszerzenia. Każdy z użytkowników będzie mógł określić własną taką funkcję, a ich implementacja będzie mogła się różnić nawet pomiędzy poszczególnymi instancjami.

Funkcje pomocnicze

Tworząc wtyczkę, możemy skonstruować dowolną ilość funkcji pomocniczych, które działać będą tylko w obszarze kodu zawartego w rozszerzeniu. Takie praktyki są nawet zalecane, ponieważ dzięki temu unikamy wielkich przerośniętych, wszystko robiących funkcji olbrzymów.

W naszym przykładzie również zdefiniowaliśmy sobie taką funkcję (8-10). Podobnie jak cała wtyczka, nie robi ona nic sensownego, ale nie o to w tym momencie chodzi. Tak jak wspomniałem, funkcję te można wywołać tylko wewnątrz kodu użytego w rozszerzeniu. Oznacza to, że nie zadziała w żadnym miejscu na naszej stronie i tym samym nie zajmie cennej przestrzeni nazw.

Wiele instancji - sekret dobrej inicjalizacji

Jednym z poważniejszych problemów z jakimi będziecie musieli się zmierzyć w aspekcie projektowania wtyczek, są kwestie wielo-instancyjności. Problem ten pojawia się w sytuacji, gdy na wtyczce po jej inicjalizacji, będą wykonywane jeszcze jakieś inne funkcje. Niestety problem ten nie objawił się w poprzednim poście, ponieważ tam wtyczki które zostały już utworzone, po prostu funkcjonowały na określonej stronie.

Sytuacja ta może objawić się nam w momencie, gdy podczas pracy będziemy chcieli skorzystać z tablicy opcji naszego pluginu. Jeśli zastosowaliście porady z pierwszego wpisu, to prawdopodobnie wartości w niej zawarte dla wcześniejszej instancji wtyczki, zostały nadpisane wartościami ostatnio zadeklarowanej instancji.

W tym przypadku, należy się więc poważnie zastanowić w jaki sposób można zachować aktualne ustawienia wszystkich instancji danej wtyczki. Na szczęście istnieje bardzo dobre rozwiązanie, a jest nim metoda data z jQuery, która najogólniej mówiąc pozwoli na zapisanie ustawień wtyczki w węźle dla którego została ona uruchomiona.

Cały proces zaczyna się od rozszerzenia wartości domyślnych, wartościami zdefiniowanymi przez użytkownika w czasie inicjalizacji wtyczki (14; polecam tutaj zwrócić uwagę na wywołanie funkcji extend - w tej postaci zwraca ona nowy obiekt). Następnie dla każdego elementu, zapisujemy opcje (17-19; najlepiej pod nazwą tworzonego przez nas rozszerzenia - dzięki temu mamy mniejsze szanse na ewentualny konflikt nazw). W kolejnym kroku wywołujemy pomocniczą funkcję inicializacji o której wspominałem wcześniej (20) oraz wywołujemy funkcję inicializacji zdefiniowaną w opcji rozszerzenia - o ile oczywiście została ona tam zadeklarowana(22-25).

Dokładny przebieg wykonania funkcji init uzależniony jest od ustawień konkretnej instancji rozszerzenia.

Dodatkowe funkcje rozszerzenia

Oczywiście rozszerzenie byłoby mało użyteczne, gdyby podczas jego pracy nie można było wywoływać innych funkcji. W naszym przypadku zostały zdefiniowane dwie dodatkowe funkcje.

Pierwsza z nich - option, umożliwia nadpisanie wartości wybranego ustawienia, lub nawet całej tablicy ustawień (28-30). W tym przypadku ponownie korzystamy z extend jednak teraz wywołujemy inną postać tej funkcji, która uzupełnia nowymi wartościami tablicę przekazaną jako pierwszy parametr.

Druga z funkcji umożliwia pobranie wartości zmiennej sTestSetting (31-33). Warto tutaj zwrócić uwagę, że w tym przypadku korzystamy z obiektu data przypisanego do konkretnego elementu, do którego odnosi się konkretna instancja wtyczki.

Testy, testy, testy

Aby sprawdzić działanie naszej wtyczki, warto przeprowadzić krótki test. Jego istotą jest przede wszystkim dowiedzenie, że wiele instancji wybranego rozszerzenia może współistnieć na jednej stronie. Ponadto sprawdzimy w praktyce działanie kilku wybranych funkcji.

Spójrzmy na kod przykładowej strony testowej:

<!DOCTYPE html>
<html lang="pl">
	<head>
		<meta charset="utf-8" />
		<title>MyPlugin test</title>
		<script src=
			"https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js">
		</script>
		<script src="myplugin.js"></script>
		<script>
			$(document).ready(function() {
				$('#par1').myplugin({
					sTestSetting: 'qwerty'
				});
				$('#par2').myplugin();
				alert($('#par1').myplugin('getTestSettingText'));
				$('#par1').myplugin('option', {sTestSetting: 'awerty'});
				alert($('#par1').myplugin('getTestSettingText'));
				alert($('#par2').myplugin('getTestSettingText'));
			});
			
		</script>
	</head>
	<body>
		<p id="par1">Par1</p>
		<p id="par2">Par2</p>
	</body>
</html>

Nas oczywiście najbardziej interesuje zawartość bloku kodu znajdującego się w liniach 11-20.

Na początku tworzymy dwie instancje rozszerzenia (12-15). W pierwszej z nich zmieniamy wartość zmiennej sTestSetting.

W kolejnym kroku sprawdzamy wartość tej zmiennej dla pierwszej wtyczki (16). Następnie, korzystając z funkcji option aktualizujemy tablicę opcji, w której definiujemy nową wartość testowanej zmiennej(17). Znanym już sposobem sprawdzamy sprawdzamy jej nową wartość, a także weryfikujemy czy wartość domyślna dla drugiej instancji wtyczki została podtrzymana. Polecam samodzielnie przetestować powyższy kod, ale mogę Wam już zdradzić z góry, że skrypt zachowa się dokładnie tak, jak tego oczekujemy:)

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus