1
votes

I created array with data about some music albums in viewmodel. I can use this array in foreach loop (for example) but I can't display only one value from this array.

My viewmodel:

function CD(data) {
    this.Author = ko.observable(data.Author);
    this.Title = ko.observable(data.Title);
    this.Price = ko.observable(data.Price);
    this.Label = ko.observable(data.Label);
    this.Id = ko.observable(data.Id);
}

function NewsListViewModel() {
    var self = this;
    self.newsList = ko.observableArray([]);

    $.getJSON("/music/news", function (allData) {
        var mapped = $.map(allData, function (item) { return new CD(item) });
        self.newsList(mapped);
    });

    self.oneObject = ko.computed(function () {
        return self.newsList()[0].Title;
    });
}

$(document).ready(function () {
    ko.applyBindings(new NewsListViewModel());
});

And how I used it in html:

<ul data-bind="foreach: newsList">
     <li data-bind="text: Title"></li>
     <li data-bind="text: Author"></li>
     <li data-bind="text: Price"></li>
     <li data-bind="text: Label"></li>
</ul>

This works perfectly. But if I trying additionally display one value:

<ul data-bind="foreach: newsList">
     <li data-bind="text: Title"></li>
     <li data-bind="text: Author"></li>
     <li data-bind="text: Price"></li>
     <li data-bind="text: Label"></li>
</ul>
...
<p data-bind="text: oneObject"</p>

it dosen't working, firebug thrown error:

Type error: self.newsList()[0].Title is undefined

Why I cannot get one specific value but I can display whole list?

PS. Code <p data-bind="text: newsList()[0].Title"</p> didn't works too.

2
Side note: There's never any reason to write ko.observableArray([]). Just ko.observableArray() is sufficient. - T.J. Crowder

2 Answers

3
votes

You get the error because $.getJSON is asynchronous.

So when your computed is declared your items are not necessary ready in the newsList so there is no first element what you could access.

So you need to check in your computed that your newsList is not empty (anyway your server could also return an empty list so it is always good to have this check) and only then access the first element:

self.oneObject = ko.computed(function () {
    if (self.newsList().length > 0)
        return self.newsList()[0].Title();
    return 'no title yet';
});

Note: you need also write .Title() to get its value because Title is an observable.

1
votes

I can see a few problems with your code:

  1. $.getJSON is asynchronous so self.newsList is empty when it's accessed by Knockout for the first time. Which in turn means that self.newsList[0] is undefined.
  2. return self.newsList()[0].Title; in the computed observable doesn't return the value you want it to return. It should be return self.newsList()[0].Title();
  3. You don't have to reinvent the wheel, there is the mapping plugin which you can use to map the data: http://knockoutjs.com/documentation/plugins-mapping.html