Pergunta

Existem muitas perguntas sobre vincular manipulações futuras a elementos inexistentes que acabam sendo respondidas com ao vivo / delegado . Estou me perguntando como executar um retorno de chamada arbitrário (para adicionar uma classe ou acionar um plugin, por exemplo) para todos os elementos existentes que correspondem a um seletor e todos os elementos futuros que correspondem ao mesmo seletor que ainda não foram criados.

Parece que a principal funcionalidade do plug-in livequery chegou ao núcleo, mas a outra parte, anexar callbacks arbitrários se perdeu ao longo do caminho.

Outra resposta comum é delegação de evento , mas e se alguém não tiver acesso a todo o código do fornecedor que está criando os elementos para tê-lo acionar os eventos?


Aqui está um código do mundo real:

// with livequery
$('input[type=text], input[type=password], textarea, .basic_form .block select, .order_form .form_item select, .order_form .form_item input')
    .livequery(function(){
        $(this)
            .focus(function(){
                $(this).addClass('active');
            })
            .blur(function(){
                $(this).removeClass('active');
            })
            .addClass('text');
    });

// with live
$('input[type=text], input[type=password], textarea, .basic_form .block select, .order_form .form_item select, .order_form .form_item input')
    .live('focus', function(){
            $(this).addClass('active');
        })
    .live('blur', function(){
            $(this).removeClass('active');
        });
    // now how to add the class to future elements?
    // (or apply another plugin or whatever arbitrary non-event thing)

Uma abordagem seria monitorar quando novos nós são adicionados / removidos e acionar novamente nossos seletores. Graças a @arnorhs , sabemos sobre o evento DOMNodeInserted , que eu ignoraria os problemas entre navegadores na esperança de que esses pequenos patches do IE pudessem algum dia chegar ao jQuery ou saber as funções jQuery DOM podem ser agrupadas.

Mesmo se pudéssemos garantir que o DOMNodeInserted fosse disparado em vários navegadores, seria ridículo vinculá-lo a vários seletores. Centenas de elementos podem ser criados a qualquer momento, e potencialmente dezenas de chamadas de seletor em cada um desses elementos podem ser arrastadas.

Minha melhor ideia até agora

Seria melhor monitorar DOMNodeInserted / Deleted e / ou conectar-se às rotinas de manipulação de DOM do jQuery para definir apenas um sinalizador de que uma "reinicialização" deve acontecer? Então, pode haver apenas um temporizador que verifica essa sinalização a cada x segundos, executando apenas todos os seletores / retornos de chamada quando o DOM realmente mudou.

Isso ainda poderia ser muito ruim se você estivesse adicionando / removendo elementos em grande número em um ritmo rápido (como com animação ou ____). Ter que analisar novamente o DOM uma vez para cada seletor salvo a cada x segundos pode ser muito intenso se x for baixo, e a interface parecerá lenta se x for alto.

Alguma outra solução nova?

Eu adicionarei uma recompensa quando permitir. Eu adicionei uma recompensa pela solução mais inovadora!

Basicamente, estou chegando a uma abordagem orientada a aspectos para manipular o DOM. Um que pode permitir que novos elementos serão criados no futuro , e eles devem ser criados com o documento inicial. Modificações já aplicadas a eles também .

JS tem feito tanta mágica ultimamente que espero que seja óbvio.

Foi útil?

Solução

Na minha opinião, os eventos DOM Nível 3 DOMNodeInserted help (que dispara apenas para nós) e DOMSubtreeModified help (que dispara para praticamente qualquer modificação, como alterações de atributos) são sua melhor chance para realizar essa tarefa.

Claro, a grande desvantagem desses eventos é que os Internet Explorer deste mundo não os apoiam
(... bem, o IE9 sim).

A outra solução razoável para este problema é conectar-se a qualquer método que possa modificar o DOM. Mas então temos que perguntar: qual é o nosso escopo aqui?

É apenas o suficiente para lidar com métodos de modificação DOM de uma biblioteca específica como jQuery? E se, por algum motivo, outra biblioteca estiver modificando o DOM ou até mesmo um método nativo?

Se for apenas para jQuery, não precisamos de .sub(). Poderíamos escrever ganchos na forma de:

<"HTML

<div id="test">Test DIV</div>

<✓JS

(function(_append, _appendTo, _after, _insertAfter, _before, _insertBefore) {
    $.fn.append = function() {
        this.trigger({
            type: 'DOMChanged',
            newnode: arguments[0]
        });
        return _append.apply(this, arguments);
    };
    $.fn.appendTo = function() {
        this.trigger({
            type: 'DOMChanged',
            newnode: this
        });
        return _appendTo.apply(this, arguments);
    };
    $.fn.after = function() {
        this.trigger({
             type: 'DOMChanged',
             newnode: arguments[0]
         });
        return _after.apply(this, arguments);
    };

    // and so forth

}($.fn.append, $.fn.appendTo, $.fn.after, $.fn.insertAfter, $.fn.before, $.fn.insertBefore));

$('#test').bind('DOMChanged', function(e) {
    console.log('New node: ', e.newnode);
});

$('#test').after('<span>new span</span>');
$('#test').append('<p>new paragraph</p>');
$('<div>new div</div>').appendTo($('#test'));


Um exemplo ao vivo do código acima pode ser encontrado aqui: http://www.jsfiddle.net / RRfTZ / 1 /

É claro que isso requer uma lista completa de métodos DOMmanip. Não tenho certeza se você pode substituir métodos nativos como .appendChild() com essa abordagem. .appendChild está localizado em Element.prototype.appendChild, por exemplo, pode valer a pena tentar.

<"atualizar

Testei sobrescrever Element.prototype.appendChild etc. no Chrome, Safari e Firefox (versão oficial mais recente). Funciona no Chrome e Safari, mas não no Firefox!


Pode haver outras maneiras de lidar com o requisito. Mas não consigo pensar em uma única abordagem que seja realmente satisfatória, como contar / observar todos os descendentes de um nó (que precisaria de um interval ou timeouts, eeek).

Uma mistura de eventos DOM Nível 3 em que métodos DOMmanip suportados e enganchados é provavelmente o melhor que você pode fazer aqui.

Outras dicas

Eu estava lendo sobre o novo lançamento do jQuery, versão 1.5 e imediatamente pensei nesta questão.

Com o jQuery 1.5, você pode criar sua própria versão do jQuery usando algo chamado jQuery.sub ();

Dessa forma, você pode realmente sobrescrever as funções padrão .append (), insert (), .html (), .. em jQuery e criar seu próprio evento personalizado chamado algo como "mydomchange" - sem afetar todos os outros scripts.

Portanto, você pode fazer algo assim (copiado da documentação .sub () com mod. menor):

var sub$ = jQuery.sub();
sub$.fn.insert = function() {
    // New functionality: Trigger a domchange event
    this.trigger("domchange");
    // Be sure to call the original jQuery remove method
    return jQuery.fn.insert.apply( this, arguments );
};

Você teria que fazer isso com todos os métodos de manipulação de dom ...

jQuery.sub () na documentação jQuery: http://api.jquery.com/jQuery.sub/

Ótima pergunta

Parece haver um evento personalizado que você pode vincular: http://javascript.gakaa.com/domnodeinserted-description.aspx

Então, acho que você poderia fazer algo como:

$(document).bind('DOMNodeInserted',function(){ /* do stuff */ });

Mas não tentei, então não tenho a menor ideia.

a propósito: questão relacionada: O javascript pode ouvir "onDomChange" em todos os elementos Dom?

Não existe uma maneira simples e óbvia de fazer isso. A única abordagem infalível é a votação ativa, o que faz com que haja um soluço de renderização entre o momento em que o novo elemento é criado e quando a votação percebe isso. Isso também pode fazer com que sua página consuma muitos recursos, dependendo da frequência com que você consulta a página. Você também pode acoplar isso, como observou, com a vinculação de vários eventos específicos do navegador para, pelo menos, fazer as coisas funcionarem melhor nesses navegadores.

Você pode substituir as funções de modificação de DOM do jQuery para acionar um evento de mudança personalizado (e usar $ .live para capturar esses eventos para manipulação), mas quando tentei isso no passado, vários plug-ins jQuery foram sutilmente quebrados (meu palpite é que alguns desses plug-ins fazem algo semelhante). No final, desisti de fazer isso de forma confiável, já que não quero desistir do desempenho e renderizar soluços para a votação ativa e não há outra maneira abrangente de fazer isso. Em vez disso, tenho um evento de inicialização que me certifico de acionar cada alteração do DOM que faço e vinculo meus eventos de manipulação a eles.

Tenha cuidado, é fácil ficar preso em um loop infinito de eventos se você não pensar nas coisas, e isso também pode ser sutil e difícil de rastrear; e pior ainda pode acontecer para um caso que seu teste de unidade não permitiu (para que seus usuários experimentem em vez de apenas você). O evento de inicialização acionado manualmente personalizado é mais fácil de diagnosticar neste sentido, pois você sempre sabe exatamente quando está acionando.

Bem, em primeiro lugar, você nem deveria usar seletores como este se estiver preocupado com o desempenho.

$('input[type=text], input[type=password], textarea, .basic_form .block select, .order_form .form_item select, .order_form .form_item input')

Qualquer navegador que não tenha implementações nativas de xpath (mais do que apenas IE iirc) ou getElementsByClassName (IE 7 e inferior) poderia facilmente passar alguns segundos mastigando isso em um grande site, de modo que a pesquisa, com certeza, ficaria completamente forada pergunta, se você quiser tão amplo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top