Arrastre de filas en Angular cuadrícula
En Ignite UI for Angular Grid, RowDrag se inicializa en el componente raízigx-grid y es configurable mediante larowDraggable entrada. Habilitar el arrastre de fila proporciona a los usuarios un asa de arrastre de fila con el que pueden iniciar el arrastre de una fila.
Angular Grid Row Drag Example
Configuración
Para activar el arrastre de filas para tiigx-grid, solo tienes que configurar las cuadrículasrowDraggable entrue. Una vez habilitada esta opción, se mostrará una fila para arrastrar una fila. Esta manilla puede usarse para iniciar el arrastre de hilera.
<igx-grid [rowDraggable]="true">
...
</igx-grid>
Hacer clic en el mando de arrastrar y mover el cursor mientras mantienes pulsado el botón hará que el evento derowDragStart la cuadrícula se active. Soltar el clic en cualquier momento harárowDragEnd que el evento se active.
A continuación, puedes encontrar una guía sobre cómo configurar unigx-grid para soportar el arrastre de filas y cómo manejar correctamente el evento de drop.
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
Activar el arrastre de filas fue bastante fácil, pero ahora tenemos que configurar cómo manejaremos el deslizamiento de filas. Podemos definir dónde queremos que se eliminen nuestras filas usando laigxDrop directiva.
Primero necesitamos importar elIgxDragDropModule módulo de nuestra app:
import { ..., IgxDragDropModule } from 'igniteui-angular/directives';
// 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>
Puedes activar la animación cuando una fila se suelta en un área no desplegable usando elanimation parámetro delrowDragEnd evento. Si se pone en verdadero, la fila arrastrada se animará de nuevo a su posición original cuando se suelta sobre un área no soltable.
Puede habilitar animaciones como esta:
export class IgxGridRowDragComponent {
public onRowDragEnd(args) {
args.animation = true;
}
}
Drop Area Event Handlers
Una vez definido nuestro área de caída en la plantilla, tenemos que declarar nuestros handlers para losigxDropenterleavedropped eventos y los eventos en el.ts archivo de nuestro componente.
Primero, echemos un vistazo a nuestrosenter yleave a los manejadores. En esos métodos, solo queremos cambiar el icono del fantasma del arrastre para poder indicar al usuario que está por 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;
}
}
}
}
ElchangeGhostIcon método privado simplemente cambia el icono dentro del fantasma de arrastre. La lógica del método encuentra el elemento que contiene el icono (usando laigx-grid__drag-indicator clase que se aplica al contenedor indicador de arrastre), cambiando el texto interno del elemento al que pasó. Los iconos en sí provienen delmaterial conjunto de fuentes y están definidos de forma separadaenum:
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 delViewChild decorador y manejamos la caída de la siguiente manera:
- Añadir una fila a
targetGridque contenga los datos de la fila que se está eliminando - Quita la fila arrastrada de la
sourceGrid
Note
Al usar los datos de fila de los argumentos de evento (args.dragData.data) o cualquier otra propiedad de fila, ten en cuenta que toda la fila se pasa en los argumentos como referencia, lo que significa que debes clonar los datos que necesitas si quieres distinguirlos de los de la cuadrícula fuente.
Templating the drag ghost
El fantasma de arrastre puede plantarse usando laIgxRowDragGhost directiva, aplicada al<ng-template> interior deligx-grid cuerpo:
<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 conigx-grid el arrastre de filas y la selección múltiple activada. La demo muestra el conteo de las filas que se arrastran actualmente:
Demostración de ejemplo
Templating the drag icon
El icono del mango de arrastrar puede plantarse usando las dedragIndicatorIconTemplate la cuadrícula. En el ejemplo que estamos construyendo, cambiemos el icono del predeterminado (drag_indicator) adrag_handle. Para ello, podemos usar eligxDragIndicatorIcon para pasar una plantilla dentro del cuerpo de eligx-grid:
<igx-grid>
...
<ng-template igxDragIndicatorIcon>
<igx-icon>drag_handle</igx-icon>
</ng-template>
...
</igx-grid>
Una vez que hemos configurado la nueva plantilla de iconos, también tenemos que ajustar elDEFAULT icono en nuestroDragIcon enum para que se cambie correctamente por elchangeIcon método:
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 fila de la cuadrícula y laigxDrop directiva, puedes crear una cuadrícula que te permita reordenar filas arrastrándolas.
Como todas las acciones ocurrirán dentro del cuerpo de la red, ahí es donde tienes que adjuntar laigxDrop directiva:
<igx-grid #grid [data]="data" [rowDraggable]="true" [primaryKey]="'ID'" igxDrop (dropped)="onDropAllowed($event)">
...
</igx-grid>
Note
¡Asegúrate de que haya unaprimaryKey especificación para la cuadrícula! La lógica necesita un identificador único para las filas para que puedan reordenarse correctamente
Una vezrowDraggable activado y definida una zona de caída, necesitas implementar un manejador sencillo para el evento de caída. Cuando se arrastra una fila, comprueba 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 hayas encontrado la fila objetivo, intercambia los registros en el
dataarray
A continuación, puedes ver esto implementado en el archivo del.ts 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, especificas una plantilla que te gustaría usar para el fantasma drag. LadropName propiedad cambiará dinámicamente, obteniendo el nombre de la fila sobre la que el cursor está flotando:
<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 usará para manejar elIgxDragDirective.dragMove evento (emitido para la fila arrastrada). El método cambiará el valor de la propiedad usada en laigxRowDragGhost plantilla y forzará un rerenderizado. Queremos suscribirnos solo aldragMove evento de la fila específica que arrastramos y darse de baja de él (para evitar fugas 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 seguir la posición del cursor, asignamos aldragMove evento de cuandoIgxDragDirective empezamos a arrastrar una fila.
Note
¡Asegúrate de que haya unaprimaryKey especificación 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 puedes ver un ejemplo de los dos métodos que usamos para comprobar si hemos llegado al borde del visor y para desplazarlo si es necesario. AceptaisGridScrolledToEdge un parámetro: la dirección en la que queremos desplazarnos por la cuadrícula (1 para "Abajo", -1 para "Arriba") y devuelvetrue si hemos llegado a la última fila en esa dirección. ElscrollGrid método 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 aldragMove evento de la fila específica como hicimos en el ejemplo anterior. ComodragMove solo se dispara cuando el cursor se mueve realmente, queremos tener una forma sencilla y agradable de desplazar automáticamente la cuadrícula cuando la fila está en uno de los bordes, pero el usuario no mueve el ratón. Tenemos un método adicional que configurará uninterval desplazamiento automático de la cuadrícula cada500ms día.
Creamos y suscribimos cuandointerval el puntero llega al borde de la cuadrícula y desdeunsubscribe ahíinterval cada vez que el ratón se mueve 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 larowDraggable directiva.