...

Пагинация с алфавитным указателем

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

  1. kirillovykh

    kirillovykh Участник

    Бизнес кейс
    Реализовать возможность выбора Контактов по первой букве фамилии, кликнув на определенную букву в отображаемом алфавите.
    Вкладка с контактами.
    [​IMG]

    Постановка
    Форма просмотра Компании:
    • На вкладке Контакты фильтрация контактов компании по ФИО, Телефон, Статус и Почта.
    • Для отфильтрованных записей постраничная навигация и алфавитный указатель по первой букве фамилии.
    • Контакты в статусе Архив отображать внизу списка.

    Решение
    Решения с пагинацией уже были https://community.elma365.com/ru/threads/3116/.
    В данном случае к пагинации добавлена логика с алфавитным указателем.

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

    На кнопках нумератора страниц вызов метода changeContactsPage с формированием таблицы контактов для текущей буквы алфавитного указателя.
    На кнопках алфавитного указателя вызов метода changeContactsAlphabetLetter с формированием таблицы контактов для первой страницы по выбранной букве.

    Листинг нумератора и указателя
    Код:
    
    <style>
        .
    pagination-container {
            
    displayflex;
            
    width100%;
            
    flex-directionrow;
            
    flex-wrapnowrap;
            
    gap0.3em;
            
    justify-contentcenter;
            
    padding1em;
        }
    </
    style>

    <% if (
    ViewContext.data.contact_pages_count && ViewContext.data.contact_current_page_number) { %>
        <
    div class="pagination-container">
            <% if (
    ViewContext.data.contact_pages_count 5) { %>
                <
    button type="button" class="btn btn-default" onclick="<%=Scripts%>.changeContactsPage(1)">&laquo&laquo</button>
                <
    button type="button" class="btn btn-default" onclick="<%=Scripts%>.changeContactsPage(<%=Math.max(...[1, ViewContext.data.contact_current_page_number - 1])%>)">&laquo</button>
            <% } %>

            <% for (
    let i Math.max(...[1ViewContext.data.contact_current_page_number 2]); <= Math.min(...[ViewContext.data.contact_pages_countViewContext.data.contact_current_page_number 2]); i++) { %>
                <% if (
    == ViewContext.data.contact_current_page_number) { %>
                    <
    button type="button" class="btn btn-primary"><%=i.toString()%></button>
                <% } else { %>
                    <
    button type="button" class="btn btn-default" onclick="<%=Scripts%>.changeContactsPage(<%=i%>)"><%=i.toString()%></button>
                <% } %>
            <% } %>
            <% if (
    ViewContext.data.contact_pages_count 5) { %>
                <
    button type="button" class="btn btn-default" onclick="<%=Scripts%>.changeContactsPage(<%=Math.min(...[ViewContext.data.contact_current_page_number + 1, ViewContext.data.contact_pages_count])%>)">&raquo</button>
                <
    button type="button" class="btn btn-default" onclick="<%=Scripts%>.changeContactsPage(<%=ViewContext.data.contact_pages_count%>)">&raquo&raquo</button>
            <% } %>
        </
    div>
    <% } %>

    Код:
    
    <style>
        .
    alphabetic-container {
            
    width100%;
            
    displayflex;
            
    flex-directionrow;
            
    flex-wrapnowrap;
            
    justify-contentspace-evenly;
            
    align-itemsbaseline;
            
    padding1em;
        }
        .
    alphabetic-element {
            
    color: var(--color-theme-main);
            
    padding0.5rem;
            
    cursorpointer;
            
    user-selectnone;
        }
        .
    alphabetic-element:hover {
            
    color: var(--color-theme-main-darken-10);
        }
        .
    alphabetic-element.active {
            
    color: var(--color-theme-main-darken-10);
            
    font-size1.5em;
            
    cursor: default;
        }
    </
    style>


    <% if (
    ViewContext.data.contact_alphabetical_index?.length && ViewContext.data.contact_current_alphabet_letter) { %>
        <
    div class="alphabetic-container">
            <% for (
    let el of ViewContext.data.contact_alphabetical_index) { %>
                <% if (
    el == ViewContext.data.contact_current_alphabet_letter) { %>
                    <
    span class="alphabetic-element active"><%=el%></span>
                <% } else {%>
                    <
    span class="alphabetic-element" onclick="<%=Scripts%>.changeContactsAlphabetLetter('<%=el%>')"><%=el%></span>
                <% } %>
            <% } %>
        </
    div>
    <% } %>
    Получение данных по компании выполняется асинхронно при инициализации формы просмотра в методе onInit, в том числе и данные по Контактам.
    При получении контактов по компании используется сортировка по статусу контакта, а затем группировка полученного массива по первой букве фамилии.

    После получения данных выполняется заполнение вкладки Контакты (чтобы не перетирать ViewContext при серверных вызовах).
    При заполнении выполняется сортировка ключей сгруппированных контактов для формирования данных алфавитного указателя и вызов метода changeContactsAlphabetLetter для первой буквы.
    При нажатии на кнопку Поиск выполняется фильтрация контактов компании по заданным фильтрам, а затем группировка отфильтрованных контактов по первой букве фамилии с формированием данных алфавитного указателя и вызовом метода changeContactsAlphabetLetter для заданной буквы.

    Листинг клиента
    Код:
    
    /**
    * Контакты по первой букве фамилии для фильтрации на форме
    */
    type ContactsByLetters = {
        [
    keystring]: ReturnType<typeof Application.fields.contacts.app.create>[],
    };


    let contacts_by_companyReturnType<typeof Application.fields.contacts.app.create>[] = [];
    let contacts_by_lettersContactsByLetters = {};
    let rows_max_count 8;


    /**
    * Получение Контактов
    */
    async function getContacts(): Promise<void> {
        
    contacts_by_company await Application.fields.contacts.app.search()
            .
    where((fg) => g.and(
                
    f.__deletedAt.eq(null),
                
    f.companies.has(Context.id)
            ))
            .
    sort('__status'true)
            .
    size(10000)
            .
    all();

        
    contacts_by_letters groupByLetter(contacts_by_company'_fullname');
    }

    /**
    * Заполнение вкладки Контакты
    *
    * @remarks вызов после получения контактов
    */
    async function setContactsTab(): Promise<void> {
        
    ViewContext.data.contact_alphabetical_index Object.keys(contacts_by_letters).sort();

        
    changeContactsAlphabetLetter(ViewContext.data.contact_alphabetical_index[0]);
    }

    function 
    groupByLetter(itemsany[], keystring): { [keystring]: ReturnType<typeof Application.fields.contacts.app.create>[] } {
        return 
    items.reduce(function (resultitem) {
            const 
    result_key item.data[key]?.lastname[0].toUpperCase() ?? '~';
            if (!
    result[result_key]) {
                
    result[result_key] = [];
            }
            
    result[result_key].push(item);
            return 
    result;
        }, {});
    }

    function 
    changeContactsAlphabetLetter(alphabet_letterstring) {
        const 
    contacts_by_letter contacts_by_letters[alphabet_letter] ?? [];

        
    ViewContext.data.contact_pages_count Math.ceil(contacts_by_letter.length rows_max_count);
        
    ViewContext.data.contact_current_alphabet_letter alphabet_letter;

        
    createContactsTable(1contacts_by_letter);

        
    ViewContext.data.contact_current_page_number 1;
    }

    function 
    changeContactsPage(page_numbernumber) {
        
    createContactsTable(page_numbercontacts_by_letters[ViewContext.data.contact_current_alphabet_letter ?? '~']);

        
    ViewContext.data.contact_current_page_number page_number;
    }

    function 
    createContactsTable(page_numbernumbercontactsReturnType<typeof Application.fields.contacts.app.create>[]) {
        const 
    contacts_table ViewContext.fields.contacts_table.create();

        for (
    let contact of contacts.slice((page_number 1) * rows_max_countpage_number rows_max_count)) {
            const 
    row contacts_table.insert();

            
    row.contact contact;
            
    row.phones contact.data._phone ?? [];
            
    row.emails contact.data.emails ?? [];
            
    row.status_name contact.data.__status?.name ?? '';
        }

        
    ViewContext.data.contacts_table contacts_table;
    }

    /**
    * При нажатии на кнопку Поиск на вкладке Контакты
    */
    async function onButtonSearchContacts(): Promise<void> {
        const 
    contacts contacts_by_company.filter(function (item) {
            const 
    fullname_search ViewContext.data.contact_fullname_searchstring?.split(/\s+/gm)
                .
    filter(Boolean)
                .
    map(=> x.toLowerCase());

            if (
    fullname_search && (!item.data._fullname ||
                !
    fullname_search.every(=> item.data._fullname?.firstname.toLowerCase().includes(x) || item.data._fullname?.lastname.toLowerCase().includes(x) || item.data._fullname?.middlename.toLowerCase().includes(x)))) {
                return 
    false;
            }

            if (
    ViewContext.data.contact_phone_searchstring && !item.data._phone?.some(=> x.tel.includes(ViewContext.data.contact_phone_searchstring!))) {
                return 
    false;
            }

            if (
    ViewContext.data.contact_email_searchstring && !item.data.emails?.some(=> x.email.includes(ViewContext.data.contact_email_searchstring!))) {
                return 
    false;
            }

            if (
    ViewContext.data.contact_status && item.data.__status?.code !== ViewContext.data.contact_status.code) {
                return 
    false;
            }

            return 
    true;
        });

        
    contacts_by_letters groupByLetter(contacts'_fullname');

        
    ViewContext.data.contact_alphabetical_index Object.keys(contacts_by_letters).sort();

        
    changeContactsAlphabetLetter(ViewContext.data.contact_alphabetical_index.find((xstring) => === ViewContext.data.contact_current_alphabet_letter) ?? ViewContext.data.contact_alphabetical_index[0]);
    }

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

    Код:
    
    let contacts_by_filterReturnType<typeof Application.fields.contacts.app.create>[] = [];


    /**
    * Получение Контактов
    */
    async function getContacts(): Promise<void> {
        
    contacts_by_company await Application.fields.contacts.app.search()
            .
    where((fg) => g.and(
                
    f.__deletedAt.eq(null),
                
    f.companies.has(Context.id)
            ))
            .
    sort('__status'true)
            .
    size(10000)
            .
    all();

        
    contacts_by_filter contacts_by_company;
        ...
    }

    function 
    changeContactsAlphabetLetter(alphabet_letter?: string) {
        const 
    contacts_by_letter contacts_by_letters[alphabet_letter] ?? contacts_by_filter;
        ...
    }

    /**
    * При нажатии на кнопку Поиск на вкладке Контакты
    */
    async function onButtonSearchContacts(): Promise<void> {
        
    contacts_by_filter contacts_by_company.filter(function (item) {
            ...
        });

        
    contacts_by_letters groupByLetter(contacts_by_filter'_fullname');
        ...
    }

    Последнее редактирование: 28 май 2024