selektor danych jquery

184

Muszę wybierać elementy na podstawie wartości przechowywanych w .data()obiekcie elementu . Przynajmniej chciałbym wybrać właściwości danych najwyższego poziomu za pomocą selektorów, być może tak:

$('a').data("category","music");
$('a:data(category=music)');

A może selektor miałby zwykły format selektora atrybutów:

$('a[category=music]');

Lub w formacie atrybutu, ale ze specyfikatorem wskazującym, że jest w .data():

$('a[:category=music]');

Znalazłem realizację Jamesa Padolsey za patrzeć proste, ale dobra. Formaty selektora powyżej metod dublowania pokazane na tej stronie. Jest też ta łatka Sizzle .

Z jakiegoś powodu przypominam sobie, że jakiś czas temu jQuery 1.4 zawierałoby obsługę selektorów wartości w .data()obiekcie jquery . Jednak teraz, gdy go szukam, nie mogę go znaleźć. Może widziałem tylko prośbę o dodanie funkcji. Czy jest na to wsparcie i po prostu tego nie widzę?

Idealnie, chciałbym wesprzeć właściwości podrzędne w data () za pomocą notacji kropkowej. Lubię to:

$('a').data("user",{name: {first:"Tom",last:"Smith"},username: "tomsmith"});
$('a[:user.name.first=Tom]');

Chciałbym również obsługiwać wiele selektorów danych, w których znajdują się tylko elementy ze WSZYSTKIM określonymi selektorami danych. Zwykły wielokrotny selektor jquery wykonuje operację LUB. Na przykład $('a.big, a.small')wybiera atagi z klasą biglub small). Szukam AND, być może takiego:

$('a').data("artist",{id: 3281, name: "Madonna"});
$('a').data("category","music");
$('a[:category=music && :artist.name=Madonna]');

Wreszcie byłoby wspaniale, gdyby operatory porównania i funkcje wyrażenia regularnego były dostępne w selektorach danych. Tak $(a[:artist.id>5000])byłoby możliwe. Zdaję sobie sprawę, że prawdopodobnie mógłbym wiele z tego zrobić filter(), ale byłoby miło mieć prosty format selektora.

Jakie rozwiązania są dostępne, aby to zrobić? Czy Jame's Padolsey jest obecnie najlepszym rozwiązaniem? Moje obawy dotyczą przede wszystkim wydajności, ale także dodatkowych funkcji, takich jak notacja kropkowa pod-właściwości i wiele selektorów danych. Czy istnieją inne implementacje, które wspierają te rzeczy lub są w jakiś sposób lepsze?

Tauren
źródło
Skorzystaj z interfejsu jquery ui core api.jqueryui.com/category/ui-core Posiada selektor: data ()
regisbsb

Odpowiedzi:

101

Stworzyłem nowy dataselektor, który powinien umożliwić wykonywanie zagnieżdżonych zapytań i warunki ORAZ. Stosowanie:

$('a:data(category==music,artist.name==Madonna)');

Wzór jest następujący:

:data( {namespace} [{operator} {check}]  )

„operator” i „sprawdź” są opcjonalne. Tak więc, jeśli tylko :data(a.b.c)będzie to po prostu sprawdzić na truthiness dnia a.b.c.

Możesz zobaczyć dostępnych operatorów w kodzie poniżej. Jednym z nich jest ~=testowanie wyrażeń regularnych:

$('a:data(category~=^mus..$,artist.name~=^M.+a$)');

Przetestowałem to z kilkoma odmianami i wydaje się, że działa całkiem dobrze. Prawdopodobnie wkrótce dodam to jako repozytorium Github (z pełnym pakietem testowym), więc uważaj!

Kod:

(function(){

    var matcher = /\s*(?:((?:(?:\\\.|[^.,])+\.?)+)\s*([!~><=]=|[><])\s*("|')?((?:\\\3|.)*?)\3|(.+?))\s*(?:,|$)/g;

    function resolve(element, data) {

        data = data.match(/(?:\\\.|[^.])+(?=\.|$)/g);

        var cur = jQuery.data(element)[data.shift()];

        while (cur && data[0]) {
            cur = cur[data.shift()];
        }

        return cur || undefined;

    }

    jQuery.expr[':'].data = function(el, i, match) {

        matcher.lastIndex = 0;

        var expr = match[3],
            m,
            check, val,
            allMatch = null,
            foundMatch = false;

        while (m = matcher.exec(expr)) {

            check = m[4];
            val = resolve(el, m[1] || m[5]);

            switch (m[2]) {
                case '==': foundMatch = val == check; break;
                case '!=': foundMatch = val != check; break;
                case '<=': foundMatch = val <= check; break;
                case '>=': foundMatch = val >= check; break;
                case '~=': foundMatch = RegExp(check).test(val); break;
                case '>': foundMatch = val > check; break;
                case '<': foundMatch = val < check; break;
                default: if (m[5]) foundMatch = !!val;
            }

            allMatch = allMatch === null ? foundMatch : allMatch && foundMatch;

        }

        return allMatch;

    };

}());
James
źródło
@JP: Bardzo słodko, wiedziałem, że mogę na ciebie liczyć! Udałem się teraz do łóżka, ale wypróbuję to jutro. Czy można również wykonywać operacje LUB? Nie potrzebuję tego, po prostu ciekawy.
Tauren
1
@JP: Jestem również ciekawy, jak oceniasz inne rozwiązania do wyboru danych, zalety / wady dla twojego kontra ich. Na przykład ta wtyczka: plugins.jquery.com/project/dataSelector .
Tauren
1
Możliwe jest wykonywanie operacji LUB, ale najlepiej mieć po prostu dwa selektory, $("a:data(condition),a:data(orCondition)")... to będzie miało ten sam efekt. Im więcej funkcji dodasz, tym będzie wolniej. Jeśli logika jest złożona, użyj $(foo).filter(function(){...}).
James
3
@JP: Czy kiedykolwiek zamieniłeś to w projekt github? Przeprowadzam test przy użyciu jQuery 1.5.1, używając selektora $ („input: data (test> 400)”) na danych wejściowych z atrybutem html5 data-test = „500”, ale selektor nic nie zwraca.
James South
1
@JamesSouth To dlatego, że selektor w tej odpowiedzi używa niskiego poziomu jQuery.data, który nie pobiera danych zdefiniowanych w atrybutach HTML5. Jeśli chcesz tę funkcjonalność, możesz zmienić jQuery.datana $('selector').data, ale to kompromis dla szybkości.
Shef
175

W tej chwili wybieram tak:

$('a[data-attribute=true]')

Co wydaje się działać dobrze, ale byłoby miło, gdyby jQuery mógł wybrać ten atrybut bez prefiksu „data-”.

Nie testowałem tego z danymi dodawanymi dynamicznie do elementów za pomocą jQuery, więc może to być wadą tej metody.

Popiół
źródło
Właśnie tego szukałem. Słodki
MikeMurko
109
To nie działa, jeśli dołączasz / modyfikujesz dane za pomocą JS za pomocą funkcji .data (), selektor atrybutów sprawdza tylko DOM, JS przechowuje .data () w pamięci
Clarence Liu
1
Czy możesz powiedzieć, w jaki sposób uzyskujesz dostęp według atrybutu, jeśli dołączasz za pomocą .data ()?
Maxim V. Pavlov
1
Sugeruję, że inne odpowiedzi podane w tym wątku są lepszą opcją dla czegoś innego niż bardzo prosty przypadek użycia.
Ash
Rozwiązanie Dmitri wygląda dla mnie ładnie, ponieważ nie opiera się na kodzie firm trzecich.
Ash
83

Możesz także użyć prostej funkcji filtrowania bez żadnych wtyczek. Nie jest to dokładnie to, czego chcesz, ale wynik jest taki sam:

$('a').data("user", {name: {first:"Tom",last:"Smith"},username: "tomsmith"});

$('a').filter(function() {
    return $(this).data('user') && $(this).data('user').name.first === "Tom";
});
Dmitri
źródło
2
To świetny pomysł, zapomniałem, że filterfunkcja przejścia może zaakceptować funkcję testową =) dzięki
Clarence Liu
Jak mogę zrobić „zawiera” zamiast równa się Tomowi?
Alisso
1
Alisso, zamiast name.first == „Tom” użyj name.first && name.first.indexOF („Tom”)> -1;
Dmitri
24

Chcę cię ostrzec $('a[data-attribute=true]') to nie działa, zgodnie z odpowiedzią Ashleya, jeśli dołączyłeś dane do elementu DOM za pomocą funkcji data ().

Działa tak, jak można się spodziewać, jeśli dodasz rzeczywistą wartość danych do kodu HTML, ale jQuery przechowuje dane w pamięci, więc wyniki, które uzyskasz $('a[data-attribute=true]') , nie byłyby prawidłowe.

Musisz użyć wtyczki danych http://code.google.com/p/jquerypluginsblog/ , skorzystać z filterrozwiązania Dmitrigo lub wykonać $ .each dla wszystkich elementów i sprawdzić iteracyjnie .data ()

Clarence Liu
źródło
W moim przypadku jest w porządku, ponieważ wstępnie wypełniam pole po stronie serwera i muszę tylko skonsultować to w ten sposób przy pierwszym załadowaniu. Filtr jest prawdopodobnie równie szybki (prawie na pewno wolniejszy), ale zyskuje na czytelności.
Don
11

Jest :data()wtyczka filtra , która właśnie to robi :)

Kilka przykładów opartych na twoim pytaniu:

$('a:data("category=music")')
$('a:data("user.name.first=Tom")');
$('a:data("category=music"):data("artist.name=Madonna")');
//jQuery supports multiple of any selector to restrict further, 
//just chain with no space in-between for this effect

Wydajność nie będzie wyjątkowo świetna w porównaniu z tym, co jest możliwe , wybieranie $._cachei chwytanie odpowiednich elementów jest zdecydowanie najszybsze, ale o wiele bardziej okrągłe i niezbyt „jQuery-ey” pod względem sposobu dotarcia do rzeczy (zazwyczaj przychodzisz od strony elementu). Co do mojej głowy, nie jestem pewien, czy i tak jest to najszybsze, ponieważ proces przechodzenia od unikalnego identyfikatora do elementu jest sam w sobie skomplikowany pod względem wydajności.

Wspomniany selektor porównania najlepiej będzie zrobić w .filter(), nie ma wbudowanej obsługi tego we wtyczce, chociaż można go dodać bez większych problemów.

Nick Craver
źródło
dzięki za dokładną odpowiedź. Czy wiesz, że użycie data-*atrybutów HTML5 i wybranie ich byłoby szybsze niż wybranie .data()właściwości? A także jakiś pomysł, gdzie mogę dowiedzieć się więcej o cache $ ._? Poszukałem go, ale niewiele znajduję.
Tauren
@Tauren - Moja wina, że ​​tak $.cachenie jest $._cache, tutaj możesz zobaczyć, jak jest zaimplementowany i używany w jądrze jQuery: github.com/jquery/jquery/blob/master/src/data.js#L4 Kiedy .data()go wywołujesz , przechowuje go jako obiekt in $.cache[elementUniqueID], który jest Id przypisany w razie potrzeby w sposób niekorzystny każdemu elementowi, np. 1, 2, 3 itd. Ten identyfikator wspinania zostanie ujawniony w jQuery 1.4.3. Myślę, że na podstawie komentarzy git z innego dnia. Zakładam, że trasa HTML 5 byłaby szybsza, zależy od dostępnych optymalizacji przeglądarki (jestem pewien, że więcej będzie dostępnych).
Nick Craver
7

Możesz ustawić data-*atrybut wiązu za pomocą attr(), a następnie wybrać za pomocą tego atrybutu:

var elm = $('a').attr('data-test',123); //assign 123 using attr()
elm = $("a[data-test=123]"); //select elm using attribute

a teraz tym wiąz, zarówno attr()i data()przyniesie 123 :

console.log(elm.attr('data-test')); //123
console.log(elm.data('test')); //123

Jeśli jednak zmienisz wartość na 456 za pomocą attr(), data() nadal będzie 123 :

elm.attr('data-test',456); //modify to 456
elm = $("a[data-test=456]"); //reselect elm using new 456 attribute

console.log(elm.attr('data-test')); //456
console.log(elm.data('test')); //123

Tak jak ja to rozumiem, wydaje się, że prawdopodobnie powinien omijać przenikanie attr()i data()poleceń w kodzie, jeśli nie muszę. Ponieważ attr()wydaje się, że odpowiada bezpośrednio DOM, podczas gdy data()współdziała z „pamięcią”, chociaż jego początkowa wartość może pochodzić z DOM. Ale kluczową kwestią jest to, że te dwie niekoniecznie są zsynchronizowane.

Więc bądź ostrożny.

W każdym razie, jeśli nie zmienisz data-*atrybutu w DOM lub w pamięci, nie będziesz miał problemu. Gdy tylko zaczniesz modyfikować wartości, mogą pojawić się potencjalne problemy.

Dzięki @Clarence Liu do odpowiedzi @ Ash, a także tego postu .

D.Tate
źródło
5

Jeśli używasz również jQueryUI, otrzymujesz (prostą) wersję :dataselektora, która sprawdza obecność elementu danych, dzięki czemu możesz zrobić coś takiego $("div:data(view)"), lub $( this ).closest(":data(view)").

Zobacz http://api.jqueryui.com/data-selector/ . Nie wiem, jak długo to trwały, ale już jest!

Paweł
źródło
To tylko sprawdza obecność elementu danych, jak powiedziałeś. Nie sprawdza wartości (zgodnie z dokumentacją). Miło to wiedzieć
Fractalf
3

Oto wtyczka, która upraszcza życie https://github.com/rootical/jQueryDataSelector

Użyj tego w ten sposób:

data selector           jQuery selector
  $$('name')              $('[data-name]')
  $$('name', 10)          $('[data-name=10]')
  $$('name', false)       $('[data-name=false]')
  $$('name', null)        $('[data-name]')
  $$('name', {})          Syntax error
Rootical V.
źródło