Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Sweet-Bob/3c41dacfdec9975faa9c8002e3594b84 to your computer and use it in GitHub Desktop.
Save Sweet-Bob/3c41dacfdec9975faa9c8002e3594b84 to your computer and use it in GitHub Desktop.
dragula
import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { run } from '@ember/runloop';
const { stringify } = JSON;
export default class ApplicationController extends Controller {
maxId = 6;
@tracked
listOne = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' }
];
@tracked
listTwo = [
{ id: 4, name: 'Item 4' },
{ id: 5, name: 'Item 5' },
{ id: 6, name: 'Item 6' }
];
get listOneString() {
return stringify(this.listOne, null, 2);
}
get listTwoString() {
return stringify(this.listTwo, null, 2);
}
@action
register(drake) {
this.drake = drake;
}
@action
pushToFirstList() {
const id = ++this.maxId;
const item = {
id,
name: `Item ${id}`
};
this.listOne.push(item);
this.listOne = this.listOne;
}
@action
handleDrop(itemEl, destListEl) {
// Get an array of avaliable list elements
const listEls = this.drake.containers;
// Get an array of item elements in the destination list
const destItemEls = [...destListEl.children];
// Get the (old) index of the item being dragged
const srcItemIndex = parseInt(itemEl.dataset.index, 10);
// Get the (old) index of the list the item was dragged from
const srcListIndex = parseInt(itemEl.dataset.listIndex, 10);
// Get the (new) index of the list that the item is being dragged to
const destListIndex = listEls.indexOf(destListEl);
// Get the (new) index of the item in the list it was dragged to
const destItemIndex = destItemEls.indexOf(itemEl);
// Get the actual source list
const srcList = srcListIndex === 0
? this.listOne
: this.listTwo;
// Get the actual destination list
const destList = destListIndex === 0
? this.listOne
: this.listTwo;
// Get the actual item
const item = srcList[srcItemIndex];
// Log the change that was made
console.log(
'Moved',
item.name,
'from position',
srcItemIndex,
'in list',
srcListIndex,
'to position',
destItemIndex,
'in list',
destListIndex,
);
// Now, apply the changes that were made to the dom
// to our actual list data structures...
// First, remove the item that was dragged
srcList.splice(srcItemIndex, 1);
// Then add it back at its new location
destList.splice(destItemIndex, 0, item);
// Remove the dragged DOM element because Ember will re-render us a new node
this.drake.remove(itemEl);
// Capture the new state
const listOne = this.listOne;
const listTwo = this.listTwo;
// Unrender the lists, because the DOM is out of sync with Ember's internals
// This is unfortunate :'-(
// But I can't find a better way
run(() => {
this.listOne = null;
this.listTwo = null;
});
// Re-render using the new state.
this.listOne = listOne;
this.listTwo = listTwo;
}
}
body {
margin: 10px;
}
ul {
margin-bottom: 25px;
list-style: disc;
margin-left: 20px;
}
.list {
margin-right: 20px;
}
.item {
margin: 5px auto;
white-space: nowrap
}
.ember-dragula {
display: flex;
}
pre {
font-size: 12px;
}
<ul>
<li>Click button to add a new item to the list</li>
<li>Move an item <em>after</em> that new item</li>
<li>Click the button again</li>
<li><strike>Notice how the new item is <em>not appended</em> to the DOM, despite the backing array being correct.</strike></li>
</ul>
<EmberDragula
@onReady={{this.register}}
@onDrop={{this.handleDrop}} as |d|
>
<d.Container class="list list-copy-1">
{{#each this.listOne as |item index|}}
<div
class="item"
data-index={{index}}
data-list-index="0"
>
{{index}} - {{item.name}}
</div>
{{/each}}
</d.Container>
<d.Container class="list list-copy-2">
{{#each this.listTwo as |item index|}}
<div
class="item"
data-index={{index}}
data-list-index="1"
>
{{index}} - {{item.name}}
</div>
{{/each}}
</d.Container>
</EmberDragula>
<br><br>
<button type='button' {{on 'click' this.pushToFirstList}}> Push a new item to first list</button>
<br><br>
<h4>List one</h4>
<pre>{{this.listOneString}}</pre>
<h4>List two</h4>
<pre>{{this.listTwoString}}</pre>
{{outlet}}
import Application from '../app';
import config from '../config/environment';
import { setApplication } from '@ember/test-helpers';
import { assign } from '@ember/polyfills';
import { start } from 'ember-qunit';
let attributes = {
rootElement: '#test-root',
autoboot: false
};
attributes = assign(attributes, config.APP);
let application = Application.create(attributes);
setApplication(application);
start();
{
"version": "0.17.1",
"EmberENV": {
"FEATURES": {},
"_TEMPLATE_ONLY_GLIMMER_COMPONENTS": false,
"_APPLICATION_TEMPLATE_WRAPPER": true,
"_JQUERY_INTEGRATION": true
},
"options": {
"use_pods": false,
"enable-testing": false
},
"dependencies": {
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.js",
"ember": "3.18.1",
"ember-template-compiler": "3.18.1",
"ember-testing": "3.18.1"
},
"addons": {
"@glimmer/component": "1.0.0",
"@zestia/ember-dragula": "10.0.4"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment