Drag and drop DOM elements in reactive sorted lists


#1

I’m working on an app with a draggable list of components, which are backed by a collection. The desired functionality is that when the user drags around these components to reorder them, their ‘order’ property is updated to reflect this, and the document updates accordingly. The dilemma is: once a drag and drop is completed, and the rank of the relevant component is updated, blaze will attempt to re-render the list of components to maintain rank order. However, since the components was already physically moved in jqueryUI, Blaze ends up moving it back, so it gets swapped twice and remains in the same place, out of sync, until a refresh.

The first solution is to cancel the jqueryUI drop event and let the changes to the data alone drive the re-ordering of the list. This seems to be what is recommended by meteor. It worked okay at first, but as the accompanying data update become more complicated (e.g. adjusting the rank of the other components once is dragged), the delay has become noticable, so you’ll drop an item, see it snap back to its origin (because jqueryUI’s drop is canceled), then a half second later it jumps back to its correct new place as blaze updates it. The other problem is that these components are focusable, and when a move like this is done, the component in slot n retains focus, instead of focus moving around with a components as it is moved around. Essentially, with this method, all UI state not reflected in the data is lost on a drag.

The other option, to forgo reactiviy altogether and rely only on jqueryui draggable, is not really workable either. We want these component lists to be editable by multiple people at once, and updated in real time as people move them around.

Has anybody encountered a problem like this? What was your solution?


#2

Does anyone have any suggestions for this? We still can’t figure out how to prevent the flicker when we drag and drop elements in these sorted lists, which is providing a subpar UX.

Any suggestions are greatly appreciated!

EDIT:

I found this related thread: https://forums.meteor.com/t/is-it-possible-to-implement-drag-drop-like-in-trello/
With a link to this project which appears to have achieved this using the jquery sortable library: https://github.com/chris-ramon/minitrello


#3

@kahmali What I’ve done in the past to address that issue is populate my UI lists with a non-reactive array (or whatever datatype the particular UI component needs) which is initially populated from the Mongo Collection using fetch(). Then I set up an observer on the Mongo Collection :

       MyCollection.find().observeChanges({
            changed: function(id, doc) {
               //check if the order property of the Collection item is different from your local copy.
              // If has changed, update your local non-reactive array and re-render your list.
              // If no change, do nothing, your view is up to date.
              
            }
           // handle added and removed as needed.
           added:function(id,doc){}
           removed: function(id){}  
}

One of the ways I wrap my head around it is to think about it like “Did I make this change? If so, just double check my data is up to date, but I probably don’t need to re-render.” “Did someone else make this change ? If so I need to update my local collection and re-render.”

This same general approach worked for me when handling a textarea that multiple users can edit at the same time.


#4

Thanks @looshi! That sounds very promising.


#5

Thanks for the suggestion! Much appreciated


#6

@kahmali @anthonyreid
You can also address this issue server side in a custom publication. Don’t send DDP change messages down to the user who made the last change. Basic logic is to store a lastModifiedBy on your Document and compare it to Meteor.userId() in a custom publication.

There is a good example of a custom publication in the meteor docs http://docs.meteor.com/#/full/meteor_publish , ‘counts-by-room’. It has a different purpose but the basic idea is there ( also they’re only handling added and removed, but you’d want to handle ‘changed’ on the document as well )
I forget all the specifics, but at one point for a particular document that many users could edit, it was easier for me to manage this type of issue server-side in a custom publication. I’m just mentioning it in case the client-side handling doesn’t work out or becomes too complicated. I think it’s best to handle it client-side if you can.


#7

Thanks for the additional info! We’re trying to implement it client-side for now, and if that gives us any trouble than we will definitely try the custom publication. It’s a bit easier to reason about, but I agree with trying to offload this work to the client. Thanks again for all your help @looshi!


#8

I was always thinking the method call on client side was quite instant.
Are you seeing it flicker even when you use stub method on client instead of waiting for whole DDP turnaround ?