Frameworka jQuery nie trzeba nikomu przedstawiać. I choć nie wszystkim podoba się ta biblioteka to nie można zaprzeczyć jednemu – jQuery jest bardzo proste w nauce, jak i dalszej rozbudowie o nowe funkcje (pluginy, selektory i inne).
W tym wpisie zajmę się przydatną, aczkolwiek rzadko wykorzystywaną możliwością rozszerzania jQuery o niestandardowe selektory.
Wbudowane selektory w jQuery
Każdy kto korzysta z jQuery korzysta też z selektorów, którymi dysponuje i które są wbudowane w standardową bibliotekę.
Jako ciekawostkę warto dodać, iż selektory w jQuery są realizowane z wykorzystaniem biblioteki Sizzle. Biblioteka ta umożliwia dostęp do drzewa DOM w sposób jaki znamy z CSS.
Kilka przykładów selektorów dla przypomnienia o czym mówimy:
- wszystkie kontenery DIV z klasą timer:
1$('div.timer')
- wszystkie elementy INPUT typu checkbox:
1$(':checkbox')
- pierwszy element LI z elementu MENU:
1$('li:first', 'menu')
- wszystkie linki poza tymi, które zawierają obrazki:
1$('a:not(:has(>img))')
Jak więc widzisz, selektory ułatwiają życie i sprawiają, że tworzony kod jest re-używalny.
Niestandardowe selektory w jQuery
Twórcy biblioteki jQuery nie są w stanie przewidzieć wszystkich przypadków użycia selektorów w tworzonych aplikacjach, więc udostępnili prosty interfejs na tworzenie własnych selektorów.
Aby więc dodać niestandardowe selektory w jQuery wklepujemy następujący kod:
1 2 3 | $.expr[':'].mySelector = function(objNode, intStackIndex, arrProperties, arrNodeStack) { // kod obsługujący selektor }; |
Jest też inna opcja na rozszerzanie jQuery o własne selektory, umożliwiająca dodatkowo dodawanie więcej niż jednego selektora na raz:
1 2 3 4 5 6 7 8 9 10 11 | $.extend($.expr[':'], { mySelector1: function(objNode, intStackIndex, arrProperties, arrNodeStack) { // kod obsługujący selektor }, mySelector2: function(objNode, intStackIndex, arrProperties, arrNodeStack) { // kod obsługujący selektor } }); |
Nasza funkcja z selektorem przyjmuje cztery parametry:
- objNode – HTML DOM Element
Jest to referencja do bieżącego elementu w drzewie DOM. Nie jest to element jQuery, lecz węzeł DOM. - intStackIndex – Integer
Indeks aktualnego węzła w stosie, na którym zastosowano selektor (liczony od 0). - arrProperties – Array
Jest to zestaw metadanych z wywołanego selektora. Czwarty element tablicy zawiera przekazane parametry w selektorze. Pozostałe elementy nie wnoszą nic ciekawego, może poza elementem trzecim, który zawiera nawiasy zastosowane w selektorze (czasami można z tego skorzystać, co pokażę w dalszym przykładzie). - arrNodeStack – Array
Zwraca kolekcję wszystkich elementów, na których użyto selektor.
Praktyczny przykład niestandardowego selektora
Na szybko napisałem selektor, który wyszukuje zewnętrzne linki:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | // define the custom selector $.extend($.expr[':'], { /** * Check whether links are external * * @param object HTML DOM Element * @param int zero-based index of the given node * @param array meta data about the custom jQuery selector execution * @param array DOM elements that are being evaluated with this selector * * @return bool link is external **/ external: function(el, i, args, nodes) { // selector only for links if (el.nodeName.toLowerCase() !== 'a' && el.nodeName.toLowerCase() !== 'link') { return false; } // set default properties args[2] = args[2] || '"'; args[3] = args[3] || ''; // get selector arguments var arrArguments = eval("([" + args[2] + args[3] + args[2] + "])"), arrLength = arrArguments.length, // host has been detected hostDetect = false; // search for host if (arrLength > 0) { for (var i = 0; i < arrLength; i++) { if (el.href.search(arrArguments[i]) != -1) { hostDetect = true; } } } else { hostDetect = true; } // check external link return hostDetect && (el.rel.search('external') != -1 || el.href.search(window.location.hostname) == -1); } }); |
Na szybko opiszę co robi powyższy kawałek kodu kiedy ktoś użyje selektora external na kolekcji elementów:
- Sprawdzamy czy mamy do czynienia z odsyłaczem (element A lub LINK).
- Ustawiamy domyślne wartości dla tablicy arrProperties – potrzebne w przypadku, gdy do selektora nie przekazano żadnych parametrów.
- Tworzymy listę argumentów przekazanych do selektora – niestety argumenty przekazane przez jQuery są typu string, dlatego czasem trzeba kombinować, stosując np. brzydki eval do zamiany na tablicę, jak w moim przypadku.
- Jeśli do selektora przekazano jakieś parametry to przechodzimy przez nie w pętli i kolejno sprawdzamy czy dany odsyłacz zawiera się w dozwolonych domenach (przekazanych jako argument).
- Na koniec sprawdzamy czy link jest zewnętrzny – czy zawiera atrybut rel=”external” lub link kieruje do zewnętrznej domeny.
Selektor w praktyce wygląda następująco:
- znajdź linki zewnętrzne i ustaw atrybut target=”_blank” (otwórz w nowy oknie):
1$('a:external').attr('target', '_blank');
- usuń wszystkie linki zewnętrzne, które prowadzą do Twittera lub Google+:
1$('a:external("plus.google.com", "twitter.com")').remove();
I to by było na tyle, pozostaje zabrać się za pisanie własnych selektorów :-) Jeśli ktoś pisze i wykorzystuje we własnych aplikacjach to zapraszam do dzielenia się kodem w komentarzach.
Warto odwiedzić
- Selektory jQuery – zanim napiszesz własny selektor sprawdź, czy nie istnieje już podobny w bibliotece jQuery.
- Extending jQuery’s selector capabilities – świetny artykuł o tworzeniu własnych selektorów od James’a Padolsey (poparty wieloma przykładami).
Można jQuery nie lubić, ale trzeba napisać, że właśnie dzięki takiej prostocie jest to framework #1 jeśli chodzi o .js :)
Czy aby na pewno tylko jQuery? Prototype.js też jedzie na Sizzle
Aż sobie popiszę na blogu o własnych selektorach do prototype :) Kamila przykład w prototype wygląda tak (kasowanie elementów z rel=external i ustawionym href)
2
3
4
5
return elem.href && elem.rel.match(/external/i);
};
$$('a:external[href=plus.google.com]', 'a:external[href=twitter.com]').invoke('remove');
@Krystian, dzięki takim rzeczom właśnie nie można jQuery nie lubić ;-)
@Krystian:
Interfejs ten nie jest idealny, choćby dlatego, że w parametrach mamy niepotrzebny bałagan. Mogliby więc zrobić to lepiej (np. http://james.padolsey.com/javascript/extending-jquerys-selector-capabilities/ – UPDATE #2). Co nie zmienia faktu, że i tak jest dostatecznie prosto i przyjemnie:-)
@Michal Wachowski:
Nie twierdzę, że tylko jQuery jest proste. Na podstawie Twojego kodu widać, że Prototype rozwiązał sprawę dużo fajniej :-)
@Kamil – bo dał bezpośredni dostęp do Sizzle, przez co mogę modyfikować, dodawać selektory wg schematu, albo całkowicie zmienić mechanizm selectora i zrezygnować z Sizzle na coś innego.