1
votes

I been trying to do checkbox Checkall and UnCheckall using subscribe and i'm partially successful doing that but i am unable to find a fix in couple of scenarios when i am dealing with subscribe .

Using subscribe :

I am here able to checkAll uncheckAll but when i uncheck a child checkbox i.e test1 or test2 i need my parent checkbox name also to be unchecked and in next turn if i check test1 the parent checkbox should be checked i.e keeping condition both child checkboxes are checked .

For fiddle : Click Here

ViewModel :

self.selectedAllBox.subscribe(function (newValue) {
        if (newValue == true) {
            ko.utils.arrayForEach(self.People(), function (item) {
                item.sel(true);
            });
        } else {
            ko.utils.arrayForEach(self.People(), function (item) {
                item.sel(false);

            });
        }
    });

The same scenario can be done perfectly in easy way using computed but due some performance issues i need to use subscribe which is best way it wont fire like computed onload .

Reference : Using computed same thing is done perfectly check this Fiddle

I tried to use change event in individual checkbox binding but its a dead end till now.

Any help is appreciated .

3

3 Answers

2
votes

Your subscription only applies to edits on the selectedAllBox. To do what you want, you'll need subscriptions on every Person checkbox as well, to check for the right conditions and uncheck the selectedAllBox in the right situations there.

It strikes me as odd that this would be acceptable but using computed() is not. Maybe you should reconsider that part of your answer. I would much rather compute a "isAllSelected" value based on my viewModel state, then bind the selectedAllBox to that.

1
votes

I solved a similar problem in my own application a couple of years ago using manual subscriptions. Although the computed observable method is concise and easy to understand, it suffers from poor performance when there's a large number of items. Hopefully the code below speaks for itself:

function unsetCount(array, propName) {
    // When an item is added to the array, set up a manual subscription
    function addItem(item) {
        var previousValue = !!item[propName]();
        item[propName]._unsetSubscription = item[propName].subscribe(function (latestValue) {
            latestValue = !!latestValue;
            if (latestValue !== previousValue) {
                previousValue = latestValue; 
                unsetCount(unsetCount() + (latestValue ? -1 : 1));
            }
        });
        return previousValue;
    }
    // When an item is removed from the array, dispose the subscription
    function removeItem(item) {
        item[propName]._unsetSubscription.dispose();
        return !!item[propName]();
    }

    // Initialize
    var tempUnsetCount = 0;
    ko.utils.arrayForEach(array(), function (item) {
        if (!addItem(item)) {
            tempUnsetCount++;
        }
    });
    var unsetCount = ko.observable(tempUnsetCount);

    // Subscribe to array changes
    array.subscribe(function (changes) {
        var tempUnsetCount = unsetCount();
        ko.utils.arrayForEach(changes, function (change) {
            if (change.moved === undefined) {
                if (change.status === 'added') {
                    if (!addItem(change.value))
                        tempUnsetCount++;
                } else {
                    if (!removeItem(change.value))
                        tempUnsetCount--;
                }
            }
        });
        unsetCount(tempUnsetCount);
    }, null, 'arrayChange');

    return unsetCount;
}

You'll still use a computed observable in your viewmodel for the the select-all value, but now it'll only need to check the unselected count:

self.unselectedPeopleCount = unsetCount(self.People, 'Selected');

self.SelectAll = ko.pureComputed({
    read: function() {
        return self.People().length &&  self.unselectedPeopleCount() === 0;
    },
    write: function(value) {
        ko.utils.arrayForEach(self.People(), function (person) {
            person.Selected(value);
        });
    }
}).extend({rateLimit:0});

Example: http://jsfiddle.net/mbest/dwnv81j0/

1
votes

The computed approach is the right way to do this. You can improve some performance issues by using pureComputed and by using rateLimit. Both require more recent versions of Knockout than the 2.2.1 used in your example (3.2 and 3.1, respectively).

self.SelectAll = ko.pureComputed({
    read: function() {
        var item = ko.utils.arrayFirst(self.People(), function(item) {
            return !item.Selected();
        });
        return item == null;           
    },
    write: function(value) {
        ko.utils.arrayForEach(self.People(), function (person) {
            person.Selected(value);
        });
    }
}).extend({rateLimit:1});

http://jsfiddle.net/mbest/AneL9/98/