https://stackoverflow.com/questions/46964468/knockoutjs-with-a-3-level-nested-viewmodel-binding - JSFiddle - JSFiddle
by jlspake
HTML
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/css/select2.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/js/select2.min.js"></script>
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
×
</button>
<h4 class="modal-title" id="myModalLabel">Create Reservation</h4>
<p data-bind="text: MessageToClient"></p>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-12">
<form id="smart-form-reservation" class="form form-inline smart-form" role="form">
<div class="row">
<section class="col col-xs-12 col-sm-12 col-md-6">
<label>Title</label>
<div>
<label class="input">
<input data-bind="value: Title" autocomplete="off" id="Title" name="Title" />
</label>
</div>
</section>
<section class="col col-xs-12 col-sm-12 col-md-6">
<label>Logo Path</label>
<div>
<label class="input">
<input data-bind="value: LogoPath" autocomplete="off" id="LogoPath" name="LogoPath" />
</label>
</div>
</section>
</div>
<div class="row">
<section class="col col-xs-12 col-sm-12 col-md-6">
<label>Reservation Start Date</label>
<div>
<label class="input">
<input data-bind="value: StartDate" autocomplete="off" id="StartDate" name="StartDate" />
</label>
</div>
</section>
<section class="col col-xs-12 col-sm-12 col-md-6">
...
CSS
.btn {
color: blue;
}
JavaScript
var myData = {
Title: 'reservation title',
LogoPath: 'logo path',
StartDate: 'start date',
EndDate: 'end date',
StartTime: 'start time',
EndTime: 'end time',
TimeSpan: 0,
MinPersons: 0,
MaxPersons: 0,
ReservationOptions: [{
Id: 0,
Title: 'option title',
TypeId: 0,
TypeDescription: 'type description',
Info: 'info',
ReservationOptionValues: [{
Id: 1,
ValueTitle: 'value 1'
},{
Id: 2,
ValueTitle: 'value 2'
}]
}],
MessageToClient: 'message to client',
}
// 2nd level
ReservationOptionsViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, reservationOptionsMapping, self);
}
ReservationOptionValues = function (data) {
var self = this;
ko.mapping.fromJS(data, reservationOptionValuesMapping, self);
}
var reservationOptionsMapping = {
'ReservationOptions': {
key: function (reservationOption) {
return ko.utils.unwrapObservable(reservationOption.Id);
},
create: function (options) {
return new ReservationOptionsViewModel(options.data, reservationOptionValuesMapping);
}
}
}
//3rd level
ReservationOptionsValuesViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, reservationOptionValuesMapping, self);
}
var reservationOptionValuesMapping = {
'ReservationOptionValues': {
key: function (reservationOptionValues) {
return ko.utils.unwrapObservable(reservationOptionValues.Id);
},
create: function (options) {
return new ReservationOptionsValuesViewModel(options.data);
}
}
}
// first level
CreateReservationViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, reservationOptionsMapping, self);
self.save = function () {
$.ajax({
url: "/Reservations/Save/",
type: "POST",
data: ko.toJSON(self),
contentType: "application/json",
...