Files
NativeScript/tns-core-modules/ui/list-view/list-view-common.ts
Panayot Cankov 1cbb1e8d0d feat(webpack): mark the CSS type for stylable views explicitly (#5257)
We want webpack's uglification to mangle function and class names
but that's what the current implementation of the CSS in {N} relys on
to get the CSS type for each view when targeted by CSS type selectors.

The implementation is changed a little so now the CSS type can be set
directly on the prototype of each View class or for TS, through decorator.

BREAKING CHANGES:


Extending classes requires marking the derived class with @CSSType
The root classes are not marked with CSSType and classes derived from ViewBase and View
will continue to work as expected. More concrete view classes (Button, Label, etc.) are
marked with @CSSType now and store their cssType on the prototype suppressing the previous
implementation that looked up the class function name. So clien classes that derive from one of
our @CSSType decorated classes will now have to be marked with @CSSType.
2018-03-12 16:34:25 +02:00

212 lines
7.6 KiB
TypeScript

import { ListView as ListViewDefinition, ItemsSource, ItemEventData } from ".";
import { CoercibleProperty, CssProperty, Style, View, Template, KeyedTemplate, Length, Property, Color, Observable, EventData, CSSType } from "../core/view";
import { parse, parseMultipleTemplates } from "../builder";
import { Label } from "../label";
import { ObservableArray, ChangedData } from "../../data/observable-array";
import { addWeakEventListener, removeWeakEventListener } from "../core/weak-event-listener";
export * from "../core/view";
// TODO: Think of a way to register these instead of relying on hardcoded values.
export module knownTemplates {
export const itemTemplate = "itemTemplate";
}
export module knownMultiTemplates {
export const itemTemplates = "itemTemplates";
}
const autoEffectiveRowHeight = -1;
@CSSType("ListView")
export abstract class ListViewBase extends View implements ListViewDefinition {
public static itemLoadingEvent = "itemLoading";
public static itemTapEvent = "itemTap";
public static loadMoreItemsEvent = "loadMoreItems";
// TODO: get rid of such hacks.
public static knownFunctions = ["itemTemplateSelector", "itemIdGenerator"]; //See component-builder.ts isKnownFunction
private _itemIdGenerator: (item: any, index: number, items: any) => number = (_item: any, index: number) => index;
private _itemTemplateSelector: (item: any, index: number, items: any) => string;
private _itemTemplateSelectorBindable = new Label();
public _defaultTemplate: KeyedTemplate = {
key: "default",
createView: () => {
if (this.itemTemplate) {
return parse(this.itemTemplate, this);
}
return undefined;
}
}
public _itemTemplatesInternal = new Array<KeyedTemplate>(this._defaultTemplate);
public _effectiveRowHeight: number = autoEffectiveRowHeight;
public rowHeight: Length;
public items: any[] | ItemsSource;
public itemTemplate: string | Template;
public itemTemplates: string | Array<KeyedTemplate>;
get separatorColor(): Color {
return this.style.separatorColor;
}
set separatorColor(value: Color) {
this.style.separatorColor = value;
}
get itemTemplateSelector(): string | ((item: any, index: number, items: any) => string) {
return this._itemTemplateSelector;
}
set itemTemplateSelector(value: string | ((item: any, index: number, items: any) => string)) {
if (typeof value === "string") {
this._itemTemplateSelectorBindable.bind({
sourceProperty: null,
targetProperty: "templateKey",
expression: value
});
this._itemTemplateSelector = (item: any, index: number, items: any) => {
item["$index"] = index;
this._itemTemplateSelectorBindable.bindingContext = item;
return this._itemTemplateSelectorBindable.get("templateKey");
};
}
else if (typeof value === "function") {
this._itemTemplateSelector = value;
}
}
get itemIdGenerator(): (item: any, index: number, items: any) => number {
return this._itemIdGenerator;
}
set itemIdGenerator(generatorFn: (item: any, index: number, items: any) => number) {
this._itemIdGenerator = generatorFn;
}
public refresh() {
//
}
public scrollToIndex(index: number) {
//
}
public _getItemTemplate(index: number): KeyedTemplate {
let templateKey = "default";
if (this.itemTemplateSelector) {
let dataItem = this._getDataItem(index);
templateKey = this._itemTemplateSelector(dataItem, index, this.items);
}
for (let i = 0, length = this._itemTemplatesInternal.length; i < length; i++) {
if (this._itemTemplatesInternal[i].key === templateKey) {
return this._itemTemplatesInternal[i];
}
}
// This is the default template
return this._itemTemplatesInternal[0];
}
public _prepareItem(item: View, index: number) {
if (item) {
item.bindingContext = this._getDataItem(index);
}
}
private _getDataItem(index: number): any {
let thisItems = <ItemsSource>this.items;
return thisItems.getItem ? thisItems.getItem(index) : thisItems[index];
}
public _getDefaultItemContent(index: number): View {
let lbl = new Label();
lbl.bind({
targetProperty: "text",
sourceProperty: "$value"
});
return lbl;
}
public _onItemsChanged(args: ChangedData<any>) {
this.refresh();
}
public _onRowHeightPropertyChanged(oldValue: Length, newValue: Length) {
this.refresh();
}
protected updateEffectiveRowHeight(): void {
rowHeightProperty.coerce(this);
}
}
ListViewBase.prototype.recycleNativeView = "auto";
export interface ListViewBase {
on(eventNames: string, callback: (data: EventData) => void, thisArg?: any);
on(event: "itemLoading", callback: (args: ItemEventData) => void, thisArg?: any);
on(event: "itemTap", callback: (args: ItemEventData) => void, thisArg?: any);
on(event: "loadMoreItems", callback: (args: EventData) => void, thisArg?: any);
}
/**
* Represents the property backing the items property of each ListView instance.
*/
export const itemsProperty = new Property<ListViewBase, any[] | ItemsSource>({
name: "items", valueChanged: (target, oldValue, newValue) => {
if (oldValue instanceof Observable) {
removeWeakEventListener(oldValue, ObservableArray.changeEvent, target._onItemsChanged, target);
}
if (newValue instanceof Observable) {
addWeakEventListener(newValue, ObservableArray.changeEvent, target._onItemsChanged, target);
}
target.refresh();
}
});
itemsProperty.register(ListViewBase);
/**
* Represents the item template property of each ListView instance.
*/
export const itemTemplateProperty = new Property<ListViewBase, string | Template>({
name: "itemTemplate", valueChanged: (target) => {
target.refresh();
}
});
itemTemplateProperty.register(ListViewBase);
/**
* Represents the items template property of each ListView instance.
*/
export const itemTemplatesProperty = new Property<ListViewBase, string | Array<KeyedTemplate>>({
name: "itemTemplates", valueConverter: (value) => {
if (typeof value === "string") {
return parseMultipleTemplates(value);
}
return value;
}
})
itemTemplatesProperty.register(ListViewBase);
const defaultRowHeight: Length = "auto";
/**
* Represents the observable property backing the rowHeight property of each ListView instance.
*/
export const rowHeightProperty = new CoercibleProperty<ListViewBase, Length>({
name: "rowHeight", defaultValue: defaultRowHeight, equalityComparer: Length.equals,
coerceValue: (target, value) => {
// We coerce to default value if we don't have display density.
return target.nativeViewProtected ? value : defaultRowHeight;
},
valueChanged: (target, oldValue, newValue) => {
target._effectiveRowHeight = Length.toDevicePixels(newValue, autoEffectiveRowHeight);
target._onRowHeightPropertyChanged(oldValue, newValue);
}, valueConverter: Length.parse
});
rowHeightProperty.register(ListViewBase);
export const separatorColorProperty = new CssProperty<Style, Color>({ name: "separatorColor", cssName: "separator-color", equalityComparer: Color.equals, valueConverter: (v) => new Color(v) });
separatorColorProperty.register(Style);