My Draggable, droppable, and sortable list
HTML
<script src="http://rniemeyer.github.com/KnockMeOut/Scripts/knockout-latest.debug.js"></script>
<script src="http://cloud.github.com/downloads/rniemeyer/knockout-sortable/knockout-sortable.js"></script>
<div id="main">
<h2>Field Types</h2>
<div class="container types" data-bind="template: { name: 'fieldTypeTmpl', foreach: Types }"></div>
</div>
<div id="results">
<h2>Table</h2>
<input type="text" data-bind="value: table.Name" />
<div class="container high" data-bind="sortable: { template: 'fieldTmpl', data: table.Fields, options: {update: onUpdate} }"></div>
</div>
<br style="clear: both;" />
<div id="results">
<h2>Table debug</h2>
<ul data-bind="foreach: table.Fields">
<li data-bind="text: Name"></li>
</ul>
</div>
<button data-bind="click: function(data, event) {console.log(ko.toJS($root.table, null, 2))}">Save</button>
<pre data-bind="text: ko.toJSON($root.selectedField)"></pre>
<!-- FIELD -->
<script id="fieldTmpl" type="text/html">
<div class="item">
<div class="handler" data-bind="click: function() {$root.selectField('');}">
<input data-bind="value: Name, click: function(e) {$root.selectField($data); e.stopPropagation(); }" />
<a class="delete-field" data-bind="click: function(){$root.deleteField($data)}">x</a>
</div>
<div style="display:none;" class="body" data-bind="visible: $root.selectedField() === $data">todo: settings</div>
</div>
</script>
<!-- FIELD TYPE -->
<script id="fieldTypeTmpl" type="text/html">
<div class="item" data-bind="drag: $data, dragOptions: {start: onDragStart, stop: onDragStop}">
<span href="#" data-bind="text: TypeName"></span>
</div>
</script>
CSS
body { font-family: arial; }
h2 { font-weight: bold; }
div { padding: 5px; margin: 5px; border: black 1px solid; }
/*p, a { font-size: .8em; }*/
ul { padding-bottom: 10px; }
.container { width: 225px; min-height: 50px; background-color: #AAA;}
.types{ background-color: orange; }
.trash { background-color: #000; }
.item { background-color: #DDD; min-width: 100px; }
.handler{ background-color: orange; padding-left: 20px; cursor: move; }
.item input { width: 100px; }
.body { zdisplay:none; }
.delete-field {float: right; width: 20px; height: 20px; background: red; cursor: pointer; text-align: center; }
#main { float: left; }
#results { float: left; width: 250px; }
JavaScript
"use strict";
var dropFieldType = null;
function FieldType(typeName, name) {
var self = this;
self.TypeName = ko.observable(typeName || "");
self.Name = ko.observable(name || this.TypeName());
self.createField = function () {
return new Field(
{
Name: this.Name(),
TypeName: this.TypeName()
}
);
}
self.onDragStart = function(event, ui) {
dropFieldType = ko.utils.domData.get(this, "ko_drag_data");
};
self.onDragStop = function(event, ui) {
dropFieldType = null;
};
}
function Field(json) {
this.Name = ko.observable(json.Name || "");
this.TypeName = ko.observable(json.TypeName || "");
}
var vm = function () {
var self = this;
self.table = {
Name: ko.observable(),
Fields: ko.observableArray([
/*new Field({Name: "text", Display: "Text", TypeName: "text"}),
new Field({Name: "image", Display: "Image", TypeName: "image"}),
new Field({Name: "number", Display: "Number", TypeName: "number"})*/
])
}
self.Types = ko.observableArray([
new FieldType("text"),
]);
self.selectedField = ko.observable();
self.selectField = function(field) {
if(self.selectedField() !== field)
self.selectedField(field);
};
self.deleteField = function(field) {
self.table.Fields.remove(field);
};
self.onUpdate = function (event, ui) {
if (dropFieldType) {
var el = ui.item[0],
field = dropFieldType.createField();
var targetIndex = ko.utils.arrayIndexOf(ui.item.parent().children(), el);
$(ui.item).remove();
self.table.Fields.splice(targetIndex, 0, field);
//self.selectField(field);
}
};
};
ko.bindingHandlers.drag = {
init: function(element, valueAccessor, allBindingsAccessor) {
//set meta-data
...