Knockout Drag/Drop - Seating Chart

by Alvin Olavarrieta

HTML

<script src="http://knockoutjs.com/downloads/knockout-3.0.0.js"></script>
<div id="roster" data-bind="foreach: students">
    <div>
        -
        <span data-bind="drag: {value: student, disabled: $root.isLocked}" class="student">
            <!-- ko if: student -->
                <!-- ko with: student -->
                    <span data-bind="text:name, style:{backgroundColor: ((sex() == 1) ? 'lightBlue': 'pink')}"></span>
                    <span data-bind="visible: notes">(<span data-bind="text:notes"></span>)</span>
                <!-- /ko -->
            <!-- /ko -->
        </span>
    </div >
</div>
<input type="checkbox" data-bind="checked: isLocked" id="isLocked"/> <label for="isLocked">Locked</label>
<table id="floor">
    <tbody data-bind="foreach: getRows()">
        <tr class="row" data-bind="foreach: $data">
            <td data-bind="drop: {value: student, disabled: !!student()}">
                <div class="seat" >
                    <span data-bind="drag: {value: student, disabled: $root.isLocked}">
                        <!-- ko with: student -->
                            <span data-bind="" class="student">
                                <span data-bind="text:name, style:{backgroundColor: ((sex() == 1) ? 'lightBlue': 'pink')}"></span>
                                <br/>
                                <span data-bind="visible: notes">(<span data-bind="text:notes"></span>)</span>
                            </span>
                        <!-- /ko -->
                    </span>
                </div>
            </td>
        </tr>
    </tbody>
</table>

<a href="#" data-bind="click: function(){ console.log(ko.toJS(this)); }" style="clear:both;float:right">console.log the JSON</a>

CSS

*{
    font-family: Helvetica, Arial;
}
#roster{
    float:left;
}
.student{
    margin:2px;
    font-size:10pt;
}
.ui-dragon{
    font-size:14pt;
}
#floor{
    border:1px solid gray;
    float:right;
}
.seat{
    border:1px solid black;
    margin:5px 10px 5px 10px;
    width:80px;
    height:80px;
}

JavaScript

(function($, ko) {
    var _dragged, _hasBeenDropped, _draggedIndex;
    ko.bindingHandlers.drag = {
        init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
            var dragElement = $(element);
            var dragOptions = {
                helper: function() {
                    return dragElement.clone().addClass("ui-dragon");
                },
                revert: true,
                revertDuration: 0,
                start: function() {
                    _hasBeenDropped = false;
                    _dragged = ko.utils.unwrapObservable(valueAccessor().value);
                    if ($.isFunction(valueAccessor().value)) {
                        valueAccessor().value(undefined);
                        dragElement.draggable("option", "revertDuration", 500);
                    } else if (valueAccessor().array) {
                        _draggedIndex = valueAccessor().array.indexOf(_dragged);
                        valueAccessor().array.splice(_draggedIndex, 1);
                    }
                },
                stop: function(e, ui) {
                    if (!_hasBeenDropped) {
                        if ($.isFunction(valueAccessor().value)) {
                            valueAccessor().value(_dragged);
                        } else if (valueAccessor().array) {
                            valueAccessor().array.splice(_draggedIndex, 0, _dragged);
                        }
                    }
                },
                cursor: 'default'
            };
            dragElement.draggable(dragOptions).disableSelection();
        },
        update: function(element, valueAccessor, allBindingsAccessor, viewModel) {
            var dragElement = $(element);
            var disabled = !! ko.utils.unwrapObservable(valueAccessor().disabled);
            dragElement.draggable("option", "disabled", disabled);
        }
    };

    ko.bindingHandlers.drop = {
        init: function(element, valueAccessor,...