The jQuery UI draggable plug-in is used within liteGrid by the DraggableRowsModule. It enables users to rearrange the order of rows as well as to place rows into a hierarchy (if the TreeGridModule is installed).
Recently a request came through to make liteGrid scroll when you are dragging rows. This becomes very important in large grids when you want to make a newly added row a child of the first row in the grid. Without scrolling, you must resort to repeatedly drag-and-dropping your way up the list. This feature sounded easy enough to implement, but like most things dealing with tables, HTML, and CSS, it wasn’t.
First there was the draggable’s ‘scroll’ option. In the demo, using this option achieves exactly the result I was after, but it didn’t work for liteGrid. As usual, I believe this to be a limitation of using the plug-in with a table element instead of with a standard block-level element. So, until I find time to rewrite the liteGrid rendering to use div’s instead of a table, that solution was out of the question.
Adding the logic necessary to make liteGrid scroll was actually quite easy to do. In the DraggableRowsModule, I added the following to the drag event handler:
//If we're dragging outside of the grid, scroll. We can't //use the built-in scroll capabilities of draggable because //its not the table that we need to scroll, but rather the parent. var body = base.liteGrid.bodyDiv; var bodyOffset = body.offset(); var bodyHeight = body.height(); var currentScroll = body.attr("scrollTop"); var rowOffset = ui.helper.offset() //If we're above the body div, scroll up. if (rowOffset.top < bodyOffset.top) { body.attr("scrollTop", currentScroll - base.options.dragIncrement); } else if (rowOffset.top > (bodyOffset.top + bodyHeight)) { body.attr("scrollTop", currentScroll + base.options.dragIncrement); }
This worked, except when the drag handle caused the window to resize. When that happened, suddenly the drop targets no longer lined up with the correct elements. You can see what I mean here:
After some digging, I found the ‘refreshPositions’ option in the draggable plug-in, but the documentation states it can have major performance impacts. Well, the documentation is right. For a grid with a lot of rows, the performance was not acceptable. What I really wanted was a way to tell draggable to update the positions on demand as opposed to every time; after all, unless the grid scrolls, there’s no need to refresh the positions. There is a work item queued up to add this functionality to jQuery UI, but no activity has been performed on it to date. [sad faec]
I tried a few different means of hacking it but ran into failures every time before I finally settled on this solution:
//This is wired in by the module's init function base.dragGhost = function(event, ui) { var dragHandle = $(event.target); //If dragging caused a scroll, the positions were refreshed already, //so we can turn thi sback off.. if (dragHandle.draggable('option', 'refreshPositions')) { dragHandle.draggable('option', 'refreshPositions', false) } //--SNIP: Boring code to handle adding children to a row-- //--SNIP: More boring code to grab offsets, see snippet above if (rowOffset.top < bodyOffset.top) { //Here's the interesting part dragHandle.draggable('option', 'refreshPositions', true); body.attr("scrollTop", currentScroll - base.options.dragIncrement); } else if (rowOffset.top > (bodyOffset.top + bodyHeight)) { //And again dragHandle.draggable('option', 'refreshPositions', true); body.attr("scrollTop", currentScroll + base.options.dragIncrement); } }
When the grid scrolls, I temporarily enable the refreshPositions option. The next time the drag handle moves, jQuery UI will refresh the drop target positions as desired, and my event handler disables the option again. This achieves the desired effect: when dragging-and-dropping, the droppable positions are only re-calculated when the grid is scrolling, otherwise they are left alone. This can still cause a slow down during grid scrolling, but it’s the only solution I’ve found so far.
This feature is available now on the liteGrid trunk.
Hey blogger, thank you very much for providing this article.. I found it heroic. Sincerely, Neil.
Impressive blog post!!