hy-push-state
Version:
Turn static web sites into dynamic web apps
180 lines (132 loc) • 4.79 kB
Markdown
# src / mixin / events.js
Copyright (c) 2018 Florian Klampfer <https://qwtel.com/>
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 <http://www.gnu.org/licenses/>.
```js
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.
```js
export const eventMixin = C =>
class extends C {
```
#### On start
```js
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.
```js
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.
```js
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`:
```js
hyPushStateEl.addEventListener('hy-push-state-start', ({ detail }) => {
const animPromise = new Promise((resolve) => {
const anim = myContent.animate(...);
anim.addEventListener('finish', resolve);
});
detail.transitionUntil(animPromise);
});
```
{:style="font-style:italic"}
#### 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.
```js
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.
```js
if (replaceElMissing) {
if (process.env.DEBUG) {
const ids = this.replaceIds
.concat(this.el.id || [])
.map(x => `#${x}`)
.join(", ");
console.warn(
`Couldn't find one or more ids of '${ids}' in the document at '${window.location}'. 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.
```js
window.history.back();
setTimeout(() => {
document.location.href = url;
}, 100);
```
If it's a different error, throw the generic `error` event.
```js
} 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.
```js
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.
```js
onError(context) {
if (process.env.DEBUG) console.error(context);
this.fireEvent("error", { detail: context });
}
```
#### Others
These event callbacks simply fire an event and pass the context as `detail`.
```js
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 });
}
};
```