window-page

19.1.0 • Public • Published

window.Page

Async chains for web page lifecycle.

Works well with:

  • async navigation
  • custom elements
  • visibility API
  • history API

window.Page is the current State instance.

Install

This is a nodejs module. It natively supports es6 modules, and provides a cjs bundle.

Chains

  • route: pathname changed and document is not prerendered; can set state.doc.
  • ready: document is ready
  • build: pathname changed and document is not prerendered
  • patch: pathname changed and document is not prerendered, or query changed
  • setup: visible, on first view or pathname changed
  • paint: visible, on first view or pathname or query changed
  • fragment: visible, location hash changed; state.hash is set.
  • close: visible, referrer closed when pathname changed, before new state is setup
  • catch: error was thrown, state.error is set.

A run is triggered by navigation (document.location changed in any way, or page history methods called, see below).

Several chains are only run when document is visible - i.e. not "hidden". This is used to prerender on server, and also prerender on client.

Route listeners can set state.doc: an optional document which styles and scripts are imported after route chain. The ready chain always has state.doc = document.

If the state.error object is removed from state during the catch chain, the navigation will continue as if the error did not happen.

Usage

Page.route(async function(state) {
 const res = await fetch(page.pathname + '.json');
 const data = await res.json();
 // keep data during navigation
 state.data = data;
 state.doc = state.parseDoc(data.template);
});

Page.connect({
  build(state) {
    // build page
  }
  async patch(state) {
    if (state.query.id == null) return;
    const data = await (fetch('/getdata?id=' + state.query.id).json())
    // do something
  }
  handleClick(e) {
    const link = e.target.closest('a[href]');
    if (!link) return;
    e.preventDefault();
    state.push(link.href);
  }
});

API

chains

For each chain, one can add or remove a listener function that receives the current state as argument.

  • state[chainLabel](fn) runs optional fn right now if the chain is reached, or wait the chain to be run. returns a promise that resolves to corresponding state.
  • state['un'+chainLabel](fn) removes fn from a chain, mostly needed for custom elements.

The fn parameter can be a function or an object with a <chain>, or a chain<Chain> method - which is a handy way to keep the value of this.

Functions listening for a given stage are run serially.

If a stage chain is already resolved, new listeners are just added immediately to the promise chain.

To append a function at the end of the current chain, use:

  • state.finish(fn) fn can return a promise.

Avoid making a chain wait its own end, or it will deadlock.

To run a custom chain:

  • state.copy().runChain(stage)

Note that custom chains do not propagate properties added to state to other chains.

listeners and navigation

Chains are implemented through native DOM emitters and listeners, and the emitter is either a script node in document.head, or a state-bound, out of tree, DOM node.

The script node can be document.currentScript when defined, unless the listener is a DOM node that is not itself a script node in document head.

Otherwise it is state.emitter. That emitter is shared between two successive states having the same pathname, and distinct otherwise.

These behaviors ensure that during navigation, a common script keeps its listeners registered, and other listeners will only be triggered during the life of the state that allowed them to be registered.

state

The state is a subclass of Loc, which extends URL class with:

  • query object
  • sameDomain, samePathname, sameQuery, sameHash, samePath methods
  • toString() returns a path when in the same domain
  • copy() to copy a state and most of its private/public properties useful for working with another stage of a state.

Important: use state.push/replace to mutate url properties. The state history methods accept partial objects.

  • state.data data is saved in navigator history - must be JSON-serializable.

  • state.referrer the previous state, or null; Is not related to document.referrer.

Page.State: the state's constructor.

Document import

When a new document is loaded after route chain, stylesheets are loaded in parallel, and scripts are loaded serially, with parallel preloading.

Those methods are called:

  • await state.mergeHead(head, prev)
  • await state.mergeBody(body, prev)

The default mergeHead method do a basic diff to keep existing scripts and links nodes. The default mergeBody method just replaces document.body with the new body.

To manage page transitions, these methods can be overriden by route listeners.

Integration with Custom Elements, event handlers

An object having build, patch, setup, paint, fragment, close methods can be plugged into Page using:

  • state.connect(node)
  • state.disconnect(node)

Furthermore, if the object owns methods named handle${Type}, they will be used as type event listeners on that object, receiving arguments (e, state).

To use "capture" listeners, just name the methods capture<Type> (new in 7.1.0).

To use the same mecanism to manage event listener on another event emitter, pass that event emitter as second argument to state.connect(listener, emitter).

To simply handle or capture events on window, use handleAll${Type} or captureAll${Type}.

These event listeners are automatically added during setup, and removed during close (or disconnect).

connectedCallback() {
  Page.connect(this);
}
disconnectedCallback() {
  Page.disconnect(this);
}
patch(state) {}
setup(state) {}
close() {}
captureSubmit(e, state) {}
handleClick(e, state) {}
handleAllClick(e, state) {}

Using the event listener on other objects (window, document...)

  • state.connect(listener, emitter)

This method accepts a second argument to configure event listeners, and benefit from automatic removal of event listeners on close.

Example:

setup(state) {
  state.connect(this, window);
}
handleScroll(e, state) {
  // got click
}

History

These methods will run chains on new state and return immediately the new state:

  • state.push(location or url, opts)
  • state.replace(location or url, opts)

Options:

  • vary (boolean, or "build", "patch", "fragment", default false) Overrides how pathname, query, hash are compared to previous state. true re-routes the page; and varying on a chain runs the next chains too. Example: reload after a form login.

  • data Assign this data to next state.data.

  • state.reload(opts) a shortcut for state.replace(state, opts), with the correct value for vary set depending on state chains being used or not. opts.vary can be set, in which case it is passed as is to replace. Example: does not call setup then close unless BUILD chain is not empty.

A convenient method only that only replaces current window.history entry:

  • state.save() useful to save current state.data.

Loc methods

State inherits from Loc:

  • parse(str) parses a url into pathname, query object, hash; and protocol, hostname, port if not the same domain as the document. returns a Loc instance.

  • format(loc) format a location to a string with only what was defined, converts obj.path to pathname, query then stringify query obj if any.

  • sameDomain(b) compare domains (protocol + hostname + port) of two url or objects.

  • samePathname(b) compare domains and pathname of two url or objects.

  • sameQuery(b) compare query strings of two url or objects.

  • samePath(b) compare domain, pathname, querystring (without hash) of two url or objects.

BrowserStack and Browser support

This project runs its tests on multiple desktop and mobile browsers using travis BrowserStack addon, sponsored by BrowserStack.

Browser Stack Logo

Tested on:

  • latest versions of Chrome, Firefox, Safari
  • iPhone >= 5
  • IE >= 10 (with URL and Promise polyfills)
  • Edge >= 13
  • android browser (on Samsung Galaxy S5)

Build Status BrowserStack Status

Debug logs

Just enable debug level in the console.

License

MIT, see LICENSE file.

Dependencies (1)

Dev Dependencies (6)

Package Sidebar

Install

npm i window-page

Weekly Downloads

8

Version

19.1.0

License

MIT

Unpacked Size

60.4 kB

Total Files

9

Last publish

Collaborators

  • kapouer