@quantumart/qa-engine-page-structure
v0.1.1
Published
**@quantumart/qa-engine-page-structure**
Downloads
3
Keywords
Readme
@quantumart/qa-engine-page-structure
Данная библиотека предоставляет набор инструментов для построения фронтенда для сайта на базе QP
Demo site
Описание
Необходимый бэкенд для построения сайта:
- База данных на основе qp
- API на .net core с методом получения структуры сайта (дерево UniversalAbstractItem)
Типы данных
interface UniversalAbstractItem {
readonly type: string;
readonly id: number;
readonly alias?: string;
readonly title?: string;
isPage: boolean;
readonly sortOrder: number;
readonly regionIds?: number[];
readonly cultureId?: number;
childItems?: UniversalAbstractItem[];
readonly untypedFields: { [key: string]: any };
}
Интерфейс UniversalAbstractItem соответствующий UniversalAbstractItem из бэкенд апи. Описывает элемент структуры сайта (виджет или страницу).
class AbstractItem
Для дальнейшей работы со структурой сайта надо сконвертировать элементы структуры сайта в экземпляры классов AbstractItem (или их наследников). Класс AbstractItem предоставляет возможность подниматься по дереву (есть свойство parent), предоставляет свойства по получению полей из untypedFields, а также свойство для получения относительного пути к странице.
interface BaseItemModel {
readonly type: string;
readonly id: number;
readonly title: string;
readonly alias: string;
readonly sortOrder: number;
readonly regionIds?: number[];
readonly cultureId?: number;
readonly isPage: boolean;
}
interface BaseWidgetModel extends BaseItemModel {
zoneName: string;
children?: BaseWidgetModel[];
}
interface BasePageModel extends BaseItemModel {
widgets?: BaseWidgetModel[];
}
Интерфейсы BaseItemModel, BaseWidgetModel и BasePageModel используются уже непосрественно для компонентов вашего сайта.
Функции
Использование
Для построения страницы с использованием библиотеки нужно сначала получить структуру сайта (дерево элементов, соответствующих интерфейсу UniversalAbstractItem
), сконвертировать его в дерево экземпляров класса AbstractItem
(или его наследников).
Далее нужно определить стартовую страницу по хосту.
Далее можно найти Path (содержит AbstractItem и остаточный путь) соответствующий текущему пути, при этом можно использовать TargetingFilter для структуры сайта.
Из полученного Path можно уже построить модель данных для страницы.
import {
AbstractItem,
DefaultWidgetFilter,
findPath,
getPageStructureContextScript,
getStartPage,
ServerPageStructureContextProvider,
} from '@quantumart/qa-engine-page-structure';
// ....
const rawSiteStructure = await apiService.getSiteStructure();
const siteStructure = new AbstractItem(rawSiteStructure, type => type === PageType.StartPage);
const startPage = getStartPage(siteStructure, req.headers.host || '');
if (!startPage) {
res.status(NOT_FOUND).send('Page not found');
return;
}
const siteStructureFilter = new SiteStructureFilter();
const path = findPath(startPage, req.path, siteStructureFilter);
const widgetFilter = new DefaultWidgetFilter(req.path);
const modelFactory = new ModelFactory(widgetFilter);
const pageModel = await modelFactory.buildPageModel(path);
Модель строится исходя из типа найденного AbstractItem и остаточного пути. Пример реализации:
interface PageModelResult {
notFound?: boolean;
redirectTo?: string;
permanentRedirect?: boolean;
pageModel?: BasePageModel;
}
export class ModelFactory {
private readonly _widgetFilter?: TargetingFilter;
constructor(widgetFilter?: TargetingFilter) {
this._widgetFilter = widgetFilter;
}
async buildPageModel(pathData?: PathData): Promise<PageModelResult> {
if (!pathData) {
return { notFound: true };
}
const page = pathData.abstractItem as AbstractItem;
const pageType = page.type as PageType;
switch (pageType) {
case PageType.StartPage: {
// ...
}
case PageType.TextPage: {
if (pathData.remainingPath && pathData.remainingPath !== '/') {
return { notFound: true };
}
const result: TextPageModel = {
...(await this.mapBasePage(page)),
text: page.getField('TEXT', ''),
hideTitle: page.getField('HIDETITLE', false),
};
return { pageModel: result };
}
case PageType.RedirectPage: {
return { redirectTo: page.getField('REDIRECTTO', ''), permanentRedirect: true };
}
default:
return { notFound: true };
}
}
private isMatchingWidget(item: AbstractItem): boolean {
return !item.isPage && (!this._widgetFilter || this._widgetFilter.match(item));
}
private async mapBaseWidget(widget: AbstractItem): Promise<BaseWidgetModel> {
const childrenPromises = widget.childItems?.filter(x => this.isMatchingWidget(x)).map(x => this.mapWidget(x));
const children = childrenPromises ? await Promise.all(childrenPromises) : [];
return {
id: widget.id,
title: widget.title || '',
type: widget.type,
alias: widget.alias || '',
zoneName: widget.zoneName,
cultureId: widget.cultureId,
regionIds: widget.regionIds,
sortOrder: widget.sortOrder,
isPage: false,
children,
};
}
private async mapBasePage(page: AbstractItem): Promise<BasePageModel> {
const widgetPromises = getPageWidgets(page, this._widgetFilter)?.map(x => this.mapWidget(x));
const widgets = widgetPromises ? await Promise.all(widgetPromises) : [];
return {
id: page.id,
title: page.title || '',
type: page.type,
alias: page.alias || '',
cultureId: page.cultureId,
regionIds: page.regionIds,
sortOrder: page.sortOrder,
isPage: true,
widgets,
};
}
private async mapWidget(widget: AbstractItem): Promise<BaseWidgetModel> {
const type = widget.type as WidgetType;
switch (type) {
case WidgetType.FaqWidget: {
const result: FaqWidgetModel = {
...(await this.mapBaseWidget(widget)),
header: widget.getField('HEADER', ''),
questions: await apiService.getFaqQuestions(widget.getField<number[]>('QUESTIONS', [])),
};
return result;
}
case WidgetType.HtmlWidget: {
const result: HtmlWidgetModel = {
...(await this.mapBaseWidget(widget)),
html: widget.getField('HTML', ''),
};
return result;
}
default:
console.error(`unknown widget type: ${type}`);
return this.mapBaseWidget(widget);
}
}
}