Open JaySunSyn opened 3 years ago
@JaySunSyn I've spent quite a lot of time trying to create a "proxy" of the onEnd
, onMove
-events that would revert the changes to the DOM when an element is dragged/dropped. I ended up here when I saw some nasty bugs in certain scenarios when lit "forgets" about a DOM-element and basically ends up with duplicates.
I've seen people in the Vue-community having similar issues: https://github.com/SortableJS/Sortable/issues/546#issuecomment-1894271252
I guess I have two questions:
Did you get anywhere with your attempts?
Did you find another solution?
I just wanted to get back here and share what I’ve learned and the approach that I took to get something that really works.
First of all, the problem that you outline @JaySunSyn is a real issue, these comments store information that Lit uses to be smart about how to update the DOM during update and we need to make sure that they are not tampered with.
The only way that I've found to make this work is to rely on Lit to do the DOM-reordering when the "drag" ends. So basically we need to restore the DOM to it's initial state when the drag ends, update the underlying array and make sure that Lit triggers a DOM-update.
I've created two elements drag-container
and drag-item
that work together and are rendered something like this
<drag-container group="controls" @dropped=${this.handleDropped}>
<div class="items">
${this.items.map((item)=>html`
<ns-drag-item>
${item.type}
</ns-drag-item>
`)}
</div>
</ns-drag-container>
Inside the drag-container
element I setup the Sortable, either on the drag-container
it self or on the first div
inside. However, I use a "proxied" version of Sortable that will store the DOM-structure when a drag starts and then revert it onEnd
or onClone
:
import Sortable from "sortablejs";
/*Lit-wrapper for SortableJS to revert DOM-manipulation*/
export const LitSortable = {
create : create
}
function create(container : HTMLElement, options : Sortable.Options) : Sortable {
let onStart = options.onStart;
let onMove = options.onMove;
let onEnd = options.onEnd;
/** Stores the child-structure of a container element.
* key = container element
* value = .childNodes of that container when drag started
*/
var map = new Map<HTMLElement,ChildNode[]>();
options.onStart = (e)=> {
// Create a new map to make sure no old childNode-state is kept from previous drags.
map = new Map<HTMLElement,ChildNode[]>();
// Store the childNodes of the container were the drag started.
map.set(e.from, [...Array.prototype.slice.call(e.from.childNodes)]);
if(onStart)
onStart(e);
}
options.onMove = (e,originalEvent) => {
// When entering this method the first, each container will have it's initial DOM-childNodes.
// We only add each container to the map the first time we see it.
if(!map.has(e.from))
map.set(e.from, [...Array.prototype.slice.call(e.from.childNodes)]);
if(!map.has(e.to))
map.set(e.to, [...Array.prototype.slice.call(e.to.childNodes)]);
if(onMove)
onMove(e,originalEvent);
}
options.onClone = (e) => {
if(!map.has(e.from))
map.set(e.from, [...Array.prototype.slice.call(e.from.childNodes)]);
if(!map.has(e.to))
map.set(e.to, [...Array.prototype.slice.call(e.to.childNodes)]);
}
options.onEnd = (e)=> {
// Here we loop over the map and restore the DOM-structure to it's initial state.
// Something other than SortableJS will have to trigger Lit to update and re-order the children.
map.forEach((children,container)=>{
container.replaceChildren();
children.forEach((child)=>{
container.appendChild(child);
});
});
if(onEnd)
onEnd(e);
}
return Sortable.create(container,options);
}
I then use the LitSortable
to create a new Sortable container:
import { LitSortable} from './lit-sortable';
this.sortable = LitSortable.create(container,options);
I really hope that this might help others that is looking to implement SortableJS with Lit.
@enkelmedia Thank you for this very helpful solution! On my end this solution works but I am seeing a pretty noticable flickering when dragging items around due to the DOM swapping that is taking place. Have you noticed this as well and know of any solution?
Actually after doing some more searching I found that this workaround actually does the trick with much less code and no screen flickering! https://lit.dev/playground/#gist=242f45fd2dbe21ecb6902f144686aae8
@jdcoldsmith nice shot !! Working greats here
Describe the bug
Sortable changes HTML comments in the DOM and therefore reference points for some template systems. Some HTML template systems use HTML comments to render dynamic content. See: https://github.com/lit/lit/blob/main/packages/lit-html/src/lit-html.ts#L188
Issue: After calling
destroy()
, the template system can't work with the HTML anymore.Workaround: Clear the whole HTML structure of the template and render the items again.
DOM before Sortable init:
DOM after Sortable init:
Expected behavior
The HTML comment structure keeps being intact.
Information
Not sure if this issue can ever get solved properly, but worth reporting I assumed :)