@mongez/events
TypeScript icon, indicating that this package has built-in type declarations

2.1.0 • Public • Published

Mongez Events

From Wikipedia:

In computer programming, event-driven programming is a programming paradigm in which the flow of the program is determined by events such as user actions (mouse clicks, key presses), sensor outputs, or message passing from other programs or threads. Event-driven programming is the dominant paradigm used in graphical user interfaces and other applications (e.g., JavaScript web applications) that are centered on performing certain actions in response to user input.

Introduction

In a nutshell, the event driven approach is a powerful paradigm to handle data and actions based on the occurrence of certain events.

Installation

Using Yarn

yarn add @mongez/events

npm

npm i @mongez/events

Basic usage

The following example shows how events can be used.

import events from "@mongez/events";

// First add an event listener for some event, let's call it `numberChange`

events.subscribe("numberChange", (number) => {
  console.log(number); // 5
});

// later in anywhere in the application

events.trigger("numberChange", 5);

This is a simple usage of the event system, you may have seen it before if you've worked with jQuery or with the DOM Events in general.

Event Subscription

The on method is called a subscription method that we subscribe our callback, in our case the callback that logged the number once the numberChange event is triggered.

You can subscribe to any event using one of the following methods: on | addEventListener or subscribe.

They are all interchangeable methods, but it's recommended to use subscribe method as it's more readable and also it is the base method for the other two methods.

import events from "@mongez/events";

// subscribe to the `numberChange` event
events.subscribe("numberChange", (number) => {
  console.log(number); // 5
});

// subscribe to the `numberChange` event
events.on("numberChange", (number) => {
  console.log(number); // 5
});

// subscribe to the `numberChange` event
events.addEventListener("numberChange", (number) => {
  console.log(number); // 5
});

Unsubscribe to event

Any subscription to certain event generates an EventSubscription instance as a return type, this is useful if you need to cancel that subscription at anytime.

import events from "@mongez/events";

const headerSubscription = events.subscribe("headerChange", () => {
  console.log("Header has been changed");
});

// three seconds later
setTimeout(() => {
  headerSubscription.unsubscribe();
}, 3000);

The EventSubscription object has the following properties and methods

type EventSubscription = {
  /**
   * The callback function that will be triggered on the dispatch method
   */
  callback: Function;
  /**
   * Event name
   */
  event: string;
  /**
   * A method to trigger the callback function
   */
  dispatch: (...args: any[]) => any;
  /**
   * Remove the callback from the events list
   */
  unsubscribe: () => void;
};

Event Trigger

As the event will have a listener/subscriber, it also needs to be triggered in certain point so the listener will know the event is fired.

You can trigger any event using trigger or emit methods and send any values in the 2nd or more arguments.

import events from "@mongez/events";

events.subscribe("cartUpdate", (cartData, mode) => {
  console.log(mode); // quantityChanged
});

Now let's trigger the event from somewhere else in the project.

const cartData = {
  totalQuantity: 10,
  totalPrice: 201,
  taxes: 200,
};

const mode = "quantityChanged";

events.trigger("cart.update", cartData, mode);

Returning values from the trigger

If any listener return any value, the last return value from the listeners will be returned.

import events from "@mongez/events";

events.on("cart.update", (cartData) => {
  if (cartData.totalQuantity === 0) return "Empty Cart";
});

events.on("cart.update", (cartData) => {
  console.log(cartData.totalQuantity); // output: 0
});

let result = events.trigger("cart.update", {
  totalQuantity: 0,
  // ...other values
});

console.log(result); // Empty Cart

Please be aware that returning false will stop calling other listeners, see next section.

Stopping the event trigger from certain listener

If we want to just stop the loop from going to the next listeners, then returning false will stop the loop from going to the next listeners.

import events from "@mongez/events";

events.on("cart.update", (cartData) => {
  return false; // prevent going to next listener
});

events.on("cart.update", (cartData) => {
  console.log(cartData.totalQuantity); // this will not be triggered
});

let result = events.trigger("cart.update", {
  totalQuantity: 0,
});

Removing Events

As we can subscribe to events, we may also need to clear all event subscriptions at once, this can be done by using off or unsubscribe method

import events from "@mongez/events";

// clear all event listeners to the `headerChange` and `footerChange` events
events.off("headerChange").off("footerChange");

You may also clear the entire events listeners by not passing any arguments to the off or to the unsubscribe method.

import events from "@mongez/events";

// clear all event listeners from the event loop
events.unsubscribe();

Triggering all event listeners

As the trigger method can be stopped from a single listener to call the other listeners, there is another way to call all event listeners regardless the return of each listener by using the triggerAll method

import events from "@mongez/events";

events.on("cart.update", (cartData) => {
  return false; // this will not prevent going to next listener
});

events.on("cart.update", (cartData) => {
  console.log(cartData.totalQuantity); // this will be called as well output is: 1
});

events.triggerAll("cart.update", {
  totalQuantity: 1,
});

Another advantage of the triggerAll method is that it returns an instance of EventTriggerResponse that informs you the state of the trigger process, the event name, number of called listeners and its outputs.

type EventTriggerResponse = {
  /**
   * Event Name
   */
  event: string;
  /**
   * Number of triggered callbacks
   */
  length: number;
  /**
   * List of all returned values from each subscription callback, undefined values will not be included
   */
  results: any[];
};

Removing events by namespace

In certain cases, we may need to remove all events that starts with certain namespace, for example we need to clear all events are related to the cart such as cart.initialized cart.update cart.remove cart.reset and so on.

So instead of removing each one separately, we can remove them all by using unsubscribeNamespace method.

import events from "@mongez/events";

events.on("cart.initialized", () => {
  console.log("Cart is initialized");
});

events.on("cart.update", (cartData) => {
  console.log("Cart is updated");
});

events.on("cart.remove", (cartItem) => {
  console.log(`Removed cart item ${cartItem.name}`);
});

// remove all cart listeners

events.unsubscribeNamespace("cart"); // all listeners will be cleared

Get all event subscriptions

To get all subscriptions of certain event, you can use the subscriptions method.

import events from "@mongez/events";

events.on("headerChange", () => {
  console.log("Header has been changed");
});

events.on("headerChange", () => {
  console.log("Header has been changed again");
});

// get length of all subscriptions to the `headerChange` event
console.log(events.subscriptions("headerChange").length); // 2

TriggerAsync

To trigger events asynchronously, you can use the triggerAsync method.

import events from "@mongez/events";

events.on("cart.update", (cartData) => {
  console.log(cartData.totalQuantity); // output: 0
});

async function main() {
  await events.triggerAsync("cart.update", {
    totalQuantity: 0,
  });
}

main();

This will wait for each listener to finish its execution before going to the next listener.

If any resolved value is returned from any listener, the last resolved value will be returned and all remaining callbacks will be ignored.

TriggerAllAsync

To trigger all events asynchronously, you can use the triggerAllAsync method.

import events from "@mongez/events";

events.on("cart.update", (cartData) => {
  console.log(cartData.totalQuantity); // output: 0
});

async function main() {
  await events.triggerAllAsync("cart.update", {
    totalQuantity: 0,
  });
}

main();

Works exactly like triggerAsync but it will call all listeners regardless the return of each listener.

Tests

To run the tests, run the following command

yarn test

Change Log

  • V2.0.0 (01 Mar 2023)
    • Removed multiple events subscriptions and unsubscriptions.
    • Added Unit Tests.
    • Enhanced parameters and return types.

TODO

  • Add triggerAsync method to trigger events asynchronously.
  • Add triggerAllAsync method to trigger all events asynchronously.

Package Sidebar

Install

npm i @mongez/events

Weekly Downloads

28

Version

2.1.0

License

MIT

Unpacked Size

30.4 kB

Total Files

19

Last publish

Collaborators

  • hassanzohdy