@v4fire/client
Version:
V4Fire client core library
229 lines (181 loc) • 4.95 kB
text/typescript
/*!
* V4Fire Client Core
* https://github.com/V4Fire/Client
*
* Released under the MIT license
* https://github.com/V4Fire/Client/blob/master/LICENSE
*/
import symbolGenerator from 'core/symbol';
import { Friend } from 'super/i-block/i-block';
import type ScrollRender from 'base/b-virtual-scroll/modules/chunk-render';
import type bVirtualScroll from 'base/b-virtual-scroll/b-virtual-scroll';
import type { RenderItem, DataToRender, ItemAttrs, VirtualItemEl } from 'base/b-virtual-scroll/interface';
export const
$$ = symbolGenerator();
export default class ComponentRender extends Friend {
override readonly C!: bVirtualScroll;
/**
* Async group
*/
readonly asyncGroup: string = 'component-render';
/**
* If false, the cache flushing process is not currently running
*/
protected canDropCache: boolean = false;
/**
* Rendered items cache
*/
protected nodesCache: Dictionary<HTMLElement> = Object.createDict();
/**
* True if rendered nodes can be cached
*/
protected get canCache(): boolean {
return this.ctx.cacheNodes && this.ctx.clearNodes;
}
/**
* API for scroll rendering
*/
protected get scrollRender(): ScrollRender {
return this.ctx.chunkRender;
}
/**
* Classname for options
*/
get optionClass(): CanUndef<string> {
return this.ctx.block?.getFullElName('option-el');
}
/**
* Re-initializes component render
*/
reInit(): void {
Object.keys(this.nodesCache).forEach((key) => {
const el = this.nodesCache[key];
el?.remove();
});
this.nodesCache = Object.createDict();
}
/**
* Returns a node from the cache by the specified key
* @param key
*/
getCachedComponent(key: string): CanUndef<HTMLElement> {
return this.nodesCache[key];
}
/**
* Saves a node to the cache by the specified key
*
* @param key
* @param node
*/
cacheNode(key: string, node: HTMLElement): HTMLElement {
if (!this.ctx.cacheNodes) {
return node;
}
this.nodesCache[key] = node;
const
{nodesCache, ctx: {cacheSize}} = this;
if (Object.keys(nodesCache).length > cacheSize) {
this.canDropCache = true;
}
return node;
}
/** @see [[bVirtualScroll.getOptionKey]] */
getItemKey(data: VirtualItemEl, index: number): string {
return String(this.ctx.getItemKey(data, index));
}
/**
* Renders the specified chunk of items
* @param items
*/
render(items: RenderItem[]): HTMLElement[] {
const
{canCache} = this;
const
res: HTMLElement[] = [],
needRender: Array<[RenderItem, number, VirtualItemEl]> = [];
for (let i = 0; i < items.length; i++) {
const
item = items[i];
if (item.node) {
res[i] = item.node;
continue;
}
const getItemKeyData = {
current: item.data,
prev: items[i - 1]?.data,
next: items[i + 1]?.data
};
if (canCache) {
const
key = this.getItemKey(getItemKeyData, item.index),
node = this.getCachedComponent(key);
if (node) {
res[i] = node;
item.node = node;
continue;
}
}
needRender.push([item, i, getItemKeyData]);
}
if (needRender.length > 0) {
const
nodes = this.createComponents(needRender.map(([item]) => item));
for (let i = 0; i < needRender.length; i++) {
const
[item, indexesToAssign, getItemKeyData] = needRender[i],
node = nodes[i];
const
key = this.getItemKey(getItemKeyData, item.index);
if (canCache) {
this.cacheNode(key, item.node = node);
}
res[indexesToAssign] = node;
}
}
return res;
}
/**
* Creates and renders components by the specified parameters
* @param items
*/
protected createComponents(items: RenderItem[]): HTMLElement[] {
const
{ctx: c, scrollRender: {items: totalItems}} = this;
const render = (children: DataToRender[]) => {
const map = ({itemAttrs, itemParams, index}) =>
this.ctx.$createElement(c.getItemComponentName(itemParams, index), itemAttrs);
return <HTMLElement[]>c.vdom.render(children.map(map));
};
const getChildrenAttrs = (props: ItemAttrs) => ({
attrs: {
'v-attrs': {
...props,
class: [this.optionClass].concat(props.class ?? []),
style: props.style
}
}
});
const getItemEl = (data, i: number) => ({
current: data,
prev: totalItems[i - 1]?.data,
next: totalItems[i + 1]?.data
});
const
children: DataToRender[] = [];
for (let i = 0; i < items.length; i++) {
const
item = items[i],
itemParams = getItemEl(item.data, item.index),
itemIndex = item.index;
const attrs = c.getItemAttrs(getItemEl(item.data, item.index), item.index);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
children.push({itemParams, itemAttrs: getChildrenAttrs(attrs!), index: itemIndex});
}
const
res = render(children);
if (res.length === 0) {
throw new Error('Failed to render components. Possibly an error occurred while creating the components.');
}
return res;
}
}