<!DOCTYPE html>
<html>
<head>
<title>angular2 playground</title>
<link rel="stylesheet" href="style.css" />
<script src="https://code.angularjs.org/tools/system.js"></script>
<script src="https://code.angularjs.org/tools/typescript.js"></script>
<script src="config.js"></script>
<script src="https://code.angularjs.org/2.0.0-alpha.46/angular2.dev.js"></script>
<script>
System.import('app')
.catch(console.error.bind(console));
</script>
</head>
<body>
<my-app>
loading...
</my-app>
</body>
</html>
body,html {
padding: 0;
margin: 0;
}
.testbox {
background-color: red;
position: absolute;
cursor: default;
}
.testbox2 {
background-color: green;
position: absolute;
width: 200px;
height: 200px;
left: 50px;
top: 300px;
cursor: default;
}
.testbox3 {
background-color: yellow;
position: absolute;
width: 200px;
height: 200px;
left: 300px;
top: 50px;
cursor: default;
}
.resize-handle {
position: absolute;
}
.resize-handle-e {
right: -3px;
top: 0;
width: 6px;
height: 100%;
cursor: e-resize;
}
.resize-handle-n {
left: 0;
top: -3px;
width: 100%;
height: 6px;
cursor: n-resize;
}
.resize-handle-w {
left: -3px;
top: 0;
width: 6px;
height: 100%;
cursor: w-resize;
}
.resize-handle-s {
left: 0;
bottom: -3px;
width: 100%;
height: 6px;
cursor: s-resize;
}
.resize-handle-ne {
top: -3px;
right: -3px;
width: 10px;
height: 10px;
cursor: ne-resize;
}
.resize-handle-se {
bottom: -3px;
right: -3px;
width: 10px;
height: 10px;
cursor: se-resize;
}
.resize-handle-sw {
bottom: -3px;
left: -3px;
width: 10px;
height: 10px;
cursor: sw-resize;
}
.resize-handle-nw {
top: -3px;
left: -3px;
width: 10px;
height: 10px;
cursor: nw-resize;
}
import { Component, View, NgStyle, bootstrap } from 'angular2/angular2'
import { Resizable } from './resizable.ts'
import { Draggable } from './draggable.ts'
@Component({
selector: 'my-app'
})
@View({
template: `
<div class="testbox resizable draggable" [draggable]="{model:widget}" [resizable]="{model:widget}" [ng-style]="widget">
draggable & resizable with model
<pre>
top : {{widget.top}}
left : {{widget.left}}
width : {{widget.width}}
height : {{widget.height}}
</pre>
</div>
<div class="testbox2 resizable draggable" draggable resizable>
draggable & resizable without model
</div>
<div class="testbox3 resizable draggable" draggable resizable
(drag-start)="onDragStart()" (drag)="onDrag()" (drag-stop)="onDragStop()"
(resize-start)="onResizeStart()" (resize)="onResize()" (resize-stop)="onResizeStop()">
<div>draggable & resizable event handling</div>
<div>status : {{status}}</div>
<div>drags : {{drags}}</div>
<div>resizes : {{resizes}}</div>
</div>
`,
directives: [ Draggable, Resizable, NgStyle ]
})
export class App {
widget = {
top: '50px',
left: '50px',
height: '200px',
width: '200px'
};
status = "idle";
drags: 0;
resizes: 0;
public onDragStart() {
this.status = "dragging";
}
public onDrag() {
this.drags++;
}
public onDragStop() {
this.status = "idle";
}
public onResizeStart() {
this.status = "resizing";
}
public onResize() {
this.resizes++;
}
public onResizeStop() {
this.status = "idle";
}
}
bootstrap(App)
.catch(err => console.error(err));
System.config({
transpiler: 'typescript',
typescriptOptions: {
emitDecoratorMetadata: true
},
map: {
app: "./src",
'@reactivex/rxjs':'https://cdn.rawgit.com/ReactiveX/RxJS/5.0.0-alpha.8/src'
},
packages: {
app: {
main: './app.ts',
defaultExtension: 'ts'
},
'@reactivex/rxjs': {
main: 'Rx.ts',
defaultExtension: 'ts'
}
}
});
import { Directive, ElementRef, EventEmitter, OnDestroy, OnInit, Renderer } from 'angular2/angular2';
import { Observable, Subscription, Subscriber } from '@reactivex/rxjs';
export class Position {
constructor(public left?: number, public top?: number) { }
}
export class DragEvent {
public cancelled: boolean = false;
constructor(public mouseDown: UIEvent, public mouseMove: UIEvent, public position: Position, public offset: Position) {
}
}
const makeInputObservable =
(node: Node, eventName: string, useCapture?: boolean): Observable<UIEvent> =>
Observable.fromEventPattern<MouseEvent>(
(handler) => { node.addEventListener(eventName, <EventListener>handler, useCapture); },
(handler) => { node.removeEventListener(eventName, <EventListener>handler, useCapture); }
);
@Directive({
selector: '[draggable]',
inputs: ['config:draggable'],
outputs: ['drag', 'dragStart', 'dragStop']
})
export class Draggable implements OnDestroy, OnInit {
public drag: EventEmitter = new EventEmitter();
public dragStart: EventEmitter = new EventEmitter();
public dragStop: EventEmitter = new EventEmitter();
private _dragSubscription: Subscription<DragEvent>;
private _isDragging: boolean = false;
private _axis: string;
private _config: any;
private _mouseDelay: number = 50;
private _mouseDelayMet: boolean;
private _mouseDelayTimer: number;
private _mouseDistance: number = 5;
private _mouseDistanceMet: boolean = false;
private _containment: ClientRect = null;
private _dragOffsetX: number;
private _dragOffsetY: number;
private _elementStartX: number;
private _elementStartY: number;
private _model: any;
set config(value: any) {
this._config = value;
this.setConfig(this._config);
}
public constructor(private _element: ElementRef, private _renderer: Renderer) {
}
public setConfig(config: any): void {
for (let key in config) {
var value = config[key];
switch (key) {
case 'axis':
this._axis = value;
break;
case 'delay':
this._mouseDelay = parseInt(value);
break;
case 'distance':
this._mouseDistance = parseInt(value);
break;
case 'containment':
this._containment = value;
break;
case 'model':
this._model = value;
break;
}
}
}
private _generatePosition(event: MouseEvent): Position {
var posX = (this._axis == 'y') ? this._elementStartX : this._elementStartX + this._dragOffsetX;
var posY = (this._axis == 'x') ? this._elementStartY : this._elementStartY + this._dragOffsetY;
return new Position(posX, posY);
}
private _start(): void {
this._isDragging = false;
this._mouseDelayMet = this._mouseDelay == 0;
this._mouseDistanceMet = this._mouseDistance == 0;
this._elementStartX = this._element.nativeElement.offsetLeft;
this._elementStartY = this._element.nativeElement.offsetTop;
if (!this._mouseDelayMet) {
this._mouseDelayTimer = setTimeout(() => {
this._mouseDelayMet = true;
}, this._mouseDelay);
}
}
private _update(mouseDownEvent: MouseEvent, mouseMoveEvent: MouseEvent): void {
this._dragOffsetX = mouseMoveEvent.clientX - mouseDownEvent.clientX;
this._dragOffsetY = mouseMoveEvent.clientY - mouseDownEvent.clientY;
this._mouseDistanceMet = Math.abs(this._dragOffsetX) > this._mouseDistance || Math.abs(this._dragOffsetY) > this._mouseDistance
if (!this._isDragging && this._mouseDelayMet && this._mouseDistanceMet) {
this.dragStart.next(event);
this._isDragging = true;
}
}
public onInit(): void {
var mouseDownObservable = Observable.fromEvent(this._element.nativeElement, 'mousedown').filter((md: MouseEvent) => md.which == 1);
var mouseMoveObservable = Observable.fromEvent(this._element.nativeElement.ownerDocument, 'mousemove');
var mouseUpObservable = Observable.fromEvent(this._element.nativeElement.ownerDocument, 'mouseup');
var clickObservable = makeInputObservable(this._element.nativeElement.ownerDocument, 'click', true);
var dragObservable = mouseDownObservable.flatMap<DragEvent>((mouseDownEvent: MouseEvent) => {
mouseDownEvent.preventDefault();
mouseDownEvent.stopPropagation();
this._start();
return mouseMoveObservable
.map((mouseMoveEvent: MouseEvent) => {
this._update(mouseDownEvent, mouseMoveEvent);
return new DragEvent(mouseDownEvent, mouseMoveEvent, this._generatePosition(mouseMoveEvent), new Position(this._dragOffsetX, this._dragOffsetY));
})
.filter(() => this._isDragging)
.takeUntil(mouseUpObservable
.map((mouseUpEvent) => {
clearInterval(this._mouseDelayTimer);
if (this._isDragging)
this.dragStop.next(mouseUpEvent);
})
.zip(clickObservable.map((clickEvent: MouseEvent) => {
if (this._isDragging) {
clickEvent.stopPropagation();
this._isDragging = false;
}
}))
);
});
this._dragSubscription = dragObservable.subscribe((event) => {
this.drag.next(event);
setTimeout(() => {
if (!event.cancelled) {
this._setStyle('top', event.position.top + 'px');
this._setStyle('left', event.position.left + 'px');
}
});
});
}
private _setStyle(styleName: string, styleValue: string) {
if (this._model) {
this._model[styleName] = styleValue
} else {
this._renderer.setElementStyle(this._element, styleName, styleValue);
}
}
public onDestroy(): void {
this._dragSubscription.unsubscribe();
}
}
import { Component, View, ElementRef, CORE_DIRECTIVES, EventEmitter, Renderer } from 'angular2/angular2';
import { DragEvent, Draggable } from "./draggable";
@Component({
selector: '[resizable]',
inputs: ['config:resizable'],
outputs: ['resize', 'resizeStart', 'resizeStop']
})
@View({
template: `
<ng-content></ng-content>
<div *ng-for="#handle of handles" [ng-class]="'resize-handle resize-handle-' + handle" draggable (drag)="onDrag($event, handle)" (drag-start)="onDragStart($event)" (drag-stop)="onDragStop($event)"></div>
`,
directives: [Draggable, CORE_DIRECTIVES]
})
export class Resizable {
public resize: EventEmitter = new EventEmitter();
public resizeStart: EventEmitter = new EventEmitter();
public resizeStop: EventEmitter = new EventEmitter();
private _handles: Array<string> = ['s', 'e', 'se'];
private _originalWidth: number;
private _originalHeight: number;
private _originalLeft: number;
private _originalTop: number;
private _minWidth: number = 0;
private _minHeight: number = 0;
private _model: any;
private _config: any;
set config(value: any) {
this._config = value;
this.setConfig(this._config);
}
get handles(): Array<string> {
return this._handles;
}
public setConfig(config: any): void {
for (let key in config) {
var value = config[key];
switch (key) {
case 'model':
this._model = value;
break;
case 'handles':
this._handles = value;
break;
case 'minWidth':
this._minWidth = parseFloat(value);
break;
case 'minHeight':
this._minHeight = parseFloat(value);
break;
}
}
}
public constructor(private _element: ElementRef, private _renderer: Renderer) {
}
private _setStyle(styleName: string, styleValue: string) {
if (this._model) {
this._model[styleName] = styleValue
} else {
this._renderer.setElementStyle(this._element, styleName, styleValue);
}
}
public onDrag(dragEvent: DragEvent, handle: string) {
if (handle.indexOf('e') != -1) {
this._setStyle('width', Math.max(this._minWidth, this._originalWidth + dragEvent.offset.left) + 'px');
}
if (handle.indexOf('s') != -1)
this._setStyle('height', Math.max(this._minHeight, this._originalHeight + dragEvent.offset.top) + 'px');
if (handle.indexOf('w') != -1) {
this._setStyle('left', (this._originalLeft + dragEvent.offset.left) + 'px');
this._setStyle('width', Math.max(this._minWidth, this._originalWidth - dragEvent.offset.left) + 'px');
}
if (handle.indexOf('n') != -1) {
this._setStyle('top', (this._originalTop + dragEvent.offset.top) + 'px');
this._setStyle('height', Math.max(this._minHeight, this._originalHeight - dragEvent.offset.top) + 'px');
}
dragEvent.cancelled = true;
this.resize.next(dragEvent);
}
public onDragStart(event: Event) {
this._originalWidth = this._element.nativeElement.offsetWidth;
this._originalHeight = this._element.nativeElement.offsetHeight;
this._originalLeft = this._element.nativeElement.offsetLeft;
this._originalTop = this._element.nativeElement.offsetTop;
this.resizeStart.next(event);
}
public onDragStop(event: Event) {
this.resizeStop.next(event);
}
}