KnockoutJS - Row and Column Calculations

2019-07-23 21:02发布

问题:

This question follows on from my original and first question 'Nested Data, Loading Children'.

I have a table which is populated with data from JSON that contains nested objects (see colour coding). The table contains one row per object that is editable. This is called the 'Allocation' row.

These values are populated from a JSON object ("Values": { " n" : 4 , "n2" : 2, etc).

Firstly I would like to display a total for each editable row. As values are updated inside the row the total updates. This use case behind this is I would like to compare the original total (Allocation.Total) with the observable total, so I can then change the css class on the controls cell to indicate if the data has changed.

This is illustrated below; (yellow indicated user changes)

http://i.stack.imgur.com/XQpLp.jpg

The second part of the question is dealing with a Column total. The above question was making my head hurt a little, but this one blows my mind. As such I'm will sleep on that one, and then update the question once it's become a little clearer.

There is one complication which as arisen from using a simplified data model. In that by saying everything is the same type of object, (crew) the word used for Allocation needs to change when the node has children. If the node has Children 'Allocation' should be called Guide.

Simplified Code Example here...

Updated with the Column Part... The Allocated.Total, is a running total of the Allocations. The difference here is this total is working down the columns of the table, no across a single row. Is this even possible? My mind started to melt a little.

Update - 26th of April

For the Column totals although it is possible to approach this by hardcoding against node1, node2, node3. I had hoped to avoid this because these are nodes that change depending on the user/request (e.g. it won't always be node1, node2, node3). I've addressed this issue in the HTML templating as this information can be found out server side, enabling me to deliver the page with the correct bindings.

I could use the same approach for the JavaScript but as we load the data in from the API, it must be possible to dynamically get this information and then the JavaScript remains nice and clean. I've been looking at how to iterate over (key, pair) values, so the node1, node2, node3 bit can become dynamic. However this is where I got quite stuck.

The second part, where it gets really complex. The Grand Total is part of what I am after, but I also need a total for each individual column (Monday - Breakfast, Monday - Lunch, Monday - Diner). This cascades from the deepest node, up to the very top.

The same is true also of Redeemed. So a person can redeem a ticket, which is reflected all the way up to the parent node. As someone who loves XPaths, this would be ancestor-or-self::Crew.

回答1:

OK. Here's what your total computed should be.

self.totalAllocation = ko.computed(function() {
            var values = self.Allocation.Values;
            return Number(values.node1()) + 
                   Number(values.node2()) + 
                   Number(values.node3());
})

Note the brackets around the nodes. This indicates that these have been changed into knockout observables. In your current code they are simply json values. Non-observables can be bound to/from knockout value/text bindings but they do not notify others when their value has changed. If the totalAllocation was using simple json values it would not automatically recompute when they changed via your value bindings.

I updated your example to use the mapping plugin. This will automatically spin through all your properties and make them observables.

The label for allocation is a simple one. This can be achieved with a computed and text binding.

self.allocLabel = ko.computed(function() {
    return self.HasChildren() ? "Guide" : "Allocation";            
});

<th data-bind="text: allocLabel">Allocation</th>

I wasn't completely clear on the last part. It seems like you want the Allocated.Total to sum all its child allocations. Does this include the Redeemed value? Should it include its own allocated total? Whatever the answer the way to do it is with a computed observable again.

Something like.

self.grandTotal = ko.computed(function() {
    var result = 0;              
    for (var i = 0; i < self.Crews().length; i += 1) {
        result += self.Crews()[i].totalAllocation();
    }   
    return result;
});

You can see I simply looped through the Crews array and summed the totalAllocation for each child. If you update a rows allocation numbers the row total and column total get updated.

http://jsfiddle.net/madcapnmckay/CGBZe/

EDIT

As mentioned in the comments. Dynamic totals should not be an issue (untested).

self.totalAllocation = ko.computed(function() {
     var values = self.Allocation.Values;
     var total = 0;
     for (var prop in values) { 
         total += Number(ko.utils.unwrapObservable(values[prop]));
     }
     return total;
})

Hope this helps.



标签: knockout.js