mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-17 04:41:36 +08:00
WeakEvents with map
This commit is contained in:
@ -225,6 +225,7 @@ export function forceGC() {
|
|||||||
if (platform.device.os === platform.platformNames.ios) {
|
if (platform.device.os === platform.platformNames.ios) {
|
||||||
// Could cause GC on the next call.
|
// Could cause GC on the next call.
|
||||||
new ArrayBuffer(4 * 1024 * 1024);
|
new ArrayBuffer(4 * 1024 * 1024);
|
||||||
|
TKUnit.wait(ASYNC);
|
||||||
}
|
}
|
||||||
utils.GC();
|
utils.GC();
|
||||||
}
|
}
|
@ -4,89 +4,64 @@ import observable = require("data/observable");
|
|||||||
import weakEvents = require("ui/core/weak-event-listener");
|
import weakEvents = require("ui/core/weak-event-listener");
|
||||||
import helper = require("./ui/helper");
|
import helper = require("./ui/helper");
|
||||||
|
|
||||||
|
class Target {
|
||||||
|
public counter: number = 0;
|
||||||
|
public onEvent(data: observable.EventData) {
|
||||||
|
this.counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export function test_addWeakEventListener_throwsWhenCalledwitnInvalid_source() {
|
export function test_addWeakEventListener_throwsWhenCalledwitnInvalid_source() {
|
||||||
TKUnit.assertThrows(() => {
|
TKUnit.assertThrows(() => {
|
||||||
weakEvents.WeakEventListener.addWeakEventListener({
|
weakEvents.addWeakEventListener(undefined, "eventName", emptyHandler, {});
|
||||||
source: undefined,
|
|
||||||
target: {},
|
|
||||||
handler: emptyHandler,
|
|
||||||
eventName: observable.Observable.propertyChangeEvent
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function test_addWeakEventListener_throwsWhenCalledwitnInvalid_target() {
|
export function test_addWeakEventListener_throwsWhenCalledwitnInvalid_target() {
|
||||||
TKUnit.assertThrows(() => {
|
TKUnit.assertThrows(() => {
|
||||||
weakEvents.WeakEventListener.addWeakEventListener({
|
weakEvents.addWeakEventListener(new observable.Observable(), "eventName", emptyHandler, undefined);
|
||||||
source: new observable.Observable(),
|
|
||||||
target: undefined,
|
|
||||||
handler: emptyHandler,
|
|
||||||
eventName: observable.Observable.propertyChangeEvent
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function test_addWeakEventListener_throwsWhenCalledwitnInvalid_handler() {
|
export function test_addWeakEventListener_throwsWhenCalledwitnInvalid_handler() {
|
||||||
TKUnit.assertThrows(() => {
|
TKUnit.assertThrows(() => {
|
||||||
weakEvents.WeakEventListener.addWeakEventListener({
|
weakEvents.addWeakEventListener(new observable.Observable(), "eventName", undefined, {});
|
||||||
source: new observable.Observable(),
|
|
||||||
target: {},
|
|
||||||
handler: undefined,
|
|
||||||
eventName: observable.Observable.propertyChangeEvent
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function test_addWeakEventListener_throwsWhenCalledwitnInvalid_name() {
|
export function test_addWeakEventListener_throwsWhenCalledwitnInvalid_name() {
|
||||||
TKUnit.assertThrows(() => {
|
TKUnit.assertThrows(() => {
|
||||||
weakEvents.WeakEventListener.addWeakEventListener({
|
weakEvents.addWeakEventListener(new observable.Observable(), undefined, emptyHandler, {});
|
||||||
source: new observable.Observable(),
|
|
||||||
target: {},
|
|
||||||
handler: emptyHandler,
|
|
||||||
eventName: undefined
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function test_addWeakEventListener_listensForEvent() {
|
export function test_addWeakEventListener_listensForEvent() {
|
||||||
var source = new observable.Observable();
|
var source = new observable.Observable();
|
||||||
var target = new Object;
|
var target = new Target();
|
||||||
var callbackCalled = false;
|
|
||||||
var handler = function (args: observable.EventData) {
|
|
||||||
callbackCalled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
weakEvents.WeakEventListener.addWeakEventListener({
|
weakEvents.addWeakEventListener(
|
||||||
source: source,
|
source,
|
||||||
target: target,
|
observable.Observable.propertyChangeEvent,
|
||||||
handler: handler,
|
target.onEvent,
|
||||||
eventName: observable.Observable.propertyChangeEvent
|
target);
|
||||||
})
|
|
||||||
|
helper.forceGC();
|
||||||
|
|
||||||
source.set("testProp", "some value");
|
source.set("testProp", "some value");
|
||||||
|
|
||||||
TKUnit.assert(callbackCalled, "Handler not called.");
|
TKUnit.assertEqual(target.counter, 1, "Handler not called.");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function test_removeWeakEventListener_StopsListeningForEvet() {
|
export function test_removeWeakEventListener_StopsListeningForEvet() {
|
||||||
var source = new observable.Observable();
|
var source = new observable.Observable();
|
||||||
var target = new Object;
|
var target = new Target();
|
||||||
var callbackCalled = false;
|
|
||||||
var handler = function (args: observable.EventData) {
|
|
||||||
callbackCalled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var listenerID = weakEvents.WeakEventListener.addWeakEventListener({
|
weakEvents.addWeakEventListener(source, observable.Observable.propertyChangeEvent, target.onEvent, target);
|
||||||
source: source,
|
weakEvents.removeWeakEventListener(source, observable.Observable.propertyChangeEvent, target.onEvent, target)
|
||||||
target: target,
|
|
||||||
handler: handler,
|
|
||||||
eventName: observable.Observable.propertyChangeEvent
|
|
||||||
})
|
|
||||||
|
|
||||||
weakEvents.WeakEventListener.removeWeakEventListener(listenerID);
|
|
||||||
|
|
||||||
source.set("testProp", "some value");
|
source.set("testProp", "some value");
|
||||||
TKUnit.assert(!callbackCalled, "Handler should not be called.");
|
TKUnit.assertEqual(target.counter, 0, "Handler should not be called.");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function test_handlerIsCalled_WithTargetAsThis() {
|
export function test_handlerIsCalled_WithTargetAsThis() {
|
||||||
@ -98,12 +73,7 @@ export function test_handlerIsCalled_WithTargetAsThis() {
|
|||||||
callbackCalled = true;
|
callbackCalled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
weakEvents.WeakEventListener.addWeakEventListener({
|
weakEvents.addWeakEventListener(source, observable.Observable.propertyChangeEvent, handler, target);
|
||||||
source: source,
|
|
||||||
target: target,
|
|
||||||
handler: handler,
|
|
||||||
eventName: observable.Observable.propertyChangeEvent
|
|
||||||
})
|
|
||||||
|
|
||||||
source.set("testProp", "some value");
|
source.set("testProp", "some value");
|
||||||
TKUnit.assert(callbackCalled, "Handler not called.");
|
TKUnit.assert(callbackCalled, "Handler not called.");
|
||||||
@ -111,19 +81,10 @@ export function test_handlerIsCalled_WithTargetAsThis() {
|
|||||||
|
|
||||||
export function test_listnerDoesNotRetainTarget() {
|
export function test_listnerDoesNotRetainTarget() {
|
||||||
var source = new observable.Observable();
|
var source = new observable.Observable();
|
||||||
var target = new Object;
|
var target = new Target();
|
||||||
var callbackCalled = false;
|
var callbackCalled = false;
|
||||||
var handler = function (args: observable.EventData) {
|
|
||||||
TKUnit.assertEqual(this, target, "this should be the target");
|
|
||||||
callbackCalled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
weakEvents.WeakEventListener.addWeakEventListener({
|
weakEvents.addWeakEventListener(source, observable.Observable.propertyChangeEvent, target.onEvent, target);
|
||||||
source: source,
|
|
||||||
target: target,
|
|
||||||
handler: handler,
|
|
||||||
eventName: observable.Observable.propertyChangeEvent
|
|
||||||
})
|
|
||||||
|
|
||||||
var targetRef = new WeakRef(target);
|
var targetRef = new WeakRef(target);
|
||||||
target = undefined;
|
target = undefined;
|
||||||
@ -134,19 +95,10 @@ export function test_listnerDoesNotRetainTarget() {
|
|||||||
|
|
||||||
export function test_listnerDoesNotRetainSource() {
|
export function test_listnerDoesNotRetainSource() {
|
||||||
var source = new observable.Observable();
|
var source = new observable.Observable();
|
||||||
var target = new Object();
|
var target = new Target();
|
||||||
var callbackCalled = false;
|
var callbackCalled = false;
|
||||||
var handler = function (args: observable.EventData) {
|
|
||||||
TKUnit.assertEqual(this, target, "this should be the target");
|
|
||||||
callbackCalled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
weakEvents.WeakEventListener.addWeakEventListener({
|
weakEvents.addWeakEventListener(source, observable.Observable.propertyChangeEvent, target.onEvent, target);
|
||||||
source: source,
|
|
||||||
target: target,
|
|
||||||
handler: handler,
|
|
||||||
eventName: observable.Observable.propertyChangeEvent
|
|
||||||
})
|
|
||||||
|
|
||||||
var sourceRef = new WeakRef(source);
|
var sourceRef = new WeakRef(source);
|
||||||
source = undefined;
|
source = undefined;
|
||||||
@ -155,50 +107,74 @@ export function test_listnerDoesNotRetainSource() {
|
|||||||
TKUnit.assert(!sourceRef.get(), "Source should be released after GC");
|
TKUnit.assert(!sourceRef.get(), "Source should be released after GC");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function test_listnerIsCleared_WhenTargetIsDead() {
|
//export function test_listnerIsCleared_WhenTargetIsDead() {
|
||||||
var source = new observable.Observable();
|
// var source = new observable.Observable();
|
||||||
|
|
||||||
var listenerID = addListenerWithSource(source);
|
// var listenerID = addListenerWithSource(source);
|
||||||
helper.forceGC();
|
// helper.forceGC();
|
||||||
|
|
||||||
for (var i = 0; i < weakEvents.WeakEventListener.cleanDeadReferencesCountTrigger; i++) {
|
// for (var i = 0; i < weakEvents.cleanDeadReferencesCountTrigger; i++) {
|
||||||
addListenerWithSource(source);
|
// addListenerWithSource(source);
|
||||||
}
|
// }
|
||||||
|
|
||||||
TKUnit.assert(types.isUndefined(weakEvents.WeakEventListener._weakEventListeners[listenerID]), "The first listener should be dead by now");
|
// TKUnit.assert(types.isUndefined(weakEvents._weakEventListeners[listenerID]), "The first listener should be dead by now");
|
||||||
}
|
//}
|
||||||
|
|
||||||
export function test_listnerIsCleared_WhenSourceIsDead() {
|
//export function test_listnerIsCleared_WhenSourceIsDead() {
|
||||||
var target = {};
|
// var target = {};
|
||||||
|
|
||||||
var listenerID = addListenerWithTarget(target);
|
// var listenerID = addListenerWithTarget(target);
|
||||||
helper.forceGC();
|
// helper.forceGC();
|
||||||
|
|
||||||
for (var i = 0; i < weakEvents.WeakEventListener.cleanDeadReferencesCountTrigger; i++) {
|
// for (var i = 0; i < weakEvents.cleanDeadReferencesCountTrigger; i++) {
|
||||||
addListenerWithTarget(target);
|
// addListenerWithTarget(target);
|
||||||
}
|
// }
|
||||||
|
|
||||||
TKUnit.assert(types.isUndefined(weakEvents.WeakEventListener._weakEventListeners[listenerID]), "The first listener should be dead by now");
|
// TKUnit.assert(types.isUndefined(weakEvents._weakEventListeners[listenerID]), "The first listener should be dead by now");
|
||||||
}
|
//}
|
||||||
|
|
||||||
function addListenerWithSource(source: observable.Observable): number {
|
//function addListenerWithSource(source: observable.Observable): number {
|
||||||
return weakEvents.WeakEventListener.addWeakEventListener({
|
// return weakEvents.addWeakEventListener({
|
||||||
source: source,
|
// source: source,
|
||||||
target: {},
|
// target: {},
|
||||||
handler: emptyHandler,
|
// handler: emptyHandler,
|
||||||
eventName: observable.Observable.propertyChangeEvent
|
// eventName: observable.Observable.propertyChangeEvent
|
||||||
})
|
// })
|
||||||
}
|
//}
|
||||||
|
|
||||||
function addListenerWithTarget(target: any): number {
|
//function addListenerWithTarget(target: any): number {
|
||||||
return weakEvents.WeakEventListener.addWeakEventListener({
|
// return weakEvents.addWeakEventListener({
|
||||||
source: new observable.Observable(),
|
// source: new observable.Observable(),
|
||||||
target: target,
|
// target: target,
|
||||||
handler: emptyHandler,
|
// handler: emptyHandler,
|
||||||
eventName: observable.Observable.propertyChangeEvent
|
// eventName: observable.Observable.propertyChangeEvent
|
||||||
})
|
// })
|
||||||
}
|
//}
|
||||||
|
|
||||||
function emptyHandler(data: observable.EventData) {
|
function emptyHandler(data: observable.EventData) {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export function testWeakMap(): void {
|
||||||
|
var source = new observable.Observable();
|
||||||
|
var target = new Target();
|
||||||
|
var targetRef = new WeakRef(target);
|
||||||
|
var weakMap = new WeakMap<observable.Observable, Target>();
|
||||||
|
|
||||||
|
weakMap.set(source, target);
|
||||||
|
TKUnit.assertEqual(weakMap.get(source), target, "target");
|
||||||
|
|
||||||
|
target = undefined;
|
||||||
|
source = undefined;
|
||||||
|
|
||||||
|
helper.forceGC();
|
||||||
|
TKUnit.wait(1);
|
||||||
|
|
||||||
|
TKUnit.waitUntilReady(function () {
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
|
||||||
|
TKUnit.assert(!targetRef.get(), "Target should be dead");
|
||||||
|
}
|
||||||
|
@ -4,7 +4,7 @@ import definition = require("ui/button");
|
|||||||
import proxy = require("ui/core/proxy");
|
import proxy = require("ui/core/proxy");
|
||||||
import formattedString = require("text/formatted-string");
|
import formattedString = require("text/formatted-string");
|
||||||
import observable = require("data/observable");
|
import observable = require("data/observable");
|
||||||
import weakEventListener = require("ui/core/weak-event-listener");
|
import weakEvents = require("ui/core/weak-event-listener");
|
||||||
|
|
||||||
var textProperty = new dependencyObservable.Property(
|
var textProperty = new dependencyObservable.Property(
|
||||||
"text",
|
"text",
|
||||||
@ -37,8 +37,6 @@ export class Button extends view.View implements definition.Button {
|
|||||||
public static textProperty = textProperty;
|
public static textProperty = textProperty;
|
||||||
public static formattedTextProperty = formattedTextProperty;
|
public static formattedTextProperty = formattedTextProperty;
|
||||||
|
|
||||||
private _formattedTextWeakListenerId: number;
|
|
||||||
|
|
||||||
public _onBindingContextChanged(oldValue: any, newValue: any) {
|
public _onBindingContextChanged(oldValue: any, newValue: any) {
|
||||||
super._onBindingContextChanged(oldValue, newValue);
|
super._onBindingContextChanged(oldValue, newValue);
|
||||||
if (this.formattedText) {
|
if (this.formattedText) {
|
||||||
@ -61,16 +59,11 @@ export class Button extends view.View implements definition.Button {
|
|||||||
set formattedText(value: formattedString.FormattedString) {
|
set formattedText(value: formattedString.FormattedString) {
|
||||||
if (this.formattedText !== value) {
|
if (this.formattedText !== value) {
|
||||||
if (this.formattedText) {
|
if (this.formattedText) {
|
||||||
weakEventListener.WeakEventListener.removeWeakEventListener(this._formattedTextWeakListenerId);
|
weakEvents.removeWeakEventListener(this.formattedText, observable.Observable.propertyChangeEvent, this.onFormattedTextChanged, this);
|
||||||
}
|
}
|
||||||
this._setValue(Button.formattedTextProperty, value);
|
this._setValue(Button.formattedTextProperty, value);
|
||||||
if (value) {
|
if (value) {
|
||||||
this._formattedTextWeakListenerId = weakEventListener.WeakEventListener.addWeakEventListener({
|
weakEvents.addWeakEventListener(value, observable.Observable.propertyChangeEvent, this.onFormattedTextChanged, this);
|
||||||
target: this,
|
|
||||||
source: value,
|
|
||||||
eventName: observable.Observable.propertyChangeEvent,
|
|
||||||
handler: this.onFormattedTextChanged
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,12 +162,11 @@ export class Binding {
|
|||||||
if (this.sourceOptions) {
|
if (this.sourceOptions) {
|
||||||
var sourceOptionsInstance = this.sourceOptions.instance.get();
|
var sourceOptionsInstance = this.sourceOptions.instance.get();
|
||||||
if (sourceOptionsInstance instanceof observable.Observable) {
|
if (sourceOptionsInstance instanceof observable.Observable) {
|
||||||
this.weakEventId = weakEvents.WeakEventListener.addWeakEventListener({
|
weakEvents.addWeakEventListener(
|
||||||
target: this,
|
sourceOptionsInstance,
|
||||||
source: this.sourceOptions.instance.get(),
|
observable.Observable.propertyChangeEvent,
|
||||||
eventName: observable.Observable.propertyChangeEvent,
|
this.onSourcePropertyChanged,
|
||||||
handler: this.onSourcePropertyChanged,
|
this);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -177,8 +176,15 @@ export class Binding {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
weakEvents.WeakEventListener.removeWeakEventListener(this.weakEventId);
|
if (this.sourceOptions) {
|
||||||
delete this.weakEventId;
|
var sourceOptionsInstance = this.sourceOptions.instance.get();
|
||||||
|
if (sourceOptionsInstance) {
|
||||||
|
weakEvents.removeWeakEventListener(sourceOptionsInstance,
|
||||||
|
observable.Observable.propertyChangeEvent,
|
||||||
|
this.onSourcePropertyChanged,
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.source) {
|
if (this.source) {
|
||||||
this.source.clear();
|
this.source.clear();
|
||||||
|
60
ui/core/weak-event-listener.d.ts
vendored
60
ui/core/weak-event-listener.d.ts
vendored
@ -2,56 +2,20 @@ declare module "ui/core/weak-event-listener" {
|
|||||||
import observable = require("data/observable");
|
import observable = require("data/observable");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface that defines all options needed for creating weak event listener.
|
* Creates and initialize WeakEventListener.
|
||||||
|
* @param source Observable class which emits the event.
|
||||||
|
* @param eventName The event name.
|
||||||
|
* @param handler The function which should be called when event occurs.
|
||||||
|
* @param target Subscriber (target) of the event listener. It will be used as a thisArg in the handler function.
|
||||||
*/
|
*/
|
||||||
export interface WeakEventListenerOptions {
|
export function addWeakEventListener(source: observable.Observable, eventName: string, handler: (eventData: observable.EventData) => void, target: any) : void;
|
||||||
/**
|
|
||||||
* Subscriber (target) of the event listener. It will be used as a thisArg in the handler function.
|
|
||||||
*/
|
|
||||||
target: any;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observable class which emits the event.
|
* Removes and clears all resources from WeakEventListener.
|
||||||
|
* @param source Observable class which emits the event.
|
||||||
|
* @param eventName The event name.
|
||||||
|
* @param handler The function which should be called when event occurs.
|
||||||
|
* @param target Subscriber (target) of the event listener. It will be used as a thisArg in the handler function.
|
||||||
*/
|
*/
|
||||||
source: observable.Observable;
|
export function removeWeakEventListener(source: observable.Observable, eventName: string, handler: (eventData: observable.EventData) => void, target: any): void;
|
||||||
|
|
||||||
/**
|
|
||||||
* Name of the event.
|
|
||||||
*/
|
|
||||||
eventName: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The function which should be called when event occurs.
|
|
||||||
*/
|
|
||||||
handler: (eventData: observable.EventData) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a class that utilize work with weak event listeners.
|
|
||||||
*/
|
|
||||||
export class WeakEventListener {
|
|
||||||
/**
|
|
||||||
* Creates and initialize WeakEventListener (if all required options are set).
|
|
||||||
* @param options An instance of WeakEventListenerOptions needed to create WeakEventListener instance.
|
|
||||||
* Returns The id of the WeakEventListener object to be used in removeWeakEventListener method.
|
|
||||||
*/
|
|
||||||
static addWeakEventListener(options: WeakEventListenerOptions): number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes and clears all resources from WeakEventListener with given id.
|
|
||||||
* @param The id of the WeakEventListener object.
|
|
||||||
*/
|
|
||||||
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 _weakEventListeners: Object;
|
|
||||||
//@endprivate
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -2,120 +2,142 @@
|
|||||||
import definition = require("ui/core/weak-event-listener");
|
import definition = require("ui/core/weak-event-listener");
|
||||||
import types = require("utils/types");
|
import types = require("utils/types");
|
||||||
|
|
||||||
var CLEAN_TRIGGER_DEFAULT = 1000;
|
var handlersForEventName = new Map<string,(eventData: observable.EventData) => void>();
|
||||||
export class WeakEventListener implements definition.WeakEventListener {
|
var sourcesMap = new WeakMap<observable.Observable, Map<string, Array<TargetHandlerPair>>>();
|
||||||
private id: number;
|
|
||||||
private targetRef: WeakRef<any>;
|
|
||||||
private senderRef: WeakRef<observable.Observable>;
|
|
||||||
private eventName: string;
|
|
||||||
private handler: (eventData: observable.EventData) => void;
|
|
||||||
|
|
||||||
static _idCounter: number = 0;
|
class TargetHandlerPair {
|
||||||
static _weakEventListeners = {};
|
tagetRef: WeakRef<Object>;
|
||||||
static _cleanDeadReferencesCountTrigger = CLEAN_TRIGGER_DEFAULT;
|
handler: (eventData: observable.EventData) => void;
|
||||||
static get cleanDeadReferencesCountTrigger(): number {
|
|
||||||
return WeakEventListener._cleanDeadReferencesCountTrigger;
|
constructor(target: Object, handler: (eventData: observable.EventData) => void) {
|
||||||
|
this.tagetRef = new WeakRef(target);
|
||||||
|
this.handler = handler;
|
||||||
}
|
}
|
||||||
static set cleanDeadReferencesCountTrigger(value: number) {
|
}
|
||||||
WeakEventListener._cleanDeadReferencesCountTrigger = value;
|
|
||||||
|
function getHandlerForEventName(eventName: string): (eventData: observable.EventData) => void {
|
||||||
|
var handler = handlersForEventName.get(eventName);
|
||||||
|
if (!handler) {
|
||||||
|
var handler = function (eventData: observable.EventData) {
|
||||||
|
var source = eventData.object;
|
||||||
|
var sourceEventMap = sourcesMap.get(source);
|
||||||
|
if (!sourceEventMap) {
|
||||||
|
// There is no event map for this source - it is safe to detach the listener;
|
||||||
|
source.removeEventListener(eventName, handlersForEventName.get(eventName));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private handlerCallback(eventData) {
|
var targetHandlerPairList = sourceEventMap.get(eventName);
|
||||||
var target = this.targetRef.get();
|
if (!targetHandlerPairList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var deadPairsIndexes = [];
|
||||||
|
for (var i = 0; i < targetHandlerPairList.length; i++) {
|
||||||
|
var pair = targetHandlerPairList[i];
|
||||||
|
|
||||||
|
var target = pair.tagetRef.get();
|
||||||
if (target) {
|
if (target) {
|
||||||
this.handler.call(target, eventData);
|
pair.handler.call(target, eventData);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// The target is dead - we can unsubscribe;
|
deadPairsIndexes.push(i);
|
||||||
WeakEventListener.removeWeakEventListener(this.id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private init(options: definition.WeakEventListenerOptions, listenerId: number) {
|
if (deadPairsIndexes.length === targetHandlerPairList.length) {
|
||||||
this.id = listenerId;
|
// There are no alive targets for this event - unsubscribe
|
||||||
this.targetRef = new WeakRef(options.target);
|
source.removeEventListener(eventName, handlersForEventName.get(eventName));
|
||||||
this.senderRef = new WeakRef(options.source);
|
sourceEventMap.delete(eventName);
|
||||||
this.eventName = options.eventName;
|
}
|
||||||
this.handler = options.handler;
|
else {
|
||||||
|
for (var j = deadPairsIndexes.length - 1; j >= 0; j--) {
|
||||||
var sourceInstance = this.senderRef.get();
|
targetHandlerPairList.splice(deadPairsIndexes[j], 1);
|
||||||
if (sourceInstance) {
|
|
||||||
sourceInstance.addEventListener(this.eventName, this.handlerCallback, this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
private clear() {
|
handlersForEventName.set(eventName, handler);
|
||||||
var sender = this.senderRef.get();
|
|
||||||
if (sender) {
|
|
||||||
sender.removeEventListener(this.eventName, this.handlerCallback, this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.targetRef.clear();
|
return handler;
|
||||||
this.targetRef = undefined;
|
}
|
||||||
this.senderRef.clear();
|
|
||||||
this.senderRef = undefined;
|
|
||||||
|
|
||||||
this.eventName = undefined;
|
function validateArgs(source: observable.Observable, eventName: string, handler: (eventData: observable.EventData) => void, target: any) {
|
||||||
this.handler = undefined;
|
if (types.isNullOrUndefined(source)) {
|
||||||
|
throw new Error("source is null or undefined");
|
||||||
}
|
}
|
||||||
|
|
||||||
static addWeakEventListener(options: definition.WeakEventListenerOptions) {
|
if (types.isNullOrUndefined(target)) {
|
||||||
if (types.isNullOrUndefined(options.target)) {
|
throw new Error("target is null or undefined");
|
||||||
throw new Error("targetWeakRef is null or undefined");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (types.isNullOrUndefined(options.source)) {
|
if (!types.isString(eventName)) {
|
||||||
throw new Error("sourceWeakRef is null or undefined");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!types.isString(options.eventName)) {
|
|
||||||
throw new Error("eventName is not a string");
|
throw new Error("eventName is not a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!types.isFunction(options.handler)) {
|
if (!types.isFunction(handler)) {
|
||||||
throw new Error("handler is not a function");
|
throw new Error("handler is not a function");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var listenerId = WeakEventListener._idCounter++;
|
export function addWeakEventListener(source: observable.Observable, eventName: string, handler: (eventData: observable.EventData) => void, target: any) {
|
||||||
var weakEventListener = new WeakEventListener();
|
validateArgs(source, eventName, handler, target);
|
||||||
weakEventListener.init(options, listenerId);
|
|
||||||
|
|
||||||
WeakEventListener._weakEventListeners[listenerId] = new WeakRef(weakEventListener);
|
var sourceEventMap = sourcesMap.get(source);
|
||||||
|
if (!sourceEventMap) {
|
||||||
if (WeakEventListener._cleanDeadReferencesCountTrigger &&
|
sourceEventMap = new Map<string, Array<TargetHandlerPair>>();
|
||||||
(listenerId % WeakEventListener._cleanDeadReferencesCountTrigger) === 0) {
|
sourcesMap.set(source, sourceEventMap);
|
||||||
WeakEventListener._cleanDeadReferences();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return listenerId;
|
var pairList = sourceEventMap.get(eventName);
|
||||||
|
if (!pairList) {
|
||||||
|
pairList = new Array<TargetHandlerPair>();
|
||||||
|
sourceEventMap.set(eventName, pairList);
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeWeakEventListener(listenerId: number) {
|
pairList.push(new TargetHandlerPair(target, handler));
|
||||||
var listenerRef = <WeakRef<WeakEventListener>>WeakEventListener._weakEventListeners[listenerId];
|
|
||||||
|
|
||||||
if (listenerRef) {
|
source.addEventListener(eventName, getHandlerForEventName(eventName));
|
||||||
var listener = <WeakEventListener>listenerRef.get();
|
}
|
||||||
if (listener) {
|
|
||||||
listener.clear();
|
export function removeWeakEventListener(source: observable.Observable, eventName: string, handler: (eventData: observable.EventData) => void, target: any) {
|
||||||
|
validateArgs(source, eventName, handler, target);
|
||||||
|
|
||||||
|
var handlerForEventWithName = handlersForEventName.get(eventName);
|
||||||
|
if (!handlerForEventWithName) {
|
||||||
|
// We have never created handler for event with this name;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sourceEventMap = sourcesMap.get(source);
|
||||||
|
if (!sourceEventMap) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetHandlerPairList = sourceEventMap.get(eventName);
|
||||||
|
if (!targetHandlerPairList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all pairs that match given target and handler or have a dead target
|
||||||
|
var targetHandlerPairsToRemove = [];
|
||||||
|
for (var i = 0; i < targetHandlerPairList.length; i++) {
|
||||||
|
var pair = targetHandlerPairList[i];
|
||||||
|
|
||||||
|
var registeredTarget = pair.tagetRef.get();
|
||||||
|
if (!registeredTarget || (registeredTarget === target && handler === pair.handler)) {
|
||||||
|
targetHandlerPairsToRemove.push(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete WeakEventListener._weakEventListeners[listenerId];
|
if (targetHandlerPairsToRemove.length === targetHandlerPairList.length) {
|
||||||
|
// There are no alive targets for this event - unsubscribe
|
||||||
|
source.removeEventListener(eventName, handlerForEventWithName);
|
||||||
|
sourceEventMap.delete(eventName);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
static _cleanDeadReferences() {
|
for (var j = targetHandlerPairsToRemove.length - 1; j >= 0; j--) {
|
||||||
var deadListeners = new Array<number>();
|
targetHandlerPairList.splice(targetHandlerPairsToRemove[j], 1);
|
||||||
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]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import dependencyObservable = require("ui/core/dependency-observable");
|
|||||||
import builder = require("ui/builder");
|
import builder = require("ui/builder");
|
||||||
import label = require("ui/label");
|
import label = require("ui/label");
|
||||||
import color = require("color");
|
import color = require("color");
|
||||||
import weakEventListener = require("ui/core/weak-event-listener");
|
import weakEvents = require("ui/core/weak-event-listener");
|
||||||
import types = require("utils/types");
|
import types = require("utils/types");
|
||||||
|
|
||||||
var ITEMS = "items";
|
var ITEMS = "items";
|
||||||
@ -132,17 +132,12 @@ export class ListView extends view.View implements definition.ListView {
|
|||||||
|
|
||||||
public _onItemsPropertyChanged(data: dependencyObservable.PropertyChangeData) {
|
public _onItemsPropertyChanged(data: dependencyObservable.PropertyChangeData) {
|
||||||
if (data.oldValue instanceof observable.Observable && types.isDefined(this._itemsChangedWeakListenerId)) {
|
if (data.oldValue instanceof observable.Observable && types.isDefined(this._itemsChangedWeakListenerId)) {
|
||||||
weakEventListener.WeakEventListener.removeWeakEventListener(this._itemsChangedWeakListenerId);
|
weakEvents.removeWeakEventListener(data.oldValue, observableArray.ObservableArray.changeEvent, this._onItemsChanged, this);
|
||||||
delete this._itemsChangedWeakListenerId;
|
delete this._itemsChangedWeakListenerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.newValue instanceof observable.Observable) {
|
if (data.newValue instanceof observable.Observable) {
|
||||||
this._itemsChangedWeakListenerId = weakEventListener.WeakEventListener.addWeakEventListener({
|
weakEvents.addWeakEventListener(data.newValue, observableArray.ObservableArray.changeEvent, this._onItemsChanged, this);
|
||||||
target: this,
|
|
||||||
source: data.newValue,
|
|
||||||
eventName: observableArray.ObservableArray.changeEvent,
|
|
||||||
handler: this._onItemsChanged,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.refresh();
|
this.refresh();
|
||||||
|
@ -4,7 +4,7 @@ import observable = require("data/observable");
|
|||||||
import dependencyObservable = require("ui/core/dependency-observable");
|
import dependencyObservable = require("ui/core/dependency-observable");
|
||||||
import proxy = require("ui/core/proxy");
|
import proxy = require("ui/core/proxy");
|
||||||
import formattedString = require("text/formatted-string");
|
import formattedString = require("text/formatted-string");
|
||||||
import weakEventListener = require("ui/core/weak-event-listener");
|
import weakEvents = require("ui/core/weak-event-listener");
|
||||||
import utils = require("utils/utils");
|
import utils = require("utils/utils");
|
||||||
import trace = require("trace");
|
import trace = require("trace");
|
||||||
|
|
||||||
@ -38,8 +38,6 @@ export class TextBase extends view.View implements definition.TextBase {
|
|||||||
public static textProperty = textProperty;
|
public static textProperty = textProperty;
|
||||||
public static formattedTextProperty = formattedTextProperty;
|
public static formattedTextProperty = formattedTextProperty;
|
||||||
|
|
||||||
private _formattedTextChangedListenerId: number;
|
|
||||||
|
|
||||||
constructor(options?: definition.Options) {
|
constructor(options?: definition.Options) {
|
||||||
super(options);
|
super(options);
|
||||||
}
|
}
|
||||||
@ -80,17 +78,11 @@ export class TextBase extends view.View implements definition.TextBase {
|
|||||||
set formattedText(value: formattedString.FormattedString) {
|
set formattedText(value: formattedString.FormattedString) {
|
||||||
if (this.formattedText !== value) {
|
if (this.formattedText !== value) {
|
||||||
if (this.formattedText) {
|
if (this.formattedText) {
|
||||||
weakEventListener.WeakEventListener.removeWeakEventListener(this._formattedTextChangedListenerId);
|
weakEvents.removeWeakEventListener(this.formattedText, observable.Observable.propertyChangeEvent, this.onFormattedTextChanged, this);
|
||||||
delete this._formattedTextChangedListenerId;
|
|
||||||
}
|
}
|
||||||
this._setValue(TextBase.formattedTextProperty, value);
|
this._setValue(TextBase.formattedTextProperty, value);
|
||||||
if (value) {
|
if (value) {
|
||||||
this._formattedTextChangedListenerId = weakEventListener.WeakEventListener.addWeakEventListener({
|
weakEvents.addWeakEventListener(value, observable.Observable.propertyChangeEvent, this.onFormattedTextChanged, this);
|
||||||
target: this,
|
|
||||||
source: value,
|
|
||||||
eventName: observable.Observable.propertyChangeEvent,
|
|
||||||
handler: this.onFormattedTextChanged,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user