views:

62

answers:

1

Here's my dilemma - I have a two-column list of elements (styled <div>s) that I need to make sortable. With elements that live in one or the other column, this is easy - I just set up two columns, place my elements where they need to be initially, mark them as "sortable," and let jQuery do its magic.

However, I have two elements in the list that need to span both columns. This, unfortunately, breaks the usability of the model. If I stick with my standard 2-column setup, I run into situations where the wide elements overlap or underlap with elements in the other column.

I also tried iterating through both columns to reposition elements so there would be no overlap, but then I realized another issue: my second column (on the right) has no knowledge of the elements positioned in the first column (on the left).

Here's a stripped down example of my markup:

<div id="wrapper">
    <div id="column-1" class="column" style="width:400px;">
        <div id="item-1" class="item" style="width:200px;">
        ...
        </div>
        <div id="item-2" class="item wide" style="width:400px;">
        ...
        </div>
        <div id="item-3" class="item" style="width:200px;">
        ...
        </div>
    </div>
    <div id="column-2" class="column">
        <div id="item-4" class="item" style="width:200px;">
        ...
        </div>
        <div id="item-5" class="item" style="width:200px;">
        ...
        </div>
    </div>
</div>

The way this should line up is something like this table:

-------------------
| item 1 | item 4 |
|      item 2     |
| item 3 | item 5 |
-------------------

And each item can be dragged and dropped anywhere else, with items 1, 3, 4, 5 moving interchangeably between the two columns, and with item 2 moving up and down as necessary. Realistically, you should also be able to end up with something like this:

-------------------
| item 1 |        |
|      item 2     |
| item 3 | item 5 |
| item 4 |        |
-------------------

But with a standard column model (i.e. the stock jQuery examples, the only resources I can find from several days of Google searches, and every attempt I've made thus far) this won't work.

So what can I do to implement this kind of layout with the jQuery UI Sortable plug-in so I can continue building my UI?

A: 

I invested more time and have hacked my way through to one possible solution. It still has its problems, but I'm at least getting most of the functionality I need.

The trick is to create multiple sortable elements and to nest them. So my document structure becomes:

<div id="wrapper">
<div id="macro-section-1" class="macro fixed" style="width:400px">
    <div class="column" style="width:400px;">
        <div id="item-1" class="item" style="width:200px;">
            ...
        </div>
    </div>
    <div class="column">
        <div id="item-4" class="item" style="width:200px;">
        ...
        </div>
    </div>
</div>
<div id="item-2" class="item wide macro mobile" style="width:400px;">
....
</div>
<div id="macro-section-2" class="macro fixed" style="width:400px;">
    <div class="column" style="width:400px;">
        <div id="item-3" class="item" style="width:200px;">
        ...
        </div>
    </div>
    <div class="column">
        <div id="item-5" class="item" style="width:200px;">
        ...
        </div>
    </div>
</div>
</div>

Then, you instantiate 2 different sortables - one to sort the smaller 1-column elements among themselves and the other to sort the 1-column "macro" elements. Note that there are 3 macro elements on this page:

  1. The first macro element is "fixed" and has two columns for smaller sortables
  2. The second macro element is "mobile," meaning it will also be a sortable
  3. The third macro element is "fixed" and has two columns for smaller sortables

The code to make this all work together is remarkably simple:

$('.column').sortable({
    forcePlaceholderSize: true,
    connectWith: '.column',
    handle: '.section-handle',
    cursor: 'move',
    opacity: 0.7
});

$('#wrapper').sortable({
    forcePlaceholderSize: true,
    handle: '.section-handle',
    cursor: 'move',
    opacity: 0.7
});

This connects the various .column elements so that the 1-column elements can be interchanged anywhere on the page. The second code block turns the entire #wrapper element into its own sortable, which allows you to reposition the large, 2-column element on the page as well.

Since I'm explicitly declaring the drag handle (a div with the section-handle class attached to each sortable element), you don't need a cancel property on your sortable() call ... the "fixed" macro elements don't have the drag handle to begin with, so they can't be manually repositioned. However, you can drag the "mobile" macro element (item-2) to anywhere else on the page without any problem.

The only issue I'm still facing (and it's more a personal frustration than anything else) is that the two sortables (#wrapper and .column) aren't aware of one another. So if you have the following layout:

-------------------
| item 1 |        |
|      item 2     |
| item 3 | item 5 |
| item 4 |        |
-------------------

And items 3, 4, and 5 are in the same sortable container, then you can't drag item 2 in between items 3 and 4. When dragging item 2, the page is aware of 3 sortable elements - the block containing item 1, the block you're dragging (item 2), and the block containing items 3, 4, and 5. So while from a usability perspective you should be able to place item 2 below items 3 and 5 ... the method I've outlined above doesn't allow you to do that.

So for now it works, but there's definitely massive room for improvement here.

EAMann