...

Ленивая загрузка данных в виджете Код

Тема в разделе "Примеры решений и дополнительных модулей", создана пользователем f.nikolaev, 4 апр 2022.

  1. f.nikolaev

    f.nikolaev Участник

    Если вы используете Виджет Код для получения и отображения большого количества однотипной информации (список файлов, приложений и т.п.), то данный процесс может занять значительное время.

    Например, получение информации о более чем 200 экземпляров приложений и их отображение занимало у меня не меньше 2 минут.

    В этом случае удобно загрузить лишь часть данных, отобразить их и сразу же запустить скрипт получения новой порции данных в “фоновом режиме”. Для этого необходимо:

    • реализовать метод рендера из массива данных,
    • реализовать метод получения части данных с возможностью дозагрузки данных,

    • реализовать слушатель события scroll, который будет вызывать отрисовку и загрузку новой порции данных.
    Результат можно увидеть ниже:

    [​IMG]


    Метод рендера.

    Для реализации метода рендера рекомендую использовать тег template в Вашем HTML. В него помещается верстка элемента отображения карточки или строки в списке.

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


    HTML:
    HTML:
    <div class="your-content">
        <h3 class="your-content__title">Список документов</h3>
        <ul class="your-content__list">
         </ul>
        </div>
    </div>
    <div class="loader-wrapper">
      <div class="loader-img"></div>
    </div>
    <template class="your-list-item-template">
      <li class="your-list-item">
        <p class="your-list-item__file-name"></p>
      </li>
    </template>
    
    JS:
    Код:
    
    async function renderListItem(appsArray){
            const 
    filesWrapper document.querySelector('.your-content__list');
            const 
    itemTemplate document.querySelector('.your-list-item-template');
            if(
    appsArray){
                
    // показываем лоадер
                
    loader.classList.add('loader-wrapper_active');

                for (
    let i 0appsArray.lengthi++){
                    
    let appApplicationItem<...>;
                    try{
                        
    app await appsArray[i].fetch();
                    }
                    catch(
    err){
                        
    console.log(err)
                    }
                    if(
    app){
                        const 
    itemElement itemTemplate.content.cloneNode(true);
                        const 
    fileNameElement itemElement.querySelector('.your-list-item__file-name');
                        
    fileNameElement.textContent app.data.__name;
                        
    filesWrapper.append(itemElement)
                    }
                }

                
    // скрываем лоадер
                
    loader.classList.remove('loader-wrapper_active');
        }
    }
    Где appsArray - массив ссылок на экземпляры приложений.

    В верстке используется лоадер. Он будет отображаться в момент загрузки данных. Стили для него приведены ниже:
    HTML:
    <style>
    .loader-wrapper {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(0, 0, 0, 0.2);
      display: flex;
      opacity: 0;
      visibility: hidden;
      justify-content: center;
      align-items: center;
      transition: 0.5s;
    }
    .loader-wrapper_active{
      opacity: 1;
      visibility: visible;
    }
    .loader-img {
      width: 200px;
      height: 200px;
      background: url(data:image/svg+xml;charset=utf-8,%3Csvg%20version%3D%221.1%22%20id%3D%22L9%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%0A%20%20viewBox%3D%220%200%20100%20100%22%20enable-background%3D%22new%200%200%200%200%22%20xml%3Aspace%3D%22preserve%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23fff%22%20d%3D%22M73%2C50c0-12.7-10.3-23-23-23S27%2C37.3%2C27%2C50%20M30.9%2C50c0-10.5%2C8.5-19.1%2C19.1-19.1S69.1%2C39.5%2C69.1%2C50%22%3E%0A%20%20%20%20%20%20%3CanimateTransform%20%0A%20%20%20%20%20%20%20%20%20attributeName%3D%22transform%22%20%0A%20%20%20%20%20%20%20%20%20attributeType%3D%22XML%22%20%0A%20%20%20%20%20%20%20%20%20type%3D%22rotate%22%0A%20%20%20%20%20%20%20%20%20dur%3D%221s%22%20%0A%20%20%20%20%20%20%20%20%20from%3D%220%2050%2050%22%0A%20%20%20%20%20%20%20%20%20to%3D%22360%2050%2050%22%20%0A%20%20%20%20%20%20%20%20%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fpath%3E%0A%3C%2Fsvg%3E) no-repeat center / contain;
    }
    </style>
    

    Метод получения данных частями.

    В Виджете, если поиск приложений будет осуществляться из серверного сценария, Вам потребуется создать числовую контекстную переменную с текущим номером итерации (Context.data.iteration_number) и значением по умолчанию равным 0. В случае с клиентским сценарием можно обойтись глобальной переменной.

    Также необходимо создать переменную, обозначающую, все ли данные загружены (Context.data.all_documents_uploaded) со значением false по умолчанию.

    Метод может быть реализован следующим образом:

    Код:
    
    const numberOfUnloadedElementsnumber 20// количество получаемых экземпляров приложений
    let countOfIterationsnumber// общее количество возможных итераций
    let firstElementNumbernumber// номер экземпляра приложения, с которого будет происходить поиск
    let numberOfAllElementsnumber 0//общее количество элементов
    let appsArray ApplicationItem<...>[] = []; //массив для найденных элементов
    async function findAppsArray(): Promise<void> {
        try{
            
    // на каждой итерации обнуляем текущий массив с данными
            
    appsArray = [];

            
    //поиск по интересующему нас приложению
            
    let docsSearch Context.fields.application_kedo_documents.app.search();

            
    // получаем общее количество элементов
            
    if(!numberOfAllElements){
                
    numberOfAllElements await docsSearch.count();
            }

            
    // получаем максимальное число итераций
            
    if(!countOfIterations){
                
    countOfIterations Math.ceil(numberOfAllElements numberOfUnloadedElements);
            }
            
    // расчитываем, с какого элемента начать поиск в зависимости от номера итерации
            
    firstElementNumber Context.data.iteration_number! * numberOfUnloadedElements;
            
    // расчитваем количество получаемых элементов, на последней итерации он может быть меньше numberOfUnloadedElements
            
    const amountOfElements = (Context.data.iteration_number != countOfIterations 1) ? numberOfUnloadedElements : (numberOfAllElements firstElementNumber);
          
    // если это последняя итерация, присваиваем значение соответствующей контекстной переменной
            
    if(Context.data.iteration_number === countOfIterations){
                
    Context.data.all_documents_uploaded true
            
    }

            try {
                
    // поиск amountOfElements элементов начиная с firstElementNumber элемента
                
    let resultApplicationItem<...>[] = [];
                
    result await docsSearch
                    
    .size(amountOfElements)
                    .
    from(firstElementNumber)
                    .
    all()

                
    // сохраняем результат
                
    if(result){
                    
    appsArray.push(...result);
                    
    Context.data.iteration_number! += 1;
                }
            } catch (
    err) {
                throw new 
    Error(err);
            };
        }
        catch(
    err){
            throw new 
    Error(err);
        }
    }

    Слушатель события scroll.

    ELMA365 - single page application. Контейнер с нашими файлами будет располагаться внутри контейнера ELMA365 с классом content-body. Прокручиваться при скролле будет именной этот контейнер. Использовать window.addEventListener не получиться.

    Реализуем слушатель и его метод:

    Код:
    
    let contentBody document.querySelector('.content-body'); // стандартный контейнер ELMA365
    let kedoDocsContainer document.querySelector('.your-content'); // контейнер с нашим содержимым
    let documentsBottomPointnumber 0// нижняя точка контейнера
    let timeOutany// переменная для id таймаута
    const loader document.querySelector('.loader-wrapper'); // элемент лоадера
    async function handleWindowScroll(){
        
    // очищаем таймаут
        
    window.clearTimeout(timeOut);
        
    // для того, чтобы обработчик срабатывал только по истечении 300мс после окончания скролла, выставляем таймаут
        // это снизит нагрузку с системы
        
    timeOut window.setTimeout(async() => {
            
    // расчитываем текущую нижнюю границу контейнера с файлам
            
    documentsBottomPoint kedoDocsContainer.offsetTop kedoDocsContainer.offsetHeight;

            
    // если мы достигли нижней границе при скролле, вызываем методы получения новой порции данных и их рендера используя написанные нами методы
            
    if(documentsBottomPoint contentBody.scrollTop window.innerHeight 0){
                
    // отображаем полученные ранее данные
                
    await renderListItem(appsArray);
                
    // если это не последняя итерация, загружаем новые данные
                
    if(!Context.data.all_documents_uploaded){
                    
    await findAppsArray();
                }
                
    // если это последняя итерация, "обнуляем" массив данных
                // новых рендеров не будет
                
    if(Context.data.all_documents_uploaded){
                    
    appsArray = [];
                }

            }
        }, 
    300)
    }
    contentBody.addEventListener('scroll'handleWindowScroll);

    Инициализация страницы.

    При инициализации виджета Вам необходимо вначале получить первую часть данных вызвав метод findAppsArray(), отобразить ее методом renderListItem() и заново вызвать findAppsArray() для получения следующей части данных.

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


    Если у Вас есть замечания или пожелания по статье, буду рад их прочитать: f.nikolaev@bpm-cons.ru.
    Последнее редактирование: 5 апр 2022
  2. kurbatov-la

    kurbatov-la Активный участник

    Как сделать, чтобы загрузка новых элементов происходила немного раньше?
    Например, если в буфере осталось 20 элементов и пользователь при скролле приближается к окончанию буфера.

    Т.е. идея сделать так, чтобы подгрузка новых элементов была более незаметной для пользователя.
    Последнее редактирование: 6 апр 2022
  3. ava_var

    ava_var Активный участник

    Код:
    
    if(documentsBottomPoint contentBody.scrollTop window.innerHeight 0){
    
    
    Попробуйте поэкспериментировать вот с этим условием.
    Как я понимаю - здесь идет проверка сколько пикселей до конца скролла осталось
  4. a.sysoev

    a.sysoev Новичок

    Подгрузите не 20 элементов, а 40. При скроле отобразите 20 и загрузите ещё 20.
    Таким образом, у вас будет всегда 20 в буфере для отображения, пока подгружаются следующие 20.