Bagaimana cara menghapus / menghapus binding yang dapat diamati di Knockout.js?

113

Saya membangun fungsionalitas ke halaman web yang dapat dilakukan pengguna beberapa kali. Melalui tindakan pengguna, objek / model dibuat dan diterapkan ke HTML menggunakan ko.applyBindings ().

HTML terikat data dibuat melalui template jQuery.

Sejauh ini baik.

Ketika saya mengulangi langkah ini dengan membuat objek / model kedua dan memanggil ko.applyBindings () saya mengalami dua masalah:

  1. Markup menunjukkan objek / model sebelumnya serta objek / model baru.
  2. Terjadi kesalahan javascript terkait dengan salah satu properti dalam objek / model, meskipun masih dirender dalam markup.

Untuk mengatasi masalah ini, setelah lulus pertama saya memanggil jQuery's .empty () untuk menghapus HTML template yang berisi semua atribut data-bind, sehingga tidak lagi di DOM. Saat pengguna memulai proses untuk umpan kedua, HTML yang terikat data ditambahkan kembali ke DOM.

Tapi seperti yang saya katakan, ketika HTML ditambahkan kembali ke DOM dan diikat ulang ke objek / model baru, itu masih menyertakan data dari objek / model pertama, dan saya masih mendapatkan kesalahan JS yang tidak terjadi selama lintasan pertama.

Kesimpulannya adalah bahwa Knockout mempertahankan properti terikat ini, meskipun markup dihapus dari DOM.

Jadi yang saya cari adalah cara untuk menghapus properti terikat ini dari Knockout; memberi tahu knockout bahwa tidak ada lagi model yang dapat diamati. Apakah ada cara untuk melakukan ini?

EDIT

Proses dasarnya adalah pengguna mengunggah file; server kemudian merespons dengan objek JSON, HTML terikat data ditambahkan ke DOM, kemudian model objek JSON terikat ke HTML ini menggunakan

mn.AccountCreationModel = new AccountViewModel(jsonData.Account);
ko.applyBindings(mn.AccountCreationModel);

Setelah pengguna membuat beberapa pilihan pada model, objek yang sama dikirim kembali ke server, HTML yang terikat data dihapus dari DOM, dan saya kemudian memiliki JS berikut

mn.AccountCreationModel = null;

Ketika pengguna ingin melakukan ini sekali lagi, semua langkah ini diulangi.

Saya khawatir kodenya terlalu 'terlibat' untuk melakukan demo jsFiddle.

awj
sumber
Memanggil ko.applyBindings beberapa kali terutama pada elemen dom yang sama tidak disarankan. Mungkin ada cara lain untuk mencapai apa yang Anda inginkan. Anda perlu memberikan lebih banyak kode. Harap sertakan jsfiddle jika memungkinkan.
madcapnmckay
Mengapa tidak mengekspos initfungsi di mana Anda meneruskan data untuk diterapkan?
KyorCode

Jawaban:

169

Sudahkah Anda mencoba memanggil metode simpul bersih knockout pada elemen DOM Anda untuk membuang objek yang terikat dalam memori?

var element = $('#elementId')[0]; 
ko.cleanNode(element);

Kemudian menerapkan pengikatan knockout lagi hanya pada elemen itu dengan model tampilan baru Anda akan memperbarui pengikatan tampilan Anda.

KodeKreachor
sumber
33
Ini berhasil - terima kasih. Saya tidak dapat menemukan dokumentasi apa pun tentang metode ini.
awj
Saya juga telah mencari dokumentasi tentang fungsi utilitas sistem gugur ini. Sedekat yang bisa saya ketahui dari kode sumber, ia memanggil deletekunci tertentu pada elemen dom itu sendiri, yang tampaknya di mana semua sihir sistem gugur disimpan. Jika ada yang punya sumber dokumentasi, saya akan sangat berterima kasih.
Patrick M
2
Anda tidak akan menemukannya. Saya telah mencari tinggi dan rendah untuk dokumen tentang fungsi utilitas ko, tetapi tidak ada. Posting blog ini adalah hal terdekat yang akan Anda temukan, tetapi hanya mencakup anggota ko.utils: knockmeout.net/2011/04/utility-functions-in-knockoutjs.html
Nick Daniels
1
Anda juga ingin menghapus acara secara manual seperti yang ditunjukkan dalam jawaban saya di bawah ini.
Michael Berkompas
1
@KKKachor Saya sudah memposting contoh kerja di bawah ini. Maksud saya adalah bahwa mungkin lebih baik untuk menjaga pengikatan tetap utuh, sambil merilis data dalam ViewModel sebagai gantinya. Dengan cara itu Anda tidak perlu berurusan dengan un / re-binding. Tampaknya lebih bersih daripada menggunakan metode tidak berdokumen untuk melepaskan ikatan secara manual langsung dari DOM. Selain itu, CleanNode bermasalah karena tidak merilis penangan acara apa pun (lihat jawabannya di sini untuk detail selengkapnya: stackoverflow.com/questions/15063794/… )
Zac
31

Untuk proyek yang sedang saya kerjakan, saya menulis ko.unapplyBindingsfungsi sederhana yang menerima node jQuery dan menghapus boolean. Ini pertama-tama melepaskan semua peristiwa jQuery karena ko.cleanNodemetode tidak menangani itu. Saya telah menguji kebocoran memori, dan tampaknya berfungsi dengan baik.

ko.unapplyBindings = function ($node, remove) {
    // unbind events
    $node.find("*").each(function () {
        $(this).unbind();
    });

    // Remove KO subscriptions and references
    if (remove) {
        ko.removeNode($node[0]);
    } else {
        ko.cleanNode($node[0]);
    }
};
Michael Berkompas
sumber
Hanya satu peringatan, saya belum menguji pengikatan ulang ke sesuatu yang baru saja ko.cleanNode()dipanggil dan tidak seluruh html diganti.
Michael Berkompas
4
bukankah solusi Anda juga akan melepaskan semua binding acara lainnya? apakah ada kemungkinan untuk hanya menghapus event handler ko?
lordvlad
tanpa mengubah inti ko yaitu
lordvlad
1
Benar, bagaimanapun itu tidak mungkin sejauh yang saya tahu tanpa perubahan inti. Lihat masalah yang saya kemukakan
Michael Berkompas
Bukankah gagasan KO bahwa Anda harus sangat jarang menyentuh dom sendiri? Jawaban ini melewati dom, dan pasti akan jauh dari tidak dapat digunakan dalam kasus penggunaan saya.
Blowsie
12

Anda dapat mencoba menggunakan with binding yang menawarkan knockout: http://knockoutjs.com/documentation/with-binding.html Idenya adalah menggunakan apply binding satu kali, dan setiap kali data Anda berubah, perbarui saja model Anda.

Katakanlah Anda memiliki model tampilan storeViewModel tingkat atas, keranjang Anda diwakili oleh cartViewModel, dan daftar item di keranjang itu - misalnya cartItemsViewModel.

Anda akan mengikat model tingkat atas - storeViewModel ke seluruh halaman. Kemudian, Anda dapat memisahkan bagian halaman Anda yang bertanggung jawab atas item keranjang atau keranjang.

Mari kita asumsikan bahwa cartItemsViewModel memiliki struktur berikut:

var actualCartItemsModel = { CartItems: [
  { ItemName: "FirstItem", Price: 12 }, 
  { ItemName: "SecondItem", Price: 10 }
] }

CartItemsViewModel bisa kosong di awal.

Langkah-langkahnya akan terlihat seperti ini:

  1. Tentukan binding di html. Pisahkan pengikatan cartItemsViewModel.

      
        <div data-bind="with: cartItemsViewModel">
          <div data-bind="foreach: CartItems">
            <span data-bind="text: ItemName"></span>
            <span data-bind="text: Price"></span>
          </div>
        </div>
      
    
  2. Model toko berasal dari server Anda (atau dibuat dengan cara lain).

    var storeViewModel = ko.mapping.fromJS(modelFromServer)

  3. Tentukan model kosong pada model tampilan tingkat atas Anda. Kemudian struktur model itu dapat diperbarui dengan data aktual.

      
        storeViewModel.cartItemsViewModel = ko.observable();
        storeViewModel.cartViewModel = ko.observable();
     
    
  4. Ikat model tampilan tingkat atas.

    ko.applyBindings(storeViewModel);

  5. Saat objek cartItemsViewModel tersedia, tetapkan ke placeholder yang ditentukan sebelumnya.

    storeViewModel.cartItemsViewModel(actualCartItemsModel);

Jika Anda ingin menghapus item keranjang: storeViewModel.cartItemsViewModel(null);

Knockout akan menangani html - yaitu akan muncul saat model tidak kosong dan konten div (yang memiliki "dengan pengikatan") akan hilang.

Sylwester Gryzio
sumber
9

Saya harus memanggil ko.applyBinding setiap kali klik tombol pencarian, dan data yang difilter dikembalikan dari server, dan dalam hal ini mengikuti pekerjaan untuk saya tanpa menggunakan ko.cleanNode.

Saya mengalami, jika kita mengganti foreach dengan template maka itu akan berfungsi dengan baik dalam kasus collections / observableArray.

Anda mungkin menemukan skenario ini berguna.

<ul data-bind="template: { name: 'template', foreach: Events }"></ul>

<script id="template" type="text/html">
    <li><span data-bind="text: Name"></span></li>
</script>
aamir sajjad
sumber
1
Saya telah menghabiskan setidaknya 4 jam mencoba menyelesaikan masalah serupa dan hanya solusi aamir yang berhasil untuk saya.
Antonin Jelinek
@AntoninJelinek hal lain yang saya alami dalam skenario saya, untuk menghapus html sepenuhnya, dan secara dinamis menambahkannya lagi untuk menghapus semuanya secara absolut. misalnya saya memiliki wadah kode Knockout div <div id = "knockoutContainerDiv"> </div> pada $ .Ajax Hasil Sukses yang saya lakukan mengikuti setiap kali metode server memanggil $ ("# knockoutContainerDiv"). children.remove (); / / hapus kontennya, panggil metode untuk menambahkan html dinamis dengan kode knockout $ ("# knockoutContainerDiv"). append ("childelements dengan kode pengikat knockout") dan panggil applyBinding lagi
aamir sajjad
1
Hei ammir, aku punya skenario yang hampir sama denganmu. Semua yang Anda sebutkan bekerja dengan baik kecuali kenyataan bahwa saya harus menggunakan ko.cleanNode (elemen); sebelum setiap pengikatan ulang.
Radoslav Minchev
@RadoslavMinchev menurut Anda apakah saya dapat membantu Anda lebih jauh, jika ya maka bagaimana, saya akan dengan senang hati berbagi pemikiran / pengalaman saya tentang pertanyaan tertentu.
aamir sajjad
Terima kasih. @aamirsajjad, hanya ingin menyebutkan bahwa yang berhasil bagi saya adalah memanggil fungsi cleanNode () untuk membuatnya berfungsi.
Radoslav Minchev
6

Alih-alih menggunakan fungsi internal KO dan berurusan dengan penghapusan penanganan peristiwa selimut JQuery, ide yang jauh lebih baik adalah menggunakan withatau templatemengikat. Saat Anda melakukan ini, ko membuat ulang bagian DOM tersebut dan secara otomatis dibersihkan. Ini juga cara yang disarankan, lihat di sini: https://stackoverflow.com/a/15069509/207661 .

Shital Shah
sumber
4

Saya pikir mungkin lebih baik untuk tetap mengikat sepanjang waktu, dan cukup perbarui data yang terkait dengannya. Saya mengalami masalah ini, dan menemukan bahwa hanya memanggil menggunakan .resetAll()metode pada larik tempat saya menyimpan data adalah cara paling efektif untuk melakukan ini.

Pada dasarnya Anda bisa memulai dengan beberapa var global yang berisi data yang akan dirender melalui ViewModel:

var myLiveData = ko.observableArray();

Butuh beberapa saat bagi saya untuk menyadari bahwa saya tidak bisa hanya membuat myLiveDataarray normal - ko.oberservableArraybagian itu penting.

Kemudian Anda dapat melanjutkan dan melakukan apa pun yang Anda inginkan myLiveData. Misalnya, lakukan $.getJSONpanggilan:

$.getJSON("http://foo.bar/data.json?callback=?", function(data) {
    myLiveData.removeAll();
    /* parse the JSON data however you want, get it into myLiveData, as below */
    myLiveData.push(data[0].foo);
    myLiveData.push(data[4].bar);
});

Setelah Anda selesai melakukannya, Anda dapat melanjutkan dan menerapkan binding menggunakan ViewModel seperti biasa:

function MyViewModel() {
    var self = this;
    self.myData = myLiveData;
};
ko.applyBindings(new MyViewModel());

Kemudian di HTML gunakan saja myDataseperti biasa.

Dengan cara ini, Anda bisa membuang myLiveData dari fungsi mana pun. Misalnya, jika Anda ingin memperbarui setiap beberapa detik, cukup gabungkan $.getJSONbaris itu dalam sebuah fungsi dan panggil setInterval. Anda tidak perlu melepas ikatan selama Anda ingat untuk tetap memasukkan myLiveData.removeAll();tali pengikat .

Kecuali data Anda sangat besar, pengguna bahkan tidak akan dapat memperhatikan waktu di antara menyetel ulang array dan kemudian menambahkan kembali data terbaru.

Zac
sumber
Sejak memposting pertanyaan, inilah yang sekarang saya lakukan. Hanya saja beberapa metode Knockout ini tidak berdokumen atau Anda benar-benar perlu melihat-lihat (sudah mengetahui nama fungsinya) untuk menemukan fungsinya.
awj
Saya harus mencari sangat keras untuk menemukan ini juga (ketika jumlah jam menggali dokumen melebihi jumlah baris kode yang dihasilkan ... wow). Senang Anda berhasil.
Zac
1

Pernahkah Anda memikirkan tentang ini:

try {
    ko.applyBindings(PersonListViewModel);
}
catch (err) {
    console.log(err.message);
}

Saya datang dengan ini karena di Knockout, saya menemukan kode ini

    var alreadyBound = ko.utils.domData.get(node, boundElementDomDataKey);
    if (!sourceBindings) {
        if (alreadyBound) {
            throw Error("You cannot apply bindings multiple times to the same element.");
        }
        ko.utils.domData.set(node, boundElementDomDataKey, true);
    }

Jadi bagi saya itu bukan masalah yang sudah terikat, itu adalah kesalahan yang tidak diketahui dan ditangani ...

ozzy432836.dll
sumber
0

Saya telah menemukan bahwa jika model tampilan berisi banyak pengikatan div, cara terbaik untuk menghapusnya ko.applyBindings(new someModelView);adalah dengan menggunakan: ko.cleanNode($("body")[0]);Ini memungkinkan Anda untuk memanggil model baru ko.applyBindings(new someModelView2);secara dinamis tanpa khawatir model tampilan sebelumnya masih terikat.

Derrick Hampton
sumber
4
Ada beberapa poin yang ingin saya tambahkan: (1) ini akan menghapus SEMUA binding dari halaman web Anda, yang mungkin sesuai dengan aplikasi Anda, tetapi saya membayangkan ada banyak aplikasi di mana binding telah ditambahkan ke beberapa bagian halaman untuk dipisahkan alasan. Mungkin tidak membantu bagi banyak pengguna untuk membersihkan SEMUA binding dalam satu perintah sweeping. (2) metode pengambilan JavaScript asli yang lebih cepat, lebih efisien, $("body")[0]adalah document.body.
awj