Skip to content

reallyland/url-observer

Repository files navigation

url-observer

URLObserver observes URL changes in web browsers


Buy Me A Coffee tippin.me Follow me

Version MIT License

Downloads Total downloads Packagephobia Bundlephobia

ci Dependency Status codecov

codebeat badge Language grade: JavaScript Code of Conduct

Inspired by PerformanceObserver but for observing history on browsers.

Table of contents

Pre-requisites

Installation

# Install via NPM
$ npm install url-observer

Usage

import 'url-observer';

const observer = new URLObserver((list, observer) => {
  for (const entry of list.getEntries()) {
    /** Process entry for each URL update */ 
  }
});
const routes = [
  /^\/test$\//i,
  /^\/test\/(?<test>[^\/]+)$/i,
];
const options = {
  dwellTime: 2e3, /** Default dwellTime. Set -1 to always push new URL */
  debug: false, /** Set to enable debug mode. This exposes hidden `routes` property. */
  matcherCallback() {
    /**
     * Override how route matching works internally.
     * By default, ES2018's RegExp named capture groups are used.
     */
  },
};

/** Call .observe() to start observing history */
observe.observe(routes, options);

/** Call .add() to add new route or before route handler to existing registered route */
observer.add({
  handleEvent: () => {
    /** Do anything before route changes. Return true to navigate to new route. */
    return true;
  }
  pathRegExp: routes[0],
  /**
   * A scoped route handler enables multiple before route handler to be registered to the
   * same route. E.g.
   * 
   * A .data-scope property or `data-scope` attribute can be set in an anchor tag so that URLObserver
   * knows which before route handler it needs to trigger before navigating to a new URL.
   * 
   * When .data-scope (or `data-scope`) is an empty string, it defaults to ':default', which is the 
   * default scope value when registering a route unless specified.
   * 
   * 1. <a href="/test/123">/test/456</a>
   *    - No before route handler will be triggered on link click as it is not a scoped link.
   * 
   * 2. <a href="/test/123" data-scope>/test/123</a>
   *    - Only before route handler registered to ':default' scope will be triggered.
   * 
   * 3. <a href="/test/123" data-scope="456">/test/456</a>
   *    - Only before route handler registered to '456' scope will be triggered.
   * 
   */
  scope: '',
});

/** Dynamically add new route without before route handler */
observer.add({ pathRegExp: /^\/test2$/i });

/** Call .disconnect() to stop observing history */
observer.disconnect();

/** Call .match() to determine if current URL is being observed by URLObserver */
const {
  /** Return true for a matched route */
  found,
  /**
   * Return URL parameters after matching the route RegExp with current URL. E.g.
   * 
   * 1. /^\/test/i
   *    - This does not output any matches
   * 2. /^\/test\/(?<test>[^\/]+)$/i
   *    - This matches URL like '/test/123' and returns { test: 123  }. However, this requires
   *      ES2018's RegExp named capture groups to work as expected.
   */
  params,
} = observer.match();

/** Remove a route from the observer */
observer.remove(routes[0]);

/** Remove a before route handler from an observing route */
observer.remove(routes[1], '456');

/** Return the history entries */
observer.takeRecords();

/** Async-ly call .updateHistory to manually update to new URL */
await observer.updateHistory('/test/789');

/** 
 * Async-ly call .updateHistory to manually update to new URL and trigger before route handler
 * with defined scope value.
 */
await observer.updateHistory('/test/456', '456');

API References

Contributing

Code of Conduct

Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.

License

MIT License © Rong Sen Ng