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 atargetGrid que contém os dados da linha que está sendo descartada
    • remova a fileira arrastada dosourceGrid
    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 nodata array

    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.

    API References

    Additional Resources

    Nossa comunidade é ativa e sempre acolhedora para novas ideias.