knockout - subtract values in an array

748 views Asked by At

I'm having a little trouble with the following. I'm using knockout to edit a list containing nested lists, a little like the Contacts Editor on the knockout website:

http://knockoutjs.com/examples/contactsEditor.html

In my example as follows the "designs" relate to the "Contacts" from the above example and the "designDeliveries" relate to the "Phone Numbers".

So I have a value within each "design" called MaxQuantity and a value within each "designDelivery" called Quantity. Effectively, what I'm trying to achieve is have another value in each "design" called lets say "Remaining" which basically will be the "MaxQuantity" minus the sum of "Quantity" in all "designDeliveries" relating to a particular "design". As I add new "designDeliveries" and populate the "Quantity" field this should of course be deducted from the new "Remaining" value for that particular design

so far I have the code below:

var DesignsModel = function (designs) {

    var self = this;
    self.designs = ko.observableArray(ko.utils.arrayMap(designs, function (design) {
        return {    BookingDesignId: design.BookingDesignId, 
            DesignName: design.DesignName, 
            MaxQuantity: design.Quantity,
            DesignDeliveries: ko.observableArray(design.DesignDeliveries) };
    }));

    self.addDesignDelivery = function (design) {
        design.DesignDeliveries.push({
            Quantity: "",
            DepotId: ""
        });
    };

    self.removeDesignDelivery = function (designDelivery) {
        $.each(self.designs(), function () { this.DesignDeliveries.remove(designDelivery) })
    };

    self.save = function () {
        self.lastSavedJson(JSON.stringify(ko.toJS(self.designs), null, 2));
    };

    self.lastSavedJson = ko.observable("");

};

ko.applyBindings(new DesignsModel(initialData));

initialData looks like this:

[{"DesignId":"1","DesignName":"Design1","Quantity":50,"DesignDeliveries":[]},{"DesignId":"2","DesignName":"Design2","Quantity":50,"DesignDeliveries":[]},{"DesignId":"3","DesignName":"Design3","Quantity":500,"DesignDeliveries":[]}] 

and the html like this:

<div data-bind="foreach: designs">

    <div>
        <span data-bind="text: DesignName"></span>
        <a href="#" data-bind="click: $root.addDesignDelivery">Add</a>
    </div>
    <div>
        <span data-bind="text: MaxQuantity"></span>
    </div>
    <div>
        <span data-bind="text: Remaining"></span>
    </div>

    <div data-bind="foreach: DesignDeliveries">
        <div>
            <input data-bind="value: Quantity" />
        </div>
        <div>  
            <a href="#" data-bind="click: $root.removeDesignDelivery">
                Delete
            </a>             
        </div>          
    </div>    
</div>

I hope this is clear enough. Does anybody have an idea how I would go about achieving this?

Thanks in advance

1

There are 1 answers

0
Artem Vyshniakov On BEST ANSWER

I would recomend you to split up your complex view model to several small view models. Then to achive desired functionality you just need to add computed that calculates remaining quantity:

var DesignDeliveryModel = function(quantity, depotId) {
    var self = this;

    self.Quantity = ko.observable(quantity);
    self.DepotId = depotId;
};

var DesignModel = function(bookingDesignId, designName, maxQuantity, deliveries) {
    var self = this;

    self.BookingDesignId = bookingDesignId;
    self.DesignName = designName;
    self.MaxQuantity = maxQuantity;
    self.DesignDeliveries = ko.observableArray(deliveries);

    self.Remaining = ko.computed(function() {
        var result = self.MaxQuantity;
        ko.utils.arrayForEach(self.DesignDeliveries(), function(item) {
            result = result - item.Quantity();
        });
        return result;
    });

    self.addDesignDelivery = function() {
        self.DesignDeliveries.push(new DesignDeliveryModel(0, ""));
    };

    self.removeDesignDelivery = function(designDelivery) {
        self.DesignDeliveries.remove(designDelivery);
    };
};

var DesignsModel = function(designs) {
    var self = this;

    self.designs = ko.observableArray(ko.utils.arrayMap(designs, function(design) {
        return new DesignModel(design.BookingDesignId, design.DesignName, design.Quantity, design.DesignDeliveries);

    }));

    self.save = function() {
        self.lastSavedJson(JSON.stringify(ko.toJS(self.designs), null, 2));
    };

    self.lastSavedJson = ko.observable("");
};

And small updates to html to change context of calling add and remove functions:

<div data-bind="foreach: designs">
    <div>
        <span data-bind="text: DesignName"></span>
        <a href="#" data-bind="click: addDesignDelivery">Add</a>
    </div>
    <div>
        <span data-bind="text: MaxQuantity"></span>
    </div>
    <div>
        <span data-bind="text: Remaining"></span>
    </div>

    <div data-bind="foreach: DesignDeliveries">
        <div>
            <input data-bind="value: Quantity" />
        </div>
        <div>  
            <a href="#" data-bind="click: $parent.removeDesignDelivery">
                Delete
            </a>             
        </div>          
    </div>    
</div>

Here is working fiddle: http://jsfiddle.net/vyshniakov/7JUGE/