1

I am having trouble figuring out how to add to a pre-existing observableArray after issuing a second AJAX request for similar, but different data.

My first JSON request does not contain the type called value, so I need to add it. My initial JSON structure:

[{"statusmsg":"OK","data":{"status":"stopped"},"sender":"hostname","statuscode":0}]

A second AJAX request is issued which gets the uptime of a service from the REST server:

[{"statusmsg":"OK","data":{"value":"","fact":"some_fact"},"sender":"hostname","statuscode":0}

I want to add to the nested object data the type value, which contains some_fact.

I have tried the following:

ko.utils.arrayForEach(self.rows(), function(row) {
   ko.utils.arrayForEach(self.timeRows, function(time) {
      if(row.sender() == time.sender()) {
         self.rows()[0].data.push({value: time.data.value()});
      }
   });
});

Note: I am using the placeholder index 0 for testing only. Ideally, I would like to just find the match based on the hostname and update the observableArray.

When this code executes, I receive the error that

TypeError: self.rows(...)[0].data.push is not a function

I also tried putting parentheses next to data like data().push, but that did not work either.

The intent is to add a new value to the original observableArray called rows so I can update my view with the new information.

Engineer2021
  • 3,288
  • 6
  • 29
  • 51
  • Depends on how you defined `rows` in your view model. Is it just a simple array of JS objects wrapped in a ko.observableArray()? Did you use ko.mapping.fromJSON() or ko.mapping.fromJS() on the data you received from the server? Did you make your own custom model to view model mapping? – Sybeus Jul 12 '14 at 23:21
  • @Sybeus: I used ko.mapping.fromJSON. I did not make a custom model. Just one ViewModel – Engineer2021 Jul 12 '14 at 23:22

2 Answers2

3

Since you've opted for the default Knockout mapping, it automatically assumes an associative array is a simple object with attributes, and thus has no array prototype functions, such as push(). You don't need the index reference either, since you've already found the row in self.rows(). So just simply set the value attribute of the 'data' sub-object of the matching row object:

ko.utils.arrayForEach(self.rows(), function(row) {
   ko.utils.arrayForEach(self.timeRows, function(time) {
      if(row.sender() == time.sender()) {
         row.data.value = time.data.value;
      }
   });
});
Sybeus
  • 1,169
  • 11
  • 18
  • Oh wow, I didn't know you could do that. I figured since value didn't exist in the first set that you had to create it somehow. – Engineer2021 Jul 13 '14 at 00:04
  • It's one of the most powerful features of JavaScript, being able to dynamically add attributes and functions to existing objects. – Sybeus Jul 13 '14 at 00:07
  • See my answer below to see what I had to do after you helped. – Engineer2021 Jul 13 '14 at 13:10
  • 2
    I thought you just wanted to build a list of objects first, then bind to it. If `data` is a truely dynamic sub-list of info per row, with AJAX updates to the sub-list over time, then I would suggest an entirely different approach. Something like this http://jsfiddle.net/Hg2cF/ so that you don't have to resort to hacks to get your view model to update the view. Knockout is all about automatically updating the view from the view model, so if you find yourself having to write that part yourself, you're defeating the purpose of Knockout. – Sybeus Jul 14 '14 at 02:47
0

To solve my problem, I used @Sybeus' answer but then I realized that my rows were not updating in my UI because my UI was bound to the old version of rows. Even when I tried to create a dummy subscriber and notify my computed observable that the rows were updated, it was still bound to the old rows as described in this answer:

If you would want to replace the array you should probably create a replacement array, push tweets into that array and finally replace the array in the observableArray by doing self.tweets(replacementArray);.

So what I had to do was the following:

self.updateServiceStartTime = function() {
  var url = "url";

  $.ajax({
     method: "GET",
     url: url,
     success: function(data) {
        var observableData = ko.mapping.fromJSON(data);
        var array = observableData();
        self.timeRows(array);

        ko.utils.arrayForEach(self.rows(), function(row) {
           ko.utils.arrayForEach(self.timeRows(), function(time) {
              if(row.sender() == time.sender()) {                    
                 if (time.data.value()) {
                    row.data.value = time.data.value;
                 }
                 else {
                    row.data.value = "No time found yet.";
                 }
              }
           });
        });
        self.cloneRows(self.rows().slice());
        self.rows.removeAll();
        self.rows(self.cloneRows().slice());
     }
  });
  };

This updates the UI with the new data that I added.

Community
  • 1
  • 1
Engineer2021
  • 3,288
  • 6
  • 29
  • 51