Zdarzenie „on create” jQuery dla dynamicznie tworzonych elementów

81

Muszę mieć możliwość dynamicznego tworzenia <select>elementu i przekształcania go w jQuery .combobox(). Powinno to być zdarzenie tworzenia elementu, w przeciwieństwie do jakiegoś zdarzenia „kliknięcia”, w którym to przypadku mógłbym po prostu użyć jQuery .on().

Więc czy coś takiego istnieje?

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

Niechętnie korzystam z zapytania na żywo, ponieważ jest bardzo przestarzałe.

AKTUALIZACJA Wspomniany select / combobox jest ładowany przez ajax do colorbox jQuery (okno modalne), stąd problem - mogę zainicjować combobox tylko przy użyciu colorbox onComplete, jednak przy zmianie jednego combobox inny select / combobox musi być utworzony dynamicznie, dlatego potrzebuję bardziej ogólny sposób wykrywania tworzenia elementu ( selectw tym przypadku).

UPDATE2 Aby spróbować dalej wyjaśnić problem - mam select/comboboxelementy tworzone rekurencyjnie, wewnątrz jest też dużo kodu inicjującego .combobox(), dlatego gdybym użył klasycznego podejścia, jak w odpowiedzi @ bipen , mój kod nadymałby się do szalonych poziomów. Mam nadzieję, że to lepiej wyjaśnia problem.

UPDATE3 Dziękuję wszystkim, teraz rozumiem, że od czasu wycofania DOMNodeInsertedmutacji DOM pozostała pustka i nie ma rozwiązania tego problemu. Muszę tylko przemyśleć moją aplikację.

Caballero
źródło
1
Może być z gotowym wydarzeniem? $(select).ready(function() { });
Pablo Banderas
@PabloBanderas readysłuży do: „Określania funkcji do wykonania, gdy DOM jest w pełni załadowany”. (z jquery.com)
Caballero
1
Dlaczego nie zastosować combobox()metody w momencie tworzenia / wstawiania, a nie później?
David mówi, że przywróć Monikę
@DavidThomas Próbowałem wyjaśnić przyczynę w mojej drugiej aktualizacji
Caballero
Więc $(document).on('onComplete', 'select', function(){ // bind here? });? (Oczywiście użyj bliższego rodzica niż ten document.)
David mówi, że przywróć Monikę

Odpowiedzi:

59

Można wydarzenie uzyskać zdarzenie dla gdy jest on dodawany do dokumentu o kodzie.onDOMNodeInserted

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

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

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

EDYCJA: po przeczytaniu wystarczy, że dwukrotnie sprawdzę, DOMNodeInsertedczy nie spowoduje to problemów w przeglądarkach. To pytanie z 2010 roku sugeruje, że IE nie obsługuje tego wydarzenia, więc wypróbuj je, jeśli możesz.

Zobacz tutaj: [link] Ostrzeżenie! typ zdarzenia DOMNodeInserted jest zdefiniowany w tej specyfikacji w celu uzyskania odniesienia i kompletności, ale ta specyfikacja jest przestarzała, gdy używa się tego typu zdarzenia.

Codesleuth
źródło
nie jest .delegateprzestarzałe z wersji 1.7 jQuery i zastąpione przez .on?
Caballero,
1
Tak, masz rację. Zmodyfikuję tę odpowiedź, ale szczerze mówiąc nie jestem pewien, czy powinieneś jej używać, ponieważ DOMNodeInsertedjest teraz przestarzała.
Codesleuth,
Próbowałem, on('DOMNodeInserted'zanim opublikowałem to pytanie. To oczywiście nie zadziałało.
Caballero
20
Ponieważ nie chodzi o to, że próbowałeś tego, powiedziałbym, że to niesprawiedliwe głosowanie. W każdym razie nie mogę ci tutaj pomóc.
Codesleuth,
21

Możesz użyć DOMNodeInsertedzdarzenia mutacji (nie ma potrzeby delegowania):

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

EDYCJA: zdarzenia mutacjiprzestarzałe , zamiast tego użyj obserwatora mutacji

Yukulélé
źródło
20

Jak wspomniano w kilku innych odpowiedziach, zdarzenia mutacji zostały wycofane, więc powinieneś używać MutationObserver zamiast tego . Ponieważ nikt jeszcze nie podał żadnych szczegółów na ten temat, proszę bardzo ...

Podstawowy interfejs API JavaScript

Interfejs API dla MutationObserver jest dość prosty. Nie jest to tak proste, jak zdarzenia mutacji, ale nadal jest w porządku.

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>

Rozbijmy to.

var observer = new MutationObserver(callback);

To tworzy obserwatora. Obserwator jeszcze niczego nie ogląda; to jest właśnie miejsce, w którym zostaje dołączony detektor zdarzeń.

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

To sprawia, że ​​obserwator zaczyna działać. Pierwszym argumentem jest węzeł, na którym obserwator będzie obserwował zmiany. Drugi argument to opcje, na co należy uważać .

  • childList oznacza, że ​​chcę obserwować dodawanie lub usuwanie elementów podrzędnych.
  • subtreeto modyfikator, który rozciąga się, childList aby obserwować zmiany w dowolnym miejscu poddrzewa tego elementu (w przeciwnym razie sprawdzałby tylko zmiany bezpośrednio w obrębie tego elementu targetNode).

Pozostałe dwie główne opcje childListto attributesi characterData, co oznacza, jak brzmią. Musisz użyć jednego z tych trzech.

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

Sprawy stają się trochę skomplikowane w wywołaniu zwrotnym. Wywołanie zwrotne otrzymuje tablicę MutationRecord s. Każdy MutationRecord można opisać kilka zmian jednego typu ( childList, attributeslub characterData). Ponieważ powiedziałem obserwatorowi tylko, żeby na niego uważał childList, nie będę się trudził sprawdzaniem typu.

var list = record.addedNodes;

Tutaj pobieram listę NodeList wszystkich dodanych węzłów podrzędnych. Będzie to puste dla wszystkich rekordów, do których nie dodano węzłów (a takich rekordów może być wiele).

Od tego momentu przeglądam dodane węzły i znajduję te, które są <select> elementy.

Nie ma tu nic naprawdę skomplikowanego.

jQuery

... ale prosiłeś o jQuery. W porządku.

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

Spowoduje to utworzenie nowego zdarzenia o nazwie domNodeInsertedprzy użyciu interfejsu API wydarzeń specjalnych jQuery . Możesz go używać w ten sposób:

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

Osobiście sugerowałbym szukanie klasy, ponieważ niektóre biblioteki będą tworzyć selectelementy do celów testowych.

Oczywiście możesz również użyć .off("domNodeInserted", ...)lub dostosować obserwację, przekazując dane w następujący sposób:

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

Spowodowałoby to sprawdzanie wyglądu select.testelementu, gdy atrybuty elementów uległy zmianie bezpośrednio w treści.

Możesz to zobaczyć na żywo poniżej lub na jsFiddle .


Uwaga

Ten kod jQuery jest dość podstawową implementacją. Nie uruchamia się w przypadkach, gdy modyfikacje w innym miejscu powodują, że twój selektor jest ważny.

Na przykład załóżmy, że twój selektor to, .test selecta dokument ma już rozszerzenie <select>. Dodanie klasy testdo <body>spowoduje, że selektor będzie prawidłowy, ale ponieważ sprawdzam tylko record.targeti record.addedNodes, zdarzenie nie zostanie uruchomione. Zmiana musi nastąpić w elemencie, który chcesz sam zaznaczyć.

Można tego uniknąć, wysyłając zapytanie o selektor za każdym razem, gdy wystąpią mutacje. Zdecydowałem się tego nie robić, aby uniknąć powielania zdarzeń dla elementów, które zostały już obsłużone. Właściwe postępowanie z sąsiadującymi lub ogólnymi kombinatorami rodzeństwa mogłoby jeszcze bardziej utrudnić sprawę.

Dla bardziej kompleksowego rozwiązania, zobacz https://github.com/pie6k/jquery.initialize , jak wspomniano w Damien Ó Ceallaigh „s odpowiedź . Jednak autor tej biblioteki ogłosił, że biblioteka jest stara i sugeruje, aby nie używać do tego jQuery.

Andrew Myers
źródło
9

Właśnie wymyśliłem to rozwiązanie, które wydaje się rozwiązać wszystkie moje problemy Ajax.

W przypadku gotowych wydarzeń używam teraz tego:

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

A dla normalnych zdarzeń wyzwalających używam teraz tego:

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

Istnieje wtyczka adampietrasiak / jquery.initialize , która jest oparta na MutationObservertym, że osiąga to w prosty sposób.

$.initialize(".some-element", function() {
    $(this).css("color", "blue");
});
Damien Bezborodow
źródło
4

Można to zrobić, DOM4 MutationObserversale będzie działać tylko w Firefox 14 + / Chrome 18+ (na razie).

Istnieje jednak „ epicki hack ” (słowa autora nie moje!), Który działa we wszystkich przeglądarkach obsługujących animacje CSS3, którymi są: IE10, Firefox 5+, Chrome 3+, Opera 12, Android 2.0+, Safari 4+. Zobacz demo z bloga. Hack polega na użyciu zdarzenia animacji CSS3 o podanej nazwie, które jest obserwowane i wykonywane w JavaScript.

andyb
źródło
4

Jednym ze sposobów, który wydaje się niezawodny (chociaż został przetestowany tylko w przeglądarkach Firefox i Chrome), jest użycie JavaScript do nasłuchiwania zdarzenia animationend(lub jego camelCased i poprzedzonego prefiksu, rodzeństwa animationEnd) i zastosowanie krótkotrwałej (w wersji demonstracyjnej 0,01 sekundy) animacji do typ elementu, który planujesz dodać. Nie jest to oczywiście onCreatezdarzenie, ale przybliża (w zgodnych przeglądarkach) onInsertiontyp zdarzenia; poniżej przedstawiono dowód słuszności koncepcji:

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

Z następującym kodem HTML:

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

Oraz (w skrócie, wersje z prefiksem-usunięte, chociaż obecne w Fiddle, poniżej) 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;
}

Demo JS Fiddle .

Oczywiście CSS można dostosować tak, aby pasował do umieszczenia odpowiednich elementów, a także selektora używanego w jQuery (powinien znajdować się jak najbliżej punktu wstawienia).

Dokumentacja nazw wydarzeń:

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

Bibliografia:

David mówi, że przywróć Monikę
źródło
4

U mnie wiązanie do ciała nie działa. Wiązanie z dokumentem za pomocą jQuery.bind () działa.

$(document).bind('DOMNodeInserted',function(e){
             var target = e.target;
         });
Milan Simek
źródło
1

Myślę, że warto wspomnieć, że w niektórych przypadkach to zadziała:

$( document ).ajaxComplete(function() {
// Do Stuff
});
user315338
źródło
1

zamiast...

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

Możesz pisać...

$('body').on('click', '.class', function() {
    // do something
});
Hasan Zahran
źródło
0

utwórz <select>z id, dołącz go do dokumentu ... i zadzwoń.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();

to powinno załatwić .comboboxsprawę ... możesz ukryć zaznaczenie, jeśli chcesz, a po wyświetleniu go ... albo użyj funkcji znajdź ...

 $(document).find('select').combobox() //though this is not good performancewise
bipen
źródło
-1

jeśli używasz angularjs, możesz napisać własną dyrektywę. Miałem ten sam problem z bootstrapSwitch. Muszę zadzwonić $("[name='my-checkbox']").bootstrapSwitch(); w javascript, ale mój obiekt wejściowy html nie został wówczas utworzony. Więc piszę własną dyrektywę i tworzę element wejściowy z <input type="checkbox" checkbox-switch>

W dyrektywie kompiluję element, aby uzyskać dostęp za pośrednictwem javascript i wykonać polecenie jquery (jak twoje .combobox()polecenie). Bardzo ważne jest, aby usunąć atrybut. W przeciwnym razie ta dyrektywa wywoła samą siebie i utworzysz pętlę

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
źródło
Pytanie dotyczyło samego jQuery, a nie AngularJS. Rozwiązanie powinno być ograniczone do wtyczek jQuery i jQuery.
James Dunne,