Angular 그리드에서 행 끌기

    Ignite UI for Angular Grid에서 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/directives';
    // 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

    템플릿에서 드롭 영역을 정의한 후에는 컴포넌트 파일에 있는 핸들러igxDropenterleavedropped와 이벤트를 선언해야 합니다..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

    드래그 고스트는 다음 명령어를 사용하여IgxRowDragGhost 템플릿화할 수 있으며, 이 명령은 몸체 내부<ng-template>igx-grid 적용됩니다:

    <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>
    

    새 아이콘 템플릿을 설정한 후에는 다음 메서드로DEFAULT 제대로 변경할 수 있도록 아이콘도 조정DragIcon enumchangeIcon 해야 합니다:

    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();
                }
            }
        }
    }
    
    

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

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

    커서의 위치를 추적하기 위해 행을 드래그하기 시작할 때 이벤트에dragMoveIgxDragDirective 바인딩합니다.

    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 생성하고 구독하며,unsubscribeinterval 마우스가 움직이거나 행이 떨어질 때마다(커서 위치와 상관없이) 이를 생성합니다.

    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

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