web-dev-qa-db-fra.com

Evénement lorsqu'un élément est ajouté à la page

Ceci a déjà été discuté ici: Comment faire une action quand un élément est ajouté à une page en utilisant Jquery?

Le répondant a suggéré de déclencher un événement personnalisé chaque fois qu'une div était ajoutée à la page. Cependant, j'écris une extension Chrome et je n'ai pas accès à la source de la page. Quelles sont mes options ici? Je suppose qu'en théorie, je pourrais simplement utiliser setTimeout pour rechercher continuellement la présence de l'élément et ajouter mon action si l'élément est là.

115
Kevin Burke

Puisque les événements de mutation DOM sont maintenant obsolète (voir la note en bas) avec les dernières spécifications et que vous n'avez aucun contrôle sur les éléments insérés car ils sont ajoutés par le code de quelqu'un d'autre, votre seule option est de vérifier en permanence pour eux.

function checkDOMChange()
{
    // check for any new element being inserted here,
    // or a particular node being modified

    // call the function again after 100 milliseconds
    setTimeout( checkDOMChange, 100 );
}

Une fois cette fonction appelée, elle s'exécutera toutes les xxx millisecondes. J'ai choisi 100, ce qui représente 1/10 (un dixième) de seconde. À moins que vous n'ayez besoin d'éléments en temps réel, cela devrait suffire.

Modifier

Comme l'a commenté Ben Davis, la spécification DOM niveau 4 introduit les observateurs de mutation (également sur Mozilla docs ), qui remplacent les événements de mutation déconseillés.

79
Jose Faeti

La réponse réelle est "utiliser les observateurs de mutation" (comme indiqué dans cette question: Déterminer si un élément HTML a été ajouté dynamiquement au DOM ), mais la prise en charge (en particulier sur IE) est limitée ( http: // caniuse.com/mutationobserver ).

La réponse réelle est donc "Utilisez des observateurs de mutations ... éventuellement. Mais allez avec la réponse de Jose Faeti pour le moment" :)

26
Asfand Qazi

Vous pouvez également exploiter les événements CSS3 Animation pour être averti lorsqu'un élément spécifique est ajouté au DOM: http://www.backalleycoder.com/2012/04/25/i-want-a-damnodeinserted/

15
jokeyrhyme

Vous pouvez utiliser livequery plugin for jQuery. Vous pouvez fournir une expression de sélecteur telle que:

$("input[type=button].removeItemButton").livequery(function () {
    $("#statusBar").text('You may now remove items.');
});

Chaque fois qu'un bouton d'une classe removeItemButton est ajouté, un message apparaît dans une barre d'état.

En termes d’efficacité, vous voudrez peut-être éviter cela, mais dans tous les cas, vous pourrez utiliser le plug-in au lieu de créer vos propres gestionnaires d’événements.

Réponse revisitée

La réponse ci-dessus était uniquement destinée à détecter qu'un élément avait été ajouté au DOM via le plug-in.

Cependant, une approche jQuery.on() serait probablement plus appropriée, par exemple:

$("#myParentContainer").on('click', '.removeItemButton', function(){
          alert($(this).text() + ' has been removed');
});

Si, par exemple, le contenu dynamique doit répondre aux clics, il est préférable de lier des événements à un conteneur parent à l'aide de jQuery.on.

13
Zero Distraction

ETA 24 avr 17 Je voulais simplifier un peu cela avec une certaine magie asyncawait, car elle est beaucoup plus succincte:

En utilisant le même observable promisifié: 

const startObservable = (domNode) => {
  var targetNode = domNode;

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

  return new Promise((resolve) => {
      var observer = new MutationObserver(function (mutations) {
         // For the sake of...observation...let's output the mutation to console to see how this all works
         mutations.forEach(function (mutation) {
             console.log(mutation.type);
         });
         resolve(mutations)
     });
     observer.observe(targetNode, observerConfig);
   })
} 

Votre fonction d'appel peut être aussi simple que:

const waitForMutation = async () => {
    const button = document.querySelector('.some-button')
    if (button !== null) button.click()
    try {
      const results = await startObservable(someDomNode)
      return results
    } catch (err) { 
      console.error(err)
    }
}

Si vous souhaitez ajouter un délai d'expiration, vous pouvez utiliser un simple motif Promise.racecomme illustré ici :

const waitForMutation = async (timeout = 5000 /*in ms*/) => {
    const button = document.querySelector('.some-button')
    if (button !== null) button.click()
    try {

      const results = await Promise.race([
          startObservable(someDomNode),
          // this will throw after the timeout, skipping 
          // the return & going to the catch block
          new Promise((resolve, reject) => setTimeout(
             reject, 
             timeout, 
             new Error('timed out waiting for mutation')
          )
       ])
      return results
    } catch (err) { 
      console.error(err)
    }
}

_/Original

Vous pouvez le faire sans bibliothèques, mais vous devrez utiliser certaines choses ES6, alors tenez compte des problèmes de compatibilité (c'est-à-dire si votre public cible est principalement Amish, Luddite ou, pire, utilisateurs d'IE8).

Nous allons d’abord utiliser l’API MutationObserver API pour construire un objet observateur. Nous allons envelopper cet objet dans une promesse et resolve() lorsque le rappel est déclenché (h/t davidwalshblog) article de blog de david walsh sur les mutations :

const startObservable = (domNode) => {
    var targetNode = domNode;

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

    return new Promise((resolve) => {
        var observer = new MutationObserver(function (mutations) {
            // For the sake of...observation...let's output the mutation to console to see how this all works
            mutations.forEach(function (mutation) {
                console.log(mutation.type);
            });
            resolve(mutations)
        });
        observer.observe(targetNode, observerConfig);
    })
} 

Ensuite, nous allons créer un generator function. Si vous ne les avez pas encore utilisées, il vous manque un peu - mais voici un bref résumé: il fonctionne comme une fonction de synchronisation et lorsqu'il trouve une expression yield <Promise>, il attend de manière non bloquante être remplies (Les générateurs font plus que cela, mais c'est ce qui nous intéresse ici).

// we'll declare our DOM node here, too
let targ = document.querySelector('#domNodeToWatch')

function* getMutation() {
    console.log("Starting")
    var mutations = yield startObservable(targ)
    console.log("done")
}

Une partie délicate des générateurs est qu’ils ne «retournent» pas comme une fonction normale. Nous allons donc utiliser une fonction d'assistance pour pouvoir utiliser le générateur comme une fonction normale. (encore, h/t à dwb )

function runGenerator(g) {
    var it = g(), ret;

    // asynchronously iterate over generator
    (function iterate(val){
        ret = it.next( val );

        if (!ret.done) {
            // poor man's "is it a promise?" test
            if ("then" in ret.value) {
                // wait on the promise
                ret.value.then( iterate );
            }
            // immediate value: just send right back in
            else {
                // avoid synchronous recursion
                setTimeout( function(){
                    iterate( ret.value );
                }, 0 );
            }
        }
    })();
}

Ensuite, à tout moment avant que la mutation DOM attendue puisse se produire, exécutez simplement runGenerator(getMutation).

Vous pouvez maintenant intégrer des mutations DOM dans un flux de contrôle de style synchrone. Comment ça?.

9
Brandon

Il existe une bibliothèque javascript prometteuse appelée Arrive qui semble être un excellent moyen de commencer à tirer parti des observateurs de mutations une fois que la prise en charge du navigateur est devenue monnaie courante.

https://github.com/uzairfarooq/arrive/

8
Dave Kiss

Découvrez ce plugin qui fait exactement cela - jquery.initialize

Cela fonctionne parfaitement comme une fonction .each, la différence est qu’il faut un sélecteur que vous avez entré et surveillez les nouveaux éléments ajoutés dans la future correspondance de ce sélecteur et les initialiser

Initialiser ressemble à ceci

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

Mais maintenant, si un nouveau sélecteur d’élément correspondant à .some-element apparaîtra sur la page, il sera instantanément initialisé. 

La manière dont le nouvel élément est ajouté n’est pas importante, vous n’avez pas besoin de vous soucier des rappels, etc.

Donc, si vous voulez ajouter un nouvel élément comme:

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

il sera instantanément initialisé.

Le plugin est basé sur MutationObserver

3
pie6k

Une solution javascript pure (sans jQuery):

const SEARCH_DELAY = 100; // in ms

// it may run indefinitely. TODO: make it cancellable, using Promise's `reject`
function waitForElementToBeAdded(cssSelector) {
  return new Promise((resolve) => {
    const interval = setInterval(() => {
      if (element = document.querySelector(cssSelector)) {
        clearInterval(interval);
        resolve(element);
      }
    }, SEARCH_DELAY);
  });
}

console.log(await waitForElementToBeAdded('#main'));
0
vedant