4

I'm using jQuery 1.7 and jQuery UI 1.8 from Google's CDN (so I have the latest versions).

I have two elements on my page, a header nav, and a content area with containers that correspond to each header list item. Currently, I have Sortable attached to both, allowing me to rearrange the visual order of both the navigation elements, and the content containers (separately). The behaviour I am trying to achieve is such that when I drag a navigation element, the content container moves with it, and when I drop the navigation element in a new position within the list, the content container sticks to its new position in the content area as well. The website I am working on is an intranet page, so I cannot provide a link, but I have included a diagram below:

Navigation:
NavOne...NavTwo...NavThree
Content:
ContentOne...ContentTwo...ContentThree

After dragging NavThree between NavOne and NavTwo, the whole arrangement should look like this:

Navigation:
NavOne...NavThree...NavTwo
Content:
ContentOne...ContentThree...ContentTwo

Is there a simple way to link the two Sortables so they behave in this fashion? It doesn't really have to be smooth or "live" (though I would really like it if it could be). I'm OK with it not refreshing the second sortable list until the reordering in the first list is completed (dropped).

I have already asked this question over at the jQuery UI forums and waited a week for a response: [LINK]

Here's a sample of the skeleton I'm testing this on before moving it to the actual project:

<!DOCTYPE html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<html>
<head>
<link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css" rel="stylesheet" type="text/css"/>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js"></script>
<style>
/* This is my CSS */
/* Basic reset -- outline for debugging */
* {padding: 0px; margin: 0px; outline: 1px solid rgba(0,0,0,0.1);}
html {width: 100%; height: 100%;}
body {width: 100%; height: 100%;}
/* Main styles */
.containerDiv {
  overflow-x: scroll;
  overflow-y: hidden;
  width: 800px;
  height: 600px;
  white-space: nowrap;
  float: left;
}
.itemDiv {
  width: 200px;
  height: 500px;
  display: inline-block;
}
.navBar ul, .navBar li {
  display: inline;
}
</style>
</head>
<body>
<!-- This is my HTML -->
<div class="navBar">
  <ul>
    <li id="nav1">Navigation 1</li>
    <li id="nav2">Navigation 2</li>
    <li id="nav3">Navigation 3</li>
  </ul>
</div>
<div class="containerDiv">
  <div class="itemDiv" id="item1">Item 1</div>
  <div class="itemDiv" id="item2">Item 2</div>
  <div class="itemDiv" id="item3">Item 3</div>
</div>
</body>
<script>
/* This is my JavaScript */
// Make containerDiv children sortable on X-axis
$(".containerDiv").sortable({
  axis: "x"
});
// Make nav children sortable on x-axis
$(".navBar").sortable({
  axis: "x",
  items: "li"
});
// Bind sorting of each (.navBar li) to its corresponding (.containerDiv .itemDiv):
$(".navBar li").bind("mouseup", function(event,ui){
// If I use "sortupdate" I get little to no response. If I use "mouseup",
// I can at least get an alert to return the value of thisId.
// Note: I am sad that console.log is blocked in Firefox,
// because its DOM inspector is better than Chrome's
  // the (.navBar li) have ids of "nav1, nav2" and so on,
  // and the (.containerDiv .itemDiv) have corresponding ids of "item1, item2"
  // which is my way of attempting to make it easier to link them.
  // This line is my attempt to target the (.containerDiv .itemDiv) which directly
  // corresponds to the (.navBar li) which is currently being sorted:
  var tempId = "#" + $(this).attr('id').replace("nav","item");
  // When this (.navBar li) is updated after a sort drag,
  // trigger the "sortupdate" event on the corresponding (.containerDiv .itemDiv):
  $(tempId).trigger("sortupdate");
});
</script>
</html>

In that week I've tried many different possible solutions and none of them have quite worked how I want them to. I feel like this should be relatively simple, but in a week of working on it (off and on), I haven't gotten anywhere useful with it.

I've already read the following related threads while looking for some insight:

I think that, failing all else, it should be possible for me to at least serialize the sortable, ajax it to the server, then have the second list update based on the serialized data, but I would like to keep this on the local browser to reduce calls to the server (until the user chooses to save the new arrangement to their account).

So, StackOverflow...is this a reasonable/achievable task that I'm trying to accomplish, or am I just bashing my head against the bricks here? Should I look for a different way to do it? I would like to avoid modifying the Sortable plugin if possible, but if I must, then I shall.

I've never used jsFiddle before, but I realized it would probably help, so here it is: http://jsfiddle.net/34u7n/1/

Community
  • 1
  • 1
Adrian
  • 727
  • 1
  • 9
  • 17

3 Answers3

1

AFAIK there is no built-in method or module available for jQuery UI Sortable so I wrote my own jQuery code to simulate such functionality.

This solution works only if you want to sync multiple sortable lists based on one master list and all list has equal number of items. Perhaps it can also made it to sync/move two way but I didn't try that.

<ul class="MasterList">
    <li>one</li>
    <li>two</li>
    <li>three</li>
</ul>

<ul class="SecondList">
    <li>one</li>
    <li>two</li>
    <li>three</li>
</ul>

<ul class="ThirdList">
    <li>one</li>
    <li>two</li>
    <li>three</li>
</ul>


$( '.MasterList' ).sortable
({
    start: function( event, ui )
    {
        // Store start order of the sorting element
        ui.item.OrderStart = ui.item.index();
    },
    stop: function( event, ui )
    {
        // Total number of sorting element
        var OrderTotal = $( '#ObjectiveListSortable li' ).length - 1;
        // Store drop order of the sorting element
        ui.item.OrderStop = ui.item.index();

        // Only do the element sync if there is any change of order. If you simply drag and drop the element back to same position then this prevent such cases.
        if ( ui.item.OrderStart != ui.item.OrderStop )
        {
            // Sorting element dropped to top
            if ( ui.item.OrderStop == 0 )
            {
                $( '.SecondList' ).eq( 0 ).before( $( '.SecondList' ).eq( ui.item.OrderStart ) );
                $( '.ThirdList' ).eq( 0 ).before( $( '.ThirdList' ).eq( ui.item.OrderStart ) );
            }
            // Sorting element dragged from top or dropped to bottom
            else if ( ui.item.OrderStart == 0 || ui.item.OrderStop == OrderTotal )
            {
                $( '.SecondList' ).eq( ui.item.OrderStop ).after( $( '.SecondList' ).eq( ui.item.OrderStart ) );
                $( '.ThirdList' ).eq( ui.item.OrderStop ).after( $( '.ThirdList' ).eq( ui.item.OrderStart ) );
            }
            else
            {
                $( '.SecondList' ).eq( ui.item.OrderStop - 1 ).after( $( '.SecondList' ).eq( ui.item.OrderStart ) );
                $( '.ThirdList' ).eq( ui.item.OrderStop - 1 ).after( $( '.ThirdList' ).eq( ui.item.OrderStart ) );
            }
        }
    }
});

Once the sync is done you can also trigger an event $( '.SecondList' ).trigger( 'refresh' ); on secondary lists so that the sortable widget recognizes the change in order of items done programmatically.

0

The reason that the sort update isn't working is that you're trying to attach the sortupdate event to each item in the list. sortupdate isn't fired on each item in the list, it's fired on the list itself, you can attach the event to the list as a whole and get the item that was moved as seen below.

$(".navBar").sortable({
  axis: "x",
  items: "li",
  update: function(event, ui) { onSort(event, ui); }
});

//this should now at least be firing when you move an item 
function onSort(event, ui)
{
    var tempId = $(ui.item).attr('id').replace("nav","item");
    alert(tempId);
}
Servy
  • 202,030
  • 26
  • 332
  • 449
  • I gave that a shot, and it does indeed produce an alert. When I tried to use that to trigger a `sortupdate` on the `.itemDiv`s, it doesn't seem to have an effect. I even attached an anonymous function to call `alert` when the `update` event fires on the `.itemDiv`s, which works when I sort the `.itemDiv`s, but it does not fire when called from the onSort function via `$(tempId).trigger("sortupdate")` or `$(tempId).trigger("update")`. Am I missing something? http://jsfiddle.net/adriantp/v88QS/1/ – Adrian May 18 '12 at 03:05
  • 1
    I don't see why you're expecting a call to `sortupdate` on the item in the other list to move the item in the other list to the position that you want it to be in. I would have thought that you'd need to check the new position of the recently moved item, and set the position of the corresponding item to be that new position. Not sure if that's me just not knowing jQuery well, but I just don't see how it ought to know where to move the item just by calling `sortupdate`. – Servy May 18 '12 at 03:17
  • I don't know why I was expecting it to do that either. After reflection it doesn't make sense; events fire as the result of actions, not the other way around. I guess my options are to either serialize the navbar and use the serialized data (through another function) to reorder the content items, or to extend the sortable plugin to allow me to pass it a new order and cause an update/refresh of the sortable. – Adrian May 18 '12 at 19:18
  • This looks like exactly my use case! Did you get any closer to a working solution in the meantime? – rvdb Sep 04 '12 at 20:53
0

If anyone is looking for this still, here is a working example which I hope will come in handy.

var index_start = 0;
var index_stop = 0;
var swap_element = 0;
var picked_element = 0;
$(document).ready(function() {
  $('#list1').sortable({
    start: function(event, ui) {
      index_start = ui.item.index(); //getting index of picked element
    },
    update: function(event, ui) {
      index_stop = ui.item.index(); //getting index where element is dropped
      /*Below steps will fetch the elements from List1 based on original Index ofpicked element in List2
       and its dropped Index position*/
      picked_element = $('.l2:eq(' + index_start + ')');
      swap_element = $('.l2:eq(' + index_stop + ')');

      swap_element.replaceWith(picked_element);
      //replaceWith method of javascript removes the picked_element from its original    
      //position & replaces swap_element with picked_element
      if (index_start > index_stop)
        picked_element.after(swap_element);
      else
        picked_element.before(swap_element);
    },
  });
});
#list2{
  width:20%;
  float:left;
  padding:10px;
}
#list2 .l1 {
  background-color:rgba( 0,0,0,0.2 );
}

#list1{
  width:30%;
  padding:10px;
  float:left;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.js"></script>
<div id="list1">
  <div class="l1">One</div>
  <div class="l1">Two</div>
  <div class="l1">Three</div>
  <div class="l1">Four</div>
  <div class="l1">Five</div>
  <div class="l1">Six</div>
</div>
<div id="list2">
  <div class="l2">One</div>
  <div class="l2">Two</div>
  <div class="l2">Three</div>
  <div class="l2">Four</div>
  <div class="l2">Five</div>
  <div class="l2">Six</div>
</div>

Source

Syfer
  • 4,262
  • 3
  • 20
  • 37