Skip to content

Commit 1a934b4

Browse files
feat(dataSource): Added pluggable dataSource ability and created LocalStorageDataSource
Closes #90
1 parent 7f8578a commit 1a934b4

File tree

6 files changed

+252
-73
lines changed

6 files changed

+252
-73
lines changed

README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
With Deep State Redirect, a parent state remembers whatever child state was last activated.
88
When the user directly reactivates the parent state, they are redirected to the nested state (which was previously activated).
99

10-
1110
## Overview and Use Case
1211

1312
Deep State Redirect (DSR) is a marker you can add to a state definition.
@@ -31,3 +30,26 @@ If used with a Sticky State, the states will be reactivated, and the DOM will be
3130
See: http://christopherthielen.github.io/ui-router-extras/#/dsr
3231

3332
TODO: Move docs here
33+
34+
### Using a custom DataStore
35+
36+
By default DSR stores the most recent redirects in memory.
37+
Alternatively, you can store the redirects in Local Storage using
38+
[LocalStorageDataStore](https://github.com/ui-router/dsr/blob/master/src/DSRDataStore.ts)
39+
or create your own DataStore.
40+
41+
When registering the DSRPlugin, pass an options object with a `dataStore` property, i.e.:
42+
43+
```js
44+
router.plugin(DSRPlugin, { dataStore: new LocalStorageDataStore() });
45+
```
46+
47+
## Example Builds
48+
49+
The [`/examples` directory](https://github.com/ui-router/dsr/tree/master/examples) contains example setups for:
50+
51+
- Angular-CLI
52+
- AngularJS + bower + script tags
53+
- AngularJS + npm + script tags
54+
- AngularJS + webpack
55+
- Create-React-App

src/DSRDataStore.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { StateOrName, UIRouter } from '@uirouter/core';
2+
import { RecordedDSR } from './interface';
3+
4+
export interface DSRDataStore {
5+
init(router: UIRouter): void;
6+
// Gets the remembered DSR target state for a given state and params
7+
get(state: StateOrName): RecordedDSR[];
8+
// Sets the remembered DSR target state for a given state and params
9+
set(state: StateOrName, recordedDSR: RecordedDSR[] | undefined): void;
10+
}
11+
12+
export class StateObjectDataStore implements DSRDataStore {
13+
private router: UIRouter;
14+
15+
private getState(stateOrName: StateOrName) {
16+
const state = this.router.stateService.get(stateOrName);
17+
return state && state.$$state();
18+
}
19+
20+
public init(router: UIRouter): void {
21+
this.router = router;
22+
}
23+
24+
public get(stateOrName: StateOrName): RecordedDSR[] {
25+
return this.getState(stateOrName).$dsr || [];
26+
}
27+
28+
public set(stateOrName: StateOrName, recordedDsr: RecordedDSR[]): void {
29+
const state = this.getState(stateOrName);
30+
if (recordedDsr) {
31+
state.$dsr = recordedDsr;
32+
} else {
33+
delete state.$dsr;
34+
}
35+
}
36+
}
37+
38+
export class LocalStorageDataStore implements DSRDataStore {
39+
private router: UIRouter;
40+
private key = 'uiRouterDeepStateRedirect';
41+
42+
private getStore() {
43+
const item = localStorage.getItem(this.key);
44+
return JSON.parse(item || '{}');
45+
}
46+
47+
private setStore(contents: any) {
48+
if (contents) {
49+
try {
50+
localStorage.setItem(this.key, JSON.stringify(contents));
51+
} catch (err) {
52+
console.error(
53+
'UI-Router Deep State Redirect: cannot store object in LocalStorage. Is there a circular reference?',
54+
contents
55+
);
56+
console.error(err);
57+
}
58+
} else {
59+
localStorage.removeItem(this.key);
60+
}
61+
}
62+
63+
private getStateName(stateOrName: StateOrName) {
64+
const state = this.router.stateService.get(stateOrName);
65+
return state && state.name;
66+
}
67+
68+
public init(router: UIRouter): void {
69+
this.router = router;
70+
}
71+
72+
public get(stateOrName: StateOrName): RecordedDSR[] {
73+
const stateName = this.getStateName(stateOrName);
74+
const store = this.getStore();
75+
return store[stateName] || [];
76+
}
77+
78+
public set(stateOrName: StateOrName, recordedDsr: RecordedDSR[]): void {
79+
const stateName = this.getStateName(stateOrName);
80+
const store = this.getStore();
81+
store[stateName] = recordedDsr;
82+
this.setStore(store);
83+
}
84+
}

src/dsr.ts

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,31 @@ import {
1212
StateService,
1313
} from '@uirouter/core';
1414

15+
import { DSRDataStore, StateObjectDataStore } from './DSRDataStore';
1516
import { _DSRConfig, DSRConfigObj, DSRFunction, DSRProp, ParamPredicate, RecordedDSR } from './interface';
1617

1718
class DSRPlugin implements UIRouterPlugin {
1819
name = 'deep-state-redirect';
1920

21+
dataStore: DSRDataStore;
2022
$transitions: TransitionService;
2123
$state: StateService;
2224
hookDeregFns = [];
2325

24-
constructor($uiRouter: UIRouter) {
26+
constructor($uiRouter: UIRouter, options: { dataStore: DSRDataStore }) {
2527
this.$transitions = $uiRouter.transitionService;
2628
this.$state = $uiRouter.stateService;
29+
this.dataStore = (options && options.dataStore) || new StateObjectDataStore();
30+
this.dataStore.init($uiRouter);
2731

2832
this.hookDeregFns.push(
29-
this.$transitions.onRetain({ retained: state => !!this.getDsrProp(state.self) }, this.recordDeepState.bind(this)),
33+
this.$transitions.onRetain({ retained: state => !!this.getDsrProp(state.self) }, this.recordDeepState.bind(this))
3034
);
3135
this.hookDeregFns.push(
32-
this.$transitions.onEnter({ entering: state => !!this.getDsrProp(state.self) }, this.recordDeepState.bind(this)),
36+
this.$transitions.onEnter({ entering: state => !!this.getDsrProp(state.self) }, this.recordDeepState.bind(this))
3337
);
3438
this.hookDeregFns.push(
35-
this.$transitions.onBefore({ to: state => !!this.getDsrProp(state.self) }, this.deepStateRedirect.bind(this)),
39+
this.$transitions.onBefore({ to: state => !!this.getDsrProp(state.self) }, this.deepStateRedirect.bind(this))
3640
);
3741
}
3842

@@ -58,12 +62,13 @@ class DSRPlugin implements UIRouterPlugin {
5862
reset(state?: StateOrName, params?: RawParams): void {
5963
const { $state } = this;
6064
if (!state) {
61-
$state.get().forEach(_state => delete _state.$$state().$dsr);
65+
$state.get().forEach(_state => this.dataStore.set(_state, undefined));
6266
} else if (!params) {
63-
delete $state.get(state).$$state().$dsr;
67+
this.dataStore.set(state, undefined);
6468
} else {
69+
const currentDSRS = this.dataStore.get(state);
6570
const $$state = $state.get(state).$$state();
66-
$$state.$dsr = ($$state.$dsr as RecordedDSR[]).filter(this.paramsEqual($$state, params, undefined, true));
71+
this.dataStore.set(state, currentDSRS.filter(this.paramsEqual($$state, params, undefined, true)));
6772
}
6873
}
6974

@@ -85,7 +90,7 @@ class DSRPlugin implements UIRouterPlugin {
8590
return state.deepStateRedirect || state.dsr;
8691
}
8792

88-
private getConfig(state: StateDeclaration): _DSRConfig {
93+
public getConfig(state: StateDeclaration): _DSRConfig {
8994
const { $state } = this;
9095
const dsrProp: DSRProp = this.getDsrProp(state);
9196
if (typeof dsrProp === 'undefined') return;
@@ -116,11 +121,11 @@ class DSRPlugin implements UIRouterPlugin {
116121
return { default: defaultTarget, params, fn };
117122
}
118123

119-
private paramsEqual(
124+
public paramsEqual(
120125
state: StateObject,
121126
transParams: RawParams,
122127
paramPredicate: ParamPredicate = () => true,
123-
negate = false,
128+
negate = false
124129
): (redirect: RecordedDSR) => boolean {
125130
const schema = state.parameters({ inherit: true }).filter(paramPredicate);
126131

@@ -132,26 +137,29 @@ class DSRPlugin implements UIRouterPlugin {
132137

133138
private recordDeepState(transition: Transition, state: StateDeclaration): void {
134139
const { $state } = this;
135-
const paramsConfig = this.getConfig(state).params;
136-
const _state = state.$$state();
140+
const hasParamsConfig: boolean = !!this.getConfig(state).params;
141+
const _state: StateObject = state.$$state();
137142

138143
transition.promise.then(() => {
139144
const transTo = transition.to();
140-
const transParams = transition.params();
141-
const recordedDsrTarget = $state.target(transTo, transParams);
142-
143-
if (paramsConfig) {
144-
const recordedDSR = (_state.$dsr as RecordedDSR[]) || [];
145-
const predicate = this.paramsEqual(transTo.$$state(), transParams, undefined, true);
146-
_state.$dsr = recordedDSR.filter(predicate);
147-
_state.$dsr.push({ triggerParams: transParams, target: recordedDsrTarget });
145+
const triggerParams = transition.params();
146+
const target: TargetState = $state.target(transTo, triggerParams);
147+
const targetStateName = target.name();
148+
const targetParams = target.params();
149+
const recordedDSR: RecordedDSR = { triggerParams, targetStateName, targetParams };
150+
151+
if (hasParamsConfig) {
152+
const currentDSRS: RecordedDSR[] = this.dataStore.get(_state);
153+
const predicate = this.paramsEqual(transTo.$$state(), triggerParams, undefined, true);
154+
const updatedDSRS = currentDSRS.filter(predicate).concat(recordedDSR);
155+
this.dataStore.set(_state, updatedDSRS);
148156
} else {
149-
_state.$dsr = recordedDsrTarget;
157+
this.dataStore.set(_state, [recordedDSR]);
150158
}
151159
});
152160
}
153161

154-
private deepStateRedirect(transition: Transition) {
162+
private deepStateRedirect(transition: Transition): TargetState | undefined {
155163
const opts = transition.options();
156164
if (opts['ignoreDsr'] || (opts.custom && opts.custom.ignoreDsr)) return;
157165

@@ -165,22 +173,27 @@ class DSRPlugin implements UIRouterPlugin {
165173
return redirect;
166174
}
167175

176+
private getTargetState(dsr: RecordedDSR): TargetState {
177+
return this.$state.target(dsr.targetStateName, dsr.targetParams);
178+
}
179+
168180
private getDeepStateRedirect(stateOrName: StateOrName, params: RawParams): TargetState {
169181
const { $state } = this;
170182
const _state = $state.get(stateOrName);
171183
const state = _state && _state.$$state();
172184
const config: _DSRConfig = this.getConfig(_state);
173-
let dsrTarget: TargetState;
185+
const currentDSRS = this.dataStore.get(stateOrName);
186+
let recordedDSR: RecordedDSR;
174187

175188
if (config.params) {
176189
const predicate = this.paramsEqual(state, params, config.params, false);
177-
const match = state.$dsr && (state.$dsr as RecordedDSR[]).filter(predicate)[0];
178-
dsrTarget = match && match.target;
190+
const match = currentDSRS.find(predicate);
191+
recordedDSR = match && match;
179192
} else {
180-
dsrTarget = state.$dsr as TargetState;
193+
recordedDSR = currentDSRS[0] && currentDSRS[0];
181194
}
182195

183-
dsrTarget = dsrTarget || config.default;
196+
let dsrTarget = recordedDSR ? this.getTargetState(recordedDSR) : config.default;
184197

185198
if (dsrTarget) {
186199
// merge original params with deep state redirect params

src/interface.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ declare module '@uirouter/core/lib/state/interface' {
99

1010
declare module '@uirouter/core/lib/state/stateObject' {
1111
interface StateObject {
12-
$dsr: TargetState | RecordedDSR[];
12+
$dsr: RecordedDSR[];
1313
}
1414
}
1515

@@ -35,6 +35,7 @@ export interface _DSRConfig {
3535
}
3636

3737
export interface RecordedDSR {
38-
target: TargetState;
38+
targetStateName: string;
39+
targetParams: RawParams;
3940
triggerParams: object;
4041
}

tslint.json

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
{
2-
"extends": [ "tslint-eslint-rules" ],
2+
"extends": ["tslint-eslint-rules"],
33
"rules": {
44
"align": [true, "parameters", "statements"],
55
"ban": false,
66
"class-name": true,
7-
"comment-format": [ true, "check-space" ],
7+
"comment-format": [true, "check-space"],
88
"curly": false,
99
"eofline": true,
1010
"forin": true,
11-
"indent": [ true, "spaces" ],
11+
"indent": [true, "spaces"],
1212
"label-position": true,
1313
"max-line-length": [true, 180],
1414
"member-access": false,
15-
"member-ordering": [ true, "static-before-instance", "variables-before-functions" ],
15+
"member-ordering": [true, "static-before-instance", "variables-before-functions"],
1616
"no-arg": true,
1717
"no-bitwise": true,
1818
"no-conditional-assignment": true,
19-
"no-console": [ true, "log", "warn", "debug", "info", "time", "timeEnd", "trace" ],
19+
"no-console": [true, "log", "warn", "debug", "info", "time", "timeEnd", "trace"],
2020
"no-construct": true,
2121
"no-debugger": true,
2222
"no-duplicate-variable": true,
@@ -27,25 +27,33 @@
2727
"no-string-literal": false,
2828
"no-switch-case-fall-through": true,
2929
"no-trailing-whitespace": true,
30-
"no-unused-expression": [ true, "allow-fast-null-checks" ],
30+
"no-unused-expression": [true, "allow-fast-null-checks"],
3131
"no-unused-variable": true,
3232
"no-use-before-declare": true,
3333
"no-var-keyword": true,
3434
"object-curly-spacing": "always",
3535
"object-literal-sort-keys": false,
36-
"one-line": [ true, "check-catch", "check-else", "check-open-brace", "check-whitespace" ],
37-
"prefer-const": [ true, { "destructuring": "all" } ],
38-
"quotemark": [ true, "single", "avoid-escape", "jsx-double" ],
36+
"one-line": [true, "check-catch", "check-else", "check-open-brace", "check-whitespace"],
37+
"prefer-const": [true, { "destructuring": "all" }],
38+
"quotemark": [true, "single", "avoid-escape", "jsx-double"],
3939
"radix": true,
40-
"semicolon": [ true, "always", "ignore-bound-class-methods", "ignore-interfaces" ],
41-
"trailing-comma": [true, {"multiline": "always", "singleline": "never"}],
42-
"triple-equals": [ true, "allow-null-check" ],
43-
"typedef": [ "call-signature", "property-declaration" ],
44-
"typedef-whitespace": [ true, { "call-signature": "nospace", "index-signature": "nospace", "parameter": "nospace", "property-declaration": "nospace", "variable-declaration": "nospace" } ],
45-
"variable-name": [ true, "ban-keywords", "allow-leading-underscore" ],
46-
"whitespace": [ true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type" ],
40+
"semicolon": [true, "always", "ignore-bound-class-methods", "ignore-interfaces"],
41+
"trailing-comma": [true],
42+
"triple-equals": [true, "allow-null-check"],
43+
"typedef": ["call-signature", "property-declaration"],
44+
"typedef-whitespace": [
45+
true,
46+
{
47+
"call-signature": "nospace",
48+
"index-signature": "nospace",
49+
"parameter": "nospace",
50+
"property-declaration": "nospace",
51+
"variable-declaration": "nospace"
52+
}
53+
],
54+
"variable-name": [true, "ban-keywords", "allow-leading-underscore"],
55+
"whitespace": [true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type"],
4756
"jsx-no-multiline-js": false,
4857
"jsx-no-lambda": false
4958
}
5059
}
51-

0 commit comments

Comments
 (0)