Skip to content

Instantly share code, notes, and snippets.

@emattias
Last active April 16, 2022 10:59
Show Gist options
  • Save emattias/5cf683aa329d42104fdab490102b035f to your computer and use it in GitHub Desktop.
Save emattias/5cf683aa329d42104fdab490102b035f to your computer and use it in GitHub Desktop.
new
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import { action, get, set, setProperties, computed } from '@ember/object';
import { recordIdentifierFor } from '@ember-data/store';
import { dependentKeyCompat } from '@ember/object/compat';
import { run, scheduleOnce } from '@ember/runloop';
import { tracked } from '@glimmer/tracking';
export default class ApplicationController extends Controller {
@service store;
@tracked mutationObserver = null;
@tracked mutationObserverOn = false;
@tracked removedNodes = [];
@action
stringify(a){
if(a.length > 0){
debugger}
return JSON.stringify(a)
}
@action
async out(el){
el.style.outline = '3px solid green';
await new Promise((resolve) => {
setTimeout(() => { resolve() }, 2000)
})
}
@action
handleMutationObserverToggle() {
this.mutationObserverOn = !this.mutationObserverOn
if(this.mutationObserverOn) {
this.observer = new MutationObserver((mutationsList) => {
mutationsList.reduce((acc, mutation) => {
this.removedNodes.setObjects(Array.from(mutation.removedNodes));
}, []);
});
this.observer.observe(this.dragulaContarinerEl, { subtree: false, childList: true });
} else {
this.observer.disconnect()
}
}
@tracked dragulaContarinerEl = null
@action
didInsert(el) {
this.dragulaContarinerEl = el
}
appName = 'Ember Twiddle';
constructor(){
super(...arguments)
const bRecord = this.store.createRecord('b', { title: 'bRecord', rowOrderPosition: '2' })
const bRecord2 = this.store.createRecord('b', { title: 'bRecord2', rowOrderPosition: '1' })
const bRecord3 = this.store.createRecord('b', { title: 'bRecord3', rowOrderPosition: '3' })
this.aRecord = this.store.createRecord('a', {
bRelation: [bRecord, bRecord2, bRecord3]
})
}
recordIdentifierFor = recordIdentifierFor;
// @computed('aRecord.bRelation.hasDirtyRelations')
//@dependentKeyCompat
get bRecords() {
const rows = this.aRecord.bRelation.toArray()
rows.sort((a, b) => {
return a.rowOrderPosition > b.rowOrderPosition ? 1 : -1;
});
return rows;
}
@action
bRecordDelete(bRecord) {
bRecord.deleteRecord()
}
@action
onDrop(el, target) {
let draggedBRecord = get(this, 'bRecords').find((b) => {
return (
el.dataset.stageRecordIdentifier ===
recordIdentifierFor(b).toString()
);
});
Array.from(target.children).forEach((el, i) => {
const bRecord = this.bRecords.find(
(rowBRecord) =>
recordIdentifierFor(rowBRecord).toString() ===
el.dataset.stageRecordIdentifier
);
set(bRecord, 'rowOrderPosition', i+1);
});
const clone = this.aRecord.bRelation.toArray()
this.aRecord.bRelation.clear()
run.next(() => {
this.aRecord.bRelation.setObjects(clone)
})
/*this.aRecord.bRelation.addObject(this.store.createRecord('b', { title: 'bRecord4', rowOrderPosition: '4' }))
*/
}
dragulaconfig = {
options: {
copy: false,
revertOnSpill: false,
removeOnSpill: false,
},
enabledEvents: ['drag', 'drop'],
};
}
import Model from 'ember-data/model';
import { belongsTo, hasMany } from 'ember-data/relationships';
/*
import attr from 'ember-data/attr';
import { belongsTo, hasMany } from 'ember-data/relationships';
*/
export default class extends Model {
@hasMany('b') bRelation;
}
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
/*
import attr from 'ember-data/attr';
import { belongsTo, hasMany } from 'ember-data/relationships';
*/
export default class extends Model {
@attr('string') text;
@attr('string') rowOrderPosition;
}
/* eslint-disable ember/use-ember-get-and-set */
import Modifier from 'ember-modifier';
// fork of https://github.com/peec/ember-css-transitions/blob/master/addon/modifiers/css-transition.js
// but with `in` and `out` callbacks for animation with web animation api instead of css classes
export default class CustomModifier extends Modifier {
clone = null;
parentElement = null;
nextElementSibling = null;
/**
* @type {(HTMLElement|undefined)}
* @private
* @readonly
*/
get el() {
return this.clone || this.element;
}
didInstall() {
this.parentElement = this.element.parentElement;
this.nextElementSibling = this.element.nextElementSibling;
this.guardedRun(this.transitionIn);
}
didUpdateArguments() {
if (
this.element &&
this.nextElementSibling &&
this.element.isConnected &&
!this.nextElementSibling.isConnected
) {
this.nextElementSibling = this.element.nextElementSibling;
}
}
willDestroy() {
this.guardedRun(this.transitionOut);
}
/**
* Adds a clone to the parentElement so it can be transitioned out
*
* @private
* @method addClone
*/
addClone() {
let original = this.element;
let parentElement = original.parentElement || this.parentElement;
let nextElementSibling =
original.nextElementSibling || this.nextElementSibling;
if (
nextElementSibling &&
nextElementSibling.parentElement !== parentElement
) {
nextElementSibling = null;
}
let clone = original.cloneNode(true);
clone.setAttribute('id', `${original.id}_clone`);
parentElement.insertBefore(clone, nextElementSibling);
this.clone = clone;
}
/**
* Removes the clone from the parentElement
*
* @private
* @method removeClone
*/
removeClone() {
if (this.clone.isConnected && this.clone.parentNode !== null) {
this.clone.parentNode.removeChild(this.clone);
}
}
*transitionIn() {
if (this.args.named.in && !this.args.named.skipIn) {
yield this.args.named.in(this.el);
}
}
*transitionOut() {
if (this.element && this.nextElementSibling && !this.element.isConnected &&
!this.nextElementSibling.isConnected) {
//alert('this.element && this.nextElementSibling are not connected')
}
if (this.args.named.out && !this.args.named.skipOut) {
// We can't stop ember from removing the element
// so we clone the element to animate it out
this.addClone();
yield this.args.named.out(this.el, this.clone);
this.removeClone();
// }
this.clone = null;
}
}
async guardedRun(f, ...args) {
let gen = f.call(this, ...args);
let isDone = false;
// stop if the function doesn't have anything else to yield
// or if the element is no longer present
while (!isDone && this.el) {
let { value, done } = gen.next();
isDone = done;
await value; // eslint-disable-line no-await-in-loop
}
}
}
import Route from '@ember/routing/route';
export default Route.extend({
});
<div>
<button {{on "click" this.handleMutationObserverToggle}}>Mutation Observer is {{if this.mutationObserverOn "on" "off"}}</button>
</div>
<EmberDragula
class="flex flex-col gap-16"
@config={{this.dragulaconfig}}
@drop="onDrop"
@dragulaEvent="dragulaEvent"
>
<EmberDragulaContainer
@tagName="ul"
class="flex flex-col gap-16"
{{did-insert this.didInsert}}
>
{{#each this.bRecords as |bRecord|}}
<li data-stage-record-identifier={{
call (fn this.recordIdentifierFor bRecord)
}}
{{custom-modifier out=this.out removedNodes=this.removedNodes ticker=this.ticker}}
>
{{call (fn this.stringify this.removedNodes)}}
<div>
<pre>title: {{bRecord.title}}</pre>
</div>
<div>
<pre>identifier: {{call (fn this.recordIdentifierFor bRecord)}}</pre>
</div>
<div>
<pre>rowOrderPosition: {{bRecord.rowOrderPosition}}</pre>
</div>
<button {{on "click" (fn this.bRecordDelete bRecord)}}>delete</button>
</li>
{{/each}}
</EmberDragulaContainer>
</EmberDragula>
<style>
li {
font-size: 12px;
padding: 10px;
outline: 1px solid red;
}
button {
margin-top: 10px;
padding: 3px;
}
</style>
{
"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",
"ember-data": "3.18.0",
"ember-dragula": "1.9.3",
"ember-composable-helpers": "4.4.1",
"ember-modifier": "2.1.2",
"@ember/render-modifiers": "1.0.2"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment