src / mixin / events.js

Copyright (c) 2018 Florian Klampfer

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see

import { Observable, timer } from "rxjs/_esm5";

Event functions

These functions are called at various points along the observable pipeline to fire events, and cause other side effects.

export const eventMixin = C =>
  class extends C {

On start

    onStart(context) {

By default, hy-push-state will wait at least duration ms before replacing the content, so that animations have enough time to finish. The behavior is encoded with a promise that resolves after duration ms.

      this.animPromise = timer(this.duration);

The transitionUntil function lets users of this component override the animation promise. This allows for event-based code execution, rather than timing-based, which prevents hiccups and glitches when, for example, painting takes longer than expected.

      const transitionUntil = promise => {
        if (process.env.DEBUG && !(promise instanceof Promise || promise instanceof Observable)) {
          console.warn("transitionUntil expects a Promise as first argument.");
        this.animPromise = promise;

      this.fireEvent("start", {
        detail: Object.assign(context, { transitionUntil }),

Example usage of transitionUntil:

hyPushStateEl.addEventListener('hy-push-state-start', ({ detail }) => {
  const animPromise = new Promise((resolve) => {
    const anim = myContent.animate(...);
    anim.addEventListener('finish', resolve);

Error callbacks

This function handles errors while trying to insert the new content into the document. If the retrieved documened doesn’t contain the ids we are looking for we can’t insert the content dynamically, so we tell the browser to open the link directly.

    onDOMError(context) {
      const { replaceElMissing, url } = context;

Ideally you should prevent this situation by adding the no-push-state CSS class on links to documents that don’t match the expected document layout. This only serves as a fallback.

      if (replaceElMissing) {
        if (process.env.DEBUG) {
          const ids = this.replaceIds
            .concat( || [])
            .map(x => `#${x}`)
            .join(", ");
            `Couldn't find one or more ids of '${ids}' in the document at '${
            }'. Opening the link directly.`

To open the link directly, we first pop one entry off the browser history. We have to do this because (some) browsers won’t handle the back button correctly otherwise. We then wait for a short time and change the document’s location. TODO: If we didn’t call pushState optimistically we wouldn’t have to do this.

        setTimeout(() => {
          document.location.href = url;
        }, 100);

If it’s a different error, throw the generic error event.

      } else {
        if (process.env.DEBUG) console.error(context);
        this.fireEvent("error", { detail: context });

If there is a network error during (pre-) fetching, fire networkerror event.

    onNetworkError(context) {
      if (process.env.DEBUG) console.error(context);
      this.fireEvent("networkerror", { detail: context });

When using the experimental script feature, fire scripterror event if something goes wrong during script insertion.

    onError(context) {
      if (process.env.DEBUG) console.error(context);
      this.fireEvent("error", { detail: context });


These event callbacks simply fire an event and pass the context as detail.

    onReady(context) {
      this.fireEvent("ready", { detail: context });

    onAfter(context) {
      this.fireEvent("after", { detail: context });

    onProgress(context) {
      this.fireEvent("progress", { detail: context });

    onLoad(context) {
      this.fireEvent("load", { detail: context });

© 2018 Florian Klampfer

Powered by Hydejack v8.4.0