jQuery-Ereignis "on create" für dynamisch erstellte Elemente

80

Ich muss in der Lage sein, <select>Elemente dynamisch zu erstellen und in jQuery umzuwandeln .combobox(). Dies sollte ein Elementerstellungsereignis sein, im Gegensatz zu einem "Klick" -Ereignis. In diesem Fall könnte ich nur jQuery verwenden .on().

Gibt es so etwas?

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

Ich verwende Livequery nur ungern, da es sehr veraltet ist.

UPDATE Die erwähnte Select / Combobox wird über Ajax in eine jQuery Colorbox (Modal Window) geladen, daher das Problem - ich kann Combobox nur mit Colorbox initiieren onComplete, aber beim Wechsel einer Combobox muss dynamisch eine andere Select / Combobox erstellt werden, dafür brauche ich eine allgemeinere Methode zum Erkennen der Erstellung eines Elements ( selectin diesem Fall).

UPDATE2 Um zu versuchen, das Problem weiter zu erklären - Ich habe select/comboboxElemente rekursiv erstellt, es enthält auch viel Initiierungscode. .combobox()Wenn ich also einen klassischen Ansatz wie in der Antwort von @ bipen verwenden würde, würde sich mein Code auf ein wahnsinniges Niveau aufblasen. Hoffe das erklärt das Problem besser.

UPDATE3 Vielen Dank an alle, ich verstehe jetzt, dass die DOMNodeInsertedDOM-Mutation eine Lücke aufweist und es keine Lösung für dieses Problem gibt. Ich muss nur meine Bewerbung überdenken.

Caballero
quelle
1
Kann mit dem fertigen Event sein? $(select).ready(function() { });
Pablo Banderas
@PabloBanderas readywird verwendet, um: "Eine Funktion anzugeben , die ausgeführt werden soll, wenn das DOM vollständig geladen ist." (von jquery.com)
Caballero
1
Warum nicht die combobox()Methode beim Erstellen / Einfügen und nicht danach anwenden ?
David sagt, Monica am
@ DavidThomas Versucht, den Grund in meinem zweiten Update zu erklären
Caballero
Also $(document).on('onComplete', 'select', function(){ // bind here? });? (Verwenden Sie natürlich einen engeren Elternteil als den document.)
David sagt, Monica am

Antworten:

58

Sie können ondas DOMNodeInsertedEreignis festlegen, um ein Ereignis abzurufen, wenn es durch Ihren Code zum Dokument hinzugefügt wird.

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

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

Hier herumgespielt: http://jsfiddle.net/Codesleuth/qLAB2/3/

BEARBEITEN: Nachdem ich herumgelesen habe, muss ich nur noch einmal überprüfen, ob DOMNodeInsertedes keine Probleme zwischen den Browsern gibt. Diese Frage aus dem Jahr 2010 deutet darauf hin, dass der IE das Ereignis nicht unterstützt. Testen Sie es daher, wenn Sie können.

Siehe hier: [Link] Warnung! Der Ereignistyp DOMNodeInserted ist in dieser Spezifikation zur Bezugnahme und Vollständigkeit definiert, diese Spezifikation veraltet jedoch die Verwendung dieses Ereignistyps.

Codesleuth
quelle
ist nicht .delegateveraltet von jQuery Version 1.7 und ersetzt durch .on?
Caballero
1
Ja, du hast recht. Ich werde diese Antwort ändern, bin mir aber ehrlich gesagt nicht sicher, ob Sie diese verwenden sollten, da DOMNodeInsertedsie jetzt veraltet ist.
Codesleuth
Ich habe es versucht, on('DOMNodeInserted'bevor ich diese Frage gestellt habe. Es hat offensichtlich nicht funktioniert.
Caballero
19
Da es nicht in Ihrer Frage steht, dass Sie es versucht haben, würde ich sagen, dass dies eine unfaire Abstimmung ist. Auf jeden Fall kann ich Ihnen hier nicht helfen.
Codesleuth
21

Sie können ein DOMNodeInsertedMutationsereignis verwenden (keine Delegierung erforderlich):

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

BEARBEITEN: Mutationsereignisse sind veraltet . Verwenden Sie stattdessen den Mutationsbeobachter

Yukulélé
quelle
19

Wie in mehreren anderen Antworten erwähnt, sind Mutationsereignisse veraltet, daher sollten Sie stattdessen MutationObserver verwenden. Da noch niemand Details dazu angegeben hat, geht es los ...

Grundlegende JavaScript-API

Die API für MutationObserver ist ziemlich einfach. Es ist nicht ganz so einfach wie die Mutationsereignisse, aber es ist immer noch in Ordnung.

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>

Lassen Sie uns das aufschlüsseln.

var observer = new MutationObserver(callback);

Dies schafft den Betrachter. Der Beobachter beobachtet noch nichts; Hier wird der Ereignis-Listener angehängt.

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

Dadurch startet der Beobachter. Das erste Argument ist der Knoten, auf dem der Beobachter nach Änderungen sucht. Das zweite Argument sind die Optionen für das, worauf zu achten ist .

  • childList bedeutet, dass ich darauf achten möchte, dass untergeordnete Elemente hinzugefügt oder entfernt werden.
  • subtreeist ein Modifikator, der erweitert wird childList , um nach Änderungen an einer beliebigen Stelle im Teilbaum dieses Elements zu suchen (andernfalls werden nur Änderungen direkt darin betrachtet targetNode).

Die anderen beiden Hauptoptionen childListsind attributesund characterData, die bedeuten, wie sie klingen. Sie müssen eine dieser drei verwenden.

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

Im Rückruf wird es etwas knifflig. Der Rückruf empfängt ein Array von MutationRecords . Jede MutationRecord kann mehrere Änderungen eines Typs beschreiben ( childList, attributesoder characterData). Da ich dem Beobachter nur gesagt habe, er solle darauf achten childList, werde ich den Typ nicht überprüfen.

var list = record.addedNodes;

Genau hier greife ich zu einer NodeList aller untergeordneten Knoten, die hinzugefügt wurden. Dies ist für alle Datensätze leer, bei denen keine Knoten hinzugefügt wurden (und möglicherweise gibt es viele solcher Datensätze).

Von da an durchlaufe ich die hinzugefügten Knoten und finde alle <select>Elemente.

Nichts wirklich komplexes hier.

jQuery

... aber du hast nach jQuery gefragt. Fein.

(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);

Dadurch wird domNodeInsertedmithilfe der jQuery-API für spezielle Ereignisse ein neues Ereignis mit dem Namen erstellt . Sie können es so verwenden:

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

Ich würde persönlich vorschlagen, nach einer Klasse zu suchen, da einige Bibliotheken selectElemente zu Testzwecken erstellen .

Natürlich können Sie die Wiedergabe auch verwenden .off("domNodeInserted", ...)oder optimieren, indem Sie Daten wie folgt übergeben:

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

Dies würde die Überprüfung des Erscheinungsbilds eines select.testElements auslösen, wenn sich Attribute für Elemente direkt im Körper ändern.

Sie können es live unten oder auf jsFiddle sehen .


Hinweis

Dieser jQuery-Code ist eine ziemlich einfache Implementierung. Es wird nicht ausgelöst, wenn Änderungen an anderer Stelle Ihren Selektor gültig machen.

Angenommen, Ihre Auswahl ist .test selectund das Dokument hat bereits eine <select>. Durch Hinzufügen der Klasse testzu <body>wird der Selektor gültig, aber da ich nur record.targetund überprüfe record.addedNodes, wird das Ereignis nicht ausgelöst. Die Änderung muss an dem Element erfolgen, das Sie selbst auswählen möchten.

Dies könnte vermieden werden, indem bei allen Mutationen nach dem Selektor gefragt wird. Ich habe mich dafür entschieden, dies nicht zu tun, um zu vermeiden, dass doppelte Elemente für Elemente verursacht werden, die bereits behandelt wurden. Der richtige Umgang mit benachbarten oder allgemeinen Geschwisterkombinatoren würde die Dinge noch schwieriger machen.

Für eine umfassendere Lösung finden https://github.com/pie6k/jquery.initialize , wie in erwähnt Damien Ó Ceallaigh ‚s Antwort . Der Autor dieser Bibliothek hat jedoch angekündigt, dass die Bibliothek alt ist, und schlägt vor, dass Sie dafür jQuery nicht verwenden sollten.

Andrew Myers
quelle
9

Ich habe gerade diese Lösung gefunden, die alle meine Ajax-Probleme zu lösen scheint.

Für On-Ready-Events verwende ich jetzt Folgendes:

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');
});

Und für normale Triggerereignisse verwende ich jetzt Folgendes:

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

Es gibt ein Plugin, adampietrasiak / jquery.initialize , das darauf basiert MutationObserver, dies einfach zu erreichen.

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

Dies ist möglich, DOM4 MutationObserversfunktioniert aber ( vorerst) nur in Firefox 14 + / Chrome 18+.

Es gibt jedoch einen " epischen Hack " (die Worte des Autors gehören nicht mir!), Der in allen Browsern funktioniert, die CSS3-Animationen unterstützen: IE10, Firefox 5+, Chrome 3+, Opera 12, Android 2.0+, Safari 4+. Siehe die Demo aus dem Blog. Der Hack besteht darin, ein CSS3-Animationsereignis mit einem bestimmten Namen zu verwenden, das in JavaScript beobachtet und verarbeitet wird.

andyb
quelle
4

Eine Möglichkeit, die scheint zuverlässig (wenn auch nur getestet in Firefox und Chrome) ist JavaScript zu verwenden , um das zu hören animationend(oder seine camelcase, und das Präfix, Geschwister animationEnd) Ereignis und wendet eine kurzlebige (in der Demo 0,01 Sekunden) Animation Der Elementtyp, den Sie hinzufügen möchten. Dies ist natürlich kein onCreateEreignis, sondern entspricht (in kompatiblen Browsern) einer onInsertionArt von Ereignis. Das Folgende ist ein Proof-of-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
});

Mit folgendem HTML:

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

Und (abgekürzt, Präfixversionen entfernt, obwohl in der Geige unten vorhanden) 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 Demo .

Offensichtlich kann das CSS an die Platzierung der relevanten Elemente sowie an den in der jQuery verwendeten Selektor angepasst werden (es sollte wirklich so nah wie möglich am Einfügepunkt sein).

Dokumentation der Ereignisnamen:

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

Verweise:

David sagt, Monica wieder einsetzen
quelle
4

Für mich funktioniert die Bindung an den Körper nicht. Das Binden an das Dokument mit jQuery.bind () funktioniert.

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

Ich denke, es ist erwähnenswert, dass dies in einigen Fällen funktionieren würde:

$( document ).ajaxComplete(function() {
// Do Stuff
});
user315338
quelle
0

Erstellen Sie eine <select>mit ID, hängen Sie sie an das Dokument an .. und rufen Sie an.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();

Dies sollte den Trick machen. Sie können die Auswahl ausblenden, wenn Sie möchten und nach dem .comboboxAnzeigen. Oder verwenden Sie find.

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

anstatt...

$(".class").click( function() {
    // do something
});

Du kannst schreiben...

$('body').on('click', '.class', function() {
    // do something
});
Hasan Zahran
quelle
-1

Wenn Sie anglejs verwenden, können Sie Ihre eigene Direktive schreiben. Ich hatte das gleiche Problem mit bootstrapSwitch. Ich muss $("[name='my-checkbox']").bootstrapSwitch(); Javascript aufrufen, aber mein HTML-Eingabeobjekt wurde zu diesem Zeitpunkt nicht erstellt. Also schreibe ich eine eigene Direktive und erstelle das Eingabeelement mit <input type="checkbox" checkbox-switch>

In der Direktive kompiliere ich das Element, um über Javascript Zugriff zu erhalten und den Befehl jquery auszuführen (wie Ihren .combobox()Befehl). Sehr wichtig ist es, das Attribut zu entfernen. Andernfalls ruft sich diese Direktive selbst auf und Sie haben eine Schleife erstellt

app.directive("checkboxSwitch", function($compile) {
return {
    link: function($scope, element) {
        var input = element[0];
        input.removeAttribute("checkbox-switch");
        var inputCompiled = $compile(input)($scope.$parent);
        inputCompiled.bootstrapSwitch();
    }
}
});
Krolock
quelle
Die Frage bezog sich nur auf jQuery, nicht auf AngularJS. Die Lösung sollte auf jQuery- und jQuery-Plugins beschränkt sein.
James Dunne