Как создать компонент пагинации в Vue.js

Разбиение на страницы, или пагинация – это механизм для разделения больших результатов на отдельные страницы. При этом пользователю предоставляются инструменты для навигации по этим страницам.

Пагинация ресурсов в веб-приложениях может быть очень полезной не только с точки зрения производительности самого приложения, но и с точки зрения пользовательского опыта.

В этом руководстве вы узнаете, как создать динамический и многоразовый компонент разбивки на страницы для приложения Vue.js.

Требования

Это руководство было протестировано на Node v16.4.0, npm v7.19.0 и Vue v2.6.11.

1: Настройка проекта

Компонент разбиения на страницы должен позволять пользователю переходить к первой и последней страницам, перемещаться вперед и назад и переходить непосредственно на ближайшую страницу в диапазоне.

Кнопки для перехода к первой, предыдущей странице, диапазону, следующей и последней странице мы будем отображать в следующем формате:

[first] [next] [1] [2] [3] [previous] [last]

Большинство приложений запрашивают API каждый раз, когда пользователь меняет страницу. Нам нужно убедиться, что наш компонент позволяет это сделать, но при этом мы не хотим делать запрос внутри компонента. Таким образом, мы обеспечим возможность многократного использования компонента во всем приложении. Кроме того, все запросы будут выполняться на уровне действий/сервисов. Чтобы добиться такого результата, нужно запускать событие с номером страницы, на которую щелкнул пользователь.

Существует несколько способов реализовать разбиение на страницы в конечной точке API.

Если API сообщает только об общем количестве записей, мы можем рассчитать общее количество страниц, разделив количество результатов на количество записей, расположенных на одной странице: totalResults/perPage.

В данном примере давайте предположим, что API сообщает нам количество результатов на страницу (perPage), общее количество страниц (totalPages) и текущую страницу (currentPage). Это будут наши динамические свойства.

Допустим, мы хотим отобразить диапазон страниц, но не хотим отображать все доступные страницы. Тогда давайте при помощи свойства в нашем компоненте (maxVisibleButtons) настроим максимальное количество видимых кнопок.

Итак, мы уже знаем, что мы хотим от компонента и какие данные нам понадобятся. Значит, мы можем установить структуру HTML и необходимые свойства.

Для создания нового проекта Vue можно использовать @vue/cli:

npx @vue/cli create –default vue-pagination-example

Затем перейдите в каталог нового проекта:

cd vue-pagination-example

Создайте в нем файл Pagination.vue и откройте его в редакторе кода:

<template>
  <ul>
    <li>
      <button
        type="button"
      >
        First
      </button>
    </li>

    <li>
      <button
        type="button"
      >
        Previous
      </button>
    </li>

    <!-- Visible Buttons Start -->

    <!-- ... -->

    <!-- Visible Buttons End -->

    <li>
      <button
        type="button"
      >
        Next
      </button>
    </li>

    <li>
      <button
        type="button"
      >
        Last
      </button>
    </li>
  </ul>
</template>

<script>
export default {
  props: {
    maxVisibleButtons: {
      type: Number,
      required: false,
      default: 3
    },
    totalPages: {
      type: Number,
      required: true
    },
    perPage: {
      type: Number,
      required: true
    },
    currentPage: {
      type: Number,
      required: true
    }
  }
};
</script>

На данном этапе разработки компонент отображает список из четырех кнопок. Чтобы получить диапазон видимых кнопок, мы будем использовать цикл for.

Сейчас нужно убедиться, что это число никогда не превышает значения свойства, которое устанавливает максимальное количество видимых кнопок. Также оно не должно превышать количества доступных страниц.

Стартовый номер цикла зависит от текущей страницы:

  1. Если текущая страница является первой, приложение должно показывать пользователю текущую и следующие страницы.
  2. Если текущая страница является последней, приложение должно показывать последнюю и предыдущие страницы.
  3. Если текущей является любая страница между первой и последней, приложение должно отображать предыдущие и последующие страницы.

Конечное число цикла также требует некоторых вычислений. Нам нужно получить наименьшее число между общим количеством страниц и позицией, на которой мы хотим остановиться. Чтобы вычислить эту позицию, нужна сумма исходной позиции и максимального количества видимых кнопок. Поскольку слева от текущей страницы мы всегда хотим отображать одну кнопку, нам нужно вычесть 1 из этого числа.

Давайте воспользуемся вычисляемым свойством, которое возвращает массив объектов с диапазоном видимых страниц. У каждого объекта будет свойство с номером страницы и еще одно свойство, которое сообщит, должна ли кнопка быть отключена. В конце концов, мы же не хотим, чтобы пользователь нажимал на страницу, на которой он и так находится.

Чтобы отобразить этот массив страниц, мы будем использовать директиву v-for. Для более сложных структур данных рекомендуется указывать key с каждой v-for. Vue использует значение key, чтобы найти элемент, который необходимо обновить. Когда это значение не указано, Vue использует стратегию «патч на месте». Хотя мы используем довольно простые данные, мы все же предоставим значение key. А если вы используете eslint-vue-plugin с правилами vue3-essential, вам всегда нужно будет указывать значение key.

Вернитесь к файлу Pagination.vue и добавьте startPage() и pages():

<template>
  <ul>
    ...
    <!-- Visible Buttons Start -->

    <li
      v-for="page in pages"
      :key="page.name"
    >
      <button
        type="button"
        :disabled="page.isDisabled"
      >
        {{ page.name }}
      </button>
    </li>

    <!-- Visible Buttons End -->
    ...
  </ul>
</template>

<script>
export default {
  ...
  computed: {
    startPage() {
      // When on the first page
      if (this.currentPage === 1) {
        return 1;
      }

      // When on the last page
      if (this.currentPage === this.totalPages) {
        return this.totalPages - this.maxVisibleButtons;
      }

      // When inbetween
      return this.currentPage - 1;
    },
    pages() {
      const range = [];

      for (
        let i = this.startPage;
        i <= Math.min(this.startPage + this.maxVisibleButtons - 1, this.totalPages);
        i++
      ) {
        range.push({
          name: i,
          isDisabled: i === this.currentPage
        });
      }

      return range;
    },
  }
};
</script>

На этом логика отображения кнопок готова.

2: Добавление прослушивателей событий

Теперь нам нужно сообщить родительскому компоненту, что пользователь нажал кнопку и какую именно кнопку он нажал.

Для этого нужно добавить к каждой из кнопок прослушиватель событий. Директива v-on позволяет прослушивать события DOM. В этом примере мы будем использовать сокращение v-on для прослушивания кликов.

Читайте также: Управление событиями с помощью Vue.js

Чтобы сообщить родительскому компоненту о событии, мы воспользуемся методом $emit для генерации события со страницей, по которой кликнули.

Также следует убедиться, что кнопки разбивки на страницы активны, только если страница доступна. Мы воспользуемся директивой v-bind, чтобы связать значение атрибута disabled с текущей странице (мы снова используем сокращение).

Чтобы шаблон был чище, мы используем вычисленные свойства, которые проверят, следует ли отключить кнопку. Использование вычисленных свойств также приведет к кэшированию значений. Это означает, что до тех пор, пока currentPage не изменится, другие запросы для того же вычисляемого свойства будут возвращать ранее вычисленный результат без необходимости повторного запуска функции.

Вернемся к файлу Pagination.vue, добавим проверку для текущей страницы и методы отслеживания событий (кликов):

<template>
  <ul>
    <li>
      <button
        type="button"
        @click="onClickFirstPage"
        :disabled="isInFirstPage"
      >
        First
      </button>
    </li>

    <li>
      <button
        type="button"
        @click="onClickPreviousPage"
        :disabled="isInFirstPage"
      >
        Previous
      </button>
    </li>

    <!-- Visible Buttons Start -->

    <li
      v-for="page in pages"
      :key="page.name"
    >
      <button
        type="button"
        @click="onClickPage(page.name)"
        :disabled="page.isDisabled"
      >
        {{ page.name }}
      </button>
    </li>

    <!-- Visible Buttons End -->

    <li>
      <button
        type="button"
        @click="onClickNextPage"
        :disabled="isInLastPage"
      >
        Next
      </button>
    </li>

    <li>
      <button
        type="button"
        @click="onClickLastPage"
        :disabled="isInLastPage"
      >
        Last
      </button>
    </li>
  </ul>
</template>

<script>
export default {
  ...
  computed: {
    ...
    isInFirstPage() {
      return this.currentPage === 1;
    },
    isInLastPage() {
      return this.currentPage === this.totalPages;
    },
  },
  methods: {
    onClickFirstPage() {
      this.$emit('pagechanged', 1);
    },
    onClickPreviousPage() {
      this.$emit('pagechanged', this.currentPage - 1);
    },
    onClickPage(page) {
      this.$emit('pagechanged', page);
    },
    onClickNextPage() {
      this.$emit('pagechanged', this.currentPage + 1);
    },
    onClickLastPage() {
      this.$emit('pagechanged', this.totalPages);
    }
  }
}
</script>

3: Добавление стилей

Теперь, когда наш компонент проверяет все необходимые функции, нам нужно добавить немного CSS, чтобы он больше напоминал компонент нумерации страниц, а не список.

Конечно, мы также хотим, чтобы пользователи могли четко определять, на какой странице они находятся. Для этого мы изменим цвет кнопки, которая отвечает за текущую страницу.

Это можно сделать, привязав HTML-класс к кнопке активной страницы через синтаксис объекта. При использовании синтаксиса объекта для привязки имен классов Vue автоматически переключает класс при изменении значения.

Хотя каждый блок внутри директивы v-for имеет доступ к свойствам родительской области, мы будем использовать method, чтобы проверить, активна ли страница. Такой подход сделает шаблон более чистым.

Добавим в файл pagination.vue проверку активной страницы, а также стили и классы CSS:

<template>
  <ul class="pagination">
    <li class="pagination-item">
      <button
        type="button"
        @click="onClickFirstPage"
        :disabled="isInFirstPage"
      >
        First
      </button>
    </li>

    <li class="pagination-item">
      <button
        type="button"
        @click="onClickPreviousPage"
        :disabled="isInFirstPage"
      >
        Previous
      </button>
    </li>

    <!-- Visible Buttons Start -->

    <li
      v-for="page in pages"
      class="pagination-item"
    >
      <button
        type="button"
        @click="onClickPage(page.name)"
        :disabled="page.isDisabled"
        :class="{ active: isPageActive(page.name) }"
      >
        {{ page.name }}
      </button>
    </li>

    <!-- Visible Buttons End -->

    <li class="pagination-item">
      <button
        type="button"
        @click="onClickNextPage"
        :disabled="isInLastPage"
      >
        Next
      </button>
    </li>

    <li class="pagination-item">
      <button
        type="button"
        @click="onClickLastPage"
        :disabled="isInLastPage"
      >
        Last
      </button>
    </li>
  </ul>
</template>

<script>
export default {
  ...
  methods: {
    ...
    isPageActive(page) {
      return this.currentPage === page;
    }
  }
}
</script>

<style>
.pagination {
  list-style-type: none;
}
.pagination-item {
  display: inline-block;
}

.active {
  background-color: #4AAE9B;
  color: #ffffff;
}
</style>

Итак, логика стилизации активных кнопок готова, идем дальше.

4: Использование компонента

На данном этапе компонент можно использовать в приложении. Для этого нужно смоделировать вызов API, указав значения для totalPages и perPage.

В этом примере строка currentPage должна иметь значение 1, а onPageChange отключит кнопку активной страницы.

<template>
  <div id="app">
    <pagination
      :totalPages="10"
      :perPage="10"
      :currentPage="currentPage"
      @pagechanged="onPageChange"
    />
  </div>
</template>

<script>
import Pagination from './components/Pagination.vue'

export default {
  name: 'App',
  components: {
    Pagination
  },
  data () {
    return {
      currentPage: 1,
    };
  },
  methods: {
    onPageChange(page) {
      console.log(page)
      this.currentPage = page;
    }
  }
}
</script>

На этом этапе вы можете запустить приложение:

npm run serve

Откройте приложение в браузере и посмотрите на нумерацию страниц. Там будут кнопки First, Previous, Next и Last. Также там будут отображаться кнопки для трех из десяти страниц. Поскольку первая страница является текущей, кнопка 1 отмечена как активная. Кроме того, по этой же причине кнопки First и Previous будут отключены.

Примечание: Пример этого кода доступен на CodePen.

Заключение

В этой статье вы научились создавать динамический и многоразовый компонент разбивки на страницы для приложения Vue.js.

Tags:

Добавить комментарий