@Component({ selector: 'app-version-1', template: ` <h1>{{title}}</h1> <table> <tbody> <tr *ngFor="let row of table"> <td *ngFor="let cell of row"> <div class="cell-content" dnd-droppable (onDropSuccess)="drop($event, cell)" (onDragEnter)="dragEnter($event, cell)" (onDragLeave)="dragLeave($event, cell)" > <span class="item" *ngFor="let item of cell" dnd-draggable [dragData]="{cell: cell, item: item}" >{{item}}</span> <span class="entered" *ngIf="cell.entered">{{cell.entered}}</span> </div> </td> </tr> </tbody> </table> `, }) export class Version1Component extends VersionBase { public static readonly title = ' '; // public dragEnter({ dragData }, cell: Cell) { cell.entered = dragData.item; } // public dragLeave({ dragData }, cell: Cell) { delete cell.entered; } // public drop({ dragData }, cell: Cell) { const index = dragData.cell.indexOf(dragData.item); dragData.cell.splice(index, 1); cell.push(dragData.item); delete cell.entered; } }
@Component({ selector: 'app-version-2', template: ` <h1>{{title}}</h1> <table> <tbody dnd-droppable (onDropSuccess)="drop($event)" (onDragEnter)="dragEnter($event)" (onDragLeave)="dragLeave($event)" > <tr *ngFor="let row of table"> <td *ngFor="let cell of row"> <div class="cell-content"> <span class="item" *ngFor="let item of cell" dnd-draggable [dragData]="{cell: cell, item: item}" (onDragEnd)="dragEnd($event)" >{{item}}</span> <span class="entered" *ngIf="cell.entered">{{cell.entered}}</span> </div> </td> </tr> </tbody> </table> `, }) export class Version2Component extends VersionBase { public static readonly title = ' droppable '; // private enteredCell: Cell; // private getTargetElement(target: EventTarget): Element { return (target instanceof Element) ? target : (target instanceof Text) ? target.parentElement : null; } // private getCell(element: Element): Cell { if (!element) { return null; } const td = element.closest('td'); const tr = element.closest('tr'); const body = element.closest('tbody'); const row = body ? Array.from(body.children).indexOf(tr) : -1; const col = tr ? Array.from(tr.children).indexOf(td) : -1; return (row >= 0 && col >= 0) ? this.table[row][col] : null; } // private clearEnteredCell() { if (this.enteredCell) { delete this.enteredCell.entered; delete this.enteredCell; } } // public dragEnter({ dragData, mouseEvent }: { dragData: any, mouseEvent: DragEvent }) { this.clearEnteredCell(); const element = this.getTargetElement(mouseEvent.target); const cell = this.getCell(element); if (cell) { cell.entered = dragData.item; this.enteredCell = cell; } } // public dragLeave({ dragData, mouseEvent }: { dragData: any, mouseEvent: DragEvent }) { const element = this.getTargetElement(mouseEvent.target) if (!element || !element.closest('td')) { this.clearEnteredCell(); } } // public drop({ dragData, mouseEvent }: { dragData: any, mouseEvent: DragEvent }) { if (this.enteredCell) { const index = dragData.cell.indexOf(dragData.item); dragData.cell.splice(index, 1); this.enteredCell.push(dragData.item); } this.clearEnteredCell(); } // public dragEnd() { this.clearEnteredCell(); } }
@Component({ selector: 'app-version-3', template: ` <h1>{{title}}</h1> <table> <tbody (dragenter)="dragEnter($event)" (dragleave)="dragLeave($event)" (dragover)="dragOver($event)" (drop)="drop($event)" > <tr *ngFor="let row of table"> <td *ngFor="let cell of row"> <div class="cell-content"> <span class="item" *ngFor="let item of cell" draggable="true" (dragstart)="dragStart($event, {cell: cell, item: item})" (dragend)="dragEnd()" >{{item}}</span> <span class="entered" *ngIf="cell.entered">{{cell.entered}}</span> </div> </td> </tr> </tbody> </table> `, }) export class Version3Component extends VersionBase { public static readonly title = ' '; // private enteredCell: Cell; // private dragData: { cell: Cell, item: string }; // , private getTargetElement(target: EventTarget): Element { return (target instanceof Element) ? target : (target instanceof Text) ? target.parentElement : null; } // private getCell(element: Element): Cell { if (!element) { return null; } const td = element.closest('td'); const tr = element.closest('tr'); const body = element.closest('tbody'); const row = body ? Array.from(body.children).indexOf(tr) : -1; const col = tr ? Array.from(tr.children).indexOf(td) : -1; return (row >= 0 && col >= 0) ? this.table[row][col] : null; } // private clearEnteredCell() { if (this.enteredCell) { delete this.enteredCell.entered; delete this.enteredCell; } } // public dragStart(event: DragEvent, dragData) { this.dragData = dragData; event.dataTransfer.effectAllowed = 'all'; event.dataTransfer.setData('Text', dragData.item); } // public dragEnter(event: DragEvent) { this.clearEnteredCell(); const element = this.getTargetElement(event.target); const cell = this.getCell(element); if (cell) { this.enteredCell = cell; this.enteredCell.entered = this.dragData.item; } } // public dragLeave(event: DragEvent) { const element = this.getTargetElement(event.target); if (!element || !element.closest('td')) { this.clearEnteredCell(); } } // public dragOver(event: DragEvent) { const element = this.getTargetElement(event.target); const cell = this.getCell(element); if (cell) { event.preventDefault(); event.dataTransfer.dropEffect = 'move'; return false; } } // public drop(event: DragEvent) { const element = this.getTargetElement(event.target); event.stopPropagation(); if (this.dragData && this.enteredCell) { const index = this.dragData.cell.indexOf(this.dragData.item); this.dragData.cell.splice(index, 1); this.enteredCell.push(this.dragData.item); } this.dragEnd(); return false; } // public dragEnd() { delete this.dragData; this.clearEnteredCell(); } }
/** * By default, zone.js will patch all possible macroTask and DomEvents * user can disable parts of macroTask/DomEvents patch by setting following flags */ // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['dragover']; // disable patch specified eventNames
@Component({ selector: 'app-version-4-cell', template: ` <span class="item" *ngFor="let item of cell" draggable="true" (dragstart)="dragStart($event, item)" (dragend)="dragEnd($event)" >{{item}}</span> <span class="entered" *ngIf="cell.entered">{{cell.entered}}</span> `, }) export class Version4CellComponent { @Input() public cell: Cell; private enteredElements: any = []; constructor( private element: ElementRef, private dndStorage: DndStorageService, ) {} // public dragStart(event: DragEvent, item: string) { this.dndStorage.set(this.cell, item); event.dataTransfer.effectAllowed = 'all'; event.dataTransfer.setData('Text', item); } // @HostListener('dragenter', ['$event']) private dragEnter(event: DragEvent) { this.enteredElements.push(event.target); if (this.cell !== this.dndStorage.cell) { this.cell.entered = this.dndStorage.item; } } // @HostListener('dragleave', ['$event']) private dragLeave(event: DragEvent) { this.enteredElements = this.enteredElements.filter(x => x != event.target); if (!this.enteredElements.length) { delete this.cell.entered; } } // @HostListener('dragover', ['$event']) private dragOver(event: DragEvent) { event.preventDefault(); event.dataTransfer.dropEffect = this.cell.entered ? 'move' : 'none'; return false; } // @HostListener('drop', ['$event']) private drop(event: DragEvent) { event.stopPropagation(); this.cell.push(this.dndStorage.item); this.dndStorage.dropped(); delete this.cell.entered; return false; } // public dragEnd(event: DragEvent) { if (this.dndStorage.isDropped) { const index = this.cell.indexOf(this.dndStorage.item); this.cell.splice(index, 1); } this.dndStorage.reset(); } } @Component({ selector: 'app-version-4', template: ` <h1>{{title}}</h1> <table> <tbody> <tr *ngFor="let row of table"> <td *ngFor="let cell of row"> <app-version-4-cell class="cell-content" [cell]="cell"></app-version-4-cell> </td> </tr> </tbody> </table> `, }) export class Version4Component extends VersionBase { public static readonly title = ' '; }
import { Injectable, Inject, NgZone } from '@angular/core'; import { EVENT_MANAGER_PLUGINS, EventManager } from '@angular/platform-browser'; @Injectable() export class OutZoneEventManager extends EventManager { constructor( @Inject(EVENT_MANAGER_PLUGINS) plugins: any[], private zone: NgZone ) { super(plugins, zone); } addEventListener(element: HTMLElement, eventName: string, handler: Function): Function { // if(eventName.endsWith('out-zone')) { eventName = eventName.split('.')[0]; // Angular return this.zone.runOutsideAngular(() => { return super.addEventListener(element, eventName, handler); }); } // return super.addEventListener(element, eventName, handler); } }
import { Observable } from 'rxjs/Observable'; import { animationFrame } from 'rxjs/scheduler/animationFrame.js'; import { Injectable } from '@angular/core'; @Injectable() export class BeforeRenderService { private tasks: Array<() => void> = []; private running: boolean = false; constructor() {} public addTask(task: () => void) { this.tasks.push(task); this.run(); } private run() { if (this.running) { return; } this.running = true; animationFrame.schedule(() => { this.tasks.forEach(x => x()); this.tasks.length = 0; this.running = false; }); } }
-export class Version4CellComponent { +export class Version5CellComponent { @Input() public cell: Cell; constructor( private element: ElementRef, private dndStorage: DndStorageService, + private changeDetector: ChangeDetectorRef, + private beforeRender: BeforeRenderService, ) {} // ... // - @HostListener('dragenter', ['$event']) + @HostListener('dragenter.out-zone', ['$event']) private dragEnter(event: DragEvent) { this.enteredElements.push(event.target); if (this.cell !== this.dndStorage.cell) { this.cell.entered = this.dndStorage.item; + this.beforeRender.addTask(() => this.changeDetector.detectChanges()); } } // - @HostListener('dragleave', ['$event']) + @HostListener('dragleave.out-zone', ['$event']) private dragLeave(event: DragEvent) { this.enteredElements = this.enteredElements.filter(x => x != event.target); if (!this.enteredElements.length) { delete this.cell.entered; + this.beforeRender.addTask(() => this.changeDetector.detectChanges()); } } // - @HostListener('dragover', ['$event']) + @HostListener('dragover.out-zone', ['$event']) private dragOver(event: DragEvent) { event.preventDefault(); event.dataTransfer.dropEffect = this.cell.entered ? 'move' : 'none'; } // ... }
Source: https://habr.com/ru/post/353354/
All Articles