Arrastar linha em Angular grade
Em Ignite UI for Angular Grid, RowDrag é inicializado no componente raiz igx-grid
e é configurável por meio da rowDraggable
entrada. Habilitar o arrastar de linha fornece aos usuários uma alça de arrastar de linha com a qual eles podem iniciar o arrasto de uma linha.
Angular Grid Row Drag Example
Configuração
Para habilitar o arrasto de linha para você igx-grid
, tudo o que você precisa fazer é definir a grade rowDraggable
para true
. Depois que isso estiver ativado, uma alça de arrastar linha será exibida em cada linha. Essa alça pode ser usada para iniciar o arrasto de linha.
<igx-grid [rowDraggable]="true">
...
</igx-grid>
Clicar na alça de arrastar e mover o cursor enquanto mantém pressionado o botão fará com que o evento da rowDragStart
grade seja acionado. Liberar o clique a qualquer momento fará com que rowDragEnd
o evento seja acionado.
Abaixo, você pode encontrar um passo a passo sobre como configurar um igx-grid
para dar suporte ao arrasto de linha e como lidar corretamente com o evento de soltar.
Neste exemplo, vamos lidar com o arrasto de uma linha de uma grade para outra, removendo-a da primeira fonte de dados e adicionando-a à segunda.
Drop Areas
Habilitar o arrasto de linha foi muito fácil, mas agora temos que configurar como lidaremos com a queda de linha. Podemos definir onde queremos que nossas linhas sejam descartadas usando a igxDrop
diretiva.
Primeiro, precisamos importar o em nosso módulo de IgxDragDropModule
aplicativo:
import { ..., IgxDragDropModule } from 'igniteui-angular';
// import { ..., IgxDragDropModule } from '@infragistics/igniteui-angular'; for licensed package
...
@NgModule({
imports: [..., IgxDragDropModule]
})
Em seguida, em nosso modelo, definimos uma área de soltar usando o seletor da diretiva:
Nesse caso, nossa área de queda será uma segunda grade inteira onde soltaremos as linhas.
<igx-grid #targetGrid igxDrop [data]="data2" [autoGenerate]="false" [emptyGridTemplate]="dragHereTemplate"
(enter)="onEnterAllowed($event)" (leave)="onLeaveAllowed($event)" (dropped)="onDropAllowed($event)" [primaryKey]="'ID'">
...
</igx-grid>
Como a grade estará inicialmente vazia, também definimos um modelo que será mais significativo para o usuário:
<ng-template #dragHereTemplate>
Drop a row to add it to the grid
</ng-template>
Você pode ativar a animação quando uma linha é solta em uma área não soltável usando o animation
rowDragEnd
parâmetro do evento. Se definido como true, a linha arrastada será animada de volta à sua posição original quando solta sobre uma área que não pode ser solta.
Você pode habilitar a animação assim:
export class IgxGridRowDragComponent {
public onRowDragEnd(args) {
args.animation = true;
}
}
Drop Area Event Handlers
Depois de definirmos nossa área de soltar no modelo, temos que declarar nossos manipuladores para os igxDrop
enter
eventos leave
dropped
e eventos no arquivo do.ts
nosso componente.
Primeiro, vamos dar uma olhada em nossos enter
manipuladores leave
. Nesses métodos, queremos apenas alterar o ícone do fantasma do drag para que possamos indicar ao usuário que ele está acima de uma área que permite soltar a linha:
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;
}
}
}
}
O changeGhostIcon
método privado apenas altera o ícone dentro do fantasma de arrastar. A lógica no método localiza o elemento que contém o ícone (usando a igx-grid__drag-indicator
classe aplicada ao contêiner indicador de arrastar), alterando o texto interno do elemento para o passado. Os ícones em si são do material
fontesconjunto de e são definidos em separado enum
:
enum DragIcon {
DEFAULT = 'drag_indicator',
ALLOW = 'add'
}
Em seguida, temos que definir o que deve acontecer quando o usuário realmente solta a linha dentro da área de soltar.
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 uma referência a cada uma de nossas grades por meio do ViewChild
decorador e lidamos com a queda da seguinte maneira:
- Adicione uma linha ao
targetGrid
que contém os dados da linha que está sendo descartada - remova a linha arrastada do
sourceGrid
Note
Ao usar dados de linha dos argumentos do evento (args.dragData.data
) ou qualquer outra propriedade de linha, observe que a linha inteira é passada nos argumentos como referência, o que significa que você deve clonar os dados necessários, se quiser distingui-los dos da grade de origem.
Templating the drag ghost
O fantasma de arrastar pode ser modelado usando a IgxRowDragGhost
diretiva, aplicada a um <ng-template>
interior do igx-grid
corpo:
<igx-grid>
...
<ng-template igxRowDragGhost>
<div>
<igx-icon fontSet="material">arrow_right_alt</igx-icon>
</div>
</ng-template>
...
</igx-grid>
O resultado da configuração pode ser exibido abaixo em um igx-grid
com arrastar linhas e seleção múltipla habilitada. A demonstração mostra a contagem das linhas arrastadas no momento:
Exemplo de demonstração
Templating the drag icon
O ícone da alça de arrastar pode ser modelado usando o da grade dragIndicatorIconTemplate
. No exemplo que estamos construindo, vamos alterar o ícone do padrão (drag_indicator
) para drag_handle
. Para fazer isso, podemos usar o igxDragIndicatorIcon
para passar um modelo dentro do igx-grid
corpo:
<igx-grid>
...
<ng-template igxDragIndicatorIcon>
<igx-icon>drag_handle</igx-icon>
</ng-template>
...
</igx-grid>
Depois de definir o novo modelo de ícone, também precisamos ajustar o DEFAULT
ícone em nosso DragIcon enum
, para que ele seja alterado corretamente pelo changeIcon
método:
enum DragIcon {
DEFAULT = "drag_handle",
...
}
Assim que nossos gerenciadores de drop estiverem configurados corretamente, estamos prontos para começar! O resultado da configuração pode ser visto abaixo:
Exemplo de demonstração
Application Demo
Using Row Drag Events
A demonstração a seguir demonstra como usar informações de evento de arrastar linha para alterar os dois estados de um componente personalizado, onde a linha é solta, e a própria grade de origem. Tente arrastar luas da grade e soltá-las em seus planetas correspondentes. O plano de fundo fantasma de arrasto de linha é alterado dinamicamente, dependendo do planeta pairado. Se você tiver sucesso, a linha na grade será selecionada e o arrastar será desativado para ela. Clicar em planetas fornecerá informações úteis.
Note
As classes aplicadas ao fantasma de arrastar linha, usado na demonstração acima, estão usando o modificador ::ng-deep, porque o arrasto de linha é um recurso de grade interna e não pode ser acessado no nível do aplicativo, devido ao encapsulamento CSS.
Row Reordering Demo
Com a ajuda dos eventos de arrastar linha da grade e da igxDrop
diretiva, você pode criar uma grade que permite reordenar as linhas arrastando-as.
Como todas as ações acontecerão dentro do corpo da grade, é aí que você deve anexar a igxDrop
diretiva:
<igx-grid #grid [data]="data" [rowDraggable]="true" [primaryKey]="'ID'" igxDrop (dropped)="onDropAllowed($event)">
...
</igx-grid>
Note
Certifique-se de que haja um primaryKey
especificado para a grade! A lógica precisa de um identificador exclusivo para as linhas para que possam ser reordenadas corretamente
Uma vez rowDraggable
habilitado e uma zona de queda definida, você precisa implementar um manipulador simples para o evento de queda. Quando uma linha é arrastada, verifique o seguinte:
- A linha foi lançada dentro da grade?
- Em caso afirmativo, em qual outra linha a linha arrastada foi solta?
- Depois de encontrar a linha de destino, troque os lugares dos registros na
data
matriz
Abaixo, você pode ver isso implementado no arquivo do.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;
}
}
Com essas etapas fáceis, você configurou uma grade que permite reordenar linhas por meio de arrastar/soltar! Você pode ver o código acima em ação na demonstração a seguir.
Segurar o ícone de arrastar permitirá que você mova uma linha para qualquer lugar na grade:
Improving UX in row drag scenarios
Ser capaz de obter o índice de linha que está atualmente abaixo do cursor oferece a oportunidade de criar funcionalidades personalizadas avançadas e melhorar a experiência do usuário do seu aplicativo. Por exemplo, você pode alterar o fantasma de arrastar ou exibir um indicador de soltar, com base na posição da linha arrastada sobre a grade. Outro comportamento útil que você pode obter dessa maneira é rolar a grade para cima ou para baixo enquanto arrasta uma linha, ao atingir a borda da grade.
Abaixo, você pode encontrar trechos de exemplo de algumas implementações personalizadas que você pode obter conhecendo a posição da linha.
Alterando o fantasma de arrastar com base na posição do cursor
Nos trechos abaixo, você vê como alterar o texto dentro do fantasma de arrastar para exibir o nome da linha pairada.
Primeiro, você especifica um modelo que gostaria de usar para o fantasma de arrastar. A dropName
propriedade mudará dinamicamente, obtendo o nome da linha sobre a qual o cursor está passando:
<ng-template igxRowDragGhost>
<div class="customGhost">
<div>{{ dropName }}</div>
</div>
</ng-template>
Em seguida, defina um método que retorne a instância da linha que você superou (semelhante ao usado na demonstração de reordenação de linha):
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;
}
}
Por fim, criamos um método que será usado para manipular o IgxDragDirective.dragMove
evento (emitido para a linha arrastada). O método alterará o valor da propriedade usada no igxRowDragGhost
modelo e forçará uma nova renderização. Queremos assinar o evento apenas da linha específica que estamos arrastando e cancelar a dragMove
assinatura dela (para evitar vazamentos de memória) sempre que uma linha for descartada.
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();
}
}
}
}
Exibindo um indicador de queda com base na posição do cursor
Na demonstração da próxima seção, você verá como exibir um indicador de onde a linha arrastada seria solta. Você pode personalizar este indicador como quiser - pode ser uma linha de espaço reservado, colocada na posição em que a linha arrastada seria solta, um estilo de borda indicando se a linha arrastada seria solta acima ou abaixo da linha atualmente pairada, etc.
Para rastrear a posição do cursor, nos ligamos ao dragMove
evento do IgxDragDirective
quando começamos a arrastar uma linha.
Note
Certifique-se de que haja um primaryKey
especificado para a grade! A lógica precisa de um identificador exclusivo para as linhas para que possam ser reordenadas corretamente
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;
}
}
}
Rolando a grade ao arrastar linha
Um cenário muito útil é poder rolar a grade quando a linha arrastada atinge sua borda superior ou inferior. Isso permite reordenar linhas fora da janela de visualização atual quando o número de linhas na grade requer uma barra de rolagem.
Abaixo você vê um exemplo dos dois métodos que usamos para verificar se chegamos à borda da janela de visualização e rolá-la, se necessário. O isGridScrolledToEdge
aceita um parâmetro - a direção em que gostaríamos de rolar a grade (1 para "Para baixo", -1 para "Para cima") e retorna true
se chegarmos à linha final nessa direção. O scrollGrid
método tentará rolar a grade em uma direção (1 ou -1), sem fazer nada se a grade já estiver nessa borda.
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();
}
}
}
}
Ainda estaremos assinando o dragMove
evento da linha específica da maneira que fizemos no exemplo anterior. Como dragMove
o é disparado apenas quando o cursor realmente se move, queremos ter uma maneira agradável e simples de rolar automaticamente a grade quando a linha está em uma das bordas, mas o usuário não move o mouse. Vamos usar um método adicional que configurará uma interval
rolagem automática da grade a 500ms
cada.
Criamos e assinamos o interval
quando o ponteiro atinge a borda da grade e a unsubscribe
partir disso toda vez que interval
o mouse se move ou a linha é solta (independentemente da posição do 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 seguir está o exemplo de ambos os cenários descritos acima - mostrando um indicador de queda e rolando a janela de visualização quando a borda da borda é atingida.
Limitations
Atualmente, não há limitações conhecidas para a rowDraggable
diretiva.