-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(DSR): Port DSR to ui-router 1.0
- Loading branch information
0 parents
commit 3c56dfa
Showing
10 changed files
with
891 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
The MIT License | ||
|
||
Copyright (c) 2013-2015 The AngularUI Team, Karsten Sperling | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
# UI-Router Core [](https://travis-ci.org/ui-router/core) | ||
|
||
UI-Router core provides client-side [Single Page Application](https://en.wikipedia.org/wiki/Single-page_application) | ||
routing for JavaScript. | ||
This core is framework agnostic. | ||
It is used to build | ||
[UI-Router for Angular 1](//ui-router.github.io/ng1), | ||
[UI-Router for Angular 2](//ui-router.github.io/ng2), and | ||
[UI-Router React](//ui-router.github.io/react). | ||
|
||
## SPA Routing | ||
|
||
Routing frameworks for SPAs update the browser's URL as the user navigates through the app. Conversely, this allows | ||
changes to the browser's URL to drive navigation through the app, thus allowing the user to create a bookmark to a | ||
location deep within the SPA. | ||
|
||
UI-Router applications are modeled as a hierarchical tree of states. UI-Router provides a | ||
[*state machine*](https://en.wikipedia.org/wiki/Finite-state_machine) to manage the transitions between those | ||
application states in a transaction-like manner. | ||
|
||
## Features | ||
|
||
UI-Router Core provides the following features: | ||
|
||
- State-machine based routing | ||
- Hierarchical states | ||
- Enter/Exit hooks | ||
- Name based hierarchical state addressing | ||
- Absolute, e.g., `admin.users` | ||
- Relative, e.g., `.users` | ||
- Flexible Views | ||
- Nested Views | ||
- Multiple Named Views | ||
- Flexible URLs and parameters | ||
- Path, Query, and non-URL parameters | ||
- Typed parameters | ||
- Built in: `int`, `string`, `date`, `json` | ||
- Custom: define your own encoding/decoding | ||
- Optional or required parameters | ||
- Default parameter values (optionally squashed from URL) | ||
- Transaction-like state transitions | ||
- Transition Lifecycle Hooks | ||
- First class async support | ||
|
||
## Get Started | ||
|
||
Get started using one of the existing UI-Router projects: | ||
|
||
- [UI-Router for Angular 1](https://ui-router.github.io/ng1) | ||
- [UI-Router for Angular 2](https://ui-router.github.io/ng2) | ||
- [UI-Router for React](https://ui-router.github.io/react) | ||
|
||
## Build your own | ||
|
||
UI-Router core can be used implement a router for any web-based component framework. | ||
There are four basic things to build for a specific component framework: | ||
|
||
### UIView | ||
|
||
A UIView is a component which acts as a viewport for another component, defined by a state. | ||
When the state is activated, the UIView should render the state's component. | ||
|
||
### UISref (optional, but useful) | ||
|
||
A `UISref` is a link (absolute, or relative) which activates a specific state and/or parameters. | ||
When the `UISref` is clicked, it should initiate a transition to the linked state. | ||
|
||
### UISrefActive (optional) | ||
|
||
When combined with a `UISref`, a `UISrefActive` toggles a CSS class on/off when its `UISref` is active/inactive. | ||
|
||
### Bootstrap mechanism (optional) | ||
|
||
Implement framework specific bootstrap requirements, if any. | ||
For example, UI-Router for Angular 1 and Angular 2 integrates with the ng1/ng2 Dependency Injection lifecycles. | ||
On the other hand, UI-Router for React uses a simple JavaScript based bootstrap, i.e., `new UIRouterReact().start();`. | ||
|
||
## Getting help | ||
|
||
[Create an issue](https://github.com/ui-router/core/issues) or contact us on [Gitter](https://gitter.im/angular-ui/ui-router). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// Karma configuration file | ||
var karma = require('karma'); | ||
|
||
module.exports = function (karma) { | ||
var config = { | ||
singleRun: true, | ||
autoWatch: false, | ||
autoWatchInterval: 0, | ||
|
||
// level of logging | ||
// possible values: LOG_DISABLE, LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG | ||
logLevel: "warn", | ||
// possible values: 'dots', 'progress' | ||
reporters: 'dots', | ||
colors: true, | ||
|
||
port: 8080, | ||
|
||
// base path, that will be used to resolve files and exclude | ||
basePath: '.', | ||
|
||
// Start these browsers, currently available: | ||
// Chrome, ChromeCanary, Firefox, Opera, Safari, PhantomJS | ||
browsers: ['PhantomJS'], | ||
|
||
frameworks: ['jasmine'], | ||
|
||
plugins: [ | ||
require('karma-webpack'), | ||
require('karma-sourcemap-loader'), | ||
require('karma-jasmine'), | ||
require('karma-phantomjs-launcher'), | ||
require('karma-chrome-launcher') | ||
], | ||
|
||
webpack: { | ||
devtool: 'inline-source-map', | ||
|
||
resolve: { | ||
modulesDirectories: ['node_modules'], | ||
extensions: ['', '.js', '.ts'] | ||
}, | ||
|
||
module: { | ||
loaders: [ | ||
{ test: /\.ts$/, loader: "awesome-typescript-loader?declaration=false&tsconfig=test/tsconfig.json" } | ||
] | ||
}, | ||
|
||
}, | ||
|
||
webpackMiddleware: { | ||
stats: { chunks: false }, | ||
}, | ||
|
||
files: ['test/index.js'], | ||
|
||
preprocessors: { | ||
'test/index.js': ['webpack', 'sourcemap'], | ||
}, | ||
|
||
}; | ||
|
||
karma.set(config); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
{ | ||
"name": "ui-router-dsr", | ||
"description": "UI-Router Deep State Redirect: redirect to the most recently activated child state", | ||
"version": "1.0.0", | ||
"scripts": { | ||
"clean": "shx rm -rf lib lib-esm", | ||
"build": "npm run clean && tsc && tsc -p tsconfig.esm.json", | ||
"test": "karma start", | ||
"watch": "run-p watch:*", | ||
"watch:buildjs": "tsc -w", | ||
"watch:test": "karma start --singleRun=false --autoWatch=true --autoWatchInterval=1", | ||
"debug": "karma start --singleRun=false --autoWatch=true --autoWatchInterval=1 --browsers=Chrome" | ||
}, | ||
"homepage": "https://ui-router.github.io", | ||
"contributors": [ | ||
{ | ||
"name": "Chris Thielen", | ||
"web": "https://github.com/christopherthielen" | ||
} | ||
], | ||
"maintainers": [ | ||
{ | ||
"name": "UIRouter Team", | ||
"web": "https://github.com/ui-router?tab=members" | ||
} | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/ui-router/dsr.git" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/ui-router/dsr/issues" | ||
}, | ||
"engines": { | ||
"node": ">=4.0.0" | ||
}, | ||
"jsnext:main": "lib-esm/index.js", | ||
"main": "lib/index.js", | ||
"typings": "lib/index.d.ts", | ||
"license": "MIT", | ||
"devDependencies": { | ||
"@types/jasmine": "^2.2.34", | ||
"@types/jquery": "^1.10.31", | ||
"@types/lodash": "^4.14.38", | ||
"awesome-typescript-loader": "^2.2.4", | ||
"conventional-changelog": "^1.1.0", | ||
"conventional-changelog-cli": "^1.1.1", | ||
"conventional-changelog-ui-router-core": "^1.3.0", | ||
"core-js": "^2.4.1", | ||
"jasmine-core": "^2.4.1", | ||
"karma": "^1.2.0", | ||
"karma-chrome-launcher": "~0.1.0", | ||
"karma-coverage": "^0.5.3", | ||
"karma-jasmine": "^1.0.2", | ||
"karma-phantomjs-launcher": "^1.0.2", | ||
"karma-script-launcher": "~0.1.0", | ||
"karma-sourcemap-loader": "^0.3.7", | ||
"karma-webpack": "^1.8.0", | ||
"lodash": "^4.16.6", | ||
"npm-run-all": "^3.1.1", | ||
"readline-sync": "^1.4.4", | ||
"shelljs": "^0.7.0", | ||
"shx": "^0.1.4", | ||
"tslint": "=2.5.0", | ||
"typescript": "^2.1.1", | ||
"ui-router-core": "^1.0.1", | ||
"webpack": "^1.13.3" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import { | ||
State, StateDeclaration, Param, UIRouter, RawParams, StateOrName, TargetState, Transition | ||
} from "ui-router-core"; | ||
export { deepStateRedirect }; | ||
|
||
declare module "ui-router-core" { | ||
interface StateDeclaration { | ||
dsr?: any; | ||
deepStateRedirect?: any; | ||
} | ||
} | ||
|
||
function deepStateRedirect($uiRouter: UIRouter): any { | ||
let $transitions = $uiRouter.transitionService; | ||
let $state = $uiRouter.stateService; | ||
|
||
$transitions.onRetain({ retained: getDsr }, recordDeepState); | ||
$transitions.onEnter({ entering: getDsr }, recordDeepState); | ||
$transitions.onBefore({ to: getDsr }, deepStateRedirect); | ||
|
||
function getDsr(state: StateDeclaration) { | ||
return state.deepStateRedirect || state.dsr; | ||
} | ||
|
||
function getConfig(state: StateDeclaration) { | ||
let dsrProp: any = getDsr(state); | ||
let propType: string = typeof dsrProp; | ||
if (propType === 'undefined') return; | ||
|
||
let params; | ||
let defaultTarget = propType === 'string' ? dsrProp : undefined; | ||
let fn: Function = propType === 'function' ? dsrProp : undefined; | ||
|
||
if (propType === 'object') { | ||
fn = dsrProp.fn; | ||
let defaultType = typeof dsrProp.default; | ||
if (defaultType === 'object') { | ||
defaultTarget = $state.target(dsrProp.default.state, dsrProp.default.params, dsrProp.default.options); | ||
} else if (defaultType === 'string') { | ||
defaultTarget = $state.target(dsrProp.default); | ||
} | ||
if (dsrProp.params === true) { | ||
params = function () { | ||
return true; | ||
} | ||
} else if (Array.isArray(dsrProp.params)) { | ||
params = function (param: Param) { | ||
return dsrProp.params.indexOf(param.id) !== -1; | ||
} | ||
} | ||
} | ||
|
||
fn = fn || ((transition, target) => target); | ||
|
||
return { params: params, default: defaultTarget, fn: fn }; | ||
} | ||
|
||
function paramsEqual(state: State, transParams: RawParams, schemaMatchFn?: (param?: Param) => boolean, negate = false) { | ||
schemaMatchFn = schemaMatchFn || (() => true); | ||
let schema = state.parameters({ inherit: true }).filter(schemaMatchFn); | ||
return function (redirect) { | ||
let equals = Param.equals(schema, redirect.triggerParams, transParams); | ||
return negate ? !equals : equals; | ||
} | ||
} | ||
|
||
function recordDeepState(transition, state) { | ||
let paramsConfig = getConfig(state).params; | ||
|
||
transition.promise.then(function () { | ||
let transTo = transition.to(); | ||
let transParams = transition.params(); | ||
let recordedDsrTarget = $state.target(transTo, transParams); | ||
|
||
if (paramsConfig) { | ||
state.$dsr = (state.$dsr || []).filter(paramsEqual(transTo.$$state(), transParams, undefined, true)); | ||
state.$dsr.push({ triggerParams: transParams, target: recordedDsrTarget }); | ||
} else { | ||
state.$dsr = recordedDsrTarget; | ||
} | ||
}); | ||
} | ||
|
||
function deepStateRedirect(transition: Transition) { | ||
let opts = transition.options(); | ||
if (opts['ignoreDsr'] || (opts.custom && opts.custom.ignoreDsr)) return; | ||
|
||
let config = getConfig(transition.to()); | ||
let redirect = getDeepStateRedirect(transition.to(), transition.params()); | ||
redirect = config.fn(transition, redirect); | ||
if (redirect && redirect.state() === transition.to()) return; | ||
|
||
return redirect | ||
} | ||
|
||
function getDeepStateRedirect(stateOrName: StateOrName, params: RawParams) { | ||
let state = $state.get(stateOrName); | ||
let dsrTarget, config = getConfig(state); | ||
let $$state = state.$$state(); | ||
|
||
if (config.params) { | ||
var predicate = paramsEqual($$state, params, config.params, false); | ||
let match = $$state['$dsr'] && $$state['$dsr'].filter(predicate)[0]; | ||
dsrTarget = match && match.target; | ||
} else { | ||
dsrTarget = $$state['$dsr']; | ||
} | ||
|
||
dsrTarget = dsrTarget || config.default; | ||
|
||
if (dsrTarget) { | ||
// merge original params with deep state redirect params | ||
let targetParams = Object.assign({}, params, dsrTarget.params()); | ||
dsrTarget = $state.target(dsrTarget.state(), targetParams, dsrTarget.options()); | ||
} | ||
|
||
return dsrTarget; | ||
} | ||
|
||
return { | ||
reset: function(state: StateOrName, params?: RawParams) { | ||
if (!state) { | ||
$state.get().forEach(state => delete state.$$state()['$dsr']); | ||
} else if (!params) { | ||
delete $state.get(state).$$state()['$dsr'] | ||
} else { | ||
var $$state = $state.get(state).$$state(); | ||
$$state['$dsr'] = $$state['$dsr'].filter(paramsEqual($$state, params, null, true)); | ||
} | ||
}, | ||
|
||
getRedirect: function (state: StateOrName, params?: RawParams) { | ||
return getDeepStateRedirect(state, params); | ||
} | ||
} | ||
} |
Oops, something went wrong.