@supermemo/ng2-dragula
Version:
Simple drag and drop with dragula
139 lines (125 loc) • 4.54 kB
text/typescript
import { Directive, Input, Output, ElementRef, OnInit, OnChanges, OnDestroy, SimpleChange, EventEmitter } from '@angular/core';
import { DragulaService } from './dragula.service';
import { DrakeWithModels } from '../DrakeWithModels';
import { Subscription } from 'rxjs';
import { Group } from '../Group';
export class DragulaDirective implements OnChanges, OnDestroy {
public dragula: string;
public dragulaModel: any[];
public dragulaModelChange = new EventEmitter<any[]>();
private subs: Subscription;
private get container(): HTMLElement {
return this.el && this.el.nativeElement;
}
private group: Group;
public constructor(private el: ElementRef, private dragulaService: DragulaService) {
}
public ngOnChanges(changes: {dragula?: SimpleChange, dragulaModel?: SimpleChange}): void {
if (changes && changes.dragula) {
const { previousValue: prev, currentValue: current, firstChange } = changes.dragula;
let hadPreviousValue = !!prev;
let hasNewValue = !!current;
// something -> null => teardown only
// something -> something => teardown, then setup
// null -> something => setup only
//
// null -> null (precluded by fact of change being present)
if (hadPreviousValue) {
this.teardown(prev);
}
if (hasNewValue) {
this.setup();
}
} else if (changes && changes.dragulaModel) {
// this code only runs when you're not changing the group name
// because if you're changing the group name, you'll be doing setup or teardown
// it also only runs if there is a group name to attach to.
const { previousValue: prev, currentValue: current, firstChange } = changes.dragulaModel;
const { drake } = this.group;
if (this.dragula && drake) {
drake.models = drake.models || [];
let prevIndex = drake.models.indexOf(prev);
if (prevIndex !== -1) {
// delete the previous
drake.models.splice(prevIndex, 1);
// maybe insert a new one at the same spot
if (!!current) {
drake.models.splice(prevIndex, 0, current);
}
} else if (!!current) {
// no previous one to remove; just push this one.
drake.models.push(current);
}
}
}
}
// call ngOnInit 'setup' because we want to call it in ngOnChanges
// and it would otherwise run twice
public setup(): void {
let checkModel = (group: Group) => {
if (this.dragulaModel) {
if (group.drake.models) {
group.drake.models.push(this.dragulaModel);
} else {
group.drake.models = [this.dragulaModel];
}
}
};
// find or create a group
let group = this.dragulaService.find(this.dragula);
if (!group) {
let options = {};
group = this.dragulaService.createGroup(this.dragula, options);
}
// ensure model and container element are pushed
checkModel(group);
group.drake.containers.push(this.container);
this.subscribe(this.dragula);
this.group = group;
}
public subscribe(name: string) {
this.subs = new Subscription();
this.subs.add(
this.dragulaService
.dropModel(name)
.subscribe(({ source, target, sourceModel, targetModel }) => {
if (source === this.el.nativeElement) {
this.dragulaModelChange.emit(sourceModel);
} else if (target === this.el.nativeElement) {
this.dragulaModelChange.emit(targetModel);
}
})
);
this.subs.add(
this.dragulaService
.removeModel(name)
.subscribe(({ source, sourceModel }) => {
if (source === this.el.nativeElement) {
this.dragulaModelChange.emit(sourceModel);
}
})
);
}
public teardown(groupName: string): void {
if (this.subs) {
this.subs.unsubscribe();
}
const group = this.dragulaService.find(groupName);
if (group) {
const itemToRemove = group.drake.containers.indexOf(this.el.nativeElement);
if (itemToRemove !== -1) {
group.drake.containers.splice(itemToRemove, 1);
}
if (this.dragulaModel && group.drake && group.drake.models) {
let modelIndex = group.drake.models.indexOf(this.dragulaModel);
if (modelIndex !== -1) {
group.drake.models.splice(modelIndex, 1);
}
}
}
}
public ngOnDestroy(): void {
this.teardown(this.dragula);
}
}