README
@selfage/stateful_navigator
Install
npm install @selfage/stateful_navigator
Overview
Written in TypeScript and compiled to ES6 with inline source map & source. See @selfage/tsconfig for full compiler options. Provides classes to manage associate observable state with browser history.
Observable state
This library is based on @selfage/message
to provide an observable state, which is a data object typically generated by @selfage/cli
.
We will use a state generated as below for all the following examples, assuming its file path is at ./state.ts
.
import { EnumDescriptor, MessageDescriptor } from '@selfage/message/descriptor';
import { EventEmitter } from 'events';
export enum Page {
HOME = 1,
HISTORY = 2,
}
export let PAGE: EnumDescriptor<Page> = {
name: 'Page',
values: [
{
name: 'HOME',
value: 1,
},
{
name: 'HISTORY',
value: 3,
},
]
}
export interface BodyState {
on(event: 'page', listener: (newValue: Page, oldValue: Page) => void): this;
on(event: 'init', listener: () => void): this;
}
export class BodyState extends EventEmitter {
private page_?: Page;
get page(): Page {
return this.page_;
}
set page(value: Page) {
let oldValue = this.page_;
if (value === oldValue) {
return;
}
this.page_ = value;
this.emit('page', this.page_, oldValue);
}
public triggerInitialEvents(): void {
if (this.page_ !== undefined) {
this.emit('page', this.page_, undefined);
}
this.emit('init');
}
public toJSON(): Object {
return {
page: this.page,
};
}
}
export let BODY_STATE: MessageDescriptor<BodyState> = {
name: 'BodyState',
factoryFn: () => {
return new BodyState();
},
fields: [
{
name: 'page',
enumDescriptor: PAGE,
},
]
};
Loader and updater
import { createLoaderAndUpdater } from '@selfage/stateful_navigator';
import { STATE } from './state';
let queryParamKey = 'q';
let [loader, updater] = createLoaderAndUpdater(STATE, queryParamKey);
// Now build your DOM tree and add listeners on loader.state
// loader.state.on('page', ...)
// When the state is changed and you want a new history entry.
updater.push();
// When the state is changed and you don't want a new histroy entry.
updater.replace();
STATE
is an instance of MessageDescriptor
and State
is the class type. queryParamKey
is used to compose a query param q=...
and to get the param value, which holds a stringified state.
createLoaderAndUpdater()
adds a listener to popstate
event to handle users clicking browser's back button, by parsing the query param q=<stringified historical state>
. However, you have to add listeners to each field of loader.state
to actually handle the state change.
updater.push()
should be called whenever you want a new history entry with the current state, which creates a new query param q=<stringfied current state>
in the URL. It shouldn't be called with every field change, because you may want to group several changes together as one history entry. updater.replace()
is the same as updater.push()
except it replace the currrent URL without creating a new history entry.
BTW, the type of loader
is HistoryLoader<State>
by import {HistoryLoader} from '@selfage/stateful_navigator/history_loader'
and the type of updater
is HistoryUpdater
by import {HistoryUpdater} '@selfage/stateful_navigator/history_updater'
;