Angular 그리드에서 행 끌기

    Ignite UI for Angular에서 RowDrag는 루트 igx-grid 구성 요소에서 초기화되고 rowDraggable 입력을 통해 구성 가능합니다. 행 끌기를 활성화하면 사용자에게 행 끌기를 시작할 수 있는 행 끌기 핸들이 제공됩니다.

    Angular Grid Row Drag Example

    구성

    igx-grid에 대한 행 드래그를 활성화하려면 그리드의 rowDraggable​ ​true로 설정하기만 하면 됩니다. 이 기능이 활성화되면 행 끌기 핸들이 각 행에 표시됩니다. 이 핸들을 사용하여 행 끌기를 시작할 수 있습니다.

    <igx-grid [rowDraggable]="true">
     ...
    </igx-grid>
    

    드래그 핸들을 클릭하고 버튼을 누른 채 커서를 움직이면 그리드의 rowDragStart 이벤트가 시작됩니다. 언제든지 클릭을 놓으면 rowDragEnd 이벤트가 발생합니다.

    아래에서는 행 드래그를 지원하도록 igx-grid 구성하는 방법과 드롭 이벤트를 적절하게 처리하는 방법에 대한 연습을 찾을 수 있습니다.

    이 예에서는 한 그리드에서 다른 그리드로 행을 끌어 첫 번째 데이터 소스에서 제거하고 두 번째 데이터 소스에 추가하는 작업을 처리합니다.

    Drop Areas

    행 드래그를 활성화하는 것은 매우 쉬웠지만 이제 행 삭제를 처리하는 방법을 구성해야 합니다. igxDrop 지시문을 사용하여 행을 삭제할 위치를 정의할 수 있습니다.

    먼저 앱 모듈에서 IgxDragDropModule을 가져와야 합니다.

    import { ..., IgxDragDropModule } from 'igniteui-angular';
    // import { ..., IgxDragDropModule } from '@infragistics/igniteui-angular'; for licensed package
    ...
    @NgModule({
        imports: [..., IgxDragDropModule]
    })
    

    그런 다음 템플릿에서 지시어 선택기를 사용하여 드롭 영역을 정의합니다.

    이 경우 놓기 영역은 행을 놓을 전체 두 번째 그리드가 됩니다.

    <igx-grid #targetGrid igxDrop [data]="data2" [autoGenerate]="false" [emptyGridTemplate]="dragHereTemplate"
        (enter)="onEnterAllowed($event)" (leave)="onLeaveAllowed($event)" (dropped)="onDropAllowed($event)" [primaryKey]="'ID'">
        ...
    </igx-grid>
    

    그리드는 처음에는 비어 있으므로 사용자에게 더 의미 있는 템플릿도 정의합니다.

    <ng-template #dragHereTemplate>
        Drop a row to add it to the grid
    </ng-template>
    

    다음을 사용하여 놓을 수 없는 영역에 행을 놓을 때 애니메이션을 활성화할 수 있습니다. animation 매개변수 rowDragEnd 이벤트. true로 설정하면 드래그된 행을 놓을 수 없는 영역 위에 놓으면 원래 위치로 다시 애니메이션이 적용됩니다.

    다음과 같이 애니메이션을 활성화할 수 있습니다:

    export class IgxGridRowDragComponent {
    
        public onRowDragEnd(args) {
            args.animation = true;
        }
    
    }
    

    Drop Area Event Handlers

    템플릿에 놓기 영역을 정의한 후에는 해당 항목에 대한 핸들러를 선언해야 합니다. igxDrop '에스 enter, leave 그리고 dropped 우리 컴포넌트의 이벤트.ts 파일.

    먼저, enterleave 핸들러를 살펴보겠습니다. 이러한 방법에서는 드래그의 고스트 아이콘을 변경하여 사용자에게 행을 드롭할 수 있는 영역 위에 있음을 표시할 수 있습니다.

    export class IgxGridRowDragComponent {
        public onEnterAllowed(args) {
            this.changeGhostIcon(args.drag.ghostElement, DragIcon.ALLOW);
        }
    
        public onLeaveAllowed(args) {
            this.changeGhostIcon(args.drag.ghostElement, DragIcon.DEFAULT);
        }
    
        private changeGhostIcon(ghost, icon: string) {
            if (ghost) {
                const currentIcon = ghost.querySelector('.igx-grid__drag-indicator > igx-icon');
                if (currentIcon) {
                    currentIcon.innerText = icon;
                }
            }
        }
    }
    

    changeGhostIcon 비공개 메소드는 드래그 고스트 내부의 아이콘을 변경합니다. 메서드의 로직은 드래그 표시기 컨테이너에 적용되는 igx-grid__drag-indicator 클래스를 사용하여 아이콘이 포함된 요소를 찾아 요소의 내부 텍스트를 전달된 텍스트로 변경합니다. 아이콘 자체는 material 글꼴 세트에서 가져온 것이며 별도의 enum에 정의됩니다.

    enum DragIcon {
        DEFAULT = 'drag_indicator',
        ALLOW = 'add'
    }
    

    다음으로 사용자가 실제로 놓기 영역 내부에 행을 놓을 때 어떤 일이 발생해야 하는지 정의해야 합니다.

    export class IgxGridRowDragComponent {
        @ViewChild('sourceGrid', { read: IgxGridComponent }) public sourceGrid: IgxGridComponent;
        @ViewChild('targetGrid', { read: IgxGridComponent }) public targetGrid: IgxGridComponent;
    
        public onDropAllowed(args) {
            this.targetGrid.addRow(args.dragData.data);
            this.sourceGrid.deleteRow(args.dragData.key);
        }
    }
    

    ViewChild 데코레이터를 통해 각 그리드에 대한 참조를 정의하고 다음과 같이 드롭을 처리합니다.

    • 삭제되는 행의 데이터가 포함된 targetGrid에 행을 추가합니다.
    • sourceGrid에서 드래그된 행을 제거합니다.
    Note

    이벤트 인수(args.dragData.data) 또는 다른 행 속성의 행 데이터를 사용하는 경우 전체 행이 인수에 참조로 전달됩니다. 즉, 원하는 경우 필요한 데이터를 복제해야 합니다. 이를 소스 그리드의 것과 구별합니다.

    Templating the drag ghost

    드래그 고스트는 igx-grid 본문 내부의 <ng-template>에 적용되는 IgxRowDragGhost 지시문을 사용하여 템플릿화할 수 있습니다.

    <igx-grid>
    ...
       <ng-template igxRowDragGhost>
            <div>
                <igx-icon fontSet="material">arrow_right_alt</igx-icon>
            </div>
        </ng-template>
    ...
    </igx-grid>
    

    구성 결과는 행 드래그 및 다중 선택이 활성화된 igx-grid에서 아래에 표시될 수 있습니다. 데모에서는 현재 드래그된 행의 개수를 보여줍니다.

    예제 데모

    Templating the drag icon

    드래그 핸들 아이콘은 그리드의 dragIndicatorIconTemplate 사용하여 템플릿화할 수 있습니다. 우리가 만들고 있는 예제에서 아이콘을 기본 아이콘(drag_indicator)에서 drag_handle로 변경해 보겠습니다. 이를 위해 igxDragIndicatorIcon 사용하여 igx-grid 본문 내부에 템플릿을 전달할 수 있습니다.

    <igx-grid>
    ...
        <ng-template igxDragIndicatorIcon>
            <igx-icon>drag_handle</igx-icon>
        </ng-template>
    ...
    </igx-grid>
    

    새 아이콘 템플릿을 설정한 후에는 DragIcon enumDEFAULT 아이콘도 조정해야 합니다. 그러면 changeIcon 메서드에 의해 적절하게 변경됩니다.

    enum DragIcon {
        DEFAULT = "drag_handle",
        ...
    }
    

    드롭 핸들러가 올바르게 구성되면 작업을 시작할 수 있습니다! 구성 결과는 다음과 같습니다.

    예제 데모

    Application Demo

    Using Row Drag Events

    다음 데모에서는 행 드래그 이벤트 정보를 사용하여 행이 삭제되는 사용자 정의 구성 요소의 상태와 소스 그리드 자체를 모두 변경하는 방법을 보여줍니다. 그리드에서 달을 끌어 해당 행성에 놓아보세요. 행 드래그 고스트 배경은 호버링된 행성에 따라 동적으로 변경됩니다. 성공하면 그리드의 행이 선택되고 끌기가 비활성화됩니다. 행성을 클릭하면 유용한 정보를 얻을 수 있습니다.

    Note

    위 데모에서 사용된 행 드래그 고스트에 적용된 클래스는 ::ng-deep 수정자를 사용하고 있습니다. 행 드래그는 내부 그리드 기능이고 CSS 캡슐화로 인해 애플리케이션 수준에서 액세스할 수 없기 때문입니다.

    Row Reordering Demo

    그리드의 행 드래그 이벤트와 igxDrop 지시문을 사용하면 행을 드래그하여 재정렬할 수 있는 그리드를 생성할 수 있습니다.

    모든 작업은 그리드 본체 내부에서 발생하므로 여기에 igxDrop 지시문을 첨부해야 합니다.

    <igx-grid #grid [data]="data" [rowDraggable]="true" [primaryKey]="'ID'" igxDrop (dropped)="onDropAllowed($event)">
        ...
    </igx-grid>
    
    Note

    그리드에 대해 primaryKey 지정되어 있는지 확인하세요! 논리에는 행을 올바르게 다시 정렬할 수 있도록 행에 대한 고유 식별자가 필요합니다.

    rowDraggable 활성화되고 드롭 영역이 정의되면 드롭 이벤트에 대한 간단한 핸들러를 구현해야 합니다. 행을 드래그할 때 다음을 확인하세요.

    • 행이 그리드 내부에 삭제되었습니까?
    • 그렇다면 드래그된 행이 다른 어느 행에 삭제되었습니까?
    • 대상 행을 찾았으면 data 배열에서 레코드 위치를 바꿉니다.

    아래에서는 구성 요소의.ts 파일에 구현된 내용을 볼 수 있습니다.

    export class GridRowReorderComponent {
        public onDropAllowed(args) {
            const event = args.originalEvent;
            const currRowIndex = this.getCurrentRowIndex(this.grid.rowList.toArray(),
                { x: event.clientX, y: event.clientY });
            if (currRowIndex === -1) { return; }
            this.grid.deleteRow(args.dragData.key);
            this.data.splice(currRowIndex, 0, args.dragData.data);
        }
    
        private getCurrentRowIndex(rowList, cursorPosition) {
            for (const row of rowList) {
                const rowRect = row.nativeElement.getBoundingClientRect();
                if (cursorPosition.y > rowRect.top + window.scrollY && cursorPosition.y < rowRect.bottom + window.scrollY &&
                    cursorPosition.x > rowRect.left + window.scrollX && cursorPosition.x < rowRect.right + window.scrollX) {
                    return this.data.indexOf(this.data.find((r) => r.rowID === row.rowID));
                }
            }
    
            return -1;
        }
    }
    

    이러한 몇 가지 간단한 단계를 통해 드래그/드롭을 통해 행을 재정렬할 수 있는 그리드를 구성했습니다! 다음 데모에서 위 코드가 실제로 작동하는 모습을 볼 수 있습니다.

    드래그 아이콘을 누르고 있으면 그리드의 어느 곳으로든 행을 이동할 수 있습니다.

    Improving UX in row drag scenarios

    현재 커서 아래에 있는 행 인덱스를 얻을 수 있으면 풍부한 사용자 정의 기능을 구축하고 애플리케이션의 UX를 개선할 수 있는 기회를 얻을 수 있습니다. 예를 들어, 그리드 위에서 드래그된 행의 위치를 기반으로 드래그 고스트를 변경하거나 드롭 표시기를 표시할 수 있습니다. 이 방법으로 달성할 수 있는 또 다른 유용한 동작은 그리드의 테두리에 도달할 때 행을 드래그하는 동안 그리드를 위나 아래로 스크롤하는 것입니다.

    아래에서는 행의 위치를 알고 달성할 수 있는 몇 가지 사용자 정의 구현의 예제 스니펫을 찾을 수 있습니다.

    커서 위치에 따라 드래그 고스트 변경

    아래 스니펫에서는 드래그 고스트 내부의 텍스트를 변경하여 마우스를 올린 행의 이름을 표시하는 방법을 볼 수 있습니다.

    먼저 드래그 고스트에 사용할 템플릿을 지정합니다. dropName 속성은 동적으로 변경되어 커서가 가리키고 있는 행의 이름을 가져옵니다.

    <ng-template igxRowDragGhost>
        <div class="customGhost">
            <div>{{ dropName }}</div>
        </div>
    </ng-template>
    

    그런 다음, 끝난 행의 인스턴스를 반환하는 메서드를 정의합니다(행 재정렬 데모에서 사용된 것과 유사).

    class MyRowGhostComponent {
        private getRowDataAtPoint(rowList: IgxGridRowComponent[], cursorPosition: Point): any {
            for (const row of rowList) {
                const rowRect = row.nativeElement.getBoundingClientRect();
                if (cursorPosition.y > rowRect.top + window.scrollY && cursorPosition.y < rowRect.bottom + window.scrollY &&
                    cursorPosition.x > rowRect.left + window.scrollX && cursorPosition.x < rowRect.right + window.scrollX) {
                    return this.data.find((r) => r.rowID === row.rowID);
                }
            }
            return null;
        }
    }
    

    마지막으로 IgxDragDirective.dragMove 이벤트(드래그된 행에 대해 발생)를 처리하는 데 사용할 메서드를 만듭니다. 이 메소드는 igxRowDragGhost 템플릿에 사용된 속성 값을 변경하고 강제로 다시 렌더링합니다. 우리는 드래그하고 있는 특정 행에 대해서만 dragMove 이벤트를 구독하고 행이 삭제될 때마다 (메모리 누수를 방지하기 위해) 구독을 취소하려고 합니다.

    class MyRowGhostComponent {
        public ngAfterViewInit(): void {
            this.grid.rowDragStart.pipe(takeUntil(this.destroy$)).subscribe(this.onRowDragStart.bind(this));
        }
    
        private onRowDragStart(e: IRowDragStartEventArgs) {
            if (e !== null) {
                this._draggedRow = e.dragData.rowData;
            }
            const directive = e.dragDirective;
            directive.dragMove
                .pipe(takeUntil(this.grid.rowDragEnd))
                .subscribe(this.onDragMove.bind(this));
        }
    
        private onDragMove(args: IDragMoveEventArgs) {
            const cursorPosition = this.getCursorPosition(args.originalEvent);
            const hoveredRowData = this.getRowDataAtPoint(
                this.grid.rowList.toArray(),
                cursorPosition
            );
            if (!hoveredRowData) {
                args.cancel = true;
                return;
            }
            const rowID = hoveredRowData.ID;
            if (rowID !== null) {
                let newName = this.dropName;
                if (rowID !== -1) {
                    const targetRow = this.grid.rowList.find((e) => {
                        return e.rowData.ID === rowID;
                    });
                    newName = targetRow?.rowData.Name;
                }
                if (newName !== this.dropName) {
                    this.dropName = newName;
                    args.owner.cdr.detectChanges();
                }
            }
        }
    }
    
    

    커서 위치에 따라 드롭 표시기 표시

    다음 섹션의 데모에서는 드래그된 행이 삭제될 위치에 대한 표시기를 표시하는 방법을 볼 수 있습니다. 이 표시기를 원하는 대로 사용자 정의할 수 있습니다. 드래그된 행이 놓일 위치에 배치되는 자리 표시자 행, 드래그된 행이 현재 마우스오버된 행 위 또는 아래에 놓일지 여부를 나타내는 테두리 스타일 등이 될 수 있습니다.

    커서의 위치를 추적하기 위해 dragMove의 이벤트 IgxDragDirective 행을 끌기 시작할 때.

    Note

    그리드에 대해 primaryKey 지정되어 있는지 확인하세요! 논리에는 행을 올바르게 다시 정렬할 수 있도록 행에 대한 고유 식별자가 필요합니다.

    public ngAfterViewInit() {
      this.grid.rowDragStart
        .pipe(takeUntil(this.destroy$))
        .subscribe(this.handleRowStart.bind(this));
    }
    
    private handleRowStart(e: IRowDragStartEventArgs): void {
      if (e !== null) {
        this._draggedRow = e.dragData.data;
      }
      const directive = e.dragDirective;
      directive.dragMove
        .pipe(takeUntil(this.grid.rowDragEnd))
        .subscribe(this.handleDragMove.bind(this));
    }
    
    private handleDragMove(event: IDragMoveEventArgs): void {
      this.handleOver(event);
    }
    
    private handleOver(event: IDragMoveEventArgs) {
      const ghostRect = event.owner.ghostElement.getBoundingClientRect();
      const rowIndex = this.getRowIndexAtPoint(this.grid.rowList.toArray(), {
        x: ghostRect.x,
        y: ghostRect.y
      });
      if (rowIndex === -1) {
        return;
      }
      const rowElement = this.grid.rowList.find(
        e => e.rowData.ID === this.grid.data[rowIndex].ID
      );
      if (rowElement) {
        this.changeHighlightedElement(rowElement.element.nativeElement);
      }
    }
    
    private clearHighlightElement(): void {
      if (this.highlightedRow !== undefined) {
        this.renderer.removeClass(this.highlightedRow, 'underlined-class');
      }
    }
    
    private setHightlightElement(newElement: HTMLElement) {
      this.renderer.addClass(newElement, 'underlined-class');
      this.highlightedRow = newElement;
    }
    
    private changeHighlightedElement(newElement: HTMLElement) {
      if (newElement !== undefined) {
        if (newElement !== this.highlightedRow) {
          this.clearHighlightElement();
          this.setHightlightElement(newElement);
        } else {
          return;
        }
      }
    }
    

    행 드래그 시 그리드 스크롤

    매우 유용한 시나리오는 드래그된 행이 위쪽 또는 아래쪽 테두리에 도달할 때 그리드를 스크롤할 수 있다는 것입니다. 이를 통해 그리드의 행 수에 스크롤 막대가 필요한 경우 현재 뷰포트 외부에서 행을 재정렬할 수 있습니다.

    아래에는 뷰포트 가장자리에 도달했는지 확인하고 필요한 경우 스크롤하는 데 사용하는 두 가지 방법의 예가 나와 있습니다. isGridScrolledToEdge는 그리드를 스크롤하려는 방향("아래"의 경우 1, "위"의 경우 -1)이라는 하나의 매개변수를 허용하고 해당 방향의 마지막 행에 도달하면 true 반환합니다. scrollGrid 메소드는 그리드를 한 방향(1 또는 -1)으로 스크롤하려고 시도하며 그리드가 이미 해당 가장자리에 있는 경우 아무 작업도 수행하지 않습니다.

    class MyGridScrollComponent {
        private isGridScrolledToEdge(dir: 1 | -1): boolean {
            if (this.grid.data[0] === this.grid.rowList.first.data && dir === -1) {
                return true;
            }
            if (
                this.grid.data[this.grid.data.length - 1] === this.grid.rowList.last.data &&
                dir === 1
            ) {
                return true;
            }
            return false;
        }
    
        private scrollGrid(dir: 1 | -1): void {
            if (!this.isGridScrolledToEdge(dir)) {
                if (dir === 1) {
                    this.grid.verticalScrollContainer.scrollNext();
                } else {
                    this.grid.verticalScrollContainer.scrollPrev();
                }
            }
        }
    }
    
    

    이전 예제에서 했던 방식으로 특정 행의 dragMove 이벤트를 계속 구독할 것입니다. dragMove 커서가 실제로 움직일 때만 실행되므로 행이 가장자리 중 하나에 있지만 사용자가 마우스를 움직이지 않을 때 그리드를 자동 스크롤하는 멋지고 간단한 방법을 원합니다. interval 설정하고 500ms 마다 그리드를 자동 스크롤하는 추가 방법을 사용하겠습니다.

    포인터가 그리드 가장자리에 도달할 때 interval 생성하고 구독하며, 마우스가 움직이거나 행이 삭제될 때마다(커서 위치에 관계없이) 해당 intervalunsubscribe.

    class MyGridScrollComponent {
        public ngAfterViewInit() {
            this.grid.rowDragStart
                .pipe(takeUntil(this.destroy$))
                .subscribe(this.onDragStart.bind(this));
            this.grid.rowDragEnd
                .pipe(takeUntil(this.destroy$))
                .subscribe(() => this.unsubInterval());
        }
    
        private onDragMove(event: IDragMoveEventArgs): void {
            this.unsubInterval();
            const dir = this.isPointOnGridEdge(event.pageY);
            if (!dir) {
                return;
            }
            this.scrollGrid(dir);
            if (!this.intervalSub) {
                this.interval$ = interval(500);
                this.intervalSub = this.interval$.subscribe(() => this.scrollGrid(dir));
            }
        }
    
        private unsubInterval(): void {
            if (this.intervalSub) {
                this.intervalSub.unsubscribe();
                this.intervalSub = null;
            }
        }
    }
    

    다음은 위에 설명된 두 가지 시나리오의 예입니다. 테두리 가장자리에 도달하면 드롭 표시기를 표시하고 뷰포트를 스크롤합니다.

    Limitations

    현재 rowDraggable 지시어에 대해 알려진 제한 사항은 없습니다.

    API References

    Additional Resources

    우리 커뮤니티는 활동적이며 항상 새로운 아이디어를 환영합니다.