diff --git a/BCL.csproj b/BCL.csproj
index b5e8216d0..9620dbb86 100644
--- a/BCL.csproj
+++ b/BCL.csproj
@@ -152,15 +152,31 @@
text.d.ts
+
+
-
- text-view.d.ts
+
+ view.d.ts
-
-
-
- text-view.d.ts
+
+ view.d.ts
+
+
+
+ label.d.ts
+
+
+ label.d.ts
+
+
+
+ text-input.d.ts
+
+
+ text-input.d.ts
+
+
@@ -223,7 +239,7 @@
-
+
diff --git a/Tests/application-tests.android.ts b/Tests/application-tests.android.ts
index b5c1f1e35..15113d7f9 100644
--- a/Tests/application-tests.android.ts
+++ b/Tests/application-tests.android.ts
@@ -20,7 +20,7 @@ var dir = context.getFilesDir();
// ```
// Tracking the current Activity
// ``` JavaScript
-if (androidApp.currentActivity === androidApp.startActivity) {
+if (androidApp.foregroundActivity === androidApp.foregroundActivity) {
//// We are currently in the main (start) activity of the application
}
// ```
@@ -29,7 +29,7 @@ if (androidApp.currentActivity === androidApp.startActivity) {
export var testAndroidApplicationInitialized = function () {
TKUnit.assert(app.android, "Android application not initialized.");
TKUnit.assert(app.android.context, "Android context not initialized.");
- TKUnit.assert(app.android.currentActivity, "Android currentActivity not initialized.");
+ TKUnit.assert(app.android.foregroundActivity, "Android currentActivity not initialized.");
TKUnit.assert(app.android.startActivity, "Android startActivity not initialized.");
TKUnit.assert(app.android.nativeApp, "Android nativeApp not initialized.");
TKUnit.assert(app.android.packageName, "Android packageName not initialized.");
diff --git a/application/application.android.ts b/application/application.android.ts
index b8e80f99c..bdf01d7ee 100644
--- a/application/application.android.ts
+++ b/application/application.android.ts
@@ -1,4 +1,5 @@
import appModule = require("application/application-common");
+import dts = require("application");
// merge the exports of the application_common file with the exports of this file
declare var exports;
@@ -21,11 +22,17 @@ var initEvents = function () {
androidApp.onActivityCreated(activity, bundle);
}
}
+
+ androidApp.currentContext = activity;
},
onActivityDestroyed: function (activity: any) {
// Clear the current activity reference to prevent leak
- if (activity === androidApp.currentActivity) {
- androidApp.currentActivity = undefined;
+ if (activity === androidApp.foregroundActivity) {
+ androidApp.foregroundActivity = undefined;
+ }
+
+ if (activity === androidApp.currentContext) {
+ androidApp.currentContext = undefined;
}
if (activity === androidApp.startActivity) {
@@ -42,7 +49,7 @@ var initEvents = function () {
gc();
},
onActivityPaused: function (activity: any) {
- if (activity === androidApp.currentActivity) {
+ if (activity === androidApp.foregroundActivity) {
if (exports.onSuspend) {
exports.onSuspend();
}
@@ -53,7 +60,7 @@ var initEvents = function () {
}
},
onActivityResumed: function (activity: any) {
- if (activity === androidApp.currentActivity) {
+ if (activity === androidApp.foregroundActivity) {
if (exports.onResume) {
exports.onResume();
}
@@ -69,7 +76,7 @@ var initEvents = function () {
}
},
onActivityStarted: function (activity: any) {
- androidApp.currentActivity = activity;
+ androidApp.foregroundActivity = activity;
if (androidApp.onActivityStarted) {
androidApp.onActivityStarted(activity);
@@ -98,12 +105,14 @@ export var init = function (nativeApp: android.app.Application) {
initialized = true;
}
-class AndroidApplication {
+class AndroidApplication implements dts.AndroidApplication {
public nativeApp: android.app.Application;
public context: android.content.Context;
- public currentActivity: android.app.Activity;
+ public currentContext: android.content.Context;
+ public foregroundActivity: android.app.Activity;
public startActivity: android.app.Activity;
public packageName: string;
+ public getActivity: (intent: android.content.Intent) => any;
public onActivityCreated: (activity: android.app.Activity, bundle: android.os.Bundle) => void;
public onActivityDestroyed: (activity: android.app.Activity) => void;
@@ -119,6 +128,7 @@ class AndroidApplication {
this.nativeApp = nativeApp;
this.packageName = nativeApp.getPackageName();
this.context = nativeApp.getApplicationContext();
+ this.getActivity = undefined;
}
public init() {
diff --git a/application/application.d.ts b/application/application.d.ts
index ec5818111..7d301d610 100644
--- a/application/application.d.ts
+++ b/application/application.d.ts
@@ -64,7 +64,12 @@ declare module "application" {
/**
* The currently active (loaded) android.app.Activity. This property is automatically updated upon Activity events.
*/
- currentActivity: android.app.Activity;
+ foregroundActivity: android.app.Activity;
+
+ /**
+ * The currently active (loaded) Context. This is typically the top-level Activity that is just created.
+ */
+ currentContext: android.content.Context;
/**
* The main (start) Activity for the application.
diff --git a/ui/core/bindable.ts b/ui/core/bindable.ts
new file mode 100644
index 000000000..b26dc39bf
--- /dev/null
+++ b/ui/core/bindable.ts
@@ -0,0 +1,115 @@
+import observable = require("ui/core/observable");
+
+export interface BindingOptions {
+ sourceProperty: string;
+ targetProperty: string;
+ twoWay?: boolean;
+}
+
+export class Bindable extends observable.Observable {
+ private _bindings = {};
+
+ public bind(source: observable.Observable, options: BindingOptions) {
+ var binding: Binding = this._bindings[options.targetProperty];
+ if (binding) {
+ binding.unbind();
+ }
+
+ binding = new Binding(this, options);
+ this._bindings[options.targetProperty] = binding;
+
+ binding.bind(source);
+ }
+
+ public unbind(options: BindingOptions) {
+ var binding: Binding = this._bindings[options.targetProperty];
+ if (binding) {
+ binding.unbind();
+ delete this._bindings[options.targetProperty];
+ }
+ }
+
+ public updateTwoWayBinding(propertyName: string, value: any) {
+ var binding: Binding = this._bindings[propertyName];
+
+ if (binding) {
+ binding.updateTwoWay(value);
+ }
+ }
+
+ public getBinding(propertyName: string) {
+ return this._bindings[propertyName];
+ }
+
+ private clearBinding(binding: Binding) {
+ binding.unbind();
+ }
+}
+
+class Binding {
+ options: BindingOptions;
+ updating = false;
+ source: observable.Observable;
+ target: Bindable;
+
+ constructor(target: Bindable, options: BindingOptions) {
+ this.target = target;
+ this.options = options;
+ }
+
+ public bind(obj: observable.Observable) {
+ this.source = obj;
+ this.updateTarget(this.source.getProperty(this.options.sourceProperty));
+
+ var that = this;
+ var callback = function (data: observable.PropertyChangeData) {
+ that.onSourcePropertyChanged(data);
+ }
+
+ this.source.addObserver(observable.Observable.propertyChangeEvent, callback);
+ }
+
+ public unbind() {
+ if (!this.source) {
+ return;
+ }
+
+ this.source.removeObserver(observable.Observable.propertyChangeEvent, this.onSourcePropertyChanged);
+ this.source = undefined;
+ this.target = undefined;
+ }
+
+ public updateTwoWay(value: any) {
+ if (this.options.twoWay) {
+ this.updateSource(value);
+ }
+ }
+
+ public onSourcePropertyChanged(data: observable.PropertyChangeData) {
+ if (data.propertyName !== this.options.sourceProperty) {
+ return;
+ }
+
+ this.updateTarget(data.value);
+ }
+
+ private updateTarget(value: any) {
+ if (this.updating) {
+ return;
+ }
+
+ this.updating = true;
+ this.target.setProperty(this.options.targetProperty, value);
+ this.updating = false;
+ }
+
+ private updateSource(value: any) {
+ if (this.updating) {
+ return;
+ }
+
+ this.updating = true;
+ this.source.setProperty(this.options.sourceProperty, value);
+ this.updating = false;
+ }
+}
\ No newline at end of file
diff --git a/ui/core/index.ts b/ui/core/index.ts
new file mode 100644
index 000000000..5f282702b
--- /dev/null
+++ b/ui/core/index.ts
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ui/core/observable.ts b/ui/core/observable.ts
index 0213f18bb..a862b54cc 100644
--- a/ui/core/observable.ts
+++ b/ui/core/observable.ts
@@ -11,8 +11,7 @@ export interface ChangeData {
export interface PropertyChangeData extends ChangeData {
propertyName: string;
- oldValue: any;
- newValue: any;
+ value: any;
cancel?: boolean;
}
@@ -23,33 +22,33 @@ export class Observable {
// true to track the Changing phase, false otherwise
private _trackChanging = false;
- public bind(eventName: string, callback: (data: ChangeData) => void) {
+ public addObserver(eventName: string, callback: (data: ChangeData) => void) {
this.verifyCallback(callback);
var list = this.getEventList(eventName, true);
list.push(callback);
}
- public setProperty(name: string, value: any) {
- // TODO: Parameter validation
-
- if (!(name in this._observers)) {
- // no observers to notify for the PropertyChange event
+ public removeObserver(eventName: string, callback: any) {
+ var list = this.getEventList(eventName, false);
+ if (!list) {
return;
}
- // create data for the change
- var data: PropertyChangeData = {
- eventName: Observable.propertyChangeEvent,
- propertyName: name,
- sender: this,
- oldValue: this[name],
- newValue: value,
- cancel: false
- };
+ var index = list.indexOf(callback);
+ if (index >= 0) {
+ list.splice(index, 1);
+ }
+ }
- if (this._trackChanging) {
+ public setProperty(name: string, value: any) {
+ // TODO: Parameter validation
+
+ // create data for the change
+ var data = this.createPropertyChangeData(name, value);
+
+ if (this.hasObservers(Observable.propertyChangeEvent) && this._trackChanging) {
data.phase = ChangePhase.Changing;
- this.notifyObservers(data);
+ this.notify(data);
if (data.cancel) {
// change is canceled by an observer
// TODO: define some priority, e.g. if someone cancels the change should others be able to override this cancelation?
@@ -58,9 +57,8 @@ export class Observable {
}
data.phase = ChangePhase.Changed;
- this.notifyObservers(data);
-
this.setPropertyCore(data);
+ this.notify(data);
}
public getProperty(name: string): any {
@@ -71,8 +69,36 @@ export class Observable {
* This method is intended to be overriden by inheritors to specify additional
*/
public setPropertyCore(data: PropertyChangeData) {
- // get the new value from the data since some observer may have it modified - e.g. validation scenario
- this[data.eventName] = data.newValue;
+ this[data.propertyName] = data.value;
+ }
+
+ // The method will return true if the change is accepted, false otherwise
+ public notify(data: ChangeData) {
+ var observers = this.getEventList(data.eventName);
+ if (!observers) {
+ return;
+ }
+
+ var i,
+ callback;
+ for (i = 0; i < observers.length; i++) {
+ callback = observers[i];
+ callback(data);
+ }
+ }
+
+ public hasObservers(eventName: string) {
+ return eventName in this._observers;
+ }
+
+ public createPropertyChangeData(name: string, value: any): PropertyChangeData {
+ return {
+ eventName: Observable.propertyChangeEvent,
+ propertyName: name,
+ sender: this,
+ value: value,
+ cancel: false
+ };
}
private getEventList(eventName: string, createIfNeeded?: boolean): Array<(data: ChangeData) => void> {
@@ -94,19 +120,4 @@ export class Observable {
throw new TypeError("Callback must be a valid function.");
}
}
-
- // The method will return true if the change is accepted, false otherwise
- private notifyObservers(data: ChangeData) {
- var observers = this.getEventList(data.eventName);
- if (!observers) {
- return;
- }
-
- var i,
- callback;
- for (i = 0; i < observers.length; i++) {
- callback = observers[i];
- callback(data);
- }
- }
}
\ No newline at end of file
diff --git a/ui/core/proxy.ts b/ui/core/proxy.ts
index b5f88f92b..281b32aa8 100644
--- a/ui/core/proxy.ts
+++ b/ui/core/proxy.ts
@@ -1,12 +1,16 @@
import observable = require("ui/core/observable");
+import bindable = require("ui/core/bindable");
-export class ProxyObject extends observable.Observable {
+export class ProxyObject extends bindable.Bindable {
public setPropertyCore(data: observable.PropertyChangeData) {
- super.setPropertyCore(data);
this.setNativeProperty(data);
}
public setNativeProperty(data: observable.PropertyChangeData) {
- // TODO:
+ // inheritors will override this method to provide specific implementation
+ }
+
+ public onNativePropertyChanged(data: observable.PropertyChangeData) {
+
}
}
\ No newline at end of file
diff --git a/ui/core/view.android.ts b/ui/core/view.android.ts
new file mode 100644
index 000000000..d6666aaa0
--- /dev/null
+++ b/ui/core/view.android.ts
@@ -0,0 +1,11 @@
+import proxy = require("ui/core/proxy");
+
+export class View extends proxy.ProxyObject {
+ public addToParent(parent: android.view.ViewGroup) {
+ var nativeInstance: android.view.View = this["android"];
+ if (nativeInstance) {
+ // TODO: Check for existing parent
+ parent.addView(nativeInstance);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/core/view.d.ts b/ui/core/view.d.ts
new file mode 100644
index 000000000..1c3f9ea3f
--- /dev/null
+++ b/ui/core/view.d.ts
@@ -0,0 +1,5 @@
+import proxy = require("ui/core/proxy");
+
+export declare class View extends proxy.ProxyObject {
+ addToParent: (parent: any) => void;
+}
\ No newline at end of file
diff --git a/ui/core/view.ios.ts b/ui/core/view.ios.ts
new file mode 100644
index 000000000..19a76f130
--- /dev/null
+++ b/ui/core/view.ios.ts
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ui/label/index.ts b/ui/label/index.ts
new file mode 100644
index 000000000..909df85b7
--- /dev/null
+++ b/ui/label/index.ts
@@ -0,0 +1,2 @@
+declare var module, require;
+module.exports = require("ui/label/label");
\ No newline at end of file
diff --git a/ui/label/label.android.ts b/ui/label/label.android.ts
new file mode 100644
index 000000000..54cf62b83
--- /dev/null
+++ b/ui/label/label.android.ts
@@ -0,0 +1,55 @@
+import observable = require("ui/core/observable");
+import view = require("ui/core/view");
+import application = require("application");
+
+export class Label extends view.View {
+ private static textProperty = "text";
+ private _android: android.widget.TextView;
+
+ constructor() {
+ super();
+
+ // TODO: Verify that this is always true
+ var context = application.android.currentContext;
+ if (!context) {
+ // TODO: Delayed loading?
+ }
+
+ this._android = new android.widget.TextView(context);
+
+ var that = this;
+ var textWatcher = new android.text.TextWatcher({
+ beforeTextChanged: function (text: string, start: number, count: number, after: number) {
+ },
+ onTextChanged: function (text: string, start: number, before: number, count: number) {
+ },
+ afterTextChanged: function (editable: android.text.IEditable) {
+ //if (that.hasObservers(observable.Observable.propertyChangeEvent)) {
+ // var data = that.createPropertyChangeData(TextView.textProperty, that.text);
+ // that.notify(data);
+ //}
+ that.updateTwoWayBinding("text", editable.toString());
+ }
+ });
+ this._android.addTextChangedListener(textWatcher);
+ }
+
+ get android(): android.widget.TextView {
+ return this._android;
+ }
+
+ get text(): string {
+ return this._android.getText().toString();
+ }
+ set text(value: string) {
+ this.setProperty(Label.textProperty, value);
+ }
+
+ public setNativeProperty(data: observable.PropertyChangeData) {
+ // TODO: Will this be a gigantic if-else switch?
+ if (data.propertyName === Label.textProperty) {
+ this._android.setText(data.value);
+ } else if (true) {
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/label/label.d.ts b/ui/label/label.d.ts
new file mode 100644
index 000000000..57342e807
--- /dev/null
+++ b/ui/label/label.d.ts
@@ -0,0 +1,5 @@
+declare module "ui/label" {
+ class Label {
+ text: string;
+ }
+}
\ No newline at end of file
diff --git a/ui/label/label.ios.ts b/ui/label/label.ios.ts
new file mode 100644
index 000000000..1f056b0e9
--- /dev/null
+++ b/ui/label/label.ios.ts
@@ -0,0 +1,5 @@
+import view = require("ui/core/view");
+
+export class Label extends view.View {
+
+}
\ No newline at end of file
diff --git a/ui/text-input/index.ts b/ui/text-input/index.ts
new file mode 100644
index 000000000..5fce8f31b
--- /dev/null
+++ b/ui/text-input/index.ts
@@ -0,0 +1,2 @@
+declare var module, require;
+module.exports = require("ui/text-input/text-input");
\ No newline at end of file
diff --git a/ui/text-input/text-input.android.ts b/ui/text-input/text-input.android.ts
new file mode 100644
index 000000000..5ca064487
--- /dev/null
+++ b/ui/text-input/text-input.android.ts
@@ -0,0 +1,51 @@
+import observable = require("ui/core/observable");
+import view = require("ui/core/view");
+import application = require("application");
+
+export class TextInput extends view.View {
+ private _android: android.widget.EditText;
+ private static textProperty = "text";
+
+ constructor() {
+ super();
+
+ var context = application.android.currentContext;
+ this._android = new android.widget.EditText(context);
+
+ // TODO: This code is same for Label, extract base class or derive from Label
+ var that = this;
+ var textWatcher = new android.text.TextWatcher({
+ beforeTextChanged: function (text: string, start: number, count: number, after: number) {
+ },
+ onTextChanged: function (text: string, start: number, before: number, count: number) {
+ },
+ afterTextChanged: function (editable: android.text.IEditable) {
+ //if (that.hasObservers(observable.Observable.propertyChangeEvent)) {
+ // var data = that.createPropertyChangeData(TextView.textProperty, that.text);
+ // that.notify(data);
+ //}
+ that.updateTwoWayBinding("text", editable.toString());
+ }
+ });
+ this._android.addTextChangedListener(textWatcher);
+ }
+
+ get android(): android.widget.EditText {
+ return this._android;
+ }
+
+ get text(): string {
+ return this._android.getText().toString();
+ }
+ set text(value: string) {
+ this.setProperty(TextInput.textProperty, value);
+ }
+
+ public setNativeProperty(data: observable.PropertyChangeData) {
+ // TODO: Will this be a gigantic if-else switch?
+ if (data.propertyName === TextInput.textProperty) {
+ this._android.setText(data.value, android.widget.TextView.BufferType.EDITABLE);
+ } else if (true) {
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/text-input/text-input.d.ts b/ui/text-input/text-input.d.ts
new file mode 100644
index 000000000..19a76f130
--- /dev/null
+++ b/ui/text-input/text-input.d.ts
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ui/text-input/text-input.ios.ts b/ui/text-input/text-input.ios.ts
new file mode 100644
index 000000000..19a76f130
--- /dev/null
+++ b/ui/text-input/text-input.ios.ts
@@ -0,0 +1 @@
+
\ No newline at end of file