@serenity-is/sleekgrid
Version:
A modern Data Grid / Spreadsheet component
203 lines (173 loc) • 6.1 kB
text/typescript
export interface IEventData {
readonly type?: string;
currentTarget?: EventTarget | null;
target?: EventTarget | null;
originalEvent?: any;
defaultPrevented?: boolean;
preventDefault?(): void;
stopPropagation?(): void;
stopImmediatePropagation?(): void;
isDefaultPrevented?(): boolean;
isImmediatePropagationStopped?(): boolean;
isPropagationStopped?(): boolean;
}
/***
* An event object for passing data to event handlers and letting them control propagation.
* <p>This is pretty much identical to how W3C and jQuery implement events.</p>
*/
export class EventData implements IEventData {
private _isPropagationStopped = false;
private _isImmediatePropagationStopped = false;
/***
* Stops event from propagating up the DOM tree.
* @method stopPropagation
*/
stopPropagation() {
this._isPropagationStopped = true;
}
/***
* Returns whether stopPropagation was called on this event object.
*/
isPropagationStopped(): boolean {
return this._isPropagationStopped;
}
/***
* Prevents the rest of the handlers from being executed.
*/
stopImmediatePropagation() {
this._isImmediatePropagationStopped = true;
}
/***
* Returns whether stopImmediatePropagation was called on this event object.\
*/
isImmediatePropagationStopped(): boolean {
return this._isImmediatePropagationStopped;
}
}
/***
* A simple publisher-subscriber implementation.
*/
export class EventEmitter<TArgs = any, TEventData extends IEventData = IEventData> {
private _handlers: ((e: TEventData, args: TArgs) => void)[] = [];
/***
* Adds an event handler to be called when the event is fired.
* <p>Event handler will receive two arguments - an <code>EventData</code> and the <code>data</code>
* object the event was fired with.<p>
* @method subscribe
* @param fn {Function} Event handler.
*/
subscribe(fn: ((e: TEventData, args: TArgs) => void)) {
this._handlers.push(fn);
}
/***
* Removes an event handler added with <code>subscribe(fn)</code>.
* @method unsubscribe
* @param fn {Function} Event handler to be removed.
*/
unsubscribe(fn: ((e: TEventData, args: TArgs) => void)) {
for (var i = this._handlers.length - 1; i >= 0; i--) {
if (this._handlers[i] === fn) {
this._handlers.splice(i, 1);
}
}
}
/***
* Fires an event notifying all subscribers.
* @param args {Object} Additional data object to be passed to all handlers.
* @param e {EventData}
* Optional.
* An <code>EventData</code> object to be passed to all handlers.
* For DOM events, an existing W3C/jQuery event object can be passed in.
* @param scope {Object}
* Optional.
* The scope ("this") within which the handler will be executed.
* If not specified, the scope will be set to the <code>Event</code> instance.
*/
notify(args?: any, e?: TEventData, scope?: object) {
e = patchEvent(e) || new EventData() as any;
scope = scope || this;
var returnValue;
for (var i = 0; i < this._handlers.length && !(e.isPropagationStopped() || e.isImmediatePropagationStopped()); i++) {
returnValue = this._handlers[i].call(scope, e, args);
}
return returnValue;
}
clear() {
this._handlers = [];
}
}
interface EventSubscriberEntry<TArgs = any, TEventData extends IEventData = IEventData> {
event: EventEmitter<TArgs, TEventData>;
handler: ((e: TEventData, args: TArgs) => void);
}
export class EventSubscriber<TArgs = any, TEventData extends IEventData = IEventData> {
private _handlers: EventSubscriberEntry<TArgs, TEventData>[] = [];
subscribe(event: EventEmitter<TArgs, TEventData>, handler: ((e: TEventData, args: TArgs) => void)): this {
this._handlers.push({
event: event,
handler: handler
});
event.subscribe(handler);
return this;
}
unsubscribe(event: EventEmitter<TArgs, TEventData>, handler: ((e: TEventData, args: TArgs) => void)): this {
var i = this._handlers.length;
while (i--) {
if (this._handlers[i].event === event &&
this._handlers[i].handler === handler) {
this._handlers.splice(i, 1);
event.unsubscribe(handler);
return this;
}
}
return this;
}
unsubscribeAll(): EventSubscriber<TArgs, TEventData> {
var i = this._handlers.length;
while (i--) {
this._handlers[i].event.unsubscribe(this._handlers[i].handler);
}
this._handlers = [];
return this; // allow chaining
}
}
/** @deprecated */
export const keyCode = {
BACKSPACE: 8,
DELETE: 46,
DOWN: 40,
END: 35,
ENTER: 13,
ESCAPE: 27,
HOME: 36,
INSERT: 45,
LEFT: 37,
PAGEDOWN: 34,
PAGEUP: 33,
RIGHT: 39,
TAB: 9,
UP: 38
}
function returnTrue() {
return true;
}
function returnFalse() {
return false;
}
// patches event so that it has methods jQuery event objects provides, for backward compatibility when jQuery is not loaded
export function patchEvent(e: IEventData) {
if (e == null)
return e;
if (!e.isDefaultPrevented && e.preventDefault)
e.isDefaultPrevented = function() { return this.defaultPrevented; }
var org1: () => void, org2: () => void;
if (!e.isImmediatePropagationStopped && (org1 = e.stopImmediatePropagation)) {
e.isImmediatePropagationStopped = returnFalse;
e.stopImmediatePropagation = function() { this.isImmediatePropagationStopped = returnTrue; org1.call(this); }
}
if (!e.isPropagationStopped && (org2 = e.stopPropagation)) {
e.isPropagationStopped = returnFalse;
e.stopPropagation = function() { this.isPropagationStopped = returnTrue; org2.call(this); }
}
return e;
}