knockout-sortable - seating chart
https://github.com/rniemeyer/knockout-sortable
by gurkavcu
HTML
<script src="https://github.com/downloads/SteveSanderson/knockout/knockout-2.0.0.debug.js"></script>
<script src="https://github.com/downloads/rniemeyer/knockout-sortable/knockout-sortable.js"></script>
<div>Seating Assignments</div>
<div class="note">Rules: no more than 4 students per table. Must have at least one student of each gender at a full table.</div>
<div id="main" data-bind="foreach: tables">
<div class="table">
<span data-bind="text: students.id"></span>
<div class="seats" data-bind="sortable: { data: students, allowDrop: $root.isTableFull }">
<div class="student" data-bind="text: name"></div>
</div>
<div class="count" data-bind="text: students().length + '/' + $root.maximumStudents, css: { ready: students().length < $root.maximumStudents }"></div>
</div>
</div>
<div id="extra">
<div>Available Students</div>
<div class="new" data-bind="sortable: availableStudents">
<div class="student" data-bind="text: name"></div>
</div>
</div>
<div id="message" data-bind="flash: lastAction"></div>
<div id="error" data-bind="flash: lastError"></div>
<div id="master">
<div>Master List</div>
<table>
<tr>
<th></th>
<th>Seat One</th>
<th>Seat Two</th>
<th>Seat Three</th>
<th>Seat Four</th>
</tr>
<tbody data-bind="foreach: tables">
<tr>
<th data-bind="text: students.id"></th>
<!-- ko foreach: students -->
<td data-bind="text: name"></td>
<!-- /ko -->
</tr>
</tbody>
<tr>
<th>Available</th>
<!-- ko foreach: availableStudents -->
<td data-bind="text: name"></td>
<!-- /ko -->
</tr>
</table>
</div>
CSS
body {
font-family: arial;
}
.table {
float: left;
text-align: center;
margin-right: 5px;
}
.table span {
font-size: .75em;
}
.seats {
border: solid 3px #666;
width: 75px;
height: 100px;
background-color: #666;
margin: 5px;
padding: 2px;
border-radius: 5px;
box-shadow: 2px 2px 2px #999;
}
.ko_container {
border: solid 3px green;
}
.student {
background-color: #444;
color: #fff;
border-radius: 3px;
margin: 2px;
padding: 1px;
text-align: center;
width: 70px;
cursor: move;
}
.new {
border: solid 1px #444;
background-color: #ccc;
height: 25px;
width: 500px;
padding: 2px;
border-radius: 5px;
}
.new div {
float: left;
}
.note {
font-style: italic;
font-size: .75em;
color: #888;
margin-bottom: 10px;
}
.count {
color: #666;
}
.ready {
color: green;
}
#extra {
clear: both;
padding-top: 20px;
}
#master {
margin-top: 40px;
}
#master td, th {
font-size: .75em;
text-align: center;
padding: 1px;
border: solid 1px black;
}
#master th {
color: #666;
}
#message, #error {
font-size: .75em;
margin-top: 10px;
background-color: orange;
color: #444;
padding: 2px;
width: 500px;
text-align: center;
border-radius: 5px;
box-shadow: 2px 2px 2px #999;
position: absolute;
}
#error {
background-color: #ff3333;
color: #ddd;
}
JavaScript
ko.bindingHandlers.flash = {
init: function(element) {
$(element).hide();
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (value) {
$(element).stop().hide().text(value).fadeIn(function() {
clearTimeout($(element).data("timeout"));
$(element).data("timeout", setTimeout(function() {
$(element).fadeOut();
valueAccessor()(null);
}, 3000));
});
}
},
timeout: null
};
var Student = function(id, name, gender) {
this.id = id;
this.name = ko.observable(name);
this.gender = gender;
};
var Table = function(id, students) {
this.students = ko.observableArray(students);
this.students.id = id;
};
var SeatingChartModel = function(tables, availableStudents) {
var self = this;
this.tables = ko.observableArray(tables);
this.availableStudents = ko.observableArray(availableStudents);
this.availableStudents.id = "Available Students";
this.lastAction = ko.observable();
this.lastError = ko.observable();
this.maximumStudents = 4;
this.isTableFull = function(parent) {
return parent().length < self.maximumStudents;
};
this.updateLastAction = function(arg) {
self.lastAction("Moved " + arg.item.name() + " from " + arg.sourceParent.id + " (seat " + (arg.sourceIndex + 1) + ") to " + arg.targetParent.id + " (seat " + (arg.targetIndex + 1) + ")");
};
//verify that if a fourth member is added, there is at least one member of each gender
this.verifyAssignments = function(arg) {
var gender, found,
parent = arg.targetParent;
if (parent.id !== "Available Students" && parent().length === 3 && parent.indexOf(arg.item) < 0) {
gender = arg.item.gender;
if (!ko.utils.arrayFirst(parent(), function(student) { return student.gender !== gender;})) {
...