podnov
podnov

Reputation: 115

jQuery UI sortable drag initiation is slow when container has hidden items

I have an unordered 'source' list that can contain up to around 1,000 list items. I want to be able to drag the items from the source list into a connected 'destination' list. I have everything working great until my source list gets filtered. I'm using the jquery quicksearch plugin to filter (search) my source list. The filter is accomplished by setting 'display:none;' on items that don't match the search.

When 1..n items in my source list are hidden, the drag operation is not fluid when initiated. Meaning, I click on the item I want to drag, move my mouse around the screen, but the item I'm dragging does not appear under my cursor until about a full second after I've initiated the drag.

For diagnosis, I've slimmed down my use case to just one list that I want to sort. I've completely eliminated the use of quicksearch by just hard coding half of my list items as hidden. I'm still able to reproduce the 'non-fluid' behavior. My example is here:

http://pastebin.com/g0mVE6sc

If I remove the overflow style from the list in my example, the performance is a little better, but still slower than I'd hope to see.

Does anyone have any suggestions for me before I start considering other options?

Thanks in advance.

Upvotes: 6

Views: 6231

Answers (6)

Pete_Gore
Pete_Gore

Reputation: 634

I got the same issue today with sortable + draggable table rows. The solution was simple : use visibility:collapse instead of display:none and removing the jQuery hide/show function for the hidden lines did the trick. It's a lot faster now. Hope I will help.

Upvotes: 0

Adriano Pedro
Adriano Pedro

Reputation: 437

More recently I came accross with this issue again... and found that my workaround was not the best solution anymore. Since the issue is the height... I've just create a CSS class with

.hidden {display: block; line-height:0; height: 0; overflow: hidden; padding: 0; margin: 0; }

and instead of setting the element hidden with just add this class and remove it to show/hide the element..

Regards, AP

Upvotes: 1

Adriano Pedro
Adriano Pedro

Reputation: 437

I came across with the same problem... I've searched for a solution, but it seems there is no solution to the jquery problem, only some workaround... I didn't found either a solution, just another workaround.

In my case I just created a general method to to a search in a sortable list, where on keyup, the code goes and do a find on every element in the list and was hiding it by fadeout if didn't match the value. This was working very well, but when you have hundreds of items in a list, the of hidden gets big enough to trigger the slow effect on the drag&drop. My solution was to reorder the list, bringing to the top the matched items.. Just remove and appendTo again...

This way I don't have problems with the hidden elements :)

Sorry this was no solution, but just another workaround..

Regards

Upvotes: 1

Danny
Danny

Reputation: 183

I was also having a similar problem, but with hidden drop containers instead of sortable items. Here is my solution applying Jordan's answer to both sortable items and their containers and simply replacing the relvent method.

$.ui.sortable.prototype.refreshPositions = function(fast) {  
    //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
    if(this.offsetParent && this.helper) {
        this.offset.parent = this._getParentOffset();
    }

    for (var i = this.items.length - 1; i >= 0; i--){
        var item = this.items[i];

        //We ignore calculating positions of all connected containers when we're not over them
        if(item.instance != this.currentContainer && this.currentContainer && item.item[0] != this.currentItem[0])
            continue;

        var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;

        if (!fast) {
            /********** MODIFICATION ***********/

            if(item.item.css('display') === 'none') {
                item.width = 0;
                item.height = 0;                    
            } else {
                item.width = t.outerWidth();
                item.height = t.outerHeight();
            }

            /********** END MODIFICATION ***********/
        }

        var p = t.offset();
        item.left = p.left;
        item.top = p.top;
    };

    if(this.options.custom && this.options.custom.refreshContainers) {
        this.options.custom.refreshContainers.call(this);
    } else {
        for (var i = this.containers.length - 1; i >= 0; i--){

            /********** MODIFICATION ***********/

            if (this.containers[i].element.css('display') == 'none') {
                this.containers[i].containerCache.left = 0;
                this.containers[i].containerCache.top = 0;
                this.containers[i].containerCache.width = 0;
                this.containers[i].containerCache.height = 0;
            } else {
                var p = this.containers[i].element.offset();
                this.containers[i].containerCache.left = p.left;
                this.containers[i].containerCache.top = p.top;
                this.containers[i].containerCache.width = this.containers[i].element.outerWidth();
                this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
            }

            /********** END MODIFICATION ***********/
        };
    }

    return this;
};

Upvotes: 5

Jordan Sendar
Jordan Sendar

Reputation: 61

If you still want to use display:none, this is a simple fix to the jQuery UI source specified in Didier's answer:

if (!fast) {
    if(item.item.css('display') === 'none') {
        item.width = 0;
        item.height = 0;
    }
    else {
        item.width = t.outerWidth();
        item.height = t.outerHeight();
    }
}

This is my very first post on stackoverflow, so do let me know if I messed something up.

Upvotes: 6

Didier Ghys
Didier Ghys

Reputation: 30666

As you can see on this jsferf example, calculating outerWidth()/outerHeight() (this is what the plugin does - see below) for hidden elements (with display none) is terribly slower than for visible elements, wether it is achieved by a style attribute or a class.

The only way I have found to bypass this and still achieve the same result is to set the height for the elements to hide to zero, instead of working with the display property, whether using the style atttibute or a class:

<li style="height: 0;">b</li>
<li class="hidden">b</li>

.hidden { height: 0 }

DEMO (with class) - DEMO (with style attr)


What's happenning with sortable when dragging an element ?

When starting dragging, the plugin refreshes the list of all items and recalculates positions of all elements. The plugin actually gets outerWidth and outerHeight:

_mouseStart: function(event, overrideHandle, noActivation) {
    ...
    //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
    this.refreshPositions();
    ...
}

refreshPositions: function(fast) {
    ...
    for (var i = this.items.length - 1; i >= 0; i--) {
        var item = this.items[i];
        ...
        if (!fast) {
            item.width = t.outerWidth();
            item.height = t.outerHeight();
        }
        var p = t.offset();
        item.left = p.left;
        item.top = p.top;
    };
    ...
    return this;
},​

Upvotes: 14

Related Questions