0

I have an object and within this object I have items and one of the items is an array which also contains objects. A sample of the data is shown below.

I am using knockout to bind this data to the view so I think I need to implement a double loop for returning the objects and the objects within the child array to be able to bind them in the view.

Sample data:

"singers": {
        "ijiyt6ih": {
            "id": ObjectId('ijiyt6ih'),
            "name": "John",
            "songs": [
                {
                    "id": ObjectId('okoiu8yi'),
                    "songName": "Hello There",
                    "year": "1980"
                },
                {
                    "id": ObjectId('sewfd323'),
                    "songName": "No More",
                    "year": "1983"
                }
            ]
        },
        "98usd96w": {
            "id": ObjectId('98usd96w'),
            "name": "Jack",
            "songs": [
                {
                    "id": ObjectId('iew342o3'),
                    "songName": "Hurry Up",
                    "year": "1985"
                }
            ]
        }
    }

I need to find a way to appropriately loop through this so that I can modify the returned data to bind it to the viewModel using knockout.

Here is how my viewModel looks like:

singersViewModel = function(data) {
   var self = {
           singerId: ko.observable(data.id),
           singerName: ko.observable(data.name),
           songName: ko.observable(...),
           songYear: ko.observable(...)
       };

I am not sure if I will have to return two different sets of data or not.

As for the looping. I was able to loop and return the list of singers to display on the page but I am not able to get the list of songs displayed within each singer.

Here is my loop so far:

var self = {},
    singer,
    tempSingers = [];

    self.singers = ko.observableArray([]);
    for (singer in singers) {
        if (singers.hasOwnProperty(singer)) {
             tempSingers.push(new singersViewModel(singers[singer]));
           }
     }
     self.singers(tempSingers);

I tried to duplicate the same type of loop for songs within this loop but i would get an error using hasOwnProperty because songs is an array.

3

1 Answer 1

1

In the included snippet you can see how you can map the original data to a viewmodel that can be bound to a view.

I've left the ids as regular properties, and converted the names into observables, so thatthey can be edited. At the bottom you can see the current viewmodel state.

There is also a sample view which iterates the list of singers, and also the list of song within each singer.

As you can see I'm implementing the solution using mapping. For mapping you need to implement a callback that receives each original object and returns a new one with a new structure. For example this part of the code

_.map(_singers, function(singer) {
    return {
      id: singer.id,
      name: ko.observable(singer.name),
      // ... songs: 
})

iterates over each singer (the sample data in the question), and for each one creates a new object with the id, an observable which includes the name (and the mapping of songs, which I don't show in this fragment).

NOTE: I'm using lodash, but many browsers support map natively as an array function

var ObjectId = function (id) { return id; }

var singers = {
        "ijiyt6ih": {
            "id": ObjectId('ijiyt6ih'),
            "name": "John",
            "songs": [
                {
                    "id": ObjectId('okoiu8yi'),
                    "songName": "Hello There",
                    "year": "1980"
                },
                {
                    "id": ObjectId('sewfd323'),
                    "songName": "No More",
                    "year": "1983"
                }
            ]
        },
        "98usd96w": {
            "id": ObjectId('98usd96w'),
            "name": "Jack",
            "songs": [
                {
                    "id": ObjectId('iew342o3'),
                    "songName": "Hurry Up",
                    "year": "1985"
                }
            ]
        }
    };

var SingersVm = function(_singers) {
  var self = this;
  self.singers = _.map(_singers, function(singer) {
    return {
      id: singer.id,
      name: ko.observable(singer.name),
      songs: _.map(singer.songs, function(song) {
        return {
          name: ko.observable(song.songName),
          id: song.id
          };
        })
    };
  });
  return self;
};

var vm = new SingersVm(singers);
//console.log(vm);
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div data-bind="foreach: singers">
  <div>
      <input data-bind="value: name"/> (<span data-bind="text: id"></span>)
      <ul data-bind="foreach:songs">
        <li>
          <input data-bind="value: name"/> (<span data-bind="text: id"></span>)
        </li>
      </ul>
  </div>
</div>
        
<pre data-bind="html: ko.toJSON($root,null,2)">
</pre>

Sign up to request clarification or add additional context in comments.

1 Comment

This is a new and neat approach i didn't consider before and thanks for sharing. But is it possible to do it a different way to make it more generic because the database could be updated with more items and that would mean i will have to update the code every time. I was looking for a way to that will loop through the items regardless of what they are.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.