Dealing with overlapping jQuery sortable lists
Asked Answered
T

4

9

This is a bit of an obscure issue, but I'm using jQuery Sortables and trying to get two connected lists working together nicely when one is positioned as fixed. It all works fine until you scroll the page a bit such that the two lists end up positioned on top of each other. Then the lists seem to get confused as to which one should be receiving the item that's being dragged, meaning you get a bunch of jittering happening as it appears/disappears in each list.

It looks like the issue is that both lists are handling the mouse/sort events since the item being dragged is technically over both lists, but what I want is to have the overlayed list (i.e. the position: fixed one) swallow the events so that the underlying main list doesn't try to receive the item.

Here's the minimal code example:

<html>
<head>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.6/jquery-ui.min.js"></script>
    <style type="text/css">
        ul { list-style-type: none; padding: 0; float: left; }
        li { margin: 5px; padding: 5px; width: 500px; border: solid 1px #F00; background-color: #FFF; }
        #overlayed { position: fixed; top: 0; background-color: #000; margin: 20px; padding: 10px; }
        #overlayed li { width: 50px; float: left; }
    </style>
    <script type="text/javascript">
        $(function() {
            $("ul").sortable({ connectWith: "ul", opacity: 0.6 }).disableSelection();
        });
    </script>
</head>
<body>
<div id="overlayed"> 
    <ul>
        <li>Item X</li>
        <li>Item Y</li>
        <li>Item Z</li>
    </ul>
</div>
<br/><br/><br/><br/><br/>
<ul>
    <li>Item 01</li>
    <li>Item 02</li>
    <li>Item 03</li>
    <li>Item 04</li>
    <li>Item 05</li>
    <li>Item 06</li>
    <li>Item 07</li>
    <li>Item 08</li>
    <li>Item 09</li>
    <li>Item 10</li>
    <li>Item 11</li>
    <li>Item 12</li>
    <li>Item 13</li>
    <li>Item 14</li>
    <li>Item 15</li>
    <li>Item 16</li>
    <li>Item 17</li>
    <li>Item 18</li>
    <li>Item 19</li>
    <li>Item 20</li>
    <li>Item 21</li>
    <li>Item 22</li>
    <li>Item 23</li>
    <li>Item 24</li>
    <li>Item 25</li>
    <li>Item 26</li>
    <li>Item 27</li>
    <li>Item 28</li>
    <li>Item 29</li>
    <li>Item 30</li>
</ul>
</body>
</html>

So the question is, how do I fix it?

Turpin answered 4/11, 2010 at 0:10 Comment(0)
T
6

I ended up fixing this issue by extending the built in sortable functionality to create a fixedSortable that detects and selectively ignores hovering over lists when there's a overlay in place. For my purposes, I just hard coded the rules, since that suited my needs/time constraints, but you should be able to make it completely generic without too much effort.

Firstly here's the code (explanation below):

<html>
<head>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.6/jquery-ui.min.js"></script>
    <style type="text/css">
        ul { list-style-type: none; padding: 0; float: left; }
        li { margin: 5px; padding: 5px; width: 500px; border: solid 1px #F00; background-color: #FFF; }
        #overlayed { position: fixed; top: 0; background-color: #000; margin: 20px; padding: 10px; }
        #overlayed li { width: 50px; float: left; }
    </style>
    <script type="text/javascript">
        (function ($, undefined) {
            $.widget("ui.fixedSortable", $.ui.sortable, {
                _init: function () {
                    this.element.data("sortable", this.element.data("fixedSortable"));
                    return $.ui.sortable.prototype._init.apply(this, arguments);
                },
                _create:function() {
                    var result = $.ui.sortable.prototype._create.apply(this, arguments);
                    this.containerCache.sortable = this;
                    return result;
                },
                _intersectsWithPointer: function (item) {
//This line....
                    if (item.instance.element.hasClass("main-list") && this.positionAbs.top + this.offset.click.top < $(window).scrollTop() + 87) { 
                        return false;
                    }
                    return $.ui.sortable.prototype._intersectsWithPointer.apply(this, arguments);
                },
                _intersectsWith: function(containerCache) {
//Also this line....
                    if (containerCache.sortable.element.hasClass("main-list") && this.positionAbs.top + this.offset.click.top < $(window).scrollTop() + 87) {
                        return false;
                    }
                    return $.ui.sortable.prototype._intersectsWith.apply(this, arguments);
                }
            });
        })(jQuery);

        $(function() {
            $("ul").fixedSortable({ connectWith: "ul", opacity: 0.6 }).disableSelection();
        });
    </script>
</head>
<body>
<div id="overlayed"> 
    <ul>
        <li>Item X</li>
        <li>Item Y</li>
        <li>Item Z</li>
    </ul>
</div>
<br/><br/><br/><br/><br/>
<ul class="main-list" >
    <li>Item 01</li>
    <li>Item 02</li>
    <li>Item 03</li>
    <li>Item 04</li>
    <li>Item 05</li>
    <li>Item 06</li>
    <li>Item 07</li>
    <li>Item 08</li>
    <li>Item 09</li>
    <li>Item 10</li>
    <li>Item 11</li>
    <li>Item 12</li>
    <li>Item 13</li>
    <li>Item 14</li>
    <li>Item 15</li>
    <li>Item 16</li>
    <li>Item 17</li>
    <li>Item 18</li>
    <li>Item 19</li>
    <li>Item 20</li>
    <li>Item 21</li>
    <li>Item 22</li>
    <li>Item 23</li>
    <li>Item 24</li>
    <li>Item 25</li>
    <li>Item 26</li>
    <li>Item 27</li>
    <li>Item 28</li>
    <li>Item 29</li>
    <li>Item 30</li>
</ul>
</body>
</html>

If adapting this yourself, you'll need to change the two commented lines marked above. Basically the if statements should evaluate to true (and thus return false) if the item being hovered over (item.instance.element and containerCache.sortable.element) is not the overlay and the event (this) is occurring within the bounds of the overlay. This way the main list never receives events in the location of the page where the overlay is. So in this code the main list doesn't receive any events if they occur in the top 87 pixels of the screen, since that's where my fixed overlay was (which is not quite accurate in this dumbed down example, but hopefully it still makes sense).

Turpin answered 7/12, 2010 at 0:46 Comment(1)
dont know if this is useful to anyone but Ive droped Alconja's code into JSBin so you can see it working. jsbin.com/iqemid/2 all thanks to Alconja for this code ^^Mestee
E
3

I implemented the change to sortable for it to take the z-index into account.

See my fork here: https://github.com/david04/jquery-ui/commit/feaf94ebabacee4d11b5e98c412d795d68ca9d51 (10 lines of code, quite simple change)

You'll need to add the option compareZIndex=true when calling sortable.

Erato answered 16/5, 2014 at 13:27 Comment(0)
B
2

This is the bug I think you're encountering, and the fix is a lot simpler:

http://bugs.jqueryui.com/ticket/7065

Blithe answered 18/11, 2011 at 18:4 Comment(2)
I think that is the same bug, but the suggested fix does not cover all scenarios (including the one mentioned by the OP). I think the real fix requires determining the stacking context of all droppable containers under the cursor and selecting the topmost container in terms of z-order.Edwyna
Ah, probably true. The fix only covers lists that overlap due to overflow: hidden, not due to z-order. It seems like you need to sort the candidates by z-order first and then traverse them until you get a hit.Blithe
P
0

I ran into this problem on a page where I have one list vertically above a second list. Even though overflow is set to hidden, the unseen overflow of the top list is still blocking the move over event for the list below it (this can be confirmed by setting overflow to auto in the top list to help visualize the overlap). It's a z-index issue.

My fix was to place this code in the sortable stop event:

 //expliclty hide the overflow items
 $('.topList .item:gt(5)').hide();
 $('.topeList .item:first').show();

What I'm doing here is explicitly hiding the top list elements, leaving only the top 5 visible so that there is no DOM offset overlap between the top and bottom list.

Polysyllable answered 10/5, 2012 at 19:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.