mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2025-04-06 10:46:57 +00:00
feat: add removeEventListener , and allow register multiple listeners
Instead of remove old one when add new one.
This commit is contained in:
parent
06adaf3331
commit
1229542762
@ -628,36 +628,62 @@ Widget.prototype.addEventListeners = function(listeners) {
|
||||
};
|
||||
|
||||
/*
|
||||
Add an event listener
|
||||
Add an event listener. Listener could return a boolean indicating whether
|
||||
to further propagation or not.
|
||||
*/
|
||||
Widget.prototype.addEventListener = function(type,handler) {
|
||||
var self = this;
|
||||
if(typeof handler === "string") { // The handler is a method name on this widget
|
||||
this.eventListeners[type] = function(event) {
|
||||
return self[handler].call(self,event);
|
||||
};
|
||||
} else { // The handler is a function
|
||||
this.eventListeners[type] = function(event) {
|
||||
return handler.call(self,event);
|
||||
};
|
||||
var listenerWrapper;
|
||||
if(typeof handler === "string") {
|
||||
// keep the original function for comparing when remove.
|
||||
listenerWrapper = { original: handler, listener: function(event) { return self[handler].call(self,event); } };
|
||||
} else {
|
||||
listenerWrapper = { original: handler, listener: function(event) { return handler.call(self,event); } };
|
||||
}
|
||||
this.eventListeners[type] = this.eventListeners[type] || [];
|
||||
this.eventListeners[type].push(listenerWrapper);
|
||||
};
|
||||
|
||||
/*
|
||||
Dispatch an event to a widget. If the widget doesn't handle the event then it is also dispatched to the parent widget
|
||||
Remove an event listener
|
||||
*/
|
||||
Widget.prototype.removeEventListener = function(type,handler) {
|
||||
if(!this.eventListeners[type]) return;
|
||||
var self = this;
|
||||
$tw.utils.each(this.eventListeners[type].slice(), function(listener) {
|
||||
if(listener.original === handler) {
|
||||
var index = self.eventListeners[type].indexOf(listener);
|
||||
if(index !== -1) {
|
||||
self.eventListeners[type].splice(index,1);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
Dispatch an event to a widget. If the widget doesn't handle the event then it is also dispatched to the parent widget.
|
||||
|
||||
An event listener can return a boolean "propagate" value, indicating whether to stop propagation. By default it is false (stop propagation).
|
||||
*/
|
||||
Widget.prototype.dispatchEvent = function(event) {
|
||||
event.widget = event.widget || this;
|
||||
// Dispatch the event if this widget handles it
|
||||
var listener = this.eventListeners[event.type];
|
||||
if(listener) {
|
||||
// Don't propagate the event if the listener returned false
|
||||
if(!listener(event)) {
|
||||
var listeners = this.eventListeners[event.type];
|
||||
if(listeners) {
|
||||
// Don't propagate the event if any of the listeners returned false
|
||||
var self = this;
|
||||
var shouldPropagate = true;
|
||||
$tw.utils.each(listeners, function(listener) {
|
||||
if(!listener.listener(event)) {
|
||||
shouldPropagate = false;
|
||||
}
|
||||
});
|
||||
if (!shouldPropagate) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Dispatch the event to the parent widget
|
||||
if(this.parentWidget) {
|
||||
if (this.parentWidget) {
|
||||
return this.parentWidget.dispatchEvent(event);
|
||||
}
|
||||
return true;
|
||||
|
198
editions/test/tiddlers/tests/test-widget-event.js
Normal file
198
editions/test/tiddlers/tests/test-widget-event.js
Normal file
@ -0,0 +1,198 @@
|
||||
/*\
|
||||
title: test-widget-event.js
|
||||
type: application/javascript
|
||||
tags: [[$:/tags/test-spec]]
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
describe("Widget Event Listeners", function() {
|
||||
var widget = require("$:/core/modules/widgets/widget.js");
|
||||
|
||||
function createWidgetNode(parseTreeNode,wiki,parentWidget) {
|
||||
return new widget.widget(parseTreeNode,{
|
||||
wiki: wiki,
|
||||
document: $tw.fakeDocument,
|
||||
parentWidget: parentWidget
|
||||
});
|
||||
}
|
||||
|
||||
it("should call all added event listeners on dispatchEvent", function() {
|
||||
var calls = [];
|
||||
var wiki = new $tw.Wiki();
|
||||
var widget = createWidgetNode({type:"widget", text:"text"}, wiki);
|
||||
|
||||
// Add a function listener.
|
||||
widget.addEventListener("testEvent", function(e) {
|
||||
calls.push("funcListener");
|
||||
return true;
|
||||
});
|
||||
// Setup a method on widget for string listener.
|
||||
widget.testHandler = function(e) {
|
||||
calls.push("methodListener");
|
||||
return true;
|
||||
};
|
||||
widget.addEventListener("testEvent", "testHandler");
|
||||
|
||||
var event = {type:"testEvent"};
|
||||
var result = widget.dispatchEvent(event);
|
||||
expect(result).toBe(true);
|
||||
expect(calls.length).toBe(2);
|
||||
expect(calls).toContain("funcListener");
|
||||
expect(calls).toContain("methodListener");
|
||||
});
|
||||
|
||||
it("should remove an event listener correctly", function() {
|
||||
var calls = [];
|
||||
var wiki = new $tw.Wiki();
|
||||
var widget = createWidgetNode({type:"widget", text:"text"}, wiki);
|
||||
|
||||
function listener(e) {
|
||||
calls.push("listener");
|
||||
return true;
|
||||
}
|
||||
// Add listener twice: once as function and then add another distinct listener.
|
||||
widget.addEventListener("removeTest", listener);
|
||||
widget.addEventListener("removeTest", function(e) {
|
||||
calls.push("secondListener");
|
||||
return true;
|
||||
});
|
||||
// Remove the function listener.
|
||||
widget.removeEventListener("removeTest", listener);
|
||||
|
||||
var event = {type:"removeTest"};
|
||||
var result = widget.dispatchEvent(event);
|
||||
expect(result).toBe(true);
|
||||
expect(calls.length).toBe(1);
|
||||
expect(calls).toContain("secondListener");
|
||||
expect(calls).not.toContain("listener");
|
||||
});
|
||||
|
||||
it("stop further propagation by returns false won't block other listeners on the same level.", function() {
|
||||
var calls = [];
|
||||
var wiki = new $tw.Wiki();
|
||||
var widget = createWidgetNode({type:"widget", text:"text"}, wiki);
|
||||
|
||||
widget.addEventListener("stopEvent", function(e) {
|
||||
calls.push("first");
|
||||
// stops further propagation, but still dispatch event for second listener.
|
||||
return false;
|
||||
});
|
||||
widget.addEventListener("stopEvent", function(e) {
|
||||
calls.push("second");
|
||||
return true;
|
||||
});
|
||||
var event = {type:"stopEvent"};
|
||||
var result = widget.dispatchEvent(event);
|
||||
expect(result).toBe(false);
|
||||
expect(calls.length).toBe(2);
|
||||
expect(calls).toContain("first");
|
||||
expect(calls).toContain("second");
|
||||
});
|
||||
|
||||
it("should dispatch event to parent widget if not handled on child", function() {
|
||||
var parentCalls = [];
|
||||
var wiki = new $tw.Wiki();
|
||||
var parentWidget = createWidgetNode({type:"widget", text:"text"}, wiki);
|
||||
parentWidget.addEventListener("parentEvent", function(e) {
|
||||
parentCalls.push("parentListener");
|
||||
return true;
|
||||
});
|
||||
// Create a child with parentWidget assigned.
|
||||
var childWidget = createWidgetNode({type:"widget", text:"text"}, wiki, parentWidget);
|
||||
// No listener on child; so dispatch should bubble up.
|
||||
var event = {type:"parentEvent"};
|
||||
var result = childWidget.dispatchEvent(event);
|
||||
expect(result).toBe(true);
|
||||
expect(parentCalls.length).toBe(1);
|
||||
expect(parentCalls).toContain("parentListener");
|
||||
});
|
||||
|
||||
it("should not dispatch event to parent if child's listener stops propagation", function() {
|
||||
var parentCalls = [];
|
||||
var wiki = new $tw.Wiki();
|
||||
var parentWidget = createWidgetNode({type:"widget", text:"text"}, wiki);
|
||||
parentWidget.addEventListener("bubbleTest", function(e) {
|
||||
parentCalls.push("parentListener");
|
||||
return true;
|
||||
});
|
||||
var childWidget = createWidgetNode({type:"widget", text:"text"}, wiki, parentWidget);
|
||||
childWidget.addEventListener("bubbleTest", function(e) {
|
||||
return false; // Stop event propagation
|
||||
});
|
||||
var event = {type:"bubbleTest"};
|
||||
var result = childWidget.dispatchEvent(event);
|
||||
expect(result).toBe(false);
|
||||
expect(parentCalls.length).toBe(0);
|
||||
});
|
||||
|
||||
it("should call multiple listeners in proper order across child and parent", function() {
|
||||
var calls = [];
|
||||
var wiki = new $tw.Wiki();
|
||||
var parentWidget = createWidgetNode({type:"widget", text:"text"}, wiki);
|
||||
parentWidget.addEventListener("chainEvent", function(e) {
|
||||
calls.push("parentListener");
|
||||
return true;
|
||||
});
|
||||
var childWidget = createWidgetNode({type:"widget", text:"text"}, wiki, parentWidget);
|
||||
childWidget.addEventListener("chainEvent", function(e) {
|
||||
calls.push("childListener");
|
||||
return true;
|
||||
});
|
||||
var event = {type:"chainEvent"};
|
||||
var result = childWidget.dispatchEvent(event);
|
||||
expect(result).toBe(true);
|
||||
expect(calls.length).toBe(2);
|
||||
// First call from child widget and then parent's listener.
|
||||
expect(calls[0]).toBe("childListener");
|
||||
expect(calls[1]).toBe("parentListener");
|
||||
});
|
||||
|
||||
// Additional tests for multiple event types
|
||||
it("should handle events of different types separately", function() {
|
||||
var callsA = [];
|
||||
var callsB = [];
|
||||
var wiki = new $tw.Wiki();
|
||||
var widget = createWidgetNode({type:"widget", text:"text"}, wiki);
|
||||
widget.addEventListener("eventA", function(e) {
|
||||
callsA.push("A1");
|
||||
return true;
|
||||
});
|
||||
widget.addEventListener("eventB", function(e) {
|
||||
callsB.push("B1");
|
||||
return true;
|
||||
});
|
||||
widget.dispatchEvent({type:"eventA"});
|
||||
widget.dispatchEvent({type:"eventB"});
|
||||
expect(callsA).toContain("A1");
|
||||
expect(callsB).toContain("B1");
|
||||
});
|
||||
|
||||
// Test using $tw.utils.each in removeEventListener internally (behavior verified via dispatch)
|
||||
it("should remove listeners using $tw.utils.each without affecting other listeners", function() {
|
||||
var calls = [];
|
||||
var wiki = new $tw.Wiki();
|
||||
var widget = createWidgetNode({type:"widget", text:"text"}, wiki);
|
||||
function listener1(e) {
|
||||
calls.push("listener1");
|
||||
return true;
|
||||
}
|
||||
function listener2(e) {
|
||||
calls.push("listener2");
|
||||
return true;
|
||||
}
|
||||
widget.addEventListener("testRemove", listener1);
|
||||
widget.addEventListener("testRemove", listener2);
|
||||
widget.removeEventListener("testRemove", listener1);
|
||||
widget.dispatchEvent({type:"testRemove"});
|
||||
expect(calls.length).toBe(1);
|
||||
expect(calls).toContain("listener2");
|
||||
expect(calls).not.toContain("listener1");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})();
|
Loading…
x
Reference in New Issue
Block a user