# Drag and Drop com múltiplas seleções

### O que iremos fazer?

Dessa vez vamos melhorar um pouco o código feito no artigo anterior, a ideia agora é permitir que o usuário possa selecionar mais de um item e arrastá-los para a outra lista, mas vamos aproveitar para organizar nosso código e criar objetos que vão nos auxiliar nessa tarefa.

Talvez vocês estejam se perguntando, mas por que fazer isso? A resposta é simples, a tecnologia utilizada aqui não possui esta funcionalidade (pelo menos não até a versão que estamos estudando) e também uma hora ou outra você vai precisar arrastar múltiplos.&#x20;

![Drag and Drop multi select](/files/-MC2hksovpx3vykz7Enq)

### O que precisamos saber?

* Precisamos estudar e entender bem o artigo anterior [Drag and Drop com Angular Material e DropList dinâmica](/blog/angular/drag-and-drop-com-angular-material-e-droplist-dinamica.md)&#x20;
* Devemos clonar o projeto de exemplo [mat-drag-and-drop-example](https://github.com/arthur-lima-dev/mat-drag-and-drop-example/tree/multi-select-drag-drop), só que dessa vez vamos utilizar o branch **multi-select-drag-drop**. Temos que ficar atentos a esse detalhe!

{% hint style="warning" %}
Leiam os comentários, todos eles sem exceção para a compreensão completa acerca deste artigo.
{% endhint %}

### Chega de enrolação, queremos aprender logo!

No arquivo **drag-and-drop.component.css** será necessário criar uma classe que muda a cor de fundo e a cor de texto do item selecionado, dessa forma iremos destacá-lo dos demais.

{% code title="drag-and-drop.component.css" %}

```css
.selected{
  color: white;
  background-color: #3caf9d!important;
}
```

{% endcode %}

No arquivo **drag-and-drop.component.html** inserimos alguns comportamentos a div **cdkDrag**.

{% code title="drag-and-drop.component.html" %}

```markup
<div class="margin-custom">
  <div class="example-container" *ngFor="let zoologico of zoologicos; let i = index">
    <h2>{{'Lista de soltar ' + (i + 1)}}</h2>
    <div class="example-list" id="{{zoologico.nome}}"
         [cdkDropListConnectedTo]="nomesDropList"
         [cdkDropListData]="zoologico.animais"
         (cdkDropListDropped)="dropItems($event)" cdkDropList>

      <div class="example-box" *ngFor="let animal of zoologico.animais"
           [ngClass]="{'selected' : animal.selecionado}"
           (cdkDragStarted)="prepararArrasto(animal)"
           (click)="selecionarAnimal($event, animal)"
           cdkDrag>
        <span>{{animal.especie}}</span>
      </div>
    </div>
  </div>
</div>
```

{% endcode %}

* **ngClass**: quando o atributo **selecionado** do item (animal) estiver *true* a classe *selected* será aplicada.
* **cdkDragStarted:** assim que o componente for arrastado esse evento será disparado e como veremos mais à frente iremos prepará-lo para evitar um comportamento inadequado.
* **click:** neste evento iremos verificar se o *Ctrl* está selecionado, caso esteja, mudaremos o valor do atributo **selecionado** do item (animal) que foi clicado.

Para controlar a lista dos itens a serem exibidos desta vez teremos um *model* para o Animal e outro para o Zoológico. Como já mencionado no artigo anterior é sempre importante representarmos nossos objetos por meio de classes.

{% code title="animal.model.ts" %}

```typescript
export class AnimalModel implements DragDropAtributos {
  selecionado: boolean;

  constructor(
    public especie: string
  ) {
  }
}
```

{% endcode %}

{% code title="zoologico.model.ts" %}

```typescript
export class ZoologicoModel {
  constructor(
    public nome: string,
    public animais: Array<AnimalModel>
  ) {
  }
}
```

{% endcode %}

Outra novidade é a interface **drag-drop-atributos.ts** para obrigarmos a implementação do atributo **selecionado**, como podemos ver no **AnimalModel**.

{% code title="drag-drop-atributos.ts" %}

```typescript
export interface DragDropAtributos {
  selecionado: boolean;
}

```

{% endcode %}

E finalmente iremos falar sobre o arquivo **drag-and-drop.component.ts** :nerd:&#x20;

```typescript
export class DragAndDropComponent {

  /**
   * De preferência crie um serviço para buscar estes objetos a partir 
   * de um banco de dados 
   */
  zoologicos = [
    new ZoologicoModel('Zoo1',
      [
        new AnimalModel('Leão'),
        new AnimalModel('Macaco'),
        new AnimalModel('Girafa')
      ]
    ),
    new ZoologicoModel('Zoo2',
      [
        new AnimalModel('Tigre'),
        new AnimalModel('Elefante'),
        new AnimalModel('Zebra')
      ]
    )
  ];

  /**
   * Nome único para cada DropList, utilizamos o nome do zoológico
   */
  nomesDropList = [...this.zoologicos.map(zoo => zoo.nome)];

  /**
   * Ao clicar em um animal dispara este evento, se o control estiver 
   * pressionado então marca/desmarca o animal como selecionado
   * @param e
   * @param animal
   */
  selecionarAnimal(e: Event, animal: AnimalModel) {
    if ((<KeyboardEvent>e).ctrlKey) {
      animal.selecionado = !animal.selecionado;
    }
  }

  /**
   * Recebe o evento de cdkDropListDropped, captura a lista de animais do 
   * container atual e filtra pelos animais selecinados, caso tenham animais 
   * selecionados então os mesmos serão movidos
   * @param event
   */
  dropItems(event: CdkDragDrop<Array<AnimalModel>>) {
    let animais: Array<AnimalModel> = event.previousContainer.data;
    let animaisSelecionados = animais.filter(a => a.selecionado);

    if (animaisSelecionados.length > 1) {
      animaisSelecionados.forEach((animal, index) => {
        this.drop(event, animais.indexOf(animal), index);
      });
    } else {
      this.drop(event, -1, 0);
    }

    this.finalizarArrasto();
  }

  /**
   * Quando o indiceAnterior for maior que -1 então significa que uma lista de 
   * animais selecionados esta sendo movida, a variável agregarAoIndiceAtual 
   * é sempre o indice da lista de animais selecionados do item que esta sendo 
   * passado.
   * @param event do tipo cdkDropListDropped
   * @param indiceAnterior -1 como escape para comportamento default
   * @param agregarAoIndiceAtual 0 como escape para comportamento default
   */
  drop(event: CdkDragDrop<Array<AnimalModel>>, indiceAnterior: number, agregarAoIndiceAtual: number) {
    const previous = indiceAnterior > -1 ? indiceAnterior : event.previousIndex;
    const current = event.currentIndex + agregarAoIndiceAtual;

    if (event.previousContainer === event.container) {
      moveItemInArray(
        event.container.data,
        previous,
        current > previous ? current : event.currentIndex
      );
    } else {
      transferArrayItem(
        event.previousContainer.data,
        event.container.data,
        previous,
        current
      );
    }
  }

  /**
   * Adiciona como selecionado o animal que for arrastado, dessa forma 
   * garantimos que não aconteça o erro de selecionar um, arrastar outro 
   * e o selecionado ficar para traz
   * @param animal
   */
  prepararArrasto(animal: AnimalModel) {
    animal.selecionado = true;
  }

  /**
   * Altera para false o valor do atributo selecionado de todos os animais
   */
  finalizarArrasto() {
    this.zoologicos.forEach(zoo =>
      zoo.animais.forEach(animal => animal.selecionado = false)
    );
  }
}
```

#### Para entendermos melhor o que foi feito, iremos ver método a método.&#x20;

```typescript
  /**
   * Ao clicar em um animal dispara este evento, se o control estiver 
   * pressionado então marca/desmarca o animal como selecionado
   * @param e
   * @param animal
   */
  selecionarAnimal(e: Event, animal: AnimalModel) {
    if ((<KeyboardEvent>e).ctrlKey) {
      animal.selecionado = !animal.selecionado;
    }
  }
```

No arquivo **drag-and-drop.component.html** temos a uma div **cdkDrag** da qual implementamos atribuímos a função **selecionarAnimal(e: Event, animal: AnimalModel)** no seu evento de *click*. Se ao clicar no item (Animal) da lista o botão *Ctrl* estiver pressionado, então o atributo **selecionado** terá seu valor alterado.

```typescript
  /**
   * Adiciona como selecionado o animal que for arrastado, 
   * dessa forma garantimos que não aconteça o erro de selecionar
   * um, arrastar outro e o selecionado ficar para traz
   * @param animal
   */
  prepararArrasto(animal: AnimalModel) {
    animal.selecionado = true;
  }

```

Existe um comportamento que não é o desejado, quando selecionamos um animal e em seguida arrastamos outro que não foi marcado como selecionado então somente esse último é arrastado para a nova lista, e não é isso que queremos não é mesmo?! Por esse motivo utilizamos o evento **cdkDragStarted** para selecionar também o animal que esta sendo arrastado, mesmo que ele já esteja como true no seu atributo **selecionado**.

```typescript
 /**
   * Recebe o evento de cdkDropListDropped, captura a lista de animais 
   * do container atual e filtra pelos animais selecinados,
   * Caso tenham animais selecionados então os mesmos serão movidos
   * @param event
   */
  dropItems(event: CdkDragDrop<Array<AnimalModel>>) {
    let animais: Array<AnimalModel> = event.previousContainer.data;
    let animaisSelecionados = animais.filter(a => a.selecionado);

    if (animaisSelecionados.length > 1) {
      animaisSelecionados.forEach((animal, index) => {
        this.drop(event, animais.indexOf(animal), index);
      });
    } else {
      this.drop(event, -1, 0);
    }

    this.finalizarArrasto();
  }
```

Neste método buscamos uma lista dos animais que foram selecionados, se a lista for maior que 1 registro então aplicamos o método **drop** item a item da lista, passando sempre o índice atual e o número que deve ser agregado ao índice para que assim os animais sejam transferidos para a outra lista na sequência correta.

```typescript
 /**
   * Quando o indiceAnterior for maior que -1 então significa que uma lista 
   * de animais selecionados esta sendo movida, a variável agregarAoIndiceAtual 
   * é sempre o indice da lista de animais selecionados do item que esta sendo 
   * passado.
   * @param event do tipo cdkDropListDropped
   * @param indiceAnterior -1 como escape para comportamento default
   * @param valorAgregacaoIndiceAtual 0 como escape para comportamento default
   */
  drop(event: CdkDragDrop<Array<AnimalModel>>, indiceAnterior: number, valorAgregacaoIndiceAtual: number) {
    const previous = indiceAnterior > -1 ? indiceAnterior : event.previousIndex;
    const current = event.currentIndex + valorAgregacaoIndiceAtual;

    if (event.previousContainer === event.container) {
      moveItemInArray(
        event.container.data,
        previous,
        current > previous ? current : event.currentIndex
      );
    } else {
      transferArrayItem(
        event.previousContainer.data,
        event.container.data,
        previous,
        current
      );
    }
  } 
```

Aqui é simples também **-1** para o **indiceAnterior** e **0** para o **valorAgregacaoIndiceAtual** são valores de escape, assim a função de **moveItemInArray** não irá se perder ao arrastar um único item e ira utilizar o **event.currentIndex** seguindo o comportamento padrão deste método do qual podemos encontrar em inúmeros exemplos na internet.

```typescript
  /**
   * Altera para false o valor do atributo selecionado de todos os animais
   */
  finalizarArrasto() {
    this.zoologicos.forEach(zoo =>
      zoo.animais.forEach(animal => animal.selecionado = false)
    );
  }
```

Para finalizar não queremos que os animais fiquem selecionados após termos arrastados eles para outro lista ou até mesmo na mesma lista, por esse motivo vamos passar *false* para o atributo **selecionado** de todos eles.

{% hint style="warning" %}
Percebeu que essa seleção só está funcionando com o *Ctrl?*  Não foi por acaso. Agora que você já sabe como funciona, que tal você mesmo implementar a opção de selecionar com *Shift*?\
Tenha em mente, que com o Shift você seleciona um *range* de animais, ou seja, se selecionarmos o animal na posição 1 e na posição 3 então a posição 2 deve ser marcada também como selecionado, fazendo isso todo resto já irá funcionar da forma que está implementada.

Boa sorte e bons estudos.
{% endhint %}

**Obrigado por chegar até aqui! Fim**:partying\_face:&#x20;

![O Arthur](/files/-M7s-eacEUwFodoje4vE)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://developers-friends.gitbook.io/blog/angular/drag-and-drop-com-angular-material-e-droplist-dinamica/drag-and-drop-com-multiplas-selecoes.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
