이제 Ignite UI for Angular 드래그 앤 드롭 모듈 또는 지시문을 가져왔으므로 and igxDrop 지시문을 사용할 igxDrag 수 있습니다.
Angular 드래그 지시문 사용
Angular 애플리케이션 내부의 요소를 페이지의 한 곳에서 다른 곳으로 드래그해야 할 때, igxDrag 지시어는 이 동작을 달성하는 데 도움이 되도록 설계되었습니다. igxDrop 지시어와 함께 사용하면 드래그된 요소의 배치도 수행할 수 있으므로 완전히 대화형 애플리케이션을 가질 수 있습니다.
드래그 기초
최종 사용자가 어느 방향으로든 5px 이상 스와이프하면 드래그 작업이 시작됩니다. 이는 사용자 정의가 가능하며 dragTolerance 입력을 사용하여 변경할 수 있습니다. 그렇지 않으면 상호 작용이 클릭으로 간주되고 dragClick 이벤트가 트리거됩니다.
드래그가 시작되면 dragStart 이벤트가 트리거됩니다. 실제 이동이 발생하지 않도록 하려면 cancel 속성을 true로 설정하여 이벤트를 취소할 수 있습니다.
실제 이동이 수행되기 전에 포인터의 마지막 위치와 다음 위치를 포함하는 dragMove 이벤트도 트리거됩니다. 요소를 드래그하는 동안 움직임이 감지될 때마다 트리거됩니다.
사용자가 마우스/터치를 놓으면 드래그 고스트 요소가 DOM에서 제거되고 dragEnd 이벤트가 발생합니다.
dragMove 이벤트의 특성상 짧은 시간 내에 여러 번 트리거될 수 있으며, 이로 인해 트리거 시 수행되는 복잡한 작업의 경우 성능 문제가 발생할 수 있습니다.
유령으로 끌기
igxDrag 지시문은 템플릿에 추가하기만 하면 모든 DOM 요소에 적용할 수 있습니다.
<divigxDrag>Drag me</div>html
igxDrag 지시문의 기본 동작은 기본 요소를 수정하지 않은 채로 두고 최종 사용자가 드래그 작업을 수행할 때 고스트 요소를 생성하는 것입니다.
고스트가 페이지에 렌더링되기 전에 추가하려는 고스트 요소의 정보를 포함하는 ghostCreate 이벤트가 트리거됩니다. 이벤트는 dragStart 이벤트 직후에 트리거됩니다. dragStart가 취소되면 고스트가 생성되지 않으며 그에 따라 ghostCreate 이벤트가 트리거되지 않습니다.
언제든지 igxDrag에 전환 애니메이션을 적용할 수 있지만 끝을 드래그하거나 요소가 현재 드래그되지 않을 때 사용하는 것이 좋습니다. 이는 transitionToOrigin 및 transitionTo 메소드를 사용하여 달성할 수 있습니다.
이름에서 알 수 있듯이 transitionToOrigin 메소드는 현재 드래그된 요소나 그 고스트를 드래그가 시작된 시작 위치로 애니메이션화합니다. transitionTo 메소드는 페이지(예: pageX 및 pageY)를 기준으로 특정 위치 또는 지정된 요소의 위치로 요소에 애니메이션을 적용합니다. 요소가 현재 드래그되고 있지 않으면 어쨌든 애니메이션이 적용되거나 고스트를 생성하여 원하는 위치로 애니메이션이 적용됩니다.
두 함수 모두 전환 애니메이션을 사용자 정의하고 지속 시간, 타이밍 함수 또는 지연을 설정하기 위해 설정할 수 있는 인수를 가지고 있습니다. 특정 시작 위치가 설정되면 거기에서 시작하는 요소에 애니메이션이 적용됩니다.
전환 애니메이션이 끝나면 고스트가 생성되면 제거되고 igxDrag 지시문은 초기 상태로 돌아갑니다. 고스트가 생성되지 않으면 그 위치를 유지합니다. 두 경우 모두 애니메이션 지속 시간에 따라 transitioned 이벤트가 트리거됩니다. 애니메이션이 적용되지 않으면 즉시 트리거됩니다.
요소 변환을 포함하는 다른 유형의 애니메이션을 가질 수 있습니다. 이는 Angular Animations 또는 기본 igxDrag 요소나 고스트에 대한 직접 CSS Animations를 사용하여 다른 요소와 마찬가지로 수행할 수 있습니다. 고스트에 적용하려면 사용자 지정 고스트를 정의하고 해당 요소에 애니메이션을 적용해야 합니다.
드래그 핸들을 사용하여 목록의 항목을 재정렬합니다. 목록 항목을 드래그하는 동안 다른 목록 항목은 애니메이션으로 다시 정렬됩니다.
EXAMPLE
TS
HTML
SCSS
import {
Component,
ElementRef,
QueryList,
ViewChild,
ViewChildren
} from'@angular/core';
import { IDragBaseEventArgs, IDragMoveEventArgs, IgxDragDirective, IgxDragLocation, IgxListComponent, IgxListItemComponent, IgxDropDirective, IgxListLineTitleDirective, IgxListLineSubTitleDirective, IgxIconComponent, IgxDragHandleDirective, IgxListActionDirective } from'igniteui-angular';
import { NgFor } from'@angular/common';
@Component({
selector: 'app-list-reorder-sample',
templateUrl: './list-reorder-sample.component.html',
styleUrls: ['./list-reorder-sample.component.scss'],
imports: [IgxListComponent, NgFor, IgxListItemComponent, IgxDropDirective, IgxDragDirective, IgxListLineTitleDirective, IgxListLineSubTitleDirective, IgxIconComponent, IgxDragHandleDirective, IgxListActionDirective]
})
exportclassListReorderSampleComponent{
@ViewChildren('dragDirRef', { read: IgxDragDirective })
public dragDirs: QueryList<IgxDragDirective>;
@ViewChild('listContainer', { read: ElementRef })
public listContainer: ElementRef;
public employees = [
{ id: 0, name: 'Ivan Cornejo', title: 'Senior Product Owner' },
{ id: 1, name: 'Amish Shiravadakar', title: 'Business Tools Director' },
{ id: 2, name: 'Elsi Hansdottir', title: 'Financial Director' },
{ id: 3, name: 'Benito Noboa', title: 'Marketing Specialist' },
{ id: 4, name: 'Beth Murphy', title: 'Platform Lead for Web' }
];
public newIndex = null;
public animationDuration = 0.3;
private listItemHeight = 55;
public getDragDirectiveRef(id: number): IgxDragDirective {
returnthis.dragDirs.find((item) => item.data.id === id);
}
publiconDragStart(event: IDragBaseEventArgs, dragIndex: number) {
// Record the current index as basis for moving up/down.this.newIndex = dragIndex;
// Sets specific class when dragging.
event.owner.data.dragged = true;
}
publiconDragEnd(event: IDragBaseEventArgs, itemIndex: number) {
if (this.newIndex !== null) {
// When we have moved the dragged element up/down, animate it to its new location.const moveDown = this.newIndex > itemIndex;
// If the new position is below add the height moved down, otherwise subtract it.const prefix = moveDown ? 1 : -1;
// The height that the new position differs from the current. We know that each item is 55px height.const movedHeight = prefix * Math.abs(this.newIndex - itemIndex) * this.listItemHeight;
const originLocation = event.owner.originLocation;
event.owner.transitionTo(
new IgxDragLocation(originLocation.pageX, originLocation.pageY + movedHeight),
{ duration: this.animationDuration }
);
} else {
// Otherwise animate it to its original position, since it is unchanged.
event.owner.transitionToOrigin({ duration: this.animationDuration });
}
}
publiconTransitioned(event: IDragBaseEventArgs, itemIndex: number) {
// We can have other items transitioned when they move to free up space where the dragged element would be.if (event.owner.data.dragged && this.newIndex != null && this.newIndex !== itemIndex) {
// If the element finished transitioning is the one were dragging,// We can update all elements their new position in the list.this.shiftElements(itemIndex, this.newIndex);
event.owner.setLocation(event.owner.originLocation);
this.newIndex = null;
}
// Disables the specific class when dragging.
event.owner.data.dragged = false;
}
publiconDragMove(event: IDragMoveEventArgs, itemIndex: number) {
const containerPosY = this.listContainer.nativeElement.getBoundingClientRect().top;
// Relative position of the dragged element to the list container.const relativePosY = event.nextPageY - containerPosY;
let newIndex = Math.floor(relativePosY / this.listItemHeight);
newIndex = newIndex < 0 ? 0 : (newIndex >= this.employees.length ? this.employees.length - 1 : newIndex);
if (newIndex === this.newIndex) {
// If the current new index is unchanged do nothing.return;
}
const movingDown = newIndex > itemIndex;
if (movingDown && newIndex > this.newIndex ||
(!movingDown && newIndex < this.newIndex && newIndex !== itemIndex)) {
// If we are moving the dragged element down and the new index is bigger than the current// this means that the element we are stepping into is not shifted up and should be shifted.// Same if we moving the dragged element up and the new index is smaller than the current.const elementToMove = this.getDragDirectiveRef(this.employees[newIndex].id);
const currentLocation = elementToMove.location;
const prefix = movingDown ? -1 : 1;
elementToMove.transitionTo(
new IgxDragLocation(currentLocation.pageX, currentLocation.pageY + prefix * this.listItemHeight),
{ duration: this.animationDuration }
);
} else {
// Otherwise if are moving up but the new index is still bigger than the current, this means that// the item we are stepping into is already shifted and should be returned to its original position.// Same if we are moving down and the new index is still smaller than the current.const elementToMove = this.getDragDirectiveRef(this.employees[this.newIndex].id);
elementToMove.transitionToOrigin({ duration: this.animationDuration });
}
this.newIndex = newIndex;
}
privateshiftElements(draggedIndex: number, targetIndex: number) {
// Move the dragged element in DOM to the new position.const movedElem = this.employees.splice(draggedIndex, 1);
this.employees.splice(targetIndex, 0, movedElem[0]);
this.dragDirs.forEach((dir) => {
if (this.employees[targetIndex].id !== dir.data.id) {
// Reset each element its location since it will be repositioned in the DOM except the element we drag.
dir.setLocation(dir.originLocation);
dir.data.shifted = false;
}
});
}
}
ts
<igx-list #listContainer><igx-list-item *ngFor="let employee of employees; index as targetIndex;"
#dragDirRef="drag"igxDrop
[igxDrag]="{ id: employee.id, dragged: false }"
(dragStart)="onDragStart($event, targetIndex)"
(dragMove)="onDragMove($event, targetIndex)"
(dragEnd)="onDragEnd($event, targetIndex)"
(transitioned)="onTransitioned($event, targetIndex)"
[ghost]="false"
[class.dragged]="dragDirRef.data && dragDirRef.data.dragged"><h4igxListLineTitle>{{employee.name}}</h4><h6igxListLineSubTitle>{{employee.title}}</h6><igx-iconigxDragHandleigxListAction>drag_indicator</igx-icon></igx-list-item></igx-list>html
사용자가 igxDrag 인스턴스가 있는 기본 요소의 상호 작용 가능한 하위 항목을 갖고자 하는 경우 igxDrag가 해당 항목을 무시하고 드래그 작업을 수행하지 않도록 igxDragIgnore 지시어를 설정할 수 있습니다. 이렇게 하면 이러한 요소가 완전히 상호 작용할 수 있게 되고 모든 마우스 이벤트를 수신하게 됩니다.
Insert- 드래그한 요소를 마지막 위치에 삽입합니다. 요소를 놓을 때 요소 아래에 하위 요소가 있는 경우 igxDrag 인스턴스 요소는 해당 하위 요소의 위치에 삽입되고 다른 하위 요소는 이동됩니다. 이는 IgxInsertDropStrategy 라는 클래스로 구현됩니다.
전략을 적용할 수 있는 방법은 dropStrategy 입력을 위에 나열된 클래스 중 하나로 설정하는 것입니다. igxDrop 인스턴스 자체를 생성하고 관리해야 하므로 제공된 값은 인스턴스가 아닌 유형이어야 합니다.
public appendStrategy = IgxAppendDropStrategy;
typescript
특정 드롭 전략을 사용할 때 해당 동작은 다음에서 취소될 수 있습니다. dropped 이벤트를 설정하여 cancel 속성을 true로 설정합니다. 그만큼 dropped 이벤트는 특정 igxDrop. 드롭 전략이 적용되지 않은 경우 igxDrop 이벤트를 취소해도 부작용은 없습니다.
자체 드롭 로직을 구현하려면 dropped 이벤트에 바인딩하고 거기에서 로직을 실행하거나 IgxDefaultDropStrategy 클래스를 확장하는 것이 좋습니다.
드래그-드롭 요소 연결
사용하여 dragChannel 그리고 dropChannel 각각 입력 igxDrag 그리고 igxDrop 지시어를 사용하면 서로 다른 요소를 연결하여 서로 간에만 상호 작용할 수 있습니다. 예를 들어, igxDrag 특정 위치에 놓일 수 있도록 요소를 제한해야 합니다. igxDrop 모든 요소를 사용할 수 있는 것은 아니지만 동일한 채널을 할당하면 쉽게 달성할 수 있습니다.
<divigxDrag [dragChannel]="['Mammals', 'Land']"> Human </div><divigxDrag [dragChannel]="['Mammals', 'Water']"> Dolphin </div><divigxDrag [dragChannel]="['Insects', 'Air']"> Butterfly </div><divigxDrag [dragChannel]="['Insects', 'Land']"> Ant </div><divigxDrop [dropChannel]="['Mammals']"> Mammals </div><divigxDrop [dropChannel]="['Insects']"> Insects </div><divigxDrop [dropChannel]="['Land']"> Land </div>html
igxDrag와 igxDrop 결합하면 다양하고 복잡한 애플리케이션 시나리오에서 사용할 수 있으므로 다음 예에서는 Kanban 보드에서 이들을 사용하는 방법을 보여줍니다.
사용자는 각 열의 카드를 재정렬할 수 있습니다. 이는 각 카드에 드롭 영역을 설정하여 수행되므로 다른 카드가 해당 영역에 들어갈 때를 감지하고 런타임 시 전환하여 더 나은 사용자 경험을 제공할 수 있습니다.
열 간에 카드를 전환하는 기능이 없으면 Kanban 보드가 아닙니다. 카드는 특정 위치의 한 열에서 다른 열로 직접 이동할 수 있습니다. 여기서는 더미 개체를 사용하여 달성되므로 카드를 놓았을 때 카드가 위치할 시각적 영역이 생성됩니다. 카드 드래그가 끝나거나 다른 열에서 나가면 더미 개체가 제거됩니다.
칸반 보드 주위로 항목을 드래그합니다.
EXAMPLE
TS
HTML
SCSS
/* eslint-disable no-shadow *//* eslint-disable @typescript-eslint/naming-convention */import { ChangeDetectorRef, Component, ElementRef, OnInit, Renderer2, ViewChild } from'@angular/core';
import { IDropBaseEventArgs, IDropDroppedEventArgs, IgxDropDirective, IgxChipComponent, IgxCardComponent, IgxDragDirective, IgxCardHeaderComponent, IgxCardHeaderTitleDirective, IgxCardContentDirective } from'igniteui-angular';
import { NgFor } from'@angular/common';
enum state {
toDo = 'toDo',
inProgress = 'inProgress',
done = 'done'
}
interface IListItem {
id: string;
text: string;
state: state;
hide?: boolean;
}
@Component({
selector: 'app-kanban-sample',
templateUrl: './kanban-sample.component.html',
styleUrls: ['./kanban-sample.component.scss'],
imports: [IgxDropDirective, IgxChipComponent, NgFor, IgxCardComponent, IgxDragDirective, IgxCardHeaderComponent, IgxCardHeaderTitleDirective, IgxCardContentDirective]
})
exportclassKanbanSampleComponentimplementsOnInit{
@ViewChild('toDo', { read: ElementRef }) public toDo: ElementRef;
@ViewChild('inProgress', { read: ElementRef }) public inProgress: ElementRef;
@ViewChild('done', { read: ElementRef }) public done: ElementRef;
public toDoList: IListItem[];
public inProgressList: IListItem[];
public doneList: IListItem[];
private dragObj;
private dummyObj;
private lastDragEnterList: string;
private currentList: string;
constructor(private renderer: Renderer2, private cdr: ChangeDetectorRef) { }
public ngOnInit(): void {
this.toDoList = [
{ id: 'STR-000132', text: 'Implement chat bubble', state: state.toDo },
{ id: 'STR-000097', text: 'Implement sticky header', state: state.toDo },
{ id: 'STR-000191', text: 'Change trial days to credit', state: state.toDo }
];
this.inProgressList = [
{ id: 'STR-000124', text: 'Implement fback widget', state: state.inProgress },
{ id: 'STR-000121', text: 'Add analytics', state: state.inProgress }
];
this.doneList = [
{ id: 'STR-000129', text: 'Add SSL to account pages', state: state.done }
];
this.dragObj = null;
this.dummyObj = null;
this.lastDragEnterList = '';
this.currentList = '';
}
publiconStateContainerEnter(event: IDropBaseEventArgs) {
// If we have entered another list container, we have to remove the 'dummy' object from the previous oneif (this.currentList !== event.owner.element.nativeElement.id) {
this[this.currentList] = this[this.currentList].filter((item) => item.id !== 'dummy');
this.cdr.detectChanges();
this.currentList = event.owner.element.nativeElement.id;
this.dummyObj = null;
}
// Add the blue container hightlight when an item starts being draggedthis.renderer.addClass(event.owner.element.nativeElement, 'active');
}
publiconStateContainerLeave(event: IDropBaseEventArgs) {
// This event also gets raised when the user drags a task over another task tile.// That means we have to re-apply the 'active' class in the `onItemEnter` event handlerthis.renderer.removeClass(event.owner.element.nativeElement, 'active');
}
publicdragStartHandler(event) {
// We have to save the dragStartList so we could remove the dragged item from it later, when it gets droppedthis.currentList = event.owner.element.nativeElement.dataset.state + 'List';
this.lastDragEnterList = this.currentList;
this.dragObj = this[this.currentList].filter((elem) => elem.id === event.owner.element.nativeElement.id)[0];
}
publicdragEndHandler(event) {
this.toDoList = this.toDoList.filter((x) => x.id !== 'dummy');
this.inProgressList = this.inProgressList.filter((x) => x.id !== 'dummy');
this.doneList = this.doneList.filter((x) => x.id !== 'dummy');
if (this.dragObj) {
this.dragObj.hide = false;
}
}
publiconItemEnter(event: IDropBaseEventArgs) {
// Applying the container highlighting againconst listContainer = event.owner.element.nativeElement.dataset.state;
this.renderer.addClass(this[listContainer].nativeElement, 'active');
const currentList = event.owner.element.nativeElement.dataset.state + 'List';
const currentItemIndex = this[currentList].findIndex((item) => item.id === event.owner.element.nativeElement.id);
// Checking if items in the same list are being reorderedif (this.lastDragEnterList === currentList) {
const draggedItemIndex = this[currentList].findIndex((item) => item.id === this.dragObj.id);
this.swapTiles(draggedItemIndex, currentItemIndex, currentList);
} else {
// We need a hidden dummy object that would make an empty space for the dragged element in the listif (!this.dummyObj) {
this.dummyObj = {id: 'dummy', text: '', state: event.owner.element.nativeElement.dataset.state};
const newCurrentList = [
...this[currentList].slice(0, currentItemIndex),
this.dummyObj,
...this[currentList].slice(currentItemIndex)
];
this[currentList] = newCurrentList;
this.cdr.detectChanges();
} else {
const dummyObjIndex = this[currentList].findIndex((item) => item.id === 'dummy');
if (dummyObjIndex !== -1) {
this.swapTiles(dummyObjIndex, currentItemIndex, currentList);
}
}
}
}
publiconItemLeave(event: IDropBaseEventArgs) {
const listContainer = event.owner.element.nativeElement.dataset.state;
this.renderer.removeClass(this[listContainer].nativeElement, 'active');
}
publiconItemDropped(event: IDropDroppedEventArgs) {
const dropListState = event.owner.element.nativeElement.id;
const dragListState = event.drag.element.nativeElement.dataset.state + 'List';
const dummyItemIndex = this[dropListState].findIndex((item) => item.id === 'dummy');
if (dropListState !== dragListState) {
// The state of the dragged object should be updated before inserting it in the dropped listthis.dragObj.state = dropListState.substring(0, dropListState.length - 4);
this[dragListState] = this[dragListState].filter((item) => item.id !== this.dragObj.id);
// Check if there is a dummy item and replace it with the dragged oneif (dummyItemIndex !== -1) {
this[dropListState].splice(dummyItemIndex, 1, this.dragObj);
} else {
this[dropListState].push(this.dragObj);
}
}
this.dragObj.hide = false;
this.dragObj = null;
// The default browser drag behavior should be cancelled
event.cancel = true;
}
private swapTiles(currentIndex: number, targetIndex: number, itemList: string): void {
const tempObj = this[itemList][currentIndex];
this[itemList].splice(currentIndex, 1);
this[itemList].splice(targetIndex, 0, tempObj);
this.cdr.detectChanges();
}
}
ts