<!DOCTYPE html>
<html>
<head>
<base href="." />
<title>angular2 playground</title>
<link rel="stylesheet" href="dragula.css" />
<link rel="stylesheet" href="style.css" />
<script src="https://unpkg.com/zone.js/dist/zone.js"></script>
<script src="https://unpkg.com/zone.js/dist/long-stack-trace-zone.js"></script>
<script src="https://unpkg.com/reflect-metadata@0.1.3/Reflect.js"></script>
<script src="https://unpkg.com/systemjs@0.19.31/dist/system.js"></script>
<script src="config.js"></script>
<script>
System.import('app')
.catch(console.error.bind(console));
</script>
</head>
<body>
<my-app>
loading....
</my-app>
</body>
</html>
/* Styles go here */
.row {
background: #FAFFB2;
border: 1px solid orange;
margin: 1em;
padding: 1em;
display: flex;
}
.column {
background: #4e42f4;
border: 1px dotted red;
padding: 1em;
margin: 1em;
}
Import the dragula styles in index.html
Import the dragula script by config.js. Add to map.
Normally we'd import ng2-dragula same way but it isn't working with angular2.1.0
so download it and import as a local module. Problem is with the dependencies
being set too low but maybe also this issue with Plunker and file extensions not
resolving?
Plunker doesn't resolve imports that omit file extensions, so update the
ng2-dragula module and directive, change dragula.directive and dragula.provider
to dragula.directive.ts and dragula.provider.ts
System.config({
//use typescript for compilation
transpiler: 'typescript',
//typescript compiler options
typescriptOptions: {
emitDecoratorMetadata: true
},
paths: {
'npm:': 'https://unpkg.com/'
},
//map tells the System loader where to look for things
map: {
'app': './src',
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
'@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
'@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
'@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
'@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
'@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
'@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js',
'@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js',
'rxjs': 'npm:rxjs',
'typescript': 'npm:typescript@2.0.2/lib/typescript.js',
'dragula': 'https://cdnjs.cloudflare.com/ajax/libs/dragula/3.7.1/dragula.min.js'
},
//packages defines our app package
packages: {
app: {
main: './main.ts',
defaultExtension: 'ts'
},
rxjs: {
defaultExtension: 'js'
}
}
});
//main entry point
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app';
platformBrowserDynamic().bootstrapModule(AppModule)
//our root app component
import {Component, NgModule} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import { DragulaModule } from '../ng2-dragula/ng2-dragula.ts';
import { DragulaService } from '../ng2-dragula/dragula.provider.ts';
@Component({
selector: 'my-app',
templateUrl: 'src/app.html'
})
export class App {
rows: any = [
{
value: "Row A"
columns: [
{
value: "RA - C A"
},
{
value: "RA - C B"
},
{
value: "RA - C C"
},
{
value: "RA - C D"
}
]
}
{
value: "Row B"
columns: [
{
value: "RB - C A"
},
{
value: "RB - C B"
},
{
value: "RB - C C"
},
{
value: "RB - C D"
}
]
}
{
value: "Row C"
columns: [
{
value: "RC - C A"
},
{
value: "RC - C B"
},
{
value: "RC - C C"
},
{
value: "RC - C D"
}
]
}
{
value: "Row D"
columns: [
{
value: "RD - C A"
},
{
value: "RD - C B"
},
{
value: "RD - C C"
},
{
value: "RD - C D"
}
]
}
]
dragCol = false
constructor(dragulaService: DragulaService) {
dragulaService.setOptions('bag', {
invalid(el, handle) {
return (el.className == "column");
}
});
}
}
@NgModule({
imports: [ BrowserModule, DragulaModule ],
declarations: [ App ],
providers: [DragulaService],
bootstrap: [ App ]
})
export class AppModule {}
Testing rows and columns:
<div [dragula]="'bag'" [dragulaModel]="rows">
<div class="row" *ngFor="let row of rows">
{{row.value}}
<div [dragula]="'bag2'" [dragulaModel]="row.columns">
<div (mousedown)="dragCol = true" (mouseup)="dragCol = false" class="column" *ngFor="let column of row.columns">
{{column.value}}
</div>
</div>
</div>
</div>
import { NgModule } from '@angular/core';
import { DragulaDirective } from './dragula.directive.ts';
import { DragulaService } from './dragula.provider.ts';
export * from './dragula.provider.ts';
export * from './dragula.directive.ts';
@NgModule({
exports: [DragulaDirective],
declarations: [DragulaDirective],
providers: [DragulaService]
})
export class DragulaModule {
}
import {
Directive,
Input,
ElementRef,
OnInit,
OnChanges,
SimpleChange
} from '@angular/core';
import { DragulaService } from './dragula.provider.ts';
import dragula from 'dragula';
@Directive({
selector: '[dragula]'
})
export class DragulaDirective implements OnInit, OnChanges {
@Input('dragula') public bag:string;
@Input() public dragulaModel:any;
private container:any;
private drake:any;
public constructor(private el:ElementRef, private dragulaService:DragulaService) {
this.container = el.nativeElement;
}
public ngOnInit():void {
// console.log(this.bag);
let bag = this.dragulaService.find(this.bag);
let checkModel = () => {
if (this.dragulaModel) {
if (this.drake.models) {
this.drake.models.push(this.dragulaModel);
} else {
this.drake.models = [this.dragulaModel];
}
}
};
if (bag) {
this.drake = bag.drake;
checkModel();
this.drake.containers.push(this.container);
} else {
this.drake = dragula({
containers: [this.container]
});
checkModel();
this.dragulaService.add(this.bag, this.drake);
}
}
public ngOnChanges(changes:{[propName:string]:SimpleChange}):void {
// console.log('dragula.directive: ngOnChanges');
// console.log(changes);
if (changes && changes['dragulaModel']) {
if (this.drake) {
if (this.drake.models) {
let modelIndex = this.drake.models.indexOf(changes['dragulaModel'].previousValue);
this.drake.models.splice(modelIndex, 1, changes['dragulaModel'].currentValue);
} else {
this.drake.models = [changes['dragulaModel'].currentValue];
}
}
}
}
}
import dragula from 'dragula';
import { Injectable, EventEmitter } from '@angular/core';
@Injectable()
export class DragulaService {
public cancel:EventEmitter<any> = new EventEmitter();
public cloned:EventEmitter<any> = new EventEmitter();
public drag:EventEmitter<any> = new EventEmitter();
public dragend:EventEmitter<any> = new EventEmitter();
public drop:EventEmitter<any> = new EventEmitter();
public out:EventEmitter<any> = new EventEmitter();
public over:EventEmitter<any> = new EventEmitter();
public remove:EventEmitter<any> = new EventEmitter();
public shadow:EventEmitter<any> = new EventEmitter();
public dropModel:EventEmitter<any> = new EventEmitter();
public removeModel:EventEmitter<any> = new EventEmitter();
private events:Array<string> = [
'cancel',
'cloned',
'drag',
'dragend',
'drop',
'out',
'over',
'remove',
'shadow',
'dropModel',
'removeModel'
];
private bags:Array<any> = [];
public add(name:string, drake:any):any {
let bag = this.find(name);
if (bag) {
throw new Error('Bag named: "' + name + '" already exists.');
}
bag = {
name: name,
drake: drake
};
this.bags.push(bag);
if (drake.models) { // models to sync with (must have same structure as containers)
this.handleModels(name, drake);
}
if (!bag.initEvents) {
this.setupEvents(bag);
}
return bag;
}
public find(name:string):any {
for (let i = 0; i < this.bags.length; i++) {
if (this.bags[i].name === name) {
return this.bags[i];
}
}
}
public destroy(name:string):void {
let bag = this.find(name);
let i = this.bags.indexOf(bag);
this.bags.splice(i, 1);
bag.drake.destroy();
}
public setOptions(name:string, options:any):void {
let bag = this.add(name, dragula(options));
this.handleModels(name, bag.drake);
}
private handleModels(name:string, drake:any):void {
let dragElm:any;
let dragIndex:number;
let dropIndex:number;
let sourceModel:any;
drake.on('remove', (el:any, source:any) => {
if (!drake.models) {
return;
}
sourceModel = drake.models[drake.containers.indexOf(source)];
sourceModel.splice(dragIndex, 1);
// console.log('REMOVE');
// console.log(sourceModel);
this.removeModel.emit([name, el, source]);
});
drake.on('drag', (el:any, source:any) => {
dragElm = el;
dragIndex = this.domIndexOf(el, source);
});
drake.on('drop', (dropElm:any, target:any, source:any) => {
if (!drake.models || !target) {
return;
}
dropIndex = this.domIndexOf(dropElm, target);
sourceModel = drake.models[drake.containers.indexOf(source)];
// console.log('DROP');
// console.log(sourceModel);
if (target === source) {
sourceModel.splice(dropIndex, 0, sourceModel.splice(dragIndex, 1)[0]);
} else {
let notCopy = dragElm === dropElm;
let targetModel = drake.models[drake.containers.indexOf(target)];
let dropElmModel = notCopy ? sourceModel[dragIndex] : JSON.parse(JSON.stringify(sourceModel[dragIndex]));
if (notCopy) {
sourceModel.splice(dragIndex, 1);
}
targetModel.splice(dropIndex, 0, dropElmModel);
target.removeChild(dropElm); // element must be removed for ngFor to apply correctly
}
this.dropModel.emit([name, dropElm, target, source]);
});
}
private setupEvents(bag:any):void {
bag.initEvents = true;
let that:any = this;
let emitter = (type:any) => {
function replicate():void {
let args = Array.prototype.slice.call(arguments);
that[type].emit([bag.name].concat(args));
}
bag.drake.on(type, replicate);
};
this.events.forEach(emitter);
}
private domIndexOf(child:any, parent:any):any {
return Array.prototype.indexOf.call(parent.children, child);
}
}
/* default dragula styles */
.gu-mirror {
position: fixed !important;
margin: 0 !important;
z-index: 9999 !important;
opacity: 0.8;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";
filter: alpha(opacity=80);
}
.gu-hide {
display: none !important;
}
.gu-unselectable {
-webkit-user-select: none !important;
-moz-user-select: none !important;
-ms-user-select: none !important;
user-select: none !important;
}
.gu-transit {
opacity: 0.2;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)";
filter: alpha(opacity=20);
}