Простая реализация стандартного веб-компонента с использованием начальной загрузки, LitElement и ES6 +

Недавно в проекте мне пришлось использовать компонент пагинатора для моих списков с помощью Bootstrap 4. Поскольку я не смог найти компонент, основанный на веб-компонентах, я решил создать его.

В этой обобщенной статье я покажу вам шаг за шагом, как создать компонент веб-пагинатора на основе стилей Bootstrap 4, LitElement и jQuery.

Для чтения этой статьи рекомендуется иметь базовые знания о LitElement. В предыдущей статье я написал введение в LitElement: Как создать легкий веб-компонент с LitElement.

Требования

LitElement: простой базовый класс для создания быстрых и легких веб-компонентов. LitElement следует стандартам веб-компонентов, поэтому ваши компоненты будут работать с любым фреймворком.

Https://lit-element.polymer-project.org/

jQuery jQuery - это быстрая, небольшая и многофункциональная библиотека JavaScript. Он значительно упрощает такие вещи, как обход и манипуляции с HTML-документами, обработка событий, анимация и Ajax с помощью простого в использовании API, который работает во множестве браузеров.

Https://jquery.com/

Bootstrap: Bootstrap - это бесплатная интерфейсная среда для более быстрой и простой веб-разработки. Нам нужны стили Bootstrap 4.

Давайте начнем:

1. Включите все необходимые зависимости

Файл: Index.html

<!doctype html>
<html class="no-js" lang="">
<head>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<!--Jquery-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js" crossorigin="anonymous"></script>
<!--LitElement-->
<script type="module">
 import { LitElement, html } from 'lit-element';
</script>
</head>
 <body>
  ...
 </body>
</html>

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

Файл: ListComponent.js

  1. Импортируйте зависимости LitElement.
  2. Импортируйте наш PaginatorComponent.
  3. Создайте наш компонент ListComponent.
  4. Убедитесь, что обновление элемента запланировано при изменении любого объявленного свойства.
  5. Инициализируйте переменные в конструкторе класса.
  6. Ссылка на исходный список со всеми элементами.
  7. Список ответов - это список, который будет отображаться на каждой странице.
  8. Вспомогательная переменная, сообщающая компоненту Paginator, если мы инициализируем разбиение на страницы. Полезно, например, если мы делаем новый вызов серверу, чтобы принести нам еще один новый список, основанный на некоторых полях поиска.
  9. Метод firstUpdated вызывается после первого обновления модели DOM элемента.
  10. Список, который мы хотим отобразить, разбит на страницы.
  11. Создайте начальный список с разбивкой на страницы на основе const: PAGINATION_SIZE.
  12. Всякий раз, когда мы изменяем содержимое исходного списка родительского элемента, мы должны повторно инициализировать пагинатор.
  13. Подпишитесь на наше «событие разбивки на страницы». Вызывается из компонента Pagination.
  14. Мы обновляем содержимое списка, которое будет отображаться на родительском элементе при использовании пагинатора.
  15. Отобразите список и включите наш компонент paginator.
  16. Включите наш компонент Paginator как дочерний элемент ListComponent.
  17. С помощью этого метода мы определяем новый настраиваемый элемент.
//1.
import { LitElement , html} 
 from '../web_modules/
 lit-element.js';
//2.
import { PaginatorComponent } 
 from 
 './paginatorComponent.js';
//3.
export class ListComponent 
 extends LitElement {
//4.
static get properties() {
  return {
   itemsPaginated: {type: Array}
  };
}
//5.
constructor() {
 super();
 //6.
 this.responseList = [];
 //7. 
 this.itemsPaginated = [];
 //8. 
 this.initPagination = true;
}
//9.
firstUpdated() {
//10.
this.responseList = 
  [1,2,3,4,5,6,7,8,9
   ,10,11,12,13,14,15];
//11
this.itemsPaginated = 
  this.responseList.slice(0,
   PAGINATION_SIZE);
//12.
this.initPagination = 
   !this.initPagination
//13.
this.addEventListener('page-event',  
   this.updateFromPaginationComponent);
}
//14.
updateFromPaginationComponent(e) {
 this.itemsPaginated 
  =e.detail.newList;
 e.stopPropagation();
}
//15.
render() {
 return html`
  <ul>
   ${this.itemsPaginated.map(
   item => html`
    <li>
     ${item}
    </li>`
    )}
  </ul>
//16. 
 <paginator
    .list = "${this.responseList}"
    .initPagination = 
      "${this.initPagination}"
 </paginator> 
 `
 }
}
//17.
customElements
  .define('list-component',
  ListComponent);

2. Создайте веб-компонент Paginator.

Файл: PaginatorComponent.js

  1. Количество кнопок вверху, не считая предыдущей / следующей,
    , но включая кнопки, отмеченные точками.
  2. Ограничивает количество элементов на странице.
  3. Инициализируем наш веб-компонент.
  4. Создайте начальные кнопки и слушателей.
  5. Управляйте ссылками и событиями пагинации.
  6. Функция DispatchEvent.
  7. Возвращает массив номеров страниц.
  8. CustomElements.define определяет новый настраиваемый элемент.
import { html, LitElement}
 from './lit-element.js';
//1.
const PAGINATION_SIZE = 10;
//2.
const LIMIT_PER_PAGE =10;
export class 
 PaginatorComponent
 extends LitElement {
    //3.
    //4.
    //5.
    //6.
    //7.
render() {
  return html`
   <nav aria-label="Page 
     navigation">
     <ul class="pagination mb-5" 
       id="pagination">
     </ul>
  </nav>
  `
 }
}
//8.
customElements
.define('paginator', Paginator);

3. Инициализируем наш веб-компонент

3.1. Если список, который нужно выгрузить, изменится, повторно отредактируйте элемент.

3.2 Если свойство «initPagination» изменяется, тогда мы устанавливаем значение «currentPage» равным единице, потому что исходный список изменился, и нам нужно перезапустить пагинатор.

3.3 Мы инициализируем переменные при создании компонента.

static get properties() {
 return {
   list: {
    type: Array
   },
   initPagination:{type: Boolean}
   };
}
//3.1
set list(value) {
 if(value && value.length >0) {
   this.originalListCopy = value;
   this.paginate(value);
 }
}
//3.2
set initPagination(value){
 this.currentPage = 1;
}
//3.3
constructor() {
    super();
    this.list = [];
    this.originalListCopy = [];
    this.currentPage = 1;
}
firstUpdated() {}

4. Создайте начальные кнопки и добавьте слушателей.

Создайте начальные кнопки и добавьте к ним слушателей в нашем пагинаторе:

4.1 общее количество страниц в функции ограничения элементов на странице. Общее количество страниц округлено в большую сторону.

4.2 Добавьте кнопку «Назад».

4.3 Добавьте кнопку «Далее».

4.4. Показать элемент нумерации страниц.

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

paginate(list) {
 $(() => {
 let numberOfItems = 
  list ? list.length : 0;
 //4.1
 let totalPages =
  Math.ceil(numberOfItems 
    / LIMIT_PER_PAGE);
 //4.2 
 $(".pagination").append(
    $("<li>")
    .addClass("page-item")
    .attr(
    {id: "previous-page"})
     .append(
       $("<a>")
       .addClass("page-link")
       .attr({
        href: "javascript:void(0)"
       })
      .text(this.t(Prev'))
  ),
 //4.3
 $("<li>")
   .addClass("page-item")
   .attr({id: "next-page"})
   .append(
    $("<a>")
    .addClass("page-link")
    .attr({
    href: "javascript:void(0)"
    })
    .text(this.t('Next'))
    )
 );
 //4.4 
 this.showPage(this.currentPage, 
  totalPages, false);
 //4.5
 $('#previous-page')
   .on("click",
     (e) => {
       e.stopImmediatePropagation();
        return this.showPage(
         this.currentPage -1,
         totalPages, true);
 });
 $(document)
   .on("click",
    ".pagination li.current-page:not(.active)",
    (e) =>{
      e.stopImmediatePropagation();
      return this.showPage(
        parseInt($(e.target).text()),   
        totalPages, true);
 });
 $('#next-page')
  .on("click", (e) => {
    e.stopImmediatePropagation();
    return this.showPage(
     this.currentPage 
     +1, totalPages, true);
    });
  });
}

5. Управление ссылками на страницы и событиями

5.1 Заменить элементы навигации (кроме пред. / След.):

showPage(whichPage, totalPages, dispatchEvent) {
 if (whichPage < 1 ||
  whichPage > totalPages)
    return false;
 //5.1
 $(".pagination li")
   .slice(1, -1).remove();
 this.getPageList(totalPages,
   whichPage,  
   PAGINATION_SIZE).forEach(item => {
 $("<li>")
    .addClass(
      "page-item " +
      (item ? 
      "current-page " : "") +
      (item === whichPage 
         ? "active " : "") 
    )
    .append(
      $("<a>")
      .addClass("page-link")
      .attr({
        href: 
         "javascript:void(0)",
      })
      .attr({id: item})
      .text(item || "...")
      )
    .insertBefore("#next-page");
    this.currentPage=whichPage;
      if(dispatchEvent === true 
          && whichPage === item){
        this.dispatchEvent();
      }
   });
return true;
}

6. D функция ispatchEvent

6.1 Отправьте событие отцу с новым списком, который будет отображаться в главном компоненте (ListComponent).

dispatchEvent() {
 let newList = 
   this.originalListCopy.slice(
    (this.currentPage - 1) *   
     LIMIT_PER_PAGE,
     this.currentPage 
     * LIMIT_PER_PAGE
   );
 let myEvent =
   new CustomEvent('pag-event',
   { bubbles: true,
     composed: false ,
     detail: {
      newList: newList
   }});
 //6.1
 this.dispatchEvent(myEvent)
}

7. Возвращает массив номеров страниц.

Возвращает массив номеров страниц maxLength (или меньше), где 0 в возвращенном массиве обозначает пробел в серии.

7.1 Параметры:

totalPages: общее количество страниц.
page: текущая страница.
maxLength: максимальный размер возвращаемого массива.

7.2 В списке нет разрывов.

7.3 Без разрыва в левой части страницы.

7.4 Без разрыва в правой части страницы

7.5 Разрывы с обеих сторон.

//7.1
getPageList(totalPages, page, maxLength) {
if (maxLength
  < MINIMUN_MAX_LENGTH)
  throw "maxLength must be at least:"
  +MINIMUN_MAX_LENGTH;
function range(start, end) {
  return Array.
    from(Array(end - start + 1),
    (_, i) => i + start);
}
let sideWidth =
  maxLength < PAGINATION_SIZE -1 ? 1 : 2;
let leftWidth = (maxLength 
  - sideWidth * 2 - 3) >> 1;
let rightWidth = (maxLength 
  - sideWidth * 2 - 2) >> 1;
//7.2
if (totalPages <= maxLength) { 
  return range(1, totalPages);
}
//7.3
if (page <= maxLength -
     sideWidth - 1 - rightWidth) {
return range(1, maxLength 
      - sideWidth - 1)
    .concat([0])
    .concat(range(totalPages 
     - sideWidth 
    + 1, totalPages));
}
//7.4
if (page >= totalPages - 
    sideWidth - 1 - rightWidth) {
return range(1, sideWidth)
      .concat([0])
      .concat(
        range(totalPages 
         - sideWidth
         - 1 - rightWidth - 
         leftWidth, totalPages)
      );
    }
 //7.5
 range(1, sideWidth)
 .concat([0])
 .concat(range(page - 
     leftWidth, page +
     rightWidth))
 .concat([0])
 .concat(range(totalPages 
     - sideWidth + 1,
 totalPages));
}

Окончательный код со всем вместе:

<!doctype html>
<html class="no-js" lang="">
<head>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<!--Jquery-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js" crossorigin="anonymous"></script>
<!--LitElement-->
<script type="module">
 import { LitElement, html } from 'lit-element';
</script>
</head>
<body>
//Our list component with the paginator component inside of it.
  <list-component></list-component>
<script type="module">
    import {
     ListComponent from './listComponent.js'  
  </script>
</body>
</html>

Результат в веб-браузере:

Заключение

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

Большое спасибо за то, что прочитали эту небольшую статью. Надеюсь, это было вам полезно!

использованная литература

Https://codepen.io/kshoeb/pen/NQboaL