mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 11:01:21 +08:00
Merge pull request #2909 from NativeScript/its
ListView item template selector
This commit is contained in:
BIN
apps/app/ui-tests-app/list-view/first-image.png
Normal file
BIN
apps/app/ui-tests-app/list-view/first-image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 990 B |
8
apps/app/ui-tests-app/list-view/list-view.css
Normal file
8
apps/app/ui-tests-app/list-view/list-view.css
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
Label, Button {
|
||||||
|
font-size: 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
border-width: 1;
|
||||||
|
margin: 1;
|
||||||
|
}
|
60
apps/app/ui-tests-app/list-view/list-view.ts
Normal file
60
apps/app/ui-tests-app/list-view/list-view.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { EventData } from 'data/observable';
|
||||||
|
import { ObservableArray } from "data/observable-array";
|
||||||
|
import { View, KeyedTemplate } from "ui/core/view";
|
||||||
|
import { Page } from 'ui/page';
|
||||||
|
import { ViewModel, Item } from './main-view-model';
|
||||||
|
import { ListView } from "ui/list-view";
|
||||||
|
import { Label } from "ui/label";
|
||||||
|
import { GridLayout } from "ui/layouts/grid-layout";
|
||||||
|
import { Color } from "color";
|
||||||
|
|
||||||
|
export function selectItemTemplate(item: Item, index: number, items: ObservableArray<Item>): string {
|
||||||
|
return item.id % 10 === 0 ? "red" : item.id % 2 === 0 ? "green" : "yellow";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pageLoaded(args: EventData) {
|
||||||
|
let page = <Page>args.object;
|
||||||
|
page.bindingContext = new ViewModel();
|
||||||
|
|
||||||
|
let lv4 = page.getViewById<ListView>("lv4");
|
||||||
|
lv4.itemTemplateSelector = (item: Item, index: number, items: ObservableArray<Item>) => {
|
||||||
|
return index % 10 === 0 ? "red" : index % 2 === 0 ? "green" : "yellow";
|
||||||
|
};
|
||||||
|
|
||||||
|
let createLabel = (backgroundColor: Color) => {
|
||||||
|
let label = new Label();
|
||||||
|
label.bind({
|
||||||
|
sourceProperty: null,
|
||||||
|
targetProperty: "text",
|
||||||
|
expression: "$value"
|
||||||
|
});
|
||||||
|
label.style.backgroundColor = backgroundColor;
|
||||||
|
return label;
|
||||||
|
};
|
||||||
|
|
||||||
|
lv4.itemTemplates = new Array<KeyedTemplate>(
|
||||||
|
{
|
||||||
|
key: "red",
|
||||||
|
createView: () => { return createLabel(new Color("red")); }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "green",
|
||||||
|
createView: () => { return createLabel(new Color("green")); }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "yellow",
|
||||||
|
createView: () => { return createLabel(new Color("yellow")); }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let scrollToBottom = true;
|
||||||
|
export function onScroll(args: EventData){
|
||||||
|
let page = (<View>args.object).page;
|
||||||
|
let gridLayout = page.getViewById<GridLayout>("grid-layout");
|
||||||
|
for (let i = 0, length = gridLayout.getChildrenCount(); i < length; i++){
|
||||||
|
let listView = <ListView>gridLayout.getChildAt(i);
|
||||||
|
listView.scrollToIndex(scrollToBottom ? listView.items.length - 1 : 0);
|
||||||
|
}
|
||||||
|
scrollToBottom = !scrollToBottom;
|
||||||
|
}
|
50
apps/app/ui-tests-app/list-view/list-view.xml
Normal file
50
apps/app/ui-tests-app/list-view/list-view.xml
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<Page loaded="pageLoaded">
|
||||||
|
<StackLayout>
|
||||||
|
<Button text="SCROLL" tap="onScroll" height="30"/>
|
||||||
|
<GridLayout id="grid-layout" columns="*,*" rows="*,*">
|
||||||
|
<ListView id="lv1" col="0" row="0" items="{{ items }}" itemTemplateSelector="selectItemTemplate">
|
||||||
|
<ListView.itemTemplates>
|
||||||
|
<template key="red">
|
||||||
|
<Label text="{{ $value }}" textWrap="true" style.backgroundColor="red"/>
|
||||||
|
</template>
|
||||||
|
<template key="green">
|
||||||
|
<Label text="{{ $value }}" textWrap="true" style.backgroundColor="green"/>
|
||||||
|
</template>
|
||||||
|
<template key="yellow">
|
||||||
|
<Label text="{{ $value }}" textWrap="true" style.backgroundColor="yellow"/>
|
||||||
|
</template>
|
||||||
|
</ListView.itemTemplates>
|
||||||
|
</ListView>
|
||||||
|
<ListView id="lv2" col="1" row="0" items="{{ items }}" itemTemplateSelector="id % 10 === 0 ? 'red' : id % 2 === 0 ? 'green' : 'yellow'">
|
||||||
|
<ListView.itemTemplates>
|
||||||
|
<template key="red">
|
||||||
|
<Label text="{{ $value }}" textWrap="true" style.backgroundColor="red"/>
|
||||||
|
</template>
|
||||||
|
<template key="green">
|
||||||
|
<Label text="{{ $value }}" textWrap="true" style.backgroundColor="green"/>
|
||||||
|
</template>
|
||||||
|
<template key="yellow">
|
||||||
|
<Label text="{{ $value }}" textWrap="true" style.backgroundColor="yellow"/>
|
||||||
|
</template>
|
||||||
|
</ListView.itemTemplates>
|
||||||
|
</ListView>
|
||||||
|
<ListView id="lv3" col="0" row="1" items="{{ items }}" itemTemplateSelector="$index % 10 === 0 ? 'wrong' : $index % 2 === 0 ? 'green' : 'yellow'">
|
||||||
|
<ListView.itemTemplate>
|
||||||
|
<Label text="{{ $value + ' itemTemplate' }}" textWrap="true"/>
|
||||||
|
</ListView.itemTemplate>
|
||||||
|
<ListView.itemTemplates>
|
||||||
|
<template key="red">
|
||||||
|
<Label text="{{ $value }}" textWrap="true" style.backgroundColor="red"/>
|
||||||
|
</template>
|
||||||
|
<template key="green">
|
||||||
|
<Label text="{{ $value }}" textWrap="true" style.backgroundColor="green"/>
|
||||||
|
</template>
|
||||||
|
<template key="yellow">
|
||||||
|
<Label text="{{ $value }}" textWrap="true" style.backgroundColor="yellow"/>
|
||||||
|
</template>
|
||||||
|
</ListView.itemTemplates>
|
||||||
|
</ListView>
|
||||||
|
<ListView id="lv4" col="1" row="1" items="{{ items }}"></ListView>
|
||||||
|
</GridLayout>
|
||||||
|
</StackLayout>
|
||||||
|
</Page>
|
51
apps/app/ui-tests-app/list-view/main-view-model.ts
Normal file
51
apps/app/ui-tests-app/list-view/main-view-model.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { Observable } from 'data/observable';
|
||||||
|
import { ObservableArray } from 'data/observable-array';
|
||||||
|
|
||||||
|
export class Item extends Observable {
|
||||||
|
private _name: string;
|
||||||
|
private _id: number;
|
||||||
|
|
||||||
|
constructor(name: string, id: number) {
|
||||||
|
super();
|
||||||
|
this._name = name;
|
||||||
|
this._id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
get name(): string {
|
||||||
|
return this._name;
|
||||||
|
}
|
||||||
|
|
||||||
|
set name(value: string) {
|
||||||
|
if (this._name !== value) {
|
||||||
|
this._name = value;
|
||||||
|
this.notifyPropertyChange('name', value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get id(): number {
|
||||||
|
return this._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
set id(value: number) {
|
||||||
|
if (this._id !== value) {
|
||||||
|
this._id = value;
|
||||||
|
this.notifyPropertyChange('id', value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString() {
|
||||||
|
return `${this.name} ${this.id}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ViewModel extends Observable {
|
||||||
|
private _items: ObservableArray<Item>;
|
||||||
|
|
||||||
|
get items(): ObservableArray<Item> {
|
||||||
|
this._items = new ObservableArray<Item>();
|
||||||
|
for (let i = 0; i < 100; i++){
|
||||||
|
this._items.push(new Item(`Item`, i));
|
||||||
|
}
|
||||||
|
return this._items;
|
||||||
|
}
|
||||||
|
}
|
BIN
apps/app/ui-tests-app/list-view/no-image.png
Normal file
BIN
apps/app/ui-tests-app/list-view/no-image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
@ -36,6 +36,7 @@ export function pageLoaded(args: EventData) {
|
|||||||
examples.set("transitions", "transitions/page0");
|
examples.set("transitions", "transitions/page0");
|
||||||
examples.set("segStyle", "segmented-bar/all");
|
examples.set("segStyle", "segmented-bar/all");
|
||||||
examples.set("flexBox", "flexbox/flexbox");
|
examples.set("flexBox", "flexbox/flexbox");
|
||||||
|
examples.set("list-view", "list-view/list-view");
|
||||||
|
|
||||||
//examples.set("listview_binding", "pages/listview_binding");
|
//examples.set("listview_binding", "pages/listview_binding");
|
||||||
//examples.set("textfield", "text-field/text-field");
|
//examples.set("textfield", "text-field/text-field");
|
||||||
|
@ -8,6 +8,7 @@ import utils = require("utils/utils");
|
|||||||
import { Label } from "ui/label";
|
import { Label } from "ui/label";
|
||||||
import helper = require("../helper");
|
import helper = require("../helper");
|
||||||
import { Page } from "ui/page";
|
import { Page } from "ui/page";
|
||||||
|
import { View, KeyedTemplate } from "ui/core/view";
|
||||||
|
|
||||||
// >> article-require-listview-module
|
// >> article-require-listview-module
|
||||||
import listViewModule = require("ui/list-view");
|
import listViewModule = require("ui/list-view");
|
||||||
@ -581,7 +582,7 @@ export class ListViewTest extends testModule.UITest<listViewModule.ListView> {
|
|||||||
if (platform.isAndroid) {
|
if (platform.isAndroid) {
|
||||||
// simulates Angular way of removing views
|
// simulates Angular way of removing views
|
||||||
(<any>listView)._realizedItems.forEach((view, nativeView, map) => {
|
(<any>listView)._realizedItems.forEach((view, nativeView, map) => {
|
||||||
console.log("view: " + view);
|
//console.log("view: " + view);
|
||||||
listView._removeView(view);
|
listView._removeView(view);
|
||||||
});
|
});
|
||||||
this.tearDown();
|
this.tearDown();
|
||||||
@ -640,6 +641,7 @@ export class ListViewTest extends testModule.UITest<listViewModule.ListView> {
|
|||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
items.push({
|
items.push({
|
||||||
text: "Item " + i,
|
text: "Item " + i,
|
||||||
|
age: i,
|
||||||
loadedCount: 0,
|
loadedCount: 0,
|
||||||
unloadedCount: 0,
|
unloadedCount: 0,
|
||||||
onViewLoaded: function onViewLoaded(args) {
|
onViewLoaded: function onViewLoaded(args) {
|
||||||
@ -767,10 +769,143 @@ export class ListViewTest extends testModule.UITest<listViewModule.ListView> {
|
|||||||
private waitUntilListViewReady(): void {
|
private waitUntilListViewReady(): void {
|
||||||
TKUnit.waitUntilReady(() => this.getNativeViewCount(this.testView) === this.testView.items.length);
|
TKUnit.waitUntilReady(() => this.getNativeViewCount(this.testView) === this.testView.items.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Multiple item templates tests
|
||||||
|
public test_ItemTemplateSelector_WhenWrongTemplateKeyIsSpecified_TheDefaultTemplateIsUsed() {
|
||||||
|
let listView = this.testView;
|
||||||
|
listView.height = 200;
|
||||||
|
|
||||||
|
listView.itemTemplate = "<Label text='default' minHeight='100' maxHeight='100'/>";
|
||||||
|
listView.itemTemplates = this._itemTemplatesString;
|
||||||
|
listView.itemTemplateSelector = "age % 2 === 0 ? 'wrong' : 'green'";
|
||||||
|
listView.items = ListViewTest.generateItemsForMultipleTemplatesTests(2);
|
||||||
|
TKUnit.wait(0.1);
|
||||||
|
|
||||||
|
let firstNativeElementText = this.getTextFromNativeElementAt(listView, 0);
|
||||||
|
|
||||||
|
TKUnit.assertEqual(firstNativeElementText, "default", "first element text");
|
||||||
|
}
|
||||||
|
|
||||||
|
public test_ItemTemplateSelector_IsCorrectlyParsedFromString() {
|
||||||
|
let listView = this.testView;
|
||||||
|
listView.itemTemplateSelector = "age % 2 === 0 ? 'red' : 'green'";
|
||||||
|
let items = ListViewTest.generateItemsForMultipleTemplatesTests(2);
|
||||||
|
let itemTemplateSelectorFunction = <any>listView.itemTemplateSelector;
|
||||||
|
TKUnit.wait(0.1);
|
||||||
|
|
||||||
|
let templateKey0 = itemTemplateSelectorFunction(items[0], 0, items);
|
||||||
|
TKUnit.assertEqual(templateKey0, "red", "itemTemplateSelector result for first item");
|
||||||
|
|
||||||
|
let templateKey1 = itemTemplateSelectorFunction(items[1], 1, items);
|
||||||
|
TKUnit.assertEqual(templateKey1, "green", "itemTemplateSelector result for second item");
|
||||||
|
}
|
||||||
|
|
||||||
|
public test_ItemTemplateSelector_ItemTemplatesAreCorrectlyParsedFromString() {
|
||||||
|
let listView = this.testView;
|
||||||
|
listView.itemTemplates = this._itemTemplatesString;
|
||||||
|
|
||||||
|
let itemTemplatesArray = <any>listView.itemTemplates;
|
||||||
|
|
||||||
|
TKUnit.assertEqual(itemTemplatesArray.length, 3, "itemTemplates array length");
|
||||||
|
|
||||||
|
let template0 = <KeyedTemplate>itemTemplatesArray[0];
|
||||||
|
TKUnit.assertEqual(template0.key, "red", "template0.key");
|
||||||
|
TKUnit.assertEqual((<Label>template0.createView()).text, "red", "template0 created view text");
|
||||||
|
|
||||||
|
let template1 = <KeyedTemplate>itemTemplatesArray[1];
|
||||||
|
TKUnit.assertEqual(template1.key, "green", "template1.key");
|
||||||
|
TKUnit.assertEqual((<Label>template1.createView()).text, "green", "template1 created view text");
|
||||||
|
|
||||||
|
let template2 = <KeyedTemplate>itemTemplatesArray[2];
|
||||||
|
TKUnit.assertEqual(template2.key, "blue", "template2.key");
|
||||||
|
TKUnit.assertEqual((<Label>template2.createView()).text, "blue", "template2 created view text");
|
||||||
|
}
|
||||||
|
|
||||||
|
public test_ItemTemplateSelector_CorrectTemplateIsUsed() {
|
||||||
|
let listView = this.testView;
|
||||||
|
listView.height = 200;
|
||||||
|
|
||||||
|
listView.itemTemplates = this._itemTemplatesString;
|
||||||
|
listView.itemTemplateSelector = "age % 2 === 0 ? 'red' : 'green'";
|
||||||
|
listView.items = ListViewTest.generateItemsForMultipleTemplatesTests(4);
|
||||||
|
TKUnit.wait(0.1);
|
||||||
|
|
||||||
|
let firstNativeElementText = this.getTextFromNativeElementAt(listView, 0);
|
||||||
|
let secondNativeElementText = this.getTextFromNativeElementAt(listView, 1);
|
||||||
|
|
||||||
|
TKUnit.assertEqual(firstNativeElementText, "red", "first element text");
|
||||||
|
TKUnit.assertEqual(secondNativeElementText, "green", "second element text");
|
||||||
|
}
|
||||||
|
|
||||||
|
public test_ItemTemplateSelector_TestVirtualization() {
|
||||||
|
let listView = this.testView;
|
||||||
|
listView.height = 300;
|
||||||
|
|
||||||
|
listView.itemTemplates = this._itemTemplatesString;
|
||||||
|
listView.itemTemplateSelector = "age % 2 === 0 ? 'red' : (age % 3 === 0 ? 'blue' : 'green')";
|
||||||
|
listView.items = ListViewTest.generateItemsForMultipleTemplatesTests(10);
|
||||||
|
TKUnit.wait(0.05);
|
||||||
|
|
||||||
|
// Forward
|
||||||
|
for(let i = 0, length = listView.items.length; i < length; i++){
|
||||||
|
listView.scrollToIndex(i);
|
||||||
|
TKUnit.wait(0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back
|
||||||
|
for(let i = listView.items.length - 1; i >= 0; i--){
|
||||||
|
listView.scrollToIndex(i);
|
||||||
|
TKUnit.wait(0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listView.android){
|
||||||
|
//(<any>listView)._dumpRealizedTemplates();
|
||||||
|
let realizedItems = <Map<android.view.View, View>>(<any>listView)._realizedItems;
|
||||||
|
TKUnit.assertTrue(realizedItems.size <= 6, 'Realized items must be 6 or less');
|
||||||
|
|
||||||
|
let realizedTemplates = <Map<string, Map<android.view.View, View>>>(<any>listView)._realizedTemplates;
|
||||||
|
TKUnit.assertEqual(realizedTemplates.size, 3, 'Realized templates');
|
||||||
|
TKUnit.assertTrue(realizedTemplates.get("red").size <= 2, 'Red realized items must be 2 or less');
|
||||||
|
TKUnit.assertTrue(realizedTemplates.get("green").size <= 2, 'Green realized items must be 2 or less');
|
||||||
|
TKUnit.assertTrue(realizedTemplates.get("blue").size <= 2, 'Blue realized items must be 2 or less');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _itemTemplatesString = `
|
||||||
|
<template key="red">
|
||||||
|
<Label text='red' style.backgroundColor='red' minHeight='100' maxHeight='100'/>
|
||||||
|
</template>
|
||||||
|
<template key='green'>
|
||||||
|
<Label text='green' style.backgroundColor='green' minHeight='100' maxHeight='100'/>
|
||||||
|
</template>
|
||||||
|
<template key='blue'>
|
||||||
|
<Label text='blue' style.backgroundColor='blue' minHeight='100' maxHeight='100'/>
|
||||||
|
</template>
|
||||||
|
`;
|
||||||
|
|
||||||
|
private static generateItemsForMultipleTemplatesTests(count: number): Array<Item> {
|
||||||
|
let items = new Array<Item>();
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
items.push({
|
||||||
|
text: "Item " + i,
|
||||||
|
age: i,
|
||||||
|
loadedCount: 0,
|
||||||
|
unloadedCount: 0,
|
||||||
|
onViewLoaded: function onViewLoaded(args) {
|
||||||
|
this.loadedCount++;
|
||||||
|
},
|
||||||
|
onViewUnloaded: function onViewUnloaded(args) {
|
||||||
|
this.unloadedCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Item {
|
interface Item {
|
||||||
text: string;
|
text: string;
|
||||||
|
age: number;
|
||||||
loadedCount: number;
|
loadedCount: number;
|
||||||
unloadedCount: number;
|
unloadedCount: number;
|
||||||
onViewLoaded: (args) => void;
|
onViewLoaded: (args) => void;
|
||||||
|
1
tns-core-modules/ui/builder/builder.d.ts
vendored
1
tns-core-modules/ui/builder/builder.d.ts
vendored
@ -5,6 +5,7 @@
|
|||||||
export function load(fileName: string, exports?: any): view.View;
|
export function load(fileName: string, exports?: any): view.View;
|
||||||
export function load(options: LoadOptions): view.View;
|
export function load(options: LoadOptions): view.View;
|
||||||
export function parse(value: string | view.Template, exports?: any): view.View;
|
export function parse(value: string | view.Template, exports?: any): view.View;
|
||||||
|
export function parseMultipleTemplates(value: string, exports?: any): Array<view.KeyedTemplate>;
|
||||||
|
|
||||||
export interface LoadOptions {
|
export interface LoadOptions {
|
||||||
path: string;
|
path: string;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {debug, ScopeError, SourceError, Source} from "utils/debug";
|
import {debug, ScopeError, SourceError, Source} from "utils/debug";
|
||||||
import * as xml from "xml";
|
import * as xml from "xml";
|
||||||
import {View, Template} from "ui/core/view";
|
import {View, Template, KeyedTemplate} from "ui/core/view";
|
||||||
import {File, path, knownFolders} from "file-system";
|
import {File, path, knownFolders} from "file-system";
|
||||||
import {isString, isFunction, isDefined} from "utils/types";
|
import {isString, isFunction, isDefined} from "utils/types";
|
||||||
import {ComponentModule, setPropertyValue, getComponentModule} from "ui/builder/component-builder";
|
import {ComponentModule, setPropertyValue, getComponentModule} from "ui/builder/component-builder";
|
||||||
@ -34,11 +34,16 @@ export function parse(value: string | Template, context: any): View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return viewToReturn;
|
return viewToReturn;
|
||||||
} else if (isFunction(value)) {
|
} else if ( isFunction(value)) {
|
||||||
return (<Template>value)();
|
return (<Template>value)();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseMultipleTemplates(value: string, context: any): Array<KeyedTemplate> {
|
||||||
|
let dummyComponent = `<ListView><ListView.itemTemplates>${value}</ListView.itemTemplates></ListView>`;
|
||||||
|
return parseInternal(dummyComponent, context).component["itemTemplates"];
|
||||||
|
}
|
||||||
|
|
||||||
function parseInternal(value: string, context: any, uri?: string): ComponentModule {
|
function parseInternal(value: string, context: any, uri?: string): ComponentModule {
|
||||||
var start: xml2ui.XmlStringParser;
|
var start: xml2ui.XmlStringParser;
|
||||||
var ui: xml2ui.ComponentParser;
|
var ui: xml2ui.ComponentParser;
|
||||||
@ -347,8 +352,9 @@ namespace xml2ui {
|
|||||||
private _state: TemplateParser.State;
|
private _state: TemplateParser.State;
|
||||||
|
|
||||||
private parent: XmlStateConsumer;
|
private parent: XmlStateConsumer;
|
||||||
|
private _setTemplateProperty: boolean;
|
||||||
|
|
||||||
constructor(parent: XmlStateConsumer, templateProperty: TemplateProperty) {
|
constructor(parent: XmlStateConsumer, templateProperty: TemplateProperty, setTemplateProperty = true) {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
|
|
||||||
this._context = templateProperty.context;
|
this._context = templateProperty.context;
|
||||||
@ -356,6 +362,7 @@ namespace xml2ui {
|
|||||||
this._templateProperty = templateProperty;
|
this._templateProperty = templateProperty;
|
||||||
this._nestingLevel = 0;
|
this._nestingLevel = 0;
|
||||||
this._state = TemplateParser.State.EXPECTING_START;
|
this._state = TemplateParser.State.EXPECTING_START;
|
||||||
|
this._setTemplateProperty = setTemplateProperty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public parse(args: xml.ParserEvent): XmlStateConsumer {
|
public parse(args: xml.ParserEvent): XmlStateConsumer {
|
||||||
@ -395,29 +402,64 @@ namespace xml2ui {
|
|||||||
|
|
||||||
if (this._nestingLevel === 0) {
|
if (this._nestingLevel === 0) {
|
||||||
this._state = TemplateParser.State.FINISHED;
|
this._state = TemplateParser.State.FINISHED;
|
||||||
this.build();
|
|
||||||
|
if (this._setTemplateProperty && this._templateProperty.name in this._templateProperty.parent.component){
|
||||||
|
let template = this._build();
|
||||||
|
this._templateProperty.parent.component[this._templateProperty.name] = template;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private build() {
|
public _build(): Template {
|
||||||
if (this._templateProperty.name in this._templateProperty.parent.component) {
|
var context = this._context;
|
||||||
var context = this._context;
|
var errorFormat = this._templateProperty.errorFormat;
|
||||||
var errorFormat = this._templateProperty.errorFormat;
|
var sourceTracker = this._templateProperty.sourceTracker;
|
||||||
var sourceTracker = this._templateProperty.sourceTracker;
|
var template: Template = () => {
|
||||||
var template: Template = () => {
|
var start: xml2ui.XmlArgsReplay;
|
||||||
var start: xml2ui.XmlArgsReplay;
|
var ui: xml2ui.ComponentParser;
|
||||||
var ui: xml2ui.ComponentParser;
|
|
||||||
|
|
||||||
(start = new xml2ui.XmlArgsReplay(this._recordedXmlStream, errorFormat))
|
(start = new xml2ui.XmlArgsReplay(this._recordedXmlStream, errorFormat))
|
||||||
// No platform filter, it has been filtered allready
|
// No platform filter, it has been filtered allready
|
||||||
.pipe(new XmlStateParser(ui = new ComponentParser(context, errorFormat, sourceTracker)));
|
.pipe(new XmlStateParser(ui = new ComponentParser(context, errorFormat, sourceTracker)));
|
||||||
|
|
||||||
start.replay();
|
start.replay();
|
||||||
|
|
||||||
return ui.rootComponentModule.component;
|
return ui.rootComponentModule.component;
|
||||||
}
|
|
||||||
this._templateProperty.parent.component[this._templateProperty.name] = template;
|
|
||||||
}
|
}
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MultiTemplateParser implements XmlStateConsumer {
|
||||||
|
private _childParsers = new Array<TemplateParser>();
|
||||||
|
|
||||||
|
constructor(private parent: XmlStateConsumer, private templateProperty: TemplateProperty) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public parse(args: xml.ParserEvent): XmlStateConsumer {
|
||||||
|
if (args.eventType === xml.ParserEventType.StartElement && args.elementName === "template"){
|
||||||
|
let childParser = new TemplateParser(this, this.templateProperty, false);
|
||||||
|
childParser["key"] = args.attributes["key"];
|
||||||
|
this._childParsers.push(childParser);
|
||||||
|
return childParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.eventType === xml.ParserEventType.EndElement){
|
||||||
|
let name = ComponentParser.getComplexPropertyName(args.elementName);
|
||||||
|
if (name === this.templateProperty.name){
|
||||||
|
let templates = new Array<KeyedTemplate>();
|
||||||
|
for (let i = 0; i < this._childParsers.length; i++){
|
||||||
|
templates.push({
|
||||||
|
key: this._childParsers[i]["key"],
|
||||||
|
createView: this._childParsers[i]._build()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.templateProperty.parent.component[this.templateProperty.name] = templates;
|
||||||
|
return this.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -433,6 +475,7 @@ namespace xml2ui {
|
|||||||
|
|
||||||
private static KNOWNCOLLECTIONS = "knownCollections";
|
private static KNOWNCOLLECTIONS = "knownCollections";
|
||||||
private static KNOWNTEMPLATES = "knownTemplates";
|
private static KNOWNTEMPLATES = "knownTemplates";
|
||||||
|
private static KNOWNMULTITEMPLATES = "knownMultiTemplates";
|
||||||
|
|
||||||
public rootComponentModule: ComponentModule;
|
public rootComponentModule: ComponentModule;
|
||||||
|
|
||||||
@ -480,6 +523,19 @@ namespace xml2ui {
|
|||||||
sourceTracker: this.sourceTracker
|
sourceTracker: this.sourceTracker
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ComponentParser.isKnownMultiTemplate(name, parent.exports)) {
|
||||||
|
return new MultiTemplateParser(this, {
|
||||||
|
context: (parent ? getExports(parent.component) : null) || this.context, // Passing 'context' won't work if you set "codeFile" on the page
|
||||||
|
parent: parent,
|
||||||
|
name: name,
|
||||||
|
elementName: args.elementName,
|
||||||
|
templateItems: [],
|
||||||
|
errorFormat: this.error,
|
||||||
|
sourceTracker: this.sourceTracker
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
@ -550,7 +606,7 @@ namespace xml2ui {
|
|||||||
return isString(name) && name.indexOf(".") !== -1;
|
return isString(name) && name.indexOf(".") !== -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getComplexPropertyName(fullName: string): string {
|
public static getComplexPropertyName(fullName: string): string {
|
||||||
var name: string;
|
var name: string;
|
||||||
|
|
||||||
if (isString(fullName)) {
|
if (isString(fullName)) {
|
||||||
@ -565,6 +621,10 @@ namespace xml2ui {
|
|||||||
return ComponentParser.KNOWNTEMPLATES in exports && exports[ComponentParser.KNOWNTEMPLATES] && name in exports[ComponentParser.KNOWNTEMPLATES];
|
return ComponentParser.KNOWNTEMPLATES in exports && exports[ComponentParser.KNOWNTEMPLATES] && name in exports[ComponentParser.KNOWNTEMPLATES];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static isKnownMultiTemplate(name: string, exports: any): boolean {
|
||||||
|
return ComponentParser.KNOWNMULTITEMPLATES in exports && exports[ComponentParser.KNOWNMULTITEMPLATES] && name in exports[ComponentParser.KNOWNMULTITEMPLATES];
|
||||||
|
}
|
||||||
|
|
||||||
private static addToComplexProperty(parent: ComponentModule, complexProperty: ComponentParser.ComplexProperty, elementModule: ComponentModule) {
|
private static addToComplexProperty(parent: ComponentModule, complexProperty: ComponentParser.ComplexProperty, elementModule: ComponentModule) {
|
||||||
// If property name is known collection we populate array with elements.
|
// If property name is known collection we populate array with elements.
|
||||||
var parentComponent = <any>parent.component;
|
var parentComponent = <any>parent.component;
|
||||||
|
@ -164,7 +164,6 @@ export function getComponentModule(elementName: string, namespace: string, attri
|
|||||||
|
|
||||||
export function setPropertyValue(instance: View, instanceModule: Object, exports: Object, propertyName: string, propertyValue: any) {
|
export function setPropertyValue(instance: View, instanceModule: Object, exports: Object, propertyName: string, propertyValue: any) {
|
||||||
// Note: instanceModule can be null if we are loading custom compnenet with no code-behind.
|
// Note: instanceModule can be null if we are loading custom compnenet with no code-behind.
|
||||||
|
|
||||||
if (isBinding(propertyValue) && instance.bind) {
|
if (isBinding(propertyValue) && instance.bind) {
|
||||||
var bindOptions = getBindingOptions(propertyName, getBindingExpressionFromAttribute(propertyValue));
|
var bindOptions = getBindingOptions(propertyName, getBindingExpressionFromAttribute(propertyValue));
|
||||||
instance.bind({
|
instance.bind({
|
||||||
@ -173,7 +172,8 @@ export function setPropertyValue(instance: View, instanceModule: Object, exports
|
|||||||
expression: bindOptions[bindingConstants.expression],
|
expression: bindOptions[bindingConstants.expression],
|
||||||
twoWay: bindOptions[bindingConstants.twoWay]
|
twoWay: bindOptions[bindingConstants.twoWay]
|
||||||
}, bindOptions[bindingConstants.source]);
|
}, bindOptions[bindingConstants.source]);
|
||||||
} else if (isEventOrGesture(propertyName, instance)) {
|
}
|
||||||
|
else if (isEventOrGesture(propertyName, instance)) {
|
||||||
// Get the event handler from page module exports.
|
// Get the event handler from page module exports.
|
||||||
var handler = exports && exports[propertyValue];
|
var handler = exports && exports[propertyValue];
|
||||||
|
|
||||||
@ -181,7 +181,11 @@ export function setPropertyValue(instance: View, instanceModule: Object, exports
|
|||||||
if (isFunction(handler)) {
|
if (isFunction(handler)) {
|
||||||
instance.on(propertyName, handler);
|
instance.on(propertyName, handler);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else if (isKnownFunction(propertyName, instance) && isFunction(exports && exports[propertyValue])) {
|
||||||
|
instance[propertyName] = exports[propertyValue];
|
||||||
|
}
|
||||||
|
else {
|
||||||
let attrHandled = false;
|
let attrHandled = false;
|
||||||
let specialSetter = getSpecialPropertySetter(propertyName);
|
let specialSetter = getSpecialPropertySetter(propertyName);
|
||||||
if (!attrHandled && specialSetter) {
|
if (!attrHandled && specialSetter) {
|
||||||
@ -211,3 +215,11 @@ function isBinding(value: any): boolean {
|
|||||||
|
|
||||||
return isBinding;
|
return isBinding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For example, ListView.itemTemplateSelector
|
||||||
|
let KNOWN_FUNCTIONS = "knownFunctions";
|
||||||
|
function isKnownFunction(name: string, instance: View): boolean {
|
||||||
|
return instance.constructor
|
||||||
|
&& KNOWN_FUNCTIONS in instance.constructor
|
||||||
|
&& instance.constructor[KNOWN_FUNCTIONS].indexOf(name) !== -1;
|
||||||
|
}
|
15
tns-core-modules/ui/core/view.d.ts
vendored
15
tns-core-modules/ui/core/view.d.ts
vendored
@ -656,6 +656,21 @@ declare module "ui/core/view" {
|
|||||||
(): View;
|
(): View;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines an interface for Template with a key.
|
||||||
|
*/
|
||||||
|
interface KeyedTemplate {
|
||||||
|
/**
|
||||||
|
* The unique key of the template.
|
||||||
|
*/
|
||||||
|
key: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function that creates the view.
|
||||||
|
*/
|
||||||
|
createView: Template;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines an interface for adding arrays declared in xml.
|
* Defines an interface for adding arrays declared in xml.
|
||||||
*/
|
*/
|
||||||
|
@ -8,6 +8,8 @@ import * as builderModule from "ui/builder";
|
|||||||
import * as labelModule from "ui/label";
|
import * as labelModule from "ui/label";
|
||||||
import * as observableArrayModule from "data/observable-array";
|
import * as observableArrayModule from "data/observable-array";
|
||||||
import * as weakEventsModule from "ui/core/weak-event-listener";
|
import * as weakEventsModule from "ui/core/weak-event-listener";
|
||||||
|
import {isString, isFunction} from "utils/types";
|
||||||
|
import { Bindable } from "ui/core/bindable";
|
||||||
|
|
||||||
var builder: typeof builderModule;
|
var builder: typeof builderModule;
|
||||||
function ensureBuilder() {
|
function ensureBuilder() {
|
||||||
@ -39,6 +41,7 @@ function ensureWeakEvents() {
|
|||||||
|
|
||||||
var ITEMS = "items";
|
var ITEMS = "items";
|
||||||
var ITEMTEMPLATE = "itemTemplate";
|
var ITEMTEMPLATE = "itemTemplate";
|
||||||
|
var ITEMTEMPLATES = "itemTemplates";
|
||||||
var ISSCROLLING = "isScrolling";
|
var ISSCROLLING = "isScrolling";
|
||||||
var LISTVIEW = "ListView";
|
var LISTVIEW = "ListView";
|
||||||
var SEPARATORCOLOR = "separatorColor";
|
var SEPARATORCOLOR = "separatorColor";
|
||||||
@ -48,6 +51,10 @@ export module knownTemplates {
|
|||||||
export var itemTemplate = "itemTemplate";
|
export var itemTemplate = "itemTemplate";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export module knownMultiTemplates {
|
||||||
|
export var itemTemplates = "itemTemplates";
|
||||||
|
}
|
||||||
|
|
||||||
function onItemsPropertyChanged(data: dependencyObservable.PropertyChangeData) {
|
function onItemsPropertyChanged(data: dependencyObservable.PropertyChangeData) {
|
||||||
var listView = <ListView>data.object;
|
var listView = <ListView>data.object;
|
||||||
listView._onItemsPropertyChanged(data);
|
listView._onItemsPropertyChanged(data);
|
||||||
@ -58,6 +65,11 @@ function onItemTemplatePropertyChanged(data: dependencyObservable.PropertyChange
|
|||||||
listView.refresh();
|
listView.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onItemTemplatesPropertyChanged(data: dependencyObservable.PropertyChangeData) {
|
||||||
|
var listView = <ListView>data.object;
|
||||||
|
listView._onItemTemplatesPropertyChanged(data);
|
||||||
|
}
|
||||||
|
|
||||||
function onRowHeightPropertyChanged(data: dependencyObservable.PropertyChangeData) {
|
function onRowHeightPropertyChanged(data: dependencyObservable.PropertyChangeData) {
|
||||||
var listView = <ListView>data.object;
|
var listView = <ListView>data.object;
|
||||||
listView._onRowHeightPropertyChanged(data);
|
listView._onRowHeightPropertyChanged(data);
|
||||||
@ -67,6 +79,21 @@ export class ListView extends view.View implements definition.ListView {
|
|||||||
public static itemLoadingEvent = "itemLoading";
|
public static itemLoadingEvent = "itemLoading";
|
||||||
public static itemTapEvent = "itemTap";
|
public static itemTapEvent = "itemTap";
|
||||||
public static loadMoreItemsEvent = "loadMoreItems";
|
public static loadMoreItemsEvent = "loadMoreItems";
|
||||||
|
public static knownFunctions = ["itemTemplateSelector"]; //See component-builder.ts isKnownFunction
|
||||||
|
|
||||||
|
private _itemTemplateSelector: (item: any, index: number, items: any) => string;
|
||||||
|
private _itemTemplateSelectorBindable = new Bindable();
|
||||||
|
public _defaultTemplate: view.KeyedTemplate = {
|
||||||
|
key: "default",
|
||||||
|
createView: () => {
|
||||||
|
if (this.itemTemplate) {
|
||||||
|
ensureBuilder();
|
||||||
|
return builder.parse(this.itemTemplate, this);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public _itemTemplatesInternal = new Array<view.KeyedTemplate>(this._defaultTemplate);
|
||||||
|
|
||||||
public static separatorColorProperty = new dependencyObservable.Property(
|
public static separatorColorProperty = new dependencyObservable.Property(
|
||||||
SEPARATORCOLOR,
|
SEPARATORCOLOR,
|
||||||
@ -93,6 +120,16 @@ export class ListView extends view.View implements definition.ListView {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
public static itemTemplatesProperty = new dependencyObservable.Property(
|
||||||
|
ITEMTEMPLATES,
|
||||||
|
LISTVIEW,
|
||||||
|
new proxy.PropertyMetadata(
|
||||||
|
undefined,
|
||||||
|
dependencyObservable.PropertyMetadataSettings.AffectsLayout,
|
||||||
|
onItemTemplatesPropertyChanged
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
public static isScrollingProperty = new dependencyObservable.Property(
|
public static isScrollingProperty = new dependencyObservable.Property(
|
||||||
ISSCROLLING,
|
ISSCROLLING,
|
||||||
LISTVIEW,
|
LISTVIEW,
|
||||||
@ -126,6 +163,40 @@ export class ListView extends view.View implements definition.ListView {
|
|||||||
this._setValue(ListView.itemTemplateProperty, value);
|
this._setValue(ListView.itemTemplateProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get itemTemplates(): string | Array<view.KeyedTemplate> {
|
||||||
|
return this._getValue(ListView.itemTemplatesProperty);
|
||||||
|
}
|
||||||
|
set itemTemplates(value: string | Array<view.KeyedTemplate>) {
|
||||||
|
let newValue = value;
|
||||||
|
if (isString(newValue)){
|
||||||
|
ensureBuilder();
|
||||||
|
newValue = builder.parseMultipleTemplates(<string>newValue, this);
|
||||||
|
}
|
||||||
|
this._setValue(ListView.itemTemplatesProperty, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (isString(value)){
|
||||||
|
this._itemTemplateSelectorBindable.bind({
|
||||||
|
sourceProperty: null,
|
||||||
|
targetProperty: "templateKey",
|
||||||
|
expression: <string>value
|
||||||
|
});
|
||||||
|
this._itemTemplateSelector = (item: any, index: number, items: any) => {
|
||||||
|
item["$index"] = index;
|
||||||
|
this._itemTemplateSelectorBindable.bindingContext = item;
|
||||||
|
return this._itemTemplateSelectorBindable.get("templateKey");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (isFunction(value)) {
|
||||||
|
this._itemTemplateSelector = <((item: any, index: number, items: any) => string)>value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get isScrolling(): boolean {
|
get isScrolling(): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -156,17 +227,23 @@ export class ListView extends view.View implements definition.ListView {
|
|||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
public _getItemTemplateContent(index: number): view.View {
|
public _getItemTemplate(index: number): view.KeyedTemplate {
|
||||||
ensureBuilder();
|
let templateKey = "default";
|
||||||
|
if (this.itemTemplateSelector){
|
||||||
var v;
|
let dataItem = this._getDataItem(index);
|
||||||
if (this.itemTemplate && this.items) {
|
templateKey = this._itemTemplateSelector(dataItem, index, this.items);
|
||||||
v = builder.parse(this.itemTemplate, this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return v;
|
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.View, index: number) {
|
public _prepareItem(item: view.View, index: number) {
|
||||||
if (item) {
|
if (item) {
|
||||||
item.bindingContext = this._getDataItem(index);
|
item.bindingContext = this._getDataItem(index);
|
||||||
@ -211,4 +288,8 @@ export class ListView extends view.View implements definition.ListView {
|
|||||||
public _onRowHeightPropertyChanged(data: dependencyObservable.PropertyChangeData) {
|
public _onRowHeightPropertyChanged(data: dependencyObservable.PropertyChangeData) {
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public _onItemTemplatesPropertyChanged(data: dependencyObservable.PropertyChangeData) {
|
||||||
|
//
|
||||||
|
}
|
||||||
}
|
}
|
@ -42,7 +42,8 @@ function onSeparatorColorPropertyChanged(data: dependencyObservable.PropertyChan
|
|||||||
export class ListView extends common.ListView {
|
export class ListView extends common.ListView {
|
||||||
private _androidViewId: number = -1;
|
private _androidViewId: number = -1;
|
||||||
private _android: android.widget.ListView;
|
private _android: android.widget.ListView;
|
||||||
public _realizedItems = new Map<android.view.View, viewModule.View>();
|
public _realizedItems = new Map<android.view.View, viewModule.View>();
|
||||||
|
public _realizedTemplates = new Map<string, Map<android.view.View, viewModule.View>>();
|
||||||
|
|
||||||
public _createUI() {
|
public _createUI() {
|
||||||
this._android = new android.widget.ListView(this._context);
|
this._android = new android.widget.ListView(this._context);
|
||||||
@ -62,7 +63,8 @@ export class ListView extends common.ListView {
|
|||||||
onItemClick: function (parent: any, convertView: android.view.View, index: number, id: number) {
|
onItemClick: function (parent: any, convertView: android.view.View, index: number, id: number) {
|
||||||
let owner = that.get();
|
let owner = that.get();
|
||||||
if (owner) {
|
if (owner) {
|
||||||
owner.notify({ eventName: ITEMTAP, object: owner, index: index, view: owner._getRealizedView(convertView, index) });
|
let view = owner._realizedTemplates.get(owner._getItemTemplate(index).key).get(convertView);
|
||||||
|
owner.notify({ eventName: ITEMTAP, object: owner, index: index, view: view });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@ -83,7 +85,7 @@ export class ListView extends common.ListView {
|
|||||||
view.bindingContext = null;
|
view.bindingContext = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
(<android.widget.BaseAdapter>this.android.getAdapter()).notifyDataSetChanged();
|
(<android.widget.BaseAdapter>this.android.getAdapter()).notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,13 +118,16 @@ export class ListView extends common.ListView {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public _getRealizedView(convertView: android.view.View, index: number) {
|
public _dumpRealizedTemplates(){
|
||||||
if (!convertView) {
|
console.log(`Realized Templates:`);
|
||||||
return this._getItemTemplateContent(index);
|
this._realizedTemplates.forEach((value, index, map) => {
|
||||||
}
|
console.log(`\t${index}:`);
|
||||||
|
value.forEach((value, index, map) => {
|
||||||
return this._realizedItems.get(convertView);
|
console.log(`\t\t${index.hashCode()}: ${value}`);
|
||||||
}
|
});
|
||||||
|
});
|
||||||
|
console.log(`Realized Items Size: ${this._realizedItems.size}`);
|
||||||
|
}
|
||||||
|
|
||||||
private clearRealizedCells(): void {
|
private clearRealizedCells(): void {
|
||||||
// clear the cache
|
// clear the cache
|
||||||
@ -137,6 +142,21 @@ export class ListView extends common.ListView {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this._realizedItems.clear();
|
this._realizedItems.clear();
|
||||||
|
this._realizedTemplates.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public _onItemTemplatesPropertyChanged(data: dependencyObservable.PropertyChangeData) {
|
||||||
|
this._itemTemplatesInternal = new Array<viewModule.KeyedTemplate>(this._defaultTemplate);
|
||||||
|
if (data.newValue){
|
||||||
|
this._itemTemplatesInternal = this._itemTemplatesInternal.concat(data.newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.android){
|
||||||
|
ensureListViewAdapterClass();
|
||||||
|
this.android.setAdapter(new ListViewAdapterClass(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,7 +197,19 @@ function ensureListViewAdapterClass() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getViewTypeCount() {
|
||||||
|
return this._listView._itemTemplatesInternal.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getItemViewType(index: number) {
|
||||||
|
let template = this._listView._getItemTemplate(index);
|
||||||
|
let itemViewType = this._listView._itemTemplatesInternal.indexOf(template);
|
||||||
|
return itemViewType;
|
||||||
|
}
|
||||||
|
|
||||||
public getView(index: number, convertView: android.view.View, parent: android.view.ViewGroup): android.view.View {
|
public getView(index: number, convertView: android.view.View, parent: android.view.ViewGroup): android.view.View {
|
||||||
|
//this._listView._dumpRealizedTemplates();
|
||||||
|
|
||||||
if (!this._listView) {
|
if (!this._listView) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -187,7 +219,19 @@ function ensureListViewAdapterClass() {
|
|||||||
this._listView.notify({ eventName: LOADMOREITEMS, object: this._listView });
|
this._listView.notify({ eventName: LOADMOREITEMS, object: this._listView });
|
||||||
}
|
}
|
||||||
|
|
||||||
let view = this._listView._getRealizedView(convertView, index);
|
// Recycle an existing view or create a new one if needed.
|
||||||
|
let template = this._listView._getItemTemplate(index);
|
||||||
|
let view: viewModule.View;
|
||||||
|
if (convertView){
|
||||||
|
view = this._listView._realizedTemplates.get(template.key).get(convertView);
|
||||||
|
if (!view){
|
||||||
|
throw new Error(`There is no entry with key '${convertView}' in the realized views cache for template with key'${template.key}'.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
view = template.createView();
|
||||||
|
}
|
||||||
|
|
||||||
let args: definition.ItemEventData = {
|
let args: definition.ItemEventData = {
|
||||||
eventName: ITEMLOADING, object: this._listView, index: index, view: view,
|
eventName: ITEMLOADING, object: this._listView, index: index, view: view,
|
||||||
android: parent,
|
android: parent,
|
||||||
@ -225,6 +269,13 @@ function ensureListViewAdapterClass() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache the view for recycling
|
||||||
|
let realizedItemsForTemplateKey = this._listView._realizedTemplates.get(template.key);
|
||||||
|
if (!realizedItemsForTemplateKey){
|
||||||
|
realizedItemsForTemplateKey = new Map<android.view.View, viewModule.View>();
|
||||||
|
this._listView._realizedTemplates.set(template.key, realizedItemsForTemplateKey);
|
||||||
|
}
|
||||||
|
realizedItemsForTemplateKey.set(convertView, args.view);
|
||||||
this._listView._realizedItems.set(convertView, args.view);
|
this._listView._realizedItems.set(convertView, args.view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
tns-core-modules/ui/list-view/list-view.d.ts
vendored
10
tns-core-modules/ui/list-view/list-view.d.ts
vendored
@ -82,6 +82,16 @@ declare module "ui/list-view" {
|
|||||||
*/
|
*/
|
||||||
itemTemplate: string | view.Template;
|
itemTemplate: string | view.Template;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets or set the list of item templates for the item template selector
|
||||||
|
*/
|
||||||
|
itemTemplates: string | Array<view.KeyedTemplate>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that returns the appropriate ket template based on the data item.
|
||||||
|
*/
|
||||||
|
|
||||||
|
itemTemplateSelector: string | ((item: any, index: number, items: any) => string);
|
||||||
/**
|
/**
|
||||||
* Gets or set the items separator line color of the ListView.
|
* Gets or set the items separator line color of the ListView.
|
||||||
*/
|
*/
|
||||||
|
@ -16,7 +16,7 @@ function ensureColor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var CELLIDENTIFIER = "cell";
|
//var CELLIDENTIFIER = "cell";
|
||||||
var ITEMLOADING = common.ListView.itemLoadingEvent;
|
var ITEMLOADING = common.ListView.itemLoadingEvent;
|
||||||
var LOADMOREITEMS = common.ListView.loadMoreItemsEvent;
|
var LOADMOREITEMS = common.ListView.loadMoreItemsEvent;
|
||||||
var ITEMTAP = common.ListView.itemTapEvent;
|
var ITEMTAP = common.ListView.itemTapEvent;
|
||||||
@ -68,9 +68,11 @@ class DataSource extends NSObject implements UITableViewDataSource {
|
|||||||
|
|
||||||
public tableViewCellForRowAtIndexPath(tableView: UITableView, indexPath: NSIndexPath): UITableViewCell {
|
public tableViewCellForRowAtIndexPath(tableView: UITableView, indexPath: NSIndexPath): UITableViewCell {
|
||||||
// We call this method because ...ForIndexPath calls tableViewHeightForRowAtIndexPath immediately (before we can prepare and measure it).
|
// We call this method because ...ForIndexPath calls tableViewHeightForRowAtIndexPath immediately (before we can prepare and measure it).
|
||||||
let cell = <ListViewCell>(tableView.dequeueReusableCellWithIdentifier(CELLIDENTIFIER) || ListViewCell.new());
|
|
||||||
let owner = this._owner.get();
|
let owner = this._owner.get();
|
||||||
|
let cell: ListViewCell;
|
||||||
if (owner) {
|
if (owner) {
|
||||||
|
let template = owner._getItemTemplate(indexPath.row);
|
||||||
|
cell = <ListViewCell>(tableView.dequeueReusableCellWithIdentifier(template.key) || ListViewCell.new());
|
||||||
owner._prepareCell(cell, indexPath);
|
owner._prepareCell(cell, indexPath);
|
||||||
|
|
||||||
let cellView: view.View = cell.view;
|
let cellView: view.View = cell.view;
|
||||||
@ -83,6 +85,9 @@ class DataSource extends NSObject implements UITableViewDataSource {
|
|||||||
view.View.layoutChild(owner, cellView, 0, 0, width, cellHeight);
|
view.View.layoutChild(owner, cellView, 0, 0, width, cellHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
cell = <ListViewCell>ListViewCell.new();
|
||||||
|
}
|
||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,11 +96,13 @@ class UITableViewDelegateImpl extends NSObject implements UITableViewDelegate {
|
|||||||
public static ObjCProtocols = [UITableViewDelegate];
|
public static ObjCProtocols = [UITableViewDelegate];
|
||||||
|
|
||||||
private _owner: WeakRef<ListView>;
|
private _owner: WeakRef<ListView>;
|
||||||
private _measureCell: ListViewCell;
|
|
||||||
|
private _measureCellMap: Map<string, ListViewCell>;
|
||||||
|
|
||||||
public static initWithOwner(owner: WeakRef<ListView>): UITableViewDelegateImpl {
|
public static initWithOwner(owner: WeakRef<ListView>): UITableViewDelegateImpl {
|
||||||
let delegate = <UITableViewDelegateImpl>UITableViewDelegateImpl.new();
|
let delegate = <UITableViewDelegateImpl>UITableViewDelegateImpl.new();
|
||||||
delegate._owner = owner;
|
delegate._owner = owner;
|
||||||
|
delegate._measureCellMap = new Map<string, ListViewCell>();
|
||||||
return delegate;
|
return delegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,10 +141,11 @@ class UITableViewDelegateImpl extends NSObject implements UITableViewDelegate {
|
|||||||
|
|
||||||
if (utils.ios.MajorVersion < 8 || height === undefined) {
|
if (utils.ios.MajorVersion < 8 || height === undefined) {
|
||||||
// in iOS 7.1 (or iOS8+ after call to scrollToRowAtIndexPath:atScrollPosition:animated:) this method is called before tableViewCellForRowAtIndexPath so we need fake cell to measure its content.
|
// in iOS 7.1 (or iOS8+ after call to scrollToRowAtIndexPath:atScrollPosition:animated:) this method is called before tableViewCellForRowAtIndexPath so we need fake cell to measure its content.
|
||||||
let cell = this._measureCell;
|
let template = owner._getItemTemplate(indexPath.row);
|
||||||
|
let cell = this._measureCellMap.get(template.key);
|
||||||
if (!cell) {
|
if (!cell) {
|
||||||
this._measureCell = (<any>tableView.dequeueReusableCellWithIdentifier(CELLIDENTIFIER)) || ListViewCell.new();
|
cell = (<any>tableView.dequeueReusableCellWithIdentifier(template.key)) || ListViewCell.new();
|
||||||
cell = this._measureCell;
|
this._measureCellMap.set(template.key, cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
height = owner._prepareCell(cell, indexPath);
|
height = owner._prepareCell(cell, indexPath);
|
||||||
@ -214,7 +222,7 @@ export class ListView extends common.ListView {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this._ios = UITableView.new();
|
this._ios = UITableView.new();
|
||||||
this._ios.registerClassForCellReuseIdentifier(ListViewCell.class(), CELLIDENTIFIER);
|
this._ios.registerClassForCellReuseIdentifier(ListViewCell.class(), this._defaultTemplate.key);
|
||||||
this._ios.autoresizingMask = UIViewAutoresizing.None;
|
this._ios.autoresizingMask = UIViewAutoresizing.None;
|
||||||
this._ios.estimatedRowHeight = DEFAULT_HEIGHT;
|
this._ios.estimatedRowHeight = DEFAULT_HEIGHT;
|
||||||
this._ios.rowHeight = UITableViewAutomaticDimension;
|
this._ios.rowHeight = UITableViewAutomaticDimension;
|
||||||
@ -224,6 +232,18 @@ export class ListView extends common.ListView {
|
|||||||
this._map = new Map<ListViewCell, view.View>();
|
this._map = new Map<ListViewCell, view.View>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public _onItemTemplatesPropertyChanged(data: dependencyObservable.PropertyChangeData) {
|
||||||
|
this._itemTemplatesInternal = new Array<view.KeyedTemplate>(this._defaultTemplate);
|
||||||
|
if (data.newValue) {
|
||||||
|
for(let i = 0, length = data.newValue.length; i < length; i++){
|
||||||
|
let template = <view.KeyedTemplate>data.newValue[i];
|
||||||
|
this._ios.registerClassForCellReuseIdentifier(ListViewCell.class(), template.key);
|
||||||
|
}
|
||||||
|
this._itemTemplatesInternal = this._itemTemplatesInternal.concat(data.newValue);
|
||||||
|
}
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
public onLoaded() {
|
public onLoaded() {
|
||||||
super.onLoaded();
|
super.onLoaded();
|
||||||
if (this._isDataDirty) {
|
if (this._isDataDirty) {
|
||||||
@ -336,7 +356,7 @@ export class ListView extends common.ListView {
|
|||||||
this._preparingCell = true;
|
this._preparingCell = true;
|
||||||
let view = cell.view;
|
let view = cell.view;
|
||||||
if (!view) {
|
if (!view) {
|
||||||
view = this._getItemTemplateContent(indexPath.row);
|
view = this._getItemTemplate(indexPath.row).createView();
|
||||||
}
|
}
|
||||||
|
|
||||||
let args = notifyForItemAtIndex(this, cell, view, ITEMLOADING, indexPath);
|
let args = notifyForItemAtIndex(this, cell, view, ITEMLOADING, indexPath);
|
||||||
|
Reference in New Issue
Block a user