More than one draggable scroll container in jQuery UI
Published Thursday, March 12, 2009 in Development, jQuery
jQuery UI's draggable plug-in has a scroll-setting which, if set to true, automatically scrolls the container of a draggable object.
The plug-in looks for the first parent-element with overflow set to either auto or scroll of the draggable element if the scroll-option is set to true.
But if you have one area with elements that you can drag onto another area you might want both the draggable and the droppable area to scroll when dragging occurrs.
Thus, I've made a few small changes to the scroll-function of the draggable plug-in which allows for the scroll-option to be a comma-separated list of CSS-selectors which will act as scroll container(s).
What I basically had to do was just wrap the whole functionality in a loop and store more than one overflow-object in an array.
The stuff you want to change to allow this starts with:
$.ui.plugin.add("draggable", "scroll", {
And you want to change it into (I've commented by changes)
$.ui.plugin.add("draggable", "scroll", {
start: function(e, ui) {
var o = ui.options;
var i = $(this).data("draggable");
var j = 0; // Added this for looping
o.scrollSensitivity = o.scrollSensitivity || 20;
o.scrollSpeed = o.scrollSpeed || 20;
i.overflows = []; // Added this to hold all the overflow elements
// If the scroll-option is a string
if (typeof(o.scroll) == 'string') {
// Break out all the selectors and store the elements in the overflows-array
var selectors = o.scroll.split(',');
for (j = 0; selectors[j]; j++) {
i.overflows.push({
overflowY: $(selectors[j]),
overflowX: $(selectors[j])
});
}
} else {
// This is the same as before except I put it all in the first element of the overflows-array
i.overflows[0] = {
overflowY: function(el) {
do { if(/auto|scroll/.test(el.css('overflow')) || (/auto|scroll/).test(el.css('overflow-y'))) return el; el = el.parent(); } while (el[0].parentNode);
return $(document);
}(this),
overflowX: function(el) {
do { if(/auto|scroll/.test(el.css('overflow')) || (/auto|scroll/).test(el.css('overflow-x'))) return el; el = el.parent(); } while (el[0].parentNode);
return $(document);
}(this)
};
}
// Added the loop so that this happens to all overflow-elements
for (j = 0; i.overflows[j]; j++) {
if(i.overflows[j].overflowY[0] != document && i.overflows[j].overflowY[0].tagName != 'HTML') i.overflows[j].overflowYOffset = i.overflows[j].overflowY.offset();
if(i.overflows[j].overflowX[0] != document && i.overflows[j].overflowX[0].tagName != 'HTML') i.overflows[j].overflowXOffset = i.overflows[j].overflowX.offset();
}
},
drag: function(e, ui) {
var o = ui.options;
var i = $(this).data("draggable");
var j = 0; // Added this for looping
// Added this for-loop as well as put everything in the overflows-array
for (j = 0; i.overflows[j]; j++) {
if(i.overflows[j].overflowY[0] != document && i.overflows[j].overflowY[0].tagName != 'HTML') {
if((i.overflows[j].overflowYOffset.top + i.overflows[j].overflowY[0].offsetHeight) - e.pageY < o.scrollSensitivity)
i.overflows[j].overflowY[0].scrollTop = i.overflows[j].overflowY[0].scrollTop + o.scrollSpeed;
if(e.pageY - i.overflows[j].overflowYOffset.top < o.scrollSensitivity)
i.overflows[j].overflowY[0].scrollTop = i.overflows[j].overflowY[0].scrollTop - o.scrollSpeed;
} else {
if(e.pageY - $(document).scrollTop() < o.scrollSensitivity)
$(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
if($(window).height() - (e.pageY - $(document).scrollTop()) < o.scrollSensitivity)
$(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
}
if(i.overflows[j].overflowX[0] != document && i.overflows[j].overflowX[0].tagName != 'HTML') {
if((i.overflows[j].overflowXOffset.left + i.overflows[j].overflowX[0].offsetWidth) - e.pageX < o.scrollSensitivity)
i.overflows[j].overflowX[0].scrollLeft = i.overflows[j].overflowX[0].scrollLeft + o.scrollSpeed;
if(e.pageX - i.overflows[j].overflowXOffset.left < o.scrollSensitivity)
i.overflows[j].overflowX[0].scrollLeft = i.overflows[j].overflowX[0].scrollLeft - o.scrollSpeed;
} else {
if(e.pageX - $(document).scrollLeft() < o.scrollSensitivity)
$(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
if($(window).width() - (e.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
$(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
}
}
}
});
So to use more than one scroll container simply specify the scroll options like so:
$('li').draggable({scroll: '#drop-area, #drag-area', refreshPositions: true});
I'm also setting refreshPositions to true so that the droppable's positions are recalculated when user drags (you can try it without to see the problem otherwise).
jQuery.com has this to say about refreshPositions: "Caution: This solves issues on highly dynamic pages, but dramatically decreases performance." so use with care.
Note that this is for version 1.5.3, not sure if this is fixed or if it looks even remotely similar in latest version.





