mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
Initial implementation of Observable + Bindable + Label
This commit is contained in:
30
BCL.csproj
30
BCL.csproj
@@ -152,15 +152,31 @@
|
||||
<TypeScriptCompile Include="text\text.ios.ts">
|
||||
<DependentUpon>text.d.ts</DependentUpon>
|
||||
</TypeScriptCompile>
|
||||
<TypeScriptCompile Include="ui\core\bindable.ts" />
|
||||
<TypeScriptCompile Include="ui\core\index.ts" />
|
||||
<TypeScriptCompile Include="ui\core\proxy.ts" />
|
||||
<TypeScriptCompile Include="ui\text-view\text-view.android.ts">
|
||||
<DependentUpon>text-view.d.ts</DependentUpon>
|
||||
<TypeScriptCompile Include="ui\core\view.android.ts">
|
||||
<DependentUpon>view.d.ts</DependentUpon>
|
||||
</TypeScriptCompile>
|
||||
<TypeScriptCompile Include="ui\text-view\text-view.common.ts" />
|
||||
<TypeScriptCompile Include="ui\text-view\text-view.d.ts" />
|
||||
<TypeScriptCompile Include="ui\text-view\text-view.ios.ts">
|
||||
<DependentUpon>text-view.d.ts</DependentUpon>
|
||||
<TypeScriptCompile Include="ui\core\view.ios.ts">
|
||||
<DependentUpon>view.d.ts</DependentUpon>
|
||||
</TypeScriptCompile>
|
||||
<TypeScriptCompile Include="ui\label\index.ts" />
|
||||
<TypeScriptCompile Include="ui\label\label.d.ts" />
|
||||
<TypeScriptCompile Include="ui\label\label.android.ts">
|
||||
<DependentUpon>label.d.ts</DependentUpon>
|
||||
</TypeScriptCompile>
|
||||
<TypeScriptCompile Include="ui\label\label.ios.ts">
|
||||
<DependentUpon>label.d.ts</DependentUpon>
|
||||
</TypeScriptCompile>
|
||||
<TypeScriptCompile Include="ui\text-input\index.ts" />
|
||||
<TypeScriptCompile Include="ui\text-input\text-input.android.ts">
|
||||
<DependentUpon>text-input.d.ts</DependentUpon>
|
||||
</TypeScriptCompile>
|
||||
<TypeScriptCompile Include="ui\text-input\text-input.ios.ts">
|
||||
<DependentUpon>text-input.d.ts</DependentUpon>
|
||||
</TypeScriptCompile>
|
||||
<TypeScriptCompile Include="ui\text-input\text-input.d.ts" />
|
||||
<TypeScriptCompile Include="utils\module-merge.ts" />
|
||||
<TypeScriptCompile Include="utils\utils_android.ts" />
|
||||
<TypeScriptCompile Include="utils\utils_ios.ts" />
|
||||
@@ -223,7 +239,7 @@
|
||||
<TypeScriptCompile Include="libjs.d.ts" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<TypeScriptCompile Include="ui\core\view.ts" />
|
||||
<TypeScriptCompile Include="ui\core\view.d.ts" />
|
||||
<TypeScriptCompile Include="ui\core\observable.ts" />
|
||||
<Content Include="_references.ts" />
|
||||
<Content Include="image-source\Readme.md" />
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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() {
|
||||
|
||||
7
application/application.d.ts
vendored
7
application/application.d.ts
vendored
@@ -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.
|
||||
|
||||
115
ui/core/bindable.ts
Normal file
115
ui/core/bindable.ts
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
1
ui/core/index.ts
Normal file
1
ui/core/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
11
ui/core/view.android.ts
Normal file
11
ui/core/view.android.ts
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
5
ui/core/view.d.ts
vendored
Normal file
5
ui/core/view.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import proxy = require("ui/core/proxy");
|
||||
|
||||
export declare class View extends proxy.ProxyObject {
|
||||
addToParent: (parent: any) => void;
|
||||
}
|
||||
1
ui/core/view.ios.ts
Normal file
1
ui/core/view.ios.ts
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
2
ui/label/index.ts
Normal file
2
ui/label/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
declare var module, require;
|
||||
module.exports = require("ui/label/label");
|
||||
55
ui/label/label.android.ts
Normal file
55
ui/label/label.android.ts
Normal file
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
5
ui/label/label.d.ts
vendored
Normal file
5
ui/label/label.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare module "ui/label" {
|
||||
class Label {
|
||||
text: string;
|
||||
}
|
||||
}
|
||||
5
ui/label/label.ios.ts
Normal file
5
ui/label/label.ios.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import view = require("ui/core/view");
|
||||
|
||||
export class Label extends view.View {
|
||||
|
||||
}
|
||||
2
ui/text-input/index.ts
Normal file
2
ui/text-input/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
declare var module, require;
|
||||
module.exports = require("ui/text-input/text-input");
|
||||
51
ui/text-input/text-input.android.ts
Normal file
51
ui/text-input/text-input.android.ts
Normal file
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
1
ui/text-input/text-input.d.ts
vendored
Normal file
1
ui/text-input/text-input.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
ui/text-input/text-input.ios.ts
Normal file
1
ui/text-input/text-input.ios.ts
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
Reference in New Issue
Block a user