...

Пагинация коробочных таблиц с возможностью редактирования ячеек

Тема в разделе "Примеры решений и дополнительных модулей", создана пользователем luppov, 3 май 2024.

  1. luppov

    luppov Новичок

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

    Решение:
    Пример основан на создании двух одинаковых таблиц, одна из которых выступает в качестве хранилища данных, а вторая для постраничного рендера на форму. В качестве формы будет использована "Форма задачи в БП"
    В данном примере отсутствует обработчик события таблицы, я пошел другим путем чтобы уйти от различных неприятностей в виде рекурсии, а именно при пагинации вызывается метод который перезаписывает данные в строках "Таблицы хранилища".
    1. Создадим две одинаковых таблицы в контексте процесса "json_table - таблица хранилище" ,"table - таблица для рендера на форму" [​IMG]
    2. Напишем сценарий БП для заполнения таблицы-хранилища данными [​IMG]
      Код:
      
      /**
      Here you can write scripts for complex server processing of the context during process execution.
      To write scripts, use TypeScript (https://www.typescriptlang.org).
      ELMA365 SDK documentation available on https://tssdk.elma365.com.
      **/

      // Метод формирования uuid для строк таблицы, если есть возможность лучше воспользоваться библиотекой npm i uuid
      function ID_generation(): string {
          const 
      valid "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ23456789!&?`~№;:^\|/'#$%&*=-+_)(}{][@";
          
      let length 15
          let id 
      ''
          
      while (length--) {
              
      id += valid.charAt(Math.floor(Math.random() * valid.length))
          }

          return 
      id
      }

      // Метод заполнит тестовыми данными таблицу и проставит uuid для каждой строки
      async function fillTableJson(): Promise<void> {
          const 
      users await Context.fields.users.app.search().where((fg) => g.and(
              
      f.__deletedAt.eq(null),
          )).
      size(10000).all()

          if (!
      users || !users.length) return

          
      let counter 1
          users
      .forEach(user => {
              const 
      row Context.data.json_table!.insert()
              
      row.uuid ID_generation()
              
      row.name = `Name ${counter}`
              
      row.user user
              row
      .users = [user]
              
      counter++
          })

          
      Context.data.json_table Context.data.json_table
      }
    3. Добавим блок задачи в БП и создадим форму в расширенном режиме [​IMG]
    4. Вынесем таблицу на форму и добавим ВКод (P.S. пагинация написана мной, не обязательно использовать именно ее как единственный правильный вариант, возможно вам будет быстрее воспользоваться какой-либо библиотекой, для меня это был наиболее быстрый вариант так как уже была заготовка)[​IMG] Теперь по порядку ВКод:

      -
      Пагинация HTML
      Код:
      
      <div class="castom-pagination__wrapper"></div>
      - Пагинация CSS
      Код:
      
      <style>
          .
      castom-pagination__wrapper {
              
      width100%;
              
      max-width526px;
              
      displayflex;
              
      align-itemscenter;
              
      justify-contentend;
              
      padding14px 1px 5px 14px;
          }

          .
      castom-paginations {
              
      displayflex;
              
      align-itemscenter;
              
      gap5px;
              
      z-index100;
          }

          .
      castom-paginations__button-prev {
              
      font-size14px;
              
      font-weight500;
              
      bordernone;
              
      border-radius2px;
              
      padding3px 5px;
              
      background-color#d9d9d9;
              
      opacity0.98;
              
      z-index100;
              -
      webkit-box-shadow0px 5px 10px 2px rgba(3460800.2);
              -
      moz-box-shadow0px 5px 10px 2px rgba(3460800.2);
              
      box-shadow0px 5px 10px 2px rgba(3460800.2);
              
      cursorpointer;
              
      transitionall 0.3s;
          }

          .
      castom-paginations__button-next {
              
      font-size14px;
              
      font-weight500;
              
      bordernone;
              
      border-radius2px;
              
      padding3px 5px;
              
      background-color#d9d9d9;
              
      opacity0.98;
              
      z-index100;
              -
      webkit-box-shadow0px 5px 10px 2px rgba(3460800.2);
              -
      moz-box-shadow0px 5px 10px 2px rgba(3460800.2);
              
      box-shadow0px 5px 10px 2px rgba(3460800.2);
              
      cursorpointer;
              
      transitionall 0.3s;
          }

          .
      castom-paginations__button-prev:active {
              
      background-color#5081e5;
              
      -webkit-box-shadow5px 5px 5px -5px rgba(3460800.6inset;
              -
      moz-box-shadow5px 5px 5px -5px rgba(3460800.6inset;
              
      box-shadow5px 5px 5px -5px rgba(3460800.6inset;
          }

          .
      castom-paginations__button-next:active {
              
      background-color#5081e5;
              
      -webkit-box-shadow5px 5px 5px -5px rgba(3460800.6inset;
              -
      moz-box-shadow5px 5px 5px -5px rgba(3460800.6inset;
              
      box-shadow5px 5px 5px -5px rgba(3460800.6inset;
          }

          .
      castom-paginations__list {
              
      displayflex;
              list-
      style-typenone;
              
      gap5px;
              
      margin0;
              
      padding0;

          }

          .
      castom-paginations__list-item {
              
      z-index100;
              
      displayflex;
          }

          .
      castom-paginations__list-button-page {
              
      font-size14px;
              
      font-weight500;
              
      colorblack;
              
      bordernone;
              
      border-radius2px;
              
      cursorpointer;
              
      padding3px 5px;
              
      background-color#d9d9d9;
              
      opacity0.98;
              
      z-index100;
              -
      webkit-box-shadow0px 5px 10px 2px rgba(3460800.2);
              -
      moz-box-shadow0px 5px 10px 2px rgba(3460800.2);
              
      box-shadow0px 5px 10px 2px rgba(3460800.2);
          }

          .
      castom-paginations__list-button-page:hover {
              
      opacity0.7;
          }

          .
      castom-paginations__list-button-page--active {
              
      background-color#5081e5;
              
      color#fff;
              
      -webkit-box-shadow5px 5px 5px -5px rgba(3460800.6inset;
              -
      moz-box-shadow5px 5px 5px -5px rgba(3460800.6inset;
              
      box-shadow5px 5px 5px -5px rgba(3460800.6inset;
          }



          .
      castom-paginations__list-item--hide {
              
      displaynone;
              
      width0;
          }

          .
      castom-paginations__list-item--one-page::after {
              
      content'...';
              
      text-aligncenter;
              
      displayflex;
              
      align-itemscenter;
              
      padding0 5px 0 5px;
          }

          .
      castom-paginations__list-item--end-page::before {
              
      content'...';
              
      text-aligncenter;
              
      displayflex;
              
      align-itemsend;
              
      padding0 5px 0 0;
          }

          @
      media(max-width:768px) {
              .
      castom-paginations__list-button-page {
                  
      font-size16px;
              }
          }
      </
      style>
      - Пагинация JS
      Код:
      
      <script>
          function 
      createClassPagination (propsForPagination) {
          class 
      Pagination {
          
      constructor(props) {
              
      this.props props
          
      }

          
      addCastomSelectorStyleMod() {
              
      // кастомные стили к пагинации тегу nav
              
      this.castomSelectorPagination =
                  
      this.pagination.classList[0] + '--' this.props.castomSelectorMod
              this
      .pagination.classList.add(this.castomSelectorPagination)

              
      // кастомный селектор к тегу ul
              
      this.castomSelectorListPages =
                  
      this.paginationListPages.classList[0] + '--' this.props.castomSelectorMod
              this
      .paginationListPages.classList.add(this.castomSelectorListPages)

              
      // кастомный селектор к тегам li
              
      if (this.listItemsAll[0]) {
                  
      this.castomSelectorListItem =
                      
      this.listItemsAll[0].classList[0] + '--' this.props.castomSelectorMod

                  this
      .listItemsAll.forEach((item) => {
                      
      item.classList.add(this.castomSelectorListItem)
                  })
              }

              
      // кастомный селектор к тегам button
              
      if (this.buttonsAll[0]) {
                  
      this.castomSelectorButton =
                      
      this.buttonsAll[0].classList[0] + '--' this.props.castomSelectorMod

                  this
      .buttonsAll.forEach((button) => {
                      
      button.classList.add(this.castomSelectorButton)
                  })
              }
          }

          
      // метод отрисовывает пагинацию
          
      init() {
              if (!
      this.props.countPages || this.props.countPages === || !this.props.selectorForWrapper)
                  return

              if (!
      this.props.range || this.props.range === || this.props.range 3) {
                  
      this.props.range 5
              
      }
              
      this.pagination document.createElement('nav')
              
      this.pagination.classList.add('castom-paginations')

              
      this.paginationListPages document.createElement('ul')
              
      this.paginationListPages.classList.add('castom-paginations__list')

              
      this.pagination.append(this.paginationListPages)

              
      this.renderPages()

              
      this.wrapper document.querySelector('.' this.props.selectorForWrapper)
              
      this.wrapper.append(this.pagination)

              
      // инициализируем listItemsAll для дальнейшего использования в методах класса
              
      this.listItemsAll = Array.from(
                  
      this.pagination.querySelectorAll('.castom-paginations__list-item'),
              )

              
      // инициализируем buttonAll для дальнейшего использования в методах класса
              
      this.buttonsAll this.pagination.querySelectorAll('.castom-paginations__list-button-page')

              
      this.handlers()

              if (
      this.props.castomSelectorMod) {
                  
      this.addCastomSelectorStyleMod()
              }
          }

          
      // метод рендерит страницы при инициализации(используется в init)
          
      renderPages() {
              for (
      let i 1<= this.props.countPagesi++) {
                  
      this.listItem document.createElement('li')
                  
      this.listItem.classList.add('castom-paginations__list-item')

                  
      this.buttonPage document.createElement('button')
                  
      this.buttonPage.setAttribute('data-index'i// присваиваем data атрибут для определения страниц
                  
      this.buttonPage.classList.add('castom-paginations__list-button-page')

                  if (
      this.props.startPage && this.props.startPage === i) {
                      
      this.buttonPage.classList.add('castom-paginations__list-button-page--active')
                  }
                  if (
      this.props.range && !== this.props.countPages) {
                      
      this.listItem.classList.add('castom-paginations__list-item--hide')
                  }

                  if (
      === this.props.countPages && this.props.countPages 5) {
                      
      this.listItem.classList.add('castom-paginations__list-item--end-page')
                  }

                  
      this.buttonPage.type 'button'
                  
      this.buttonPage.textContent i

                  this
      .listItem.append(this.buttonPage)
                  
      this.paginationListPages.append(this.listItem)
              }
          }

          
      // метод сбрасывает на 1 страницу
          
      reset() {
              
      this.pagination.textContent ''
              
      this.props.startPage 1
              this
      .init()
          }

          
      //метод очищает страницы пагинации
          
      removeRenderPages() {
              
      this.listItemsAll.forEach((itemidx) => {
                  if (
      idx !== && idx !== this.props.countPages 1)
                      
      item.classList.add('castom-paginations__list-item--hide')
              })
          }

          
      // метод рендерит страницу при клике на соответстующий номер
          
      nextRanderPages() {
              if (
      this.props.countPages 2) return

              
      // Ecли клик по странице с индексом меньше, чем колличество отображемых страниц
              
      if (this.props.startPage this.props.range) {
                  
      this.removeRenderPages()
                  
      this.listItemsAll.forEach((itemidx) => {
                      if (
      idx <= this.props.range 1) {
                          
      item.classList.remove('castom-paginations__list-item--hide')
                      }
                  })
                  
      this.listItemsAll[0].classList.remove('castom-paginations__list-item--one-page')
                  if (
      this.props.countPages 5) {
                      
      this.listItemsAll[this.props.countPages 1].classList.add(
                          
      'castom-paginations__list-item--end-page',
                      )
                  }
                  return
              }

              
      // Ecли клик по последней странице или меньше последней, но болше чем кол-во страниц минус кол-во отображаемых страниц
              
      if (
                  
      this.props.startPage <= this.props.countPages &&
                  
      this.props.startPage this.props.countPages this.props.range &&
                  
      this.props.startPage !== this.props.countPages this.props.range 1
              
      ) {
                  
      this.removeRenderPages()

                  
      this.listItemsAll.forEach((itemidx) => {
                      if (
                          
      idx this.props.countPages &&
                          
      idx this.props.countPages this.props.range 1
                      
      ) {
                          
      item.classList.remove('castom-paginations__list-item--hide')
                      }
                  })
                  
      this.listItemsAll[this.props.countPages 1].classList.remove(
                      
      'castom-paginations__list-item--end-page',
                  )
                  if (
      this.props.countPages 5) {
                      
      this.listItemsAll[0].classList.add('castom-paginations__list-item--one-page')
                  }
                  return
              }

              
      // В остальных случаях при клике на страницу
              
      this.removeRenderPages()

              
      // добавить многоточие после первой и перед последней страницей
              
      if (this.props.countPages 5) {
                  
      this.listItemsAll[0].classList.add('castom-paginations__list-item--one-page')
                  
      this.listItemsAll[this.props.countPages 1].classList.add(
                      
      'castom-paginations__list-item--end-page',
                  )
              }

              for (
                  
      let i this.props.startPage;
                  
      this.props.startPage this.props.range 1;
                  
      i--
              ) {
                  
      this.listItemsAll[i].classList.remove('castom-paginations__list-item--hide')
              }

              for (
                  
      let i this.props.startPage;
                  
      this.props.startPage this.props.range 1;
                  
      i++
              ) {
                  
      this.listItemsAll[i].classList.remove('castom-paginations__list-item--hide')
              }
          }

          
      handlers() {
              
      this.pagination.addEventListener('click', (e) => {
                  if (
      e.target.closest('.castom-paginations__list-button-page')) {
                      
      this.buttonsAll.forEach((page) => {
                          if (
      page.closest('.castom-paginations__list-button-page--active')) {
                              
      page.classList.remove('castom-paginations__list-button-page--active')
                          }
                      })

                      
      e.target.classList.add('castom-paginations__list-button-page--active')
                      
      this.props.startPage = +e.target.dataset.index

                      this
      .nextRanderPages() // перерисовывает пагинацию относительно выбранной страницы
                      
      this.props.callback(this.props.startPage// вызываем callback переданный из пропсов (должен принимать номер страницы)
                  
      }
              })
          }
      }
         return new 
      Pagination(propsForPagination)
      }

      function 
      insertPagination(quantityPages,call) {
         
          if (!
      quantityPages || quantityPages === || isNaN(quantityPages)) return
         
          const 
      propsForPagination = {
              
      selectorForWrapper'castom-pagination__wrapper',
              
      countPagesquantityPages// кол-во страниц
              
      range5// сколько страниц отображать
              
      startPage1// номер активной страницы
              
      callbackcall// callback function принимает номер страницы по которой выполнен click
          
      }

          const 
      pagination createClassPagination(propsForPagination)
          
      pagination.init()

      }

      function 
      clearPagination () {
        
      document.querySelector('.castom-pagination__wrapper').textContent ''
      }

      </script>
      - ВКод Отрисовать пагинацию
      Код:
      
      <script>
          <%= 
      Scripts %>.initTable()  
      </script>
    5. В контекст формы добавим переменную с номером последней строки, которую будем получать динамически и скрывать последнюю строку таблицы стилями[​IMG] Так выглядит ВКод со стилями который скроет последнюю строку таблицы
      Код:
      
        
      <style>
          .
      target-table elma-type-table-full-line[data-index="<%= ViewContext.data.last_line_number %>"] {
              
      displaynone;
          }
      </
      style>

    6. Клиентский сценарий
      Код:
      
      /* Client scripts module */
      declare const consoleany
      declare const insertPaginationany
      declare const clearPaginationany

      const quantityItemsInTable 20 // Кол-во элементов таблицы на 1 странице


      // Что бы уйти от различных setTimeout initTable вызывается в ВКод - е
      async function initTable() {
          if (!
      Context.data.json_table || !Context.data.json_table.length) return
          const 
      quantityPages Math.ceil(Context.data.json_table.length quantityItemsInTable// Кол-во страниц для пагинации
          
      insertPagination(quantityPageschangePage// Отрисовать пагинацию
          
      fillTable(1// Заполнить таблицу
          
      setLastLineNumber() // Установить номер последней строки таблицы
      }


      // callback который передается в инстанс пагинации для листания по страницам
      async function changePage(numPagenumber) {
          
      saveChangesToJsonTable() // Сохранить данные в таблице хранилище

          
      Context.data.table Context.fields.table.create()  // Создать заного новый экземпляр таблицы

          
      fillTable(numPage// Заполнить таблицу
          
      setLastLineNumber() // Установить номер последней строки таблицы (в ВКод будем скрывать ее)
      }

      function 
      setLastLineNumber() {
          if (!
      Context.data.table || !Context.data.table.length) return
          
      ViewContext.data.last_line_number Context.data.table.length 1
      }

      async function saveChangesToJsonTable() {
          if (!
      Context.data.table || !Context.data.table.length || !Context.data.json_table || !Context.data.json_table.length) return
          for (
      let i 0Context.data.table.lengthi++) {
              const 
      findRowToJasonTable Context.data.json_table!.find(row => row.uuid === Context.data.table![i].uuid)

              if (!
      findRowToJasonTable) continue
             
              
      findRowToJasonTable.name Context.data.table![i].name
              findRowToJasonTable
      .user Context.data.table![i].user
              findRowToJasonTable
      .users Context.data.table![i].users
          
      }
          
      Context.data.json_table Context.data.json_table
      }

      // Функция заполняет таблицу данными
      function fillTable(pagenumber) {
          if (!
      Context.data.json_table || !Context.data.json_table.length) return

          const 
      startRange page quantityItemsInTable quantityItemsInTable // C какого кол-ва элементов отрисовать таблицу
          
      const endRange page quantityItemsInTable // По какое кол-во элементов отрисовать таблицу

          
      for (let i startRangeendRangei++) {
              if (
      === Context.data.json_table.length) break
              const 
      row Context.data.table!.insert();
              
      /*
               Тут не стал писать проверку на то что изменились данные или же нет, на мой взгляд проверка может оказаться трудозатратнее чем без нее,
               через JSON.stringify(data) === JSON.stringify(newData) считаю не надежным, при смене ключей в объекте местами это не сработает.
              */
              
      row.uuid Context.data.json_table[i].uuid
              row
      .name Context.data.json_table[i].name
              row
      .user Context.data.json_table[i].user
              row
      .users Context.data.json_table[i].users

          
      }

          
      Context.data.table Context.data.table
      }
    Результат:
    [​IMG]
    Вывод:

    • Написан простой вариант редактируемой таблицы с пагинацией без обработчиков событий и различных костылей в виде "setTimeout"
    • Таблица почти с тысячей элементов разбита постранично и не забивает так сильно стэк задачами рендера, что дает нормальную загрузку формы.
    Последнее редактирование: 3 май 2024