Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

knockout unable to process binding "foreach"

I'm new to Knockout and I'm building an app that's effectively a large-scale calculator. So far I have two instances of knockout running on one page. One instance is working perfectly fine, however the other one is entirely broken and just won't seem to register at all?

Below is my Javascript, fetchYear is the function that works perfectly fine and fetchPopulation is the one that's completely broken. It doesn't seem to register "ageview" from the HTML at all and I can't figure out.

The error:

Uncaught ReferenceError: Unable to process binding "foreach: function (){return ageView }" Message: ageView is not defined

Thanks in advance.

JS:

var index = {

    fetchYear: function () {
        Item = function(year){
            var self = this;
            self.year = ko.observable(year || '');
            self.chosenYear = ko.observable('');
            self.horizon = ko.computed(function(){
                if(self.chosenYear() == '' || self.chosenYear().horizon == undefined)
                    return [];
                return self.chosenYear().horizon;
            });
        };
        YearViewModel = function(yeardata) {
            var self = this;
            self.yearSelect = yeardata;
            self.yearView = ko.observableArray([ new Item() ]);
            self.add = function(){
                self.yearView.push(new Item("New"));
            }; 
        };
        ko.applyBindings(new YearViewModel(yearData));
    },

    fetchPopulation: function () {
        popItem = function(age){
            var self = this;
            self.age = ko.observable(age || '');
            self.chosenAge = ko.observable('');
            self.population = ko.computed(function(){
                if(self.chosenAge() == '' || self.chosenAge().population == undefined)
                    return [];
                return self.chosenAge().population;
            });
        };
        PopulationViewModel = function(populationdata) {
            var self = this;
            self.ageSelect = populationdata;
            self.ageView = ko.observableArray([ new popItem() ]);
            self.add = function(){
                self.ageView.push(new popItem("New"));
            }; 
        };
        ko.applyBindings(new PopulationViewModel(populationData));
    }

}

index.fetchYear();
index.fetchPopulation();

HTML:

<div class="row" data-bind="foreach: yearView">
    <div class="grid_6">
        <img src="assets/img/index/calendar.png" width="120" height="120" />
        <select class="s-year input-setting" data-bind="options: $parent.yearSelect, optionsText: 'year', value: chosenYear"></select>
        <label for="s-year">Start year for the model analysis</label>
    </div>
    <div class="grid_6">
        <img src="assets/img/index/clock.png" width="120" height="120" />
        <select class="s-horizon input-setting" data-bind="options: horizon, value: horizon"></select>
        <label for="s-horizon">Analysis time horizon</label>
    </div>
</div>

<div class="row" data-bind="foreach: ageView">
    <div class="grid_6">
        <img src="assets/img/index/calendar.png" width="120" height="120" />
        <select class="s-year input-setting" data-bind="options: ageSelect, optionsText: 'age', value: chosenAge"></select>
        <label for="s-agegroup">Age group of <br> target population</label>
    </div>
    <div class="grid_6">
        <img src="assets/img/index/clock.png" width="120" height="120" />
        <input class="s-population input-setting"></input>
        <label for="s-population">Size of your patient <br> population <strong>National</strong> </label>
    </div>
</div>
like image 663
leaksterrr Avatar asked Apr 15 '14 13:04

leaksterrr


2 Answers

When you do this (in fetchYear):

ko.applyBindings(new YearViewModel(yearData));

You are binding the entire page with the YearViewModel view model. But the YearViewModel doesn't have a property called ageView so you get the error and knockout stops trying to bind anything else.

What you need to do is restrict your bindings to cover only part of the dom by passing the element you want to ko.applyBindings. For example:

<div class="row" id="yearVM" data-bind="foreach: yearView">
//....
<div class="row" id="popVM" data-bind="foreach: ageView">

And then:

ko.applyBindings(new YearViewModel(yearData), document.getElementById("yearVM"));
//...
ko.applyBindings(new PopulationViewModel(populationData), document.getElementById("popVM"));

Now your bindings are restricted just to the part of the DOM that actually displays stuff from that model.

Another alternative is to just have your two view models as part of a parent view model and then you can apply the binding to the entire page. This makes it easier if you need to mix parts from both VMs and they are not conveniently separated in distinct sections of your page. Something like:

var myParentVM = {
    yearVM : index.fetchYear(),          // note, make this return the VM instead of binding it
    popVM : index.fetchPopulation(),     // ditto
}

ko.applyBindings(myParentVM);

And then you'd declare your bindings like so:

<div class="row" data-bind="foreach: yearVM.yearView">
like image 160
Matt Burland Avatar answered Oct 12 '22 06:10

Matt Burland


The main reason why this is not working is because you call ko.applyBindings() more than once on a page (that is not really forbidden but is a bad practice in my opinion).

If you need to call it twice, you must call it with a container for which region this bind is meant to.

Something like this:

ko.applyBindings(new YearViewModel(yearData), document.getElementById('YourYearViewElementId'));

The error you get is from the first binding, which tries to process the whole page and does not find the 'ageView' in its ViewModel.

Better would be if you build a single ViewModel for a single Page where you have sub-models for sections if needed.

Some pseudo code for such a scenario:

var Section1ViewModel = function() {
    var self = this;

    self.property1 = ko.observable();
    self.myComputed = ko.computed(function () {
        // do some fancy stuff
    });
    self.myFunc = function() {
        // do some more fancy stuff
    };
}

var Section2ViewModel = function() {
    var self = this;

    self.property1 = ko.observable();
    self.myComputed = ko.computed(function () {
        // do some fancy stuff
    });
    self.myFunc = function() {
        // do some more fancy stuff
    };
}

var PageViewModel = function() {
    var self = this;

    self.section1 = ko.observable(new Section1ViewModel());
    self.section2 = ko.observable(new Section2ViewModel());

    self.myGlobalFunc = function() {
        // do some even more fancy stuff
    }
}

ko.applyBindings(new PageViewModel());

Hope that helps.

Best regards, Chris

like image 38
Chris Avatar answered Oct 12 '22 04:10

Chris