JSFiddle - React, Tailwind, and code Playground
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout-postbox/0.6.0/knockout-postbox.min.js"></script>
<html>
<body>
<form id="billForm">
<table class="table timesheet">
<thead>
<tr>
<th>Time</th>
<th>Rate</th>
<th>Amount</th>
</tr>
</thead>
<tbody data-bind="foreach: timesheets">
<tr>
<td class="time"><input type="text" data-bind="value: time, name: function(data, event) { return 'time_'+ $index(); }()"></td>
<td class="rate"><input type="text" data-bind="value: rate, name: function(data, event) { return 'rate_'+ $index(); }()"></td>
<td class="amount"><input type="text" data-bind="value: amount, name: function(data, event) { return 'amount_'+ $index(); }()"></td>
</tr>
</tbody>
<tfoot>
<button type="button" data-bind="$root.addTimesheet">Add timesheet</button>
</tfoot>
</table>
<table class="table item">
<thead>
<tr>
<th>Qty</th>
<th>Price</th>
<th>Amount</th>
</tr>
</thead>
<tbody data-bind="foreach: items">
<tr>
<td class="qty"><input type="text" data-bind="value: quantity, name: function(data, event) { return 'qty_'+ $index(); }()"></td>
<td class="price"><input type="text" data-bind="value: price, name: function(data, event) { return 'price_'+ $index(); }()"></td>
<td class="amount"><input type="text" data-bind="value: amount, name: function(data, event) { return 'amount_'+ $index(); }()"></td>
</tr>
</tbody>
<tfoot>
<button type="button" data-bind="$root.addItem">Add item</button>
...
JavaScript
function getNumberFromFormattedValue(value) {
if (value != '' && value !== null) {
return value.toString().replace(/[^0-9.]*/g,'');
}
return value;
}
function BillTimesheetViewModel(item) {
var self = this;
self.time = ko.observable(item.time);
self.rate = ko.observable(item.rate);
self.total = ko.observable(item.amount);
self.amount = ko.computed({
read: function(){
var time = getNumberFromFormattedValue(self.time());
if (time !== null) { time = parseFloat(time); }
var rate = getNumberFromFormattedValue(self.rate());
if (rate !== null) { rate = parseFloat(rate); }
if (!(isNaN(time) || isNaN(rate) || time === null || rate === null || time === '' || rate === '')) {
var newValue = time * rate;
self.total(newValue);
return newValue;
}
// Don't change the value
},
write: function(value){
var newValue = getNumberFromFormattedValue(value);
if (newValue !== null) { newValue = parseFloat(newValue); }
var time = getNumberFromFormattedValue(self.time());
if (time !== null) { parseFloat(time); }
if (!(isNaN(newValue) || isNaN(time) || newValue === null || time === null || newValue === '' || time === '')) {
self.rate( newValue / time );
}
self.total(value);
},
owner: self
});
self.time.subscribe(function(){
if (self.time() === '' || self.time() === null || self.rate() === '' || self.rate() === null) {
self.total('');
self.amount('');
} else {
var time = self.time();
var rate = self.rate();
if (time !== '' && time !== null...