Arrastar linha em Angular grade
Em Ignite UI for Angular Grid, o RowDrag é inicializado no componente raizigx-grid e pode ser configurado viarowDraggable entrada. Ativar o arrastar de linha oferece aos usuários uma alça de arrastar de linha com a qual podem iniciar o arrastar de uma linha.
Angular Grid Row Drag Example
Configuração
Para ativar o arrastar de linhas para você,igx-grid tudo o que você precisa fazer é definir as gradesrowDraggable.true Uma vez ativada, uma alça de arrastar de linha será exibida em cada linha. Essa alça pode ser usada para iniciar arrastar de fileira.
<igx-grid [rowDraggable]="true">
...
</igx-grid>
Clicar na alavanca de arrastar e mover o cursor enquanto o botão é pressionado fará com que o evento darowDragStart grade dispare. Soltar o clique a qualquer momento farárowDragEnd com que o evento dispare.
Abaixo, você pode encontrar um guia sobre como configurar eigx-grid suportar arrastar linhas e como lidar corretamente com o evento de drop.
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
Ativar o arrastar de linhas foi bem fácil, mas agora temos que configurar como vamos lidar com o dropping de linhas. Podemos definir onde queremos que nossas linhas sejam descartadas usando aigxDrop diretiva.
Primeiro, precisamos importar oIgxDragDropModule módulo do nosso app:
import { ..., IgxDragDropModule } from 'igniteui-angular/directives';
// 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 é largada em uma área não droppable usando oanimation parâmetro dorowDragEnd evento. Se configurada como true, a fileira arrastada será animada de volta à sua posição original quando for deixada cair sobre uma área não droppable.
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 drop no template, precisamos declarar nossos handlers para osigxDrop eventos the'senterleavedropped e eventos no arquivo do nosso componente..ts
Primeiro, vamos dar uma olhada em nossosenter eleave nos manipuladores. Nesses métodos, só queremos mudar o ícone do fantasma do arrasto para indicar ao usuário que ele está acima de uma área que permite que ele deixe a linha cair:
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;
}
}
}
}
OchangeGhostIcon método privado só muda o ícone dentro do fantasma de arrasto. A lógica do método encontra o elemento que contém o ícone (usando aigx-grid__drag-indicator classe aplicada ao contêiner do indicador de arrastar), mudando o texto interno do elemento para o passado. Os ícones em si vêm domaterial conjunto de fontes e são definidos de forma separadaenum:
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 para cada uma de nossas grades via decoradorViewChild e lidamos com a queda da seguinte forma:
- Adicione uma linha a
targetGridque contém os dados da linha que está sendo descartada - remova a fileira arrastada do
sourceGrid
Note
Ao usar dados de linha dos argumentos do evento (args.dragData.data) ou qualquer outra propriedade de linha, note que toda a linha é 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 arrasto pode ser modelado usando aIgxRowDragGhost diretiva, aplicada ao<ng-template> interior doigx-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 visto abaixo, comigx-grid arrastar de linha e múltiplas seleções ativadas. A demonstração mostra a contagem das linhas atualmente arrastadas:
Exemplo de demonstração
Templating the drag icon
O ícone da alça de arrastar pode ser modelado usando a gradedragIndicatorIconTemplate. No exemplo que estamos construindo, vamos mudar o ícone do padrão (drag_indicator) paradrag_handle. Para isso, podemos usar oigxDragIndicatorIcon para passar um template dentro do corpo do corpoigx-grid:
<igx-grid>
...
<ng-template igxDragIndicatorIcon>
<igx-icon>drag_handle</igx-icon>
</ng-template>
...
</igx-grid>
Depois de definirmos o novo modelo de ícone, também precisamos ajustar oDEFAULT ícone no nossoDragIcon enum, para que ele seja corretamente alterado pelochangeIcon 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 de linha da grade e daigxDrop diretiva, você pode criar uma grade que permite reordenar as linhas arrastando-as.
Como todas as ações ocorrerão dentro do corpo da grade, é aí que você deve anexar aigxDrop diretiva:
<igx-grid #grid [data]="data" [rowDraggable]="true" [primaryKey]="'ID'" igxDrop (dropped)="onDropAllowed($event)">
...
</igx-grid>
Note
Certifique-se de que haja umaprimaryKey especificação para a grade! A lógica precisa de um identificador único para as linhas para que possam ser reordenadas corretamente
Depois querowDraggable está ativado e uma zona de queda está definida, você precisa implementar um manipulador simples para o evento de drop. 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 registros no
dataarray
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 drag. AdropName propriedade mudará dinamicamente, obtendo o nome da linha sobre a qual o cursor está pairando:
<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 lidar com oIgxDragDirective.dragMove evento (emitido para a linha arrastada). O método altera o valor da propriedade usada noigxRowDragGhost template e força uma rerenderização. Queremos assinar apenas odragMove evento da linha específica que estamos arrastando e cancelar a inscrição dela (para evitar vazamentos de memória) toda vez 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, atribuímos aodragMove evento de quandoIgxDragDirective começamos a arrastar uma linha.
Note
Certifique-se de que haja umaprimaryKey especificação para a grade! A lógica precisa de um identificador único 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 viewport e para rolar a página, se necessário. AceitaisGridScrolledToEdge um parâmetro – a direção em que queremos deslocar a grade (1 para "Down", -1 para "Up") e retornatrue se já tivermos alcançado a última linha nessa direção. OscrollGrid 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 odragMove evento da linha específica da mesma forma que fizemos no exemplo anterior. ComodragMove é disparado apenas quando o cursor realmente se move, queremos ter uma forma simples 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 ter um método adicional que configura umainterval rolagem automática da grade a cada500ms vez.
Criamos e nos inscrevemosinterval quando o ponteiro chega à borda da grade e aunsubscribe partir dissointerval toda vez que o mouse se move ou a linha é descartada (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 arowDraggable diretiva.