Search code examples
javascriptwebknockout.jsknockout-mapping-plugin

Trouble in Knockout when mapping data in nested observableArray


I have trouble trying to map data in a nested observableArray (timeSlots). When I fill my participants observableArray it works just fine. I bet I have to do something special when it's nested.

I'm new to Knockout so not sure what I am doing wrong. I've been looking at the documentation to no avail, especially at:

http://knockoutjs.com/documentation/plugins-mapping.html

Here is my code: (EDITED)

<script type="text/javascript">

var TimeSlot = function (startTime, endTime) {
    var self = this;
    self.startTime = startTime;
    self.endTime = endTime;
};

var Participant = function (id, timeSlots) {
    var self = this;
    self.Name = id;
    self.roomName = ko.observable();
    self.timeSlots = ko.observableArray();

    var timeSlotVMs = _.map(timeSlots, function(ts) {
        return new TimeSlot(ts.startTime, ts.endTime);
    });

    ko.utils.arrayPushAll(self.timeSlots(), timeSlotVMs);
};

function ViewModel() {
    var self = this;
    self.participants = ko.observableArray([]);

    self.addPerson = function () {
         var data = '{"TimeSlot":[{"startTime":"05:23","endTime":"06:32"},
                     {"startTime":"10:23","endTime":"11:32"}]}';
         var partViewModel = new Participant(self.selectedValue().Id(), data.TimeSlot);
         self.participants.push();
    };
};



var viewModel = new ViewModel();
ko.applyBindings(viewModel);

</script>

And HTML if needed:

<tbody data-bind="foreach: viewModel.participants">
    <tr>
        <td data-bind="text: Name"></td>
        <td data-bind="text: roomName"></td>

        <td data-bind="foreach: viewModel.participants.timeSlots">
                <span data-bind="text: startTime"></span>
        </td>
    </tr> 
</tbody>

Solution

  • Try this:

    var TimeSlot = function (startTime, endTime) {
        var self = this;
        self.startTime = startTime;
        self.endTime = endTime;
    };
    
    var Participant = function (id, timeSlots) {
        var self = this;
        self.Name = id;
        self.roomName = ko.observable();
        self.timeSlots = ko.observableArray();
    
        var timeSlotVMs = _.map(timeSlots, function(ts) {
            return new TimeSlot(ts.startTime, ts.endTime); 
        });
        ko.utils.arrayPushAll(self.timeSlots(), timeSlotVMs);        
    };
    
    function ViewModel() {
        var self = this;
        self.participants = ko.observableArray();
    
        self.addPerson = function () {
             var data = {
                 TimeSlot: [
                   {startTime:"05:23",endTime:"06:32"},
                   {startTime:"10:23",endTime:"11:32"}
                ]
             };
             var partViewModel = new Participant(123, data.TimeSlot);
             self.participants.push(partViewModel);
        };
    };
    

    I used lodash library function 'map', to create array of viewmodels.

    Also, you have error in your layout. Use timeSlots instead viewModel.participants.timeSlots:

    <tbody data-bind="foreach: participants">
        <tr>
            <td data-bind="text: Name"></td>
            <td data-bind="text: roomName"></td>
    
            <td data-bind="foreach: timeSlots">
                <span data-bind="text: startTime"></span>
            </td>
        </tr> 
    </tbody>