diff --git a/CrossPlatformModules.csproj b/CrossPlatformModules.csproj
index 8bafda30a..34cb2a8d7 100644
--- a/CrossPlatformModules.csproj
+++ b/CrossPlatformModules.csproj
@@ -76,6 +76,9 @@
list-view.xml
+
+ repeater.xml
+
main-page.xml
@@ -150,6 +153,7 @@
+
time-picker-tests-native.d.ts
@@ -347,6 +351,10 @@
trace.d.ts
+
+ repeater.d.ts
+
+
list-picker.d.ts
@@ -592,6 +600,7 @@
+
@@ -1528,6 +1537,9 @@
PreserveNewest
+
+ PreserveNewest
+
@@ -1590,7 +1602,7 @@
False
-
+
\ No newline at end of file
diff --git a/apps/gallery-app/main-page.xml b/apps/gallery-app/main-page.xml
index 3240e80fa..8fdfa0701 100644
--- a/apps/gallery-app/main-page.xml
+++ b/apps/gallery-app/main-page.xml
@@ -32,6 +32,7 @@
+
diff --git a/apps/gallery-app/views/repeater.ts b/apps/gallery-app/views/repeater.ts
new file mode 100644
index 000000000..4d1212ea1
--- /dev/null
+++ b/apps/gallery-app/views/repeater.ts
@@ -0,0 +1,10 @@
+export function pageLoaded(args) {
+ var page = args.object;
+
+ var itemsArr = [];
+ for (var i = 1; i <= 64; i++) {
+ itemsArr.push({ title: "List item " + i });
+ }
+
+ page.bindingContext = { items: itemsArr };
+}
\ No newline at end of file
diff --git a/apps/gallery-app/views/repeater.xml b/apps/gallery-app/views/repeater.xml
new file mode 100644
index 000000000..5dbc7828e
--- /dev/null
+++ b/apps/gallery-app/views/repeater.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/tests/testRunner.ts b/apps/tests/testRunner.ts
index 86a2a72a8..e9dd5c237 100644
--- a/apps/tests/testRunner.ts
+++ b/apps/tests/testRunner.ts
@@ -69,6 +69,7 @@ allTests["DATE-PICKER"] = require("./ui/date-picker/date-picker-tests");
allTests["TIME-PICKER"] = require("./ui/time-picker/time-picker-tests");
allTests["WEB-VIEW"] = require("./ui/web-view/web-view-tests");
allTests["WEAK-EVENTS"] = require("./weak-event-listener-tests");
+allTests["REPEATER"] = require("./ui/repeater/repeater-tests");
if (!isRunningOnEmulator()) {
allTests["LOCATION"] = require("./location-tests");
diff --git a/apps/tests/ui/repeater/repeater-tests.ts b/apps/tests/ui/repeater/repeater-tests.ts
new file mode 100644
index 000000000..8888b1c72
--- /dev/null
+++ b/apps/tests/ui/repeater/repeater-tests.ts
@@ -0,0 +1,420 @@
+import TKUnit = require("../../TKUnit");
+import app = require("application");
+import helper = require("../helper");
+import viewModule = require("ui/core/view");
+import stackLayoutModule = require("ui/layouts/stack-layout");
+
+//
+// # Repeater
+// Using a Repeater requires the repeater module.
+// ``` JavaScript
+import repeaterModule = require("ui/repeater");
+// ```
+// Other modules which will be used in the code samples in this article:
+// ``` JavaScript
+import observableArray = require("data/observable-array");
+import labelModule = require("ui/label");
+// ```
+
+// ### Binding the Repeater items property to collection in the view-model.
+//```XML
+//
+// {%raw%}{%endraw%}
+//
+//```
+
+// ### Define the Repeater itemTemplate property.
+//```XML
+//
+// {%raw%}
+//
+//
+//
+// {%endraw%}
+//
+//```
+
+// ### Define the Repeater itemsLayout property. Default is .
+//```XML
+//
+// {%raw%}
+//
+//
+//
+// {%endraw%}
+//
+//```
+
+// ### Repeater with WrapLayout inside ScrollView.
+//```XML
+//
+// {%raw%}
+//
+//
+//
+//
+//
+//
+//
+//
+// {%endraw%}
+//
+//```
+
+//
+
+var ASYNC = 0.2;
+var FEW_ITEMS = [0, 1, 2];
+var MANY_ITEMS = [];
+for (var i = 0; i < 100; i++) {
+ MANY_ITEMS[i] = i;
+}
+
+export function test_set_items_to_array_loads_all_items() {
+ var repeater = new repeaterModule.Repeater();
+
+ function testAction(views: Array) {
+ //
+ // ### Using Repeater with Array
+ // ``` JavaScript
+ var colors = ["red", "green", "blue"];
+ repeater.items = colors;
+ // ```
+ //
+
+ TKUnit.wait(ASYNC);
+
+ TKUnit.assert(getChildAtText(repeater, 0) === "red", "Item not created for index 0");
+ TKUnit.assert(getChildAtText(repeater, 1) === "green", "Item not created for index 1");
+ TKUnit.assert(getChildAtText(repeater, 2) === "blue", "Item not created for index 2");
+ };
+
+ helper.buildUIAndRunTest(repeater, testAction);
+}
+
+export function test_set_items_to_array_creates_views() {
+ var repeater = new repeaterModule.Repeater();
+
+ function testAction(views: Array) {
+ repeater.items = FEW_ITEMS;
+
+ TKUnit.wait(ASYNC);
+ TKUnit.assertEqual(getChildrenCount(repeater), FEW_ITEMS.length, "views count.");
+ };
+
+ helper.buildUIAndRunTest(repeater, testAction);
+}
+
+export function test_refresh_after_adding_items_to_array_loads_new_items() {
+
+ var repeater = new repeaterModule.Repeater();
+
+ function testAction(views: Array) {
+ var colors = ["red", "green", "blue"];
+ repeater.items = colors;
+
+ TKUnit.wait(ASYNC);
+ TKUnit.assertEqual(getChildrenCount(repeater), colors.length, "views count.");
+ //
+ // > Note, that changing the array after the repeater is shown will not update the UI.
+ // You can force-update the UI using the refresh() method.
+ // ``` JavaScript
+ colors.push("yellow");
+ //// Manually trigger the update so that the new color is shown.
+ repeater.refresh();
+ // ```
+ //
+ TKUnit.wait(ASYNC);
+ TKUnit.assertEqual(getChildrenCount(repeater), colors.length, "views count.");
+ };
+
+ helper.buildUIAndRunTest(repeater, testAction);
+}
+
+export function test_refresh_reloads_all_items() {
+ var repeater = new repeaterModule.Repeater();
+
+ function testAction(views: Array) {
+ var testStarted = false;
+
+ var itemsToBind = >FEW_ITEMS;
+
+ repeater.items = itemsToBind;
+
+ TKUnit.wait(ASYNC);
+ testStarted = true;
+
+ itemsToBind[0] = "red";
+ itemsToBind[1] = "green";
+ itemsToBind[2] = "blue";
+
+ repeater.refresh();
+
+ TKUnit.wait(ASYNC);
+
+ TKUnit.assert(getChildAtText(repeater, 0) === "red", "Item not created for index 0");
+ TKUnit.assert(getChildAtText(repeater, 1) === "green", "Item not created for index 1");
+ TKUnit.assert(getChildAtText(repeater, 2) === "blue", "Item not created for index 2");
+ };
+
+ helper.buildUIAndRunTest(repeater, testAction);
+}
+
+export function test_set_itmes_to_null_clears_items() {
+ var repeater = new repeaterModule.Repeater();
+
+ function testAction(views: Array) {
+ repeater.items = FEW_ITEMS;
+ TKUnit.wait(ASYNC);
+ TKUnit.assertEqual(getChildrenCount(repeater), FEW_ITEMS.length, "views count.");
+
+ repeater.items = null;
+ TKUnit.wait(ASYNC);
+ TKUnit.assertEqual(getChildrenCount(repeater), 0, "views count.");
+ };
+
+ helper.buildUIAndRunTest(repeater, testAction);
+}
+
+export function test_set_itmeLayout_accepted() {
+ //
+ // ### Using Repeater with different layout.
+ // ``` JavaScript
+ var repeater = new repeaterModule.Repeater();
+ var stackLayout = new stackLayoutModule.StackLayout();
+ stackLayout.orientation = "horizontal";
+ repeater.itemsLayout = stackLayout;
+ // ```
+ //
+
+ function testAction(views: Array) {
+
+ repeater.items = FEW_ITEMS;
+ TKUnit.wait(ASYNC);
+ TKUnit.assert((repeater.itemsLayout).orientation === "horizontal", "views count.");
+ TKUnit.assertEqual(getChildrenCount(repeater), FEW_ITEMS.length, "views count.");
+ };
+
+ helper.buildUIAndRunTest(repeater, testAction);
+}
+
+export function test_set_itmes_to_undefiend_clears_items() {
+ var repeater = new repeaterModule.Repeater();
+
+ function testAction(views: Array) {
+ repeater.items = FEW_ITEMS;
+ TKUnit.wait(ASYNC);
+ TKUnit.assertEqual(getChildrenCount(repeater), FEW_ITEMS.length, "views count.");
+
+ repeater.items = undefined;
+ TKUnit.wait(ASYNC);
+ TKUnit.assertEqual(getChildrenCount(repeater), 0, "views count.");
+ };
+
+ helper.buildUIAndRunTest(repeater, testAction);
+}
+
+export function test_set_itmes_to_different_source_loads_new_items() {
+ var repeater = new repeaterModule.Repeater();
+
+ function testAction(views: Array) {
+ repeater.items = [1, 2, 3];
+ TKUnit.wait(ASYNC);
+ TKUnit.assertEqual(getChildrenCount(repeater), 3, "views count.");
+
+ repeater.items = ["a", "b", "c", "d"];
+ TKUnit.wait(ASYNC);
+ TKUnit.assertEqual(getChildrenCount(repeater), 4, "views count.");
+ };
+
+ helper.buildUIAndRunTest(repeater, testAction);
+}
+
+export function test_set_items_to_observable_array_loads_all_items() {
+ var repeater = new repeaterModule.Repeater();
+
+ function testAction(views: Array) {
+ //
+ // ### Using Repeater with ObservableArray
+ // ``` JavaScript
+ var colors = new observableArray.ObservableArray(["red", "green", "blue"]);
+ repeater.items = colors;
+ // ```
+ //
+
+ TKUnit.assert(getChildAtText(repeater, 0) === "red", "Item not created for index 0");
+ TKUnit.assert(getChildAtText(repeater, 1) === "green", "Item not created for index 1");
+ TKUnit.assert(getChildAtText(repeater, 2) === "blue", "Item not created for index 2");
+ };
+
+ helper.buildUIAndRunTest(repeater, testAction);
+}
+
+export function test_add_to_observable_array_refreshes_the_Repeater() {
+ var repeater = new repeaterModule.Repeater();
+
+ function testAction(views: Array) {
+ var colors = new observableArray.ObservableArray(["red", "green", "blue"]);
+ repeater.items = colors;
+
+ TKUnit.wait(ASYNC);
+ TKUnit.assertEqual(getChildrenCount(repeater), 3, "getChildrenCount");
+
+ //
+ // > When using ObservableArray the repeater will be automatically updated when items are added or removed form the array.
+ // ``` JavaScript
+ colors.push("yellow");
+ //// The Repeater will be updated automatically.
+ // ```
+ //
+ TKUnit.wait(ASYNC);
+ TKUnit.assertEqual(getChildrenCount(repeater), 4, "getChildrenCount");
+
+ };
+
+ helper.buildUIAndRunTest(repeater, testAction);
+}
+
+export function test_remove_from_observable_array_refreshes_the_Repeater() {
+ var repeater = new repeaterModule.Repeater();
+ var data = new observableArray.ObservableArray([1, 2, 3]);
+
+ function testAction(views: Array) {
+ repeater.items = data;
+
+ TKUnit.wait(ASYNC);
+ TKUnit.assertEqual(getChildrenCount(repeater), 3, "getChildrenCount");
+
+ data.pop();
+ TKUnit.wait(ASYNC);
+ TKUnit.assertEqual(getChildrenCount(repeater), 2, "getChildrenCount");
+
+ };
+
+ helper.buildUIAndRunTest(repeater, testAction);
+}
+
+export function test_splice_observable_array_refreshes_the_Repeater() {
+ var repeater = new repeaterModule.Repeater();
+ var data = new observableArray.ObservableArray(["a", "b", "c"]);
+
+ function testAction(views: Array) {
+ repeater.items = data;
+
+ TKUnit.wait(ASYNC);
+ TKUnit.assertEqual(getChildrenCount(repeater), 3, "getChildrenCount");
+
+ // Remove the first 2 elements and add
+ data.splice(0, 2, "d", "e", "f");
+ TKUnit.wait(ASYNC);
+ TKUnit.assertEqual(getChildrenCount(repeater), 4, "getChildrenCount");
+
+ };
+
+ helper.buildUIAndRunTest(repeater, testAction);
+}
+
+export function test_usingAppLevelConvertersInRepeaterItems() {
+ var repeater = new repeaterModule.Repeater();
+
+ var dateConverter = function (value, format) {
+ var result = format;
+ var day = value.getDate();
+ result = result.replace("DD", month < 10 ? "0" + day : day);
+ var month = value.getMonth() + 1;
+ result = result.replace("MM", month < 10 ? "0" + month : month);
+ result = result.replace("YYYY", value.getFullYear());
+ return result;
+ };
+
+ app.resources["dateConverter"] = dateConverter;
+
+ var data = new observableArray.ObservableArray();
+
+ data.push({ date: new Date() });
+
+ function testAction(views: Array) {
+ repeater.itemTemplate = "";
+ repeater.items = data;
+
+ TKUnit.wait(ASYNC);
+
+ TKUnit.assertEqual(getChildAtText(repeater, 0), dateConverter(new Date(), "DD.MM.YYYY"), "element");
+ };
+
+ helper.buildUIAndRunTest(repeater, testAction);
+}
+
+export function test_BindingRepeaterToASimpleArray() {
+ var repeater = new repeaterModule.Repeater();
+
+ function testAction(views: Array) {
+ repeater.itemTemplate = "";
+ repeater.items = [1, 2, 3];
+
+ TKUnit.wait(ASYNC);
+
+ TKUnit.assertEqual(getChildAtText(repeater, 0), "1", "first element text");
+ TKUnit.assertEqual(getChildAtText(repeater, 1), "2", "second element text");
+ TKUnit.assertEqual(getChildAtText(repeater, 2), "3", "third element text");
+ }
+
+ helper.buildUIAndRunTest(repeater, testAction);
+}
+
+export function test_BindingRepeaterToASimpleArrayWithExpression() {
+ var repeater = new repeaterModule.Repeater();
+
+ function testAction(views: Array) {
+ repeater.itemTemplate = "";
+ repeater.items = [1, 2, 3];
+
+ TKUnit.wait(ASYNC);
+
+ TKUnit.assertEqual(getChildAtText(repeater, 0), "1 some static text", "first element text");
+ TKUnit.assertEqual(getChildAtText(repeater, 1), "2 some static text", "second element text");
+ TKUnit.assertEqual(getChildAtText(repeater, 2), "3 some static text", "third element text");
+ }
+
+ helper.buildUIAndRunTest(repeater, testAction);
+}
+/*
+export function test_no_memory_leak_when_items_is_regular_array() {
+ var createFunc = function (): repeaterModule.Repeater {
+ var repeater = new repeaterModule.Repeater();
+ repeater.items = FEW_ITEMS;
+ return repeater;
+ };
+
+ helper.buildUIWithWeakRefAndInteract(createFunc,(list) => {
+ TKUnit.assert(list.isLoaded, "Repeater should be loaded here");
+ });
+}
+
+export function test_no_memory_leak_when_items_is_observable_array() {
+ // Keep the reference to the observable array to test the weakEventListener
+ var colors = new observableArray.ObservableArray(["red", "green", "blue"]);
+
+ var createFunc = function (): repeaterModule.Repeater {
+ var repeater = new repeaterModule.Repeater();
+ repeater.items = colors;
+ return repeater;
+ };
+
+ helper.buildUIWithWeakRefAndInteract(createFunc,(list) => {
+ TKUnit.assert(list.isLoaded, "Repeater should be loaded here");
+ });
+}
+*/
+function getChildrenCount(repeater: repeaterModule.Repeater): number {
+ return repeater.itemsLayout.getChildrenCount();
+}
+
+function getChildAt(repeater: repeaterModule.Repeater, index: number): viewModule.View {
+ return repeater.itemsLayout.getChildAt(index);
+}
+
+function getChildAtText(repeater: repeaterModule.Repeater, index: number): string {
+ return (getChildAt(repeater, index)).text + "";
+}
+
diff --git a/ui/builder/builder.ts b/ui/builder/builder.ts
index bebdc1a67..b68cad7ac 100644
--- a/ui/builder/builder.ts
+++ b/ui/builder/builder.ts
@@ -10,6 +10,11 @@ var KNOWNCOLLECTIONS = "knownCollections";
export function parse(value: string, exports: any): view.View {
var viewToReturn: view.View;
+
+ if (exports instanceof view.View) {
+ exports = getExports(exports);
+ }
+
var componentModule = parseInternal(value, exports);
if (componentModule) {
@@ -150,7 +155,7 @@ function parseInternal(value: string, exports: any): componentBuilder.ComponentM
}
}
- }, (e) => {
+ },(e) => {
throw new Error("XML parse error: " + e.message);
}, true);
@@ -184,7 +189,7 @@ function loadInternal(fileName: string, exports: any): componentBuilder.Componen
// Read the XML file.
fileAccess.readText(fileName, result => {
componentModule = parseInternal(result, exports);
- }, (e) => {
+ },(e) => {
throw new Error("Error loading file " + fileName + " :" + e.message);
});
}
@@ -234,3 +239,13 @@ interface ComplexProperty {
name: string;
items?: Array;
}
+
+function getExports(instance: view.View): any {
+ var parent = instance.parent;
+
+ while (parent && (parent).exports === undefined) {
+ parent = parent.parent;
+ }
+
+ return parent ? (parent).exports : undefined;
+}
\ No newline at end of file
diff --git a/ui/list-view/list-view-common.ts b/ui/list-view/list-view-common.ts
index 5a55f14f3..164b8a172 100644
--- a/ui/list-view/list-view-common.ts
+++ b/ui/list-view/list-view-common.ts
@@ -105,7 +105,7 @@ export class ListView extends view.View implements definition.ListView {
var v;
if (this.itemTemplate && this.items) {
- v = builder.parse(this.itemTemplate, getExports(this));
+ v = builder.parse(this.itemTemplate, this);
}
return v;
@@ -145,14 +145,4 @@ export class ListView extends view.View implements definition.ListView {
private _onItemsChanged(args: observable.EventData) {
this.refresh();
}
-}
-
-function getExports(instance: view.View): any {
- var parent = instance.parent;
-
- while (parent && (parent).exports === undefined) {
- parent = parent.parent;
- }
-
- return parent ? (parent).exports : undefined;
}
\ No newline at end of file
diff --git a/ui/repeater/package.json b/ui/repeater/package.json
new file mode 100644
index 000000000..219eeec41
--- /dev/null
+++ b/ui/repeater/package.json
@@ -0,0 +1,2 @@
+{ "name" : "repeater",
+ "main" : "repeater.js" }
\ No newline at end of file
diff --git a/ui/repeater/repeater.d.ts b/ui/repeater/repeater.d.ts
new file mode 100644
index 000000000..55393ac0b
--- /dev/null
+++ b/ui/repeater/repeater.d.ts
@@ -0,0 +1,49 @@
+/**
+ * Contains the Repeater class, which represents a UI Repeater component.
+ */
+declare module "ui/repeater" {
+ import view = require("ui/core/view");
+ import dependencyObservable = require("ui/core/dependency-observable");
+ import layoutModule = require("ui/layouts/layout");
+
+ /**
+ * Represents a UI Repeater component.
+ */
+ export class Repeater extends view.View {
+ /**
+ * Represents the observable property backing the items property of each Repeater instance.
+ */
+ public static itemsProperty: dependencyObservable.Property;
+
+ /**
+ * Represents the item template property of each Repeater instance.
+ */
+ public static itemTemplateProperty: dependencyObservable.Property;
+
+ /**
+ * Represents the items layout property of each Repeater instance.
+ */
+ public static itemsLayoutProperty: dependencyObservable.Property;
+
+ /**
+ * Gets or set the items collection of the Repeater.
+ * The items property can be set to an array or an object defining length and getItem(index) method.
+ */
+ items: any;
+
+ /**
+ * Gets or set the item template of the Repeater.
+ */
+ itemTemplate: string;
+
+ /**
+ * Gets or set the items layout of the Repeater. Default value is StackLayout with orientation="vertical".
+ */
+ itemsLayout: layoutModule.Layout;
+
+ /**
+ * Forces the Repeater to reload all its items.
+ */
+ refresh();
+ }
+}
\ No newline at end of file
diff --git a/ui/repeater/repeater.ts b/ui/repeater/repeater.ts
new file mode 100644
index 000000000..795506700
--- /dev/null
+++ b/ui/repeater/repeater.ts
@@ -0,0 +1,227 @@
+import definition = require("ui/repeater");
+import proxy = require("ui/core/proxy");
+import dependencyObservable = require("ui/core/dependency-observable");
+import viewModule = require("ui/core/view");
+import observable = require("data/observable");
+import observableArray = require("data/observable-array");
+import weakEvents = require("ui/core/weak-event-listener");
+import types = require("utils/types");
+import layoutModule = require("ui/layouts/layout");
+import stackLayoutModule = require("ui/layouts/stack-layout");
+import builder = require("ui/builder");
+import utils = require("utils/utils");
+import platform = require("platform");
+import labelModule = require("ui/label");
+
+var ITEMS = "items";
+var ITEMTEMPLATE = "itemTemplate";
+var LAYOUT = "layout";
+var REPEATER = "Repeater";
+
+export module knownTemplates {
+ export var itemTemplate = "itemTemplate";
+}
+
+function onItemsPropertyChanged(data: dependencyObservable.PropertyChangeData) {
+ var repeater = data.object;
+ repeater._onItemsPropertyChanged(data);
+}
+
+function onItemTemplatePropertyChanged(data: dependencyObservable.PropertyChangeData) {
+ var repeater = data.object;
+ repeater.refresh();
+}
+
+function onItemsLayoutPropertyPropertyChanged(data: dependencyObservable.PropertyChangeData) {
+ var repeater = data.object;
+ repeater.refresh();
+}
+
+export class Repeater extends viewModule.CustomLayoutView implements definition.Repeater {
+ private isDirty: boolean = true;
+ private _ios: UIView;
+
+ constructor() {
+ super();
+
+ if (platform.device.os === platform.platformNames.ios) {
+ this._ios = UIView.new();
+ }
+ }
+
+ public static itemsProperty = new dependencyObservable.Property(
+ ITEMS,
+ REPEATER,
+ new proxy.PropertyMetadata(
+ undefined,
+ dependencyObservable.PropertyMetadataSettings.AffectsLayout,
+ onItemsPropertyChanged
+ )
+ );
+
+ public static itemTemplateProperty = new dependencyObservable.Property(
+ ITEMTEMPLATE,
+ REPEATER,
+ new proxy.PropertyMetadata(
+ undefined,
+ dependencyObservable.PropertyMetadataSettings.AffectsLayout,
+ onItemTemplatePropertyChanged
+ )
+ );
+
+ public static itemsLayoutProperty = new dependencyObservable.Property(
+ LAYOUT,
+ REPEATER,
+ new proxy.PropertyMetadata(
+ undefined,
+ dependencyObservable.PropertyMetadataSettings.AffectsLayout,
+ onItemsLayoutPropertyPropertyChanged
+ )
+ );
+
+ get items(): any {
+ return this._getValue(Repeater.itemsProperty);
+ }
+ set items(value: any) {
+ this._setValue(Repeater.itemsProperty, value);
+ }
+
+ get itemTemplate(): string {
+ return this._getValue(Repeater.itemTemplateProperty);
+ }
+ set itemTemplate(value: string) {
+ this._setValue(Repeater.itemTemplateProperty, value);
+ }
+
+ get itemsLayout(): layoutModule.Layout {
+ return this._getValue(Repeater.itemsLayoutProperty);
+ }
+ set itemsLayout(value: layoutModule.Layout) {
+ this._setValue(Repeater.itemsLayoutProperty, value);
+ }
+
+ public refresh() {
+ this.isDirty = true;
+
+ this._createChildren();
+ }
+
+ public onLoaded() {
+ super.onLoaded();
+
+ this._createChildren();
+ }
+
+ public onUnloaded() {
+ super.onUnloaded();
+ }
+
+ public _onItemsPropertyChanged(data: dependencyObservable.PropertyChangeData) {
+ if (data.oldValue instanceof observable.Observable) {
+ weakEvents.removeWeakEventListener(data.oldValue, observableArray.ObservableArray.changeEvent, this._onItemsChanged, this);
+ }
+
+ if (data.newValue instanceof observable.Observable) {
+ weakEvents.addWeakEventListener(data.newValue, observableArray.ObservableArray.changeEvent, this._onItemsChanged, this);
+ }
+
+ if (types.isUndefined(this.itemsLayout)) {
+ this.itemsLayout = new stackLayoutModule.StackLayout();
+ }
+
+ if (this.itemsLayout.parent !== this) {
+ this._addView(this.itemsLayout);
+ }
+
+ this.refresh();
+ }
+
+ private _onItemsChanged(args: observable.EventData) {
+ this.refresh();
+ }
+
+ private _createChildren() {
+ if (this.isDirty && this.isLoaded) {
+ clearItemsLayout(this.itemsLayout);
+
+ if (!types.isNullOrUndefined(this.items) && types.isNumber(this.items.length)) {
+ var i: number;
+ for (i = 0; i < this.items.length; i++) {
+ var viewToAdd = !types.isNullOrUndefined(this.itemTemplate) ? builder.parse(this.itemTemplate, this) : this._getDefaultItemContent(i);
+ if (!types.isNullOrUndefined(viewToAdd)) {
+ this.itemsLayout.addChild(viewToAdd);
+ viewToAdd.bindingContext = this._getDataItem(i);
+ }
+ }
+ }
+ this.isDirty = false;
+ }
+ }
+
+ public _getDefaultItemContent(index: number): viewModule.View {
+ var lbl = new labelModule.Label();
+ lbl.bind({
+ targetProperty: "text",
+ sourceProperty: "$value"
+ });
+ return lbl;
+ }
+
+ private _getDataItem(index: number): any {
+ return this.items.getItem ? this.items.getItem(index) : this.items[index];
+ }
+
+ get ios(): UIView {
+ return this._ios;
+ }
+
+ get _childrenCount(): number {
+ var count = 0;
+
+ if (this.itemsLayout) {
+ count++;
+ }
+
+ return count;
+ }
+
+ public _eachChildView(callback: (child: viewModule.View) => boolean) {
+ if (this.itemsLayout) {
+ callback(this.itemsLayout);
+ }
+ }
+
+ public onLayout(left: number, top: number, right: number, bottom: number): void {
+ viewModule.View.layoutChild(this, this.itemsLayout, 0, 0, right, bottom);
+ }
+
+ public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void {
+ var result = viewModule.View.measureChild(this, this.itemsLayout, widthMeasureSpec, heightMeasureSpec);
+
+ var width = utils.layout.getMeasureSpecSize(widthMeasureSpec);
+ var widthMode = utils.layout.getMeasureSpecMode(widthMeasureSpec);
+
+ var height = utils.layout.getMeasureSpecSize(heightMeasureSpec);
+ var heightMode = utils.layout.getMeasureSpecMode(heightMeasureSpec);
+
+ var widthAndState = viewModule.View.resolveSizeAndState(result.measuredWidth, width, widthMode, 0);
+ var heightAndState = viewModule.View.resolveSizeAndState(result.measuredHeight, height, heightMode, 0);
+
+ this.setMeasuredDimension(widthAndState, heightAndState);
+ }
+}
+
+function clearItemsLayout(itemsLayout: layoutModule.Layout) {
+ if (!types.isNullOrUndefined(itemsLayout)) {
+ var i: number = itemsLayout.getChildrenCount();
+ if (i > 0) {
+ while (i >= 0) {
+ var child = itemsLayout.getChildAt(i);
+ if (!types.isNullOrUndefined(child)) {
+ itemsLayout.removeChild(child);
+ }
+ i--;
+ }
+ }
+ }
+}
\ No newline at end of file