Angular Tree Grid の行ドラッグ

    Ignite UI for Angular Tree Grid では、RowDrag がルート igx-tree-grid コンポーネントで初期化されて、rowDraggable 入力で設定できます。行ドラッグを有効にすると、ユーザーは行ドラッグ ハンドルを使用して行のドラッグを開始できます。

    Angular Tree Grid 行ドラッグの例

    構成

    igx-tree-grid の行ドラッグを有効にするには、グリッドの rowDraggabletrue に設定します。これが有効になると、行ドラッグ ハンドルが各行に表示されます。このハンドルは行ドラッグを開始するために使用できます。

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

    ドラッグ ハンドルをクリックしてボタンを押しながらカーソルを動かすと、グリッドの rowDragStart イベントが発生します。クリックをリリースすると、rowDragEnd イベントが発生します。

    以下は、行ドラッグをサポートするための igx-tree-grid の設定方法と、ドロップイベントの適切な処理方法についてのチュートリアルです。

    この例では、グリッドから指定された領域に行をドラッグし、ドロップするとグリッドから削除します。

    ドロップ エリア

    行ドラッグを簡単に有効にできました。次は行ドロップを処理する方法を設定する必要があります。 igxDrop ディレクティブを使用して、行をドロップする場所を定義できます。

    はじめに、アプリ モジュールに IgxDragDropModule をインポートする必要があります。

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

    次にテンプレートでディレクティブのセレクターを使ってドロップ エリアを定義します。

    <div class="drop-area" igxDrop (enter)="onEnterAllowed($event)" (leave)="onLeaveAllowed($event)"
    (dropped)="onDropAllowed($event)">
        <igx-icon>delete</igx-icon>
        <div>Drag a row here to delete it</div>
    </div>
    

    rowDragEnd イベントの animation パラメーターを使用して、ドロップできない領域に行がドロップされたときにアニメーションを有効にできます。true に設定されている場合、ドラッグされた行は、ドロップできない領域の上にドロップされると元の位置に戻ります。

    以下はアニメーションを有効にする方法です。

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

    ドロップ エリア イベント ハンドラー

    テンプレートでドロップ領域を定義したら、コンポーネントの .ts ファイルで igxDropenterleavedropped イベントを宣言する必要があります。

    はじめに、enterleave ハンドラーを見てみましょう。これらのメソッドでは、ドラッグの ghost のアイコンを変更して、行をドロップできる領域の上にあることをユーザーに示すことができます。

    export class IgxTreeGridRowDragComponent {
        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 private メソッドは、ドラッグ ゴースト内のアイコンを変更するだけです。メソッドのロジックは、アイコンを含む要素を検索し (ドラッグ インジケーター コンテナーに適用される igx-grid__drag-indicator クラスを使用)、要素の内部テキストを渡されたものに変更します。 アイコンは material フォントセットからのもので、別の enum で定義されています。

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

    次に、ユーザーが実際にドロップ領域内に行をドロップしたときに何が起こるかを定義する必要があります。

    export class IgxTreeGridRowDragComponent {
    
        public onDropAllowed(args: IDropDroppedEventArgs) {
            const draggedRow: RowType = args.dragData;
            draggedRow.delete();
        }
    
    }
    

    行が削除されたら、行の delete() メソッドを呼び出すだけです。

    Note

    イベント引数 (args.dragData.data) または他の行プロパティからの行データを使用する場合、行全体が参照として引数に渡されることに注意してください。つまり、ソースグリッドのデータと区別する必要がある場合は、必要なデータを複製する必要があります。

    ドラッグ ゴーストのテンプレート化

    ドラッグゴーストは、igx-tree-grid の本文内の <ng-template> に適用される IgxRowDragGhost ディレクティブを使用してテンプレート化できます。

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

    以下は、行ドラッグと複数選択を有効にした igx-tree-grid で確認できる設定の結果です。以下のデモでは、現在ドラッグされている行の数を示します。

    デモ

    ドラッグ アイコンのテンプレート化

    ドラッグ ハンドル アイコンは、グリッドの dragIndicatorIconTemplate を使用してテンプレート化できます。作成している例で、アイコンをデフォルトのもの (drag_indicator) から drag_handle に変更します。 igxDragIndicatorIcon を使用して igx-tree-grid の本文内にテンプレートを渡して変更できます。

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

    新しいアイコン テンプレートの設定後、DragIcon enumDEFAULT アイコンも調整する必要があるため、changeIcon メソッドによって適切に変更されます。

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

    ドロップ エリアのスタイル

    ドロップ ハンドラが正しく設定されたら、次にドロップ領域をスタイル設定します。

    .drop-area {
        width: 160px;
        height: 160px;
        background-color: #d3d3d3;
        border: 1px dashed #131313;
        display: flex;
        justify-content: center;
        align-items: center;
        flex-flow: column;
        text-align: center;
        margin: 8px;
    }
    
    :host {
        display: flex;
        justify-content: center;
        align-items: center;
        flex-flow: column;
        width: 100%;
    }
    

    結果は以下のデモで確認できます。

    デモ

    アプリケーション デモ

    行の並べ替えデモ

    グリッドの行ドラッグ イベントと igxDrop ディレクティブを使用して、ドラッグよる行の並べ替えるが可能なグリッドを作成できます。

    すべてのアクションはグリッド本体の内側で発生するため、ここで igxDrop ディレクティブをアタッチする必要があります:

    <igx-tree-grid igxPreventDocumentScroll  #treeGrid [data]="localData" [rowDraggable]="true" foreignKey="ParentID"
        [primaryKey]="'ID'" (rowDragStart)="rowDragStart($event)" igxDrop (dropped)="dropInGrid($event)">
        ...
    </igx-tree-grid>
    
    Note

    グリッドに primaryKey が指定されていることを確認してください!ロジックが行を適切に並べ替えられるように、行には一意の識別子が必要です。

    rowDraggable が有効になり、ドロップ エリアが定義されたら、ドロップ イベントの単純なハンドラーを実装する必要があります。行をドラッグするときは、以下を確認してください:

    • 行が展開されていますか? そうであれば、行を縮小します。
    • 行はグリッド内にドロップされましたか?
    • そうであれば、ドラッグされた行が他のどの行にドロップされましたか?
    • ターゲット行が見つかれば、data 配列内のレコードの位置を入れ替えます。
    • 行は最初に選択されてましたか? そうであれば、選択済みとしてマークします。

    以下では、コンポーネントの .ts ファイルに実装されていることがわかります。

    export class TreeGridRowReorderComponent {
        public rowDragStart(args: any): void {
            const targetRow = args.dragData;
            if (targetRow.expanded) {
                this.treeGrid.collapseRow(targetrow.key);
            }
        }
    
        public dropInGrid(args: IDropDroppedEventArgs): void {
            const draggedRow = args.dragData;
            const event = args.originalEvent;
            const cursorPosition: Point = { x: event.clientX, y: event.clientY };
            this.moveRow(draggedRow, cursorPosition);
        }
    
        private moveRow(draggedRow: RowType, cursorPosition: Point): void {
            const row = this.catchCursorPosOnElem(this.treeGrid.rowList.toArray(), cursorPosition);
            if (!row) { return; }
            if (row.data.ParentID === -1) {
                this.performDrop(draggedRow, row).ParentID = -1;
            } else {
                if (row.data.ParentID === draggedrow.data.ParentID) {
                    this.performDrop(draggedRow, row);
                } else {
                    const rowIndex = this.getRowIndex(draggedrow.data);
                    this.localData[rowIndex].ParentID = row.data.ParentID;
                }
            }
            if (draggedRow.selected) {
                this.treeGrid.selectRows([this.treeGrid.rowList.toArray()
                    .find((r) => r.rowID === draggedrow.key).rowID], false);
            }
    
            this.localData = [...this.localData];
        }
    
        private performDrop(
            draggedRow: IgxTreeGridRowComponent, targetRow: IgxTreeGridRowComponent) {
            const draggedRowIndex = this.getRowIndex(draggedrow.data);
            const targetRowIndex: number = this.getRowIndex(targetrow.data);
            if (draggedRowIndex === -1 || targetRowIndex === -1) { return; }
            this.localData.splice(draggedRowIndex, 1);
            this.localData.splice(targetRowIndex, 0, draggedrow.data);
            return this.localData[targetRowIndex];
        }
    
        private getRowIndex(rowData: any): number {
            return this.localData.indexOf(rowData);
        }
    
        private catchCursorPosOnElem(rowListArr: IgxTreeGridRowComponent[], cursorPosition: Point)
            : IgxTreeGridRowComponent {
            for (const row of rowListArr) {
                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 row;
                }
            }
    
            return null;
        }
    }
    

    これらの簡単な手順で、ドラッグ/ドロップで行を並べ替えることができるグリッドを構成しました! 次のデモで、上記コードの動作を確認できます。

    行の選択も有効で、ドラッグした行をドロップしても選択が保持されます。

    制限

    現在、rowDraggable ディレクティブに既知の制限はありません。

    API リファレンス

    その他のリソース

    コミュニティに参加して新しいアイデアをご提案ください。