Knockout Mapping re-rendering everything

ぐ巨炮叔叔 提交于 2019-12-10 11:56:17

问题


I'm using the Knockout mapping plug-in to refresh the UI with JSON retrieved from the server every 3 seconds. The UI consists of some nested foreach bindings. However, it seems that everything in all the foreach bindings are getting completely deleted and re-rendered with every refresh, even when nothing has changed.

var testData = {
    Answers: [],
    Inspectable: {
        Categories: [{
            Id: 1,
            Name: "Test Category",
            Questions: [{
                Id: 1,
                Text: "Test Question",
                Active: true,
                Answers: [{
                    Text: "Test Answer",
                    Id: 1
                }]
            }]
        }]
    }
};

function ViewModel() {

    var self = this;

    this.refreshUrl = $("[data-view=edit]").data("url");

    this.refresh = function(callback) {
        $.get(self.refreshUrl, function(data) {
            //Ignoring actual JSON data for testing
            ko.mapping.fromJS(testData, {}, self);
            if (typeof callback == "function") {
                callback.call(self);
            }
        });
    }

    this.addedQuestion = function() {
        // Gets called for every question every refresh by afterRender
        // Never gets called at all by afterAdd
    }

};

var refreshing = false, handler;
window.viewModel = new ViewModel();

//Initialize the UI after initial AJAX is completed
viewModel.refresh(function() {

    ko.applyBindings(this);

        $(document).on("click", ".add-question", function() {
        if (!refreshing) {
            handler = setInterval(viewModel.refresh, 3000);
            refreshing = true;
        }
    });
});

Does anyone see anything glaringly wrong with this?

EDIT

I edited the script to use a static JavaScript object. It still re-renders every refresh. Also updated to Knockout 2.3.0. Here's the view:

    <!-- ko foreach: Inspectable.Categories -->
        <div class="row row-fluid space-above">
            <h4 class="orange" data-bind="text: Name"></h4>
            <!-- ko foreach: { data: Questions, afterRender: $root.addedQuestion } -->
                <!-- ko if: Active() || ~$.map($root.Answers(), function(a) { return a.Id() == Id() }) -->
                    <div class="question space-above">
                        <p><strong data-bind="text: Text"></strong></p>
                        <div class="answers" data-bind="foreach: Answers">
                            <!-- ko if: $parent.AllowMultiple --><label class="checkbox"><input type="checkbox" data-url="<%= Url.Action("AddOrRemoveAnswer") %>" data-bind="attr: { value: Id, name: 'question-' + $parent.Id() }"/><!-- ko text: Text --><!-- /ko --></label><!-- /ko -->
                            <!-- ko ifnot: $parent.AllowMultiple --><label class="radio"><input type="radio" data-url="<%= Url.Action("AddOrRemoveAnswer") %>" data-bind="attr: { value: Id, name: 'question-' + $parent.Id() }"/><!-- ko text: Text --><!-- /ko --></label><!-- /ko -->
                        </div>
                    </div>
                <!-- /ko -->
            <!-- /ko -->
            <!-- ko if: Questions().length == 0 -->
                <div class="question space-above">
                    <p><strong>No questions in this category.</strong> <a class="add-question" data-bind="attr: { href: '<%= Url.Action("Create", "Questions") %>?categoryId=' + Id() + '&inProgress=true' }" target="_blank">Add some.</a> </p>
                </div>
            <!-- /ko -->
            <!-- ko if: Questions().length > 0 -->
                <div class="question space-above">
                    <a class="add-question" data-bind="text: 'New question for ' + Name(), attr: { href: '<%= Url.Action("Create", "Questions") %>?categoryId=' + Id() + '&inProgress=true' }" target="_blank"></a>
                </div>
            <!-- /ko -->
        </div>
    <!-- /ko -->

回答1:


The reason your bindings are getting completely deleted and re-rendered with every refresh, even when nothing has changed is because you're calling ko.applyBindings every refresh.

You do not want ko.applyBindings inside of your refresh function. You want to call ko.applyBindings once and that's it. After that, KO will handle updating the DOM when the data updates (or vice versa). You just want:

var testDate = { ... };
function ViewModel () { ... }
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
viewModel.refresh(function () {
  // ko.applyBindings(this); <- Get rid of this here, no bueno
  $(document).on("click", ....);
});

And that's it. Every time you've been calling refresh, you get data from the server that updates the viewModel. KO in turn will update the DOM as necessary if values update. When you call applyBindings after all that, KO goes through the DOM with a broadsword, updating all of your bindings whether it needs to or not.


Quick jQuery Tip:

$.get(...) returns a promise. There are three important functions that a promise returns, .done(), .fail(), and .always(). For your this.refresh() function, return the $.get result like this:

this.refresh = function () {
  return $.get(...);
};

Then what ever calls it would do this:

viewModel.refresh().done(function(data){
  // Callback function
});

Now you don't have to pass a callback function, check if callback is type of function, or worry about handling the callback function in the event of a failure. It's also universal and you can continue returning the promise down a chain of functions that will all wait til the $.get request is resolved before they perform their function.



来源:https://stackoverflow.com/questions/18219830/knockout-mapping-re-rendering-everything

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!