Làm cách nào để xóa / loại bỏ các ràng buộc có thể quan sát được trong Knockout.js?


113

Tôi đang xây dựng chức năng trên một trang web mà người dùng có thể thực hiện nhiều lần. Thông qua hành động của người dùng, một đối tượng / mô hình được tạo và áp dụng cho HTML bằng cách sử dụng ko.applyBindings ().

HTML ràng buộc dữ liệu được tạo thông qua các mẫu jQuery.

Càng xa càng tốt.

Khi tôi lặp lại bước này bằng cách tạo đối tượng / mô hình thứ hai và gọi ko.applyBindings (), tôi gặp hai vấn đề:

  1. Đánh dấu hiển thị đối tượng / mô hình trước đó cũng như đối tượng / mô hình mới.
  2. Lỗi javascript xảy ra liên quan đến một trong các thuộc tính trong đối tượng / mô hình, mặc dù nó vẫn được hiển thị trong đánh dấu.

Để giải quyết vấn đề này, sau lần vượt qua đầu tiên, tôi gọi .empty () của jQuery để loại bỏ HTML mẫu chứa tất cả các thuộc tính data-bind, để nó không còn trong DOM. Khi người dùng bắt đầu quy trình cho lần chuyển thứ hai, HTML liên kết dữ liệu sẽ được thêm lại vào DOM.

Nhưng như tôi đã nói, khi HTML được thêm lại vào DOM và liên kết lại với đối tượng / mô hình mới, nó vẫn bao gồm dữ liệu từ đối tượng / mô hình đầu tiên và tôi vẫn gặp lỗi JS không xảy ra. trong lần vượt qua đầu tiên.

Kết luận dường như là Knockout đang giữ các thuộc tính liên kết này, mặc dù đánh dấu đã bị xóa khỏi DOM.

Vì vậy, những gì tôi đang tìm kiếm là một phương tiện loại bỏ các thuộc tính bị ràng buộc này khỏi Knockout; nói với knockout rằng không còn một mô hình có thể quan sát được nữa. Có cách nào để làm việc này không?

BIÊN TẬP

Quá trình cơ bản là người dùng tải lên một tệp; sau đó máy chủ phản hồi với một đối tượng JSON, HTML liên kết dữ liệu được thêm vào DOM, sau đó mô hình đối tượng JSON được liên kết với HTML này bằng cách sử dụng

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

Khi người dùng đã thực hiện một số lựa chọn trên mô hình, cùng một đối tượng được đăng trở lại máy chủ, HTML liên kết dữ liệu sẽ bị xóa khỏi DOM và sau đó tôi có JS sau

mn.AccountCreationModel = null;

Khi người dùng muốn làm điều này một lần nữa, tất cả các bước này được lặp lại.

Tôi e rằng mã quá 'liên quan' để thực hiện một bản demo jsFiddle.


Không nên gọi ko.applyBindings nhiều lần, đặc biệt là trên cùng một phần tử chứa dom. Có thể có một cách khác để đạt được những gì bạn muốn. Tuy nhiên, bạn sẽ cần cung cấp thêm mã. Vui lòng bao gồm jsfiddle nếu có thể.
madcapnmckay

Tại sao không hiển thị một inithàm mà bạn truyền dữ liệu sẽ được áp dụng?
KyorCode

Câu trả lời:


169

Bạn đã thử gọi phương thức nút sạch của knockout trên phần tử DOM của mình để loại bỏ các đối tượng bị ràng buộc trong bộ nhớ chưa?

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

Sau đó, việc áp dụng lại các ràng buộc loại trực tiếp chỉ trên phần tử đó với các mô hình chế độ xem mới của bạn sẽ cập nhật liên kết chế độ xem của bạn.


33
Điều này hoạt động - cảm ơn. Tuy nhiên, tôi không thể tìm thấy bất kỳ tài liệu nào về phương pháp này.
awj

Tôi cũng đã tìm kiếm tài liệu về chức năng tiện ích loại trực tiếp này. Gần như tôi có thể biết từ mã nguồn, nó đang gọi deletemột số khóa nhất định trên chính các phần tử dom, đó rõ ràng là nơi lưu trữ tất cả ma thuật loại trực tiếp. Nếu ai đó có nguồn về tài liệu, tôi sẽ rất có trách nhiệm.
Patrick M

2
Bạn sẽ không tìm thấy nó. Tôi đã tìm kiếm tài liệu cao và thấp cho các chức năng của tiện ích không, nhưng không có tài liệu nào tồn tại. Bài đăng trên blog này là thứ gần nhất bạn sẽ tìm thấy, nhưng nó chỉ bao gồm các thành viên của ko.utils: knockmeout.net/2011/04/utility-functions-in-knockoutjs.html
Nick Daniels

1
Bạn cũng sẽ muốn xóa các sự kiện theo cách thủ công như được hiển thị trong câu trả lời của tôi bên dưới.
Michael Berkompas

1
@KodeKreachor Tôi đã đăng ví dụ làm việc bên dưới. Quan điểm của tôi là tốt hơn nên giữ nguyên ràng buộc, trong khi giải phóng dữ liệu trong ViewModel. Bằng cách đó, bạn không bao giờ phải đối phó với việc hủy / tái ràng buộc. Nó có vẻ gọn gàng hơn so với việc sử dụng các phương pháp không có tài liệu để hủy liên kết thủ công trực tiếp từ DOM. Ngoài ra, CleanNode có vấn đề vì nó không phát hành bất kỳ trình xử lý sự kiện nào (xem câu trả lời tại đây để biết thêm chi tiết: stackoverflow.com/questions/15063794/… )
Zac

31

Đối với một dự án tôi đang thực hiện, tôi đã viết một ko.unapplyBindingshàm đơn giản chấp nhận một nút jQuery và loại bỏ boolean. Đầu tiên, nó bỏ liên kết tất cả các sự kiện jQuery vì ko.cleanNodephương thức không xử lý điều đó. Tôi đã kiểm tra rò rỉ bộ nhớ và nó dường như hoạt động tốt.

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

Chỉ một lời cảnh báo, tôi chưa thử nghiệm liên kết lại với thứ gì đó vừa được ko.cleanNode()gọi và không phải toàn bộ html được thay thế.
Michael Berkompas

4
giải pháp của bạn sẽ không liên kết tất cả các ràng buộc sự kiện khác? Có khả năng chỉ loại bỏ các trình xử lý sự kiện của ko?
Lordvlad

mà không thay đổi cốt lõi ko được
lordvlad

1
Đúng, tuy nhiên điều đó là không thể theo tôi có thể nói nếu không có sự thay đổi cốt lõi. Xem vấn đề này tôi đã đưa ra ở đây: github.com/SteveSanderson/knockout/issues/724
Michael Berkompas

Ý của KO rằng bạn rất hiếm khi được tự mình chạm vào con chó đó sao? Câu trả lời này lặp lại qua dom, và chắc chắn sẽ không thể sử dụng được trong trường hợp sử dụng của tôi.
Blowsie

12

Bạn có thể thử sử dụng với ràng buộc mà knockout cung cấp: http://knockoutjs.com/documentation/with-binding.html Ý tưởng là sử dụng liên kết áp dụng một lần và bất cứ khi nào dữ liệu của bạn thay đổi, chỉ cần cập nhật mô hình của bạn.

Giả sử bạn có một cửa hàng mô hình chế độ xem cấp cao nhấtViewModel, giỏ hàng của bạn được đại diện bởi cartViewModel và danh sách các mặt hàng trong giỏ hàng đó - giả sử cartItemsViewModel.

Bạn sẽ liên kết mô hình cấp cao nhất - storeViewModel với toàn bộ trang. Sau đó, bạn có thể tách các phần của trang chịu trách nhiệm về các mặt hàng trong giỏ hàng hoặc giỏ hàng.

Giả sử rằng cartItemsViewModel có cấu trúc sau:

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

CartItemsViewModel có thể trống ở phần đầu.

Các bước sẽ giống như sau:

  1. Xác định các ràng buộc trong html. Tách ràng buộc 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. Mô hình cửa hàng đến từ máy chủ của bạn (hoặc được tạo theo bất kỳ cách nào khác).

    var storeViewModel = ko.mapping.fromJS(modelFromServer)

  3. Xác định các mô hình trống trên mô hình chế độ xem cấp cao nhất của bạn. Sau đó, một cấu trúc của mô hình đó có thể được cập nhật với dữ liệu thực tế.

      
        storeViewModel.cartItemsViewModel = ko.observable();
        storeViewModel.cartViewModel = ko.observable();
     
    
  4. Ràng buộc mô hình chế độ xem cấp cao nhất.

    ko.applyBindings(storeViewModel);

  5. Khi đối tượng cartItemsViewModel có sẵn thì hãy gán nó cho trình giữ chỗ đã xác định trước đó.

    storeViewModel.cartItemsViewModel(actualCartItemsModel);

Nếu bạn muốn xóa các mặt hàng trong giỏ hàng: storeViewModel.cartItemsViewModel(null);

Knockout sẽ quản lý html - tức là nó sẽ xuất hiện khi mô hình không trống và nội dung của div (cái có "with binding") sẽ biến mất.


9

Tôi phải gọi ko.applyBinding mỗi khi nhấp vào nút tìm kiếm và dữ liệu đã lọc được trả về từ máy chủ và trong trường hợp này, cách làm sau hoạt động đối với tôi mà không cần sử dụng ko.cleanNode.

Tôi có kinh nghiệm, nếu chúng tôi thay thế foreach bằng template thì nó sẽ hoạt động tốt trong trường hợp tập hợp / ObservableArray.

Bạn có thể thấy tình huống này hữu ích.

<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
Tôi đã dành ít nhất 4 giờ để giải quyết vấn đề tương tự và chỉ có giải pháp của aamir phù hợp với tôi.
Antonin Jelinek

@AntoninJelinek một điều khác mà tôi đã trải qua trong kịch bản của mình, đó là xóa hoàn toàn html và nối thêm một lần nữa để xóa hoàn toàn mọi thứ. ví dụ: tôi có vùng chứa mã Knockout div <div id = "knockoutContainerDiv"> </div> trên $ .Ajax Kết quả thành công Tôi thực hiện theo sau mỗi lần phương thức máy chủ gọi $ ("# knockoutContainerDiv"). children.remove (); / / remove nội dung của nó gọi phương thức nối html động với mã loại bỏ $ ("# knockoutContainerDiv"). append ("các thành phần con có mã ràng buộc loại trực tiếp") và gọi lại applyBinding
aamir sajjad

1
Này bạn, tôi đã có khá nhiều tình huống giống như bạn. Mọi thứ bạn đề cập đều hoạt động tốt ngoại trừ việc tôi phải sử dụng ko.cleanNode (element); trước mọi ràng buộc lại.
Radoslav Minchev

@RadoslavMinchev bạn có nghĩ rằng tôi có thể giúp bạn thêm không, nếu có thì làm thế nào, tôi rất vui được chia sẻ suy nghĩ / kinh nghiệm của mình về câu hỏi cụ thể.
aamir Sajjad

Cám ơn. @aamirsajjad, tôi chỉ muốn đề cập rằng điều hiệu quả với tôi là gọi hàm cleanNode () để làm cho nó hoạt động.
Radoslav Minchev

6

Thay vì sử dụng các chức năng bên trong của KO và xử lý việc loại bỏ trình xử lý sự kiện tổng hợp của JQuery, một ý tưởng tốt hơn là sử dụng withhoặc templatecác ràng buộc. Khi bạn làm điều này, không tạo lại phần đó của DOM và do đó nó tự động được làm sạch. Đây cũng là cách được khuyến khích, xem tại đây: https://stackoverflow.com/a/15069509/207661 .


4

Tôi nghĩ có thể tốt hơn nếu giữ nguyên thời gian ràng buộc và chỉ cần cập nhật dữ liệu liên quan đến nó. Tôi gặp phải vấn đề này và nhận thấy rằng chỉ gọi bằng .resetAll()phương thức trên mảng mà tôi đang giữ dữ liệu của mình là cách hiệu quả nhất để thực hiện việc này.

Về cơ bản, bạn có thể bắt đầu với một số var toàn cục chứa dữ liệu được hiển thị thông qua ViewModel:

var myLiveData = ko.observableArray();

Tôi mất một lúc để nhận ra rằng tôi không thể chỉ tạo myLiveDatamột mảng bình thường - ko.oberservableArrayphần này rất quan trọng.

Sau đó, bạn có thể tiếp tục và làm bất cứ điều gì bạn muốn myLiveData. Ví dụ: thực hiện $.getJSONcuộc gọi:

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

Khi bạn đã hoàn tất việc này, bạn có thể tiếp tục và áp dụng các ràng buộc bằng ViewModel của mình như bình thường:

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

Sau đó, trong HTML, chỉ cần sử dụng myDatanhư bình thường.

Bằng cách này, bạn có thể kết hợp với myLiveData từ bất kỳ chức năng nào. Ví dụ: nếu bạn muốn cập nhật vài giây một lần, chỉ cần bọc $.getJSONdòng đó trong một hàm và gọi setIntervalnó. Bạn sẽ không bao giờ cần gỡ bỏ ràng buộc miễn là bạn nhớ giữ nguyên myLiveData.removeAll();dòng.

Trừ khi dữ liệu của bạn thực sự lớn, người dùng thậm chí sẽ không thể nhận thấy thời gian giữa việc đặt lại mảng và sau đó thêm lại dữ liệu mới nhất.


Trong thời gian kể từ khi đăng câu hỏi, đây là những gì tôi làm. Chỉ là một số phương thức Knockout này không có tài liệu hoặc bạn thực sự cần phải xem xét xung quanh (đã biết tên hàm) để tìm những gì nó hoạt động.
awj

Tôi cũng đã phải rất vất vả để tìm ra điều này (khi số giờ đào tài liệu vượt quá số dòng mã kết quả ... wow). Tôi vui vì bạn đã làm nó hoạt động.
Zac


1

Bạn đã nghĩ về điều này chưa:

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

Tôi nghĩ ra điều này vì trong Knockout, tôi đã tìm thấy mã này

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

Vì vậy, đối với tôi, nó không thực sự là một vấn đề mà nó đã bị ràng buộc, nó là lỗi không được phát hiện và xử lý ...


0

Tôi nhận thấy rằng nếu mô hình xem chứa nhiều liên kết div thì cách tốt nhất để xóa ko.applyBindings(new someModelView);là sử dụng: ko.cleanNode($("body")[0]);Điều này cho phép bạn gọi ko.applyBindings(new someModelView2);động mới mà không phải lo lắng về mô hình xem trước đó vẫn bị liên kết.


4
Có một số điểm tôi muốn bổ sung: (1) điều này sẽ xóa TẤT CẢ các ràng buộc khỏi trang web của bạn, điều này có thể phù hợp với ứng dụng của bạn nhưng tôi tưởng tượng có rất nhiều ứng dụng mà các ràng buộc đã được thêm vào nhiều phần của trang để tách biệt lý do. Việc xóa TẤT CẢ các ràng buộc trong một lệnh quét duy nhất có thể không hữu ích đối với nhiều người dùng. (2), hiệu quả hơn, phương thức JavaScript mẹ đẻ nhanh của nhặt đồ $("body")[0]document.body.
awj
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.