JSFiddle - React, Tailwind, and code Playground

by homer2

HTML

<script src="http://cloud.github.com/downloads/SteveSanderson/knockout/knockout-2.1.0beta.js"></script>
<hr/>
<table border='1' style="border-collapse: collapse;">
<thead>
    <tr><th></th>
        <th>Jan</th><th>%</th>
        <th>Feb</th><th>%</th>
        <th>Mar</th><th>%</th>
        <th>Apr</th><th>%</th>
        <th>May</th><th>%</th>
        <th>Jun</th><th>%</th>
        <th>Jul</th><th>%</th>
        <th>Aug</th><th>%</th>
        <th>Sep</th><th>%</th>
        <th>Oct</th><th>%</th>
        <th>Nov</th><th>%</th>
        <th>Dec</th><th>%</th>
        <th>Year End<br />Actuals/Forecast</th><th>%</th>
    </tr>
</thead>
    <tbody data-bind="foreach: resourceCategories">
        <tr>
            <td data-bind="text: name"></td>
            <!-- ko foreach: monthAmounts -->
            <td data-bind="css: { negative: amount() < 0}">
                <span type="text" data-bind="text: amount"></span>
                <!-- ko if: monthNumber > $root.currentMonthNumber -->
                <input type="text" data-bind="value: amount, valueUpdate: 'afterkeydown'"></input>
                <!-- /ko -->
            </td>
            <td data-bind="text: (amount()/$root.laborBasis[$index()].value()).toFixed(2) + '%'">% of Labor basis</td>
            <!-- /ko -->
            <td data-bind="text: totalAmount">totalExpenses</td>
            <td></td>
        </tr>
    </tbody>
    <tbody>
        <tr>
            <td>Month Total</td>
            <!-- ko foreach: monthTotals -->
            <td>
                <span type="text" data-bind="text: value"></span>
            </td>
            <td></td>
            <!-- /ko -->
            <td>totalAmount</td>
            <td>averageAmount</td>
        </tr>
        <tr>
            <td>Labor Basis</td>
            <!-- ko foreach: laborBasis -->
            <td>
                <span type="text" data-bind="text: value"></span>
            </td>
            <td></td>
            <!-- /ko -->
           ...

JavaScript

function MonthAmount(id, amount, monthNumber) {
    var self = this;
    self.id = id;
    self.amount = ko.observable(amount);
    self.monthNumber = monthNumber;
}

function ResourceCategory(id, name, monthAmounts) {
    var self = this;
    self.id = id;
    self.name = name;
    self.monthAmounts = ko.utils.arrayMap(monthAmounts, function(monthAmount) {
        return new MonthAmount(monthAmount.Id, monthAmount.Amount, monthAmount.MonthNumber);
    });
    
    self.totalAmount = ko.computed(function() {
        var sum = 0;
        for (var i = 0; i < self.monthAmounts.length; i++) {
            sum += parseFloat(self.monthAmounts[i].amount());
        }
        return sum;
    });
    
    self.averageAmount = ko.computed(function() {
        return self.totalAmount()/self.monthAmounts.length;
    });
    
}

function OverheadViewModel(json) {
    var self = this;
    
    self.resourceCategories = ko.utils.arrayMap(json.ResourceCategories, function(resourceCategory) {
        return new ResourceCategory(resourceCategory.Id, resourceCategory.Name, resourceCategory.MonthAmounts);
    });
    
    self.laborBasis = new Array();
    for (var i = 0; i < 12; i++) {
        self.laborBasis.push({"value": ko.observable(100000)});
    }
    
    self.monthTotals = new Array();
    for (var monthNumber = 0; monthNumber < 11; monthNumber++) {
        self.monthTotals.push({"value": ko.computed(function() {
            var val = 0;
            for (var i = 0; i < self.resourceCategories.length; i++) {
                val += parseFloat(self.resourceCategories[i].monthAmounts[monthNumber].amount());                
            }
            return val;
        })});
    }
    
    self.monthTotal4 = ko.computed(function() {
            var val = 0;
            for (var i = 0; i < self.resourceCategories.length; i++) {
                val += parseFloat(self.resourceCategories[i].monthAmounts[4].amount());
            }
            return val;
        });
                ...