Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A better way to set up observableArray of computed objects in Knockoutjs

In this jsfiddle I have a simple example of what I want to do working. It's all working just fine. But it seems like there has to be a better way to create these arrays with some loops or something. I've been trying all day and haven't succeeded. Can someone tell me if

  1. it's possible
  2. what voodoo I need to perform in order for it to work.

Here's the code from the fiddle.

View:

<table>
<tbody>
    <tr>
        <td></td>
        <!-- ko foreach: topvals -->
        <td >
            <input type="text" data-bind="value: val"/>
        </td>
        <!-- /ko -->
    </tr>
    <tr>
        <td><input type="text" data-bind="value:sidevals()[0].val"/></td>
        <!-- ko foreach: intersections()[0] -->
        <td><span data-bind="text: val"></span></td>
        <!-- /ko -->
    </tr>
    <tr>
        <td ><input type="text" data-bind="value:sidevals()[1].val"/></td>
        <!-- ko foreach: intersections()[1] -->
        <td><span data-bind="text: val"></span></td>
        <!-- /ko -->
    </tr>
</tbody>
</table>

Viewmodel:

function ViewModel() {

    this.topvals = ko.observableArray([
        { val: ko.observable(6) },
        { val: ko.observable(1) },
        { val: ko.observable(1) },
        { val: ko.observable(1) }
    ]);

    this.sidevals = ko.observableArray([
        { val: ko.observable(1) },
        { val: ko.observable(1) },
        { val: ko.observable(1) }
    ]);

    this.intersections = ko.observableArray([
        [
            { val: ko.computed(function () { return this.topvals()[0].val() * this.sidevals()[0].val(); }, this) },
            { val: ko.computed(function () { return this.topvals()[1].val() * this.sidevals()[0].val(); }, this) },
            { val: ko.computed(function () { return this.topvals()[2].val() * this.sidevals()[0].val(); }, this) },
            { val: ko.computed(function () { return this.topvals()[3].val() * this.sidevals()[0].val(); }, this) }
        ],
        [
            { val: ko.computed(function () { return this.topvals()[0].val() * this.sidevals()[1].val(); }, this) },
            { val: ko.computed(function () { return this.topvals()[1].val() * this.sidevals()[1].val(); }, this) },
            { val: ko.computed(function () { return this.topvals()[2].val() * this.sidevals()[1].val(); }, this) },
            { val: ko.computed(function () { return this.topvals()[3].val() * this.sidevals()[1].val(); }, this) }
        ]
    ]);
}

ko.applyBindings(new ViewModel());
like image 896
jgonyer Avatar asked Oct 02 '22 04:10

jgonyer


2 Answers

  • To create the topvals and sidevals arrays you can use ko.mapping.fromJS.
  • To create the computed intersections you can return an "array of arrays" using the ko.utils.arrayMap function.

Here is the final code (and a jsfiddle):

function ViewModel() {
    var self = this;

    var calculateIntersection = function(topval, sideval) {
        return topval * sideval;
    };

    var topvals = [{ val: 1 }, { val: 2 }, { val: 3 }];
    var sidevals = [{ val: 1 }, { val: 2 }, { val: 3 }];

    self.topvals = ko.mapping.fromJS(topvals);
    self.sidevals = ko.mapping.fromJS(sidevals);
    self.intersections = ko.computed(function() {
        return ko.utils.arrayMap(self.sidevals(), function(sideval) {
            return ko.utils.arrayMap(self.topvals(), function(topval) {
                return calculateIntersection(topval.val(), sideval.val());
            });
        });
    });
}

ko.applyBindings(new ViewModel());

And here is the view:

<table>
    <tbody>
        <tr>
            <td></td>
            <!-- ko foreach: topvals -->
            <td >
                <input type="text" data-bind="value: val"/>
            </td>
            <!-- /ko -->
        </tr>
        <!-- ko foreach: sidevals -->
        <tr>
            <td><input type="text" data-bind="value: val"/></td>
            <!-- ko foreach: $root.intersections()[$index()] -->
            <td><span data-bind="text: $data"></span></td>
            <!-- /ko -->
        </tr>
        <!-- /ko -->
    </tbody>
</table>
like image 64
david.s Avatar answered Oct 13 '22 12:10

david.s


This simple view model already does it:

function ViewModel(vals1, vals2) {
    this.topvals = ko.observableArray(mapToObservables(vals1));
    this.sidevals = ko.observableArray(mapToObservables(vals2));
    this.calc = function (val1, val2) {
        return ko.unwrap(val1) * ko.unwrap(val2);
    };
}

ko.applyBindings(new ViewModel([6, 5, 4, 3], [1, 2, 3]));

where mapToObservables stands for either the mapping plugin or a function like this one:

function mapToObservables(vals) {
    return ko.utils.arrayMap(vals, function (val) {
        return { val: ko.observable(val) };
    });
};

and this view

<table>
<tbody>
    <tr>
        <td></td>
        <!-- ko foreach: topvals -->
        <td><input type="text" data-bind="value: val"/></td>
        <!-- /ko -->
    </tr>
    <!-- ko foreach: sidevals -->
    <tr>
        <td><input type="text" data-bind="value: val"/></td>
        <!-- ko foreach: $root.topvals -->
        <td><span data-bind="text: $root.calc(val, $parent.val)"></span></td>
        <!-- /ko -->
    </tr>
    <!-- /ko -->
</tbody>
</table>

See: http://jsfiddle.net/dt9kL/

A variant would be to use a separate sub-model for the values: http://jsfiddle.net/dt9kL/1/. For encapsulating a single value this is overkill, but it might be useful for more complex structures.

like image 36
Tomalak Avatar answered Oct 13 '22 11:10

Tomalak