JSFiddle - React, Tailwind, and code Playground
by SpAm
HTML
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap-theme.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="container">
<div class="row">
<div class="col-sm-12" style="text-align:center;">
<button class="btn btn-default btn-sm" type="button" data-bind="click:addExtraRow">Add extra row</button>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<table class="table table-bordered table-consensed" style="width:auto;margin:0px auto">
<thead>
<tr class="bg-primary">
<th>Start time</th>
<th>End time</th>
<th>Overtime</th>
</tr>
</thead>
<tbody data-bind="foreach:entries">
<tr>
<td style="width:6em;">
<input type="text" class="form-control input-sm timeField" data-bind="textInput:start, css:{'alert-success':startMoment}" maxlength="5"/>
</td>
<td style="width:6em;">
<input type="text" class="form-control input-sm timeField" data-bind="textInput:end, css:{'alert-success':endMoment}" maxlength="5"/>
</td>
<td style="width:14em;" data-bind="text:total"></td>
</tr>
</tbody>
<tbody>
<tr class="bg-success">
<td colspan="2">Overall Total</td>
<td data-bind="text:overallTotal"></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
CSS
.row {
margin-top:10px;
}
JavaScript
function OverTimeCalculator(){
var self = this;
self.entries = ko.observableArray([]);
for(var i=0;i<3;i++) {
self.entries.push(new OverTimeEntry());
}
self.overallTotalMinutes = ko.computed(function(){
var entries = self.entries().reduce(function(total, entry){
total+=entry.totalMinutes();
return total;
}, 0);
return entries;
});
self.overallTotal = ko.computed(function(){
var tot = self.overallTotalMinutes();
if( tot<60 ) {
return tot + " mins";
}
return Math.floor((tot/60)) + " hour(s) " + ( tot%60 ) + " mins";
});
self.addExtraRow = function(){
self.entries.push(new OverTimeEntry());
}
ko.applyBindings(self);
}
$(document).on('keyup', '.timeField', function (e) {
var time = $.trim($(this).val());
if (/^[0-9]:[0-5][0-9]$/.test(time))
time = "0" + time;
if (/^([01][0-9]|2[0-3]):?[0-5][0-9]$/.test(time))
time = time.indexOf(":") === -1 ? (time.replace(/(\S{2})/, "$1:")) : time;
$(this).val(time);
$(this).change();
});
function OverTimeEntry(){
var self = this;
self.start = ko.observable("");
self.end = ko.observable("");
self.startMoment = ko.computed(function(){
var start = self.start();
return validateTime(start);
});
self.endMoment = ko.computed(function(){
var end = self.end();
return validateTime(end);
});
self.totalMinutes = ko.computed(function(){
if( self.startMoment()&&self.endMoment() ) {
return calcTotal(self.startMoment(), self.endMoment());
}
return 0;
});
self.total = ko.computed(function(){
var tot = self.totalMinutes();
if( tot<60 ) {
return tot + " mins";
}
return Math.floor((tot/60)) + " hour(s) " + ( tot%60 ) + " mins";
});
function validateTime(time){
if (time != "") {
return moment(time, 'HH:mm', true).isValid() ? moment(time, 'HH:mm', true) : null;
}
return null;
}
function calcTotal(start, end){
var totalMinutes = 0;
...