From f499ee95a8690c8fa1594885c21f412195d0818d Mon Sep 17 00:00:00 2001 From: Vladimir Enchev Date: Fri, 22 May 2015 11:44:24 +0300 Subject: [PATCH 01/14] first repeater definition + properties added --- CrossPlatformModules.csproj | 7 ++ ui/repeater/package.json | 2 + ui/repeater/repeater.d.ts | 81 +++++++++++++++++++ ui/repeater/repeater.ts | 156 ++++++++++++++++++++++++++++++++++++ 4 files changed, 246 insertions(+) create mode 100644 ui/repeater/package.json create mode 100644 ui/repeater/repeater.d.ts create mode 100644 ui/repeater/repeater.ts diff --git a/CrossPlatformModules.csproj b/CrossPlatformModules.csproj index 8bafda30a..b8b21c6f6 100644 --- a/CrossPlatformModules.csproj +++ b/CrossPlatformModules.csproj @@ -347,6 +347,10 @@ trace.d.ts + + repeater.d.ts + + list-picker.d.ts @@ -1528,6 +1532,9 @@ PreserveNewest + + PreserveNewest + 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..e4885cadc --- /dev/null +++ b/ui/repeater/repeater.d.ts @@ -0,0 +1,81 @@ +/** + * 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"); + + /** + * Represents a UI Repeater component. + */ + export class Repeater extends view.View { + /** + * Represents the observable property backing the orientation property of each Repeater instance. + */ + public static orientationProperty: dependencyObservable.Property; + + /** + * Dependency property used to support binding operations for the items wrapping of the current Repeater instance. + */ + public static wrapProperty: dependencyObservable.Property; + + /** + * Represents the observable property backing the itemWidth property of each Repeater instance. + */ + public static itemWidthProperty: dependencyObservable.Property; + + /** + * Represents the observable property backing the itemHeight property of each Repeater instance. + */ + public static itemHeightProperty: dependencyObservable.Property; + + /** + * 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; + + /** + * Gets or sets if layout should be horizontal or vertical. + * The default value is vertical. + */ + orientation: string; + + /** + * Gets or sets whether the Repeater wraps items or not. + */ + wrap: boolean; + + /** + * Gets or sets the width used to measure and layout each child. + * Default value is Number.NaN which does not restrict children. + */ + itemWidth: number; + + /** + * Gets or sets the height used to measure and layout each child. + * Default value is Number.NaN which does not restrict children. + */ + itemHeight: number; + + /** + * 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; + + /** + * 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..6069dbc38 --- /dev/null +++ b/ui/repeater/repeater.ts @@ -0,0 +1,156 @@ +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 enums = require("ui/enums"); + +var ITEMS = "items"; +var WRAP = "wrap"; +var ORIENTATION = "orientation"; +var ITEMTEMPLATE = "itemTemplate"; +var ITEMWIDTH = "itemWidth"; +var ITEMHEIGHT = "itemHeight"; +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 validateOrientation(value: any): boolean { + return value === enums.Orientation.vertical || value === enums.Orientation.horizontal; +} + +function isWidthHeightValid(value: any): boolean { + return isNaN(value) || (value >= 0.0 && value !== Number.POSITIVE_INFINITY); +} + +export class Repeater extends viewModule.View implements definition.Repeater { + + 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 orientationProperty = new dependencyObservable.Property( + ORIENTATION, + REPEATER, + new proxy.PropertyMetadata(enums.Orientation.vertical, + dependencyObservable.PropertyMetadataSettings.AffectsLayout, + undefined, + validateOrientation) + ); + + public static wrapProperty = new dependencyObservable.Property( + WRAP, + REPEATER, + new proxy.PropertyMetadata(false, dependencyObservable.PropertyMetadataSettings.AffectsLayout) + ); + + public static itemWidthProperty = new dependencyObservable.Property( + ITEMWIDTH, + REPEATER, + new proxy.PropertyMetadata(Number.NaN, + dependencyObservable.PropertyMetadataSettings.AffectsLayout, + undefined, + isWidthHeightValid) + ); + + public static itemHeightProperty = new dependencyObservable.Property( + ITEMHEIGHT, + REPEATER, + new proxy.PropertyMetadata(Number.NaN, + dependencyObservable.PropertyMetadataSettings.AffectsLayout, + undefined, + isWidthHeightValid) + ); + + 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); + } + + public refresh() { + // + } + + 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); + } + + this.refresh(); + } + + private _onItemsChanged(args: observable.EventData) { + this.refresh(); + } + + get wrap(): boolean { + return this._getValue(Repeater.wrapProperty); + } + set wrap(value: boolean) { + this._setValue(Repeater.wrapProperty, value); + } + + get orientation(): string { + return this._getValue(Repeater.orientationProperty); + } + set orientation(value: string) { + this._setValue(Repeater.orientationProperty, value); + } + + get itemWidth(): number { + return this._getValue(Repeater.itemWidthProperty); + } + set itemWidth(value: number) { + this._setValue(Repeater.itemWidthProperty, value); + } + + get itemHeight(): number { + return this._getValue(Repeater.itemHeightProperty); + } + set itemHeight(value: number) { + this._setValue(Repeater.itemHeightProperty, value); + } +} \ No newline at end of file From 342bae570d9a94c5142d2151d4634d21fc87337c Mon Sep 17 00:00:00 2001 From: Vladimir Enchev Date: Mon, 25 May 2015 12:09:15 +0300 Subject: [PATCH 02/14] getExports moved to builder --- ui/builder/builder.ts | 19 +++++++++++++++++-- ui/list-view/list-view-common.ts | 12 +----------- 2 files changed, 18 insertions(+), 13 deletions(-) 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 From 5a85775bf0283ad5823d473d25d7aadb6b775f83 Mon Sep 17 00:00:00 2001 From: Vladimir Enchev Date: Mon, 25 May 2015 12:09:28 +0300 Subject: [PATCH 03/14] repeater reworked --- ui/repeater/repeater.d.ts | 48 ++--------- ui/repeater/repeater.ts | 170 +++++++++++++++++++++++++------------- 2 files changed, 119 insertions(+), 99 deletions(-) diff --git a/ui/repeater/repeater.d.ts b/ui/repeater/repeater.d.ts index e4885cadc..55393ac0b 100644 --- a/ui/repeater/repeater.d.ts +++ b/ui/repeater/repeater.d.ts @@ -4,31 +4,12 @@ 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 orientation property of each Repeater instance. - */ - public static orientationProperty: dependencyObservable.Property; - - /** - * Dependency property used to support binding operations for the items wrapping of the current Repeater instance. - */ - public static wrapProperty: dependencyObservable.Property; - - /** - * Represents the observable property backing the itemWidth property of each Repeater instance. - */ - public static itemWidthProperty: dependencyObservable.Property; - - /** - * Represents the observable property backing the itemHeight property of each Repeater instance. - */ - public static itemHeightProperty: dependencyObservable.Property; - /** * Represents the observable property backing the items property of each Repeater instance. */ @@ -40,27 +21,9 @@ declare module "ui/repeater" { public static itemTemplateProperty: dependencyObservable.Property; /** - * Gets or sets if layout should be horizontal or vertical. - * The default value is vertical. + * Represents the items layout property of each Repeater instance. */ - orientation: string; - - /** - * Gets or sets whether the Repeater wraps items or not. - */ - wrap: boolean; - - /** - * Gets or sets the width used to measure and layout each child. - * Default value is Number.NaN which does not restrict children. - */ - itemWidth: number; - - /** - * Gets or sets the height used to measure and layout each child. - * Default value is Number.NaN which does not restrict children. - */ - itemHeight: number; + public static itemsLayoutProperty: dependencyObservable.Property; /** * Gets or set the items collection of the Repeater. @@ -72,6 +35,11 @@ declare module "ui/repeater" { * 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. diff --git a/ui/repeater/repeater.ts b/ui/repeater/repeater.ts index 6069dbc38..b9fc782ac 100644 --- a/ui/repeater/repeater.ts +++ b/ui/repeater/repeater.ts @@ -5,14 +5,16 @@ 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 enums = require("ui/enums"); +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"); var ITEMS = "items"; -var WRAP = "wrap"; -var ORIENTATION = "orientation"; var ITEMTEMPLATE = "itemTemplate"; -var ITEMWIDTH = "itemWidth"; -var ITEMHEIGHT = "itemHeight"; +var LAYOUT = "layout"; var REPEATER = "Repeater"; export module knownTemplates { @@ -29,15 +31,22 @@ function onItemTemplatePropertyChanged(data: dependencyObservable.PropertyChange repeater.refresh(); } -function validateOrientation(value: any): boolean { - return value === enums.Orientation.vertical || value === enums.Orientation.horizontal; +function onItemsLayoutPropertyPropertyChanged(data: dependencyObservable.PropertyChangeData) { + var repeater = data.object; + repeater.refresh(); } -function isWidthHeightValid(value: any): boolean { - return isNaN(value) || (value >= 0.0 && value !== Number.POSITIVE_INFINITY); -} +export class Repeater extends viewModule.CustomLayoutView implements definition.Repeater { + private isDirty: boolean = true; + private _ios: UIView; -export class Repeater extends viewModule.View implements definition.Repeater { + constructor() { + super(); + + if (platform.device.os === platform.platformNames.ios) { + this._ios = UIView.new(); + } + } public static itemsProperty = new dependencyObservable.Property( ITEMS, @@ -59,37 +68,14 @@ export class Repeater extends viewModule.View implements definition.Repeater { ) ); - public static orientationProperty = new dependencyObservable.Property( - ORIENTATION, + public static itemsLayoutProperty = new dependencyObservable.Property( + LAYOUT, REPEATER, - new proxy.PropertyMetadata(enums.Orientation.vertical, - dependencyObservable.PropertyMetadataSettings.AffectsLayout, + new proxy.PropertyMetadata( undefined, - validateOrientation) - ); - - public static wrapProperty = new dependencyObservable.Property( - WRAP, - REPEATER, - new proxy.PropertyMetadata(false, dependencyObservable.PropertyMetadataSettings.AffectsLayout) - ); - - public static itemWidthProperty = new dependencyObservable.Property( - ITEMWIDTH, - REPEATER, - new proxy.PropertyMetadata(Number.NaN, dependencyObservable.PropertyMetadataSettings.AffectsLayout, - undefined, - isWidthHeightValid) - ); - - public static itemHeightProperty = new dependencyObservable.Property( - ITEMHEIGHT, - REPEATER, - new proxy.PropertyMetadata(Number.NaN, - dependencyObservable.PropertyMetadataSettings.AffectsLayout, - undefined, - isWidthHeightValid) + onItemsLayoutPropertyPropertyChanged + ) ); get items(): any { @@ -106,8 +92,22 @@ export class Repeater extends viewModule.View implements definition.Repeater { 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 _onItemsPropertyChanged(data: dependencyObservable.PropertyChangeData) { @@ -126,31 +126,83 @@ export class Repeater extends viewModule.View implements definition.Repeater { this.refresh(); } - get wrap(): boolean { - return this._getValue(Repeater.wrapProperty); - } - set wrap(value: boolean) { - this._setValue(Repeater.wrapProperty, value); + private _createChildren() { + if (this.isDirty && this.isLoaded) { + if (types.isDefined(this.items) && types.isNumber(this.items.length)) { + + if (types.isUndefined(this.itemsLayout)) { + this.itemsLayout = new stackLayoutModule.StackLayout(); + } + + if (this.itemsLayout.parent !== this) { + this._addView(this.itemsLayout); + } + + clearItemsLayout(this.itemsLayout); + + var i: number; + for (i = 0; i < this.items.length; i++) { + var viewToAdd = builder.parse(this.itemTemplate, this); + if (types.isDefined(viewToAdd)) { + this.itemsLayout.addChild(viewToAdd); + viewToAdd.bindingContext = this._getDataItem(i); + } + } + } + this.isDirty = false; + } } - get orientation(): string { - return this._getValue(Repeater.orientationProperty); - } - set orientation(value: string) { - this._setValue(Repeater.orientationProperty, value); + private _getDataItem(index: number): any { + return this.items.getItem ? this.items.getItem(index) : this.items[index]; } - get itemWidth(): number { - return this._getValue(Repeater.itemWidthProperty); - } - set itemWidth(value: number) { - this._setValue(Repeater.itemWidthProperty, value); + get ios(): UIView { + return this._ios; } - get itemHeight(): number { - return this._getValue(Repeater.itemHeightProperty); + get _childrenCount(): number { + var count = 0; + + if (this.itemsLayout) { + count++; + } + + return count; } - set itemHeight(value: number) { - this._setValue(Repeater.itemHeightProperty, value); + + 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) { + var i: number = itemsLayout.getChildrenCount(); + if (i > 0) { + while (i >= 0) { + itemsLayout.removeChild(itemsLayout.getChildAt(i)); + i--; + } } } \ No newline at end of file From 0da206a02497ae8618ae5ffdc413ddbfb6dcb19b Mon Sep 17 00:00:00 2001 From: Vladimir Enchev Date: Mon, 25 May 2015 15:23:46 +0300 Subject: [PATCH 04/14] default item content added + fixes --- ui/repeater/repeater.ts | 58 ++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/ui/repeater/repeater.ts b/ui/repeater/repeater.ts index b9fc782ac..1b0d89a3e 100644 --- a/ui/repeater/repeater.ts +++ b/ui/repeater/repeater.ts @@ -11,6 +11,7 @@ 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"; @@ -101,6 +102,7 @@ export class Repeater extends viewModule.CustomLayoutView implements definition. public refresh() { this.isDirty = true; + this._createChildren(); } @@ -110,6 +112,13 @@ export class Repeater extends viewModule.CustomLayoutView implements definition. 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); @@ -119,6 +128,14 @@ export class Repeater extends viewModule.CustomLayoutView implements definition. 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(); } @@ -128,22 +145,13 @@ export class Repeater extends viewModule.CustomLayoutView implements definition. private _createChildren() { if (this.isDirty && this.isLoaded) { - if (types.isDefined(this.items) && types.isNumber(this.items.length)) { - - if (types.isUndefined(this.itemsLayout)) { - this.itemsLayout = new stackLayoutModule.StackLayout(); - } - - if (this.itemsLayout.parent !== this) { - this._addView(this.itemsLayout); - } - - clearItemsLayout(this.itemsLayout); + 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 = builder.parse(this.itemTemplate, this); - if (types.isDefined(viewToAdd)) { + 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); } @@ -153,6 +161,15 @@ export class Repeater extends viewModule.CustomLayoutView implements definition. } } + 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]; } @@ -198,11 +215,16 @@ export class Repeater extends viewModule.CustomLayoutView implements definition. } function clearItemsLayout(itemsLayout: layoutModule.Layout) { - var i: number = itemsLayout.getChildrenCount(); - if (i > 0) { - while (i >= 0) { - itemsLayout.removeChild(itemsLayout.getChildAt(i)); - i--; + 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 From 0c0ef14ca46416d39e61575fde118240c7ea6d15 Mon Sep 17 00:00:00 2001 From: Vladimir Enchev Date: Mon, 25 May 2015 15:24:01 +0300 Subject: [PATCH 05/14] tests added --- CrossPlatformModules.csproj | 3 +- apps/tests/testRunner.ts | 1 + apps/tests/ui/repeater/repeater-tests.ts | 375 +++++++++++++++++++++++ 3 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 apps/tests/ui/repeater/repeater-tests.ts diff --git a/CrossPlatformModules.csproj b/CrossPlatformModules.csproj index b8b21c6f6..7712d1427 100644 --- a/CrossPlatformModules.csproj +++ b/CrossPlatformModules.csproj @@ -150,6 +150,7 @@ + time-picker-tests-native.d.ts @@ -1597,7 +1598,7 @@ False - + \ 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..75bff24a5 --- /dev/null +++ b/apps/tests/ui/repeater/repeater-tests.ts @@ -0,0 +1,375 @@ +import TKUnit = require("../../TKUnit"); +import app = require("application"); +import helper = require("../helper"); +import viewModule = require("ui/core/view"); +import observable = require("data/observable"); +import types = require("utils/types"); + +//  +// # 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%} +// +//``` + +//  + +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) { + var indexes = {}; + // + // ### 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_native_views() { + var repeater = new repeaterModule.Repeater(); + + function testAction(views: Array) { + repeater.items = FEW_ITEMS; + + TKUnit.wait(ASYNC); + TKUnit.assertEqual(getChildrenCount(repeater), FEW_ITEMS.length, "Native 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, "Native 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, "Native views count."); + }; + + helper.buildUIAndRunTest(repeater, testAction); +} + +export function test_refresh_reloads_all_items() { + var repeater = new repeaterModule.Repeater(); + + function testAction(views: Array) { + var indexes = {}; + 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_native_items() { + var repeater = new repeaterModule.Repeater(); + + function testAction(views: Array) { + repeater.items = FEW_ITEMS; + TKUnit.wait(ASYNC); + TKUnit.assertEqual(getChildrenCount(repeater), FEW_ITEMS.length, "Native views count."); + + repeater.items = null; + TKUnit.wait(ASYNC); + TKUnit.assertEqual(getChildrenCount(repeater), 0, "Native views count."); + }; + + helper.buildUIAndRunTest(repeater, testAction); +} + +export function test_set_itmes_to_undefiend_clears_native_items() { + var repeater = new repeaterModule.Repeater(); + + function testAction(views: Array) { + repeater.items = FEW_ITEMS; + TKUnit.wait(ASYNC); + TKUnit.assertEqual(getChildrenCount(repeater), FEW_ITEMS.length, "Native views count."); + + repeater.items = undefined; + TKUnit.wait(ASYNC); + TKUnit.assertEqual(getChildrenCount(repeater), 0, "Native 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, "Native views count."); + + repeater.items = ["a", "b", "c", "d"]; + TKUnit.wait(ASYNC); + TKUnit.assertEqual(getChildrenCount(repeater), 4, "Native 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) { + var indexes = {}; + // + // ### 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_listview() { + 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_listview() { + 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_listview() { + 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_usingAppLevelConvertersInListViewItems() { + 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 = "