Skip to content

Commit 3c56dfa

Browse files
feat(DSR): Port DSR to ui-router 1.0
0 parents  commit 3c56dfa

File tree

10 files changed

+891
-0
lines changed

10 files changed

+891
-0
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License
2+
3+
Copyright (c) 2013-2015 The AngularUI Team, Karsten Sperling
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

README.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# UI-Router Core  [![Build Status](https://travis-ci.org/ui-router/core.svg?branch=master)](https://travis-ci.org/ui-router/core)
2+
3+
UI-Router core provides client-side [Single Page Application](https://en.wikipedia.org/wiki/Single-page_application)
4+
routing for JavaScript.
5+
This core is framework agnostic.
6+
It is used to build
7+
[UI-Router for Angular 1](//ui-router.github.io/ng1),
8+
[UI-Router for Angular 2](//ui-router.github.io/ng2), and
9+
[UI-Router React](//ui-router.github.io/react).
10+
11+
## SPA Routing
12+
13+
Routing frameworks for SPAs update the browser's URL as the user navigates through the app. Conversely, this allows
14+
changes to the browser's URL to drive navigation through the app, thus allowing the user to create a bookmark to a
15+
location deep within the SPA.
16+
17+
UI-Router applications are modeled as a hierarchical tree of states. UI-Router provides a
18+
[*state machine*](https://en.wikipedia.org/wiki/Finite-state_machine) to manage the transitions between those
19+
application states in a transaction-like manner.
20+
21+
## Features
22+
23+
UI-Router Core provides the following features:
24+
25+
- State-machine based routing
26+
- Hierarchical states
27+
- Enter/Exit hooks
28+
- Name based hierarchical state addressing
29+
- Absolute, e.g., `admin.users`
30+
- Relative, e.g., `.users`
31+
- Flexible Views
32+
- Nested Views
33+
- Multiple Named Views
34+
- Flexible URLs and parameters
35+
- Path, Query, and non-URL parameters
36+
- Typed parameters
37+
- Built in: `int`, `string`, `date`, `json`
38+
- Custom: define your own encoding/decoding
39+
- Optional or required parameters
40+
- Default parameter values (optionally squashed from URL)
41+
- Transaction-like state transitions
42+
- Transition Lifecycle Hooks
43+
- First class async support
44+
45+
## Get Started
46+
47+
Get started using one of the existing UI-Router projects:
48+
49+
- [UI-Router for Angular 1](https://ui-router.github.io/ng1)
50+
- [UI-Router for Angular 2](https://ui-router.github.io/ng2)
51+
- [UI-Router for React](https://ui-router.github.io/react)
52+
53+
## Build your own
54+
55+
UI-Router core can be used implement a router for any web-based component framework.
56+
There are four basic things to build for a specific component framework:
57+
58+
### UIView
59+
60+
A UIView is a component which acts as a viewport for another component, defined by a state.
61+
When the state is activated, the UIView should render the state's component.
62+
63+
### UISref (optional, but useful)
64+
65+
A `UISref` is a link (absolute, or relative) which activates a specific state and/or parameters.
66+
When the `UISref` is clicked, it should initiate a transition to the linked state.
67+
68+
### UISrefActive (optional)
69+
70+
When combined with a `UISref`, a `UISrefActive` toggles a CSS class on/off when its `UISref` is active/inactive.
71+
72+
### Bootstrap mechanism (optional)
73+
74+
Implement framework specific bootstrap requirements, if any.
75+
For example, UI-Router for Angular 1 and Angular 2 integrates with the ng1/ng2 Dependency Injection lifecycles.
76+
On the other hand, UI-Router for React uses a simple JavaScript based bootstrap, i.e., `new UIRouterReact().start();`.
77+
78+
## Getting help
79+
80+
[Create an issue](https://github.com/ui-router/core/issues) or contact us on [Gitter](https://gitter.im/angular-ui/ui-router).

karma.conf.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Karma configuration file
2+
var karma = require('karma');
3+
4+
module.exports = function (karma) {
5+
var config = {
6+
singleRun: true,
7+
autoWatch: false,
8+
autoWatchInterval: 0,
9+
10+
// level of logging
11+
// possible values: LOG_DISABLE, LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG
12+
logLevel: "warn",
13+
// possible values: 'dots', 'progress'
14+
reporters: 'dots',
15+
colors: true,
16+
17+
port: 8080,
18+
19+
// base path, that will be used to resolve files and exclude
20+
basePath: '.',
21+
22+
// Start these browsers, currently available:
23+
// Chrome, ChromeCanary, Firefox, Opera, Safari, PhantomJS
24+
browsers: ['PhantomJS'],
25+
26+
frameworks: ['jasmine'],
27+
28+
plugins: [
29+
require('karma-webpack'),
30+
require('karma-sourcemap-loader'),
31+
require('karma-jasmine'),
32+
require('karma-phantomjs-launcher'),
33+
require('karma-chrome-launcher')
34+
],
35+
36+
webpack: {
37+
devtool: 'inline-source-map',
38+
39+
resolve: {
40+
modulesDirectories: ['node_modules'],
41+
extensions: ['', '.js', '.ts']
42+
},
43+
44+
module: {
45+
loaders: [
46+
{ test: /\.ts$/, loader: "awesome-typescript-loader?declaration=false&tsconfig=test/tsconfig.json" }
47+
]
48+
},
49+
50+
},
51+
52+
webpackMiddleware: {
53+
stats: { chunks: false },
54+
},
55+
56+
files: ['test/index.js'],
57+
58+
preprocessors: {
59+
'test/index.js': ['webpack', 'sourcemap'],
60+
},
61+
62+
};
63+
64+
karma.set(config);
65+
};

package.json

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
{
2+
"name": "ui-router-dsr",
3+
"description": "UI-Router Deep State Redirect: redirect to the most recently activated child state",
4+
"version": "1.0.0",
5+
"scripts": {
6+
"clean": "shx rm -rf lib lib-esm",
7+
"build": "npm run clean && tsc && tsc -p tsconfig.esm.json",
8+
"test": "karma start",
9+
"watch": "run-p watch:*",
10+
"watch:buildjs": "tsc -w",
11+
"watch:test": "karma start --singleRun=false --autoWatch=true --autoWatchInterval=1",
12+
"debug": "karma start --singleRun=false --autoWatch=true --autoWatchInterval=1 --browsers=Chrome"
13+
},
14+
"homepage": "https://ui-router.github.io",
15+
"contributors": [
16+
{
17+
"name": "Chris Thielen",
18+
"web": "https://github.com/christopherthielen"
19+
}
20+
],
21+
"maintainers": [
22+
{
23+
"name": "UIRouter Team",
24+
"web": "https://github.com/ui-router?tab=members"
25+
}
26+
],
27+
"repository": {
28+
"type": "git",
29+
"url": "https://github.com/ui-router/dsr.git"
30+
},
31+
"bugs": {
32+
"url": "https://github.com/ui-router/dsr/issues"
33+
},
34+
"engines": {
35+
"node": ">=4.0.0"
36+
},
37+
"jsnext:main": "lib-esm/index.js",
38+
"main": "lib/index.js",
39+
"typings": "lib/index.d.ts",
40+
"license": "MIT",
41+
"devDependencies": {
42+
"@types/jasmine": "^2.2.34",
43+
"@types/jquery": "^1.10.31",
44+
"@types/lodash": "^4.14.38",
45+
"awesome-typescript-loader": "^2.2.4",
46+
"conventional-changelog": "^1.1.0",
47+
"conventional-changelog-cli": "^1.1.1",
48+
"conventional-changelog-ui-router-core": "^1.3.0",
49+
"core-js": "^2.4.1",
50+
"jasmine-core": "^2.4.1",
51+
"karma": "^1.2.0",
52+
"karma-chrome-launcher": "~0.1.0",
53+
"karma-coverage": "^0.5.3",
54+
"karma-jasmine": "^1.0.2",
55+
"karma-phantomjs-launcher": "^1.0.2",
56+
"karma-script-launcher": "~0.1.0",
57+
"karma-sourcemap-loader": "^0.3.7",
58+
"karma-webpack": "^1.8.0",
59+
"lodash": "^4.16.6",
60+
"npm-run-all": "^3.1.1",
61+
"readline-sync": "^1.4.4",
62+
"shelljs": "^0.7.0",
63+
"shx": "^0.1.4",
64+
"tslint": "=2.5.0",
65+
"typescript": "^2.1.1",
66+
"ui-router-core": "^1.0.1",
67+
"webpack": "^1.13.3"
68+
}
69+
}

src/deepStateRedirect.ts

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import {
2+
State, StateDeclaration, Param, UIRouter, RawParams, StateOrName, TargetState, Transition
3+
} from "ui-router-core";
4+
export { deepStateRedirect };
5+
6+
declare module "ui-router-core" {
7+
interface StateDeclaration {
8+
dsr?: any;
9+
deepStateRedirect?: any;
10+
}
11+
}
12+
13+
function deepStateRedirect($uiRouter: UIRouter): any {
14+
let $transitions = $uiRouter.transitionService;
15+
let $state = $uiRouter.stateService;
16+
17+
$transitions.onRetain({ retained: getDsr }, recordDeepState);
18+
$transitions.onEnter({ entering: getDsr }, recordDeepState);
19+
$transitions.onBefore({ to: getDsr }, deepStateRedirect);
20+
21+
function getDsr(state: StateDeclaration) {
22+
return state.deepStateRedirect || state.dsr;
23+
}
24+
25+
function getConfig(state: StateDeclaration) {
26+
let dsrProp: any = getDsr(state);
27+
let propType: string = typeof dsrProp;
28+
if (propType === 'undefined') return;
29+
30+
let params;
31+
let defaultTarget = propType === 'string' ? dsrProp : undefined;
32+
let fn: Function = propType === 'function' ? dsrProp : undefined;
33+
34+
if (propType === 'object') {
35+
fn = dsrProp.fn;
36+
let defaultType = typeof dsrProp.default;
37+
if (defaultType === 'object') {
38+
defaultTarget = $state.target(dsrProp.default.state, dsrProp.default.params, dsrProp.default.options);
39+
} else if (defaultType === 'string') {
40+
defaultTarget = $state.target(dsrProp.default);
41+
}
42+
if (dsrProp.params === true) {
43+
params = function () {
44+
return true;
45+
}
46+
} else if (Array.isArray(dsrProp.params)) {
47+
params = function (param: Param) {
48+
return dsrProp.params.indexOf(param.id) !== -1;
49+
}
50+
}
51+
}
52+
53+
fn = fn || ((transition, target) => target);
54+
55+
return { params: params, default: defaultTarget, fn: fn };
56+
}
57+
58+
function paramsEqual(state: State, transParams: RawParams, schemaMatchFn?: (param?: Param) => boolean, negate = false) {
59+
schemaMatchFn = schemaMatchFn || (() => true);
60+
let schema = state.parameters({ inherit: true }).filter(schemaMatchFn);
61+
return function (redirect) {
62+
let equals = Param.equals(schema, redirect.triggerParams, transParams);
63+
return negate ? !equals : equals;
64+
}
65+
}
66+
67+
function recordDeepState(transition, state) {
68+
let paramsConfig = getConfig(state).params;
69+
70+
transition.promise.then(function () {
71+
let transTo = transition.to();
72+
let transParams = transition.params();
73+
let recordedDsrTarget = $state.target(transTo, transParams);
74+
75+
if (paramsConfig) {
76+
state.$dsr = (state.$dsr || []).filter(paramsEqual(transTo.$$state(), transParams, undefined, true));
77+
state.$dsr.push({ triggerParams: transParams, target: recordedDsrTarget });
78+
} else {
79+
state.$dsr = recordedDsrTarget;
80+
}
81+
});
82+
}
83+
84+
function deepStateRedirect(transition: Transition) {
85+
let opts = transition.options();
86+
if (opts['ignoreDsr'] || (opts.custom && opts.custom.ignoreDsr)) return;
87+
88+
let config = getConfig(transition.to());
89+
let redirect = getDeepStateRedirect(transition.to(), transition.params());
90+
redirect = config.fn(transition, redirect);
91+
if (redirect && redirect.state() === transition.to()) return;
92+
93+
return redirect
94+
}
95+
96+
function getDeepStateRedirect(stateOrName: StateOrName, params: RawParams) {
97+
let state = $state.get(stateOrName);
98+
let dsrTarget, config = getConfig(state);
99+
let $$state = state.$$state();
100+
101+
if (config.params) {
102+
var predicate = paramsEqual($$state, params, config.params, false);
103+
let match = $$state['$dsr'] && $$state['$dsr'].filter(predicate)[0];
104+
dsrTarget = match && match.target;
105+
} else {
106+
dsrTarget = $$state['$dsr'];
107+
}
108+
109+
dsrTarget = dsrTarget || config.default;
110+
111+
if (dsrTarget) {
112+
// merge original params with deep state redirect params
113+
let targetParams = Object.assign({}, params, dsrTarget.params());
114+
dsrTarget = $state.target(dsrTarget.state(), targetParams, dsrTarget.options());
115+
}
116+
117+
return dsrTarget;
118+
}
119+
120+
return {
121+
reset: function(state: StateOrName, params?: RawParams) {
122+
if (!state) {
123+
$state.get().forEach(state => delete state.$$state()['$dsr']);
124+
} else if (!params) {
125+
delete $state.get(state).$$state()['$dsr']
126+
} else {
127+
var $$state = $state.get(state).$$state();
128+
$$state['$dsr'] = $$state['$dsr'].filter(paramsEqual($$state, params, null, true));
129+
}
130+
},
131+
132+
getRedirect: function (state: StateOrName, params?: RawParams) {
133+
return getDeepStateRedirect(state, params);
134+
}
135+
}
136+
}

0 commit comments

Comments
 (0)