How to calculate a total in a grid with breeze.js

2019-08-13 02:42发布

问题:

I am working on an MVC web application using the MVVM pattern, breeze.js and knockout.js. This is the first time I use these js libraries and I still have to grasp how they work.

One of the pages of the application has a grid where both columns and rows are generated dynamically. I need to add an additional column where for each row I have the total of values displayed in the following row cells. Here an example:

Data type |   Comment   |  Fact 1 | Fact 2 |  Total    | Value 1 | Value 2 | Value 3 | Value 4
==============================================================================================
Item 1    | any comment |  fact 1 | fact 2 | calc. sum |    10   |   20    |    30   |   40

The grid is generated by binding a breeze entity object (planningItems) to templates. The object has the properties DataTypeId, Comment, Member, Total, FactValues. Total is the calculated sum.

<script type="text/html" id="list-planning-template">
<tr data-bind="mouseOverButton: $data">
    <td style="text-align: center">
        <button class="actionbutton actionbutton-item" data-bind="selectItem: $root.selectedItems, itemId: FactId"></button>
    </td>
    <td data-bind="text: DataTypeId" />
    <td data-bind="text: Comment().Text" />
    <!-- ko foreach: FactMembers -->
    <td data-bind="text: Member().Code"></td>
    <!-- /ko -->
    <td data-bind="text: Total" />
    <!-- ko foreach: FactValues -->
    <td style="width: 50px" data-bind="text: Value"></td>
    <!-- /ko -->
</tr>

I have been trying to add the Total property by extending the breeze entity object in the following way:

var FactCtor = function () {
this.Total = ko.computed({
    read: function () {
        var sum = 0;
        if (this.FactValues) {
            this.FactValues().forEach(function (fv) {
                sum += fv.Value();
            });
        }
        return sum;
    },
    deferEvaluation: true
}, this);
};

manager.metadataStore.registerEntityTypeCtor("Fact", FactCtor);

Essentially, what this code is supposed to do is to extend the entity by adding a knockout computed observable named Total with deferred evaluation. The function iterates through the breeze observable array FactValues and adds the values. I have been mucking about different versions of this code to no avail. Can anyone give me a hint on what is wrong with this code?

回答1:

UPDATE:

We were not able to get the code posted in my previous post to work. We were eventually able to overcome the problem by using a custom binding with breeze. Here is the code:

   ko.bindingHandlers.getFyTotal = {
   update: function (element, valueAccessor) {
       var sum = 0;
       var fact = valueAccessor();
       if (fact.FactValues()) {
           fact.FactValues().forEach(function (fv) {
               sum += parseFloat(fv.Value());
           });
       }

       $(element).html(sum);
   }
};

The custom binding is then referenced in the HTML code the following way:

<td data-bind="getFyTotal: $data" />

Hope this may help others.

REVISED VERSION:

We have updated the above code to take advantage of ko.utils functions:

ko.bindingHandlers.getFyTotal = {
update: function (element, valueAccessor) {
    var sum = 0;
    var fact = valueAccessor();
    if (fact.FactValues()) {
        ko.utils.arrayForEach(fact.FactValues(), function (fv) {
            sum += parseFloat(fv.Value());
        });
    }

    $(element).html(sum);
  }
};


回答2:

I modeled your code outside of Breeze and it's working:

http://jsfiddle.net/DazWilkin/yGZ7g/7/

I made a small tweak of adding a reference to FactValues (observableArray) on your constructor to overcome what - I believe - is the looping/this issue in JavaScript.

However, I haven't tried this in Breeze and wanted to do something similar. I was unable to get a similar function working and ultimately, created the totals during the 'then' processing of my executeQuery:

...manager.executeQuery(....).then(function(data) {
   ...
   Fact.Total(FactValues()
      .map(function(fv){ return fv.Value(); })
      .reduce(function (total,curr) { return total+curr; });
   ...
}

I will try to get back to working on my version of this today and, if I find a better solution, I'll report back.