1

I know this was asked multiple times already, but none of that answered my question. I have the following: I get data over JSON to Javascript into a two dimensional array. When I load the site, the table shows up like wanted. Now when I click a button (just for testing), it is updating one value from the array and logging that array in the console, where I see the changed array. The problem is that the change is not showing up in the table. When I just add a value to the array, it is showing up in the table. What am I doing wrong?

HTML:

<table id="sortsTable">
   <tbody data-bind="foreach: sorts">
   <tr>
     <td data-bind="text: $data.name"></td>
     <td data-bind="text: $data.ingName"></td>
   </tr>
   </tbody>
</table>
<button data-bind="click: addPerson">Add</button>

JS:

var sorts = ko.observableArray([]);

$(function() {
var request = new XMLHttpRequest();
var formData = new FormData();
var responseElements = [];
request.open("POST", "scripts.php", true);
formData.append("action", "getSorts");
request.onreadystatechange = function() {
    if (request.readyState == 4 && request.status == 200) {
        responseElements = JSON.parse(request.responseText);
        sorts = convertList(responseElements);
        ko.applyBindings(new AppViewModel(sorts));
    }
}
request.send(formData);

});

function convertList(response) {  //just the function to convert the json object to a more useful array
    var names = [];
    var ingredients = [];
    var sorts = [];
    for (var index = 0; index < response.length; index++) {
        var name = response[index]['name'];
        var ing = response[index]['ingName'];
        if (names.indexOf(name) == -1) {
            names.push(name);
        }
        if (ingredients.indexOf(ing) == -1) {
            var nameIndex = names.indexOf(name);
            if (ingredients[nameIndex] == undefined) {
                ingredients[nameIndex] = ing;
            } else {
                ingredients[nameIndex] = ingredients[nameIndex] + ", " + ing;
            }
        }
    }
    for (var i = 0; i < names.length; i++) {
        sorts[i] = {};
        sorts[i]['name'] = names[i];
        sorts[i]['ingName'] = ingredients[i];
    }
    return sorts;
}

function AppViewModel(data) {
    var self = this;
    self.sorts = data;
    self.addPerson = function() {
        console.log("click");
        self.sorts[0]["name"] = "test";  //doesn't update table
        //self.sorts.push({name: "qwer", ingName: "we"});  //works like expected
        console.log(self.sorts);
    };
}

Thanks.

jukisu
  • 19
  • 1
  • 6
  • Probably what's happening is that knockout.js is monitoring changes to the array, but not updates to the variables. When you add a new element, you're changing the contents of the array, but when you update an element, you're mutating a variable, but the array can retain all the same pointers to variables it already has. You'll likely need to fire a refresh event in knockout. – tswei Mar 15 '17 at 12:41
  • Yes it doesn't look like you're using any observables? Which really makes using knockout pointless. I suggest you read some knockout tutorials – Jason Spake Mar 15 '17 at 12:46
  • @JasonSpake i am using an observable array. look at the beginning of the code. Am I not doing it right? – jukisu Mar 15 '17 at 12:55
  • @tswei how should I do this refreshing? – jukisu Mar 15 '17 at 12:55
  • You might try the solution here: http://stackoverflow.com/questions/13231738/refresh-observablearray-when-items-are-not-observables – tswei Mar 15 '17 at 13:56

2 Answers2

3

The observable array only monitors which items are added to it, not the items themselves, so

self.sorts.push({name: "qwer", ingName: "we"});  //works like expected

works because you're getting the observable array to add to it's items, but

self.sorts[0]["name"] = "test";  //doesn't update table

doesn't work because the observable array has no way of knowing that an item inside it has changed. For this to work the properties of the items in the array will need to be observable.

In convertList switch to:

for (var i = 0; i < names.length; i++) {
    sorts[i] = {};
    sorts[i]['name'] = ko.observable(names[i]);
    sorts[i]['ingName'] = ko.observable(ingredients[i]);
}

And they must be set by calling the observable setter method like so:

self.addPerson = function() {
    console.log("click");
    self.sorts[0]["name"]("test");
    ...

Also as an aside, you seem to have some other issues here. You define sorts as an observable array on the first line, but you overwrite it with the return value of convertList which is a normal array, not an observable one.

sorts = convertList(responseElements);
ko.applyBindings(new AppViewModel(sorts));

I'd remove the first line and create sorts as an observable array

function convertList(response) {  //just the function to convert the json object to a more useful array
    var names = [];
    var ingredients = [];
    var sorts = ko.observableArray([]);
    ...
user3432422
  • 1,359
  • 1
  • 11
  • 13
  • In convertList switch to: for (var i = 0; i < names.length; i++) { sorts[i] = {}; sorts[i]['name'] = ko.observable(names[i]); sorts[i]['ingName'] = ko.observable(ingredients[i]); } this doesn't work, because when initializing an observable array, the argument must be an array, not a value... – jukisu Mar 15 '17 at 13:35
  • But we're not initialising observable arrays here, we're initialising observables. – user3432422 Mar 15 '17 at 13:40
  • well, right, didn't see that I guess. I changed it, now for some reason the array is empty.. so it doesn't output anything at all – jukisu Mar 15 '17 at 13:47
2

The issue is that when you bind this:

 <td data-bind="text: $data.name"></td>

$data.name is not observable, it's a simple property on an object, created here:

sorts[i]['name'] = names[i];

Knockout will quite happily bind properties like this, and display them, but any updates to them are not visible to knockout. Instead, as well as your observableArray, you also need to make any individual properties you want the ability to update observable as well:

sorts[i]['name'] = ko.observable(names[i]);

Then when you update it, knockout will see the change. Note however that you can't simply just assign to the property, as you'll just overwrite the knockout observable and it will be lost, instead you need to call the observable with the update:

self.sorts[0]["name"]("test");
James Thorpe
  • 31,411
  • 5
  • 72
  • 93