@nativescript-community/ui-collectionview
Version:
Allows you to easily add a collection view (grid list view) to your projects. Supports vertical and horizontal modes, templating, and more.
441 lines (436 loc) • 18.1 kB
JavaScript
import * as i0 from '@angular/core';
import { EventEmitter, ElementRef, IterableDiffers, NgZone, TemplateRef, ViewContainerRef, Component, ChangeDetectionStrategy, Inject, ViewChild, Output, ContentChild, Input, HostListener, Directive, Host, NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
import { CollectionView, ListViewViewTypes, CLog, CLogTypes } from '@nativescript-community/ui-collectionview';
import * as i1 from '@nativescript/angular';
import { registerElement, DetachedLoader, isListLikeIterable, NativeScriptRendererFactory, extractSingleViewRecursive } from '@nativescript/angular';
import { ObservableArray, Trace, LayoutBase } from '@nativescript/core';
const _c0 = ["loader"];
registerElement('CollectionView', () => CollectionView);
const NG_VIEW = '_ngViewRef';
class ItemContext {
$implicit;
item;
index;
even;
odd;
constructor($implicit, item, index, even, odd) {
this.$implicit = $implicit;
this.item = item;
this.index = index;
this.even = even;
this.odd = odd;
}
}
class CollectionViewComponent {
_elementRef;
_iterableDiffers;
_renderer;
_ngZone;
get nativeElement() {
return this._collectionView;
}
get listView() {
return this._collectionView;
}
loader;
setupItemView = new EventEmitter();
itemTemplateQuery;
autoReuseViews = false;
detachedLoaderFactory() {
const ref = this.loader.createComponent(DetachedLoader, {
index: 0
});
this.loader.detach(0);
this._loaders.push(ref);
return ref;
}
get itemTemplate() {
return this._itemTemplate;
}
set itemTemplate(value) {
this._itemTemplate = value;
this._collectionView.refresh();
}
get items() {
return this._items;
}
set items(value) {
this._items = value;
let needDiffer = true;
if (value instanceof ObservableArray) {
needDiffer = false;
}
if (needDiffer && !this._differ && isListLikeIterable(value)) {
this._differ = this._iterableDiffers.find(this._items).create((_index, item) => item);
}
this._collectionView.items = this._items;
}
_collectionView;
_items;
_differ;
_itemTemplate;
_templateMap;
_loaders;
constructor(_elementRef, _iterableDiffers, _renderer, _ngZone) {
this._elementRef = _elementRef;
this._iterableDiffers = _iterableDiffers;
this._renderer = _renderer;
this._ngZone = _ngZone;
this._collectionView = _elementRef.nativeElement;
this._collectionView.on(CollectionView.itemLoadingEvent, this.onItemLoading, this);
this._collectionView.itemViewLoader = this.itemViewLoader;
this._loaders = [];
}
itemViewLoader = (viewType) => this._ngZone.run(() => {
switch (viewType) {
case ListViewViewTypes.ItemView:
if (this._itemTemplate && this.loader) {
const typedView = this.getOrCreate(this._itemTemplate);
return typedView;
}
break;
}
return null;
});
ngAfterContentInit() {
if (Trace.isEnabled()) {
CLog(CLogTypes.info, 'CollectionView.ngAfterContentInit()');
}
this.setItemTemplates();
}
ngOnDestroy() {
this._collectionView.off(CollectionView.itemLoadingEvent, this.onItemLoading, this);
this._collectionView = null;
this._loaders.forEach((l) => l.destroy());
this._loaders = null;
this.viewToLoader = null;
this.viewToTemplate = null;
this.viewPool = null;
this._items = null;
this._differ = null;
this._itemTemplate = null;
if (this._templateMap) {
this._templateMap.clear();
}
this._templateMap = null;
}
ngDoCheck() {
if (Trace.isEnabled()) {
CLog(CLogTypes.info, 'ngDoCheck() - execute differ? ' + this._differ);
}
if (this._differ) {
if (Trace.isEnabled()) {
CLog(CLogTypes.info, 'ngDoCheck() - execute differ');
}
const changes = this._differ.diff(this._items);
if (changes) {
if (Trace.isEnabled()) {
CLog(CLogTypes.info, 'ngDoCheck() - refresh');
}
this.refresh();
}
}
}
registerTemplate(key, template) {
if (Trace.isEnabled()) {
CLog(CLogTypes.info, 'registerTemplate for key: ' + key);
}
if (!this._templateMap) {
this._templateMap = new Map();
}
const keyedTemplate = {
key,
createView: this.getItemTemplateViewFactory(template)
};
this._templateMap.set(key, keyedTemplate);
}
onItemLoading(args) {
if (!args.view && !this.itemTemplate) {
return;
}
if (!this.items)
return;
const index = args.index;
const items = args.object.items;
const currentItem = typeof items.getItem === 'function' ? items.getItem(index) : items[index];
let viewRef;
if (Trace.isEnabled()) {
CLog(CLogTypes.info, `onItemLoading: ${index} - Reusing existing view`);
}
viewRef = args.view[NG_VIEW];
// Getting angular view from original element (in cases when ProxyViewContainer
// is used NativeScript internally wraps it in a StackLayout)
if (!viewRef && args.view instanceof LayoutBase && args.view.getChildrenCount() > 0) {
viewRef = args.view.getChildAt(0)[NG_VIEW];
}
if (!viewRef && Trace.isEnabled()) {
if (Trace.isEnabled()) {
CLog(CLogTypes.info, `ViewReference not found for item ${index}. View recycling is not working`);
}
}
if (!viewRef) {
if (Trace.isEnabled()) {
CLog(CLogTypes.info, `onItemLoading: ${index} - Creating view from template`);
}
viewRef = this.loader.createEmbeddedView(this.itemTemplate, new ItemContext(), 0);
args.view = getItemViewRoot(viewRef);
args.view[NG_VIEW] = viewRef;
}
this.setupViewRef(viewRef, currentItem, index);
this.detectChangesOnChild(viewRef, index);
}
onItemRecyclingInternal(args) {
if (!args.view) {
return;
}
let ngView = args.view[NG_VIEW];
// Getting angular view from original element (in cases when ProxyViewContainer
// is used NativeScript internally wraps it in a StackLayout)
if (!ngView && args.view instanceof LayoutBase && args.view.getChildrenCount() > 0) {
ngView = args.view.getChildAt(0)[NG_VIEW];
}
// console.log('recycling', args.view);
if (ngView) {
ngView.detach();
}
}
onItemDisposingInternal(args) {
if (!args.view) {
return;
}
let ngView = args.view[NG_VIEW];
// Getting angular view from original element (in cases when ProxyViewContainer
// is used NativeScript internally wraps it in a StackLayout)
if (!ngView && args.view instanceof LayoutBase && args.view.getChildrenCount() > 0) {
ngView = args.view.getChildAt(0)[NG_VIEW];
}
if (ngView) {
ngView.detach();
this.storeViewRef(ngView);
}
}
setupViewRef(view, data, index) {
const context = view.context;
context.$implicit = data;
context.item = data;
context.index = index;
context.even = index % 2 === 0;
context.odd = !context.even;
this.setupItemView.next({
context,
data,
index,
view
});
}
getItemTemplateViewFactory(template) {
return () => {
const viewRef = this.loader.createEmbeddedView(template, new ItemContext(), 0);
const resultView = getItemViewRoot(viewRef);
resultView[NG_VIEW] = viewRef;
return resultView;
};
}
viewPool = new Map();
storeViewRef(viewRef) {
const templateRef = this.viewToTemplate.get(viewRef);
if (templateRef) {
const scrap = this.viewPool.get(templateRef);
if (scrap) {
if (scrap.scrapHead.size >= scrap.scrapSize) {
viewRef.destroy();
this.viewToLoader.get(viewRef)?.destroy();
}
else {
scrap.scrapHead.add(viewRef);
}
}
}
}
viewToTemplate = new WeakMap();
viewToLoader = new WeakMap();
getOrCreate(templateRef) {
return this._ngZone.run(() => {
let viewRef = this.getView(templateRef);
if (!viewRef) {
const loader = this.detachedLoaderFactory();
// viewRef = this.loader.createEmbeddedView(templateRef, new ItemContext(), 0);
viewRef = loader.instance.vc.createEmbeddedView(templateRef, new ItemContext(), 0);
this.viewToLoader.set(viewRef, loader);
this.viewToTemplate.set(viewRef, templateRef);
}
viewRef.detach();
const resultView = getItemViewRoot(viewRef);
resultView[NG_VIEW] = viewRef;
resultView.reusable = this.autoReuseViews;
return resultView;
});
}
getView(templateRef) {
const pool = this.getViewPool(templateRef);
while (pool.scrapHead.size > 0) {
const viewRef = pool.scrapHead.values().next().value;
pool.scrapHead.delete(viewRef);
if (!viewRef.destroyed) {
return viewRef;
}
}
return null;
}
getViewPool(templateRef) {
if (!this.viewPool.has(templateRef)) {
this.viewPool.set(templateRef, {
scrapSize: this.autoReuseViews ? Infinity : 0,
scrapHead: new Set()
});
}
return this.viewPool.get(templateRef);
}
setItemTemplates() {
// The itemTemplateQuery may be changed after list items are added that contain <template> inside,
// so cache and use only the original template to avoid errors.
this.itemTemplate = this.itemTemplateQuery;
if (this._templateMap) {
if (Trace.isEnabled()) {
CLog(CLogTypes.info, 'Setting templates');
}
const templates = [];
this._templateMap.forEach((value) => {
templates.push(value);
});
this._collectionView.itemTemplates = templates;
}
else {
// If the map was not initialized this means that there are no named templates, so we register the default one.
this._collectionView.itemTemplate = this.getItemTemplateViewFactory(this.itemTemplate);
}
}
detectChangesOnChild(viewRef, index) {
if (Trace.isEnabled()) {
CLog(CLogTypes.info, 'Manually detect changes in child: ' + index);
}
viewRef.markForCheck();
viewRef.detectChanges();
}
refresh() {
if (this._collectionView) {
this._collectionView.refresh();
}
}
static ɵfac = function CollectionViewComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || CollectionViewComponent)(i0.ɵɵdirectiveInject(ElementRef), i0.ɵɵdirectiveInject(IterableDiffers), i0.ɵɵdirectiveInject(NativeScriptRendererFactory), i0.ɵɵdirectiveInject(NgZone)); };
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: CollectionViewComponent, selectors: [["CollectionView"]], contentQueries: function CollectionViewComponent_ContentQueries(rf, ctx, dirIndex) { if (rf & 1) {
i0.ɵɵcontentQuery(dirIndex, TemplateRef, 7, TemplateRef);
} if (rf & 2) {
let _t;
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.itemTemplateQuery = _t.first);
} }, viewQuery: function CollectionViewComponent_Query(rf, ctx) { if (rf & 1) {
i0.ɵɵviewQuery(_c0, 7, ViewContainerRef);
} if (rf & 2) {
let _t;
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.loader = _t.first);
} }, hostBindings: function CollectionViewComponent_HostBindings(rf, ctx) { if (rf & 1) {
i0.ɵɵlistener("itemLoading", function CollectionViewComponent_itemLoading_HostBindingHandler($event) { return ctx.onItemLoading($event); })("itemRecycling", function CollectionViewComponent_itemRecycling_HostBindingHandler($event) { return ctx.onItemRecyclingInternal($event); })("itemDisposing", function CollectionViewComponent_itemDisposing_HostBindingHandler($event) { return ctx.onItemDisposingInternal($event); });
} }, inputs: { autoReuseViews: "autoReuseViews", itemTemplate: "itemTemplate", items: "items" }, outputs: { setupItemView: "setupItemView" }, decls: 3, vars: 0, consts: [["loader", ""]], template: function CollectionViewComponent_Template(rf, ctx) { if (rf & 1) {
i0.ɵɵelementStart(0, "DetachedContainer");
i0.ɵɵelement(1, "Placeholder", null, 0);
i0.ɵɵelementEnd();
} }, encapsulation: 2, changeDetection: 0 });
}
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(CollectionViewComponent, [{
type: Component,
args: [{
selector: 'CollectionView',
template: `
<DetachedContainer>
<Placeholder #loader></Placeholder>
</DetachedContainer>
`,
changeDetection: ChangeDetectionStrategy.OnPush
}]
}], () => [{ type: i0.ElementRef, decorators: [{
type: Inject,
args: [ElementRef]
}] }, { type: i0.IterableDiffers, decorators: [{
type: Inject,
args: [IterableDiffers]
}] }, { type: i1.NativeScriptRendererFactory, decorators: [{
type: Inject,
args: [NativeScriptRendererFactory]
}] }, { type: i0.NgZone, decorators: [{
type: Inject,
args: [NgZone]
}] }], { loader: [{
type: ViewChild,
args: ['loader', { read: ViewContainerRef, static: true }]
}], setupItemView: [{
type: Output
}], itemTemplateQuery: [{
type: ContentChild,
args: [TemplateRef, { read: TemplateRef, static: true }]
}], autoReuseViews: [{
type: Input
}], itemTemplate: [{
type: Input
}], items: [{
type: Input
}], onItemLoading: [{
type: HostListener,
args: ['itemLoading', ['$event']]
}], onItemRecyclingInternal: [{
type: HostListener,
args: ['itemRecycling', ['$event']]
}], onItemDisposingInternal: [{
type: HostListener,
args: ['itemDisposing', ['$event']]
}] }); })();
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(CollectionViewComponent, { className: "CollectionViewComponent", filePath: "collectionview-comp.ts", lineNumber: 53 }); })();
function getItemViewRoot(viewRef, rootLocator = extractSingleViewRecursive) {
const rootView = rootLocator(viewRef.rootNodes, 0);
return rootView;
}
class TemplateKeyDirective {
templateRef;
collectionView;
constructor(templateRef, collectionView) {
this.templateRef = templateRef;
this.collectionView = collectionView;
}
set cvTemplateKey(value) {
if (Trace.isEnabled()) {
CLog(CLogTypes.info, 'cvTemplateKey: ' + value);
}
if (this.collectionView && this.templateRef) {
this.collectionView.registerTemplate(value.toLowerCase(), this.templateRef);
}
}
static ɵfac = function TemplateKeyDirective_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || TemplateKeyDirective)(i0.ɵɵdirectiveInject(i0.TemplateRef), i0.ɵɵdirectiveInject(CollectionViewComponent, 1)); };
static ɵdir = /*@__PURE__*/ i0.ɵɵdefineDirective({ type: TemplateKeyDirective, selectors: [["", "cvTemplateKey", ""]], inputs: { cvTemplateKey: "cvTemplateKey" } });
}
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TemplateKeyDirective, [{
type: Directive,
args: [{ selector: '[cvTemplateKey]' }]
}], () => [{ type: i0.TemplateRef }, { type: CollectionViewComponent, decorators: [{
type: Host
}] }], { cvTemplateKey: [{
type: Input
}] }); })();
// External
class CollectionViewModule {
static ɵfac = function CollectionViewModule_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || CollectionViewModule)(); };
static ɵmod = /*@__PURE__*/ i0.ɵɵdefineNgModule({ type: CollectionViewModule });
static ɵinj = /*@__PURE__*/ i0.ɵɵdefineInjector({});
}
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(CollectionViewModule, [{
type: NgModule,
args: [{
declarations: [CollectionViewComponent, TemplateKeyDirective],
exports: [CollectionViewComponent, TemplateKeyDirective],
schemas: [NO_ERRORS_SCHEMA]
}]
}], null, null); })();
(function () { (typeof ngJitMode === "undefined" || ngJitMode) && i0.ɵɵsetNgModuleScope(CollectionViewModule, { declarations: [CollectionViewComponent, TemplateKeyDirective], exports: [CollectionViewComponent, TemplateKeyDirective] }); })();
/**
* Generated bundle index. Do not edit.
*/
export { CollectionViewComponent, CollectionViewModule, TemplateKeyDirective };
//# sourceMappingURL=nativescript-community-ui-collectionview-angular.mjs.map