UNPKG

@nstudio/ui-collectionview

Version:

Customized NativeScript CollectionView for high performance lists. Supports vertical and horizontal modes, templating, and more.

151 lines 7.01 kB
import * as React from 'react'; import { NSVRoot, render as RNSRender, registerElement, unmountComponentAtNode } from 'react-nativescript'; export function registerCollectionView() { registerElement('collectionView', () => require('..').CollectionView); } /** * A React wrapper around the NativeScript CollectionView component. * @see https://docs.nativescript.org/ui/ns-ui-widgets/list-view * @module ui/list-view/list-view */ export class _CollectionView extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); this.argsViewToRootKeyAndRef = new Map(); this.roots = new Set(); /** * CollectionView code-behind: * @see https://github.com/NativeScript/nativescript-sdk-examples-js/blob/master/app/ns-ui-widgets-category/list-view/code-behind/code-behind-ts-page.ts * CollectionView item templates: * @see https://medium.com/@alexander.vakrilov/faster-nativescript-CollectionView-with-multiple-item-templates-8f903a32e48f * Cell state in CollectionView: * @see https://medium.com/@alexander.vakrilov/managing-component-state-in-nativescript-CollectionView-b139e45d899b * @see https://github.com/NativeScript/nativescript-angular/issues/1245#issuecomment-393465035 * loadMoreItems: * @see https://github.com/NativeScript/nativescript-sdk-examples-js/blob/master/app/ns-ui-widgets-category/list-view/events/events-ts-page.ts */ this.defaultOnItemLoading = (args) => { const { logLevel, onCellRecycle, onCellFirstLoad } = this.props._debug; const { items, itemTemplateSelector } = this.props; const item = _CollectionView.isItemsSource(items) ? items.getItem(args.index) : items[args.index]; const template = itemTemplateSelector ? typeof itemTemplateSelector === 'string' ? itemTemplateSelector : itemTemplateSelector(item, args.index, items) : null; const cellFactory = template === null ? this.props.cellFactory : this.props.cellFactories ? this.props.cellFactories.get(template).cellFactory : this.props.cellFactory; if (typeof cellFactory === 'undefined') { console.warn(`CollectionView: No cell factory found, given template ${template}!`); return; } const view = args.view; if (!view) { const rootKeyAndRef = this.renderNewRoot(item, cellFactory); args.view = rootKeyAndRef.nativeView; /* Here we're re-using the ref - I assume this is best practice. If not, we can make a new one on each update instead. */ this.argsViewToRootKeyAndRef.set(args.view, rootKeyAndRef); if (onCellFirstLoad) onCellFirstLoad(rootKeyAndRef.nativeView); } else { if (onCellRecycle) onCellRecycle(view); const { rootKey, nativeView } = this.argsViewToRootKeyAndRef.get(view); if (typeof rootKey === 'undefined') { console.error('Unable to find root key that args.view corresponds to!', view); return; } if (!nativeView) { console.error('Unable to find ref that args.view corresponds to!', view); return; } // args.view = null; RNSRender(cellFactory(item), null, () => { // console.log(`Rendered into cell! detachedRootRef:`); }, rootKey); } }; this.renderNewRoot = (item, cellFactory) => { const node = this.getNativeView(); if (!node) { throw new Error('Unable to get ref to CollectionView'); } const rootKey = `CollectionView-${node._domId}-${this.roots.size.toString()}`; const root = new NSVRoot(); RNSRender(cellFactory(item), root, () => { // console.log(`Rendered into cell! ref:`); }, rootKey); this.roots.add(rootKey); return { rootKey, nativeView: root.baseRef.nativeView }; }; this.state = { nativeCells: {}, nativeCellToItemIndex: new Map(), itemIndexToNativeCell: props._debug.logLevel === 'debug' ? new Map() : undefined }; } getNativeView() { const ref = this.props.forwardedRef || this.myRef; return ref.current ? ref.current.nativeView : null; } componentDidMount() { const node = this.getNativeView(); if (!node) { console.warn('React ref to NativeScript View lost, so unable to set item templates.'); return; } /* NOTE: does not support updating of this.props.cellFactories upon Props update. */ if (this.props.cellFactories) { const itemTemplates = []; this.props.cellFactories.forEach((info, key) => { const { placeholderItem, cellFactory } = info; itemTemplates.push({ key, createView: () => { const rootKeyAndRef = this.renderNewRoot(placeholderItem, cellFactory); this.argsViewToRootKeyAndRef.set(rootKeyAndRef.nativeView, rootKeyAndRef); return rootKeyAndRef.nativeView; } }); }); node.itemTemplates = itemTemplates; } } componentWillUnmount() { this.roots.forEach((root) => unmountComponentAtNode(root)); } static isItemsSource(arr) { /** * Same implementation as used in official ListPicker component: * @see https://github.com/NativeScript/NativeScript/blob/b436ecde3605b695a0ffa1757e38cc094e2fe311/tns-core-modules/ui/list-picker/list-picker-common.ts#L74 */ return typeof arr.getItem === 'function'; } render() { const { // Only used by the class component; not the JSX element. forwardedRef, //@ts-ignore children, _debug, cellFactories, cellFactory, ...rest } = this.props; if (children) { console.warn("Ignoring 'children' prop on CollectionView; not supported."); } return React.createElement("collectionView", { ...rest, onItemLoading: this.defaultOnItemLoading, ref: forwardedRef || this.myRef }); } } _CollectionView.defaultProps = { _debug: { logLevel: 'info', onCellFirstLoad: undefined, onCellRecycle: undefined } }; //@ts-ignore export const CollectionView = React.forwardRef((props, ref) => ( //@ts-ignore React.createElement(_CollectionView, { ...props, forwardedRef: ref }))); //# sourceMappingURL=index.js.map