@eclipse-scout/core
Version:
Eclipse Scout runtime
200 lines (180 loc) • 6.28 kB
text/typescript
/*
* Copyright (c) 2010, 2024 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
import {arrays, Event, EventHandler, EventListener, objects, scout} from '../index';
import $ from 'jquery';
export type EventSubTypePredicate = (type: Event, subType: string) => boolean;
export class EventSupport {
protected _eventListeners: EventListener[];
protected _subTypePredicates: EventSubTypePredicate[];
constructor() {
this._eventListeners = [];
this._subTypePredicates = objects.createMap();
}
protected _assertFunc(func: EventHandler) {
if (!func) {
throw new Error('Missing callback function');
}
}
/**
* Registers the given event handler for the event specified by the type param.
*
* @param type One or more event names separated by space.
* @param func Event handler executed when the event is triggered. An event object is passed to the function as first parameter.
* @param origFunc Used internally when func is registered with {@link one}. The property is set on the listener
* object so the event-handler can be de-registered by using the original function.
*/
on(type: string, func: EventHandler, origFunc?: EventHandler): EventListener {
this._assertFunc(func);
let listener = {
type: type,
func: func,
origFunc: origFunc
};
this.addListener(listener);
return listener;
}
/**
* Registers the given event handler for the event specified by the type param.
* The function will only be called once. After that it is automatically de-registered using {@link off}.
*
* @param type One or more event names separated by space.
* @param func Event handler executed when the event is triggered. An event object is passed to the function as first parameter
*/
one(type: string, func: EventHandler): EventListener {
this._assertFunc(func);
let offFunc = event => {
this.off(type, offFunc);
func(event);
};
return this.on(type, offFunc, func);
}
/**
* De-registers the given event handler for the event specified by the type param.
*
* @param type One or more event names separated by space.<br/>
* Important: the string must be equal to the one used for {@link on} or {@link one}. This also applies if a string containing multiple types separated by space was used.
* @param func The exact same event handler that was used for registration using {@link on} or {@link one}.
* If no handler is specified, all handlers are de-registered for the given type.
*/
off(type: string, func?: EventHandler) {
if (!type && !func) {
return;
}
for (let i = this._eventListeners.length - 1; i >= 0; i--) {
let listener = this._eventListeners[i];
let funcMatches = (func === listener.func || func === listener.origFunc);
let typeMatches = (type === listener.type);
let remove = false;
if (func && type) {
remove = (funcMatches && typeMatches);
} else if (func) {
remove = funcMatches;
} else { // always type. all other cases have been checked above
remove = typeMatches;
}
if (remove) {
this._eventListeners.splice(i, 1);
}
}
}
/**
* Adds an event handler using {@link one} and returns a promise.
* The promise is resolved as soon as the event is triggered.
*/
when(type: string): JQuery.Promise<Event> {
let deferred = $.Deferred();
this.one(type, deferred.resolve.bind(deferred));
return deferred.promise();
}
addListener(listener: EventListener) {
this._eventListeners.push(listener);
}
removeListener(listener: EventListener) {
arrays.remove(this._eventListeners, listener);
}
count(type?: string, func?: EventHandler): number {
let count = 0;
this._eventListeners.forEach(listener => {
if (type && type !== listener.type) {
return;
}
if (func && func !== listener.func) {
return;
}
count++;
});
return count;
}
/**
* @returns the event types of the registered listeners
*/
types(): string[] {
let types = new Set<string>();
for (const listener of this._eventListeners) {
if (listener.type) {
types.add(listener.type);
}
}
return Array.from(types);
}
trigger(type: string, event?: Event) {
event = event || {} as Event;
event.type = type;
// Create copy because firing a trigger might modify the list of listeners
let listeners = this._eventListeners.slice();
// Use traditional "for" loop to reduce size of stack trace
for (let i = 0; i < listeners.length; i++) {
let listener = listeners[i];
if (!listener.type || this._typeMatches(event, listener.type)) {
listener.func(event);
}
}
}
protected _typeMatches(event: Event, listenerType: string): boolean {
let eventType = event.type;
let types = listenerType.split(' ');
// support for multi type definition 'type1 type2 [...]'
for (let i = 0; i < types.length; i++) {
if (eventType === types[i]) {
return true;
}
if (this._subTypeMatches(event, types[i])) {
return true;
}
}
return false;
}
protected _subTypeMatches(event: Event, listenerType: string): boolean {
if (listenerType.indexOf(':') < 0) {
return false;
}
let parts = listenerType.split(':');
let type = parts[0];
let subType = parts[1];
if (type !== event.type) {
return false;
}
let predicate = this._subTypePredicates[type];
if (!predicate) {
return;
}
return predicate(event, subType);
}
/**
*
* @param type the type which could contain a subtype
* @param predicate the predicate which will be tested when an event with the given type is triggered.
*/
registerSubTypePredicate(type: string, predicate: EventSubTypePredicate) {
scout.assertParameter('type', type);
scout.assertParameter('predicate', predicate);
this._subTypePredicates[type] = predicate;
}
}