Draggable, droppable, and sortable list

http://www.knockmeout.net/2011/05/dragging-dropping-and-sorting-with.html

by Carl Bowen

HTML

<script src="http://rniemeyer.github.com/KnockMeOut/Scripts/jquery.tmpl.js"></script>
<script src="http://rniemeyer.github.com/KnockMeOut/Scripts/knockout-latest.debug.js"></script>
<div id="main">
  <h2>High Priority Tasks</h2>
  <div class="container high" data-bind="template: { name: 'taskTmpl', foreach: highPriorityTasks, templateOptions: { parentList: highPriorityTasks} }, sortableList: highPriorityTasks"></div>

  <h2>Tasks</h2>
  <div class="container" data-bind="template: { name: 'taskTmpl', foreach: normalPriorityTasks, templateOptions: { parentList: normalPriorityTasks} }, sortableList: normalPriorityTasks"></div>

  <a href="#" data-bind="click: addTask">Add Task</a>
  <hr />
  <h2>Trash</h2>
  <div class="container trash" data-bind="sortableList: trash"></div>

  <script id="taskTmpl" type="text/html">
    <div class="item" data-bind="sortableItem: { item: $data, parentList: $item.parentList }">
      <a href="#" data-bind="text: name, click: function() { viewModel.selectTask($data); }, visible: $data !== viewModel.selectedTask()"></a>
      <input data-bind="value: name, visibleAndSelect: $data === viewModel.selectedTask(), event: { blur: function() { viewModel.selectTask(''); } }" />
    </div>
  </script>

</div>

<div id="results">
  <h2>High Priority Tasks</h2>
  <ul data-bind="template: { name: 'resultTmpl', foreach: highPriorityTasks }"></ul>
  <h2>Tasks</h2>
  <ul data-bind="template: { name: 'resultTmpl', foreach: normalPriorityTasks }"></ul>
</div>

<script id="resultTmpl" type="text/html">
  <li data-bind="text: name"></li>
</script>

<button type="button" onclick="clicky();">Click Me!</button>

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: 125px;
  min-height: 50px;
  background-color: #AAA;
}

.high {
  background-color: red;
}

.trash {
  background-color: #000;
}

.item {
  background-color: #DDD;
  cursor: move;
}

.item input {
  width: 100px;
}

#main {
  float: left;
}

#results {
  margin-left: 175px;
  width: 150px;
}

JavaScript

function Task(name) {
  this.name = ko.observable(name);
};

var viewModel = {
  highPriorityTasks: ko.observableArray([]),
  normalPriorityTasks: ko.observableArray([]),
  selectedTask: ko.observable(),
  selectTask: function(task) {
    this.selectedTask(task);
  },
  addTask: function() {
    var task = new Task("new");
    this.selectedTask(task);
    this.normalPriorityTasks.push(task);
  },
  trash: []
};
//localStorage.setItem("state","");
//localStorage.removeItem("state");
if ("state" in localStorage) {
  var state = JSON.parse(localStorage.getItem("state"));

  for (i in state.highPriorityTasks) {
    var task = new Task(state.highPriorityTasks[i].name);
    viewModel.highPriorityTasks.push(task);
  };

  for (i in state.normalPriorityTasks) {
    var task = new Task(state.normalPriorityTasks[i].name);
    viewModel.normalPriorityTasks.push(task);
  };

} else {
  viewModel.highPriorityTasks = ko.observableArray([
    new Task("Get dog food"),
    new Task("Mow lawn"),
    new Task("Fix car")
  ]);
  viewModel.normalPriorityTasks = ko.observableArray([
    new Task("Fix fence"),
    new Task("Walk dog"),
    new Task("Read book")
  ]);
  localStorage.setItem("state", ko.toJSON(viewModel));
}


//connect items with observableArrays
ko.bindingHandlers.sortableList = {
  init: function(element, valueAccessor, allBindingsAccessor, context) {
    $(element).data("sortList", valueAccessor()); //attach meta-data
    $(element).sortable({
      update: function(event, ui) {
        var item = ui.item.data("sortItem");
        if (item) {
          //identify parents
          var originalParent = ui.item.data("parentList");
          var newParent = ui.item.parent().data("sortList");
          //figure out its new position
          var position = ko.utils.arrayIndexOf(ui.item.parent().children(), ui.item[0]);
          if (position >= 0) {
            originalParent.remove(item);
            newParent.splice(position, 0, item);
          }
         ...