Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to iterate an Knockout ObservableArray? (length 0)

I'm new to knockout.js (and this is also my first stackoverflow post) and I'm now facing the following problem.

I'm not able to bind the data from web api to a ko.observablearray. Why is the length of this Announcements ko.observablearray always 0? The code works fine with client side data (by adding new announcements)..

Here's the JS-code:

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

    self.AnnouncementText = ko.observable();
    self.AllDepartmentsBool = ko.observable();
    self.Editable = ko.observable(false);

    self.Add = function () {
        viewModel.Announcements.push(self);
        viewModel.AnnouncementToEdit(new AnnouncementModel());
    };

    self.Delete = function () {
        ajaxHelper(announcementsUri + self.ID, 'DELETE').done(
        viewModel.Announcements.remove(self));
    };

    self.Edit = function () {
        self.Editable(!self.Editable());
    };
}

//The ViewModel
function AnnouncementsViewModel() {
    var self = this;

    self.InitialData = ko.observableArray();
    self.Announcements = ko.observableArray();
    self.AnnouncementToEdit = ko.observable(new AnnouncementModel());
    self.error = ko.observable();

    function getAllAnnouncements() {
        ajaxHelper(announcementsUri, 'GET').done(function(data) {
            self.InitialData(data);        
        });
    };

    getAllAnnouncements();
};

var viewModel = new AnnouncementsViewModel();
ko.applyBindings(viewModel, document.getElementById("announcements-container"));

function createAnnouncement(announcementDto) {
    var announcement = new AnnouncementModel();
    announcement.AnnouncementText = ko.observable(announcementDto.AnnouncementText);
    announcement.AllDepartmentsBool = ko.observable(announcementDto.AllDepartmentsBool);
    announcement.Editable = ko.observable(false);

    return announcement;
}

var length = viewModel.InitialData.length;
for (i = 0; i < length; i++) {
    var newAnnouncement = createAnnouncement(InitialData[i]);
    viewModel.Announcements.push(newAnnouncement);
}

The HTML:

<div id="announcements-container" style="display: inline-block; float: right">
    <ul id="announcements-list" class="newsticker" data-bind="foreach: Announcements">
        <li>
            <span data-bind="html: AnnouncementText"></span>
        </li>
    </ul>
    @Html.Partial("_AnnouncementsModal")
</div>

The InitialData gets populated from the api as it should:


GOT IT WORKING! :

Thanks for the quick answers. I got the code working by iterating the data with .forEach(). Another problem was that the initialData didn't get populated in it's current scope so I edited the getAnnouncements function to work like this :

function getAllAnnouncements() {
    ajaxHelper(announcementsUri, 'GET').done(function(data) {
        data.forEach(function (entry) {
            var newAnnouncement = createAnnouncement(entry);
            self.Announcements.push(newAnnouncement);
        });
    });
};
like image 426
Markus Walden Avatar asked Mar 14 '23 18:03

Markus Walden


1 Answers

This line is the likely culprit:

var length = viewModel.InitialData.length;

Remember that InitialData is a function. Functions have a length (it's their "arity", the number of formal arguments they have), but the observable function for an observable array's length isn't the array's length..

You probably wanted the length of the array inside it:

var length = viewModel.InitialData().length;
// -------------------------------^^

Your various calls to push on observable arrays work even though length doesn't because Knockout provides push (and several other things) on the observable array function, as James points out.


Similarly, this line:

var newAnnouncement = createAnnouncement(InitialData[i]);

probably wants to be using the array as well (and is missing viewModel. in front of InitialData).

So that whole section probably wants to be refactored a bit:

viewModel.InitialData().forEach(function(entry) {
    var newAnnouncement = createAnnouncement(entry);
    viewModel.Announcements.push(newAnnouncement);
});

or without forEach (but really, it's nearly 2016, and it's shimmable on obsolete browsers);

var data = viewModel.InitialData();
for (var i = 0; i < data.length; ++i) {
    var newAnnouncement = createAnnouncement(data[i]);
    viewModel.Announcements.push(newAnnouncement);
}

Side note: Your code (at least as it is in the question) was also falling prey to The Horror of Implicit Globals by not declaring the i that you use in that for loop. I've added a var above, but this is another reason for using forEach to loop through arrays.

like image 129
T.J. Crowder Avatar answered Mar 23 '23 20:03

T.J. Crowder