@dharmax/state-router 1.1.2 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ReadMe.md +29 -18
- package/dist/router.d.ts +1 -1
- package/dist/state-manager.d.ts +6 -3
- package/dist/state-manager.js +18 -4
- package/package.json +2 -2
- package/src/state-manager.ts +22 -6
package/ReadMe.md
CHANGED
|
@@ -6,37 +6,48 @@ What is a functional router? it's a router that captures a url changes and
|
|
|
6
6
|
per specific pattern of url, it simply triggers a handler. It supports hash notation
|
|
7
7
|
as well as normal url patterns. It also supports contexts (url parameters).
|
|
8
8
|
|
|
9
|
-
Together with the state manager - which will be the main object you'd need
|
|
10
|
-
to work with, you get a very
|
|
9
|
+
Together with the **state manager** included here - which will be the main object you'd need
|
|
10
|
+
to work with, you get a very friendly and strong semantic application state management:
|
|
11
11
|
each state can be given a logical name, route and an associated page/component.
|
|
12
12
|
|
|
13
|
+
# Features
|
|
14
|
+
* Very easy to use
|
|
15
|
+
* Purely Functional - no need for anything by simple JS
|
|
16
|
+
* Only one tiny dependency
|
|
17
|
+
* Easily allow any authorization logic to be used
|
|
18
|
+
* Tiny footprint
|
|
19
|
+
* Doesn't monopolise the URL
|
|
20
|
+
* Supports state contexts
|
|
13
21
|
|
|
14
22
|
# Example
|
|
15
23
|
|
|
16
24
|
## state definitions
|
|
17
25
|
|
|
18
26
|
```javascript
|
|
27
|
+
|
|
28
|
+
// this is how you define the states: logical name, component name, route (as string or regex)
|
|
19
29
|
stateManager.addState('main', 'main-page', /main$/);
|
|
20
30
|
stateManager.addState('login', 'login-box', 'login')
|
|
21
31
|
stateManager.addState('signup', 'signup-box', /signup/)
|
|
22
32
|
stateManager.addState('my-profile') // will assume the page name and the route are the same...
|
|
23
|
-
stateManager.addState('inbox')
|
|
33
|
+
stateManager.addState('inbox') // automatically assume the logical name, component and route are the same
|
|
24
34
|
stateManager.addState('about')
|
|
25
|
-
stateManager.addState('discussion', 'discussion-page', 'discussion/%')
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
stateManager.onChange( event => {
|
|
35
|
-
// this specific example works with RiotJs, but you get the drift
|
|
36
|
-
this.update({
|
|
37
|
-
currentPage: event.data.pageName
|
|
35
|
+
stateManager.addState('discussion', 'discussion-page', 'discussion/%') // route with parameter (the % is added to the state's context )
|
|
36
|
+
|
|
37
|
+
// this is an example of how you handle the state change (navigation events)
|
|
38
|
+
stateManager.onChange( event => {
|
|
39
|
+
// this specific example works with RiotJs, but you get the drift
|
|
40
|
+
this.update({
|
|
41
|
+
currentPage: event.data.pageName
|
|
42
|
+
})
|
|
38
43
|
})
|
|
39
|
-
})
|
|
40
|
-
|
|
41
44
|
|
|
45
|
+
// this is how you can add hooks by which you can block navigation based
|
|
46
|
+
// on any logic you want.
|
|
47
|
+
stateManager.registerChangeAuthority( async targetState => {
|
|
48
|
+
if ( ! await PermissionManager.isUserAllowedToSeePage(targetState.pageName))
|
|
49
|
+
return false
|
|
50
|
+
return openConfirmDialog('Confirm leaving this page')
|
|
51
|
+
})
|
|
52
|
+
|
|
42
53
|
```
|
package/dist/router.d.ts
CHANGED
package/dist/state-manager.d.ts
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import { RoutingMode } from "./router";
|
|
2
2
|
import { IPubSubHandle, PubSubEvent } from "@dharmax/pubsub";
|
|
3
|
-
export
|
|
4
|
-
export
|
|
3
|
+
export type ApplicationStateName = string;
|
|
4
|
+
export type ApplicationState = {
|
|
5
5
|
name: ApplicationStateName;
|
|
6
6
|
pageName: string;
|
|
7
7
|
route: RegExp;
|
|
8
8
|
mode?: string | string[];
|
|
9
9
|
};
|
|
10
|
+
export type ChangeAuthority = (state: ApplicationState) => Promise<boolean>;
|
|
10
11
|
export declare class StateManager {
|
|
11
12
|
private allStates;
|
|
12
13
|
private appState;
|
|
13
14
|
private previousState;
|
|
14
15
|
private stateContext;
|
|
15
16
|
static dispatcher: import("@dharmax/pubsub").PubSub;
|
|
17
|
+
private changeAuthorities;
|
|
16
18
|
constructor(mode?: RoutingMode);
|
|
17
19
|
onChange(handler: (event: PubSubEvent, data: any) => void): IPubSubHandle;
|
|
20
|
+
registerChangeAuthority(authorityCallback: (state: ApplicationState) => Promise<boolean>): void;
|
|
18
21
|
getState(): ApplicationState;
|
|
19
22
|
get previous(): ApplicationState;
|
|
20
23
|
get context(): ApplicationState;
|
|
@@ -30,7 +33,7 @@ export declare class StateManager {
|
|
|
30
33
|
* @param stateName state
|
|
31
34
|
* @param context extra context (e.g. sub-state)
|
|
32
35
|
*/
|
|
33
|
-
setState(stateName: ApplicationStateName, context?: any): boolean
|
|
36
|
+
setState(stateName: ApplicationStateName, context?: any): Promise<boolean>;
|
|
34
37
|
/**
|
|
35
38
|
* Define an application state
|
|
36
39
|
* @param name
|
package/dist/state-manager.js
CHANGED
|
@@ -3,12 +3,21 @@ import dispatcher from "@dharmax/pubsub";
|
|
|
3
3
|
export class StateManager {
|
|
4
4
|
constructor(mode = 'hash') {
|
|
5
5
|
this.allStates = {};
|
|
6
|
+
this.changeAuthorities = [];
|
|
6
7
|
router.mode = mode;
|
|
7
8
|
router.listen();
|
|
8
9
|
}
|
|
9
10
|
onChange(handler) {
|
|
10
11
|
return StateManager.dispatcher.on('state:changed', handler);
|
|
11
12
|
}
|
|
13
|
+
/*
|
|
14
|
+
Add a hook which enable conditional approval of state change. It can be more than one; when a state
|
|
15
|
+
change is requested, all the registered authorities must return true (asynchronously) otherwise the change
|
|
16
|
+
requested doesn't happen.
|
|
17
|
+
**/
|
|
18
|
+
registerChangeAuthority(authorityCallback) {
|
|
19
|
+
this.changeAuthorities.push(authorityCallback);
|
|
20
|
+
}
|
|
12
21
|
getState() {
|
|
13
22
|
return this.appState || {};
|
|
14
23
|
}
|
|
@@ -31,7 +40,7 @@ export class StateManager {
|
|
|
31
40
|
/** attempts to restore state from current url */
|
|
32
41
|
restoreState(defaultState) {
|
|
33
42
|
let dest = window.location.hash;
|
|
34
|
-
if (dest
|
|
43
|
+
if (dest === '#login' || dest === '')
|
|
35
44
|
dest = '#' + defaultState;
|
|
36
45
|
router.navigate(dest);
|
|
37
46
|
}
|
|
@@ -40,12 +49,17 @@ export class StateManager {
|
|
|
40
49
|
* @param stateName state
|
|
41
50
|
* @param context extra context (e.g. sub-state)
|
|
42
51
|
*/
|
|
43
|
-
setState(stateName, context) {
|
|
52
|
+
async setState(stateName, context) {
|
|
44
53
|
const newState = this.allStates[stateName];
|
|
45
54
|
if (!newState) {
|
|
46
55
|
alert(`Undefined app state ${stateName}`);
|
|
47
56
|
return false;
|
|
48
57
|
}
|
|
58
|
+
// check if the state change was declined by any change authority and if so - don't do it and return false
|
|
59
|
+
const changeConfirmations = await Promise.all(this.changeAuthorities.map(a => a(newState)));
|
|
60
|
+
if (changeConfirmations.includes(false))
|
|
61
|
+
return false;
|
|
62
|
+
// perform the change
|
|
49
63
|
this.previousState = this.appState;
|
|
50
64
|
this.stateContext = context;
|
|
51
65
|
this.appState = newState;
|
|
@@ -75,8 +89,8 @@ export class StateManager {
|
|
|
75
89
|
}
|
|
76
90
|
registerStateByState(state) {
|
|
77
91
|
this.allStates[state.name] = state;
|
|
78
|
-
router.add(state.route, (context) => {
|
|
79
|
-
if (this.setState(state.name, context)) {
|
|
92
|
+
router.add(state.route, async (context) => {
|
|
93
|
+
if (await this.setState(state.name, context)) {
|
|
80
94
|
// @ts-ignore
|
|
81
95
|
if (window.ga) {
|
|
82
96
|
// @ts-ignore
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dharmax/state-router",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "A cute and tight router and application state controller",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://github.com/dharmax/state-routerr",
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"typescript": "^4.
|
|
28
|
+
"typescript": "^4.9.3"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@dharmax/pubsub": "^1.1.0"
|
package/src/state-manager.ts
CHANGED
|
@@ -11,6 +11,7 @@ export type ApplicationState = {
|
|
|
11
11
|
mode?: string | string[]
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export type ChangeAuthority = (state: ApplicationState) => Promise<boolean>
|
|
14
15
|
|
|
15
16
|
export class StateManager {
|
|
16
17
|
private allStates: { [name: string]: ApplicationState } = {}
|
|
@@ -19,6 +20,7 @@ export class StateManager {
|
|
|
19
20
|
private stateContext: ApplicationState
|
|
20
21
|
|
|
21
22
|
public static dispatcher = dispatcher
|
|
23
|
+
private changeAuthorities: ChangeAuthority[] = [];
|
|
22
24
|
|
|
23
25
|
constructor(mode: RoutingMode = 'hash') {
|
|
24
26
|
|
|
@@ -27,11 +29,20 @@ export class StateManager {
|
|
|
27
29
|
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
onChange(handler: (event: PubSubEvent, data: any) => void):IPubSubHandle {
|
|
32
|
+
onChange(handler: (event: PubSubEvent, data: any) => void): IPubSubHandle {
|
|
31
33
|
|
|
32
34
|
return StateManager.dispatcher.on('state:changed', handler)
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
/*
|
|
38
|
+
Add a hook which enable conditional approval of state change. It can be more than one; when a state
|
|
39
|
+
change is requested, all the registered authorities must return true (asynchronously) otherwise the change
|
|
40
|
+
requested doesn't happen.
|
|
41
|
+
**/
|
|
42
|
+
registerChangeAuthority(authorityCallback: (targetState: ApplicationState) => Promise<boolean>) {
|
|
43
|
+
this.changeAuthorities.push(authorityCallback)
|
|
44
|
+
}
|
|
45
|
+
|
|
35
46
|
getState(): ApplicationState {
|
|
36
47
|
return this.appState || <ApplicationState>{}
|
|
37
48
|
}
|
|
@@ -55,7 +66,7 @@ export class StateManager {
|
|
|
55
66
|
this.setState(state)
|
|
56
67
|
}
|
|
57
68
|
|
|
58
|
-
/** attempts to restore state from current url */
|
|
69
|
+
/** attempts to restore state from current url. Currently, works only in hash mode */
|
|
59
70
|
restoreState(defaultState: ApplicationStateName) {
|
|
60
71
|
let dest = window.location.hash
|
|
61
72
|
if (dest == '#login' || dest == '')
|
|
@@ -68,7 +79,7 @@ export class StateManager {
|
|
|
68
79
|
* @param stateName state
|
|
69
80
|
* @param context extra context (e.g. sub-state)
|
|
70
81
|
*/
|
|
71
|
-
setState(stateName: ApplicationStateName, context?: any): boolean {
|
|
82
|
+
async setState(stateName: ApplicationStateName, context?: any): Promise<boolean> {
|
|
72
83
|
|
|
73
84
|
const newState = this.allStates[stateName];
|
|
74
85
|
if (!newState) {
|
|
@@ -76,6 +87,12 @@ export class StateManager {
|
|
|
76
87
|
return false
|
|
77
88
|
}
|
|
78
89
|
|
|
90
|
+
// check if the state change was declined by any change authority and if so - don't do it and return false
|
|
91
|
+
const changeConfirmations = await Promise.all(this.changeAuthorities.map( authority => authority(newState) ))
|
|
92
|
+
if (changeConfirmations.find( c => c === false))
|
|
93
|
+
return false
|
|
94
|
+
|
|
95
|
+
// perform the change
|
|
79
96
|
this.previousState = this.appState
|
|
80
97
|
this.stateContext = context
|
|
81
98
|
this.appState = newState
|
|
@@ -108,15 +125,14 @@ export class StateManager {
|
|
|
108
125
|
|
|
109
126
|
registerStateByState(state: ApplicationState) {
|
|
110
127
|
this.allStates[state.name] = state
|
|
111
|
-
router.add(state.route, (context: any) => {
|
|
112
|
-
if (this.setState(state.name, context)) {
|
|
128
|
+
router.add(state.route, async (context: any) => {
|
|
129
|
+
if (await this.setState(state.name, context)) {
|
|
113
130
|
|
|
114
131
|
// @ts-ignore
|
|
115
132
|
if (window.ga) {
|
|
116
133
|
// @ts-ignore
|
|
117
134
|
window.ga('send', 'pageview', `/${state.name}/${context || ''}`);
|
|
118
135
|
}
|
|
119
|
-
|
|
120
136
|
}
|
|
121
137
|
})
|
|
122
138
|
}
|