Skip to content

Commit

Permalink
feat(draggable): add mwlDraggable directive
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt Lewis committed Nov 18, 2016
1 parent 6a98766 commit c6771eb
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 45 deletions.
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,27 @@ npm install --save angular-draggable-droppable
Then use it in your app like so:

```typescript
import {Component} from '@angular/core';
import {HelloWorld} from 'angular-draggable-droppable';
import {Component, NgModule} from '@angular/core';
import {DragAndDropModule} from 'angular-draggable-droppable';

@NgModule({
declarations: [DemoApp],
imports: [DragAndDropModule],
bootstrap: [DemoApp]
})
class DemoModule {}

@Component({
selector: 'demo-app',
directives: [HelloWorld],
template: '<hello-world></hello-world>'
template: '<div mwlDraggable (dragEnd)="dragEnd($event)">Drag me!</div>'
})
export class DemoApp {}
class DemoApp {

dragEnd(event) {
console.log('Element was dragged', event);
}

}
```

You may also find it useful to view the [demo source](https://github.com/mattlewis92/angular-draggable-droppable/blob/master/demo/demo.ts).
Expand Down
15 changes: 14 additions & 1 deletion demo/demo.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@ import {Component} from '@angular/core';

@Component({
selector: 'demo-app',
template: '<hello-world></hello-world>'
template: '<div mwlDraggable>Drag me!</div>',
styles: [`
[mwlDraggable] {
background-color: red;
width: 200px;
color: white;
height: 200px;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
cursor: move;
}
`]
})
export class Demo {}
10 changes: 5 additions & 5 deletions src/dragAndDrop.module.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {HelloWorld} from './helloWorld.component';
import {Draggable} from './draggable.directive';

@NgModule({
declarations: [
HelloWorld
Draggable
],
imports: [CommonModule],
exports: [HelloWorld]
exports: [
Draggable
]
})
export class DragAndDropModule {}
86 changes: 86 additions & 0 deletions src/draggable.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {Directive, HostListener, OnInit, ElementRef, Renderer, Output, EventEmitter} from '@angular/core';
import {Subject} from 'rxjs/Subject';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/takeUntil';
import 'rxjs/add/operator/takeLast';

type Coordinates = {x: number, y: number};

@Directive({
selector: '[mwlDraggable]'
})
export class Draggable implements OnInit {

public mouseDown: Subject<any> = new Subject();

public mouseMove: Subject<any> = new Subject();

public mouseUp: Subject<any> = new Subject();

@Output() dragStart: EventEmitter<Coordinates> = new EventEmitter<Coordinates>();

@Output() dragging: EventEmitter<Coordinates> = new EventEmitter<Coordinates>();

@Output() dragEnd: EventEmitter<Coordinates> = new EventEmitter<Coordinates>();

constructor(public element: ElementRef, private renderer: Renderer) {}

ngOnInit(): void {

const mouseDrag: Observable<Coordinates> = this.mouseDown.flatMap((mouseDownEvent: MouseEvent) => {

this.dragStart.next({x: 0, y: 0});

const mouseMove: Observable<Coordinates> = this.mouseMove
.map((mouseMoveEvent: MouseEvent) => {
return {
x: mouseMoveEvent.clientX - mouseDownEvent.clientX,
y: mouseMoveEvent.clientY - mouseDownEvent.clientY
};
})
.takeUntil(Observable.merge(this.mouseUp, this.mouseDown));

mouseMove.takeLast(1).subscribe((finalCoords: Coordinates) => {
this.dragEnd.next(finalCoords);
this.renderer.setElementStyle(this.element.nativeElement, 'transform', '');
});

return mouseMove;

});

mouseDrag.subscribe(({x, y}: Coordinates) => {
this.dragging.next({x, y});
this.renderer.setElementStyle(this.element.nativeElement, 'transform', `translate(${x}px, ${y}px)`);
});

}

/**
* @private
*/
@HostListener('mousedown', ['$event'])
onMouseDown(event: MouseEvent): void {
this.mouseDown.next(event);
}

/**
* @private
*/
@HostListener('document:mousemove', ['$event'])
onMouseMove(event: MouseEvent): void {
this.mouseMove.next(event);
}

/**
* @private
*/
@HostListener('document:mouseup', ['$event'])
onMouseUp(event: MouseEvent): void {
this.mouseUp.next(event);
}

}
11 changes: 0 additions & 11 deletions src/helloWorld.component.ts

This file was deleted.

68 changes: 68 additions & 0 deletions test/draggable.directive.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {Component, ViewChild} from '@angular/core';
import {TestBed, ComponentFixture} from '@angular/core/testing';
import {expect} from 'chai';
import * as sinon from 'sinon';
import {DragAndDropModule} from '../src/index';
import {Draggable} from '../src/draggable.directive';

const triggerDomEvent: Function = (eventType: string, target: HTMLElement | Element, eventData: Object = {}) => {
const event: Event = document.createEvent('Event');
Object.assign(event, eventData);
event.initEvent(eventType, true, true);
target.dispatchEvent(event);
};

describe('draggable directive', () => {

@Component({
template: `
<div
mwlDraggable
(dragStart)="dragStart($event)"
(dragging)="dragging($event)"
(dragEnd)="dragEnd($event)" >
Drag me!
</div>`,
})
class TestCmp {

@ViewChild(Draggable) public draggable: Draggable;
public dragStart: sinon.SinonSpy = sinon.spy();
public dragging: sinon.SinonSpy = sinon.spy();
public dragEnd: sinon.SinonSpy = sinon.spy();

}

beforeEach(() => {
TestBed.configureTestingModule({imports: [DragAndDropModule], declarations: [TestCmp]});
});

let fixture: ComponentFixture<TestCmp>;
beforeEach(() => {
document.body.style.margin = '0px';
fixture = TestBed.createComponent(TestCmp);
fixture.detectChanges();
document.body.appendChild(fixture.nativeElement.children[0]);
});

afterEach(() => {
fixture.destroy();
document.body.innerHTML = '';
});

it('should make the element draggable', () => {
const draggableElement: HTMLElement = fixture.componentInstance.draggable.element.nativeElement;
triggerDomEvent('mousedown', draggableElement, {clientX: 5, clientY: 10});
expect(fixture.componentInstance.dragStart).to.have.been.calledWith({x: 0, y: 0});
triggerDomEvent('mousemove', draggableElement, {clientX: 7, clientY: 10});
expect(fixture.componentInstance.dragging).to.have.been.calledWith({x: 2, y: 0});
expect(draggableElement.style.transform).to.equal('translate(2px, 0px)');
triggerDomEvent('mousemove', draggableElement, {clientX: 7, clientY: 8});
expect(fixture.componentInstance.dragging).to.have.been.calledWith({x: 2, y: -2});
expect(draggableElement.style.transform).to.equal('translate(2px, -2px)');
triggerDomEvent('mouseup', draggableElement, {clientX: 7, clientY: 8});
expect(fixture.componentInstance.dragEnd).to.have.been.calledWith({x: 2, y: -2});
expect(draggableElement.style.transform).to.equal('');
});

});
2 changes: 1 addition & 1 deletion test/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import 'zone.js/dist/fake-async-test';
import 'zone.js/dist/sync-test';
import 'zone.js/dist/proxy';
import 'zone.js/dist/jasmine-patch';
import 'rxjs';
import 'rxjs/Observable';
import {use} from 'chai';
import * as sinonChai from 'sinon-chai';
import {TestBed} from '@angular/core/testing';
Expand Down
22 changes: 0 additions & 22 deletions test/helloWorld.component.spec.ts

This file was deleted.

42 changes: 42 additions & 0 deletions webpack.config.umd.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,48 @@ module.exports = {
commonjs: '@angular/common',
commonjs2: '@angular/common',
amd: '@angular/common'
},
'rxjs/Subject': {
root: ['rx', 'Subject'],
commonjs: 'rxjs/Subject',
commonjs2: 'rxjs/Subject',
amd: 'rxjs/Subject'
},
'rxjs/Observable': {
root: ['rx', 'Observable'],
commonjs: 'rxjs/Observable',
commonjs2: 'rxjs/Observable',
amd: 'rxjs/Observable'
},
'rxjs/add/observable/merge': {
root: ['rx', 'Observable', 'merge'],
commonjs: 'rxjs/add/observable/merge',
commonjs2: 'rxjs/add/observable/merge',
amd: 'rxjs/add/observable/merge'
},
'rxjs/add/operator/map': {
root: ['rx', 'Observable'],
commonjs: 'rxjs/add/operator/map',
commonjs2: 'rxjs/add/operator/map',
amd: 'rxjs/add/operator/map'
},
'rxjs/add/operator/mergeMap': {
root: ['rx', 'Observable'],
commonjs: 'rxjs/add/operator/mergeMap',
commonjs2: 'rxjs/add/operator/mergeMap',
amd: 'rxjs/add/operator/mergeMap'
},
'rxjs/add/operator/takeUntil': {
root: ['rx', 'Observable'],
commonjs: 'rxjs/add/operator/takeUntil',
commonjs2: 'rxjs/add/operator/takeUntil',
amd: 'rxjs/add/operator/takeUntil'
},
'rxjs/add/operator/takeLast': {
root: ['rx', 'Observable'],
commonjs: 'rxjs/add/operator/takeLast',
commonjs2: 'rxjs/add/operator/takeLast',
amd: 'rxjs/add/operator/takeLast'
}
},
devtool: 'source-map',
Expand Down

0 comments on commit c6771eb

Please sign in to comment.