Como limitar a quantidade de links de paginação com Vue?

14/03/2020 20:45

Ter um componente de paginação é essencial para qualquer projeto Vue. E, geralmente, a parte chata de um componente de paginação é ter que configurar a limitação dos links que são exibidos, uma vez que o excesso desses acaba causando problemas de usabilidade.

Bem, inicialmente, vamos pensar no nosso componente de paginação com a seguinte estrutura básica:

<template>
    <ul>
        <li v-for="i in lastPage">
            <a @click="$emit('input', i)">{{ i }}</a>
        </li>
    </ul>
</template>
<script>
export default {
    name: 'Pagination',

    props: {
        lastPage: Number,
        value: Number,
        limitLinks: {
            type: Number,
            default: 10
        }
    }
}
</script>

A chamada desse componente vai funcionar assim:

<pagination :last-page="100" v-model="currentPage"  />

Nota: O uso de value e $emit('input') possibilita que o v-model possa ser utilizado no nosso componente. Toda vez que $emit('input') é chamado, o valor do v-model é atualizado.

Da forma atual, com o valor 100 definido em lastPage, faria com que tivéssemos 100 links. Mas não é o que desejamos. Precisamos que os links sejam limitados de 10 em 10. E claro, isso deve ser feito baseando-se no valor atual da paginação.

Para fazer isso, podemos usar uma lógica onde obtemos os valores iniciais e finais baseando no limite e valor atual. No caso, como o nosso limite padrão é 10. Sendo assim, podemos fazer o seguinte para descobrir o valor inicial do loop:

const start = Math.floor(this.value / this.limitLinks) * this.limitLinks;

Basicamente, o que estamos fazendo é arredondar o valor da divisão da página atual pelo número de links e multiplicando o resultado pelo número de links. Assim, conseguimos obter a dezena inicial de acordo com o valor atual.

Ou seja: 25 divido por 10 sendo arredondado para baixo é equivalente a 2 que, ao multiplar pelo limite 10, é 20. Assim sendo, sabemos que, para o valor 25, o primeiro link a ser exibido na paginação é 20.

E para descobrir o último?

Bem, basta somar o valor inicial com o limite:

const end = start + limit;

Ou seja, se temos 25 como valor atual, o valor final é 30.

Porém há um problema nessa lógica. E se o valor atual for 25, porém o valor máximo de paginaçao for 27. Bem, podemos usar o Math.min para resolver isso:

const end = Math.min(start + this.limitLinks, this.lastPage);

Sendo assim, podemos construir um loop para gerar os números do nosso link:

generateLinks() {

    const links = [];
    const start = Math.floor(this.value / this.limitLinks) * this.limitLinks;
    const end = Math.min(start + this.limitLinks, this.lastPage);

    for (let i = start; i < end; i++) {
        links.push(i);
    }

    return links;
}

Como vocês podem ver, existe um último ajuste a ser feito: os valores estão iniciando com 0 e terminando com 9. No caso, precisamos que comece com 1 e termine com 10, comece com 11 e termine com 21, comece com 31 e termine com 40… Sendo assim, basta somar i + 1:

generateLinks() {

    const links = [];
    const start = Math.floor(this.value / this.limitLinks) * this.limitLinks;
    const end = Math.min(start + this.limitLinks, this.lastPage);

    for (let i = start; i < end; i++) {
        links.push(i + 1);
    }

    return links;
}

Conhecendo a estrutura do Vue, creio que nesse caso seja interessante usar essa lógica de generateLinks como propriedade computada do Vue. Sendo assim, adicionaremos a função criada acima dentro de computed e chamaremos ela de numbers.

export default {
    // restante do código ...
    
    computed: {
        numbers() {

            const links = [];
            const start = Math.floor(this.value / this.limitLinks) * this.limitLinks;
            const end = Math.min(start + this.limitLinks, this.lastPage);

            for (let i = start; i < end; i++) {
                links.push(i + 1);
            }

            return links;
        }       
    }
}

Agora está tudo certo. Para finalizarmos, precisamos alterar o valor utilizado no v-for. Tiraremos o lastPage e usaremos a propriedade computada numbers. Além disso, vamos adicionar também um link para voltar à página anterior e um para avançar para a próxima página.

Aqui está o código final:

<template>
  <ul>
    <li>
      <a @click="$emit('input', value - 1)">&laquo; anterior</a>
    </li>
    <li v-for="number in numbers" :key="`pagination-number-${number}`">
      <a @click="$emit('input', number)">{{ number }}</a>
    </li>
      <li>
      <a @click="$emit('input', value + 1)">próximo &raquo;</a>
    </li>
  </ul>
</template>

<script>
export default {
  name: "Pagination",

  props: {
    lastPage: Number,
    value: Number,
    limitLinks: {
      type: Number,
      default: 10
    }
  },

  computed: {
    numbers() {
      const links = [];
      const start = Math.floor(this.value / this.limitLinks) * this.limitLinks;
      const end = Math.min(start + this.limitLinks, this.lastPage);

      for (let i = start; i < end; i++) {
        links.push(i + 1);
      }

      return links;
    }
  }
};
</script>

Artigos relacionados