├── .gitignore
├── LICENSE
├── README.md
├── bower.json
├── config
├── config.js
└── karma.conf.js
├── gulpfile.js
├── index.js
├── package.json
├── statehelper.js
├── statehelper.min.js
└── test
└── statehelperSpec.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | bower_components
3 | node_modules
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Mark Lagendijk
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | 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, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ui-router.stateHelper
2 | A helper module for AngularUI Router, which allows you to define your states as an object tree.
3 |
4 | ## Installation
5 | 1. `bower install angular-ui-router.stateHelper` or `npm install angular-ui-router.statehelper`
6 | 2. Reference `stateHelper.min.js`.
7 | 3. Add a dependency on `ui.router.stateHelper` in your app module.
8 |
9 | ## Usage
10 | ``` javascript
11 | // NOTE: when using child states with views you should make sure that its parent has a template containing a `ui-view` directive.
12 | angular.module('myApp', [ 'ui.router', 'ui.router.stateHelper' ])
13 | .config(function(stateHelperProvider){
14 | stateHelperProvider
15 | .state({
16 | name: 'root',
17 | templateUrl: 'root.html',
18 | children: [
19 | {
20 | name: 'contacts',
21 | template: '',
22 | children: [
23 | {
24 | name: 'list',
25 | templateUrl: 'contacts.list.html'
26 | }
27 | ]
28 | },
29 | {
30 | name: 'products',
31 | templateUrl: 'products.html',
32 | children: [
33 | {
34 | name: 'list',
35 | templateUrl: 'products.list.html'
36 | }
37 | ]
38 | }
39 | ]
40 | })
41 | .state({
42 | name: 'rootSibling',
43 | templateUrl: 'rootSibling.html'
44 | });
45 | });
46 | ```
47 |
48 | ## Options
49 |
50 | * keepOriginalNames (default _false_)
51 | * siblingTraversal (default _false_)
52 |
53 | ### Dot notation name conversion
54 | By default, all state names are converted to use ui-router's dot notation (e.g. `parentStateName.childStateName`).
55 | This can be disabled by calling `.state()` with options `options.keepOriginalNames = true`.
56 | For example:
57 |
58 | ``` javascript
59 | angular.module('myApp', ['ui.router', 'ui.router.stateHelper'])
60 | .config(function(stateHelperProvider){
61 | stateHelperProvider.state({
62 | name: 'root',
63 | templateUrl: 'root.html',
64 | children: [
65 | {
66 | name: 'contacts',
67 | templateUrl: 'contacts.html'
68 | }
69 | ]
70 | }, { keepOriginalNames: true });
71 | });
72 | ```
73 |
74 | ### Sibling Traversal
75 | Child states may optionally receive a reference to the name of the previous state (if available) and the next state (if available) in order to facilitate sequential state traversal as in the case of building wizards or multi-part forms. Enable this by setting `options.siblingTraversal = true`.
76 |
77 | Example:
78 | ``` javascript
79 |
80 | angular.module('myApp', ['ui.router', 'ui.router.stateHelper'])
81 | .config(function(stateHelperProvider){
82 | stateHelperProvider.state({
83 | name: 'resume',
84 | children: [
85 | {
86 | name: 'contactInfo',
87 | },
88 | {
89 | name: 'experience',
90 | },
91 | {
92 | name: 'education',
93 | }
94 | ]
95 | }, { siblingTraversal: true });
96 | });
97 |
98 | console.log($state.get('resume.contactInfo').previousSibling) // undefined
99 | console.log($state.get('resume.contactInfo').nextSibling) // 'resume.experience'
100 |
101 | console.log($state.get('resume.experience').previousSibling) // 'resume.contactInfo'
102 | console.log($state.get('resume.experience').nextSibling) // 'resume.education'
103 |
104 | console.log($state.get('resume.education').previousSibling) // 'resume.experience'
105 | console.log($state.get('resume.education').nextSibling) // undefined
106 | ```
107 |
108 |
109 | ## Name change
110 | Before 1.2.0 `.setNestedState` was used instead of `.state`. In 1.2.0 `setNestedState` was deprecated in favour of `.state`, and chaining was added. This makes it easier to switch between `$stateProvider` and `stateHelperProvider`.
111 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-ui-router.stateHelper",
3 | "version": "1.3.1",
4 | "description": "A helper module for AngularUI Router, which allows you to define your states as an object tree.",
5 | "main": "statehelper.js",
6 | "homepage": "https://github.com/marklagendijk/ui-router.stateHelper",
7 | "authors": [
8 | "Mark Lagendijk "
9 | ],
10 | "keywords": [
11 | "angular",
12 | "helper",
13 | "ui.router",
14 | "ui-router",
15 | "ui",
16 | "router"
17 | ],
18 | "license": "MIT",
19 | "ignore": [
20 | "**/.*",
21 | "node_modules",
22 | "package.json",
23 | "gulpfile.js",
24 | "bower_components",
25 | "test",
26 | "tests",
27 | "config"
28 | ],
29 | "dependencies": {
30 | "angular": ">=1.2.0",
31 | "angular-ui-router": "~0.2.11"
32 | },
33 | "devDependencies": {
34 | "angular-mocks": ">=1.2.8"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/config/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testFiles: [
3 | 'bower_components/angular/angular.js',
4 | 'bower_components/angular-mocks/angular-mocks.js',
5 | 'bower_components/angular-ui-router/release/angular-ui-router.js',
6 | 'statehelper.min.js',
7 | 'test/**/*.js'
8 | ]
9 | };
--------------------------------------------------------------------------------
/config/karma.conf.js:
--------------------------------------------------------------------------------
1 | var config = require('./config.js');
2 | module.exports = function(karmaConfig){
3 | karmaConfig.set({
4 |
5 | // base path, that will be used to resolve files and exclude
6 | basePath: '../',
7 |
8 |
9 | // frameworks to use
10 | frameworks: ['jasmine'],
11 |
12 |
13 | // list of files / patterns to load in the browser
14 | files: config.testFiles,
15 |
16 |
17 | // test results reporter to use
18 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
19 | reporters: ['progress'],
20 |
21 |
22 | // web server port
23 | port: 9876,
24 |
25 |
26 | // enable / disable colors in the output (reporters and logs)
27 | colors: true,
28 |
29 |
30 | // level of logging
31 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
32 | logLevel: karmaConfig.LOG_WARN,
33 |
34 |
35 | // enable / disable watching file and executing tests whenever any file changes
36 | autoWatch: true,
37 |
38 |
39 | // Start these browsers, currently available:
40 | // - Chrome
41 | // - ChromeCanary
42 | // - Firefox
43 | // - Opera (has to be installed with `npm install karma-opera-launcher`)
44 | // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`)
45 | // - PhantomJS
46 | // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`)
47 | browsers: ['Chrome'],
48 |
49 |
50 | // If browser does not capture in given timeout [ms], kill it
51 | captureTimeout: 60000,
52 |
53 |
54 | // Continuous Integration mode
55 | // if true, it capture browsers, run tests and exit
56 | singleRun: false
57 | });
58 | };
59 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require("gulp");
2 | var ngmin = require('gulp-ng-annotate');
3 | var uglify = require("gulp-uglify");
4 | var rename = require("gulp-rename");
5 | var karma = require("gulp-karma");
6 |
7 | var config = require('./config/config.js');
8 |
9 | gulp.task("test", ["minify"], function(){
10 | return gulp.src(config.testFiles)
11 | .pipe(karma({
12 | configFile: 'config/karma.conf.js',
13 | action: 'run'
14 | }));
15 | });
16 |
17 | gulp.task("minify", function(){
18 | return gulp.src("statehelper.js")
19 | .pipe(ngmin())
20 | .pipe(uglify())
21 | .pipe(rename("statehelper.min.js"))
22 | .pipe(gulp.dest("./"));
23 | });
24 |
25 | gulp.task("default", ["minify", "test"]);
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | require('./statehelper');
2 | module.exports = 'ui.router.stateHelper';
3 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-ui-router.statehelper",
3 | "version": "1.3.1",
4 | "description": "A helper module for AngularUI Router, which allows you to define your states as an object tree.",
5 | "main": "index.js",
6 | "dependencies": {},
7 | "devDependencies": {
8 | "gulp": "~3.8.0",
9 | "gulp-karma": "^0.0.4",
10 | "gulp-ng-annotate": "^0.3.3",
11 | "gulp-rename": "~0.2.1",
12 | "gulp-uglify": "~0.1.0",
13 | "jasmine-core": "^2.2.0",
14 | "karma": "^0.12.24",
15 | "karma-chrome-launcher": "^0.1.7",
16 | "karma-firefox-launcher": "~0.1.3",
17 | "karma-jasmine": "^0.3.5",
18 | "karma-phantomjs-launcher": "~0.1.1",
19 | "karma-requirejs": "~0.2.1",
20 | "karma-script-launcher": "~0.1.0"
21 | },
22 | "directories": {
23 | "test": "test"
24 | },
25 | "scripts": {
26 | "test": "gulp test"
27 | },
28 | "repository": {
29 | "type": "git",
30 | "url": "https://marklagendijk@github.com/marklagendijk/ui-router.stateHelper.git"
31 | },
32 | "keywords": [
33 | "angular",
34 | "helper",
35 | "ui.router",
36 | "ui-router",
37 | "ui",
38 | "router"
39 | ],
40 | "author": "Mark Lagendijk ",
41 | "license": "MIT",
42 | "bugs": {
43 | "url": "https://github.com/marklagendijk/ui-router.stateHelper/issues"
44 | },
45 | "homepage": "https://github.com/marklagendijk/ui-router.stateHelper"
46 | }
47 |
--------------------------------------------------------------------------------
/statehelper.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A helper module for AngularUI Router, which allows you to define your states as an object tree.
3 | * @author Mark Lagendijk
4 | * @license MIT
5 | */
6 | angular.module('ui.router.stateHelper', [ 'ui.router' ])
7 | .provider('stateHelper', ['$stateProvider', function($stateProvider){
8 | var self = this;
9 |
10 | /**
11 | * Recursively sets the states using $stateProvider.state.
12 | * Child states are defined via a `children` property.
13 | *
14 | * 1. Recursively calls itself for all descendant states, by traversing the `children` properties.
15 | * 2. Converts all the state names to dot notation, of the form `grandfather.father.state`.
16 | * 3. Sets `parent` property of the descendant states.
17 | *
18 | * @param {Object} state - A regular ui.router state object.
19 | * @param {Array} [state.children] - An optional array of child states.
20 | * @deprecated {Boolean} keepOriginalNames - An optional flag that prevents conversion
21 | * of names to dot notation if true. (use options.keepOriginalNames instead)
22 | * @param {Object} [options] - An optional options object.
23 | * @param {Boolean} [options.keepOriginalNames=false] An optional flag that
24 | * prevents conversion of names to dot notation if true.
25 | * @param {Boolean} [options.siblingTraversal=false] An optional flag that
26 | * adds `nextSibling` and `previousSibling` properties when enabled
27 | */
28 | this.state = function(state){
29 | var args = Array.prototype.slice.apply(arguments);
30 | var options = {
31 | keepOriginalNames: false,
32 | siblingTraversal: false
33 | };
34 |
35 | if (typeof args[1] === 'boolean') {
36 | options.keepOriginalNames = args[1];
37 | }
38 | else if (typeof args[1] === 'object') {
39 | angular.extend(options, args[1]);
40 | }
41 |
42 | if (!options.keepOriginalNames) {
43 | fixStateName(state);
44 | }
45 |
46 | $stateProvider.state(state);
47 |
48 | if(state.children && state.children.length){
49 | state.children.forEach(function(childState){
50 | childState.parent = state;
51 | self.state(childState, options);
52 | });
53 |
54 | if (options.siblingTraversal) {
55 | addSiblings(state);
56 | }
57 | }
58 |
59 | return self;
60 | };
61 |
62 | this.setNestedState = this.state;
63 |
64 | self.$get = angular.noop;
65 |
66 | /**
67 | * Converts the name of a state to dot notation, of the form `grandfather.father.state`.
68 | * @param state
69 | */
70 | function fixStateName(state){
71 | if(state.parent){
72 | state.name = (angular.isObject(state.parent) ? state.parent.name : state.parent) + '.' + state.name;
73 | }
74 | }
75 |
76 | function addSiblings(state) {
77 | state.children.forEach(function (childState, idx, array) {
78 | if (array[idx + 1]) {
79 | childState.nextSibling = array[idx + 1].name;
80 | }
81 | if (array[idx - 1]) {
82 | childState.previousSibling = array[idx - 1].name;
83 | }
84 | });
85 | }
86 | }]);
87 |
--------------------------------------------------------------------------------
/statehelper.min.js:
--------------------------------------------------------------------------------
1 | angular.module("ui.router.stateHelper",["ui.router"]).provider("stateHelper",["$stateProvider",function(e){function t(e){e.parent&&(e.name=(angular.isObject(e.parent)?e.parent.name:e.parent)+"."+e.name)}function a(e){e.children.forEach(function(e,t,a){a[t+1]&&(e.nextSibling=a[t+1].name),a[t-1]&&(e.previousSibling=a[t-1].name)})}var n=this;this.state=function(r){var i=Array.prototype.slice.apply(arguments),l={keepOriginalNames:!1,siblingTraversal:!1};return"boolean"==typeof i[1]?l.keepOriginalNames=i[1]:"object"==typeof i[1]&&angular.extend(l,i[1]),l.keepOriginalNames||t(r),e.state(r),r.children&&r.children.length&&(r.children.forEach(function(e){e.parent=r,n.state(e,l)}),l.siblingTraversal&&a(r)),n},this.setNestedState=this.state,n.$get=angular.noop}]);
--------------------------------------------------------------------------------
/test/statehelperSpec.js:
--------------------------------------------------------------------------------
1 | /* globals: beforeEach, describe, it, module, inject, expect */
2 | describe('ui-router.stateHelper', function(){
3 | var stateHelperProvider, $stateProvider, rootState, expectedState;
4 |
5 | var $injector;
6 |
7 | var stateHelperProviderState;
8 |
9 | beforeEach(module('ui.router.stateHelper', function(_stateHelperProvider_, _$stateProvider_){
10 | stateHelperProvider = _stateHelperProvider_;
11 | $stateProvider = _$stateProvider_;
12 | }));
13 |
14 | beforeEach(inject(function(_$injector_){
15 | $injector = _$injector_;
16 |
17 | rootState = {
18 | name: 'root',
19 | children: [
20 | {
21 | name: 'login',
22 | templateUrl: '/partials/views/login.html'
23 | },
24 | {
25 | name: 'backup',
26 | children: [
27 | {
28 | name: 'dashboard'
29 | }
30 | ]
31 | }
32 | ]
33 | };
34 |
35 | spyOn($stateProvider, 'state').and.callThrough();
36 | }));
37 |
38 | describe('.state', function(){
39 | beforeEach(inject(function(){
40 | expectedState = {
41 | name: 'root',
42 | children: [
43 | {
44 | name: 'root.login',
45 | // nextSibling: 'root.backup',
46 | templateUrl: '/partials/views/login.html'
47 | },
48 | {
49 | name: 'root.backup',
50 | // previousSibling: 'root.login',
51 | children: [
52 | {
53 | name: 'root.backup.dashboard'
54 | }
55 | ]
56 | }
57 | ]
58 | };
59 |
60 | stateHelperProviderState = stateHelperProvider.state(rootState, { siblingTraversal: false});
61 | }));
62 |
63 | it('should set each state', function(){
64 | expect($stateProvider.state.calls.count()).toBe(4);
65 | });
66 |
67 | it('should convert names to dot notation, set parent references', function(){
68 | // Since the states are objects which contain references to each other, we are testing the eventual
69 | // root state object (and not the root state object as it is passed to $stateProvider.$state).
70 | // Because of this we have to test everything at once
71 |
72 | expectedState.children[0].parent = expectedState;
73 | expectedState.children[1].parent = expectedState;
74 | expectedState.children[1].children[0].parent = expectedState.children[1];
75 |
76 | // expect($stateProvider.state.argsForCall[0][0]).toEqual(expectedState);
77 | expect($stateProvider.state.calls.argsFor(0)[0]).toEqual(expectedState);
78 | });
79 |
80 | it('should return itself to support chaining', function(){
81 | expect(stateHelperProviderState).toBe(stateHelperProvider);
82 | });
83 | });
84 |
85 | describe('.state with keepOriginalNames set to true', function(){
86 | beforeEach(inject(function(){
87 | expectedState = {
88 | name: 'root',
89 | children: [
90 | {
91 | name: 'login',
92 | // nextSibling: 'backup',
93 | templateUrl: '/partials/views/login.html'
94 | },
95 | {
96 | name: 'backup',
97 | // previousSibling: 'login',
98 | children: [
99 | {
100 | name: 'dashboard'
101 | }
102 | ]
103 | }
104 | ]
105 | };
106 |
107 | stateHelperProvider.state(rootState, { keepOriginalNames: true });
108 | }));
109 |
110 | it('should not convert names to dot notation, set parent references', function(){
111 | // Since the states are objects which contain references to each other, we are testing the eventual
112 | // root state object (and not the root state object as it is passed to $stateProvider.$state).
113 | // Because of this we have to test everything at once
114 |
115 | expectedState.children[0].parent = expectedState;
116 | expectedState.children[1].parent = expectedState;
117 | expectedState.children[1].children[0].parent = expectedState.children[1];
118 |
119 | expect($stateProvider.state.calls.argsFor(0)[0]).toEqual(expectedState);
120 | });
121 | });
122 |
123 | describe('.setNestedState', function(){
124 | it('should support .setNestedState as legacy name', function(){
125 | stateHelperProvider.setNestedState(rootState);
126 | expect($stateProvider.state.calls.count()).toBe(4);
127 | });
128 | });
129 |
130 | describe('children have references to siblings', function (){
131 | beforeEach(function () {
132 | stateHelperProvider.state(rootState, { siblingTraversal: true });
133 | });
134 |
135 | it('should see the next sibling', function (){
136 | var $state = $injector.get('$state');
137 | expect($state.get('root.login').nextSibling).toBeDefined();
138 | expect($state.get('root.login').nextSibling).toBe('root.backup');
139 | });
140 |
141 | it('should see the previous sibling', function (){
142 | var $state = $injector.get('$state');
143 | expect($state.get('root.backup').previousSibling).toBeDefined();
144 | expect($state.get('root.backup').previousSibling).toBe('root.login');
145 | });
146 | });
147 | });
148 |
149 |
150 |
151 |
--------------------------------------------------------------------------------