web-dev-qa-db-fra.com

Evénement "à la création" jQuery pour les éléments créés dynamiquement

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

Alors, quelque chose comme ça existe-t-il?

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

Je suis réticent à utiliser Livequery, car il est très obsolète.

[~ # ~] update [~ # ~] La sélection/liste déroulante mentionnée est chargée via ajax dans une colorbox jQuery (fenêtre modale), d'où le problème - Je ne peux initier qu'une combobox à l'aide de colorbox onComplete, mais lors du changement d'une combobox, un autre sélecteur/sélecteur doit être créé de manière dynamique. Par conséquent, j'ai besoin d'un moyen plus générique de détecter la création d'un élément (selectin ce cas).

UPDATE2 Pour essayer d'expliquer davantage le problème - J'ai des éléments select/combobox Créés de manière récursive, il y a aussi beaucoup de code d'initiation dans .combobox(), donc si j'utilisais une approche classique, comme dans réponse de @ bipen , mon code se 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 DOMNodeInserted, il reste un vide dans la mutation DOM et qu'il n'y a pas de solution à cela problème. Je vais juste devoir repenser mon application.

70
Caballero

Vous pouvez on l'événement DOMNodeInserted pour obtenir un événement pour son ajout au document par votre code.

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

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

Fiddled here: http://jsfiddle.net/Codesleuth/qLAB2/3/

EDIT: après avoir lu, il me suffit de vérifier que DOMNodeInserted ne posera pas de problèmes de navigation. Cette question à partir de 2010 suggère IE ne supporte pas l'événement, alors testez-le si vous le pouvez.

Voir ici: [link] Attention! le type d'événement DOMNodeInserted est défini dans cette spécification pour référence et complétude, mais cette spécification déconseille l'utilisation de ce type d'événement.

52
Codesleuth

Vous pouvez utiliser l'événement de mutation DOMNodeInserted (pas de délégation nécessaire):

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

EDIT: Les événements de mutation sont déconseillés , utilisez observateur de mutation à la place

19
Yukulélé

Comme mentionné dans plusieurs autres réponses, événements de mutation sont obsolètes. Vous devez donc utiliser MutationObserver . Puisque personne n’a encore donné de détails à ce sujet, la voici ...

API JavaScript basique

L'API pour MutationObserver est assez simple. Ce n'est pas aussi simple que les événements de mutation, mais ça reste correct.

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 cela.

var observer = new MutationObserver(callback);

Cela crée l'observateur. L'observateur ne regarde encore rien; c'est là que l'auditeur 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 surveillera les modifications. Le deuxième argument est le options pour ce qu'il faut surveiller . Le childList signifie que je veux surveiller les éléments enfants ajoutés ou supprimés. Le subtree est un modificateur qui étend childList pour surveiller les modifications n'importe où dans la sous-arborescence de cet élément (sinon, les modifications seraient examinées directement dans <body>). attributes et characterData sont deux autres possibilités importantes, ce qui veut dire leur sonorité.

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

Les choses deviennent un peu délicates à l'intérieur du rappel. Le rappel reçoit un tableau de MutationRecord s. Chaque MutationRecord peut décrire plusieurs modifications d'un même type (childList, attributes ou characterData.). Puisque j'ai seulement dit à l'observateur de surveiller childList, je ne me soucierai pas de vérifier le type.

var list = record.addedNodes;

Ici, je récupère une liste de noeuds de tous les noeuds enfants ajoutés. Ce sera vide pour tous les enregistrements où les nœuds ne sont pas ajoutés (et il peut y en avoir beaucoup).

À partir de là, je parcours les nœuds ajoutés et trouve ceux qui sont des éléments <select>.

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, à l'aide de l'API événements spéciaux jQuery . Vous pouvez l'utiliser comme ceci:

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

Personnellement, je suggérerais de rechercher une classe car certaines bibliothèques créeront des éléments select à des fins de test.

Naturellement, vous pouvez aussi utiliser .off("domNodeInserted", ...) ou ajuster la surveillance en transmettant des 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'apparence d'un élément select.test Chaque fois que les attributs sont modifiés pour les éléments directement à l'intérieur du corps.

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>

Remarque

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

Par exemple, supposons que votre sélecteur soit .test select Et que le document ait déjà un <select>. Ajouter la classe test à <body> Rendra le sélecteur valide, mais comme je ne vérifie que record.target Et record.addedNodes, L'événement ne se déclenche pas. Le changement doit arriver à 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 créer des doublons pour des éléments déjà traités. Traiter correctement avec adjacent ​​ou combinateurs généraux frères et soeurs rendrait les choses encore plus difficiles.

Pour une solution plus complète, voir https://github.com/pie6k/jquery.initialize , comme indiqué dans Damien Ó Ceallaighanswer =.

8
Andrew Myers

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

Pour les événements prêts, 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 de déclenchement normaux, j'utilise maintenant ceci:

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

Cela pourrait être fait avec DOM4 MutationObservers mais ne fonctionnera que dans Firefox 14 +/Chrome 18+ (pour le moment).

Cependant, il existe un " epic hack " (les mots de l'auteur ne sont pas les miens!) Qui fonctionnent dans tous les navigateurs prenant en charge les animations CSS3, à savoir: IE10, Firefox 5+, Chrome 3+, Opera 12, Android 2.0+, Safari 4+. Voir la démo ) du blog Le hack consiste à utiliser un événement d'animation CSS3 avec un nom donné, observé et utilisé en JavaScript.

4
andyb

Il existe un plugin, adampietrasiak/jquery.initialize , basé sur MutationObserver qui permet de réaliser cela simplement .

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

Une façon qui semble fiable (bien que testée uniquement dans Firefox et Chrome) est d’utiliser JavaScript pour écouter le animationend (ou son camelCased, et préfixé, événement soeur animationEnd), puis appliquez une animation de courte durée (dans la démo de 0,01 seconde) au type d'élément que vous souhaitez ajouter. Ceci, bien sûr, n'est pas un événement onCreate, mais se rapproche (dans des navigateurs compatibles ) d'un type onInsertion un événement; Ce qui suit est une preuve de concept:

$(document).on('webkitAnimationEnd animationend MSAnimationEnd oanimationend', function(e){
    var eTarget = e.target;
    console.log(eTarget.tagName.toLowerCase() + ' added to ' + eTarget.parentNode.tagName.toLowerCase());
    $(eTarget).draggable(); // or whatever other method you'd prefer
});

Avec le code HTML suivant:

<div class="wrapper">
    <button class="add">add a div element</button>
</div>

Et (versions abrégées, préfixées-supprimées bien que présentes dans le violon, ci-dessous) CSS:

/* vendor-prefixed alternatives removed for brevity */
@keyframes added {
    0% {
        color: #fff;
    }
}

div {
    color: #000;
    /* vendor-prefixed properties removed for brevity */
    animation: added 0.01s linear;
    animation-iteration-count: 1;
}

JS Fiddle démo .

Il est évident que le code CSS peut être ajusté en fonction de l'emplacement des éléments pertinents, ainsi que du sélecteur utilisé dans jQuery (il doit en réalité être aussi proche du point d'insertion que possible).

Documentation des noms d'événement:

Mozilla   |  animationend
Microsoft |  MSAnimationEnd
Opera     |  oanimationend
Webkit    |  webkitAnimationEnd
W3C       |  animationend

Les références:

4
David Thomas

Pour moi, la liaison au corps ne fonctionne pas. La liaison au document à l'aide de jQuery.bind () fait.

$(document).bind('DOMNodeInserted',function(e){
             var target = e.target;
         });
4
Milan Simek

créer un <select> avec l'identifiant, ajoutez-le au document .. et appelez .combobox

  var dynamicScript='<select id="selectid"><option value="1">...</option>.....</select>'
  $('body').append(dynamicScript); //append this to the place your wanted.
  $('#selectid').combobox();  //get the id and add .combobox();

cela devrait faire l'affaire .. vous pouvez masquer la sélection si vous voulez et après .combobox le montrer .. sinon utiliser trouver ..

 $(document).find('select').combobox() //though this is not good performancewise
0
bipen