KO Sortable Binding v4

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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.2/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.selection/1.0.1/jquery.selection.min.js"></script>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons&amp;_.css">
<button data-bind="click: add">
Add
</button>
<hr/>
<div class="car-park" data-bind="sortable: { multiColumn: true, boundField: CarPark, columnsField: 'Lanes', itemsField: 'Vehicles' }, foreach: CarPark().Lanes()">
      <div class="lane" data-bind="foreach: Vehicles()"> 
          <div class="vehicle"><i></i><span data-bind="text: ID"></span></div>
      </div>
</div>
<xmp data-bind="click: purge, html: JSON.stringify(ko.toJS(CarPark()), null, '\t')"></xmp>

CSS

.lane {
    width: 60px;
    min-height: 60px;
    padding-right: 20px;
    float: left;
    border-right: 1px dotted #000000;
  }
  
  .vehicle {
    margin: 10px;
    width: 40px;
    height: 40px;
    color: #000000;
    cursor: pointer;
    border: 1px solid transparent;
  }
  
  .vehicle:hover {
    color: #00aa00;
    cursor: move;
  }
  
  .vehicle span {
    font-family: Arial;
    font-weight: bold;
    background: #000000;
    color: #ffffff;
    padding: 1px 3px;
    font-size: 10px;
  }
  
  .vehicle i:before {
    font-family: Material Icons;
    content: 'directions_car';
    width: 40px;
    height: 40px;
    font-size: 40px;
    font-style: normal;
  }
  
  .sortable-placeholder {
    border: 1px dotted black;
    margin: 10px;
    width: 40px;
    height: 40px;
    background: rgba(255, 255, 255, 0.5);
  }
  
  xmp {
    float: right;
    font-size: 10px;
    font-weight: bold;
    background: #ffffff;
    padding: 4px;
    margin-right: 50px;
  }

JavaScript

var __i = 0;

function Model(data, mapping)
{
    var _this = this;
    var _vid = 7;

    this.CarPark = ko.observable();
    
    this.add = function()
    {
    	_this.CarPark().Lanes()[Math.floor(Math.random() * 3)].Vehicles.push({ ID: _vid++ });
    };

    ko.mapping.fromJS({
        CarPark: {
            Lanes: [
                {
                    ID: 'A',
                    Vehicles: [
                        { ID: 1 },
                        { ID: 2 },
                    ]
                },
                {
                    ID: 'B',
                    Vehicles: [
                        { ID: 3 }
                    ]
                },
                {
                    ID: 'C',
                    Vehicles: [
                        { ID: 4 },
                        { ID: 5 },
                        { ID: 6 }
                    ]
                },
            ]
        },
    }, {}, _this);

    this.purge = function()
    {
        _this.CarPark.valueHasMutated();
        _this.CarPark().Lanes.valueHasMutated();
    };
}


ko.bindingHandlers.sortable = {
    update:
    function(el, f_valueaccessor, allbindings, viewmodel, bindingcontext) 
    {
        var val = ko.unwrap(f_valueaccessor());
        var obs = ko.isObservable(f_valueaccessor()) ? f_valueaccessor() : null;
        var multicolumn = false;
        var placeholder = 'sortable-placeholder';
        var colsfield = null;
        var itemsfield = null;

        var options = {};

        if (typeof (val) == 'object')
        {
            multicolumn = val.multiColumn;
            obs = val.boundField;
            placeholder = typeof (val.placeholder) != 'undefined' ? val.placeholder : placeholder;
            options = val.options;
            colsfield = val.columnsField;
            itemsfield = val.itemsField;
            val = true;
        }

        obs && obs(); // Force KO to track this as a dependency
        var obscolumns = obs && colsfield ?...