KnockOut JS drag drop

by amitava82

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>
<script src="http://ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js"></script>
<a href="#" data-bind="viewer: $root">Debug</a>
<hr/>

<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>

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.viewer = {
    init: function(element, valueAccessor) {
        var win,
            updateText = function(content) {
               var el = win.document.getElementById("output");
               el['innerText' in el ? 'innerText' : 'textContent'] = content;
            };
            
        ko.utils.registerEventHandler(element, "click", function(event) {
            win = win || window.open();
            win.document.writeln("<pre id='output'></pre>"); 
            updateText(JSON.stringify(ko.toJS(valueAccessor()), null, 2));       
        });
        
        //set up a throttled computed observable to handle updates
        ko.computed({
            read: function() {
                //make sure we have a subscription to all observables in valueAccessor()
                var content = JSON.stringify(ko.toJS(valueAccessor()), null, 2); 
                if (win) {
                    if (!win.closed) {
                       updateText(content);
                    } else {
                        //clear it out
                        win = null;
                    }          
                }
            },
            disposeWhenNodeIsRemoved: element            
        }).extend({ throttle: 1 });
    }
};

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,...