88 votes

Événement jQuery "on create" pour les éléments créés dynamiquement

Je dois être capable de créer dynamiquement <select> et le transformer en élément jQuery .combobox() . Il devrait s'agir d'un événement de création d'élément, par opposition à un événement de "clic", auquel cas je pourrais simplement utiliser jQuery. .on() .

Est-ce que quelque chose comme ça existe ?

$(document).on("create", "select", function() {
    $(this).combobox();
}

J'hésite à utiliser livequery, car il est très dépassé.

UPDATE La boîte de sélection/combinaison mentionnée est chargée via ajax dans une colorbox jQuery (fenêtre modale), d'où le problème : je ne peux lancer la combobox qu'en utilisant la colorbox. onComplete Cependant, lors du changement d'une combobox, une autre select/combobox doit être créée dynamiquement. J'ai donc besoin d'un moyen plus générique de détecter la création d'un élément ( select dans ce cas).

UPDATE2 Pour essayer d'expliquer davantage le problème, j'ai select/combobox créés de manière récursive, il y a également beaucoup de code d'initiation à l'intérieur .combobox() donc si j'utilisais une approche classique, comme en La réponse de @bipen mon code gonflerait à des niveaux insensés. J'espère que cela explique mieux le problème.

UPDATE3 Merci à tous, je comprends maintenant que depuis la dépréciation de la fonction DOMNodeInserted il y a un vide dans la mutation des DOM et il n'y a pas de solution à ce problème. Je vais devoir repenser mon application.

63voto

Codesleuth Points 5443

Vous pouvez on le site DOMNodeInserted pour obtenir un événement lorsqu'il est ajouté au document par votre code.

$('body').on('DOMNodeInserted', 'select', function () {
      //$(this).combobox();
});

$('<select>').appendTo('body');
$('<select>').appendTo('body');

J'ai bricolé ici : http://jsfiddle.net/Codesleuth/qLAB2/3/

EDIT : après avoir lu les articles, j'ai juste besoin de vérifier à nouveau. DOMNodeInserted ne posera pas de problèmes sur les différents navigateurs. Cette question de 2010 suggère qu'IE ne supporte pas l'événement, alors testez-le si vous le pouvez.

Voir ici : [lien] Attention ! Le type d'événement DOMNodeInserted est défini dans cette spécification à titre de référence et pour être complet, mais cette spécification déprécie l'utilisation de ce type d'événement.

26voto

Andrew Myers Points 2050

Comme mentionné dans plusieurs autres réponses, événements de mutation ont été dépréciés, vous devriez donc utiliser MutationObserver à la place. Puisque personne n'a encore donné de détails à ce sujet, voici...

API JavaScript de base

L'API de MutationObserver est assez simple. Elle n'est pas aussi simple que celle des événements de mutation, mais elle est tout de même acceptable.

function callback(records) {
  records.forEach(function (record) {
    var list = record.addedNodes;
    var i = list.length - 1;

    for ( ; i > -1; i-- ) {
      if (list[i].nodeName === 'SELECT') {
        // Insert code here...
        console.log(list[i]);
      }
    }
  });
}

var observer = new MutationObserver(callback);

var targetNode = document.body;

observer.observe(targetNode, { childList: true, subtree: true });

<script>
  // For testing
  setTimeout(function() {
    var $el = document.createElement('select');
    document.body.appendChild($el);
  }, 500);
</script>

Décomposons ça.

var observer = new MutationObserver(callback);

Cela crée l'observateur. L'observateur ne regarde encore rien ; c'est juste l'endroit où l'écouteur d'événement est attaché.

observer.observe(targetNode, { childList: true, subtree: true });

Cela fait démarrer l'observateur. Le premier argument est le nœud sur lequel l'observateur va surveiller les changements. Le deuxième argument est le options pour savoir ce qu'il faut surveiller .

  • childList signifie que je veux surveiller les éléments enfants qui sont ajoutés ou retirés.
  • subtree est un modificateur qui étend childList pour surveiller les changements survenant n'importe où dans la sous-arborescence de cet élément (autrement, il ne regarderait que les changements directement dans l'arborescence targetNode ).

Les deux autres options principales, outre childList sont attributes y characterData ce qui signifie à peu près ce à quoi ils ressemblent. Vous devez utiliser l'un de ces trois.

function callback(records) {
  records.forEach(function (record) {

Les choses se compliquent un peu dans le callback. Le callback reçoit un tableau de MutationRecord s. Chaque MutationRecord peut décrire plusieurs modifications d'un même type ( childList , attributes o characterData ). Puisque j'ai seulement dit à l'observateur de surveiller childList je ne prendrai pas la peine de vérifier le type.

var list = record.addedNodes;

Ici, je récupère une NodeList de tous les nœuds enfants qui ont été ajoutés. Cette liste sera vide pour tous les enregistrements où des noeuds n'ont pas été ajoutés (et il peut y avoir beaucoup de tels enregistrements).

A partir de là, je fais une boucle à travers les noeuds ajoutés et trouve ceux qui sont <select> éléments.

Rien de vraiment complexe ici.

jQuery

...mais vous avez demandé jQuery. Bien.

(function($) {

  var observers = [];

  $.event.special.domNodeInserted = {

    setup: function setup(data, namespaces) {
      var observer = new MutationObserver(checkObservers);

      observers.push([this, observer, []]);
    },

    teardown: function teardown(namespaces) {
      var obs = getObserverData(this);

      obs[1].disconnect();

      observers = $.grep(observers, function(item) {
        return item !== obs;
      });
    },

    remove: function remove(handleObj) {
      var obs = getObserverData(this);

      obs[2] = obs[2].filter(function(event) {
        return event[0] !== handleObj.selector && event[1] !== handleObj.handler;
      });
    },

    add: function add(handleObj) {
      var obs = getObserverData(this);

      var opts = $.extend({}, {
        childList: true,
        subtree: true
      }, handleObj.data);

      obs[1].observe(this, opts);

      obs[2].push([handleObj.selector, handleObj.handler]);
    }
  };

  function getObserverData(element) {
    var $el = $(element);

    return $.grep(observers, function(item) {
      return $el.is(item[0]);
    })[0];
  }

  function checkObservers(records, observer) {
    var obs = $.grep(observers, function(item) {
      return item[1] === observer;
    })[0];

    var triggers = obs[2];

    var changes = [];

    records.forEach(function(record) {
      if (record.type === 'attributes') {
        if (changes.indexOf(record.target) === -1) {
          changes.push(record.target);
        }

        return;
      }

      $(record.addedNodes).toArray().forEach(function(el) {
        if (changes.indexOf(el) === -1) {
          changes.push(el);
        }
      })
    });

    triggers.forEach(function checkTrigger(item) {
      changes.forEach(function(el) {
        var $el = $(el);

        if ($el.is(item[0])) {
          $el.trigger('domNodeInserted');
        }
      });
    });
  }

})(jQuery);

Cela crée un nouvel événement appelé domNodeInserted en utilisant le API pour les événements spéciaux de jQuery . Vous pouvez l'utiliser comme suit :

$(document).on("domNodeInserted", "select", function () {
  $(this).combobox();
});

Je suggérerais personnellement de chercher un cours, car certaines bibliothèques créent select éléments à des fins de test.

Naturellement, vous pouvez aussi utiliser .off("domNodeInserted", ...) ou affiner la surveillance en passant dans les données comme ceci :

$(document.body).on("domNodeInserted", "select.test", {
  attributes: true,
  subtree: false
}, function () {
  $(this).combobox();
});

Cela déclencherait la vérification de l'apparition d'un select.test chaque fois que les attributs des éléments situés directement dans le corps sont modifiés.

Vous pouvez le voir en direct ci-dessous ou sur jsFiddle .

(function($) {
  $(document).on("domNodeInserted", "select", function() {
    console.log(this);
    //$(this).combobox();
  });
})(jQuery);

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<script>
  // For testing
  setTimeout(function() {
    var $el = document.createElement('select');
    document.body.appendChild($el);
  }, 500);
</script>

<script>
  (function($) {

    var observers = [];

    $.event.special.domNodeInserted = {

      setup: function setup(data, namespaces) {
        var observer = new MutationObserver(checkObservers);

        observers.push([this, observer, []]);
      },

      teardown: function teardown(namespaces) {
        var obs = getObserverData(this);

        obs[1].disconnect();

        observers = $.grep(observers, function(item) {
          return item !== obs;
        });
      },

      remove: function remove(handleObj) {
        var obs = getObserverData(this);

        obs[2] = obs[2].filter(function(event) {
          return event[0] !== handleObj.selector && event[1] !== handleObj.handler;
        });
      },

      add: function add(handleObj) {
        var obs = getObserverData(this);

        var opts = $.extend({}, {
          childList: true,
          subtree: true
        }, handleObj.data);

        obs[1].observe(this, opts);

        obs[2].push([handleObj.selector, handleObj.handler]);
      }
    };

    function getObserverData(element) {
      var $el = $(element);

      return $.grep(observers, function(item) {
        return $el.is(item[0]);
      })[0];
    }

    function checkObservers(records, observer) {
      var obs = $.grep(observers, function(item) {
        return item[1] === observer;
      })[0];

      var triggers = obs[2];

      var changes = [];

      records.forEach(function(record) {
        if (record.type === 'attributes') {
          if (changes.indexOf(record.target) === -1) {
            changes.push(record.target);
          }

          return;
        }

        $(record.addedNodes).toArray().forEach(function(el) {
          if (changes.indexOf(el) === -1) {
            changes.push(el);
          }
        })
      });

      triggers.forEach(function checkTrigger(item) {
        changes.forEach(function(el) {
          var $el = $(el);

          if ($el.is(item[0])) {
            $el.trigger('domNodeInserted');
          }
        });
      });
    }

  })(jQuery);
</script>

Note

Ce code jQuery est une implémentation assez basique. Il ne se déclenche pas dans les cas où des modifications apportées ailleurs rendent votre sélecteur valide.

Par exemple, supposons que votre sélecteur soit .test select et le document a déjà un <select> . Ajout de la classe test a <body> rendra le sélecteur valide, mais parce que je vérifie seulement record.target y record.addedNodes l'événement ne se déclenche pas. Le changement doit se produire dans l'élément que vous souhaitez sélectionner lui-même.

Cela pourrait être évité en demandant le sélecteur à chaque fois que des mutations se produisent. J'ai choisi de ne pas le faire pour éviter de provoquer des événements en double pour des éléments qui ont déjà été traités. Traiter correctement les adjacent o combinateurs généraux de frères et sœurs rendrait les choses encore plus délicates.

Pour une solution plus complète, voir https://github.com/pie6k/jquery.initialize comme indiqué dans Damien Ó Ceallaigh 's réponse . Cependant, l'auteur de cette bibliothèque a annoncé que celle-ci était ancienne et suggère de ne pas utiliser jQuery pour cela.

22voto

Yukulélé Points 825

Vous pouvez utiliser DOMNodeInserted événement de mutation (délégation inutile) :

~~$('body').on('DOMNodeInserted', function(e) { var target = e.target; //inserted element; });~~

EDITAR: Événements de mutation sont déprécié utiliser observateur de mutation au lieu de

10voto

Dieter Gribnitz Points 523

Je viens de trouver cette solution qui semble résoudre tous mes problèmes d'ajax.

Pour les événements prêts à l'emploi, j'utilise maintenant ceci :

function loaded(selector, callback){
    //trigger after page load.
    $(function () {
        callback($(selector));
    });
    //trigger after page update eg ajax event or jquery insert.
    $(document).on('DOMNodeInserted', selector, function () {
        callback($(this));
    });
}

loaded('.foo', function(el){
    //some action
    el.css('background', 'black');
});

Et pour les événements déclencheurs normaux, j'utilise maintenant ceci :

$(document).on('click', '.foo', function () {
    //some action
    $(this).css('background', 'pink');
});

6voto

Il existe un plugin, adampietrasiak/jquery.initialize qui est basé sur MutationObserver qui permet d'y parvenir simplement.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X