0

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 );
    }
});
Stevanicus
  • 7,561
  • 9
  • 49
  • 70
  • Unrelated, but why aren't you using the `click` binding handler here? – Retsam Jul 20 '15 at 14:34
  • @Retsam - just happens to be so for this example - the click binding could be used - does it however help me further though? – Stevanicus Jul 20 '15 at 14:35
  • @Stevanicus nice thought tough but there will be slight performance issue at initial check/uncheck . check console here for time logs http://jsfiddle.net/g9jpxcm2/2/ . Later requests will take no time because subscription's are maintained(not disposed until you do manually) & initially time delay becoz (subscriptions tobuild) .cheers – super cool Jul 20 '15 at 15:40
  • 2
    What kind of performance issue are you having? – Roy J Jul 20 '15 at 16:06
  • Not really having any performance issues, just wonder if there is a way to optimise it – Stevanicus Jul 20 '15 at 16:29
  • Optimizing, particularly this sort of micro-optimizing, is a Bad Idea. See: http://c2.com/cgi/wiki?PrematureOptimization – Roy J Jul 20 '15 at 16:49
  • It's only a micro-optimization' in this example, in my actual scenario we are talking about hundreds of records which trigger events on further reactive output, so 1 observable value change in effect could alter 3 or more others. Times that by 100 for example and I already have 300 events. So I'm just looking for the optimization in a generic/dynamic environment. – Stevanicus Jul 20 '15 at 17:39

2 Answers2

2

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>
Roy J
  • 42,522
  • 10
  • 78
  • 102
0

You can change your model around to track the collection of checked tasks, rather than directly indicating which tasks are checked. It should make implementation easier and faster since only a single observable would be updating. Just make an (observable) array and bind the checked binding to the array. Also make sure the value is bound.

<div id="tasks" data-bind="foreach: tasks">
    <label data-bind="text: name"></label>
    <input type="checkbox" data-bind="checked: $parent.checkedTasks, value: $data" />
</div>
var data = {
    tasks: [{
        name: "Task 1"
    },{
        name: "Task 2"
    },{
        name: "Task 3"
    }]
};

ko.applyBindings( ko.mapping.fromJS(data, {}, {
    checkedTasks: ko.observableArray([])
}), $("#tasks")[0]);

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

    if (checked) {
        model.checkedTasks(model.tasks().slice(0));
    } else {
        model.checkedTasks([]);
    }
});

fiddle

Jeff Mercado
  • 129,526
  • 32
  • 251
  • 272
  • Thanks for your answer. I have the observable per task because that it how I receive the data from the server. So although I can alter the view model afterwards like you have done in the applyBindings method call, I still need to the send the data back to the server the same way it came in. – Stevanicus Jul 20 '15 at 17:35
  • I think it would be a better overall experience if you generated the data when communicating with the server. In general, the less bound and/or dependent observables you have in your model, the less has to be recalculated when a value changes. If it was generated, you would only have to pay that cost when transmitting data, rather than when clicking around, at least in theory. – Jeff Mercado Jul 20 '15 at 17:40
  • Yea the problem is though that in this case it may be checkboxes... but in theory it could be completely generic and dynamic. The mapped observable in this case "done" can be mapped to any value in any nested object, so I want to touch the view model as little as possible. It's simply, get the data, whatever it is, show it, allow some interaction of some sort, send it back. – Stevanicus Jul 20 '15 at 17:45
  • In essence you are probably right as that is also how Knockout has recommended it in their example, unfortunately it doesn't fit my situation :) – Stevanicus Jul 20 '15 at 17:47