ListView item template selector

This commit is contained in:
Rossen Hristov
2016-10-03 17:54:34 +03:00
parent 8ce37215f7
commit cd6039ce6c
9 changed files with 436 additions and 51 deletions

View File

@ -8,6 +8,7 @@ import utils = require("utils/utils");
import { Label } from "ui/label";
import helper = require("../helper");
import { Page } from "ui/page";
import { View, KeyedTemplate } from "ui/core/view";
// >> article-require-listview-module
import listViewModule = require("ui/list-view");
@ -581,7 +582,7 @@ export class ListViewTest extends testModule.UITest<listViewModule.ListView> {
if (platform.isAndroid) {
// simulates Angular way of removing views
(<any>listView)._realizedItems.forEach((view, nativeView, map) => {
console.log("view: " + view);
//console.log("view: " + view);
listView._removeView(view);
});
this.tearDown();
@ -640,6 +641,7 @@ export class ListViewTest extends testModule.UITest<listViewModule.ListView> {
for (let i = 0; i < count; i++) {
items.push({
text: "Item " + i,
age: i,
loadedCount: 0,
unloadedCount: 0,
onViewLoaded: function onViewLoaded(args) {
@ -767,10 +769,143 @@ export class ListViewTest extends testModule.UITest<listViewModule.ListView> {
private waitUntilListViewReady(): void {
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(0, items[0]);
TKUnit.assertEqual(templateKey0, "red", "itemTemplateSelector result for first item");
let templateKey1 = itemTemplateSelectorFunction(1, items[1]);
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 {
text: string;
age: number;
loadedCount: number;
unloadedCount: number;
onViewLoaded: (args) => void;

View File

@ -5,6 +5,7 @@
export function load(fileName: string, exports?: any): view.View;
export function load(options: LoadOptions): 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 {
path: string;

View File

@ -1,6 +1,6 @@
import {debug, ScopeError, SourceError, Source} from "utils/debug";
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 {isString, isFunction, isDefined} from "utils/types";
import {ComponentModule, setPropertyValue, getComponentModule} from "ui/builder/component-builder";
@ -39,6 +39,11 @@ export function parse(value: string | Template, context: any): View {
}
}
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 {
var start: xml2ui.XmlStringParser;
var ui: xml2ui.ComponentParser;
@ -347,8 +352,9 @@ namespace xml2ui {
private _state: TemplateParser.State;
private parent: XmlStateConsumer;
private _setTemplateProperty: boolean;
constructor(parent: XmlStateConsumer, templateProperty: TemplateProperty) {
constructor(parent: XmlStateConsumer, templateProperty: TemplateProperty, setTemplateProperty = true) {
this.parent = parent;
this._context = templateProperty.context;
@ -356,6 +362,7 @@ namespace xml2ui {
this._templateProperty = templateProperty;
this._nestingLevel = 0;
this._state = TemplateParser.State.EXPECTING_START;
this._setTemplateProperty = setTemplateProperty;
}
public parse(args: xml.ParserEvent): XmlStateConsumer {
@ -395,12 +402,15 @@ namespace xml2ui {
if (this._nestingLevel === 0) {
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() {
if (this._templateProperty.name in this._templateProperty.parent.component) {
public _build(): Template {
var context = this._context;
var errorFormat = this._templateProperty.errorFormat;
var sourceTracker = this._templateProperty.sourceTracker;
@ -416,9 +426,41 @@ namespace xml2ui {
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;
}
}
export namespace TemplateParser {
@ -433,6 +475,7 @@ namespace xml2ui {
private static KNOWNCOLLECTIONS = "knownCollections";
private static KNOWNTEMPLATES = "knownTemplates";
private static KNOWNMULTITEMPLATES = "knownMultiTemplates";
public rootComponentModule: ComponentModule;
@ -481,6 +524,19 @@ namespace xml2ui {
});
}
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 {
var componentModule: ComponentModule;
@ -550,7 +606,7 @@ namespace xml2ui {
return isString(name) && name.indexOf(".") !== -1;
}
private static getComplexPropertyName(fullName: string): string {
public static getComplexPropertyName(fullName: string): string {
var name: string;
if (isString(fullName)) {
@ -565,6 +621,10 @@ namespace xml2ui {
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) {
// If property name is known collection we populate array with elements.
var parentComponent = <any>parent.component;

View File

@ -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) {
// Note: instanceModule can be null if we are loading custom compnenet with no code-behind.
if (isBinding(propertyValue) && instance.bind) {
var bindOptions = getBindingOptions(propertyName, getBindingExpressionFromAttribute(propertyValue));
instance.bind({
@ -181,7 +180,12 @@ export function setPropertyValue(instance: View, instanceModule: Object, exports
if (isFunction(handler)) {
instance.on(propertyName, handler);
}
} else {
}
//TODO_ITS: This code may break some people if they have an export named like one of their attribue values.
else if (isFunction(exports && exports[propertyValue])) {
instance[propertyName] = exports[propertyValue];
}
else {
let attrHandled = false;
let specialSetter = getSpecialPropertySetter(propertyName);
if (!attrHandled && specialSetter) {

View File

@ -656,6 +656,21 @@ declare module "ui/core/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.
*/

View File

@ -8,6 +8,8 @@ import * as builderModule from "ui/builder";
import * as labelModule from "ui/label";
import * as observableArrayModule from "data/observable-array";
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;
function ensureBuilder() {
@ -39,6 +41,7 @@ function ensureWeakEvents() {
var ITEMS = "items";
var ITEMTEMPLATE = "itemTemplate";
var ITEMTEMPLATES = "itemTemplates";
var ISSCROLLING = "isScrolling";
var LISTVIEW = "ListView";
var SEPARATORCOLOR = "separatorColor";
@ -48,6 +51,10 @@ export module knownTemplates {
export var itemTemplate = "itemTemplate";
}
export module knownMultiTemplates {
export var itemTemplates = "itemTemplates";
}
function onItemsPropertyChanged(data: dependencyObservable.PropertyChangeData) {
var listView = <ListView>data.object;
listView._onItemsPropertyChanged(data);
@ -58,6 +65,11 @@ function onItemTemplatePropertyChanged(data: dependencyObservable.PropertyChange
listView.refresh();
}
function onItemTemplatesPropertyChanged(data: dependencyObservable.PropertyChangeData) {
var listView = <ListView>data.object;
listView._onItemTemplatesPropertyChanged(data);
}
function onRowHeightPropertyChanged(data: dependencyObservable.PropertyChangeData) {
var listView = <ListView>data.object;
listView._onRowHeightPropertyChanged(data);
@ -68,6 +80,20 @@ export class ListView extends view.View implements definition.ListView {
public static itemTapEvent = "itemTap";
public static loadMoreItemsEvent = "loadMoreItems";
private _itemTemplateSelector: (index: number, item: 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(
SEPARATORCOLOR,
LISTVIEW,
@ -93,6 +119,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(
ISSCROLLING,
LISTVIEW,
@ -126,6 +162,40 @@ export class ListView extends view.View implements definition.ListView {
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 | ((index: number, item: any) => string) {
return this._itemTemplateSelector;
}
set itemTemplateSelector(value: string | ((index: number, item: any) => string)) {
if (isString(value)){
this._itemTemplateSelectorBindable.bind({
sourceProperty: null,
targetProperty: "templateKey",
expression: <string>value
});
this._itemTemplateSelector = (index: number, item: any) => {
item["$index"] = index;
this._itemTemplateSelectorBindable.bindingContext = item;
return this._itemTemplateSelectorBindable.get("templateKey");
};
}
else if (isFunction(value)) {
this._itemTemplateSelector = <((index: number, item: any) => string)>value;
}
}
get isScrolling(): boolean {
return false;
}
@ -156,15 +226,21 @@ export class ListView extends view.View implements definition.ListView {
//
}
public _getItemTemplateContent(index: number): view.View {
ensureBuilder();
var v;
if (this.itemTemplate && this.items) {
v = builder.parse(this.itemTemplate, this);
public _getItemTemplate(index: number): view.KeyedTemplate {
let templateKey = "default";
if (this.itemTemplateSelector){
let dataItem = this._getDataItem(index);
templateKey = this._itemTemplateSelector(index, dataItem);
}
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) {
@ -211,4 +287,8 @@ export class ListView extends view.View implements definition.ListView {
public _onRowHeightPropertyChanged(data: dependencyObservable.PropertyChangeData) {
this.refresh();
}
public _onItemTemplatesPropertyChanged(data: dependencyObservable.PropertyChangeData) {
//
}
}

View File

@ -8,6 +8,7 @@ import definition = require("ui/list-view");
import {ProxyViewContainer} from "ui/proxy-view-container";
import * as layoutBase from "ui/layouts/layout-base";
import * as colorModule from "color";
import * as traceModule from "trace";
let color: typeof colorModule;
function ensureColor() {
@ -16,6 +17,13 @@ function ensureColor() {
}
}
var trace: typeof traceModule;
function ensureTrace() {
if (!trace) {
trace = require("trace");
}
}
let ITEMLOADING = common.ListView.itemLoadingEvent;
let LOADMOREITEMS = common.ListView.loadMoreItemsEvent;
let ITEMTAP = common.ListView.itemTapEvent;
@ -43,6 +51,7 @@ export class ListView extends common.ListView {
private _androidViewId: number = -1;
private _android: android.widget.ListView;
public _realizedItems = new Map<android.view.View, viewModule.View>();
public _realizedTemplates = new Map<string, Map<android.view.View, viewModule.View>>();
public _createUI() {
this._android = new android.widget.ListView(this._context);
@ -62,7 +71,8 @@ export class ListView extends common.ListView {
onItemClick: function (parent: any, convertView: android.view.View, index: number, id: number) {
let owner = that.get();
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 });
}
}
}));
@ -116,12 +126,15 @@ export class ListView extends common.ListView {
});
}
public _getRealizedView(convertView: android.view.View, index: number) {
if (!convertView) {
return this._getItemTemplateContent(index);
}
return this._realizedItems.get(convertView);
public _dumpRealizedTemplates(){
console.log(`Realized Templates:`);
this._realizedTemplates.forEach((value, index, map) => {
console.log(`\t${index}:`);
value.forEach((value, index, map) => {
console.log(`\t\t${index.hashCode()}: ${value}`);
});
});
console.log(`Realized Items Size: ${this._realizedItems.size}`);
}
private clearRealizedCells(): void {
@ -137,6 +150,21 @@ export class ListView extends common.ListView {
});
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 +205,20 @@ function ensureListViewAdapterClass() {
return true;
}
public getViewTypeCount() {
let viewTypeCount = this._listView._itemTemplatesInternal.length;
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 {
//this._listView._dumpRealizedTemplates();
if (!this._listView) {
return null;
}
@ -187,7 +228,19 @@ function ensureListViewAdapterClass() {
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 = {
eventName: ITEMLOADING, object: this._listView, index: index, view: view,
android: parent,
@ -225,6 +278,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);
}

View File

@ -82,6 +82,16 @@ declare module "ui/list-view" {
*/
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 | ((index: number, item: any) => string);
/**
* Gets or set the items separator line color of the ListView.
*/

View File

@ -16,7 +16,7 @@ function ensureColor() {
}
}
var CELLIDENTIFIER = "cell";
//var CELLIDENTIFIER = "cell";
var ITEMLOADING = common.ListView.itemLoadingEvent;
var LOADMOREITEMS = common.ListView.loadMoreItemsEvent;
var ITEMTAP = common.ListView.itemTapEvent;
@ -68,9 +68,11 @@ class DataSource extends NSObject implements UITableViewDataSource {
public tableViewCellForRowAtIndexPath(tableView: UITableView, indexPath: NSIndexPath): UITableViewCell {
// 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 cell: ListViewCell;
if (owner) {
let template = owner._getItemTemplate(indexPath.row);
cell = <ListViewCell>(tableView.dequeueReusableCellWithIdentifier(template.key) || ListViewCell.new());
owner._prepareCell(cell, indexPath);
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);
}
}
else {
cell = <ListViewCell>ListViewCell.new();
}
return cell;
}
}
@ -91,11 +96,13 @@ class UITableViewDelegateImpl extends NSObject implements UITableViewDelegate {
public static ObjCProtocols = [UITableViewDelegate];
private _owner: WeakRef<ListView>;
private _measureCell: ListViewCell;
private _measureCellMap: Map<string, ListViewCell>;
public static initWithOwner(owner: WeakRef<ListView>): UITableViewDelegateImpl {
let delegate = <UITableViewDelegateImpl>UITableViewDelegateImpl.new();
delegate._owner = owner;
delegate._measureCellMap = new Map<string, ListViewCell>();
return delegate;
}
@ -134,10 +141,11 @@ class UITableViewDelegateImpl extends NSObject implements UITableViewDelegate {
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.
let cell = this._measureCell;
let template = owner._getItemTemplate(indexPath.row);
let cell = this._measureCellMap.get(template.key);
if (!cell) {
this._measureCell = (<any>tableView.dequeueReusableCellWithIdentifier(CELLIDENTIFIER)) || ListViewCell.new();
cell = this._measureCell;
cell = (<any>tableView.dequeueReusableCellWithIdentifier(template.key)) || ListViewCell.new();
this._measureCellMap.set(template.key, cell);
}
height = owner._prepareCell(cell, indexPath);
@ -214,7 +222,7 @@ export class ListView extends common.ListView {
constructor() {
super();
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.estimatedRowHeight = DEFAULT_HEIGHT;
this._ios.rowHeight = UITableViewAutomaticDimension;
@ -224,6 +232,18 @@ export class ListView extends common.ListView {
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() {
super.onLoaded();
if (this._isDataDirty) {
@ -336,7 +356,7 @@ export class ListView extends common.ListView {
this._preparingCell = true;
let view = cell.view;
if (!view) {
view = this._getItemTemplateContent(indexPath.row);
view = this._getItemTemplate(indexPath.row).createView();
}
let args = notifyForItemAtIndex(this, cell, view, ITEMLOADING, indexPath);