jQuery - Memicu peristiwa ketika elemen dihapus dari DOM

211

Saya mencoba mencari cara untuk mengeksekusi beberapa kode js ketika sebuah elemen dihapus dari halaman:

jQuery('#some-element').remove(); // remove some element from the page
/* need to figure out how to independently detect the above happened */

apakah ada acara yang dirancang khusus untuk itu, seperti:

jQuery('#some-element').onremoval( function() {
    // do post-mortem stuff here
});

Terima kasih.

Sa125
sumber
1
karena penasaran, apa yang ingin dilakukan dengan elemen yang telah dihapus?
Natrium
8
Saya memiliki elemen yang secara independen menempel pada potongan yang saya hapus, jadi saya ingin mendeteksi kapan potongan itu hilang untuk menghilangkan elemen itu juga. Saya bisa mendesain ulang semuanya, tetapi menyelesaikan hal di atas akan menghemat banyak waktu (dan kode).
Sa125

Jawaban:

118

Baru diperiksa, ini sudah ada di dalam versi JQuery saat ini:

jQuery - v1.9.1

jQuery UI - v1.10.2

$("#myDiv").on("remove", function () {
    alert("Element was removed");
})

Penting : Ini adalah fungsionalitas skrip Jquery UI (bukan JQuery), jadi Anda harus memuat kedua skrip (jquery dan jquery-ui) untuk membuatnya berfungsi. Berikut ini contohnya: http://jsfiddle.net/72RTz/

Philipp Munin
sumber
11
Ini sangat membantu. Saya mengetahui bahwa fungsi ini ada dalam komponen "Widget" dari jQuery UI jika Anda tidak ingin mengunduh seluruh pustaka UI.
Neil
5
Apakah ini didokumentasikan di mana saja? Saya baru saja melihat dokumentasi widget UI jQuery dan tidak dapat menemukan yang menyebutkan ini. Ingin melihat apakah ini didukung secara resmi / ada peringatan tentang penggunaannya ...
josh
Yang ini tidak berfungsi - dicoba di jQuery 1.10.2, namun jawaban di bawah ini oleh @mtkopone berfungsi dengan baik karena itu saya akan memilih untuk memperbarui jawaban dalam pertanyaan itu
Marcin
20
Yang ini hanya berfungsi sebagian. Jika Anda melakukannya removepada elemen itu api, tetapi jika elemen dihancurkan dengan cara lain, dengan ditimpa katakan, itu tidak berhasil.
Carl Smith
Anda mungkin benar, mungkin ada nama acara lain untuk kasus ini. Akan lebih bagus jika Anda dapat memberikan sampel jsfiddle untuk kasus Anda.
Philipp Munin
195

Anda dapat menggunakan acara khusus jQuery untuk ini.

Dalam semua kesederhanaan,

Mempersiapkan:

(function($){
  $.event.special.destroyed = {
    remove: function(o) {
      if (o.handler) {
        o.handler()
      }
    }
  }
})(jQuery)

Pemakaian:

$('.thing').bind('destroyed', function() {
  // do stuff
})

Tambahan untuk menjawab komentar Pierre dan DesignerGuy:

Agar panggilan balik tidak menyala saat memanggil $('.thing').off('destroyed'), ubah kondisi if ke:if (o.handler && o.type !== 'destroyed') { ... }

mtkopone
sumber
15
Solusi yang sangat bagus. Ben Alman memiliki artikel bagus tentang acara-acara khusus untuk info lebih lanjut: benalman.com/news/2010/03/jquery-special-events
antti_s
11
+1 telah berhasil sepenuhnya melewatkan acara khusus ini sampai sekarang, sangat berguna! Satu hal untuk menyatakan baru saja mengimplementasikan hal di atas, akan lebih baik untuk mengubah o.handler()ke o.handler.apply(this,arguments)sebaliknya acara dan objek data tidak bisa melewati pendengar acara.
Pebbl
6
Ini tidak berfungsi ketika Anda menghapus elemen tanpa jQuery.
djjeck
5
ini tidak berfungsi a) ketika elemen dilepaskan alih-alih dihapus atau b) ketika beberapa pustaka non-jquery lama menggunakan innerHTML untuk menghancurkan elemen Anda (mirip dengan apa yang dikatakan jjjeck)
Charon ME
5
Waspadalah bahwa pawang dipanggil ketika Anda $('.thing').unbind('destroyed')yang benar-benar dapat mengganggu (karena tidak mengikat berarti kita tidak ingin pawang dipanggil ...)
Pierre
54

Anda dapat mengikat ke acara DOMNodeRemoved (bagian dari spesifikasi DOM Level 3 WC3).

Bekerja di IE9, rilis terbaru Firefox dan Chrome.

Contoh:

$(document).bind("DOMNodeRemoved", function(e)
{
    alert("Removed: " + e.target.nodeName);
});

Anda juga bisa mendapatkan notifikasi ketika elemen memasukkan dengan mengikat DOMNodeInserted

Adam
sumber
13
Catatan: "Menambahkan pendengar mutasi DOM ke dokumen secara mendalam menurunkan kinerja modifikasi DOM lebih lanjut ke dokumen itu (menjadikannya 1,5 - 7 kali lebih lambat!)." dari: developer.mozilla.org/en/DOM/Mutation_events
Matt Crinklaw-Vogt
1
Senang ini, meskipun nodeName jarang berguna bagi saya. Saya hanya menggunakan e.target.classNameatau if ($(e.target).hasClass('my-class')) { ....
Micah Alcorn
Ini tidak bekerja pada elemen itu sendiri, hanya anak-anaknya.
Xdg
Jawaban yang luar biasa! Sangat membantu saya!
Kir Mazur
Mungkin lambat dan ide buruk untuk kode dalam produksi, tetapi ini sepertinya satu-satunya metode yang bekerja secara serempak (tidak seperti MutationObserver) dan untuk setiap simpul (bukan hanya simpul yang dihapus melalui jQuery). Ini adalah opsi bagus untuk debugging.
Kinerja Tertentu
38

Tidak ada acara bawaan untuk menghapus elemen, tetapi Anda dapat membuatnya dengan metode penghapusan default jQuery yang memperluas palsu. Perhatikan bahwa callback harus dipanggil sebelum benar-benar menghapusnya untuk menyimpan referensi.

(function() {
    var ev = new $.Event('remove'),
        orig = $.fn.remove;
    $.fn.remove = function() {
        $(this).trigger(ev);
        return orig.apply(this, arguments);
    }
})();

$('#some-element').bind('remove', function() {
    console.log('removed!');
    // do pre-mortem stuff here
    // 'this' is still a reference to the element, before removing it
});

// some other js code here [...]

$('#some-element').remove();

Catatan: beberapa masalah dengan jawaban ini telah diuraikan oleh poster lain.

  1. Ini tidak akan berfungsi ketika node dihapus melalui html() replace()atau metode jQuery lainnya
  2. Acara ini meletus
  3. Penggantian jQuery UI juga dihapus

Solusi paling elegan untuk masalah ini adalah: https://stackoverflow.com/a/10172676/216941

David Hellsing
sumber
2
Terima kasih untuk itu! Satu tambahan kecil: karena peristiwa penghapusan itu muncul, Anda juga akan menerimanya ketika seorang anak dihapus, jadi lebih baik tuliskan pawang seperti itu:$('#some-element').bind('remove', function(ev) { if (ev.target === this) { console.log('removed!'); } });
meyertee
3
Ini tidak berhasil bagi saya - saya harus mengembalikan hasilnya dari orig.apply.
fturtle
1
Sebenarnya, @Adam, memang, tapi ini kompatibel dengan browser. Dengan tambahan meyertee / fturtle, ini adalah solusi yang sangat andal, selama Anda hanya menghapus elemen dengan metode ini, daripada memodifikasi / mengosongkan HTML, dll. Untuk sesuatu yang lebih fleksibel, ya acara mutasi DOM baik-baik saja, tetapi saya telah skeptis terhadap mereka sebagai Anda harus mendengarkan acara bisnis di aplikasi, bukan yang DOM yang strukturnya cenderung berubah seiring perkembangan lebih lanjut. Selain itu, berlangganan acara mutasi DOM berarti program Anda berpotensi rentan terhadap keterlambatan dalam hierarki DOM yang kompleks.
Will Morgan
1
Satu-satunya perbedaan pendapat saya tentang keakuratan adalah pernyataan - 'tidak ada event yang dibuat untuk menghilangkan elemen' --- ada acara built in untuk browser yang mengimplementasikan acara DOM Level 3 (seperti yang dijelaskan dalam jawaban saya).
Adam
4
Meskipun ini akan mendeteksi elemen yang dihapus menggunakan fungsi 'hapus', itu akan gagal mendeteksi elemen yang dihapus dengan cara lain (misalnya menggunakan html jQuery, ganti, dll). Tolong, lihat jawaban saya untuk solusi yang lebih lengkap.
zah
32

Hooking .remove()bukan cara terbaik untuk menangani ini karena ada banyak cara untuk menghapus elemen dari halaman (misalnya dengan menggunakan .html(), .replace(), dll).

Untuk mencegah berbagai bahaya kebocoran memori, secara internal jQuery akan mencoba memanggil fungsi jQuery.cleanData()untuk setiap elemen yang dihapus terlepas dari metode yang digunakan untuk menghapusnya.

Lihat jawaban ini untuk lebih jelasnya: kebocoran memori javascript

Jadi, untuk hasil terbaik, Anda harus mengaitkan cleanDatafungsinya, yang persis seperti yang dilakukan plugin jquery.event.destroyed :

http://v3.javascriptmvc.com/jquery/dist/jquery.event.destroyed.js

zah
sumber
Wawasan tentang cleanDataini sangat membantu saya! Terima kasih banyak, Joe :)
Petr Vostrel
1
Jawaban yang bagus, tapi tetap saja itu hanya berfungsi untuk metode jQuery. Jika Anda berintegrasi dengan platform lain yang dapat "menarik karpet" dari bawah Anda - jawaban Adam paling masuk akal.
Bron Davies
7

Bagi mereka yang menggunakan jQuery UI:

jQuery UI telah diganti beberapa metode jQuery untuk melaksanakan removeacara yang akan ditangani tidak hanya ketika Anda secara eksplisit menghapus elemen yang diberikan, tetapi juga jika elemen akan dihapus dari DOM oleh membersihkan diri metode jQuery (misalnya replace, html, dll) . Ini pada dasarnya memungkinkan Anda untuk memasang kail ke acara yang sama yang dipecat ketika jQuery "membersihkan" acara dan data yang terkait dengan elemen DOM.

John Resig telah mengindikasikan bahwa dia terbuka untuk ide mengimplementasikan acara ini di versi inti jQuery di masa depan, tapi saya tidak yakin di mana tempatnya saat ini.

StriplingWarrior
sumber
6

Hanya jQuery yang diperlukan (Tidak diperlukan UI jQuery)

( Saya telah mengekstrak ekstensi ini dari kerangka UI jQuery )

Bekerja dengan: empty()dan html()danremove()

$.cleanData = ( function( orig ) {
    return function( elems ) {
        var events, elem, i;
        for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) {
            try {

                // Only trigger remove when necessary to save time
                events = $._data( elem, "events" );
                if ( events && events.remove ) {
                    $( elem ).triggerHandler( "remove" );
                }

            // Http://bugs.jquery.com/ticket/8235
            } catch ( e ) {}
        }
        orig( elems );
    };
} )( $.cleanData );

Dengan solusi ini Anda juga dapat melepas ikatan handler acara.

$("YourElemSelector").off("remove");

Cobalah! - Contoh

$.cleanData = (function(orig) {
  return function(elems) {
    var events, elem, i;
    for (i = 0;
      (elem = elems[i]) != null; i++) {
      try {

        // Only trigger remove when necessary to save time
        events = $._data(elem, "events");
        if (events && events.remove) {
          $(elem).triggerHandler("remove");
        }

        // Http://bugs.jquery.com/ticket/8235
      } catch (e) {}
    }
    orig(elems);
  };
})($.cleanData);


$("#DivToBeRemoved").on("remove", function() {
  console.log("div was removed event fired");
});

$("p").on("remove", function() {
  console.log("p was removed event fired");
});

$("span").on("remove", function() {
  console.log("span was removed event fired");
});

// $("span").off("remove");

$("#DivToBeRemoved").on("click", function() {
  console.log("Div was clicked");
});

function RemoveDiv() {
  //       $("#DivToBeRemoved").parent().html("");    
  $("#DivToBeRemoved").remove();
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h3>OnRemove event handler attached to elements `div`, `p` and `span`.</h3>
<div class="container">
  <br>
  <button onclick="RemoveDiv();">Click here to remove div below</button>
  <div id="DivToBeRemoved">
    DIV TO BE REMOVED 
    contains 1 p element 
    which in turn contains a span element
    <p>i am p (within div)
      <br><br><span>i am span (within div)</span></p>
  </div>
</div>

Demo Tambahan - jsBin

Legenda
sumber
4

Saya tidak bisa mendapatkan jawaban ini untuk bekerja dengan tidak mengikat (meskipun ada pembaruan lihat di sini ), tetapi saya bisa mencari jalan keluar. Jawabannya adalah untuk membuat acara khusus 'destroy_proxy' yang memicu acara 'hancur'. Anda menempatkan pendengar acara di 'hancur_proxy' dan 'dihancurkan', maka ketika Anda ingin melepaskan ikatan, Anda hanya membatalkan ikatan acara 'dihancurkan':

var count = 1;
(function ($) {
    $.event.special.destroyed_proxy = {
        remove: function (o) {
            $(this).trigger('destroyed');
        }
    }
})(jQuery)

$('.remove').on('click', function () {
    $(this).parent().remove();
});

$('li').on('destroyed_proxy destroyed', function () {
    console.log('Element removed');
    if (count > 2) {
        $('li').off('destroyed');
        console.log('unbinded');
    }
    count++;
});

Ini biola

Bill Johnston
sumber
Bagaimana dengan kompatibilitas browser di sini? Apakah masih berfungsi?
Vladislav Rastrusny
3

Saya suka jawaban mtkopone menggunakan acara khusus jQuery, tetapi perhatikan bahwa itu tidak berfungsi a) ketika elemen dilepaskan alih-alih dihapus atau b) ketika beberapa perpustakaan non-jquery lama menggunakan innerHTML untuk menghancurkan elemen Anda

Charon ME
sumber
3
Saya downvoted karena pertanyaan yang diajukan secara eksplisit untuk penggunaan .remove (), bukan detach. detachdigunakan terutama untuk tidak memicu pembersihan karena elemen tersebut mungkin direncanakan untuk dipasang kembali nanti. b) masih benar dan belum ada penanganan yang dapat diandalkan, setidaknya sejak peristiwa mutasi DOM diimplementasikan secara luas.
Pierre
4
Saya yakin Anda melakukan ini hanya untuk mendapatkan lencana "kritik";)
Charon ME
1

Saya tidak yakin ada pegangan acara untuk ini, jadi Anda harus menyimpan salinan DOM dan membandingkannya dengan DOM yang ada di semacam jajak pendapat - yang bisa sangat tidak menyenangkan. Firebug melakukan ini - jika Anda memeriksa HTML dan menjalankan beberapa perubahan DOM, Firebug menyoroti perubahan dalam warna kuning di konsol Firebug untuk waktu yang singkat.

Atau, Anda dapat membuat fungsi hapus ...

var removeElements = function(selector) {
    var elems = jQuery(selector);

    // Your code to notify the removal of the element here...
    alert(elems.length + " elements removed");

    jQuery(selector).remove();
};

// Sample usage
removeElements("#some-element");
removeElements("p");
removeElements(".myclass");
Fenton
sumber
1
+1 untuk gagasan ini. Meskipun Anda juga dapat memperluas jQuery (gaya plugin) untuk mendapatkan panggilan jQuery yang lebih standar seperti: $ ('. ItemToRemove'). CustomRemove () ;. Anda juga bisa membuatnya sehingga menerima panggilan balik sebagai parameter.
user113716
1

Ini adalah cara membuat pendengar hapus jQuery langsung :

$(document).on('DOMNodeRemoved', function(e)
{
  var $element = $(e.target).find('.element');
  if ($element.length)
  {
    // do anything with $element
  }
});

Atau:

$(document).on('DOMNodeRemoved', function(e)
{
  $(e.target).find('.element').each(function()
  {
    // do anything with $(this)
  }
});
Buzogany Laszlo
sumber
1
Akan lebih baik untuk bebas melakukan itu. Sayangnya itu tidak dapat diandalkan, karena peristiwa mutasi sedang ditinggalkan: developer.mozilla.org/en-US/docs/Web/Guide/Events/…
Kamafeather
1

Acara "hapus" dari jQuery berfungsi dengan baik, tanpa penambahan. Mungkin lebih dapat diandalkan pada waktunya untuk menggunakan trik sederhana, daripada menambal jQuery.

Cukup modifikasi atau tambahkan atribut di elemen yang akan Anda hapus dari DOM. Dengan demikian, Anda dapat memicu fungsi pembaruan apa pun, yang hanya akan mengabaikan elemen yang akan dihancurkan, dengan atribut "do_not_count_it".

Misalkan kita memiliki tabel dengan sel yang sesuai dengan harga, dan Anda hanya perlu menunjukkan harga terakhir: Ini adalah pemilih untuk memicu ketika sel harga dihapus (kami memiliki tombol di setiap baris tabel yang melakukan itu, tidak ditampilkan sini)

$('td[validity="count_it"]').on("remove", function () {
    $(this).attr("validity","do_not_count_it");
    update_prices();
});

Dan di sini adalah fungsi yang menemukan harga terakhir dalam tabel, tidak memperhitungkan yang terakhir, jika itu yang dihapus. Memang, ketika acara "hapus" dipicu, dan ketika fungsi ini dipanggil, elemen tersebut belum dihapus.

function update_prices(){
      var mytable=$("#pricestable");
      var lastpricecell = mytable.find('td[validity="count_it"]').last();
}

Pada akhirnya, fungsi update_prices () berfungsi dengan baik, dan setelah itu, elemen DOM dihapus.

philippe
sumber
0

referensi ke jawaban @vid:

Ketika Anda ingin melakukannya dengan fungsi lain, mis. html () seperti dalam kasus saya, jangan lupa tambahkan kembali dalam fungsi baru:

(function() {
    var ev = new $.Event('html'),
        orig = $.fn.html;
    $.fn.html = function() {
        $(this).trigger(ev);
        return orig.apply(this, arguments);
    }
})();
Patryk M
sumber
0

Ini.

$.each(
  $('#some-element'), 
        function(i, item){
            item.addEventListener('DOMNodeRemovedFromDocument',
                function(e){ console.log('I has been removed'); console.log(e);
                })
         })
Matas Vaitkevicius
sumber
1
DOMNodeRemovedFromDocument tampaknya tidak didukung di Firefox. Mungkin coba MutationObserver ?
EricP
0

ekstensi untuk jawaban Adam jika Anda perlu mencegah default, berikut ini adalah pekerjaannya:

$(document).on('DOMNodeRemoved', function(e){
        if($(e.target).hasClass('my-elm') && !e.target.hasAttribute('is-clone')){
            let clone = $(e.target).clone();
            $(clone).attr('is-clone', ''); //allows the clone to be removed without triggering the function again

            //you can do stuff to clone here (ex: add a fade animation)

            $(clone).insertAfter(e.target);
            setTimeout(() => {
                //optional remove clone after 1 second
                $(clone).remove();
            }, 1000);
        }
    });
SwiftNinjaPro
sumber
0

Kami juga dapat menggunakan DOMNodeRemoved:

$("#youridwhichremoved").on("DOMNodeRemoved", function () {
// do stuff
})
Den Pat
sumber