Coding Slave
Coding Slave

Reputation: 53

Knockout - How to setup Computed properties for child and parent as same time?

I have a Collection of items that I would like to display the Total as well as individual Percentage of each item. The problem seems to be that I need a reference of parent object when computing the total. So I figured I'll define a computed getTotal for the Collection and a percentage for the item.

    function Collection() {
    var self = this;
    self.parts = ko.observableArray();
    self.getTotal = ko.computed(function() {
        var total = self.parts.length;
        return total;
    });
}

function Part(amount, parent) {
    var self = this;
    self.amount = ko.observable(amount);
    self.parent = parent;

    self.percentage = ko.computed(function() {
        return self.amount() / self.parent.getTotal();
    });
}

var partsData = [40, 50, 30];
var collection = new Collection();
for (var i = 0; i < partsData.length; ++i) {
    collection.parts.push(new Part(partsData[i], collection));
}

ko.applyBindings(collection);

And my HTML is

    <ul data-bind="foreach: parts">
    <li>
        <p data-bind="text: amount"></p>
        <p data-bind="text: percentage"></p>
    </li>
</ul>

However the Collection.parts is always empty. For some reason push it not triggering the items to be recalculated.

Also is this the right way for doing this? I could now figure out how I could simultaneously create an observableArray in the Collection while giving each child item a reference to the parent object.

Upvotes: 3

Views: 2813

Answers (1)

Roman Bataev
Roman Bataev

Reputation: 9361

  1. Your getTotal is supposed to return sum of amounts, not number of parts, right?
  2. Your don't have to push elements to parts one by one. You can do collection.parts($.map([40, 50, 30], function(el){return new Part(el, collection);});) (I use jQuery.map here)
  3. In general I think percentage doesn't belong to Part, since then it has to be passed a collection, which is bad (unnecessary dependency). I would do it this way instead:
function ViewModel() {
    var self = this;
    self.parts = ko.observableArray([]);
    self.total = ko.computed(function(){
        var s = 0;
        $.each(self.parts(), function(i, el){s += el.amount();});
        return s;
    });
}

function Part(amount){
    this.amount = ko.observable(amount);
}

$(function() {
    var view = new ViewModel();
    var parts = $.map([40, 50, 30], function(el){return new Part(el);});
    view.parts(parts);
    ko.applyBindings(view);
});

Html:

<ul data-bind="foreach: parts()">
    <li>
        <p data-bind="text: amount"></p>
        <p data-bind="text: amount() / $root.total()"></p>
    </li>
</ul>

Upvotes: 6

Related Questions