TimeScorer
by whelkaholism
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.3.0/knockout-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.js"></script>
<div class="controls">
<div class="control style">
<label>Bar width</label>
<input type="text" data-bind="textInput: BarWidth" />
</div>
<div class="control addbars">
<label>Add</label>
<input type="text" data-bind="value: NumberToAdd" />
<label>bars, </label>
<input type="text" data-bind="value: BeatsPerBar" />
<label>beats, </label>
<input type="text" data-bind="value: TicksPerBeat" />
<label>ticks</label>
<button type="button" data-bind="click: addBars">+</button>
<button type="button" data-bind="click: clearBars">Clear</button>
<a class="popout" target="_blank" href="http://fiddle.jshell.net/whelkaholism/a6b4uo44/show/">Popout</a>
</div>
</div>
<div class="score" data-bind="foreach: Bars">
<table class="bar" data-bind="attr: { style: $parent.BarStyle }">
<tr class="notes_container" data-bind="foreach: Ticks">
<td class="notes" data-bind="css: { beat: IsBeat }, attr: { style: TickStyle }"></td>
</tr>
<tr class="ticks" data-bind="foreach: Ticks">
<td data-bind="css: { beat: IsBeat }">
<span data-bind="text: TickMark"></span>
</td>
</tr>
</table>
</div>
CSS
* {
font-family: Verdana;
}
.controls {
margin-bottom: 1em;
}
.controls .popout {
float:right;
}
.control {
margin-bottom: 0.5em;
}
.control.addbars input[type=text] {
width: 2em;
}
.bar {
float: left;
width: 100%;
border-collapse: collapse;
border: 1px solid #000000;
margin-bottom: 1em;
margin-right: -1px;
}
.notes {
height: 4em;
border-bottom: 1px solid #909090;
border-left: 1px dotted #bbbbbb;
}
.notes_container .notes:first-child {
border-left: none;
}
.ticks .beat {
font-weight: bold;
border-left: 1px solid #bbbbbb;
}
.ticks span {
margin-left: -0.25em;
}
.ticks .beat span {
margin-left: -0.5em;
}
@media print {
.controls {
display: none
}
.bar {
page-break-inside: avoid
}
}
JavaScript
function BarModel(data, mapping)
{
var _this = this;
if(data)
ko.mapping.fromJS(data, mapping, _this);
this.BeatsPerBar = this.BeatsPerBar || ko.observable();
this.TicksPerBeat = this.TicksPerBeat || ko.observable();
this.Ticks = ko.computed(
function()
{
var a = [];
for(var ibeat = 0; ibeat < _this.BeatsPerBar(); ibeat++)
{
a.push({ IsBeat: true, TickMark: ibeat + 1 });
for(var itick = 0; itick < _this.TicksPerBeat() - 1; itick++)
a.push({ IsBeat: false, TickMark: '-' });
}
$.each(a,
function()
{
this.TickStyle = 'width:' + Math.floor(100 / a.length) + '%';
}
);
return a;
}
);
}
function TimeScorerModel(data, mapping)
{
var _this = this;
var _mapping = $.extend({}, {}, mapping);
if(data)
ko.mapping.fromJS(data, _mapping, _this);
this.Bars = this.Bars || ko.observableArray();
this.NumberToAdd = this.NumberToAdd || ko.observable(4);
this.BeatsPerBar = this.BeatsPerBar || ko.observable(4);
this.TicksPerBeat = this.TicksPerBeat || ko.observable(2);
this.BarWidth = this.BarWidth || ko.observable('50%');
//
this.BarStyle = ko.computed(
function()
{
return 'width:' + _this.BarWidth();
}
);
//
this.addBars = function(model, e)
{
var num = !isNaN(parseInt(model)) ? model : _this.NumberToAdd();
for(var i = 0; i < num; i++)
{
_this.Bars.push(new BarModel({
BeatsPerBar: _this.BeatsPerBar(),
TicksPerBeat: _this.TicksPerBeat()
}, mapping));
}
}
this.clearBars = function(model, e)
{
...