Если вы используете Виджет Код для получения и отображения большого количества однотипной информации (список файлов, приложений и т.п.), то данный процесс может занять значительное время.
Например, получение информации о более чем 200 экземпляров приложений и их отображение занимало у меня не меньше 2 минут.
В этом случае удобно загрузить лишь часть данных, отобразить их и сразу же запустить скрипт получения новой порции данных в “фоновом режиме”. Для этого необходимо:
- реализовать метод рендера из массива данных,
- реализовать метод получения части данных с возможностью дозагрузки данных,
- реализовать слушатель события scroll, который будет вызывать отрисовку и загрузку новой порции данных.
Результат можно увидеть ниже:
Метод рендера.
Для реализации метода рендера рекомендую использовать тег 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 = 0; i < appsArray.length; i++){
let app: ApplicationItem<...>;
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 numberOfUnloadedElements: number = 20; // количество получаемых экземпляров приложений
let countOfIterations: number; // общее количество возможных итераций
let firstElementNumber: number; // номер экземпляра приложения, с которого будет происходить поиск
let numberOfAllElements: number = 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 result: ApplicationItem<...>[] = [];
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 documentsBottomPoint: number = 0; // нижняя точка контейнера
let timeOut: any; // переменная для 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.