Ron Ron - 1 year ago 72
Ajax Question

Jquery sortable connected lists AJAX call

I'm working on a sortable list with parent items and sub items. The user should be able to move items between both of them, also move parent items including their sub items.

I figured out how do do this with a connected sortable list:

To achieve this, I didn't use the ID of the list:

$('#id_of_the_list').sortable({ ... })

but a general .sortable class:

$('.sortable').sortable({ ... })

My problem now is that the AJAX call just grabs the IDs of the parent items, not the sub items.

<script type="text/javascript">
$(document).ready(function () {
connectWith: 'ul.sortable',
update: function () {
type: 'post', data: $('.sortable').sortable('serialize'), dataType: 'script',
complete: function (request) {
}, url: '/tasks/sort_goal_task'

I think there is a pretty similar issue here, but I couldn't make this code work.

Thanks for any help,

Answer Source

This might be a sort of complex answer and not exactly what you were looking for, or more than.

Part of the initial issue was that it's very hard to reach the nested lists. Since they have no items in them and essentially render to ul with 0 for height. I would propose that you make block them with at least some height.

I also removed the padding and added it back manually in the li to give the spacial relationship between Task and Sub Task. I began to separate list with more classes, parent and child so that I could know which I was styling and working with.

This parent and child relationship lends itself to 2 sortable lists essentially, that happen to be nested. So I created options for both $(".parent").sortable() and $(".child").sortable() in an attempt to help organize them. This could also give us the ability to accept or reject (cancel) sortable items based on which type of task they are or where they originated.

This is in no way perfect, I found lots of little odd bugs here and there, but the core element is the ability to sort Tasks and Sub Tasks and drag a Sub Task to another Task.

Working Example Thus far:


<div id="container">
  <ul id="list" class="parent sortable">
    <li id="t1" class="task" data-id="l1">
      Task 1 <span class="add-subTask-button ui-icon ui-icon-plus"></span>
      <ul class="child sortable"></ul>
    <li id="t2" class="task" data-id="l2">
      Task 2 <span class="add-subTask-button ui-icon ui-icon-plus"></span>
      <ul class="child sortable">
        <li id="t2_1" class="subTask" data-id="l2-1">
          Sub Task 1 <span class="remove-subTask-button ui-icon ui-icon-minus"></span>
        <li id="t2_2" class="subTask" data-id="l2-2">
          Sub Task 2 <span class="remove-subTask-button ui-icon ui-icon-minus"></span>
    <li id="t3" class="task" data-id="l3">
      Task 3 <span class="add-subTask-button ui-icon ui-icon-plus"></span>
      <ul class="child sortable"></ul>

If you review the Demos, you'll see they are largely all ul and li style lists. Yes, you can use other things, but it's largely easier to treat each list item as what it is, the object item in the list, and adjust it's attributes for data and ids etc.


#container {
  width: 600px;
  margin: 30px auto;

ul.parent {
  font-size: 18px;
  width: 200px;
  margin: 0;
  padding: 0;

ul.parent li {
  list-style: none;
  margin-bottom: 2px;
  padding-left: 3px;
  border: 1px solid #999;
  border-radius: 6px;
  position: relative;

ul.parent li span.ui-icon {
  position: absolute;
  top: .25em;
  right: 2px;
  left: auto;
  cursor: pointer;

ul.child {
  display: block;
  font-size: 14px;
  padding-left: 30px;
  min-height: 1em;

ul.child li {
  list-style: none;
  border: 1px solid #bbb;
  margin-bottom: 1px;

.placeholder {
  background: yellow;
  height: 1em;

.handle {
  background: orange;
  width: 20px;
  height: 20px;
  float: left;
  margin: 0 5px 10px 5px;

.container {
  overflow: hidden

I didn't change a lot here just made some things more specific. I also added borders to give the user a visual queue or awareness of the groupings and their relationships.


$(document).ready(function() {
  // Make tasks sortable
  function fullSerial() {
    var par = {};
    $.each($("#list").sortable("toArray"), function(key, val) {
      par[val] = $("#" + val + " .child").sortable("toArray");
    return par;

    placeholder: 'placeholder',
    connectWith: ".sortable",
    over: function(e, ui) {
      if (ui.item.hasClass("subTask")) {

    placeholder: 'placeholder',
    accept: ".child",
    connectWith: ".sortable",

  $(".sortable").on("sortupdate", function() {

  $(".add-subTask-button").click(function() {
    var task = $(this).parent();
    var taskName = prompt("Enter Sub Task Name.");
    var newId = task.attr("id") + "_" + (task.find("ul.child li").length + 1);
    var subTask = $("<li>", {
      id: newId,
      class: "subTask",
      "data-id": newId
    }).html(taskName + "<span class='remove-subTask-button ui-icon ui-icon-minus'></span>");

  $(".remove-subTask-button").click(function() {
    var subTask = $(this).parent();
    var ulElem = subTask.parent();

Through testing, I found that the serialize method doesn't play well with nested sortable lists. So I made a function to build an object of Tasks with an array of of Sub Tasks as the element of each Task. I suspect using JSON.serialize() might get you something you can pass along, but you can always manipulate the fullSerial to return a serialized string from the object instead of the object itself.

You can see the two sortables. Since it was not clear from your OP if you wanted to allow Sub Tasks to be made into Tasks or not, there is an attempt to prevent such activity. I tried the same code in receive and it should revert the sort... but neither did. Also if you're crafty enough, you can drag a whole Task and it's Sub Tasks into a Sub Task position.

To be able to perform deeper testing, I added a method to add an item on the fly and dynamically generate/remove Sub Tasks.

I hope that helps. As I said upfront, it might be more than you needed, yet your question lead to a complex answer. Even with out the added elements.