send-and-receive
Version:
Two small helper methods that simplify communication between nodes in different subtrees of the browser DOM.
254 lines (244 loc) • 8.26 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = global || self, factory(global.sar = {}));
}(this, function (exports) { 'use strict';
function defineProperty(obj, name, getter) {
Object.defineProperty(obj, name, {
get: getter,
enumerable: true,
configurable: false,
});
}
function createSubsription(context) {
var subscription = {};
defineProperty(subscription, 'received', function () { return context.received; });
defineProperty(subscription, 'remaining', function () { return context.remaining; });
defineProperty(subscription, 'cancelled', function () { return context.cancelled; });
defineProperty(subscription, 'cancel', function () { return context.cancel; });
defineProperty(subscription, 'paused', function () { return context.paused; });
defineProperty(subscription, 'pause', function () { return context.pause; });
defineProperty(subscription, 'resume', function () { return context.resume; });
return subscription;
}
/**
* Dispatches an event of the specified `type` with the
* specified `data` (optional).
*
* @param type The name of the event.
* @param data The optional payload of the event.
* @example
*
* send('player:play', { src: 'song.mp3' });
*/
function send(type, data) {
return dispatchEvent(new CustomEvent(type, { detail: data }));
}
var DEFAULT_RECEIVE_OPTIONS = {
limit: Number.POSITIVE_INFINITY,
};
/**
* Listens on dispatched events of the specified `type`
* and, when it receives one, invokes `callback` with the
* data passed when sending.
*
* @param type The name of the event.
* @param callback The function to invoke.
* @param options The configuration options.
* @returns A subscription object.
* @example
*
* const subscription = receive('player:play', (data) => {
* doSomethingWith(data);
* });
*
* @description
*
* Use the returned subscription object to retrieve some
* metadata or to cancel receiving further events:
*
* @example
*
* subscription.received //=> How often has the event been received?
* subscription.remaining //=> How many remaining events can it receive?
*
* subscription.cancelled //=> Did we completely opt out of receiving further events?
* subscription.cancel() //=> Unlisten from the event and set cancelled status.
*
* subscription.paused //=> Did we temporarily stop receiving further events?
* subscription.pause() //=> Pause listening and set paused status.
* subscription.resume() //=> Resume listening and unset paused status.
*
* @description
*
* Note that both `subscription.pause()` and `subscription.resume()`
* will throw an error if the subscription has been cancelled.
*
* By default, the number of events it can receive is not limited, which
* means `subscription.remaining` will always return *positive infinity*.
*
* Besides calling `subscription.cancel()` in order to stop listening to
* further events, you can also restrict the number of times the event
* will be received by supplying the `limit` option:
*
* @example
*
* receive('player:play', callback, { limit: 1 });
*
* @description
*
* Here, after the event has been received once, it will be auto-cancelled.
* Furthermore, the subscription's `received` property will have changed
* from `0` to `1`, and the `remaining` property from `1` to `0`.
*/
function receive(type, callback, ref) {
if ( ref === void 0 ) ref = DEFAULT_RECEIVE_OPTIONS;
var limit = ref.limit;
if (limit <= 0) {
throw new RangeError('limit must be greater than 0');
}
var handler = function (event) {
if (context.received < limit) {
context.received++;
context.remaining--;
if (context.remaining === 0) {
context.cancel();
}
callback(event.detail);
}
};
var context = {
received: 0,
remaining: limit,
cancelled: false,
cancel: function cancel() {
if (context.cancelled)
{ return; }
removeEventListener(type, handler, false);
context.cancelled = true;
},
paused: false,
pause: function pause() {
if (context.cancelled) {
throw new Error('cannot pause a cancelled subscription');
}
if (context.paused)
{ return; }
removeEventListener(type, handler, false);
context.paused = true;
},
resume: function resume() {
if (context.cancelled) {
throw new Error('cannot resume a cancelled subscription');
}
if (!context.paused)
{ return; }
addEventListener(type, handler, false);
context.paused = false;
},
};
addEventListener(type, handler, false);
return createSubsription(context);
}
/**
* A convenience method for the case when you want to receive
* the event only once.
*
* @param type The name of the event.
* @param callback The function to invoke.
* @returns A subscription object.
* @example
*
* receiveOnce('player:play', callback);
*
* @description
*
* This is semantically the same as calling `receive` with
* `{ limit: 1 }` as options.
*/
function receiveOnce(type, callback) {
return receive(type, callback, { limit: 1 });
}
/**
* A convenience method to create both a sender function and
* a receiver function for the specified `type`.
*
* @param type The name of the event.
* @returns An array of size 2, where the first element is the
* send function and the second element the receive function.
*
* @description
*
* This method is especially useful when coding in TypeScript,
* as it allows strict-typing the `data`:
*
* @example
*
* // a.ts
* import { create } from 'send-and-receive';
*
* const [sendPlay, receivePlay] = create<Song>('player:play');
*
* export { receivePlay };
*
* // later on (button click, etc.)
* sendPlay({ src: 'song.mp3' });
*
* // b.ts
* import { receivePlay } from './a.js';
*
* receivePlay((song) => {
* doSomethingWith(song.src);
* });
*
* @description
*
* Optionally, you can pass a function as the second argument which
* transforms the arguments passed to `send` into the data structure
* supplied to the `receive` callback:
*
* @example
*
* interface Options {
* action: "push" | "replace";
* }
*
* const [navigateTo, receiveNavigateTo] = create(
* "navigate-to",
* (url: string, options: Options = { action: "push" }) => ({
* ...options,
* url,
* })
* );
*
* // send...
* navigateTo("/foo", { action: "replace" });
*
* // receive...
* receiveNavigateTo(({ url, action }) => history[action](url));
*/
function create(type, buildData) {
if ( buildData === void 0 ) buildData = identity;
return [
function createdSend() {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
return send(type, buildData.apply(void 0, args));
},
function createdReceive(callback, options) {
if ( options === void 0 ) options = DEFAULT_RECEIVE_OPTIONS;
return receive(type, callback, options);
} ];
}
var identity = function () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
return args[0];
};
exports.create = create;
exports.receive = receive;
exports.receiveOnce = receiveOnce;
exports.send = send;
Object.defineProperty(exports, '__esModule', { value: true });
}));
//# sourceMappingURL=umd.js.map