JSFiddle - React, Tailwind, and code Playground
HTML
<script src="http://cloud.github.com/downloads/SteveSanderson/knockout/jquery.tmpl.js"></script>
<script src="https://raw.github.com/SteveSanderson/knockout/master/build/output/knockout-latest.js"></script>
<script src="https://raw.github.com/SteveSanderson/knockout.mapping/master/build/output/knockout.mapping-latest.js"></script>
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/themes/base/jquery-ui.css">
<div class="toolbar">
<div>
<button data-bind="click: function(){readOnly(false)}, visible: readOnly">Edit</button>
<button data-bind="click: function(){App.save()}, visible: editable">Save</button>
<button data-bind="click: function(){readOnly(true)}, visible: editable">Cancel</button>
</div>
<div style="float: right;">
<span data-bind="text: readOnly"></span>
</div>
</div>
<div id="toolbarSpacer">
</div>
<div class="partner">
<h1>
<span data-bind="text: partner.name"></span>
(<span data-bind="text: partner.gci"></span>)
</h1>
<span class="location" data-bind="text: partner.location"></span>
</div>
<h2 data-bind="text: description"></h2>
<div id="tabs">
<ul>
<li><a href="#tabs-1">Details</a></li>
<li><a href="#tabs-2">People</a></li>
<li><a href="#tabs-3">More</a></li>
<li><a href="#tabs-4">JSON</a></li>
</ul>
<div id="tabs-1">
<table><tr><td>
<table class="label">
<tbody>
<tr>
<td>Description:</td>
<td>
<span data-bind="text: description, visible: readOnly"></span>
<input type="text" name="description" data-bind="value: description, visible: editable"
width="150" />
...
CSS
body {
font: 62.5% "Trebuchet MS", sans-serif;
margin: 20px;
font-size: 0.8em;
}
td {
vertical-align: top;
}
table.label td:first-child {
font-weight: bold;
text-align: right;
}
#products {
width: 145px;
}
#products select {
width: 140px;
}
#products span {
display: inline-block;
}
#products span.remove {
float: right;
cursor: pointer;
}
.meta {
color: gray;
}
div.partner h1 {
color: #CC3333;
margin: 0; padding: 0;
}
div.toolbar {
position: fixed;
border: 1px solid black;
left: 0;
top: 0;
right: 0;
background-color: gray;
}
#toolbarSpacer {
height: 30px;
}
table.contact {
width: 600px;
border: 1px solid black;
border-collapse: collapse;
}
table.contact td, table.contact th {
border: 1px solid gray;
}
JavaScript
function RefValue(code, label) {
this.code = code;
this.label = label;
this.toString = function() {
return this.label;
};
};
RefValue.comparator = function(a, b){
if (a == null || b == null) {
return a == null ? -1 : 1;
};
if (a.label < b.label) {
return -1;
} else if (a.label > b.label) {
return 1;
} else {
return 0;
}
};
function RVList(items) {
this.items = items;
this.get = function(code) {
for (i = 0, len = this.items.length; i < len; i++) {
if (this.items[i].code == code) {
return this.items[i];
}
}
return null;
};
}
window.rm = {
months: ["Jan","Feb","Mar","Apr","Mar","Jun",
"Jul","Aug","Sep","Oct","Nov","Dec"],
padMinutes: function(min) {
return min < 10 ? ("0" + min) : min;
},
formatDate: function(timestamp) {
var d = new Date(timestamp);
return d.getFullYear() + "-" + d.getMonth() + "-" + d.getDate();
},
formatDateTime: function(timestamp) {
var d = new Date(timestamp);
return d.getFullYear() + "-" + this.months[d.getMonth()] + "-" + d.getDate()
+ " " + d.getHours() + ":" + this.padMinutes(d.getMinutes());
},
oppStatus: new RVList([
new RefValue("OPEN", "Open"),
new RefValue("ONHOLD","On Hold"),
new RefValue("WON", "Won"),
new RefValue("LOST","Lost"),
new RefValue("ABAND", "Abandoned")
]),
products: new RVList([
new RefValue("",""),
new RefValue("AIR", "Air"),
new RefValue("OCEAN", "Ocean"),
new RefValue("INSUR", "Insurance"),
...