Artykuł

paź 13 2011
0

Tworzenie własnych rozszerzeń jQuery

Wielokrotnie na łamach Alt Control Delete, pisałem na temat jQuery. Były tematy związane z selektorami, zdarzeniami, czy też wykorzystaniem dodatkowych wtyczek. Nigdy nie pojawił się jednak żaden dedykowany wpis, który opisywałby proces tworzenia wtyczki od podstaw, z różnymi związanymi z tym tematem aspektami (raz pojawił się jedynie wpis, który opisywał moją autorską wtyczkę, wykorzystywaną na tym blogu do walidacji formularzy).

Dziś chciałbym nadrobić zaległości i omówić krok po kroku cały proces tworzenia wtyczki jQuery.

Idea tworzenia rozszerzeń jQuery

Jedną z głównych zasad, w codziennej sztuce projektowania/tworzenia jest dla mnie unikanie powtarzalności kodu. Co mam w tym momencie na myśli?

Spójrzmy na przykład na Alt Control Delete. U góry i u dołu każdej strony, widoczne są linki nawigacyjne, które pozwalają szybko przenieść się w wybrany obszar strony np. do góry, do menu, bądź też do stopki strony. Funkcjonalność ta została właśnie zrealizowana za pomocą rozszerzenia jQuery. Dzięki temu, dla każdego odnośnika, do którego została podpięta wtyczka, zostanie zrealizowany dokładnie ten sam kod (oczywiście z innymi ustawieniami).

Oprócz zapewnienia pewnej unifikacji kodu, wtyczki zwiększają skalowalność oraz rozszerzalność, a także pozwalają na tworzenie zmiennych oraz właściwości, które mogą ustawiać docelowi użytkownicy tych rozszerzeń.

Nie da się ukryć, że wtyczki to jeden z większych atutów tego framework'a. Dzięki rozszerzaniu obiektu jQuery, jesteśmy w stanie korzystać z naszego pluginu w dowolnym miejscu strony (oczywiście musimy mieć w tym czasie dostęp do kodu wtyczki).

Jednak mimo oczywistych zalet tworzenia własnych rozszerzeń, nie powinniśmy wpadać w skrajności w tej materii. Jeśli tworzymy kod, dedykowany pod konkretną stronę/aplikację i jest to kod, który nie będzie nigdzie więcej wykorzystywany, to nie ma żadnych przesłanek do tworzenia wtyczki jQuery dla takiego rozwiązania (chyba, że jesteście masochistami, z dużą ilością wolnego czasu;)) Zawsze, w sytuacji kiedy kod jest niepowtarzalny i że tak powiem lokalny, powinniśmy raczej darować sobie tworzenie nowego rozszerzenia, chyba że istnieją ku temu jakieś sensowne przesłanki (np. szansa na powtórne wykorzystanie kodu w przyszłości).

Zadanie dla wtyczki

Zanim przystąpimy do pracy, musimy sobie zadać jedno, ale za to bardzo ważne pytanie - co też nasza super wtyczka ma dla nas czynić?

Dla naszych dzisiejszych manewrów, możemy wykorzystać funkcjonalność dynamicznego scrollowania strony, o której wspomniałem wcześniej.

Wiedząc już co chcemy zrobić, rozpoczniemy teraz tworzenie pluginu, który tworzyć będziemy metodą progresywnego ulepszania.

Tworzenie podstawowej struktury wtyczki

Tak jak wspomniałem wcześniej, tworząc nową wtyczkę rozszerzamy obiekt jQuery.fn dodając nową właściwość funkcji. Czynimy to mniej więcej w następujący sposób:

jQuery.fn.myPluginName = function() {
    // Plugin code
};

Zapewne w tym momencie, zastanawiacie się gdzie jest charakterystyczny $ ,z którego tan namiętnie zawsze korzystamy w jQuery? Został on celowo zastąpiony słowem kluczowym jQuery, które jest mu tożsame. Użycie słowa kluczowego, zapewnia kompatybilność z innymi frameworkami, które również używają znaku dolara. Na szczęście idzie to łatwo obejść następującą konstrukcją:

(function($) {
    $.fn.myPluginName  = function() {
		// Plugin code
	};
})(jQuery);

W tym przypadku, tworzymy samowywołującą się funkcję, która mapuje znak dolara. Wewnątrz tej zewnętrznej funkcji, musimy umieścić cały kod naszego rozszerzenia.

Kod testowy

Do testów, potrzebować będziemy oczywiście trochę kodu HTML, zasadniczo powinno wystarczyć mniej więcej coś takiego:

<!DOCTYPE html>
<html lang="pl">
	<head>
		<meta charset="utf-8" />
		<script    
		    src="http://ajax.googleapis.com/ajax/libs/jquery/1.6/jquery.min.js">
        </script>
		<!-- kod naszej wtyczki, bądź link do pliku z nią -->
		<title>Test</title>
	</head>
	<body>
		<h1 id="page-header">Nagłówek</h1>
		<p>Tutaj jakiś bardzo długi tekst, który wymaga przewijania strony...</p>
		<a href="#" id="bottom-to-top-btn">do góry</a>
	</body>
</html>

Oczywiście, tak jak wspomniałem w komentarzach, musimy jeszcze tutaj dodać kod samego rozszerzenia, jego wywołanie oraz dostatecznie dużą ilość tekstu, która zapewni konieczność przewijania ekranu.

Nasza wtyczka będzie działać w bardzo prosty sposób. Zostanie ona podpięta do elementu o identyfikatorze bottom-to-top-btn, który po kliknięciu (bądź też innym zdarzeniu - o tym później) przenosić będzie nas płynnym przewinięciem do nagłówka o identyfikatorze page-header.

Oczywiście wtyczka będzie uniwersalna i będzie ją można docelowo zastosować dla dowolnej pary elementów.

Podstawowy kod wtyczki

Umiemy już stworzyć ramową strukturę wtyczki, czas więc zatem przejść do konkretów. Naszą wtyczkę do przewijania, zbudujemy trochę w inny sposób niż ma to miejsce na Alt Control Delete. Bez dłuższego przeciągania, spójrzmy na pierwszą wersję wtyczki:

(function($) {
	$.fn.scrollTo  = function() {
		return this.bind("click", function(oEvent) {
			oEvent.preventDefault();
			var oTargetElement = $("#page-header");
			var nTargetOffset = oTargetElement.offset().top;
			$("html,body").animate({scrollTop: nTargetOffset}, 1000, "swing");
		});	
	};
})(jQuery);

Za sprawą powyższego kodu uzyskaliśmy działającą wtyczkę, którą możemy podpiąć do dowolnego elementu naszej strony. Niestety w chwili obecnej, elementem do którego nasza wtyczka będzie przewijać stronę, będzie zawsze nasz nagłówek. Omówmy jednak najpierw bieżący kod, by wiedzieć co obecnie się tu wyprawia, a później zastanowimy się co z tym fantem (oraz kilkoma innymi) zrobić.

Na powyższym listingu, interesuje nas kod w liniach od 3-8 (kod struktury wtyczki powinniście już w tym momencie znać). W linii 3 (czyli w momencie, w którym zaczyna się właściwy kod wtyczki) od razu korzystamy z klauzuli return. Choć może się to wydawać dziwne, to tak naprawdę jest to proceder bardzo popularny w wielu mniejszych wtyczkach.

Istotą zwracania wyniku, jest wykonanie kodu dla tablicy elementów przekazanej w wywołaniu wtyczki. Nasza wtyczka ma docelowo zostać podpięta do elementu o identyfikatorze bottom-to-top-btn. Nic nie stoi jednak na przeszkodzie, by podpiąć ja do dowolnego paragrafu (a tych z reguły jest więcej niż jeden na stronie).

Wróćmy jednak do kodu. Tak jak mówiłem wyżej, w linii 3 podpinamy obsługę określonego zdarzenia (w tym przypadku click) do wszystkich elementów przekazanych w tablicy. Celowo użyłem w tym przypadku funkcji bind, ponieważ dzięki temu w łatwy sposób będziemy mogli zmienić typ zdarzenia, które aktywuje dalsze akcje naszego rozszerzenia.

W linii 4, zapobiegamy domyślnej akcji, skojarzonej z tym zdarzeniem (czyli np. w przypadku elementu a - kliknięciu i przejściu pod wybrany adres).

W linii 5, określamy element, do którego ma przewijać się strona. To bardzo ważne miejsce. W tym przypadku docelowy element określiliśmy na sztywno w kodzie (przynajmniej na razie) - unikajcie stosowania takich praktyk w rzeczywistości.

W linii 6, określamy wartość liczbową (punkt strony), do którego ma nastąpić przesunięcie. Wykorzystujemy do tego funkcje jQuery.

Zwieńczeniem prac naszej wtyczki jest linia 7, w której animujemy nasze przesunięcie za pomocą metody animate. Metoda ta, przyjmuje kilka argumentów. Na początku wskazujemy w jaki sposób ma być realizowana animacja, a następnie za pomocą wartości 1000, podajemy czas w milisekundach w jakim ma zostać ona zrealizowana oraz słownie podajemy jedną z właściwości typu easing, która jest wykorzystywana podczas animacji.

Testowanie...

Aby przetestować kod, najlepiej umieścić wywołanie wtyczki w sekcji document...ready. Dzięki temu, będziemy mieć pewność, że cały kod html naszej strony został załadowany. Spójrzmy zatem na poniższy kod:

$(document).ready(function() {
	$("#bottom-to-top-btn").scrollTo();
});

Po załadowaniu się strony, do elementu o identyfikatorze bottom-to-top-btn podpinamy obsługę przewijania. Prawda, że proste:)?

Wartości domyślne i opcje

W chwili obecnej, napisany przez nas kod, ma niestety kilka istotnych wad. Przede wszystkim, docelowy element do którego przewijać ma się nasza strona określony jest na stałe. Podobnie ma się sprawa z typem zdarzenia, które w tej chwili na sztywno zostało określone jako kliknięcie. Istnieje również kilka mniej, lub bardziej istotnych rzeczy, które mogłyby być konfigurowalne.

Aby móc korzystać z opcji, musimy zdefiniować tablicę z mapą wartości domyślnych. Dzięki temu, do pluginu będziemy podawać tylko te zmienione wartości, które chcemy w tej konkretnej jego instancji zmodyfikować.

Spójrzmy zatem na rozszerzoną wersję pluginu, obsługującą dodatkowe opcje:

(function($) {
	$.fn.scrollTo  = function(oOptions) {
		var oDefaults = {
			sEventType: "click",
			sTargetElementSelector: "body",
			nSpeed: 1000,
			sEasingEffect: "linear"
		};
		var oOptions = jQuery.extend(oDefaults, oOptions);
		return this.bind(oOptions.sEventType, function(oEvent) {
			oEvent.preventDefault();
			var oTargetElement = $(oOptions.sTargetElementSelector);
			var nTargetOffset = oTargetElement.offset().top;
			$("html,body").animate({scrollTop: nTargetOffset}, 
				oOptions.nSpeed, oOptions.sEasingEffect);
		});	
	};
})(jQuery);

Przeanalizujemy pokrótce co się tu dzieje. Jak widać, zmiany dotknęły praktycznie cały kod. Na początku, w linii 2, przekazujemy obiekt opcji, który jest mapą par klucz-wartość, które przekazujemy do naszego rozszerzenia.

W liniach 3-8, definiujemy obiekt zawierający mapę wartości domyślnych. Nasz obiekt ma cztery konfigurowalne wartości:

  • sEventType - typ zdarzenia np. click, mouseover
  • sTargetElementSelector - element do którego ma się przewijać ekran po zajściu zdarzenia określonego w poprzedniej właściwości. Domyślnie jest to body - czyli przewijanie nastąpi do góry strony
  • nSpeed - wartość w milisekundach, używana do metody animate
  • nEasing - wartość easing używana do animacji

Po określeniu wartości domyślnych, czynimy magię w linii 9. Za pomocą metody extend z obiektu jQuery nadpisujemy wartości domyślne naszego rozszerzenia, wartościami przekazanymi w obiekcie oOptions. Metoda ta działa na tyle inteligentnie, że zmienia tylko wartości, które zostały podane w wywołaniu pluginu. Jeśli jakieś wartości nie zostały podane, to rozszerzenie automatycznie skorzysta z wartości domyślnych.

Aby skorzystać z określonych przez nas ustawień, musimy się odwołać do ustanowionych przez nas właściwości obiektu oOptions, np. w ten sposób: oOptions.sEventType.

Zasadnicze wywołanie naszej wtyczki nie zmieniło się. Aby jednak skorzystać z nowych funkcjonalności, musimy w wywołaniu funkcji przekazać obiekt zawierający mapę naszych właściwości:

$(document).ready(function() {
	$("#bottom-to-top-btn").scrollTo({
		nSpeed: 5000, 
		sEventType: "mouseover",
		sEasingEffect: "linear", 
		sTargetElementSelector: "#page-header"
	});
});

W powyższym przykładzie, nadpisałem wszystkie możliwe wartości (poszczególne pary klucz:wartość należy rozdzielać przecinkiem). Nic nie stoi jednak na przeszkodzie, by nadpisać określone wartości, bądź też tak jak wspomniałem wyżej, wywołać rozszerzenie bez żadnego argumentu. W obu wyżej wymienionych przypadkach zostaną zastosowane wartości domyślne, dla elementów które zostały niezmienione.

Funkcje pomocnicze

Nie zanosi się, by nasze rozszerzenie urosło do monstrualnych rozmiarów. Cały nasz kod, możemy z powodzeniem ulokować w jednym miejscu. Warto jednak wiedzieć, że w samym rozszerzeniu możemy bezproblemowo korzystać z innych funkcji. Stworzymy zatem mała funkcję (tak trochę na siłę), która wyliczać będzie dla nas wartość offsetu. Funkcja ta, będzie dostępna tylko z wnętrza naszego rozszerzenia. Dzięki temu, nie musimy się obawiać o żadne konflikty nazw. Spójrzmy na kod:

(function($) {
	$.fn.scrollTo  = function(oOptions) {
		var CalculateOffset = function(oTargetElement){
			return oTargetElement.offset().top;
		};
		var oDefaults = {
			sEventType: "click",
			sTargetElementSelector: "body",
			nSpeed: 1000,
			sEasingEffect: "linear"
		};
		var oOptions = jQuery.extend(oDefaults, oOptions);
		return this.bind(oOptions.sEventType, function(oEvent) {
			oEvent.preventDefault();
			var oTargetElement = $(oOptions.sTargetElementSelector);
			$("html,body").animate({scrollTop: CalculateOffset(oTargetElement)}, 
				oOptions.nSpeed, oOptions.sEasingEffect);
		});	
	};
})(jQuery);

Myślę, że kod przedstawiony powyżej, jest na tyle klarowny, że nie wymaga już dodatkowego wyjaśnienia. Wywołanie samego pluginu, nie zmieniło się po tej ingerencji.

Funkcje użytkownika

Przekazując obiekt ustawień użytkownika, możemy nadpisywać również funkcje zawarte w naszym rozszerzeniu. Przykładowo, możemy stworzyć funkcję beforeStart, która będzie aktywowana tuż przed wywołaniem animacji. Domyślnie byłaby ona pusta, ale dalibyśmy użytkownikom możliwość jej nadpisania, tak jak robiliśmy to przed chwilą z różnymi właściwościami. Funkcje tego typu, mogą zatem zachowywać się jak klasyczne zdarzenia.

Przejdźmy do kodu:

(function($) {
	$.fn.scrollTo  = function(oOptions) {
		var CalculateOffset = function(oTargetElement){
			return oTargetElement.offset().top;
		};
		var oDefaults = {
			sEventType: "click",
			sTargetElementSelector: "body",
			nSpeed: 1000,
			sEasingEffect: "linear",
			beforeStart: function(){
				// nothing here
			}
		};
		var oOptions = jQuery.extend(oDefaults, oOptions);
		return this.bind(oOptions.sEventType, function(oEvent) {
			oEvent.preventDefault();
			oOptions.beforeStart();
			var oTargetElement = $(oOptions.sTargetElementSelector);
			$("html,body").animate({scrollTop: CalculateOffset(oTargetElement)}, 
				oOptions.nSpeed, oOptions.sEasingEffect);
		});	
	};
})(jQuery);

Jak widać, nasza nowa funkcja została umieszczona w obiekcie oDefaults, a jej ciało jest puste. Wywołanie funkcji, następuje zaraz przed samą animacją. Pusta funkcja nie zrobi nam nic złego, ale i dobrego też nie. Kluczem do sukcesu, będzie odpowiednie wywołanie samego pluginu. Np. takie:

$(document).ready(function() {
	var oElement = $("#bottom-to-top-btn").scrollTo({
		beforeStart: function(){
			alert("Rozpoczynam animację!");
		}
	});
});

Jak widać, nadpisaliśmy funkcję beforeStart i teraz, za każdym razem kiedy akcja naszego rozszerzenia zostanie wywołana, na ekranie wyświetli się komunikat: Rozpoczynam animację!.

Oczywiście taki przykład, w rzeczywistości może być delikatnie mówiąc upierdliwy, ale za to w świetny sposób pokazuję istotę sprawy.

Podsumowanie

Przedstawiony w niniejszym wpisie materiał, powinien pozwolić na tworzenie prostych i średnio zaawansowanych rozszerzeń. W przypadku bardziej ambitniejszych projektów, konieczne może okazać się użycie dedykowanych zdarzeń oraz metod, wywoływanych już po aktywacji wtyczki.

Wszystkie listingi zaprezentowane w niniejszym wpisie, zostały sprawdzone i przetestowane. Działające demo, można obejrzeć tutaj.

W razie pytań, wątpliwości, zapraszam do komentarzy;)

Materiały w sieci

Data ostatniej modyfikacji: 28.11.2011, 18:36.

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

Send to Kindle

Komentarze

blog comments powered by Disqus