Closed PhillippOhlandt closed 9 years ago
What does it mean to prevent? Sortable — based on DOM manipulation elements.
I just need the drag and drop feature and the info which items was changed. VueJS will do the DOM manipulation then.
Then you should not be used Sortable
.
P.S. https://github.com/RubaXa/Sortable/issues/176
You can use Sortable with vuejs. Remove the dragged element and rebuild your data array:
Sortable.create(this.el, {
animation: 150,
handle: '.uk-nestable-handle',
onEnd: function (e) {
// Remove the dragged item from dom
e.item.remove()
// Clone container
var clone = _a.clone(self._scope.model[self._scope.$key])
self._scope.model[self._scope.$key] = []
// Move to new position
var swappedEl = clone.splice(e.oldIndex, 1)[0]
clone.splice(e.newIndex, 0, swappedEl)
// On next Tick update with clone
self._scope.$nextTick(function() {
self._scope.model[self._scope.$key] = clone
})
}
})
Is there any clean way to achieve this? There is no reason to not have a flag to disable DOM manipulation...
@mgrandl Just in case you ever run into this I had a similar situation with Vue.js and Vue-Sortable.js Seems the component became disconnected.
My temp solution was creating an action (vuex) or you can do it against the local state for a component that forced an updated on the next tick, similar to what @onefriendaday shared. That refresh method runs on the onEnd event from the Sortable, it clones the last element in the list of items, then pop the last one and push the clone in. It is a bit messy but Vue-Sortable might need an updated to handle this properly.
I ran into the same issue when using SortableJS with Lit. This is my solution, which should work for other frameworks too. Note it's in Typescript but you can convert it to javascript easily (just remove the type information and the '!' that tell typescript a variable is not null).
let childNodes: ChildNode[] = [];
Sortable.create(myContainer, {
onStart: (e) => {
const node = e.item as Node;
// Remember the list of child nodes when drag started.
childNodes =
Array.prototype.slice.call(node.parentNode!.childNodes);
// Filter out the 'sortable-fallback' element used on mobile/old browsers.
childNodes = childNodes.filter(
(node) =>
node.nodeType != Node.ELEMENT_NODE ||
!(node as HTMLElement).classList.contains('sortable-fallback')
);
},
onEnd: (e) => {
// Undo DOM changes by re-adding all children in their original order.
const node = e.item as Node;
const parentNode = node.parentNode!;
for (const childNode of childNodes) {
parentNode.appendChild(childNode);
}
if (e.oldIndex == e.newIndex) return;
// Then move the element using your own logic.
// (assuming this.myList is the array being sorted).
const element = this.myList.splice(e.oldIndex, 1)[0];
this.myList.splice(e.newIndex, 0, element);
// Ask for component redraw (if needed).
// This is how it's done in Lit. Adapt to your framework.
this.requestUpdate();
},
});
@miwucs you saved my night =D I've spent several hours fiddling with this, my use case is simular but I need to drag between different containers. I was on the same track with "resetting" the DOM after the from but your example help me the last bit =D
Figured I'll share my code here if anyone needs to drag between containers.
let fromChildNodes: ChildNode[] = [];
let toChildNodes : ChildNode[] = [];
this.sortable = Sortable.create(rowsContainer,{
onStart: (e) => {
const node = e.item as Node;
fromChildNodes = [];
// Remember the list of child nodes when drag started.
fromChildNodes = Array.prototype.slice.call(node.parentNode!.childNodes);
// Filter out the 'sortable-fallback' element used on mobile/old browsers.
fromChildNodes = fromChildNodes.filter(
(node) =>
node.nodeType != Node.ELEMENT_NODE ||
!(node as HTMLElement).classList.contains('sortable-fallback')
);
},
onMove: function (evt, originalEvent) {
// Move is called when the drag enters a new "to"-container.
// here we're storing the children on the two container to be
// used to restore it if there is a drop.
toChildNodes = [];
toChildNodes = Array.prototype.slice.call(evt.to.childNodes);
// Filter out the 'sortable-fallback' element used on mobile/old browsers.
toChildNodes = toChildNodes.filter(
(node) =>
node.nodeType != Node.ELEMENT_NODE ||
!(node as HTMLElement).classList.contains('sortable-fallback')
);
},
onEnd : (e)=> {
// Undo DOM changes by re-adding all children in their original order.
for(const childNode of fromChildNodes){
e.from.appendChild(childNode);
}
for (const childNode of toChildNodes) {
e.to.appendChild(childNode);
}
const element = this.myList.splice(e.oldIndex, 1)[0];
this.myList.splice(e.newIndex, 0, element);
// Ask for component redraw (if needed).
// This is how it's done in Lit. Adapt to your framework.
this.requestUpdate();
},
});
Is anyone handling a similar case here, but with Vue/Vuex?
@miwucs Thank you so much.. I had the same issue and am able to fix the issue with your approach.
i found a good solution here https://lightrun.com/answers/sortablejs-sortable-prevent-lib-from-manipulating-the-dom-vuejs event.item.remove() then the change to the dom by sortable will be reverted and the framework can do the rerendering
It may be not the best answer, but it's very simple: I use sortablejs-vue3 which provides a very low-level and lightweight wrapper for sortable. I listen to the changes and make the corresponding changes in my data and then I simply force Vue to recreate the sortable-component after each change:
Template:
<Sortable
v-if="renderSortable"
:list="your-list-with-data-goes-here"
item-key="id"
:options="{
group: {
name: 'components',
put: true
},
handle: '.handle',
'ghost-class': 'drag-ghost-component',
}"
@add="handleAdd"
@sort="handleSort"
@clone="cloneItem"
@remove="handleRemove"
>
</Sortable>
Methods:
cloneItem(event){
//make a copy of your data and append it to the clone-attribute
let copy=JSON.parse(JSON.stringify(this.your-list-goes-here[event.oldIndex]));
event.clone.listData=copy;
},
async updateSortable(){
//brutish way to force complete recreation of the sortable-component:
this.renderSortable=false;
await this.$nextTick();
this.renderSortable=true;
},
handleRemove(ev){
this.component.components.splice(ev.oldIndex,1);
this.updateSortable();
},
handleAdd(ev){
//insert the listData you appended via clone-method:
this.component.components.splice(ev.newIndex,0,ev.clone.listData);
this.updateSortable();
},
handleSort(ev){
//prevent cases that are already handled by add or remove:
if(!ev.item.parentElement || ev.from!==ev.to) return;
//swap the two elements:
let comp1=this.your-list-goes-here[ev.oldindex];
let comp2=this.your-list-goes-here[ev.newIndex];
this.your-list-goes-here[ev.oldIndex]=comp2;
this.your-list-goes-here[ev.newIndex]=comp1;
this.updateSortable();
},
Adding my simple solution, to see if I'm missing something. You'll need to add this to each of the onAdd/onUpdate events, possibly others if you're using them. So create a func that takes the SortableEvent:
// cancel the UI update so <framework> will take care of it
evt.item.remove();
if (evt.oldIndex !== undefined) {
evt.from.insertBefore(evt.item, evt.from.children[evt.oldIndex]);
}
@dotnetprofessional, dude you saved my day. Your answer is the only one worked for me. ❤️
in my case, putting it into onEnd
was enough.
You can skip the evt.item.remove()
, insertBefore
will move the item to the new place:
If the given node already exists in the document, insertBefore() moves it from its current position to the new position. (That is, it will automatically be removed from its existing parent before appending it to the specified new parent.)
https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore
Is there a way to prevent the lib from manipulating the DOM? I use it in a VueJS component and adjust my data array in the sortablejs
onSort
function. My list will be rendered based on my data array so it's a little but dump to do with with this lib too.And I have some problems with the manipulated DOM by this lib. See more here: https://github.com/yyx990803/vue/issues/1272