Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Define Listener interface and remove support for explicit binding object in listeners #567

Merged
merged 1 commit into from
Jan 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Coordinator, { ActivationOptions } from '../coordinator';
import { Strategy, StrategyOptions } from '../strategy';
import { Listener } from '@orbit/core';
import { Source } from '@orbit/data';
import { assert } from '@orbit/utils';

Expand Down Expand Up @@ -55,7 +56,7 @@ export class ConnectionStrategy extends Strategy {
protected _event: string;
protected _action: string | Function;
protected _catch: Function;
protected _listener: Function;
protected _listener: Listener;
protected _filter: Function;

constructor(options: ConnectionStrategyOptions) {
Expand Down Expand Up @@ -99,16 +100,16 @@ export class ConnectionStrategy extends Strategy {
async activate(coordinator: Coordinator, options: ActivationOptions = {}): Promise<void> {
await super.activate(coordinator, options);
this._listener = this._generateListener();
this.source.on(this._event, this._listener, this);
this.source.on(this._event, this._listener);
}

async deactivate(): Promise<void> {
await super.deactivate()
this.source.off(this._event, this._listener, this);
this.source.off(this._event, this._listener);
this._listener = null;
}

protected _generateListener(): Function {
protected _generateListener(): Listener {
const target = this.target as any;

return (...args: any[]) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Coordinator, { ActivationOptions, LogLevel } from '../coordinator';
import { Strategy, StrategyOptions } from '../strategy';
import { Listener } from '@orbit/core';
import {
Source,
isPullable,
Expand All @@ -20,7 +21,7 @@ export interface EventLoggingStrategyOptions extends StrategyOptions {
export class EventLoggingStrategy extends Strategy {
protected _events?: string[];
protected _interfaces?: string[];
protected _eventListeners: Dict<Dict<Function>>;
protected _eventListeners: Dict<Dict<Listener>>;

constructor(options: EventLoggingStrategyOptions = {}) {
options.name = options.name || 'event-logging';
Expand Down Expand Up @@ -115,17 +116,17 @@ export class EventLoggingStrategy extends Strategy {
protected _addListener(source: Source, event: string): void {
const listener = this._generateListener(source, event);
deepSet(this._eventListeners, [source.name, event], listener);
source.on(event, listener, this);
source.on(event, listener);
}

protected _removeListener(source: Source, event: string): void {
const listener = deepGet(this._eventListeners, [source.name, event]);
source.off(event, listener, this);
source.off(event, listener);
this._eventListeners[source.name][event] = null;
}

protected _generateListener(source: Source, event: string): Function {
return (...args: any[]): void => {
protected _generateListener(source: Source, event: string): Listener {
return (...args: any[]) => {
console.log(this._logPrefix, source.name, event, ...args);
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,13 @@ import Coordinator, {
} from '../../src/index';
import {
Source,
TransformBuilder,
buildTransform
TransformBuilder
} from '@orbit/data';

const { module, test } = QUnit;

module('EventLoggingStrategy', function(hooks) {
const t = new TransformBuilder();
const tA = buildTransform([t.addRecord({ type: 'planet', id: 'a', attributes: { name: 'a' } })], null, 'a');
const tB = buildTransform([t.addRecord({ type: 'planet', id: 'b', attributes: { name: 'b' } })], null, 'b');
const tC = buildTransform([t.addRecord({ type: 'planet', id: 'c', attributes: { name: 'c' } })], null, 'c');

let eventLoggingStrategy;

Expand Down
9 changes: 5 additions & 4 deletions packages/@orbit/core/src/bucket.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import evented, { Evented } from './evented';
import { Listener } from './notifier';

/**
* Settings used to instantiate and/or upgrade a `Bucket`.
Expand Down Expand Up @@ -47,11 +48,11 @@ export abstract class Bucket implements Evented {
private _version: number;

// Evented interface stubs
on: (event: BUCKET_EVENTS, callback: Function, binding?: object) => void;
off: (event: BUCKET_EVENTS, callback: Function, binding?: object) => void;
one: (event: BUCKET_EVENTS, callback: Function, binding?: object) => void;
on: (event: BUCKET_EVENTS, listener: Listener) => void;
off: (event: BUCKET_EVENTS, listener: Listener) => void;
one: (event: BUCKET_EVENTS, listener: Listener) => void;
emit: (event: BUCKET_EVENTS, ...args: any[]) => void;
listeners: (event: BUCKET_EVENTS) => any[];
listeners: (event: BUCKET_EVENTS) => Listener[];

constructor(settings: BucketSettings = {}) {
if (settings.version === undefined) {
Expand Down
58 changes: 33 additions & 25 deletions packages/@orbit/core/src/evented.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Notifier from './notifier';
import Notifier, { Listener } from './notifier';
import { deprecate } from '@orbit/utils';

export const EVENTED = '__evented__';

Expand All @@ -23,11 +24,11 @@ export function isEvented(obj: any): boolean {
* ```
*/
export interface Evented {
on: (event: string, callback: Function, binding?: object) => void;
off: (event: string, callback: Function, binding?: object) => void;
one: (event: string, callback: Function, binding?: object) => void;
on: (event: string, listener: Listener) => void;
off: (event: string, listener: Listener) => void;
one: (event: string, listener: Listener) => void;
emit: (event: string, ...args: any[]) => void;
listeners: (event: string) => any[];
listeners: (event: string) => Listener[];
}

/**
Expand Down Expand Up @@ -78,35 +79,43 @@ export default function evented(Klass: any): void {

proto[EVENTED] = true;

proto.on = function(eventName: string, callback: Function, _binding: object) {
const binding = _binding || this;
proto.on = function(eventName: string, listener: Listener) {
if (arguments.length > 2) {
deprecate('`binding` argument is no longer supported when configuring `Evented` listeners. Please pre-bind listeners before calling `on`.');
}

notifierForEvent(this, eventName, true).addListener(callback, binding);
notifierForEvent(this, eventName, true).addListener(listener);
};

proto.off = function(eventName: string, callback: Function, _binding: object) {
const binding = _binding || this;
proto.off = function(eventName: string, listener: Listener) {
if (arguments.length > 2) {
deprecate('`binding` argument is no longer supported when configuring `Evented` listeners. Please pre-bind listeners before calling `off`.');
}

const notifier = notifierForEvent(this, eventName);

if (notifier) {
if (callback) {
notifier.removeListener(callback, binding);
if (listener) {
notifier.removeListener(listener);
} else {
removeNotifierForEvent(this, eventName);
}
}
};

proto.one = function(eventName: string, callback: Function, _binding: object) {
let binding = _binding || this;
let notifier = notifierForEvent(this, eventName, true);
proto.one = function(eventName: string, listener: Listener) {
if (arguments.length > 2) {
deprecate('`binding` argument is no longer supported when configuring `Evented` listeners. Please pre-bind listeners before calling `off`.');
}

const notifier = notifierForEvent(this, eventName, true);

let callOnce = function() {
callback.apply(binding, arguments);
notifier.removeListener(callOnce, binding);
const callOnce = function() {
listener(...arguments);
notifier.removeListener(callOnce);
};

notifier.addListener(callOnce, binding);
notifier.addListener(callOnce);
};

proto.emit = function(eventName: string, ...args: any[]) {
Expand All @@ -131,9 +140,9 @@ export default function evented(Klass: any): void {
export function settleInSeries(obj: Evented, eventName: string, ...args: any[]): Promise<void> {
const listeners = obj.listeners(eventName);

return listeners.reduce((chain, [callback, binding]) => {
return listeners.reduce((chain, listener) => {
return chain
.then(() => callback.apply(binding, args))
.then(() => listener(...args))
.catch(() => {});
}, Promise.resolve());
}
Expand All @@ -158,7 +167,7 @@ function notifierForEvent(object: any, eventName: string, createIfUndefined = fa
}
let notifier = object._eventedNotifiers[eventName];
if (!notifier && createIfUndefined) {
notifier = object._eventedNotifiers[eventName] = new Notifier();
notifier = object._eventedNotifiers[eventName] = new Notifier(object);
}
return notifier;
}
Expand All @@ -169,14 +178,13 @@ function removeNotifierForEvent(object: any, eventName: string) {
}
}

function fulfillEach(listeners: [Function, object][], args: any[], resolve: Function, reject: Function): Promise<any> {
function fulfillEach(listeners: Listener[], args: any[], resolve: Function, reject: Function): Promise<any> {
if (listeners.length === 0) {
resolve();
} else {
let listener;
[listener, ...listeners] = listeners;
let [callback, binding] = listener;
let response = callback.apply(binding, args);
let response = listener(...args);

if (response) {
return Promise.resolve(response)
Expand Down
2 changes: 1 addition & 1 deletion packages/@orbit/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ export { default as TaskProcessor } from './task-processor';
export { Bucket, BucketSettings, BUCKET_EVENTS } from './bucket';
export { default as evented, Evented, isEvented, settleInSeries, fulfillInSeries } from './evented';
export * from './exception';
export { default as Notifier } from './notifier';
export { default as Notifier, Listener } from './notifier';
export { default as Log, LogOptions } from './log';
9 changes: 5 additions & 4 deletions packages/@orbit/core/src/log.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { assert } from '@orbit/utils';
import evented, { Evented } from './evented';
import { Listener } from './notifier';
import { Bucket } from './bucket';
import { NotLoggedException, OutOfRangeException } from './exception';

Expand All @@ -25,11 +26,11 @@ export default class Log implements Evented {
public reified: Promise<void>;

// Evented interface stubs
on: (event: string, callback: () => void, binding?: any) => void;
off: (event: string, callback: () => void, binding?: any) => void;
one: (event: string, callback: () => void, binding?: any) => void;
on: (event: string, listener: Listener) => void;
off: (event: string, listener: Listener) => void;
one: (event: string, listener: Listener) => void;
emit: (event: string, ...args: any[]) => void;
listeners: (event: string) => any[];
listeners: (event: string) => Listener[];

constructor(options: LogOptions = {}) {
this._name = options.name;
Expand Down
36 changes: 21 additions & 15 deletions packages/@orbit/core/src/notifier.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { deprecate } from '@orbit/utils';

export type Listener = (...args: any[]) => any;

/**
* The `Notifier` class can emit messages to an array of subscribed listeners.
* Here's a simple example:
Expand All @@ -19,7 +23,7 @@
* Calls to `emit` will send along all of their arguments.
*/
export default class Notifier {
public listeners: [Function, object][];
public listeners: Listener[];

constructor() {
this.listeners = [];
Expand All @@ -29,22 +33,26 @@ export default class Notifier {
* Add a callback as a listener, which will be triggered when sending
* notifications.
*/
addListener(callback: Function, binding: object) {
binding = binding || this;
this.listeners.push([callback, binding]);
addListener(listener: Listener) {
if (arguments.length > 1) {
deprecate('`binding` argument is no longer supported for individual `Notifier` listeners. Please pre-bind listeners before calling `addListener`.');
}

this.listeners.push(listener);
}

/**
* Remove a listener so that it will no longer receive notifications.
*/
removeListener(callback: Function, binding: object) {
let listeners = this.listeners;
let listener;

binding = binding || this;
for (var i = 0, len = listeners.length; i < len; i++) {
listener = listeners[i];
if (listener && listener[0] === callback && listener[1] === binding) {
removeListener(listener: Listener) {
if (arguments.length > 1) {
deprecate('`binding` argument is no longer supported for individual `Notifier` listeners. Please pre-bind listeners before calling `removeListener`.');
}

const listeners = this.listeners;

for (let i = 0, len = listeners.length; i < len; i++) {
if (listeners[i] === listener) {
listeners.splice(i, 1);
return;
}
Expand All @@ -55,8 +63,6 @@ export default class Notifier {
* Notify registered listeners.
*/
emit(...args: any[]) {
this.listeners.slice(0).forEach((listener) => {
listener[0].apply(listener[1], args);
});
this.listeners.slice(0).forEach(listener => listener(...args));
}
}
9 changes: 5 additions & 4 deletions packages/@orbit/core/src/task-queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Task, Performer } from './task';
import TaskProcessor from './task-processor';
import { Bucket } from './bucket';
import evented, { Evented, settleInSeries } from './evented';
import { Listener } from './notifier';
import { assert } from '@orbit/utils';

/**
Expand Down Expand Up @@ -56,11 +57,11 @@ export default class TaskQueue implements Evented {
private _reified: Promise<any>;

// Evented interface stubs
on: (event: TASK_QUEUE_EVENTS, callback: Function, binding?: object) => void;
off: (event: TASK_QUEUE_EVENTS, callback: Function, binding?: object) => void;
one: (event: TASK_QUEUE_EVENTS, callback: Function, binding?: object) => void;
on: (event: TASK_QUEUE_EVENTS, listener: Listener) => void;
off: (event: TASK_QUEUE_EVENTS, listener: Listener) => void;
one: (event: TASK_QUEUE_EVENTS, listener: Listener) => void;
emit: (event: TASK_QUEUE_EVENTS, ...args: any[]) => void;
listeners: (event: TASK_QUEUE_EVENTS) => [Function, object][];
listeners: (event: TASK_QUEUE_EVENTS) => Listener[];

/**
* Creates an instance of `TaskQueue`.
Expand Down
Loading