จะล้าง / ลบการผูกที่สังเกตได้ใน Knockout.js ได้อย่างไร


113

ฉันกำลังสร้างฟังก์ชันการทำงานบนหน้าเว็บซึ่งผู้ใช้สามารถดำเนินการได้หลายครั้ง ผ่านการกระทำของผู้ใช้อ็อบเจ็กต์ / โมเดลถูกสร้างขึ้นและนำไปใช้กับ HTML โดยใช้ ko.applyBindings ()

HTML ที่ผูกข้อมูลถูกสร้างขึ้นผ่านเทมเพลต jQuery

จนถึงตอนนี้ดีมาก

เมื่อฉันทำขั้นตอนนี้ซ้ำโดยสร้างวัตถุ / โมเดลที่สองและโทรหา ko.applyBindings () ฉันพบปัญหาสองประการ:

  1. มาร์กอัปจะแสดงวัตถุ / โมเดลก่อนหน้าตลอดจนวัตถุ / โมเดลใหม่
  2. ข้อผิดพลาดของจาวาสคริปต์เกิดขึ้นที่เกี่ยวข้องกับคุณสมบัติอย่างใดอย่างหนึ่งในวัตถุ / โมเดลแม้ว่าจะยังแสดงผลในมาร์กอัป

เพื่อแก้ไขปัญหานี้หลังจากผ่านครั้งแรกฉันเรียก jQuery ของ. empty () เพื่อลบ HTML ที่มีเทมเพลตซึ่งมีแอตทริบิวต์การผูกข้อมูลทั้งหมดเพื่อไม่ให้อยู่ใน DOM อีกต่อไป เมื่อผู้ใช้เริ่มกระบวนการสำหรับการส่งครั้งที่สอง HTML ที่ผูกข้อมูลจะถูกเพิ่มเข้าไปใน DOM อีกครั้ง

แต่อย่างที่ฉันพูดไปเมื่อ HTML ถูกเพิ่มเข้าไปใน DOM อีกครั้งและเชื่อมโยงกับอ็อบเจ็กต์ / โมเดลใหม่มันยังคงรวมข้อมูลจากอ็อบเจ็กต์ / โมเดลแรกและฉันยังคงได้รับข้อผิดพลาด JS ซึ่งไม่เกิดขึ้น ในช่วงแรก

ข้อสรุปดูเหมือนว่า Knockout ยึดคุณสมบัติที่ถูกผูกไว้เหล่านี้แม้ว่ามาร์กอัปจะถูกลบออกจาก DOM

ดังนั้นสิ่งที่ฉันกำลังมองหาคือวิธีการลบคุณสมบัติที่ถูกผูกไว้เหล่านี้ออกจาก Knockout บอกสิ่งที่น่าพิศวงว่าไม่มีโมเดลที่สังเกตได้อีกต่อไป มีวิธีทำไหม?

แก้ไข

กระบวนการพื้นฐานคือผู้ใช้อัปโหลดไฟล์ จากนั้นเซิร์ฟเวอร์ตอบสนองด้วยออบเจ็กต์ JSON HTML ที่ผูกข้อมูลจะถูกเพิ่มไปยัง DOM จากนั้นโมเดลอ็อบเจ็กต์ JSON จะถูกผูกไว้กับ HTML นี้โดยใช้

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

เมื่อผู้ใช้ทำการเลือกบางอย่างในโมเดลวัตถุเดียวกันจะถูกโพสต์กลับไปที่เซิร์ฟเวอร์ HTML ที่ผูกข้อมูลจะถูกลบออกจาก DOM จากนั้นฉันก็มี JS ต่อไปนี้

mn.AccountCreationModel = null;

เมื่อผู้ใช้ต้องการทำอีกครั้งขั้นตอนเหล่านี้จะทำซ้ำ

ฉันกลัวว่าโค้ดจะ 'เกี่ยวข้อง' เกินไปที่จะทำการสาธิต jsFiddle


ไม่แนะนำให้เรียก ko.applyBindings หลาย ๆ ครั้งโดยเฉพาะอย่างยิ่งในองค์ประกอบ Dom ที่มีองค์ประกอบเดียวกัน อาจมีวิธีอื่นในการบรรลุสิ่งที่คุณต้องการ คุณจะต้องระบุรหัสเพิ่มเติม กรุณาใส่ jsfiddle ถ้าเป็นไปได้
madcapnmckay

ทำไมไม่เปิดเผยinitฟังก์ชันที่คุณส่งผ่านข้อมูลไปใช้?
KyorCode

คำตอบ:


169

คุณได้ลองเรียกเมธอด clean node ของสิ่งที่น่าพิศวงบนองค์ประกอบ DOM ของคุณเพื่อกำจัดวัตถุที่ถูกผูกไว้ในหน่วยความจำหรือไม่?

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

จากนั้นใช้การผูกสิ่งที่น่าพิศวงอีกครั้งกับองค์ประกอบนั้นกับโมเดลมุมมองใหม่ของคุณจะอัปเดตการเชื่อมมุมมองของคุณ


33
ใช้งานได้ - ขอบคุณ ฉันไม่พบเอกสารใด ๆ เกี่ยวกับวิธีนี้
awj

ฉันก็ค้นหาเอกสารเกี่ยวกับฟังก์ชันยูทิลิตี้ที่น่าพิศวงนี้เช่นกัน ใกล้เคียงที่สุดเท่าที่ฉันสามารถบอกได้จากซอร์สโค้ดมันกำลังเรียกdeleteใช้คีย์บางอย่างบนองค์ประกอบ dom ซึ่งเห็นได้ชัดว่าเป็นที่เก็บเวทมนตร์ที่น่าพิศวงทั้งหมด หากใครมีแหล่งข้อมูลเกี่ยวกับเอกสารฉันจะต้องรับผิดชอบมาก
Patrick M

2
คุณจะไม่พบมัน ฉันได้ค้นหาเอกสารในฟังก์ชันยูทิลิตี้ ko สูงและต่ำ แต่ไม่มีอยู่ โพสต์บล็อกนี้เป็นสิ่งที่ใกล้เคียงที่สุดที่คุณจะพบ แต่ครอบคลุมเฉพาะสมาชิกของ ko.utils: knockmeout.net/2011/04/utility-functions-in-knockoutjs.html
Nick Daniels

1
คุณจะต้องลบเหตุการณ์ด้วยตนเองตามที่แสดงในคำตอบด้านล่าง
Michael Berkompas

1
@KodeKreachor ฉันได้โพสต์ตัวอย่างการทำงานไว้ด้านล่างแล้ว ประเด็นของฉันคือมันอาจจะดีกว่าที่จะทำให้การเชื่อมต่อเหมือนเดิมในขณะที่ปล่อยข้อมูลภายใน ViewModel แทน ด้วยวิธีนี้คุณจะไม่ต้องจัดการกับการยกเลิก / ผูกใหม่ ดูเหมือนว่าจะสะอาดกว่าการใช้วิธีการที่ไม่มีเอกสารในการเลิกผูกโดยตรงจาก DOM ด้วยตนเอง นอกเหนือจากนั้น CleanNode ยังมีปัญหาเนื่องจากไม่ปล่อยตัวจัดการเหตุการณ์ใด ๆ (ดูคำตอบที่นี่สำหรับรายละเอียดเพิ่มเติม: stackoverflow.com/questions/15063794/… )
Zac

31

สำหรับโปรเจ็กต์ที่ฉันกำลังทำอยู่ฉันเขียนko.unapplyBindingsฟังก์ชันง่ายๆที่ยอมรับโหนด jQuery และบูลีนลบ ก่อนอื่นให้ยกเลิกการผูกเหตุการณ์ jQuery ทั้งหมดเนื่องจากko.cleanNodeวิธีการไม่ดูแลสิ่งนั้น ฉันได้ทดสอบการรั่วไหลของหน่วยความจำแล้วและดูเหมือนว่าจะใช้งานได้ดี

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

มีเพียงข้อแม้เดียวฉันยังไม่ได้ทดสอบการเชื่อมโยงซ้ำกับสิ่งที่เพิ่งko.cleanNode()เรียกและไม่ได้แทนที่ html ทั้งหมด
Michael Berkompas

4
โซลูชันของคุณจะไม่ยกเลิกการผูกเหตุการณ์อื่น ๆ ทั้งหมดด้วยหรือไม่? มีความเป็นไปได้ที่จะลบเฉพาะตัวจัดการเหตุการณ์ของ ko หรือไม่?
lordvlad

โดยไม่ต้องดัดแปลง ko core นั่นคือ
lordvlad

1
จริงอย่างไรก็ตามมันเป็นไปไม่ได้เท่าที่ฉันสามารถบอกได้หากไม่มีการเปลี่ยนแปลงหลัก ดูปัญหานี้ที่ฉันนำเสนอที่นี่: github.com/SteveSanderson/knockout/issues/724
Michael Berkompas

ไม่ใช่ความคิดของ KO ที่คุณไม่ค่อยควรจะสัมผัสกับโดมด้วยตัวเอง? คำตอบนี้วนลูปผ่านโดมและแน่นอนว่าจะไม่สามารถใช้งานได้ในกรณีการใช้งานของฉัน
Blowsie

12

คุณสามารถลองใช้ด้วยการผูกข้อเสนอที่น่าพิศวง: http://knockoutjs.com/documentation/with-binding.html แนวคิดคือใช้การผูกครั้งเดียวและเมื่อใดก็ตามที่ข้อมูลของคุณเปลี่ยนแปลงเพียงอัปเดตโมเดลของคุณ

สมมติว่าคุณมีมุมมองระดับบนสุด storeViewModel รถเข็นของคุณแสดงโดย cartViewModel และรายการสินค้าในรถเข็นนั้น - พูดว่า cartItemsViewModel

คุณจะผูกโมเดลระดับบนสุด - storeViewModel เข้ากับทั้งเพจ จากนั้นคุณสามารถแยกส่วนต่างๆของเพจของคุณที่รับผิดชอบในรถเข็นหรือสินค้าในรถเข็นได้

สมมติว่า cartItemsViewModel มีโครงสร้างดังต่อไปนี้:

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

cartItemsViewModel สามารถว่างได้ในตอนต้น

ขั้นตอนจะมีลักษณะดังนี้:

  1. กำหนดการผูกใน html แยกการผูก 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. โมเดลร้านค้ามาจากเซิร์ฟเวอร์ของคุณ (หรือสร้างด้วยวิธีอื่นใด)

    var storeViewModel = ko.mapping.fromJS(modelFromServer)

  3. กำหนดโมเดลว่างบนโมเดลมุมมองระดับบนสุดของคุณ จากนั้นโครงสร้างของแบบจำลองนั้นสามารถอัปเดตด้วยข้อมูลจริง

      
        storeViewModel.cartItemsViewModel = ko.observable();
        storeViewModel.cartViewModel = ko.observable();
     
    
  4. ผูกโมเดลมุมมองระดับบนสุด

    ko.applyBindings(storeViewModel);

  5. เมื่ออ็อบเจ็กต์ cartItemsViewModel พร้อมใช้งานแล้วกำหนดให้กับตัวยึดตำแหน่งที่กำหนดไว้ก่อนหน้านี้

    storeViewModel.cartItemsViewModel(actualCartItemsModel);

หากคุณต้องการล้างรายการในรถเข็น: storeViewModel.cartItemsViewModel(null);

สิ่งที่น่าพิศวงจะดูแล html - กล่าวคือจะปรากฏขึ้นเมื่อโมเดลไม่ว่างเปล่าและเนื้อหาของ div (อันที่มี "มีการผูก") จะหายไป


9

ฉันต้องโทรหา ko.applyBinding ทุกครั้งที่คลิกปุ่มค้นหาและข้อมูลที่ถูกกรองจะถูกส่งคืนจากเซิร์ฟเวอร์และในกรณีนี้จะทำงานให้ฉันต่อไปโดยไม่ต้องใช้ ko.cleanNode

ฉันมีประสบการณ์ถ้าเราแทนที่ foreach ด้วยเทมเพลตมันควรจะทำงานได้ดีในกรณีของคอลเลกชัน / observableArray

คุณอาจพบว่าสถานการณ์นี้มีประโยชน์

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

<script id="template" type="text/html">
    <li><span data-bind="text: Name"></span></li>
</script>

1
ฉันใช้เวลาอย่างน้อย 4 ชั่วโมงในการพยายามแก้ไขปัญหาที่คล้ายกันและมีเพียงวิธีแก้ปัญหาของ aamir เท่านั้นที่ใช้ได้กับฉัน
Antonin Jelinek

@AntoninJelinek สิ่งอื่น ๆ ที่ฉันพบในสถานการณ์ของฉันเพื่อลบ html อย่างสมบูรณ์และมีพลวัตต่อท้ายอีกครั้งเพื่อลบทุกอย่างอย่างสมบูรณ์ ตัวอย่างเช่นฉันมีคอนเทนเนอร์รหัสสิ่งที่น่าพิศวง div <div id = "knockoutContainerDiv"> </div> บน $ .Ajax ผลสำเร็จฉันทำตามทุกครั้งที่วิธีการเซิร์ฟเวอร์เรียก $ ("# knockoutContainerDiv") children.remove (); / / ลบเนื้อหาเรียกวิธีการต่อท้าย html แบบไดนามิกด้วยรหัสสิ่งที่น่าพิศวง $ ("# knockoutContainerDiv") ผนวก ("childelements with knockout binding code") และเรียกใช้ applyBinding อีกครั้ง
aamir sajjad

1
เฮ้ฉันมีสถานการณ์เดียวกันกับคุณ ทุกอย่างที่คุณกล่าวถึงทำงานได้ดียกเว้นความจริงที่ว่าฉันต้องใช้ ko.cleanNode (องค์ประกอบ); ก่อนการผูกใหม่ทุกครั้ง
Radoslav Minchev

@RadoslavMinchev คุณคิดว่าฉันสามารถช่วยคุณเพิ่มเติมได้หรือไม่ถ้าใช่ฉันยินดีที่จะแบ่งปันความคิด / ประสบการณ์ของฉันเกี่ยวกับคำถามเฉพาะ
aamir sajjad

ขอบคุณ. @aamirsajjad แค่อยากจะบอกว่าสิ่งที่ได้ผลสำหรับฉันคือการเรียกใช้ฟังก์ชัน cleanNode () เพื่อให้มันใช้งานได้
Radoslav Minchev

6

แทนที่จะใช้ฟังก์ชันภายในของ KO และจัดการกับการลบตัวจัดการเหตุการณ์ผ้าห่มของ JQuery แนวคิดที่ดีกว่าคือการใช้withหรือtemplateการผูก เมื่อคุณทำเช่นนี้ ko จะสร้างส่วนนั้นของ DOM ขึ้นมาใหม่และทำความสะอาดโดยอัตโนมัติ นี้ยังเป็นวิธีที่แนะนำให้ดูที่นี่: https://stackoverflow.com/a/15069509/207661


4

ฉันคิดว่ามันอาจจะดีกว่าที่จะรักษาความผูกพันไว้ตลอดเวลาและเพียงอัปเดตข้อมูลที่เกี่ยวข้อง ฉันพบปัญหานี้และพบว่าการโทรโดยใช้.resetAll()เมธอดบนอาร์เรย์ที่ฉันเก็บข้อมูลไว้เป็นวิธีที่มีประสิทธิภาพที่สุดในการดำเนินการนี้

โดยทั่วไปคุณสามารถเริ่มต้นด้วย global var ที่มีข้อมูลที่จะแสดงผลผ่าน ViewModel:

var myLiveData = ko.observableArray();

ฉันใช้เวลาสักพักกว่าจะรู้ว่าฉันไม่สามารถสร้างmyLiveDataอาร์เรย์ปกติได้ - ko.oberservableArrayส่วนนี้สำคัญ

myLiveDataจากนั้นคุณสามารถไปข้างหน้าและทำสิ่งที่คุณต้องการ ตัวอย่างเช่น$.getJSONโทร:

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

เมื่อคุณทำเสร็จแล้วคุณสามารถดำเนินการต่อและใช้การเชื่อมโยงโดยใช้ ViewModel ของคุณได้ตามปกติ:

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

จากนั้นใน HTML ให้ใช้myDataตามปกติ

วิธีนี้คุณสามารถโคลนกับ myLiveData จากฟังก์ชันใดก็ได้ ตัวอย่างเช่นหากคุณต้องการอัปเดตทุกๆสองสามวินาทีเพียงแค่ตัด$.getJSONบรรทัดนั้นในฟังก์ชันแล้วเรียกsetIntervalใช้ คุณไม่จำเป็นต้องนำการผูกออกตราบเท่าที่คุณจำไว้ว่าให้เข้าmyLiveData.removeAll();แถว

เว้นแต่ว่าข้อมูลของคุณจะมีขนาดใหญ่มากผู้ใช้จะไม่สามารถสังเกตเห็นเวลาระหว่างการรีเซ็ตอาร์เรย์และเพิ่มข้อมูลที่เป็นปัจจุบันที่สุด


ตั้งแต่โพสต์คำถามนี่คือสิ่งที่ฉันทำตอนนี้ เป็นเพียงว่าวิธีการ Knockout เหล่านี้บางส่วนอาจไม่มีเอกสารหรือคุณจำเป็นต้องมองไปรอบ ๆ (รู้ชื่อฟังก์ชันอยู่แล้ว) เพื่อค้นหาว่ามันทำอะไร
awj

ฉันต้องหาสิ่งนี้ยากมากเช่นกัน (เมื่อจำนวนชั่วโมงในการขุดเอกสารเกินจำนวนบรรทัดของโค้ดที่เป็นผลลัพธ์ ... ว้าว) ดีใจที่คุณใช้งานได้
Zac

2

เมื่อเร็ว ๆ นี้ฉันมีปัญหาหน่วยความจำรั่วและko.cleanNode(element);จะไม่ทำเพื่อฉัน - ko.removeNode(element);ทำ Javascript + Knockout.js หน่วยความจำรั่ว - จะตรวจสอบได้อย่างไรว่าอ็อบเจ็กต์ถูกทำลาย


ในสิ่งที่น่าพิศวง 3.1 ko.removeNode เรียก ko.cleanNode ฉันไม่รู้ว่าเป็นเช่นนั้นสำหรับเวอร์ชันก่อนหน้านี้
kiprainey

1

คุณคิดเกี่ยวกับสิ่งนี้หรือไม่:

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

ฉันคิดสิ่งนี้ขึ้นมาเพราะใน Knockout ฉันพบรหัสนี้

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

สำหรับฉันแล้วมันไม่ใช่ปัญหาที่มันถูกผูกไว้แล้ว แต่ข้อผิดพลาดไม่ได้ถูกจับและจัดการกับ ...


0

ฉันพบว่าหากโมเดลมุมมองมีการเชื่อมโยง div จำนวนมากวิธีที่ดีที่สุดในการล้างko.applyBindings(new someModelView);คือการใช้: ko.cleanNode($("body")[0]);สิ่งนี้ช่วยให้คุณสามารถเรียกใหม่ko.applyBindings(new someModelView2);แบบไดนามิกโดยไม่ต้องกังวลว่าโมเดลมุมมองก่อนหน้ายังคงถูกผูกอยู่


4
มีสองสามจุดที่ฉันต้องการเพิ่ม: (1) สิ่งนี้จะล้างการผูกทั้งหมดออกจากหน้าเว็บของคุณซึ่งอาจเหมาะกับแอปพลิเคชันของคุณ แต่ฉันคิดว่ามีแอปพลิเคชั่นมากมายที่มีการเพิ่มการผูกลงในหลายส่วนของหน้าเพื่อแยกกัน เหตุผล อาจไม่เป็นประโยชน์สำหรับผู้ใช้จำนวนมากในการล้างการเชื่อมโยงทั้งหมดในคำสั่งเดียวแบบกวาด (2) ได้เร็วและมีประสิทธิภาพมากขึ้นวิธี JavaScript พื้นเมืองเรียกเป็น$("body")[0] document.body
AWJ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.