seletor de dados jquery

184

Preciso selecionar elementos com base nos valores armazenados no .data()objeto de um elemento . No mínimo, gostaria de selecionar propriedades de dados de nível superior usando seletores, talvez assim:

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

Ou talvez o seletor esteja no formato regular de seletor de atributos:

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

Ou no formato de atributo, mas com um especificador para indicar que está em .data():

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

Eu achei a implementação de James Padolsey simples, mas boa. O seletor formata os métodos de espelhamento mostrados nessa página. Há também este patch Sizzle .

Por alguma razão, lembro de ler um tempo atrás que o jQuery 1.4 incluiria suporte para seletores de valores no .data()objeto jquery . No entanto, agora que estou procurando, não consigo encontrá-lo. Talvez tenha sido apenas uma solicitação de recurso que eu vi. Existe suporte para isso e eu simplesmente não estou vendo?

Idealmente, eu gostaria de oferecer suporte a subpropriedades em data () usando a notação de ponto. Como isso:

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

Também gostaria de oferecer suporte a vários seletores de dados, onde apenas elementos com TODOS os seletores de dados especificados são encontrados. O seletor múltiplo de jquery regular faz uma operação OR. Por exemplo, $('a.big, a.small')seleciona atags com classe bigou small). Estou procurando um AND, talvez assim:

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

Por fim, seria ótimo se operadores de comparação e recursos regex estivessem disponíveis nos seletores de dados. Então $(a[:artist.id>5000])seria possível. Sei que provavelmente poderia fazer muito disso usando filter(), mas seria bom ter um formato seletor simples.

Quais soluções estão disponíveis para fazer isso? O Padolsey de Jame é a melhor solução no momento? Minha preocupação é principalmente com relação ao desempenho, mas também aos recursos extras, como notação de ponto de subpropriedade e vários seletores de dados. Existem outras implementações que suportam essas coisas ou são melhores de alguma forma?

Tauren
fonte
Use o núcleo jQuery UI api.jqueryui.com/category/ui-core Tem um: data () selector
regisbsb

Respostas:

101

Eu criei um novo dataseletor que deve permitir que você faça consultas aninhadas e condições AND. Uso:

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

O padrão é:

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

"operator" e "check" são opcionais. Portanto, se você apenas o tiver :data(a.b.c), simplesmente verificará a veracidade de a.b.c.

Você pode ver os operadores disponíveis no código abaixo. Entre eles está o ~=que permite o teste de regex:

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

Eu testei com algumas variações e parece funcionar muito bem. Provavelmente adicionarei isso como um repositório do Github em breve (com um conjunto de testes completo), então fique de olho!

O código:

(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
fonte
@JP: Muito doce, eu sabia que podia contar com você! Vou para a cama agora, mas vou tentar amanhã. Também é possível executar operações OR? Eu não preciso disso, apenas curioso.
Tauren
1
@JP: Além disso, estou curioso sobre a sua opinião sobre as outras soluções de seleção de dados, prós / contras do seu versus do deles. Por exemplo, este plugin: plugins.jquery.com/project/dataSelector .
Tauren
1
É possível realizar operações OR, mas talvez seja melhor simplesmente ter dois seletores, $("a:data(condition),a:data(orCondition)")... isso terá o mesmo efeito. Quanto mais recursos você adicionar, mais lento será. Se a lógica for complexa, use $(foo).filter(function(){...}).
24410 James
3
@JP: Você já transformou isso em um projeto no github? Estou executando um teste usando o jQuery 1.5.1 usando o seletor $ ("input: data (test> 400)") em uma entrada com o atributo html5 data-test = "500", mas o seletor não está retornando nada.
James Sul
1
@JamesSouth Isso ocorre porque o seletor nesta resposta usa o nível baixo jQuery.data, que não obtém os dados definidos nos atributos HTML5. Se você deseja essa funcionalidade, pode mudar jQuery.datapara $('selector').data, mas isso é uma troca de velocidade.
Shef
175

No momento eu estou selecionando assim:

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

O que parece funcionar muito bem, mas seria bom se o jQuery pudesse selecionar por esse atributo sem o prefixo 'data-'.

Eu não testei isso com os dados adicionados aos elementos via jQuery dinamicamente, para que possa ser a queda desse método.

Cinza
fonte
Era exatamente isso que eu estava procurando. Doce
MikeMurko
109
Este na verdade não trabalho se você anexar / modificar dados via JS através da função .data (), o seletor de atributo apenas verifica o DOM, JS armazena .data () na memória
Clarence Liu
1
Você poderia dizer como você acessa por atributo se anexar via .data ()?
Maxim V. Pavlov
1
Eu sugeriria que as outras respostas fornecidas neste tópico sejam uma opção melhor para qualquer coisa que não seja um caso de uso muito simples.
Ash
A solução da Dmitri parece boa para mim, pois não depende de código de terceiros.
Ash
83

Você também pode usar uma função de filtragem simples sem nenhum plug-in. Não é exatamente isso que você deseja, mas o resultado é o mesmo:

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

$('a').filter(function() {
    return $(this).data('user') && $(this).data('user').name.first === "Tom";
});
Dmitri
fonte
2
Esta é uma ótima idéia, eu esqueci a filterfunção de passagem poderia aceitar uma função de teste =) graças
Clarence Liu
Como eu poderia fazer um "contém" em vez de ser igual a Tom?
precisa
1
Além disso, em vez de name.first == "Tom" use name.first && name.first.indexOF ("Tom")> -1;
Dmitri
24

Quero avisar que $('a[data-attribute=true]')não funciona, conforme a resposta de Ashley, se você anexou dados a um elemento DOM por meio da função data ().

Funciona como seria de esperar se você adicionasse um atributo de dados real ao seu HTML, mas o jQuery armazena os dados na memória, para que os resultados obtidos $('a[data-attribute=true]')não sejam corretos.

Você precisará usar o plug-in de dados http://code.google.com/p/jquerypluginsblog/ , usar a filtersolução de Dmitri ou fazer $ .each sobre todos os elementos e verificar .data () iterativamente

Clarence Liu
fonte
No meu caso, tudo bem, pois estou preenchendo previamente o campo no lado do servidor e só preciso consultá-lo dessa maneira no carregamento inicial. O filtro é provavelmente tão rápido (cada um é quase certamente mais lento), mas isso ganha com a legibilidade.
Don
11

Existe um :data()plugin de filtro que faz exatamente isso :)

Alguns exemplos com base em sua pergunta:

$('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

O desempenho não será muito bom comparado ao que é possível , selecionar $._cachee pegar os elementos correspondentes é de longe o mais rápido, mas muito mais abrangente e não muito "jQuery-ey" em termos de como você consegue coisas (você geralmente vem do lado do elemento). Do meu ponto de vista, não tenho certeza se isso é mais rápido, já que o processo de passar de um ID exclusivo para um elemento é complicado em si, em termos de desempenho.

O seletor de comparação que você mencionou será o melhor a fazer em um .filter(), não há suporte interno para isso no plug-in, embora você possa adicioná-lo sem muitos problemas.

Nick Craver
fonte
obrigado pela resposta completa. Você sabe se o uso de data-*atributos HTML5 e a seleção deles seriam mais rápidos do que a seleção de .data()propriedades? Além disso, alguma idéia de onde posso descobrir mais sobre o cache $ ._? Eu pesquisei por isso, mas não estou encontrando muito.
Tauren 23/05
@Tauren - Minha culpa é $.cachenão $._cache, você pode ver como ele é implementado e usado no núcleo jQuery aqui: github.com/jquery/jquery/blob/master/src/data.js#L4 Quando você chama .data()ele é realmente armazená-lo como um objeto in $.cache[elementUniqueID], que é um ID atribuído conforme necessário de maneira incremental para cada elemento, por exemplo, 1, 2, 3 etc. Esse ID de escalada será exposto no jQuery 1.4.3. Creio que, com base nos comentários do git no outro dia. Eu diria que a rota do HTML 5 seria mais rápida, depende das otimizações do navegador disponíveis (tenho certeza de que mais estarão disponíveis).
Nick Craver
7

Você pode definir um data-*atributo em um olmo usando attr()e selecione usando esse atributo:

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

e agora para esse olmo, ambos attr()e data()renderá 123 :

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

No entanto, se você modificar o valor a ser 456 usando attr(), data() ainda será 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

Pelo que entendi, parece que você provavelmente deve evitar misturas attr()e data()comandos no seu código, se não for necessário. Porque attr()parece corresponder diretamente ao DOM enquanto data()interage com a 'memória', embora seu valor inicial possa ser do DOM. Mas o ponto principal é que os dois não estão necessariamente sincronizados.

Portanto, tenha cuidado.

De qualquer forma, se você não estiver alterando o data-*atributo no DOM ou na memória, não terá problemas. Assim que você começa a modificar os valores, é possível que surjam problemas em potencial.

Obrigado a @Clarence Liu à resposta de @ Ash, bem como a este post .

D.Tate
fonte
5

Se você também usa o jQueryUI, obtém uma versão (simples) do :dataseletor que verifica a presença de um item de dados, para que você possa fazer algo como $("div:data(view)"), ou $( this ).closest(":data(view)").

Consulte http://api.jqueryui.com/data-selector/ . Não sei há quanto tempo eles têm, mas está lá agora!

Paulo
fonte
Isso só verifica a presença do item de dados como você disse. Não verifica os valores (de acordo com os documentos). Mas é bom saber sobre isso
Fractalf
3

Aqui está um plugin que simplifica a vida https://github.com/rootical/jQueryDataSelector

Use-o assim:

data selector           jQuery selector
  $$('name')              $('[data-name]')
  $$('name', 10)          $('[data-name=10]')
  $$('name', false)       $('[data-name=false]')
  $$('name', null)        $('[data-name]')
  $$('name', {})          Syntax error
Raiz V.
fonte