Search code examples
javascriptjqueryknockout.jsobservablebatch-updates

Improve knockout performance when changing multiple observables at once


I would be grateful for any performance suggestions for the "click" event in the JSFiddle provided.

The idea is to improve performance when changing multiple observables at once.

I was unable to find any documentation as to pausing and resuming update notifications in a batch fashion.

http://jsfiddle.net/g9jpxcm2/

$("#all").click(function(){
    var tasks = ko.dataFor($("#tasks")[0]).tasks(),
        checked = this.checked;

    //TODO: performance? Batch changes?
    for(var i = 0, l = tasks.length; i<l; i++){
         tasks[i].done( !!checked );
    }
});

Solution

  • It is usually a good idea to focus on elegant solutions that work, and to optimize only when you have a performance problem, or at least reasonably expect a particular piece of code to become a bottleneck.

    In that spirit, I have coded up a solution that makes the All checkbox a bit more responsive:

    1. If all the task boxes are checked, All will be checked
    2. If All is checked and a task box is unchecked, All will be unchecked
    3. Checking All causes all task boxes to be checked
    4. Unchecking All causes all task boxes to be unchecked

    There is no click event handling and no use of jQuery. Although this runs through the task items every time a value changes, I think you could have a hundred task boxes and have no noticeable performance problem.

    var viewModel = ko.mapping.fromJS({
    tasks: [{
        name: "Task 1",
        done: false    
    },{
        name: "Task 2",
        done: true    
    },{
        name: "Task 3",
        done: false    
    }]
    });
    
    viewModel.allChecked = ko.computed({
    read: function () {
        var tasks = viewModel.tasks();
        for (var i=0; i<tasks.length; ++i) {
            if (!tasks[i].done()) {
                console.debug("Nope");
                return false;
            }
        }
        return true;
    },
    write: function (newValue) {
        var tasks = viewModel.tasks();
        for (var i=0; i<tasks.length; ++i) {
            tasks[i].done(newValue);
        }
    }
    });
    
    ko.applyBindings(viewModel);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
    <script src="https://cdn.rawgit.com/SteveSanderson/knockout.mapping/master/build/output/knockout.mapping-latest.js"></script>
    <label>All</label>
    <input id="all" type="checkbox" data-bind="checked:allChecked" />
    <br><br><br>
    <div id="tasks" data-bind="foreach: tasks">
    <label data-bind="text: name"></label>
    <input type="checkbox" data-bind="checked: done" />    
    </div>