I am using Dragula to create a set of drag and drop components in Ember. I pass a list of items through to a parent wrapper that contains multiple droppable buckets. This list of items is filtered initially so that they correct items are rendered in the correct bucket. Then Dragula is wired up so items can be dragged and dropped. When a drop event occurs, I try to update the underlying Ember object. This can cause the filter to be re-applied and some rendering to occur. The problem is the DOM has been manipulated by Dragula and is not the same as Ember thinks it should be and DOM nodes just disappear.
How can I get Ember and Dragula to play nice when they both think they own the DOM and its current representation? I've tried cancelling the draggle drop event and then letting Ember set values with limited success.
dnd-wrapper/template.hbs
{{yield (action "register")}}
dnd-wrapper/component.js
export default Ember.Component.extend({
drake: null,
buckets: [],
items: [],
initDragula: Ember.on('willInsertElement', function() {
this.set('drake', window.dragula());
}),
setupDragulaEvents: Ember.on('didInsertElement', function() {
this.get('drake').on('drop', (itemEl, destinationEl, sourceEl) => {
let dest = this.buckets.findBy('element', destinationEl);
let source = this.buckets.findBy('element', sourceEl);
let item = this.items.findBy('element', itemEl);
item.component.set('item.bucket', dest.component.get('value'));
});
}),
actions: {
register(type, obj) {
if(type === 'bucket') {
this.get('drake').containers.push(obj.element);
this.buckets.pushObject(obj);
}
else {
this.items.pushObject(obj);
}
}
}
});
dnd-bucket/template.hbs
<h2>bucket {{value}}</h2>
<ul>
{{#each filteredItems as |item|}}
{{dnd-item item=item register=register}}
{{/each}}
</ul>
dnd-bucket/component.js
export default Ember.Component.extend({
items: null,
registerWithWrapper: Ember.on('didInsertElement', function() {
this.register('bucket', {
component: this,
element: this.$('ul')[0]
});
}),
filteredItems: Ember.computed('[email protected]', function() {
return this.get('items').filterBy('bucket', this.get('value'));
})
});
dnd-item/template.hbs
{{item.title}}
dnd-item/component.js
export default Ember.Component.extend({
registerWithWrapper: Ember.on('didInsertElement', function() {
this.register('item', {
component: this,
element: this.$()[0]
});
})
});
Online Demo: http://ember-twiddle.com/c086d2853a926c310a23
GitHub Demo: https://github.com/RyanHirsch/dragula-ember-example
I ran into the exact same problem you're describing. I've spent hours trying different things to fix it, and I think I finally came up with a hacky but workable solution. The meat of it is that I had to force the 'source' bucket to rerender its items after triggering the drop event.
Unfortunately I had to use brute force to get rerendering to work, calling rerender on any of the parent views didn't work. Ember thinks the item(s) that disappeared from the DOM are still there and rendered properly.
I ended up wrapping the filteredItems list in the dnd-bucket template with {{#if listVisible}}{{/if}}
. If you then set itemsVisible false and true it forces the list to rerender and repairs the DOM damage. I also had to make sure these sets were before and after render in the runloop.
resetView: function() {
Ember.run.scheduleOnce('render', this, () => {
this.set("listVisible", false);
});
Ember.run.scheduleOnce('afterRender', this, () => {
this.set("listVisible", true);
});
}
In my case I triggered resetView after updating the models in the drop callback. Although this fix is hacky, the visual result is acceptable. If DOM elements did disappear, they get replaced which is a little jarring. But for 99.9% of the time when no DOM elements went missing, there are no visual glitches at all.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With