mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
WeakEvents refactoring and used in ListView
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
<IISExpressAnonymousAuthentication />
|
||||
<IISExpressWindowsAuthentication />
|
||||
<IISExpressUseClassicPipelineMode />
|
||||
<UseGlobalApplicationHostFile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Cross</Configuration>
|
||||
@@ -186,6 +187,7 @@
|
||||
<DependentUpon>main-page.xml</DependentUpon>
|
||||
</TypeScriptCompile>
|
||||
<TypeScriptCompile Include="apps\editable-text-demo\model.ts" />
|
||||
<TypeScriptCompile Include="apps\tests\weak-event-listener-tests.ts" />
|
||||
<TypeScriptCompile Include="apps\ui-tests-app\pages\i61.ts" />
|
||||
<TypeScriptCompile Include="apps\ui-tests-app\pages\i73.ts" />
|
||||
<TypeScriptCompile Include="apps\ui-tests-app\pages\gestures.ts" />
|
||||
@@ -1575,7 +1577,7 @@
|
||||
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
|
||||
</WebProjectProperties>
|
||||
</FlavorProperties>
|
||||
<UserProperties ui_2scroll-view_2package_1json__JSONSchema="http://json.schemastore.org/package" apps_2editable-text-demo_2package_1json__JSONSchema="http://json.schemastore.org/package" apps_2absolute-layout-demo_2package_1json__JSONSchema="http://json.schemastore.org/package" apps_2gallery-app_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2content-view_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2web-view_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2layouts_2linear-layout_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2layouts_2absolute-layout_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2layouts_2dock-layout_2package_1json__JSONSchema="" ui_2layouts_2grid-layout_2package_1json__JSONSchema="" ui_2layouts_2wrap-layout_2package_1json__JSONSchema="http://json.schemastore.org/package" />
|
||||
<UserProperties ui_2layouts_2wrap-layout_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2layouts_2grid-layout_2package_1json__JSONSchema="" ui_2layouts_2dock-layout_2package_1json__JSONSchema="" ui_2layouts_2absolute-layout_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2layouts_2linear-layout_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2web-view_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2content-view_2package_1json__JSONSchema="http://json.schemastore.org/package" apps_2gallery-app_2package_1json__JSONSchema="http://json.schemastore.org/package" apps_2absolute-layout-demo_2package_1json__JSONSchema="http://json.schemastore.org/package" apps_2editable-text-demo_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2scroll-view_2package_1json__JSONSchema="http://json.schemastore.org/package" />
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
||||
@@ -68,6 +68,8 @@ allTests["LIST-PICKER"] = require("./ui/list-picker/list-picker-tests");
|
||||
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");
|
||||
|
||||
if (!isRunningOnEmulator()) {
|
||||
allTests["LOCATION"] = require("./location-tests");
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import TKUnit = require("../TKUnit");
|
||||
import utils = require("utils/utils");
|
||||
import types = require("utils/types");
|
||||
import styling = require("ui/styling");
|
||||
import platform = require("platform");
|
||||
|
||||
var DELTA = 0.1;
|
||||
|
||||
@@ -174,11 +175,7 @@ export function buildUIWithWeakRefAndInteract<T extends view.View>(createFunc: (
|
||||
}
|
||||
|
||||
sp.removeChild(weakRef.get());
|
||||
if (newPage.ios) {
|
||||
// Could cause GC on the next call.
|
||||
new ArrayBuffer(4 * 1024 * 1024);
|
||||
}
|
||||
utils.GC();
|
||||
forceGC();
|
||||
|
||||
TKUnit.assert(!weakRef.get(), weakRef.get() + " leaked!");
|
||||
testFinished = true;
|
||||
@@ -222,4 +219,12 @@ export function assertAreClose(actual: number, expected: number, message: string
|
||||
var delta = Math.floor(density) !== density ? 1.1 : DELTA;
|
||||
|
||||
TKUnit.assertAreClose(actual, expected, delta, message);
|
||||
}
|
||||
|
||||
export function forceGC() {
|
||||
if (platform.device.os === platform.platformNames.ios) {
|
||||
// Could cause GC on the next call.
|
||||
new ArrayBuffer(4 * 1024 * 1024);
|
||||
}
|
||||
utils.GC();
|
||||
}
|
||||
204
apps/tests/weak-event-listener-tests.ts
Normal file
204
apps/tests/weak-event-listener-tests.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
import TKUnit = require("./TKUnit");
|
||||
import types = require("utils/types");
|
||||
import observable = require("data/observable");
|
||||
import weakEvents = require("ui/core/weak-event-listener");
|
||||
import helper = require("./ui/helper");
|
||||
|
||||
export function test_addWeakEventListener_throwsWhenCalledwitnInvalid_source() {
|
||||
TKUnit.assertThrows(() => {
|
||||
weakEvents.WeakEventListener.addWeakEventListener({
|
||||
source: undefined,
|
||||
target: {},
|
||||
handler: emptyHandler,
|
||||
eventName: observable.Observable.propertyChangeEvent
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function test_addWeakEventListener_throwsWhenCalledwitnInvalid_target() {
|
||||
TKUnit.assertThrows(() => {
|
||||
weakEvents.WeakEventListener.addWeakEventListener({
|
||||
source: new observable.Observable(),
|
||||
target: undefined,
|
||||
handler: emptyHandler,
|
||||
eventName: observable.Observable.propertyChangeEvent
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function test_addWeakEventListener_throwsWhenCalledwitnInvalid_handler() {
|
||||
TKUnit.assertThrows(() => {
|
||||
weakEvents.WeakEventListener.addWeakEventListener({
|
||||
source: new observable.Observable(),
|
||||
target: {},
|
||||
handler: undefined,
|
||||
eventName: observable.Observable.propertyChangeEvent
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function test_addWeakEventListener_throwsWhenCalledwitnInvalid_name() {
|
||||
TKUnit.assertThrows(() => {
|
||||
weakEvents.WeakEventListener.addWeakEventListener({
|
||||
source: new observable.Observable(),
|
||||
target: {},
|
||||
handler: emptyHandler,
|
||||
eventName: undefined
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function test_addWeakEventListener_listensForEvent() {
|
||||
var source = new observable.Observable();
|
||||
var target = new Object;
|
||||
var callbackCalled = false;
|
||||
var handler = function (args: observable.EventData) {
|
||||
callbackCalled = true;
|
||||
}
|
||||
|
||||
weakEvents.WeakEventListener.addWeakEventListener({
|
||||
source: source,
|
||||
target: target,
|
||||
handler: handler,
|
||||
eventName: observable.Observable.propertyChangeEvent
|
||||
})
|
||||
|
||||
source.set("testProp", "some value");
|
||||
|
||||
TKUnit.assert(callbackCalled, "Handler not called.");
|
||||
}
|
||||
|
||||
export function test_removeWeakEventListener_StopsListeningForEvet() {
|
||||
var source = new observable.Observable();
|
||||
var target = new Object;
|
||||
var callbackCalled = false;
|
||||
var handler = function (args: observable.EventData) {
|
||||
callbackCalled = true;
|
||||
}
|
||||
|
||||
var listenerID = weakEvents.WeakEventListener.addWeakEventListener({
|
||||
source: source,
|
||||
target: target,
|
||||
handler: handler,
|
||||
eventName: observable.Observable.propertyChangeEvent
|
||||
})
|
||||
|
||||
weakEvents.WeakEventListener.removeWeakEventListener(listenerID);
|
||||
|
||||
source.set("testProp", "some value");
|
||||
TKUnit.assert(!callbackCalled, "Handler should not be called.");
|
||||
}
|
||||
|
||||
export function test_handlerIsCalled_WithTargetAsThis() {
|
||||
var source = new observable.Observable();
|
||||
var target = new Object;
|
||||
var callbackCalled = false;
|
||||
var handler = function (args: observable.EventData) {
|
||||
TKUnit.assertEqual(this, target, "this should be the target");
|
||||
callbackCalled = true;
|
||||
}
|
||||
|
||||
weakEvents.WeakEventListener.addWeakEventListener({
|
||||
source: source,
|
||||
target: target,
|
||||
handler: handler,
|
||||
eventName: observable.Observable.propertyChangeEvent
|
||||
})
|
||||
|
||||
source.set("testProp", "some value");
|
||||
TKUnit.assert(callbackCalled, "Handler not called.");
|
||||
}
|
||||
|
||||
export function test_listnerDoesNotRetainTarget() {
|
||||
var source = new observable.Observable();
|
||||
var target = new Object;
|
||||
var callbackCalled = false;
|
||||
var handler = function (args: observable.EventData) {
|
||||
TKUnit.assertEqual(this, target, "this should be the target");
|
||||
callbackCalled = true;
|
||||
}
|
||||
|
||||
weakEvents.WeakEventListener.addWeakEventListener({
|
||||
source: source,
|
||||
target: target,
|
||||
handler: handler,
|
||||
eventName: observable.Observable.propertyChangeEvent
|
||||
})
|
||||
|
||||
var targetRef = new WeakRef(target);
|
||||
target = undefined;
|
||||
helper.forceGC();
|
||||
|
||||
TKUnit.assert(!targetRef.get(), "Target should be released after GC");
|
||||
}
|
||||
|
||||
export function test_listnerDoesNotRetainSource() {
|
||||
var source = new observable.Observable();
|
||||
var target = new Object();
|
||||
var callbackCalled = false;
|
||||
var handler = function (args: observable.EventData) {
|
||||
TKUnit.assertEqual(this, target, "this should be the target");
|
||||
callbackCalled = true;
|
||||
}
|
||||
|
||||
weakEvents.WeakEventListener.addWeakEventListener({
|
||||
source: source,
|
||||
target: target,
|
||||
handler: handler,
|
||||
eventName: observable.Observable.propertyChangeEvent
|
||||
})
|
||||
|
||||
var sourceRef = new WeakRef(source);
|
||||
source = undefined;
|
||||
helper.forceGC();
|
||||
|
||||
TKUnit.assert(!sourceRef.get(), "Source should be released after GC");
|
||||
}
|
||||
|
||||
export function test_listnerIsCleared_WhenTargetIsDead() {
|
||||
var source = new observable.Observable();
|
||||
|
||||
var listenerID = addListenerWithSource(source);
|
||||
helper.forceGC();
|
||||
|
||||
for (var i = 0; i < weakEvents.WeakEventListener.cleanDeadReferencesCountTrigger; i++) {
|
||||
addListenerWithSource(source);
|
||||
}
|
||||
|
||||
TKUnit.assert(types.isUndefined(weakEvents.WeakEventListener._weakEventListeners[listenerID]), "The first listener should be dead by now");
|
||||
}
|
||||
|
||||
export function test_listnerIsCleared_WhenSourceIsDead() {
|
||||
var target = {};
|
||||
|
||||
var listenerID = addListenerWithTarget(target);
|
||||
helper.forceGC();
|
||||
|
||||
for (var i = 0; i < weakEvents.WeakEventListener.cleanDeadReferencesCountTrigger; i++) {
|
||||
addListenerWithTarget(target);
|
||||
}
|
||||
|
||||
TKUnit.assert(types.isUndefined(weakEvents.WeakEventListener._weakEventListeners[listenerID]), "The first listener should be dead by now");
|
||||
}
|
||||
|
||||
function addListenerWithSource(source: observable.Observable): number {
|
||||
return weakEvents.WeakEventListener.addWeakEventListener({
|
||||
source: source,
|
||||
target: {},
|
||||
handler: emptyHandler,
|
||||
eventName: observable.Observable.propertyChangeEvent
|
||||
})
|
||||
}
|
||||
|
||||
function addListenerWithTarget(target: any): number {
|
||||
return weakEvents.WeakEventListener.addWeakEventListener({
|
||||
source: new observable.Observable(),
|
||||
target: target,
|
||||
handler: emptyHandler,
|
||||
eventName: observable.Observable.propertyChangeEvent
|
||||
})
|
||||
}
|
||||
|
||||
function emptyHandler(data: observable.EventData) {
|
||||
// Do nothing.
|
||||
}
|
||||
@@ -33,12 +33,12 @@ function onFormattedTextPropertyChanged(data: dependencyObservable.PropertyChang
|
||||
(<proxy.PropertyMetadata>formattedTextProperty.metadata).onSetNativeValue = onFormattedTextPropertyChanged;
|
||||
|
||||
export class Button extends view.View implements definition.Button {
|
||||
|
||||
public static tapEvent = "tap";
|
||||
|
||||
public static textProperty = textProperty;
|
||||
public static formattedTextProperty = formattedTextProperty;
|
||||
|
||||
private _formattedTextWeakListenerId: number;
|
||||
|
||||
public _onBindingContextChanged(oldValue: any, newValue: any) {
|
||||
super._onBindingContextChanged(oldValue, newValue);
|
||||
if (this.formattedText) {
|
||||
@@ -60,20 +60,17 @@ export class Button extends view.View implements definition.Button {
|
||||
|
||||
set formattedText(value: formattedString.FormattedString) {
|
||||
if (this.formattedText !== value) {
|
||||
var weakEventOptions: weakEventListener.WeakEventListenerOptions = {
|
||||
targetWeakRef: new WeakRef(this),
|
||||
eventName: observable.Observable.propertyChangeEvent,
|
||||
sourceWeakRef: new WeakRef(value),
|
||||
handler: this.onFormattedTextChanged,
|
||||
handlerContext: this,
|
||||
key: "formattedText"
|
||||
};
|
||||
if (this.formattedText) {
|
||||
weakEventListener.WeakEventListener.removeWeakEventListener(weakEventOptions);
|
||||
weakEventListener.WeakEventListener.removeWeakEventListener(this._formattedTextWeakListenerId);
|
||||
}
|
||||
this._setValue(Button.formattedTextProperty, value);
|
||||
if (value) {
|
||||
weakEventListener.WeakEventListener.addWeakEventListener(weakEventOptions);
|
||||
this._formattedTextWeakListenerId = weakEventListener.WeakEventListener.addWeakEventListener({
|
||||
target: this,
|
||||
source: value,
|
||||
eventName: observable.Observable.propertyChangeEvent,
|
||||
handler: this.onFormattedTextChanged
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import observable = require("data/observable");
|
||||
import definition = require("ui/core/bindable");
|
||||
import dependencyObservable = require("ui/core/dependency-observable");
|
||||
import weakEventListener = require("ui/core/weak-event-listener");
|
||||
import weakEvents = require("ui/core/weak-event-listener");
|
||||
import appModule = require("application");
|
||||
import types = require("utils/types");
|
||||
import trace = require("trace");
|
||||
@@ -108,7 +108,7 @@ export class Bindable extends dependencyObservable.DependencyObservable implemen
|
||||
}
|
||||
|
||||
trace.write(
|
||||
"Binding target: " + binding.target.get() +
|
||||
"Binding target: " + binding.target.get() +
|
||||
" targetProperty: " + binding.options.targetProperty +
|
||||
" to the changed context: " + newValue, trace.categories.Binding);
|
||||
binding.unbind();
|
||||
@@ -124,9 +124,7 @@ export class Binding {
|
||||
updating = false;
|
||||
source: WeakRef<Object>;
|
||||
target: WeakRef<Bindable>;
|
||||
weakEventListenerOptions: weakEventListener.WeakEventListenerOptions;
|
||||
|
||||
weakEL = weakEventListener.WeakEventListener;
|
||||
weakEventId: number;
|
||||
|
||||
private sourceOptions: { instance: WeakRef<any>; property: any };
|
||||
private targetOptions: { instance: WeakRef<any>; property: any };
|
||||
@@ -145,11 +143,11 @@ export class Binding {
|
||||
if (typeof (obj) === "number") {
|
||||
obj = new Number(obj);
|
||||
}
|
||||
|
||||
|
||||
if (typeof (obj) === "boolean") {
|
||||
obj = new Boolean(obj);
|
||||
}
|
||||
|
||||
|
||||
if (typeof (obj) === "string") {
|
||||
obj = new String(obj);
|
||||
}
|
||||
@@ -164,15 +162,12 @@ export class Binding {
|
||||
if (this.sourceOptions) {
|
||||
var sourceOptionsInstance = this.sourceOptions.instance.get();
|
||||
if (sourceOptionsInstance instanceof observable.Observable) {
|
||||
this.weakEventListenerOptions = {
|
||||
targetWeakRef: this.target,
|
||||
sourceWeakRef: this.sourceOptions.instance,
|
||||
this.weakEventId = weakEvents.WeakEventListener.addWeakEventListener({
|
||||
target: this,
|
||||
source: this.sourceOptions.instance.get(),
|
||||
eventName: observable.Observable.propertyChangeEvent,
|
||||
handler: this.onSourcePropertyChanged,
|
||||
handlerContext: this,
|
||||
key: this.options.targetProperty
|
||||
}
|
||||
this.weakEL.addWeakEventListener(this.weakEventListenerOptions);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,24 +177,25 @@ export class Binding {
|
||||
return;
|
||||
}
|
||||
|
||||
this.weakEL.removeWeakEventListener(this.weakEventListenerOptions);
|
||||
this.weakEventListenerOptions = undefined;
|
||||
if (this.source) {
|
||||
this.source.clear();
|
||||
}
|
||||
if (this.sourceOptions) {
|
||||
this.sourceOptions.instance.clear();
|
||||
this.sourceOptions = undefined;
|
||||
}
|
||||
if (this.targetOptions) {
|
||||
this.targetOptions = undefined;
|
||||
}
|
||||
weakEvents.WeakEventListener.removeWeakEventListener(this.weakEventId);
|
||||
delete this.weakEventId;
|
||||
|
||||
if (this.source) {
|
||||
this.source.clear();
|
||||
}
|
||||
if (this.sourceOptions) {
|
||||
this.sourceOptions.instance.clear();
|
||||
this.sourceOptions = undefined;
|
||||
}
|
||||
if (this.targetOptions) {
|
||||
this.targetOptions = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public updateTwoWay(value: any) {
|
||||
if (this.updating) {
|
||||
return;
|
||||
}
|
||||
if (this.updating) {
|
||||
return;
|
||||
}
|
||||
if (this.options.twoWay) {
|
||||
if (this._isExpression(this.options.expression)) {
|
||||
var changedModel = {};
|
||||
@@ -238,15 +234,15 @@ export class Binding {
|
||||
var exp = polymerExpressions.PolymerExpressions.getExpression(expression);
|
||||
if (exp) {
|
||||
var context = this.source && this.source.get && this.source.get() || global;
|
||||
var model = {};
|
||||
model[contextKey] = context;
|
||||
model[resourcesKey] = appModule.resources;
|
||||
return exp.getValue(model, isBackConvert, changedModel);
|
||||
var model = {};
|
||||
model[contextKey] = context;
|
||||
model[resourcesKey] = appModule.resources;
|
||||
return exp.getValue(model, isBackConvert, changedModel);
|
||||
}
|
||||
return new Error(expression + " is not a valid expression.");
|
||||
}
|
||||
catch (e) {
|
||||
var errorMessage = "Run-time error occured in file: " + e.sourceURL + " at line: " + e.line + " and column: " + e.column;
|
||||
var errorMessage = "Run-time error occured in file: " + e.sourceURL + " at line: " + e.line + " and column: " + e.column;
|
||||
return new Error(errorMessage);
|
||||
}
|
||||
}
|
||||
@@ -293,7 +289,7 @@ export class Binding {
|
||||
}
|
||||
else if (sourceOptionsInstance instanceof observable.Observable) {
|
||||
value = sourceOptionsInstance.get(this.sourceOptions.property);
|
||||
}
|
||||
}
|
||||
else if (sourceOptionsInstance && this.sourceOptions.property &&
|
||||
this.sourceOptions.property in sourceOptionsInstance) {
|
||||
value = sourceOptionsInstance[this.sourceOptions.property];
|
||||
|
||||
36
ui/core/weak-event-listener.d.ts
vendored
36
ui/core/weak-event-listener.d.ts
vendored
@@ -8,12 +8,12 @@ declare module "ui/core/weak-event-listener" {
|
||||
/**
|
||||
* Weak reference to the subscriber (target) of the event listener.
|
||||
*/
|
||||
targetWeakRef: WeakRef<any>;
|
||||
target: any;
|
||||
|
||||
/**
|
||||
* Weak reference to an instance of observable.Observable class which emits the event.
|
||||
* Weak reference to an instance of observable. Observable class which emits the event.
|
||||
*/
|
||||
sourceWeakRef: WeakRef<observable.Observable>;
|
||||
source: observable.Observable;
|
||||
|
||||
/**
|
||||
* Name of the event.
|
||||
@@ -24,16 +24,6 @@ declare module "ui/core/weak-event-listener" {
|
||||
* The function which should be called when event occurs.
|
||||
*/
|
||||
handler: (eventData: observable.EventData) => void;
|
||||
|
||||
/**
|
||||
* The context (thisArg) in which handler should be executed.
|
||||
*/
|
||||
handlerContext?: any;
|
||||
|
||||
/**
|
||||
* A string to use as key for key value pair instance.
|
||||
*/
|
||||
key: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,14 +33,26 @@ declare module "ui/core/weak-event-listener" {
|
||||
/**
|
||||
* Creates and initialize WeakEventListener (if all required options are set).
|
||||
* @param options An instance of WeakEventListenerOptions needed to create WeakEventListener instance.
|
||||
* Returns true if a WeakEventListener instance is created successfully.
|
||||
* Returns The id of the WeakEventListener object to be used in removeWeakEventListener method.
|
||||
*/
|
||||
static addWeakEventListener(options: WeakEventListenerOptions): boolean;
|
||||
static addWeakEventListener(options: WeakEventListenerOptions): number;
|
||||
|
||||
/**
|
||||
* Removes and clears all resources from WeakEventListener.
|
||||
* @param options An instance of WeakEventListenerOptions used to create the WeakEventListener instance.
|
||||
* @param The id of the WeakEventListener object.
|
||||
*/
|
||||
static removeWeakEventListener(options: WeakEventListenerOptions): void;
|
||||
static removeWeakEventListener(listenerId: number): void;
|
||||
|
||||
/**
|
||||
* Specifies how often will internal clearing of dead references will occur.
|
||||
* Clearing will be triggered on addWeakEventListener on every N times where N is
|
||||
* the value of this property. Set 0 to disable clearing.
|
||||
*/
|
||||
static cleanDeadReferencesCountTrigger: number;
|
||||
|
||||
//@private
|
||||
static _cleanDeadReferences();
|
||||
static _weakEventListeners: Object;
|
||||
//@endprivate
|
||||
}
|
||||
}
|
||||
@@ -1,103 +1,121 @@
|
||||
import observable = require("data/observable");
|
||||
import definition = require("ui/core/weak-event-listener");
|
||||
import types = require("utils/types");
|
||||
|
||||
var CLEAN_TRIGGER_DEFAULT = 1000;
|
||||
export class WeakEventListener implements definition.WeakEventListener {
|
||||
private listener: WeakRef<any>;
|
||||
private sender: WeakRef<observable.Observable>;
|
||||
private id: number;
|
||||
private targetRef: WeakRef<any>;
|
||||
private senderRef: WeakRef<observable.Observable>;
|
||||
private eventName: string;
|
||||
private handler: (eventData: observable.EventData) => void;
|
||||
private handlerContext: any;
|
||||
|
||||
static rootWeakEventListenersMap = new WeakMap();
|
||||
static _idCounter: number = 0;
|
||||
static _weakEventListeners = {};
|
||||
static _cleanDeadReferencesCountTrigger = CLEAN_TRIGGER_DEFAULT;
|
||||
static get cleanDeadReferencesCountTrigger(): number {
|
||||
return WeakEventListener._cleanDeadReferencesCountTrigger;
|
||||
}
|
||||
static set cleanDeadReferencesCountTrigger(value: number) {
|
||||
WeakEventListener._cleanDeadReferencesCountTrigger = value;
|
||||
}
|
||||
|
||||
private handlerCallback(eventData) {
|
||||
if (this.handler) {
|
||||
if (this.handlerContext) {
|
||||
this.handler.call(this.handlerContext, eventData);
|
||||
}
|
||||
else {
|
||||
this.handler(eventData);
|
||||
}
|
||||
var target = this.targetRef.get();
|
||||
if (target) {
|
||||
this.handler.call(target, eventData);
|
||||
}
|
||||
else {
|
||||
// The target is dead - we can unsubscribe;
|
||||
WeakEventListener.removeWeakEventListener(this.id);
|
||||
}
|
||||
}
|
||||
|
||||
private init(options: definition.WeakEventListenerOptions) {
|
||||
this.listener = options.targetWeakRef;
|
||||
this.sender = options.sourceWeakRef;
|
||||
private init(options: definition.WeakEventListenerOptions, listenerId: number) {
|
||||
this.id = listenerId;
|
||||
this.targetRef = new WeakRef(options.target);
|
||||
this.senderRef = new WeakRef(options.source);
|
||||
this.eventName = options.eventName;
|
||||
this.handler = options.handler;
|
||||
if (options.handlerContext) {
|
||||
this.handlerContext = options.handlerContext;
|
||||
}
|
||||
var sourceInstance = this.sender.get();
|
||||
|
||||
var sourceInstance = this.senderRef.get();
|
||||
if (sourceInstance) {
|
||||
sourceInstance.addEventListener(this.eventName, this.handlerCallback, this);
|
||||
}
|
||||
}
|
||||
|
||||
static addWeakEventListener(options: definition.WeakEventListenerOptions) {
|
||||
if (options.targetWeakRef && options.sourceWeakRef && options.eventName && options.handler && options.key) {
|
||||
var weakEventListener = new WeakEventListener();
|
||||
weakEventListener.init(options);
|
||||
var targetWeakEventListenersMap = WeakEventListener.getWeakMapValueByKeys([options.sourceWeakRef, options.targetWeakRef]);
|
||||
targetWeakEventListenersMap[options.key] = weakEventListener;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private clear() {
|
||||
var sourceInstance = this.sender.get();
|
||||
if (sourceInstance) {
|
||||
sourceInstance.removeEventListener(this.eventName, this.handlerCallback, this);
|
||||
this.sender.clear();
|
||||
var sender = this.senderRef.get();
|
||||
if (sender) {
|
||||
sender.removeEventListener(this.eventName, this.handlerCallback, this);
|
||||
}
|
||||
this.listener = undefined;
|
||||
|
||||
this.targetRef.clear();
|
||||
this.targetRef = undefined;
|
||||
this.senderRef.clear();
|
||||
this.senderRef = undefined;
|
||||
|
||||
this.eventName = undefined;
|
||||
this.handler = undefined;
|
||||
this.handlerContext = undefined;
|
||||
}
|
||||
|
||||
static getWeakMapValueByKeys(keys) {
|
||||
var result;
|
||||
if (!WeakEventListener.rootWeakEventListenersMap) {
|
||||
WeakEventListener.rootWeakEventListenersMap = new WeakMap();
|
||||
static addWeakEventListener(options: definition.WeakEventListenerOptions) {
|
||||
if (types.isNullOrUndefined(options.target)) {
|
||||
throw new Error("targetWeakRef is null or undefined");
|
||||
}
|
||||
var currentMap = WeakEventListener.rootWeakEventListenersMap;
|
||||
var i;
|
||||
for (i = 0; i < keys.length - 1; i++) {
|
||||
if (currentMap.has(keys[i])) {
|
||||
currentMap = <WeakMap<{}, {}>>currentMap.get(keys[i]);
|
||||
}
|
||||
else {
|
||||
var innerMap = new WeakMap();
|
||||
currentMap.set(keys[i], innerMap);
|
||||
currentMap = innerMap;
|
||||
|
||||
if (types.isNullOrUndefined(options.source)) {
|
||||
throw new Error("sourceWeakRef is null or undefined");
|
||||
}
|
||||
|
||||
if (!types.isString(options.eventName)) {
|
||||
throw new Error("eventName is not a string");
|
||||
}
|
||||
|
||||
if (!types.isFunction(options.handler)) {
|
||||
throw new Error("handler is not a function");
|
||||
}
|
||||
|
||||
var listenerId = WeakEventListener._idCounter++;
|
||||
var weakEventListener = new WeakEventListener();
|
||||
weakEventListener.init(options, listenerId);
|
||||
|
||||
WeakEventListener._weakEventListeners[listenerId] = new WeakRef(weakEventListener);
|
||||
|
||||
if (WeakEventListener._cleanDeadReferencesCountTrigger &&
|
||||
(listenerId % WeakEventListener._cleanDeadReferencesCountTrigger) === 0) {
|
||||
WeakEventListener._cleanDeadReferences();
|
||||
}
|
||||
|
||||
return listenerId;
|
||||
}
|
||||
|
||||
static removeWeakEventListener(listenerId: number) {
|
||||
var listenerRef = <WeakRef<WeakEventListener>>WeakEventListener._weakEventListeners[listenerId];
|
||||
|
||||
if (listenerRef) {
|
||||
var listener = <WeakEventListener>listenerRef.get();
|
||||
if (listener) {
|
||||
listener.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (currentMap.has(keys[keys.length - 1])) {
|
||||
result = currentMap.get(keys[keys.length - 1]);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
result = {};
|
||||
currentMap.set(keys[keys.length - 1], result);
|
||||
}
|
||||
return result;
|
||||
delete WeakEventListener._weakEventListeners[listenerId];
|
||||
}
|
||||
|
||||
static removeWeakEventListener(options: definition.WeakEventListenerOptions) {
|
||||
if (options && options.sourceWeakRef && options.targetWeakRef && options.key) {
|
||||
var weakMapValueForKey = WeakEventListener.getWeakMapValueByKeys([options.sourceWeakRef, options.targetWeakRef]);
|
||||
if (weakMapValueForKey && weakMapValueForKey[options.key]) {
|
||||
if (weakMapValueForKey[options.key] instanceof definition.WeakEventListener) {
|
||||
(<WeakEventListener>weakMapValueForKey[options.key]).clear();
|
||||
}
|
||||
delete weakMapValueForKey[options.key];
|
||||
static _cleanDeadReferences() {
|
||||
var deadListeners = new Array<number>();
|
||||
for (var id in WeakEventListener._weakEventListeners) {
|
||||
var listenerRef = <WeakRef<WeakEventListener>>WeakEventListener._weakEventListeners[id];
|
||||
|
||||
var listener = listenerRef.get();
|
||||
if (!listener || !listener.targetRef.get() || !listener.senderRef.get()) {
|
||||
deadListeners.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < deadListeners.length; i++) {
|
||||
WeakEventListener.removeWeakEventListener(deadListeners[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,13 @@ import builder = require("ui/builder");
|
||||
import label = require("ui/label");
|
||||
import color = require("color");
|
||||
import weakEventListener = require("ui/core/weak-event-listener");
|
||||
import types = require("utils/types");
|
||||
|
||||
var ITEMS = "items";
|
||||
var ITEMTEMPLATE = "itemTemplate";
|
||||
var ISSCROLLING = "isScrolling";
|
||||
var LISTVIEW = "ListView";
|
||||
var SEPARATORCOLOR = "separatorColor";
|
||||
var WEAKEVENTKEY = "_observableArrayChanged";
|
||||
|
||||
export module knownTemplates {
|
||||
export var itemTemplate = "itemTemplate";
|
||||
@@ -69,13 +69,7 @@ export class ListView extends view.View implements definition.ListView {
|
||||
)
|
||||
);
|
||||
|
||||
private _itemsChanged: (args: observable.EventData) => void;
|
||||
private _weakEventListenerOptions: weakEventListener.WeakEventListenerOptions;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._itemsChanged = (args: observable.EventData) => { this.refresh(); };
|
||||
}
|
||||
private _itemsChangedWeakListenerId: number;
|
||||
|
||||
get items(): any {
|
||||
return this._getValue(ListView.itemsProperty);
|
||||
@@ -136,27 +130,27 @@ export class ListView extends view.View implements definition.ListView {
|
||||
return lbl;
|
||||
}
|
||||
|
||||
|
||||
public _onItemsPropertyChanged(data: dependencyObservable.PropertyChangeData) {
|
||||
if (data.oldValue instanceof observable.Observable && this._weakEventListenerOptions) {
|
||||
weakEventListener.WeakEventListener.removeWeakEventListener(this._weakEventListenerOptions);
|
||||
this._weakEventListenerOptions = null;
|
||||
if (data.oldValue instanceof observable.Observable && types.isDefined(this._itemsChangedWeakListenerId)) {
|
||||
weakEventListener.WeakEventListener.removeWeakEventListener(this._itemsChangedWeakListenerId);
|
||||
delete this._itemsChangedWeakListenerId;
|
||||
}
|
||||
|
||||
if (data.newValue instanceof observable.Observable) {
|
||||
this._weakEventListenerOptions = {
|
||||
targetWeakRef: new WeakRef(this),
|
||||
sourceWeakRef: new WeakRef(data.newValue),
|
||||
this._itemsChangedWeakListenerId = weakEventListener.WeakEventListener.addWeakEventListener({
|
||||
target: this,
|
||||
source: data.newValue,
|
||||
eventName: observableArray.ObservableArray.changeEvent,
|
||||
handler: this._itemsChanged,
|
||||
handlerContext: this,
|
||||
key: WEAKEVENTKEY
|
||||
};
|
||||
weakEventListener.WeakEventListener.addWeakEventListener(this._weakEventListenerOptions);
|
||||
handler: this._onItemsChanged,
|
||||
});
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
private _onItemsChanged(args: observable.EventData) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
function getExports(instance: view.View): any {
|
||||
|
||||
@@ -38,6 +38,8 @@ export class TextBase extends view.View implements definition.TextBase {
|
||||
public static textProperty = textProperty;
|
||||
public static formattedTextProperty = formattedTextProperty;
|
||||
|
||||
private _formattedTextChangedListenerId: number;
|
||||
|
||||
constructor(options?: definition.Options) {
|
||||
super(options);
|
||||
}
|
||||
@@ -77,20 +79,18 @@ export class TextBase extends view.View implements definition.TextBase {
|
||||
|
||||
set formattedText(value: formattedString.FormattedString) {
|
||||
if (this.formattedText !== value) {
|
||||
var weakEventOptions: weakEventListener.WeakEventListenerOptions = {
|
||||
targetWeakRef: new WeakRef(this),
|
||||
eventName: observable.Observable.propertyChangeEvent,
|
||||
sourceWeakRef: new WeakRef(value),
|
||||
handler: this.onFormattedTextChanged,
|
||||
handlerContext: this,
|
||||
key: "formattedText"
|
||||
};
|
||||
if (this.formattedText) {
|
||||
weakEventListener.WeakEventListener.removeWeakEventListener(weakEventOptions);
|
||||
weakEventListener.WeakEventListener.removeWeakEventListener(this._formattedTextChangedListenerId);
|
||||
delete this._formattedTextChangedListenerId;
|
||||
}
|
||||
this._setValue(TextBase.formattedTextProperty, value);
|
||||
if (value) {
|
||||
weakEventListener.WeakEventListener.addWeakEventListener(weakEventOptions);
|
||||
this._formattedTextChangedListenerId = weakEventListener.WeakEventListener.addWeakEventListener({
|
||||
target: this,
|
||||
source: value,
|
||||
eventName: observable.Observable.propertyChangeEvent,
|
||||
handler: this.onFormattedTextChanged,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user