From c6771eb9a6ae1183eaf8614353fffb1a57d22c3f Mon Sep 17 00:00:00 2001 From: Matt Lewis Date: Fri, 18 Nov 2016 16:01:20 +0000 Subject: [PATCH] feat(draggable): add mwlDraggable directive --- README.md | 22 ++++++-- demo/demo.component.ts | 15 +++++- src/dragAndDrop.module.ts | 10 ++-- src/draggable.directive.ts | 86 +++++++++++++++++++++++++++++++ src/helloWorld.component.ts | 11 ---- test/draggable.directive.spec.ts | 68 ++++++++++++++++++++++++ test/entry.ts | 2 +- test/helloWorld.component.spec.ts | 22 -------- webpack.config.umd.js | 42 +++++++++++++++ 9 files changed, 233 insertions(+), 45 deletions(-) create mode 100644 src/draggable.directive.ts delete mode 100644 src/helloWorld.component.ts create mode 100644 test/draggable.directive.spec.ts delete mode 100644 test/helloWorld.component.spec.ts diff --git a/README.md b/README.md index 33fb655..0a5fbed 100644 --- a/README.md +++ b/README.md @@ -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: '' + template: '
Drag me!
' }) -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). diff --git a/demo/demo.component.ts b/demo/demo.component.ts index 6baa6e8..379608c 100644 --- a/demo/demo.component.ts +++ b/demo/demo.component.ts @@ -2,6 +2,19 @@ import {Component} from '@angular/core'; @Component({ selector: 'demo-app', - template: '' + template: '
Drag me!
', + 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 {} diff --git a/src/dragAndDrop.module.ts b/src/dragAndDrop.module.ts index a474ebb..cd3dbcd 100644 --- a/src/dragAndDrop.module.ts +++ b/src/dragAndDrop.module.ts @@ -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 {} \ No newline at end of file diff --git a/src/draggable.directive.ts b/src/draggable.directive.ts new file mode 100644 index 0000000..fa1b518 --- /dev/null +++ b/src/draggable.directive.ts @@ -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 = new Subject(); + + public mouseMove: Subject = new Subject(); + + public mouseUp: Subject = new Subject(); + + @Output() dragStart: EventEmitter = new EventEmitter(); + + @Output() dragging: EventEmitter = new EventEmitter(); + + @Output() dragEnd: EventEmitter = new EventEmitter(); + + constructor(public element: ElementRef, private renderer: Renderer) {} + + ngOnInit(): void { + + const mouseDrag: Observable = this.mouseDown.flatMap((mouseDownEvent: MouseEvent) => { + + this.dragStart.next({x: 0, y: 0}); + + const mouseMove: Observable = 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); + } + +} \ No newline at end of file diff --git a/src/helloWorld.component.ts b/src/helloWorld.component.ts deleted file mode 100644 index 97fa02d..0000000 --- a/src/helloWorld.component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { - Component -} from '@angular/core'; - -@Component({ - selector: 'hello-world', - template: 'Hello world from the {{ projectTitle }} module!' -}) -export class HelloWorld { - projectTitle: string = 'angular 2.0+ drag and drop'; -} diff --git a/test/draggable.directive.spec.ts b/test/draggable.directive.spec.ts new file mode 100644 index 0000000..d21500a --- /dev/null +++ b/test/draggable.directive.spec.ts @@ -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: ` +
+ Drag me! +
`, + }) + 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; + 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(''); + }); + +}); \ No newline at end of file diff --git a/test/entry.ts b/test/entry.ts index ed693f3..345b5d1 100644 --- a/test/entry.ts +++ b/test/entry.ts @@ -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'; diff --git a/test/helloWorld.component.spec.ts b/test/helloWorld.component.spec.ts deleted file mode 100644 index a376f8f..0000000 --- a/test/helloWorld.component.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - inject, - ComponentFixture, - TestBed -} from '@angular/core/testing'; -import {expect} from 'chai'; -import {HelloWorld} from './../src/helloWorld.component'; -import {DragAndDropModule} from '../src'; - -describe('hello-world component', () => { - - beforeEach(() => { - TestBed.configureTestingModule({imports: [DragAndDropModule]}); - }); - - it('should say hello world', () => { - const fixture: ComponentFixture = TestBed.createComponent(HelloWorld); - fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML.trim()).to.equal('Hello world from the angular 2.0+ drag and drop module!'); - }); - -}); diff --git a/webpack.config.umd.js b/webpack.config.umd.js index 86277ac..15a42d0 100644 --- a/webpack.config.umd.js +++ b/webpack.config.umd.js @@ -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',