fix(virtual-scroll): JSX can render headers and footers

This commit is contained in:
Manu Mtz.-Almeida
2018-05-10 17:17:33 +02:00
parent 50021cd0be
commit 012127dd7c
7 changed files with 67 additions and 44 deletions

View File

@ -17,10 +17,10 @@ export class VirtualScroll {
private el: ElementRef, private el: ElementRef,
public cd: ChangeDetectorRef, public cd: ChangeDetectorRef,
) { ) {
this.el.nativeElement.itemRender = this.itemRender.bind(this); this.el.nativeElement.nodeRender = this.nodeRender.bind(this);
} }
private itemRender(el: HTMLElement|null, cell: any, index?: number) { private nodeRender(el: HTMLElement|null, cell: any, index?: number) {
if (!el) { if (!el) {
const node = this.itmTmp.viewContainer.createEmbeddedView( const node = this.itmTmp.viewContainer.createEmbeddedView(
this.getComponent(cell.type), this.getComponent(cell.type),

View File

@ -99,7 +99,6 @@ import {
HeaderFn, HeaderFn,
ItemHeightFn, ItemHeightFn,
ItemRenderFn, ItemRenderFn,
NodeHeightFn,
} from './components/virtual-scroll/virtual-scroll-utils'; } from './components/virtual-scroll/virtual-scroll-utils';
declare global { declare global {
@ -7213,16 +7212,17 @@ declare global {
*/ */
'headerFn': HeaderFn; 'headerFn': HeaderFn;
'itemHeight': ItemHeightFn; 'itemHeight': ItemHeightFn;
'itemRender': ItemRenderFn;
/** /**
* The data that builds the templates within the virtual scroll. It's important to note that when this data has changed, then the entire virtual scroll is reset, which is an expensive operation and should be avoided if possible. * The data that builds the templates within the virtual scroll. It's important to note that when this data has changed, then the entire virtual scroll is reset, which is an expensive operation and should be avoided if possible.
*/ */
'items': any[]; 'items': any[];
'markDirty': (offset: number, len?: number) => void; 'markDirty': (offset: number, len?: number) => void;
'markDirtyTail': () => void; 'markDirtyTail': () => void;
'nodeHeight': NodeHeightFn; 'nodeRender': ItemRenderFn;
'positionForItem': (index: number) => number; 'positionForItem': (index: number) => number;
'renderer': (item: any) => JSX.Element; 'renderFooter': (item: any, index: number) => JSX.Element;
'renderHeader': (item: any, index: number) => JSX.Element;
'renderItem': (item: any, index: number) => JSX.Element;
} }
} }
@ -7267,13 +7267,14 @@ declare global {
*/ */
'headerFn'?: HeaderFn; 'headerFn'?: HeaderFn;
'itemHeight'?: ItemHeightFn; 'itemHeight'?: ItemHeightFn;
'itemRender'?: ItemRenderFn;
/** /**
* The data that builds the templates within the virtual scroll. It's important to note that when this data has changed, then the entire virtual scroll is reset, which is an expensive operation and should be avoided if possible. * The data that builds the templates within the virtual scroll. It's important to note that when this data has changed, then the entire virtual scroll is reset, which is an expensive operation and should be avoided if possible.
*/ */
'items'?: any[]; 'items'?: any[];
'nodeHeight'?: NodeHeightFn; 'nodeRender'?: ItemRenderFn;
'renderer'?: (item: any) => JSX.Element; 'renderFooter'?: (item: any, index: number) => JSX.Element;
'renderHeader'?: (item: any, index: number) => JSX.Element;
'renderItem'?: (item: any, index: number) => JSX.Element;
} }
} }
} }

View File

@ -260,11 +260,6 @@ and what data to give to the header template. The function must return
#### itemRender
#### items #### items
@ -275,12 +270,22 @@ entire virtual scroll is reset, which is an expensive operation and
should be avoided if possible. should be avoided if possible.
#### nodeHeight #### nodeRender
#### renderer #### renderFooter
#### renderHeader
#### renderItem
@ -364,11 +369,6 @@ and what data to give to the header template. The function must return
#### item-render
#### items #### items
@ -379,12 +379,22 @@ entire virtual scroll is reset, which is an expensive operation and
should be avoided if possible. should be avoided if possible.
#### node-height #### node-render
#### renderer #### render-footer
#### render-header
#### render-item

View File

@ -82,7 +82,7 @@
return el; return el;
} }
virtual.itemRender = (el, cell) => { virtual.nodeRender = (el, cell) => {
if (cell.type === 0) return renderItem(el, cell.value); if (cell.type === 0) return renderItem(el, cell.value);
return renderHeader(el, cell.value); return renderHeader(el, cell.value);
}; };

View File

@ -56,7 +56,7 @@
return el; return el;
} }
virtual.itemRender = (el, cell) => { virtual.nodeRender = (el, cell) => {
if (cell.type === 0) return renderItem(el, cell.value); if (cell.type === 0) return renderItem(el, cell.value);
return renderHeader(el, cell.value); return renderHeader(el, cell.value);
}; };

View File

@ -40,7 +40,6 @@ export interface VirtualNode {
const MIN_READS = 2; const MIN_READS = 2;
export type NodeHeightFn = (node: VirtualNode, index: number) => number;
export type HeaderFn = (item: any, index: number, items: any[]) => string | null; export type HeaderFn = (item: any, index: number, items: any[]) => string | null;
export type ItemHeightFn = (item: any, index?: number) => number; export type ItemHeightFn = (item: any, index?: number) => number;
export type ItemRenderFn = (el: HTMLElement|null, cell: Cell, domIndex?: number) => HTMLElement; export type ItemRenderFn = (el: HTMLElement|null, cell: Cell, domIndex?: number) => HTMLElement;
@ -104,7 +103,7 @@ export function updateVDom(dom: VirtualNode[], heightIndex: Uint32Array, cells:
export function doRender( export function doRender(
el: HTMLElement, el: HTMLElement,
itemRender: ItemRenderFn, nodeRender: ItemRenderFn,
dom: VirtualNode[], dom: VirtualNode[],
updateCellHeight: Function updateCellHeight: Function
) { ) {
@ -119,9 +118,9 @@ export function doRender(
if (node.change === NodeChange.Cell) { if (node.change === NodeChange.Cell) {
if (i < childrenNu) { if (i < childrenNu) {
child = children[i] as HTMLElement; child = children[i] as HTMLElement;
itemRender(child, cell, i); nodeRender(child, cell, i);
} else { } else {
child = itemRender(null, cell, i); child = nodeRender(null, cell, i);
child.classList.add('virtual-item'); child.classList.add('virtual-item');
el.appendChild(child); el.appendChild(child);
} }

View File

@ -1,10 +1,10 @@
import { Component, Element, EventListenerEnable, Listen, Method, Prop, Watch } from '@stencil/core'; import { Component, Element, EventListenerEnable, Listen, Method, Prop, Watch } from '@stencil/core';
import { QueueController } from '../../interface'; import { QueueController } from '../../interface';
import { Cell, DomRenderFn, HeaderFn, ItemHeightFn, import { Cell, DomRenderFn, HeaderFn, ItemHeightFn,
ItemRenderFn, NodeHeightFn, Range, ItemRenderFn, Range,
VirtualNode, calcCells, calcHeightIndex, doRender, VirtualNode, calcCells, calcHeightIndex, doRender,
findCellIndex, getRange, getShouldUpdate, getViewport, findCellIndex, getRange, getShouldUpdate, getViewport,
inplaceUpdate, positionForIndex, resizeBuffer, updateVDom } from './virtual-scroll-utils'; inplaceUpdate, positionForIndex, resizeBuffer, updateVDom, CellType } from './virtual-scroll-utils';
@Component({ @Component({
@ -98,11 +98,15 @@ export class VirtualScroll {
* should be avoided if possible. * should be avoided if possible.
*/ */
@Prop() items?: any[]; @Prop() items?: any[];
@Prop() renderer?: (item: any) => JSX.Element;
@Prop() nodeHeight?: NodeHeightFn;
@Prop() itemHeight?: ItemHeightFn; @Prop() itemHeight?: ItemHeightFn;
@Prop() itemRender?: ItemRenderFn;
// JSX API
@Prop() renderItem?: (item: any, index: number) => JSX.Element;
@Prop() renderHeader?: (item: any, index: number) => JSX.Element;
@Prop() renderFooter?: (item: any, index: number) => JSX.Element;
// Low level API
@Prop() nodeRender?: ItemRenderFn;
@Prop() domRender?: DomRenderFn; @Prop() domRender?: DomRenderFn;
@Watch('itemHeight') @Watch('itemHeight')
@ -255,11 +259,11 @@ export class VirtualScroll {
); );
// write DOM // write DOM
if (this.itemRender) { if (this.nodeRender) {
doRender(this.el, this.itemRender, this.virtualDom, this.updateCellHeight.bind(this)); doRender(this.el, this.nodeRender, this.virtualDom, this.updateCellHeight.bind(this));
} else if (this.domRender) { } else if (this.domRender) {
this.domRender(this.virtualDom); this.domRender(this.virtualDom);
} else if (this.renderer) { } else if (this.renderItem) {
this.el.forceUpdate(); this.el.forceUpdate();
} }
if (this.heightChanged) { if (this.heightChanged) {
@ -370,24 +374,33 @@ export class VirtualScroll {
} }
} }
renderVirtualNode(node: VirtualNode) {
const cell = node.cell;
switch(cell.type) {
case CellType.Item: return this.renderItem!(cell.value, cell.index);
case CellType.Header: return this.renderHeader!(cell.value, cell.index);
case CellType.Footer: return this.renderFooter!(cell.value, cell.index);
}
}
render() { render() {
const renderer = this.renderer; const renderItem = this.renderItem;
if (renderer) { if (renderItem) {
return this.virtualDom.map((dom) => { return this.virtualDom.map((node) => {
const item = renderer(dom.cell.value) as any; const item = this.renderVirtualNode(node) as any;
const classes = ['virtual-item']; const classes = ['virtual-item'];
if (!item.vattrs) { if (!item.vattrs) {
item.vattrs = {}; item.vattrs = {};
} }
item.vattrs.class += ' virtual-item'; item.vattrs.class += ' virtual-item';
if (!dom.visible) { if (!node.visible) {
classes.push('virtual-loading'); classes.push('virtual-loading');
} }
item.vattrs.class += ' ' + classes.join(' '); item.vattrs.class += ' ' + classes.join(' ');
if (!item.vattrs.style) { if (!item.vattrs.style) {
item.vattrs.style = {}; item.vattrs.style = {};
} }
item.vattrs.style['transform'] = `translate3d(0,${dom.top}px,0)`; item.vattrs.style['transform'] = `translate3d(0,${node.top}px,0)`;
return item; return item;
}); });
} }