Introdução

O vuedraggable é uma biblioteca para VueJS e é utilizada para realizar ordenação de listas.

Essa biblioteca pode ser utilizada em situações onde precise fazer ordenação, por exemplo com uma lista simples, ordenar itens de uma tabela, ordenar itens de duas listas e outros.

Essa biblioteca conta com mais de 400 mil downloads semanais no NPM e atualizações recentes, o que da uma confiabilidade maior para utilizar em um projeto.

Neste artigo você vai ver como fazer as três ordenações citadas acima.

Instalação

Para fazer a instalação do vuedraggable é possível fazer com o NPM ou Yarn:

npm install vuedraggable
ou
yarn add vuedraggable

A configuração será realizada em cada componente que utilizar a reordenação.

Projeto

Para os exemplos, já deixei criado um projeto e fiz a instalação e configuração do BootstrapVue.

Vou utilizar ele porque é o que estou mais familiarizado, porém os exemplos poderão ser adaptados para qualquer outro framework.

O main.js está dessa forma:

import Vue from 'vue'
import App from './App.vue'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'

import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

Vue.use(BootstrapVue)
Vue.use(IconsPlugin)

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

Eu criei também 3 componentes, um para cada exemplo, fiz a importação e declarei o uso no App.vue.

Também criei uma classe cursor-pointer, que vou utilizar para indicar que a lista pode ser reordenável.

<template>
  <div id="app" class="container mt-3">
    <lista-padrao />

    <!-- <tabela /> -->

    <!-- <duas-listas /> -->
  </div>
</template>

<script>
import ListaPadrao from "./components/ListaPadrao.vue"
import Tabela from "./components/Tabela.vue"
import DuasListas from "./components/DuasListas.vue"
export default {
  name: 'App',
  components: {
    Tabela,
    DuasListas,
    ListaPadrao
  },
}
</script>

<style>
.cursor-pointer {
  cursor: pointer;
}
</style>

O vuedraggable será importado em cada componente.

Ordenar lista

Agora é preciso criar o componente da lista que será reordenada, criei dentro do diretório components, o arquivo ListaPadrao.vue.

Primeiramente, criei a estrutura do componente Vue sem renderizar a lista na tela, só colocando ela no data.

<template>
  <div>
      <!-- lista -->
      <div>
          <pre>{{ list }}</pre>
      </div>
  </div>
</template>

<script>

export default {
    name: "Lista padrão",
   
    data() {
        return {
            list: [
            { name: "Pessoa 1", id: 0 },
            { name: "Pessoa 2", id: 1 },
            { name: "Pessoa 3", id: 2 }
            ],
       }
    },
}
</script>

A próxima etapa é importar a biblioteca e declarar como componente para poder utilizar, dessa forma:

import draggable from "vuedraggable";
export default {
    name: "Lista padrão",
    components: {
        draggable
    },
    data() {
        return {
            list: [
            { name: "Pessoa 1", id: 0 },
            { name: "Pessoa 2", id: 1 },
            { name: "Pessoa 3", id: 2 }
            ],
       }
    },
}

Com isso, onde estava o comentário <!– lista –>, agora precisa substituir pelo componente de ordenação draggable:

<draggable
    :list="list"
    class="list-group"
>
    <div 
        class="list-group-item cursor-pointer"
        v-for="element in list"
        :key="element.name"
    >
        {{ element.name }}
    </div>
</draggable>

Na tela já terá a lista com a opção de clicar no item e arrastar para a nova posição, fazendo assim o funcionamento da reordenação.

Nesse bloco de código, primeiramente tem o componente recém importado do draggable, ele recebe duas props:

A primeira prop é a lista que será reordenada, ele precisa dessa lista para manter os itens.

A segunda é a classe que será colocada no HTML, no caso do bootstrap-vue precisa ter a classe list-group, para renderizar uma lista corretamente.

Caso esteja utilizando outro, substitua pela classe de lista do seu framework.

Dentro do draggable tem uma lista sendo renderizada com uma div, e com a classe list-group-item, que é do bootstrap-vue para cada item.

A classe cursor-pointer que foi declarada anteriormente, é colocada nesse ponto, porque com ela, o usuário passa o mouse por cima do item, e vira a “mãozinha” indicando que pode clicar e arrastar.

Com esse código, o resultado é este:

Ordenar tabela

Para a tabela criei outro componente chamado Tabela.vue dentro do diretório de components. E no App.vue descomentei o <tabela /> e comentei o <lista-padrao />.

A estrutura base é uma tabela e agora já vou fazer a importação e configuração, assim como visto acima:

<template>
  <div>
      <table class="table table-striped">
          <thead class="thead-dark">
              <th>Id</th>
              <th>Nome</th>
          </thead>
          <!-- linhas da tabela -->
      </table>

      <div>
          <pre>{{ list }}</pre>
      </div>
  </div>
</template>

<script>
import draggable from "vuedraggable";
export default {
    name: "Tabela",
    components: {
        draggable
    },
    data() {
        return {
            list: [
            { name: "Pessoa 1", id: 0 },
            { name: "Pessoa 2", id: 1 },
            { name: "Pessoa 3", id: 2 }
            ],
       }
    },
}
</script>

Como o que será reordenável são as linhas da tabela, a estrutura dela se mantém normal, com a tabela em si e o header.

Onde estiver o comentário <!– linhas da tabela –> será substituído pelo código abaixo:

<draggable
    :list="list" tag="tbody"
>
        <tr v-for="element in list" :key="element.name" class="cursor-pointer">
            <td scope="row">{{ element.id }}</td>
            <td>{{ element.name }}</td>
        </tr>
</draggable>

Neste caso, também tem a prop list, mas a diferença é a prop tag.

Quando precisar indicar ao vuedraggable que ele precisa renderizar uma tag específica para o framework, precisa indicar a prop tag com o que precisa ser renderizado, nesse caso o tbody.

O resultado é esse:

Ordenar duas listas

Criei outro componente chamado DuasListas.vue dentro do diretório de components. E no App.vue descomentei o <duas-listas /> e comentei o <tabela />.

A estrutura base da tela vai ter duas colunas, com um tamanho de 6 cada, isso para as listas ficarem lado a lado e mais fácil de reordenar:

<template>
    <div>
        <b-row>
            <b-col md="6">
                <!-- lista 1 -->
            </b-col>
            <b-col md="6">
                <!-- lista 2 -->
            </b-col>
        </b-row>
        <b-row>
            <b-col md="6"><pre>{{ list1 }}</pre></b-col>
            <b-col md="6"><pre>{{ list2 }}</pre></b-col>
        </b-row>
    </div>
</template>

<script>
import draggable from "vuedraggable";
export default {
    name: "Lista padrão",
    components: {
        draggable
    },
    data() {
        return {
            list1: [
            { name: "Pessoa 1", id: 0 },
            { name: "Pessoa 2", id: 1 },
            { name: "Pessoa 3", id: 2 }
            ],
            list2: [
            { name: "Pessoa 4", id: 3 },
            { name: "Pessoa 5", id: 4 },
            { name: "Pessoa 6", id: 5 },
            ],
       }
    },
}
</script>

O código da lista 1 e lista 2 são parecidos, o que muda é só a lista para renderizar, trocar no v-for e no list, o nome das variáveis entre list1 e list2, o código do <template> com as listas fica dessa forma:

<template>
  <div>
    <b-row>
      <b-col md="6">
        <draggable :list="list1" class="list-group" group="people">
          <div
            class="list-group-item cursor-pointer"
            v-for="element in list1"
            :key="element.name"
          >
            {{ element.name }}
          </div>
        </draggable>
      </b-col>
      <b-col md="6">
        <draggable :list="list2" class="list-group" group="people">
          <div
            class="list-group-item cursor-pointer"
            v-for="element in list2"
            :key="element.name"
          >
            {{ element.name }}
          </div>
        </draggable>
      </b-col>
    </b-row>
    <b-row>
      <b-col md="6">
        <pre>{{ list1 }}</pre>
      </b-col>
      <b-col md="6">
        <pre>{{ list2 }}</pre>
      </b-col>
    </b-row>
  </div>
</template>

Agora para cada draggable, foi adicionado a prop group, esta é a responsável por fazer as duas listas terem literalmente o mesmo grupo de dados, o que da a possibilidade de fazer com que os dados de uma lista vá para outra e vice versa.

E o resultado é esse:

Vídeo

Conclusão

A biblioteca vuedraggable é muito boa para trabalhar com esse tipo de ordenação, com ela ainda é possível adicionar efeitos de transição na lista, ordenar elementos filhos e entre outros casos, recomendo que dê uma olhada na biblioteca caso precise de algum outro caso.

Código fonte

O código fonte está no meu CodeSandbox, neste link.

Para ver outros canais onde o posto conteúdo, veja os Links do Programando Soluções.

Referências

https://www.npmjs.com/package/vuedraggable

https://programandosolucoes.dev.br/2020/08/25/como-utilizar-bootstrapvue/

https://bootstrap-vue.org/