Skip to content

Instantly share code, notes, and snippets.

@DeadWisdom
Last active December 23, 2020 20:39
Show Gist options
  • Save DeadWisdom/62bff0179366fff137038f0bdf616e11 to your computer and use it in GitHub Desktop.
Save DeadWisdom/62bff0179366fff137038f0bdf616e11 to your computer and use it in GitHub Desktop.
class ConsumerEvent extends Event {
constructor(name) {
super('consume-' + name, { bubbles: true, cancelable: true });
this.provider = null;
this.providerEvent = name + '-changed';
}
}
const providers = new WeakMap();
export function provide(source, name, value, providerEvent) {
providerEvent = providerEvent || name + '-changed';
this.provide(source, name, value);
let providing = providers.get(source);
// Register our source as a provider
if (providing === undefined) {
providing = new Map();
providers.set(source, providing);
}
if (!providing.has(name)) {
providing.set(name, value);
// Listen for a consume-*name* event, notify consumer that we promise to provide
source.addEventListener('consume-' + name, e => {
e.provider = source;
e.providerEvent = providerEvent;
e.value = providing[name];
e.stopPropagation();
e.preventDefault();
});
} else {
// Updated our stored value
providing[name] = value;
}
// Notify of value change
let evt = new CustomEvent(providerEvent, {detail: { value: value, provider: source }});
source.dispatchEvent(evt);
}
export function consume(consumer, name, callback) {
// Send the consumer event
let evt = new ConsumerEvent(name)
consumer.dispatchEvent(evt);
if (!evt.provider) {
// We couldn't find a provider up the DOM, so we're done here.
return null;
}
// Since a provider was found, we send the callback an artificial initial update event:
let initialEvent = new CustomEvent(name, { detail: { value: evt.value, provider: evt.provider, initial: true } });
callback(initialEvent);
// We could simply return the event to the consumer so they can handle
// registration themselves, but for this example, we'll subscribe to the event:
evt.provider.addEventListener(evt.providerEvent, callback);
// If we do that, we can further provide a disconnector for convenience:
evt.disconnect = () => evt.provider.removeEventListener(evt.providerEvent, callback);
// Return the event, the consumer gets to know who the provider is
return evt;
}
// Usage ////
// A provider simply calls this as many times as necessary, whenever the data updates:
// This will trigger 'user-changed' on the element.
class UserProvider extends LitElement {
onUserUpdate(userData) {
provide(this, 'user', userData);
}
}
// Or the provider could use anything as the source. Now the window itself is triggering 'user-changed':
provide(window, 'user', userData);
// A consumer simply consumes as needed:
class UserConsumer extends LitElement {
connectedCallback() {
super.connectedCallback();
this._userChangeSubscription = consume(this, 'user', this._onUserChange = this.onUserChange.bind(this));
}
disconnectedCallback() {
super.disconnectedCallback();
if (this._userChangeSubscription) {
this._userChangeSubscription.disconnect();
}
}
onUserChange(e) {
this.user = e.detail.value;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment