jquery detectando div de determinada classe foi adicionado ao DOM

92

Estou usando .on()para vincular eventos de divs que são criados após o carregamento da página. Funciona bem para clicar, entrar com o mouse ... mas preciso saber quando um novo div da classe MyClass foi adicionado. Estou procurando por isso:

$('#MyContainer').on({

  wascreated: function () { DoSomething($(this)); }

}, '.MyClass');

Como eu faço isso? Consegui escrever meu aplicativo inteiro sem um plugin e quero mantê-lo assim.

Obrigado.

francês
fonte
É o seu próprio código que está gerando os elementos? Nesse caso, você pode verificar no momento de criá-lo e agir de acordo.
Surreal Dreams
Sim, isso é verdade, mas com .on (), posso vincular eventos em document.ready e então não preciso me preocupar com a vinculação ao gerar o HTML. Estou procurando o mesmo comportamento com DoSomething.
francês
você pode usar $ ("div / your-selector"). hasClass ("MyClass"); para verificar se um div tem a classe específica ou não. Eu acho que você poderia colocar esta API para seu uso.
KBN
.on () é apenas para eventos. Você poderia configurar um evento personalizado, mas ainda teria que acionar esse evento ao criar novos elementos.
Surreal Dreams
3
Duplicado de evento quando elemento adicionado à página
Dan Dascalescu

Respostas:

105

Anteriormente, era possível se conectar ao domManipmétodo jQuery para capturar todas as manipulações dom do jQuery e ver quais elementos foram inseridos etc., mas a equipe da jQuery desligou isso no jQuery 3.0+, pois geralmente não é uma boa solução para se conectar a métodos jQuery dessa maneira, e fizemos isso de forma que o domManipmétodo interno não esteja mais disponível fora do código jQuery principal.

Eventos de mutação também foram descontinuados, como antes que se pudesse fazer algo como

$(document).on('DOMNodeInserted', function(e) {
    if ( $(e.target).hasClass('MyClass') ) {
       //element with .MyClass was inserted.
    }
});

isso deve ser evitado e, hoje, devem ser usados ​​Observadores de mutação, que funcionariam assim

var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        console.log(mutation)
        if (mutation.addedNodes && mutation.addedNodes.length > 0) {
            // element added to DOM
            var hasClass = [].some.call(mutation.addedNodes, function(el) {
                return el.classList.contains('MyClass')
            });
            if (hasClass) {
                // element has class `MyClass`
                console.log('element ".MyClass" added');
            }
        }
    });
});

var config = {
    attributes: true,
    childList: true,
    characterData: true
};

observer.observe(document.body, config);
adeneo
fonte
O Firefox e o webkit devem estar bem, mas o suporte para isso é um tanto pobre no IE. Deve funcionar no IE9, mas provavelmente não tanto no IE8. Eu sei que já vi soluções alternativas e realmente não testei isso no IE, então teste primeiro, se não estiver funcionando, então veja se há uma solução alternativa. Existe um plugin que parece adicionar suporte ao IE aqui , mas ainda não tentei, apenas encontrei em uma pesquisa no Google.
adeneo
2
Usei inicialmente sua resposta, mas o fato de que não estava funcionando em todos os navegadores foi um problema. Então, eu rapidamente descobri uma solução para evitar esse problema em primeiro lugar. E então, algumas semanas atrás, esse problema de detecção de inserção de nó voltou novamente em meu código. Desta vez, o código que postei na minha resposta funciona EXATAMENTE como deveria, em vários navegadores e sem erros. Então é por isso que mudei minha resposta como aceita; é assim que o SO funciona, o OP decide qual resposta ele aceita e a multidão vota nas respostas. Com o tempo, as pessoas votarão e decidirão a resposta que melhor se adequa a elas.
francês
2
E também gostaria de dizer que você me ajudou várias vezes com respostas de alta qualidade que salvaram meu dia. Alterar a resposta aceita para a minha é puramente baseado no código e em como ele se encaixa no problema que eu tive que resolver.
Frenchie
obteve "Uncaught TypeError: Não é possível ler a propriedade 'contém' de undefined" com este código
gordie
53

Aqui está meu plug-in que faz exatamente isso - jquery.initialize

O uso é o mesmo que você usaria a .eachfunção, mas com .initializefunção no elemento, a diferença .eaché que também inicializará elementos adicionados no futuro sem qualquer código adicional - não importa se você o adiciona com AJAX ou qualquer outra coisa.

Initialize tem exatamente a mesma sintaxe da função .each

$(".some-element").initialize( function(){
    $(this).css("color", "blue");
});

Mas agora, se um novo elemento correspondente ao seletor de algum elemento aparecer na página, ele será inicializado instantaneamente. A forma como o novo item é adicionado não é importante, você não precisa se preocupar com retornos de chamada, etc.

$("<div/>").addClass('some-element').appendTo("body"); //new element will have blue color!

Plugin é baseado em MutationObserver

Adam Pietrasiak
fonte
Muito obrigado.
Brandon Harris
como parar a inicialização? Quero parar de assistir quando as coisas que eu procurava forem adicionadas ...
Evgeny Vostok
$ (". some-element"). initialize [..] está obsoleto. Use $ .initialize (". AddDialog", function () {$ ('. NoData'). Hide ();}); em vez de.
Heimi,
É possível executar a .initializefunção callback apenas para elementos que são adicionados dinamicamente (por exemplo, via Ajax) e pular o retorno de chamada para elementos já presentes quando a página carrega inicialmente? Estou tentando adicionar um parâmetro a optionspara fazer isso, mas até agora não consegui fazer isso funcionar.
Nevermore
Você pode adicionar um problema no github sobre isso. Por enquanto, tecnicamente você poderia fazer$('.myClass').addClass('ignore-initialize'); $.initialize('.myClass:not(.ignore-initialize)', callback);
Adam Pietrasiak
19

Depois de revisar este e vários outros posts, tentei destilar o que achei o melhor de cada um em algo simples que me permitiu detectar quando uma classe de elementos é inserida e então agir sobre esses elementos .

function onElementInserted(containerSelector, elementSelector, callback) {

    var onMutationsObserved = function(mutations) {
        mutations.forEach(function(mutation) {
            if (mutation.addedNodes.length) {
                var elements = $(mutation.addedNodes).find(elementSelector);
                for (var i = 0, len = elements.length; i < len; i++) {
                    callback(elements[i]);
                }
            }
        });
    };

    var target = $(containerSelector)[0];
    var config = { childList: true, subtree: true };
    var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
    var observer = new MutationObserver(onMutationsObserved);    
    observer.observe(target, config);

}

onElementInserted('body', '.myTargetElement', function(element) {
    console.log(element);
});

É importante para mim que isso permite que a) o elemento de destino exista em qualquer profundidade dentro de "addedNodes" eb) a capacidade de lidar com o elemento apenas quando inicialmente inserido (sem necessidade de pesquisar a configuração do documento inteiro ou ignorar "já processado" bandeiras).

John Clegg
fonte
1
Sua condição deve ser if(mutation.addedNodes.length)porque addedNodese os removedNodesarrays estão sempre lá em mutationdo tipo childList(verificado no IE11, Chrome53, FF49). Ainda +1, pois a sua é a única resposta onde addedNodesé considerada ( callbacké chamada somente quando um nó é adicionado); não apenas neste tópico, mas em muitos outros tópicos. Todos os outros estão apenas invocando o callbackfor all mutations, mesmo se o nó estiver sendo excluído.
Vivek Athalye
17

3 anos de experiência depois, é assim que escuto "elemento de uma determinada classe adicionado ao DOM" : você simplesmente adiciona um gancho na html()função jQuery , como este:

function Start() {

   var OldHtml = window.jQuery.fn.html;

   window.jQuery.fn.html = function () {

     var EnhancedHtml = OldHtml.apply(this, arguments);

     if (arguments.length && EnhancedHtml.find('.MyClass').length) {

         var TheElementAdded = EnhancedHtml.find('.MyClass'); //there it is
     }

     return EnhancedHtml;
   }
}

$(Start);

Isso funciona se você estiver usando jQuery, o que eu faço. E não depende do evento específico do navegadorDOMNodeInserted , que não é compatível com navegadores diferentes. Eu também adicionei a mesma implementação para.prepend()

No geral, isso funciona como um encanto para mim e, espero, para você também.

francês
fonte
6
Hmm - eu esclareceria que isso funciona se você estiver usando exclusivamente o htmlhelper do jQuery para modificar o DOM, não apenas "usando jQuery" em geral. Os elementos adicionados usando, digamos, jQuery.appendnão acionarão este evento.
iameli
1
Sim, e é por isso que esclareci que adicionei a mesma implementação com .preprend () e se você usar .append (), faça a mesma coisa com .append ()
frenchie
Existe uma abordagem muito mais rápida, usando animações CSS3 . A biblioteca de código aberto também empresta código muito mais simples:insertionQ('#intercom-container').every(function(/element){ element.style.display = 'none'; });
Dan Dascalescu
3
Por que o downvote ?? Isso realmente funciona: nenhum plugin e nenhuma função setTimeout hacky.
francês
2
Votos positivos - esta é uma opção válida - por que diabos (!!!) foi rejeitado ?????? Dan, CSS3 Animations não são XBC: caniuse.com/#search=keyframes ; davidwalsh.name/detect-node-insertion .
Cody,
6

você poderia usar eventos de mutação

http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-eventgroupings-mutationevents

EDITAR

do MDN: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Mutation_events

Obsoleto Este recurso foi removido da web. Embora alguns navegadores ainda possam suportá-lo, ele está em processo de exclusão. Não o use em projetos antigos ou novos. Páginas ou aplicativos da Web que o usam podem quebrar a qualquer momento.

Observadores de mutação são a substituição proposta para eventos de mutação em DOM4. Eles devem ser incluídos no Firefox 14 e Chrome 18.

https://developer.mozilla.org/en/docs/Web/API/MutationObserver

MutationObserverfornece aos desenvolvedores uma maneira de reagir às mudanças em um DOM. Ele é projetado como uma substituição para eventos de mutação definidos na especificação de eventos DOM3.

Exemplo de uso

O exemplo a seguir foi retirado de http://hacks.mozilla.org/2012/05/dom-mutationobserver-reacting-to-dom-changes-without-killing-browser-performance/ .

// select the target node
var target = document.querySelector('#some-id');

// create an observer instance
var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    console.log(mutation.type);
  });    
});

// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true };

// pass in the target node, as well as the observer options
observer.observe(target, config);

// later, you can stop observing
observer.disconnect();
RGB
fonte
2
Os eventos de mutação estão praticamente obsoletos de acordo com a mdn , em favor de MutationObservers
Olga
Agora que o IE10 é EOL, deve ser quase seguro dizer que MutationObserver é amplamente suportado.
rymo
0

Duas adições à resposta de adeneo:

(1) podemos mudar a linha

return el.classList.contains('MyClass');

Para

if( el.classList ) {
    return el.classList.contains('MyClass');
} else {
    return false;
}

Portanto, não veremos o "Uncaught TypeError: Cannot read property 'contains' of undefined"erro.

(2) add subtree: trueno configpara encontrar nós adicionados de forma recursiva em todos os elementos adicionados.

Agrul
fonte