Arrastre de filas en Angular

    En Ignite UI for Angular Grid, RowDrag se inicializa en el componente raíz igx-grid y se puede configurar mediante la entrada rowDraggable. Habilitar el arrastre de filas proporciona a los usuarios un controlador de arrastre de filas con el que pueden iniciar el arrastre de una fila.

    Angular Grid Row Drag Example

    Configuración

    Para habilitar el arrastre de filas para su igx-grid, todo lo que necesita hacer es configurar el rowDraggable de la cuadrícula en true. Una vez que esto esté habilitado, se mostrará un controlador de arrastre de fila en cada fila. Este controlador se puede utilizar para iniciar el arrastre de filas.

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

    Al hacer clic en el controlador de arrastre y mover el cursor mientras se mantiene presionado el botón, se activará el evento rowDragStart de la cuadrícula. Si suelta el clic en cualquier momento, se activará el evento rowDragEnd.

    A continuación, puede encontrar un tutorial sobre cómo configurar un igx-grid para admitir el arrastre de filas y cómo manejar adecuadamente el evento de colocación.

    En este ejemplo, nos encargaremos de arrastrar una fila de una cuadrícula a otra, eliminarla de la primera fuente de datos y agregarla a la segunda.

    Drop Areas

    Habilitar el arrastre de filas fue bastante fácil, pero ahora tenemos que configurar cómo manejaremos el descenso de filas. Podemos definir dónde queremos que se eliminen nuestras filas usando la igxDropdirectiva .

    Primero necesitamos importar IgxDragDropModule en nuestro módulo de aplicación:

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

    Luego, en nuestra plantilla, definimos un área de colocación usando el selector de directivas:

    En este caso, nuestra área de colocación será una segunda cuadrícula completa donde colocaremos las filas.

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

    Dado que la cuadrícula inicialmente estará vacía, también definimos una plantilla que será más significativa para el usuario:

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

    Puede habilitar la animación cuando una fila se coloca en un área no desplegable utilizando el parámetro animation del evento rowDragEnd. Si se establece en verdadero, la fila arrastrada volverá a su posición original cuando se suelte sobre un área que no se puede soltar.

    Puede habilitar animaciones como esta:

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

    Drop Area Event Handlers

    Una vez que hemos definido nuestra área de colocación en la plantilla, tenemos que declarar nuestros controladores para el igxDrop 's enter, leave y dropped eventos en nuestro componente.ts archivo.

    Primero, echemos un vistazo a nuestros controladores enter y leave. En esos métodos, solo queremos cambiar el ícono del fantasma del arrastre para poder indicarle al usuario que está encima de un área que le permite soltar la fila:

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

    El changeGhostIcon privado El método simplemente cambia el ícono dentro del fantasma de arrastre. La lógica del método encuentra el elemento que contiene el icono (usando el igx-grid__drag-indicator clase que se aplica al contenedor del indicador de arrastre), cambiando el texto interno del elemento al pasado. Los iconos en sí son del material conjunto de fuentes y se definen en un separado enum:

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

    A continuación, tenemos que definir qué debería suceder cuando el usuario realmente coloca la fila dentro del área de colocación.

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

    Definimos una referencia a cada una de nuestras cuadrículas a través del decorador ViewChild y manejamos la caída de la siguiente manera:

    • agregue una fila al targetGrid que contenga los datos de la fila que se elimina
    • eliminar la fila arrastrada de sourceGrid
    Note

    Cuando utilice datos de fila de los argumentos del evento (args.dragData.data) o cualquier otra propiedad de fila, tenga en cuenta que la fila completa se pasa en los argumentos como referencia, lo que significa que debe clonar los datos que necesita, si desea distinguirlo del de la cuadrícula de origen.

    Templating the drag ghost

    Se puede crear una plantilla para el fantasma de arrastre usando la directiva IgxRowDragGhost, aplicada a un <ng-template> dentro del cuerpo de igx-grid:

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

    El resultado de la configuración se puede ver a continuación en una igx-grid con el arrastre de filas y la selección múltiple habilitada. La demostración muestra el recuento de las filas arrastradas actualmente:

    Demostración de ejemplo

    Templating the drag icon

    El icono del controlador de arrastre se puede crear como plantilla usando dragIndicatorIconTemplate de la cuadrícula. En el ejemplo que estamos creando, cambiemos el ícono predeterminado (drag_indicator) a drag_handle. Para hacerlo, podemos usar igxDragIndicatorIcon para pasar una plantilla dentro del cuerpo de igx-grid:

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

    Una vez que hayamos configurado la nueva plantilla de ícono, también necesitamos ajustar el ícono DEFAULT en nuestra DragIcon enum, para que se cambie correctamente mediante el método changeIcon:

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

    Una vez que nuestros controladores de entrega estén configurados correctamente, ¡estamos listos para comenzar! El resultado de la configuración se puede ver a continuación:

    Demostración de ejemplo

    Application Demo

    Using Row Drag Events

    La siguiente demostración demuestra cómo utilizar la información del evento de arrastre de filas para cambiar ambos estados de un componente personalizado, donde se suelta la fila, y la propia cuadrícula de origen. Intenta arrastrar lunas desde la cuadrícula y suéltalas en sus planetas correspondientes. El fondo del fantasma de arrastre de fila cambia dinámicamente, dependiendo del planeta sobre el que se encuentre. Si tiene éxito, se seleccionará la fila de la cuadrícula y se desactivará el arrastre. Al hacer clic en los planetas obtendrás información útil.

    Note

    Las clases aplicadas al fantasma de arrastre de filas, utilizadas en la demostración anterior, utilizan el modificador ::ng-deep, porque el arrastre de filas es una característica de la cuadrícula interna y no se puede acceder a ella en el nivel de la aplicación, debido a la encapsulación de CSS.

    Row Reordering Demo

    Con la ayuda de los eventos de arrastre de filas de la cuadrícula y la directiva igxDrop, puede crear una cuadrícula que le permita reordenar las filas arrastrándolas.

    Dado que todas las acciones sucederán dentro del cuerpo del grid, ahí es donde debes adjuntar la directiva igxDrop:

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

    ¡Asegúrese de que haya una primaryKey especificada para la cuadrícula! La lógica necesita un identificador único para las filas para que puedan reordenarse correctamente.

    Una vez que rowDraggable esté habilitado y se haya definido una zona de colocación, debe implementar un controlador simple para el evento de colocación. Cuando se arrastra una fila, verifique lo siguiente:

    • ¿Se dejó caer la fila dentro de la cuadrícula?
    • Si es así, ¿en qué otra fila se soltó la fila arrastrada?
    • Una vez que haya encontrado la fila de destino, intercambie las ubicaciones de los registros en la matriz data.

    A continuación, puede ver esto implementado en el archivo.ts del componente:

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

    ¡Con estos sencillos pasos, ha configurado una cuadrícula que permite reordenar filas mediante arrastrar y soltar! Puede ver el código anterior en acción en la siguiente demostración.

    Mantener presionado el ícono de arrastrar le permitirá mover una fila a cualquier lugar de la cuadrícula:

    Improving UX in row drag scenarios

    Ser capaz de obtener el índice de fila que se encuentra actualmente debajo del cursor le brinda la oportunidad de crear ricas funcionalidades personalizadas y mejorar la UX de su aplicación. Por ejemplo, puede cambiar el fantasma de arrastre o mostrar un indicador de colocación, según la posición de la fila arrastrada sobre la cuadrícula. Otro comportamiento útil que puedes lograr de esa manera es desplazar la cuadrícula hacia arriba o hacia abajo mientras arrastras una fila, al llegar al borde de la cuadrícula.

    A continuación puede encontrar fragmentos de ejemplo de un par de implementaciones personalizadas que puede lograr conociendo la posición de la fila.

    Cambiar el fantasma de arrastre según la posición del cursor

    En los fragmentos a continuación, verá cómo puede cambiar el texto dentro del fantasma de arrastre para mostrar el nombre de la fila sobre la que se encuentra.

    Primero, especifica una plantilla que le gustaría usar para el fantasma de arrastre. La propiedad dropName cambiará dinámicamente, obteniendo el nombre de la fila sobre la que se encuentra el cursor:

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

    Luego, define un método que devuelva la instancia de la fila que has terminado (similar al usado en la demostración de reordenamiento de filas):

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

    Finalmente, creamos un método que se utilizará para manejar el evento IgxDragDirective.dragMove (emitido para la fila arrastrada). El método cambiará el valor de la propiedad utilizada en la plantilla igxRowDragGhost y forzará una nueva representación. Queremos suscribirnos al evento dragMove solo de la fila específica que estamos arrastrando y cancelar la suscripción (para evitar pérdidas de memoria) cada vez que se suelta una fila.

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

    Mostrar un indicador de caída basado en la posición del cursor

    En la demostración de la siguiente sección verá cómo puede mostrar un indicador de dónde se soltaría la fila arrastrada. Puede personalizar este indicador como desee: puede ser una fila de marcador de posición, colocada en la posición donde se soltaría la fila arrastrada, un estilo de borde que indica si la fila arrastrada se soltaría encima o debajo de la fila actualmente suspendida, etc.

    Para rastrear la posición del cursor, nos vinculamos al evento dragMove de IgxDragDirective cuando comenzamos a arrastrar una fila.

    Note

    ¡Asegúrese de que haya una primaryKey especificada para la cuadrícula! La lógica necesita un identificador único para las filas para que puedan reordenarse correctamente.

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

    Desplazarse por la cuadrícula al arrastrar filas

    Un escenario muy útil es poder desplazar la cuadrícula cuando la fila arrastrada alcanza su borde superior o inferior. Esto permite reordenar filas fuera de la ventana gráfica actual cuando el número de filas en la cuadrícula requiere una barra de desplazamiento.

    A continuación verá un ejemplo de los dos métodos que utilizamos para comprobar si hemos llegado al borde de la ventana gráfica y desplazarnos si es necesario. isGridScrolledToEdge acepta un parámetro: la dirección en la que nos gustaría desplazar la cuadrícula (1 para "Abajo", -1 para "Arriba") y devuelve true si llegamos a la última fila en esa dirección. El método scrollGrid intentará desplazar la cuadrícula en una dirección (1 o -1), sin hacer nada si la cuadrícula ya está en ese borde.

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

    Seguiremos suscribiéndonos al evento dragMove de la fila específica como lo hicimos en el ejemplo anterior. Dado que dragMove se activa solo cuando el cursor realmente se mueve, queremos tener una manera agradable y sencilla de desplazar automáticamente la cuadrícula cuando la fila está en uno de los bordes, pero el usuario no mueve el mouse. Usaremos un método adicional que configurará un interval, desplazando automáticamente la cuadrícula cada 500ms.

    Creamos y nos suscribimos al interval cuando el puntero alcanza el borde de la cuadrícula y nos unsubscribe de ese interval cada vez que se mueve el mouse o se suelta la fila (independientemente de la posición del cursor).

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

    A continuación se muestra el ejemplo de ambos escenarios descritos anteriormente: se muestra un indicador de caída y se desplaza la ventana gráfica cuando se alcanza el borde del borde.

    Limitations

    Actualmente, no existen limitaciones conocidas para la directiva rowDraggable.

    API References

    Additional Resources

    Nuestra comunidad es activa y siempre da la bienvenida a nuevas ideas.