JSFiddle - React, Tailwind, and code Playground
HTML
<script src="http://knockoutjs.com/downloads/knockout-3.0.0.js"></script>
<div class='liveExample'>
<h2>Orders</h2>
<div id='contactsList'>
<table class='contactsEditor'>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Service</th>
</tr>
<tbody data-bind="foreach: contacts">
<tr>
<td>
<input data-bind='value: firstName' />
<div> <a href='#' data-bind='click: $root.removeContact'>Delete</a>
</div>
</td>
<td>
<input data-bind='value: lastName' />
</td>
<td>
<table>
<tbody data-bind="foreach: services">
<tr>
<td>
<!--<select data-bind='options: catalog, value: selectedId,optionsValue:"id", optionsText: "name", optionsCaption: "Select..."'></select>-->
<select data-bind='options: catalog, value: selectedId, optionsText: "name", optionsCaption: "Select..."'></select>
</td>
<td> <span data-bind='text: selectedId() && formatCurrency(selectedId().price)'></span>
</td>
<td> <a href='#' data-bind='click: $root.removeService'>Delete</a>
</td>
</tr>
</tbody>
</table> <a href='#' data-bind='click: $root.addService'>Add service</a>
<div>total: <span data-bind="text: total"></span>
</div>
</td>
</tr>
</tbody>
...
CSS
body {
font-family: arial;
font-size: 14px;
}
.liveExample {
padding: 1em;
background-color: #EEEEDD;
border: 1px solid #CCC;
max-width: 655px;
}
.liveExample input {
font-family: Arial;
}
.liveExample b {
font-weight: bold;
}
.liveExample p {
margin-top: 0.9em;
margin-bottom: 0.9em;
}
.liveExample select[multiple] {
width: 100%;
height: 8em;
}
.liveExample h2 {
margin-top: 0.4em;
font-weight: bold;
font-size: 1.2em;
}
.liveExample TR {
vertical-align: top;
}
.liveExample TABLE, .liveExample TD, .liveExample TH {
padding: 0.2em;
border-width: 0;
margin: 0;
}
.liveExample TD A {
font-size: 0.8em;
text-decoration: none;
}
.liveExample table.contactsEditor > tbody > TR {
border-bottom: 1px solid silver;
}
.liveExample td input {
width: 8em;
}
li {
list-style-type: disc;
margin-left: 20px;
}
JavaScript
function Contact(data) {
var self = this;
self.debt = data.debt,
self.firstName = data.firstName,
self.lastName = data.lastName,
self.services = ko.observableArray(data.services),
self.total = ko.computed(function () {
var total = 0;
var serviceItems = self.services()
ko.utils.arrayForEach(serviceItems, function (service) {
if (service.selectedId()) {
total += parseInt(service.selectedId().price);
}
})
return total;
})
}
formatCurrency = function (value) {
return value + " USD";
}
var serviceTypes = [{
name: "Service One",
id: "1",
price: "10"
}, {
name: "Service Two",
id: "2",
price: "9"
}, {
name: "Service Three",
id: "3",
price: "25"
}, {
name: "Service Four",
id: "4",
price: "42"
}];
function Catalog(serviceTypes, contact) {
var that = this;
//console.log(contact);
this.catalog = serviceTypes;
this.selectedId = ko.observable();
}
function ContactsModel(contacts) {
var self = this;
self.contacts = ko.observableArray(contacts);
self.contactsTotal = ko.computed(function () {
var total = 0;
ko.utils.arrayForEach(self.contacts(), function (contact) {
total += contact.total();
})
return total;
})
self.addContact = function () {
self.contacts.push(new Contact({
debt: "0",
firstName: "",
lastName: "",
services: [new Catalog(serviceTypes)]
}));
};
self.save = function () {
self.lastSavedJson(JSON.stringify(ko.toJS(self.contacts), null, 2));
};
self.lastSavedJson = ko.observable("")
};
ko.applyBindings(new ContactsModel([new Contact({
debt: "10",
firstName: "John",
lastName: "Carter",
services: [new Catalog(serviceTypes, 1)]
})]));