Skip to content

Commit

Permalink
feat(DSR): Port DSR to ui-router 1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
christopherthielen committed Nov 12, 2016
0 parents commit 3c56dfa
Show file tree
Hide file tree
Showing 10 changed files with 891 additions and 0 deletions.
21 changes: 21 additions & 0 deletions LICENSE
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.
80 changes: 80 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# UI-Router Core  [![Build Status](https://travis-ci.org/ui-router/core.svg?branch=master)](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).
65 changes: 65 additions & 0 deletions karma.conf.js
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);
};
69 changes: 69 additions & 0 deletions package.json
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"
}
}
136 changes: 136 additions & 0 deletions src/deepStateRedirect.ts
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);
}
}
}
Loading

0 comments on commit 3c56dfa

Please sign in to comment.