UNPKG

@nstudio/ui-collectionview

Version:

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

670 lines 24.8 kB
var CollectionViewBase_1; import { Builder, CSSType, Label, Observable, ObservableArray, PercentLength, Property, ProxyViewContainer, Trace, Utils, View, addWeakEventListener, booleanConverter, heightProperty, makeParser, makeValidator, profile, removeWeakEventListener, widthProperty } from '@nativescript/core'; export const CollectionViewTraceCategory = 'NativescriptCollectionView'; // iOS only export var ContentInsetAdjustmentBehavior; (function (ContentInsetAdjustmentBehavior) { ContentInsetAdjustmentBehavior[ContentInsetAdjustmentBehavior["Always"] = 0] = "Always"; ContentInsetAdjustmentBehavior[ContentInsetAdjustmentBehavior["Automatic"] = 1] = "Automatic"; ContentInsetAdjustmentBehavior[ContentInsetAdjustmentBehavior["Never"] = 2] = "Never"; ContentInsetAdjustmentBehavior[ContentInsetAdjustmentBehavior["ScrollableAxes"] = 3] = "ScrollableAxes"; })(ContentInsetAdjustmentBehavior || (ContentInsetAdjustmentBehavior = {})); export var CLogTypes; (function (CLogTypes) { CLogTypes[CLogTypes["log"] = 0] = "log"; CLogTypes[CLogTypes["info"] = 1] = "info"; CLogTypes[CLogTypes["warning"] = 2] = "warning"; CLogTypes[CLogTypes["error"] = 3] = "error"; })(CLogTypes || (CLogTypes = {})); export const CLog = (type, ...args) => { Trace.write(args.map((a) => (a && typeof a === 'object' ? a.toString() : a)).join(' '), CollectionViewTraceCategory, type); }; const autoEffectiveRowHeight = 0; const autoEffectiveColWidth = 0; // export * from 'ui/core/view'; export var ListViewViewTypes; (function (ListViewViewTypes) { ListViewViewTypes[ListViewViewTypes["ItemView"] = 0] = "ItemView"; })(ListViewViewTypes || (ListViewViewTypes = {})); export var knownTemplates; (function (knownTemplates) { knownTemplates.itemTemplate = 'itemTemplate'; })(knownTemplates || (knownTemplates = {})); export var knownMultiTemplates; (function (knownMultiTemplates) { knownMultiTemplates.itemTemplates = 'itemTemplates'; })(knownMultiTemplates || (knownMultiTemplates = {})); function toDevicePixels(length, auto = Number.NaN, parentAvailableWidth = Number.NaN) { if (length === 'auto') { // tslint:disable-line return auto; } if (typeof length === 'number') { return Math.floor(Utils.layout.toDevicePixels(length)); } if (!length) { return auto; } switch (length.unit) { case 'px': return Math.floor(length.value); case '%': return Math.floor(parentAvailableWidth * length.value); case 'dip': default: return Math.floor(Utils.layout.toDevicePixels(length.value)); } } let CollectionViewBase = CollectionViewBase_1 = class CollectionViewBase extends View { static registerPlugin(key, plugin) { this.plugins[key] = plugin; } static registerLayoutStyle(style, generator) { this.layoutStyles[style] = generator; } constructor() { super(); this._innerWidth = 0; this._innerHeight = 0; this._dataUpdatesSuspended = false; this.layoutStyle = 'grid'; this.plugins = []; // public _itemIdGenerator: (item: any, index: number, items: any) => number = (_item: any, index: number) => index; this._itemIdGenerator = null; this.onSpanSizeChanged = (oldValue, newValue) => { this.spanSize = newValue; this.refresh(); }; this.onItemOverlapChanged = (oldValue, newValue) => { this.itemOverlap = newValue; this.refresh(); }; this._isDataDirty = false; this._defaultTemplate = { key: 'default', createView: () => { if (this.itemTemplate) { return Builder.parse(this.itemTemplate, this); } return undefined; }, }; this._itemTemplatesInternal = new Map(); this._itemTemplatesInternal.set(this._defaultTemplate.key, this._defaultTemplate); } notifyForItemAtIndex(eventName, view, index, bindingContext, native) { throw new Error('Method not implemented.'); } updateInnerSize() { const lastInnerWidth = this._innerWidth; const lastInnerHeight = this._innerHeight; const width = this.getMeasuredWidth(); const height = this.getMeasuredHeight(); if (width === 0 || height === 0) { return false; } this._innerWidth = width - this.effectivePaddingLeft - this.effectivePaddingRight; if (this.colWidth) { let newValue = toDevicePixels(this.colWidth, autoEffectiveColWidth, this._innerWidth); // We cannot use 0 for auto as it throws for android. if (__ANDROID__) { newValue = Math.floor(newValue); } if (newValue !== this._effectiveColWidth) { this._effectiveColWidth = newValue; } } this._innerHeight = height - this.effectivePaddingTop - this.effectivePaddingBottom; if (this.rowHeight) { let newValue = toDevicePixels(this.rowHeight, autoEffectiveRowHeight, this._innerHeight); if (__ANDROID__) { newValue = Math.floor(newValue); } if (newValue !== this._effectiveRowHeight) { this._effectiveRowHeight = newValue; } } return lastInnerWidth !== this._innerWidth || lastInnerHeight !== this._innerHeight; } onMeasure(widthMeasureSpec, heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); this.updateInnerSize(); } _prepareItem(view, index) { const context = this.getItemAtIndex(index); if (view) { // we check old bindingContext to see if properties disappeared. // if so we set them to null for the View to update if (view.bindingContext && view.bindingContext !== context && typeof context === 'object') { Object.keys(view.bindingContext).forEach((k) => { if (!context.hasOwnProperty(k)) { context[k] = null; } }); view.bindingContext = context; } } return context; } notifyLoading(args) { this.notify(args); } getItemAtIndex(index) { // will be overriden in onItemsChangedInternal const thisItems = this.items; return thisItems.getItem ? thisItems.getItem(index) : thisItems[index]; } isHorizontal() { return this.orientation === 'horizontal'; } computeSpanCount() { let spanCount = 1; if (this.isHorizontal()) { if (this._effectiveRowHeight) { spanCount = Math.max(Math.floor(this._innerHeight / this._effectiveRowHeight), 1) || 1; } } else { if (this._effectiveColWidth) { spanCount = Math.max(Math.floor(this._innerWidth / this._effectiveColWidth), 1) || 1; } } return spanCount; } _onRowHeightPropertyChanged(oldValue, newValue) { this.refresh(); } _onColWidthPropertyChanged(oldValue, newValue) { this.refresh(); } onItemViewLoaderChanged() { } get itemViewLoader() { return this._itemViewLoader; } set itemViewLoader(value) { if (this._itemViewLoader !== value) { this._itemViewLoader = value; this.onItemViewLoaderChanged(); } } get padding() { return this.style.padding; } set padding(value) { this.style.padding = value; } get paddingTop() { return this.style.paddingTop; } set paddingTop(value) { this.style.paddingTop = value; } get paddingRight() { return this.style.paddingRight; } set paddingRight(value) { this.style.paddingRight = value; } get paddingBottom() { return this.style.paddingBottom; } set paddingBottom(value) { this.style.paddingBottom = value; } get paddingLeft() { return this.style.paddingLeft; } set paddingLeft(value) { this.style.paddingLeft = value; } resolveTemplateView(template) { return Builder.parse(template, this); } _getDefaultItemContent() { const lbl = new Label(); lbl['defaultItemView'] = true; lbl.bind({ targetProperty: 'text', sourceProperty: '$value', }); return lbl; } getTemplateFromSelector(templateKey) { const key = templateKey.toLowerCase(); if (this._itemTemplatesInternal.has(key)) { return this._itemTemplatesInternal.get(key); } return this._itemTemplatesInternal.get('default'); } getViewForViewType(viewType, templateKey) { let newView; if (templateKey) { const template = this.getTemplateFromSelector(templateKey); newView = template.createView(); } if (!newView && this._itemViewLoader !== undefined) { newView = this._itemViewLoader(templateKey); } if (newView) { return newView; } let templateString; switch (viewType) { case ListViewViewTypes.ItemView: templateString = this.itemTemplate; if (templateString === undefined) { return undefined; // return this._getDefaultItemContent(); } break; } return templateString === undefined ? undefined : this.resolveTemplateView(templateString); } onItemTemplateSelectorChanged(oldValue, newValue) { if (typeof newValue === 'string') { if (!this._itemTemplateSelectorBindable) { this._itemTemplateSelectorBindable = new ProxyViewContainer(); } this._itemTemplateSelectorBindable.bind({ sourceProperty: null, targetProperty: 'templateKey', expression: newValue, }); this._itemTemplateSelector = function (item, index, items) { item['$index'] = index; this._itemTemplateSelectorBindable.bindingContext = item; return this._itemTemplateSelectorBindable.get('templateKey'); }; } else if (typeof newValue === 'function') { this._itemTemplateSelector = newValue; } } onItemIdGeneratorChanged(oldValue, newValue) { if (typeof newValue === 'string') { if (!this._itemIdGeneratorBindable) { this._itemIdGeneratorBindable = new ProxyViewContainer(); } this._itemIdGeneratorBindable.bind({ sourceProperty: null, targetProperty: 'itemId', expression: newValue, }); this._itemIdGenerator = function (item, index, items) { item['$index'] = index; this._itemIdGeneratorBindable.bindingContext = item; return this._itemIdGeneratorBindable.get('itemId'); }; } else if (typeof newValue === 'function') { this._itemIdGenerator = newValue; } } onTemplateAdded(t) { } onTemplateRemoved(key) { } addTemplate(key, t) { if (!t.key) { t.key = t._key; delete t._key; } this._itemTemplatesInternal.set(t.key.toLowerCase(), t); this.onTemplateAdded(t); } removeTemplate(key) { const didDelete = this._itemTemplatesInternal.delete(key.toLowerCase()); if (didDelete) { this.onTemplateRemoved(key); } } onItemTemplatesChanged(oldValue, newValue) { this._itemTemplatesInternal = new Map(); if (newValue) { newValue.forEach((t) => { if (!t.key) { t.key = t._key; delete t._key; } this._itemTemplatesInternal.set(t.key, t); }); } if (!this._itemTemplatesInternal.has(this._defaultTemplate.key)) { this._itemTemplatesInternal.set(this._defaultTemplate.key, this._defaultTemplate); } } onItemTemplateChanged(oldValue, newValue) { } // onItemTemplateSelectorPropertyChanged(oldValue, newValue) { // this.onItemTemplateSelectorChanged(oldValue, newValue); // } getItemSourceAtIndex(index) { return this.items.getItem(index); } getItemArrayAtIndex(index) { return this.items[index]; } onItemsChanged(oldValue, newValue) { const getItem = newValue && newValue.getItem; this.isItemsSourceIn = typeof getItem === 'function'; // we override the method to prevent the test on every getItem this.getItemAtIndex = this.isItemsSourceIn ? this.getItemSourceAtIndex.bind(this) : this.getItemArrayAtIndex.bind(this); if (oldValue instanceof Observable) { removeWeakEventListener(oldValue, ObservableArray.changeEvent, this.onSourceCollectionChangedInternal, this); } if (newValue instanceof Observable) { addWeakEventListener(newValue, ObservableArray.changeEvent, this.onSourceCollectionChangedInternal, this); } this.refresh(); } onLoaded() { super.onLoaded(); if (this._isDataDirty && this._effectiveColWidth !== undefined && this._effectiveRowHeight !== undefined) { this.refresh(); } } onSourceCollectionChanged(event) { this.refresh(); } onSourceCollectionChangedInternal(event) { if (this._dataUpdatesSuspended === false) { this.onSourceCollectionChanged(event); } } // onItemsChanged(oldValue, newValue) { // this.onItemsChangedInternal(oldValue, newValue); // } [widthProperty.getDefault]() { return '100%'; } [heightProperty.getDefault]() { return '100%'; } suspendUpdates() { this._dataUpdatesSuspended = true; } updatesSuspended() { return this._dataUpdatesSuspended; } resumeUpdates(refresh) { this._dataUpdatesSuspended = false; if (refresh === true) { this.refresh(); } } _callItemReorderedEvent(oldPosition, newPosition, item) { const args = { eventName: CollectionViewBase_1.itemReorderedEvent, object: this, index: oldPosition, item, data: { targetIndex: newPosition }, view: this.draggingView, }; this.notify(args); this.draggingView = null; } _canReorderToPosition(oldPosition, newPosition, item) { const args = { returnValue: true, eventName: CollectionViewBase_1.itemReorderCheckEvent, object: this, index: oldPosition, item, data: { targetIndex: newPosition }, view: this.draggingView, }; this.notify(args); return args.returnValue; } _reorderItemInSource(oldPosition, newPosition, callEvents = true) { this.suspendUpdates(); const ownerSource = this.items; const item = this.getItemAtIndex(oldPosition); ownerSource.splice(oldPosition, 1); ownerSource.splice(newPosition, 0, item); this.resumeUpdates(false); if (callEvents) { this._callItemReorderedEvent(oldPosition, newPosition, item); } } shouldMoveItemAtIndex(index) { // if (!this.reorderEnabled) { // return false; // } const item = this.getItemAtIndex(index); const view = (this.draggingView = this.getViewForItemAtIndex(index)); let args = { returnValue: true, eventName: CollectionViewBase_1.itemReorderStartingEvent, object: this, index, item, view, }; this.notify(args); if (!args.returnValue) { return false; } args = { eventName: CollectionViewBase_1.itemReorderStartedEvent, object: this, index, item, view, }; this.notify(args); return true; } }; CollectionViewBase.itemLoadingEvent = 'itemLoading'; CollectionViewBase.itemRecyclingEvent = 'itemRecycling'; CollectionViewBase.itemDisposingEvent = 'itemDisposing'; CollectionViewBase.bindedEvent = 'binded'; CollectionViewBase.scrollEvent = 'scroll'; CollectionViewBase.scrollStartEvent = 'scrollStart'; CollectionViewBase.scrollEndEvent = 'scrollEnd'; CollectionViewBase.itemTapEvent = 'itemTap'; CollectionViewBase.itemHighlightEvent = 'itemHighlight'; CollectionViewBase.itemHighlightEndEvent = 'itemHighlightEnd'; CollectionViewBase.displayItemEvent = 'displayItem'; CollectionViewBase.itemReorderedEvent = 'itemReordered'; CollectionViewBase.itemReorderCheckEvent = 'itemReorderCheck'; CollectionViewBase.itemReorderStartingEvent = 'itemReorderStarting'; CollectionViewBase.itemReorderStartedEvent = 'itemReorderStarted'; CollectionViewBase.loadMoreItemsEvent = 'loadMoreItems'; CollectionViewBase.dataPopulatedEvent = 'dataPopulated'; CollectionViewBase.knownFunctions = ['itemTemplateSelector', 'itemIdGenerator', 'spanSize']; // See component-builder.ts isKnownFunction CollectionViewBase.plugins = {}; CollectionViewBase.layoutStyles = {}; __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", [View, Number]), __metadata("design:returntype", void 0) ], CollectionViewBase.prototype, "_prepareItem", null); __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", void 0) ], CollectionViewBase.prototype, "notifyLoading", null); __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", void 0) ], CollectionViewBase.prototype, "onItemIdGeneratorChanged", null); __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", void 0) ], CollectionViewBase.prototype, "onItemTemplatesChanged", null); __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", void 0) ], CollectionViewBase.prototype, "onItemsChanged", null); __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", void 0) ], CollectionViewBase.prototype, "onSourceCollectionChanged", null); __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", void 0) ], CollectionViewBase.prototype, "onSourceCollectionChangedInternal", null); CollectionViewBase = CollectionViewBase_1 = __decorate([ CSSType('CollectionView'), __metadata("design:paramtypes", []) ], CollectionViewBase); export { CollectionViewBase }; const defaultRowHeight = 'auto'; export const rowHeightProperty = new Property({ name: 'rowHeight', defaultValue: defaultRowHeight, equalityComparer: PercentLength.equals, valueConverter: PercentLength.parse, valueChanged: (target, oldValue, newValue) => { if (target._innerHeight !== 0) { target._effectiveRowHeight = PercentLength.toDevicePixels(newValue, autoEffectiveRowHeight, target._innerHeight); target._onRowHeightPropertyChanged(oldValue, newValue); } }, }); rowHeightProperty.register(CollectionViewBase); const defaultColWidth = { unit: '%', value: 1 }; export const colWidthProperty = new Property({ name: 'colWidth', defaultValue: defaultColWidth, equalityComparer: PercentLength.equals, valueConverter: PercentLength.parse, valueChanged: (target, oldValue, newValue) => { if (target._innerWidth !== 0) { target._effectiveColWidth = PercentLength.toDevicePixels(newValue, autoEffectiveColWidth, target._innerWidth); target._onColWidthPropertyChanged(oldValue, newValue); } }, }); colWidthProperty.register(CollectionViewBase); const converter = makeParser(makeValidator('horizontal', 'vertical')); export const orientationProperty = new Property({ name: 'orientation', defaultValue: 'vertical', affectsLayout: true, valueChanged: (target, oldValue, newValue) => { target.refresh(); }, valueConverter: converter, }); orientationProperty.register(CollectionViewBase); export const itemTemplateProperty = new Property({ name: 'itemTemplate', valueChanged(target, oldValue, newValue) { target.onItemTemplateChanged(oldValue, newValue); }, }); itemTemplateProperty.register(CollectionViewBase); export const itemTemplatesProperty = new Property({ name: 'itemTemplates', valueConverter: (value) => { if (typeof value === 'string') { return Builder.parseMultipleTemplates(value); } return value; }, valueChanged(target, oldValue, newValue) { target.onItemTemplatesChanged(oldValue, newValue); }, }); itemTemplatesProperty.register(CollectionViewBase); export const itemTemplateSelectorProperty = new Property({ name: 'itemTemplateSelector', defaultValue: undefined, valueChanged(target, oldValue, newValue) { target.onItemTemplateSelectorChanged(oldValue, newValue); }, }); itemTemplateSelectorProperty.register(CollectionViewBase); export const itemIdGeneratorProperty = new Property({ name: 'itemIdGenerator', defaultValue: undefined, valueChanged(target, oldValue, newValue) { target.onItemIdGeneratorChanged(oldValue, newValue); }, }); itemIdGeneratorProperty.register(CollectionViewBase); export const itemsProperty = new Property({ name: 'items', defaultValue: undefined, valueChanged(target, oldValue, newValue) { target.onItemsChanged(oldValue, newValue); }, }); itemsProperty.register(CollectionViewBase); export const spanSizeProperty = new Property({ name: 'spanSize', defaultValue: undefined, valueChanged(target, oldValue, newValue) { target.onSpanSizeChanged(oldValue, newValue); }, }); spanSizeProperty.register(CollectionViewBase); export const isScrollEnabledProperty = new Property({ name: 'isScrollEnabled', defaultValue: true, valueConverter: booleanConverter, }); isScrollEnabledProperty.register(CollectionViewBase); export const isBounceEnabledProperty = new Property({ name: 'isBounceEnabled', defaultValue: true, valueConverter: booleanConverter, }); isBounceEnabledProperty.register(CollectionViewBase); export const reverseLayoutProperty = new Property({ name: 'reverseLayout', defaultValue: false, valueConverter: booleanConverter, }); reverseLayoutProperty.register(CollectionViewBase); export const loadMoreThresholdProperty = new Property({ name: 'loadMoreThreshold', defaultValue: 1, valueConverter: (v) => parseInt(v, 10), }); loadMoreThresholdProperty.register(CollectionViewBase); export const reorderingEnabledProperty = new Property({ name: 'reorderEnabled', defaultValue: false, valueConverter: booleanConverter, }); reorderingEnabledProperty.register(CollectionViewBase); export const reorderLongPressEnabledProperty = new Property({ name: 'reorderLongPressEnabled', defaultValue: false, valueConverter: booleanConverter, }); reorderLongPressEnabledProperty.register(CollectionViewBase); export const scrollBarIndicatorVisibleProperty = new Property({ name: 'scrollBarIndicatorVisible', defaultValue: true, valueConverter: booleanConverter, }); scrollBarIndicatorVisibleProperty.register(CollectionViewBase); export const autoReloadItemOnLayoutProperty = new Property({ name: 'autoReloadItemOnLayout', defaultValue: false, valueConverter: booleanConverter, }); autoReloadItemOnLayoutProperty.register(CollectionViewBase); export const itemOverlapProperty = new Property({ name: 'itemOverlap', defaultValue: undefined, valueChanged(target, oldValue, newValue) { target.onItemOverlapChanged(oldValue, newValue); }, }); itemOverlapProperty.register(CollectionViewBase); //# sourceMappingURL=common.js.map