Разделение одной длинной страницы на несколько страниц

Вопрос или проблема

Я работаю над документами, которые могут занимать много страниц с множеством различных переменных форматирования. Большая часть работы выполняется в глубоко вложенных тегах OL и таблицах. Некоторые теги ol используют классы для разделения между сеткой 2 (1fr 1fr), в то время как другие остаются на блочном уровне. Мне нужна логика, которая может разбивать страницы без переполнения, сохраняя указанный нижний колонтитул. Это осуществляется с использованием Angular.

Примечание: HTML обернут в компонент и передает номер ID и текст прав (отказ от ответственности), который используется в качестве постоянного нижнего колонтитула на страницах.

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

import {
    Component,
    AfterViewInit,
    ElementRef,
    ViewChild,
    Input,
} from "@angular/core";

@Component({
    selector: "app-dynamic-form-page",
    templateUrl: "./dynamic-form-page.component.html",
    styleUrls: [
        "/src/assets/styles/forms.scss",
        "./dynamic-form-page.component.scss",
    ],
})
export class DynamicFormPageComponent implements AfterViewInit {
    @Input() formId: string = "";
    @Input() rightsText: string = ""; // Для нижних колонтитулов
    @ViewChild("fullContent", { static: false }) fullContent: ElementRef;

    constructor() {}
    pages: string[] = []; // Массив для хранения страниц с содержимым
    readonly totalPageHeight = 1056;
    readonly pagePadding = 192;
    readonly pageHeight = this.totalPageHeight - this.pagePadding;

    ngAfterViewInit() {
        this.paginateContent(); // Вызов логики пагинации после инициализации представления
    }

    paginateContent() {
        const contentElement = this.fullContent.nativeElement; // Содержимое для пагинации
        let currentPageContent = ""; // Хранить содержимое для каждой страницы
        let currentHeight = 0; // Отслеживать высоту текущей страницы
        const parentElement = document.createElement("div"); // Смоделированная страница
        parentElement.style.width = "8.5in";
        parentElement.style.height = "11in";
        parentElement.style.position = "absolute"; // Элемент вне экрана
        parentElement.style.visibility = "hidden";
        document.body.appendChild(parentElement); // Добавить вне экрана для расчетов высоты
        let tempContainer = document.createElement("div");
        tempContainer.innerHTML = contentElement.innerHTML; // Загрузить все содержимое во временный контейнер
        while (tempContainer.firstChild) {
            const nextElement = tempContainer.firstChild;
            // Обрабатываем только элементы
            if (nextElement.nodeType === 1) {
                const clonedElement = nextElement.cloneNode(true) as HTMLElement;
                parentElement.appendChild(clonedElement); // Добавить клон для моделирования страницы
                const elementHeight = clonedElement.offsetHeight; // Получить высоту текущего элемента
                console.log(
                    "Текущая сущность:",
                    clonedElement.outerHTML.slice(0, 100),
                    "Высота:",
                    elementHeight,
                );
                // Обработать большие элементы: если элемент слишком велик для страницы, перенести его на следующую страницу
                if (elementHeight > this.pageHeight) {
                    console.log("Обнаружен большой элемент. Перемещение на следующую страницу.");
                    this.pages.push(`${currentPageContent}`); // Добавить содержимое текущей страницы
                    currentPageContent = ""; // Начать заново для следующей страницы
                    parentElement.innerHTML = ""; // Очистить смоделированный контейнер
                    currentHeight = 0; // Сбросить высоту
                    currentPageContent += (nextElement as HTMLElement).outerHTML; // Начать следующую страницу с этого элемента
                    currentHeight = elementHeight; // Установить высоту новой страницы
                    tempContainer.removeChild(nextElement); // Удалить обработанный элемент
                    continue;
                }
                // Если добавление этого элемента превышает высоту страницы, создать новую страницу
                if (currentHeight + elementHeight > this.pageHeight) {
                    this.pages.push(`${currentPageContent}`); // Сохранить содержимое для этой страницы
                    console.log(
                        `Страница ${this.pages.length + 1} создана. Длина содержимого: ${
                            currentPageContent.length
                        }`,
                    );
                    currentPageContent = ""; // Начать заново для следующей страницы
                    parentElement.innerHTML = ""; // Очистить контейнер смоделированной страницы
                    currentHeight = 0; // Сбросить высоту для новой страницы
                }
                // Аккумулировать outerHTML текущего элемента в содержание страницы
                currentPageContent += (nextElement as HTMLElement).outerHTML;
                currentHeight += elementHeight; // Обновить текущую высоту
            }
            // Удалить обработанный элемент из tempContainer
            tempContainer.removeChild(nextElement); // Очистить оригинальное содержимое
        }
        // Убедиться, что последняя страница добавлена, даже если это не полноценная страница
        if (currentPageContent.trim().length > 0) {
            this.pages.push(`${currentPageContent}`); // Добавить последнюю страницу
            console.log(`Последняя страница создана. Всего страниц: ${this.pages.length}`);
        }
        // Удалить смоделированный элемент из DOM
        document.body.removeChild(parentElement);
        // Добавить нижние колонтитулы ко всем страницам
        const totalPages = this.pages.length;
        this.pages = this.pages.map((pageContent, index) =>
            this.addFooter(pageContent, index, totalPages),
        );
    }

    addFooter(content: string, pageIndex: number, totalPages: number): string {
        return `
      
${content}
`; } }

Проблема с этим, хотя стилизация для двойного списка остается нетронутой, заключается в том, что вложенные элементы ol вытекают за пределы страницы. Я пытался разделить вложенные элементы ol>li на отдельные элементы, но это убрало необходимую стилизацию и все еще создало некоторое переполнение на страницах.

Ответ или решение

Разделение длинной страницы на несколько страниц в Angular

Ваша задача по разделению длинной страницы на несколько страниц с использованием Angular — это вызов, особенно когда ваши документы содержат сложные вложенные теги <ol> и таблицы. В этой статье мы подробно рассмотрим, как правильно реализовать эту функциональность, учитывая все аспекты форматирования и визуального представления вашего контента.

1. Понимание структуры документа

Для начала важно осознать, что ваше содержимое может включать вложенные списки и разные классы CSS, которые влияют на отображение. Убедитесь, что HTML-код правильно структурирован и что вы можете извлекать нужные данные.

2. Логика пагинации

Ваш текущий код демонстрирует основную логику, которая включает создание виртуального контейнера для элементов контента. Это позволяет вычислять размер каждого элемента и управлять количеством содержимого, которое помещается на каждой странице. Однако наличие вложенных списков может усложнить расчёты, что приводит к переполнению страниц.

Вот несколько рекомендаций по доработке вашего метода paginateContent:

  • Корректная обработка вложенных элементов: Перед тем как клонить элемент и добавлять его в родительский элемент для расчётов, важно правильно обрабатывать глубоко вложенные элементы <ol>. Вы можете использовать рекурсию для обхода этих элементов, обеспечивая корректное добавление высоты каждого элемента.

  • Используйте getBoundingClientRect: Вместо использования offsetHeight попробуйте использовать метод getBoundingClientRect() для получения размеров элементов. Это может улучшить точность разметки.

const elementHeight = clonedElement.getBoundingClientRect().height; 
  • Разделение длинных списков: Если элемент <ol> превышает лимит страницы, подумайте над логикой, которая будет разбивать его на подпункты и обрабатывать их по одному. Это может помочь сохранить стили и избежать переполнения.

3. Пример улучшенного кода пагинации

Ниже представлен упрощённый вариант вашего метода paginateContent, который включает улучшенную логику обработки вложенных элементов:

paginateContent() {
    const contentElement = this.fullContent.nativeElement;
    let currentPageContent = "";
    let currentHeight = 0;
    const parentElement = this.createOffscreenDiv();

    let tempContainer = document.createElement("div");
    tempContainer.innerHTML = contentElement.innerHTML;

    while (tempContainer.firstChild) {
        const nextElement = tempContainer.firstChild;
        if (nextElement.nodeType === 1) {
            const clonedElement = nextElement.cloneNode(true) as HTMLElement;
            parentElement.appendChild(clonedElement);

            const elementHeight = clonedElement.getBoundingClientRect().height;

            if (elementHeight > this.pageHeight) {
                this.pages.push(currentPageContent);
                currentPageContent = clonedElement.outerHTML;
                currentHeight = elementHeight;
            } else if (currentHeight + elementHeight > this.pageHeight) {
                this.pages.push(currentPageContent);
                currentPageContent = clonedElement.outerHTML;
                currentHeight = elementHeight;
            } else {
                currentPageContent += clonedElement.outerHTML;
                currentHeight += elementHeight;
            }
            tempContainer.removeChild(nextElement);
        }
    }
    if (currentPageContent.trim().length > 0) {
        this.pages.push(currentPageContent);
    }
    document.body.removeChild(parentElement);
    this.addFooterToPages();
}

createOffscreenDiv() {
    const div = document.createElement("div");
    div.style.width = "8.5in";
    div.style.height = "11in";
    div.style.position = "absolute";
    div.style.visibility = "hidden";
    document.body.appendChild(div);
    return div;
}

addFooterToPages() {
    const totalPages = this.pages.length;
    this.pages = this.pages.map((pageContent, index) =>
        this.addFooter(pageContent, index, totalPages)
    );
}

4. Добавление подвала

Ваш метод добавления подвала addFooter уже хорошо структурирован. Убедитесь, что вы правильно добавляете номер страницы и текст для прав на каждом листе.

Заключение

Применив данные рекомендации, вы улучшите работу вашего приложения с длинными страницами. Пагинация с учётом сложных конструкций HTML требовала внимательного подхода, но с правильным алгоритмом вы сможете эффективно управлять отображением содержимого без ущерба для стиля и функциональности.

Будьте внимательны к деталям и тестируйте обновления на разных устройствах и браузерах, чтобы гарантировать стабильную работу вашей системы.

Оцените материал
Добавить комментарий

Капча загружается...