Вопрос или проблема
Я работаю над документами, которые могут занимать много страниц с множеством различных переменных форматирования. Большая часть работы выполняется в глубоко вложенных тегах 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 требовала внимательного подхода, но с правильным алгоритмом вы сможете эффективно управлять отображением содержимого без ущерба для стиля и функциональности.
Будьте внимательны к деталям и тестируйте обновления на разных устройствах и браузерах, чтобы гарантировать стабильную работу вашей системы.