├── .editorconfig ├── .gitignore ├── .jscsrc ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── docs └── images │ ├── all.ui-router.png │ ├── legend.png │ └── ui.router.state.png ├── gulpfile.js ├── helpers.js ├── index.js ├── package-lock.json ├── package.json ├── templates ├── all.def ├── legend.def ├── module.def └── modules.def └── test ├── angular-architecture-graph.test.js ├── expected └── ui.router.dot ├── fixtures └── ui-router.js └── intern.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tmp 3 | lcov.info -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "excludeFiles": [ 3 | "node_modules/**", 4 | "gulpfile.js", 5 | "docs/**", 6 | "tmp/**", 7 | "templates/**", 8 | "test/fixtures/**" 9 | ], 10 | "disallowEmptyBlocks": true, 11 | "disallowKeywords": [ 12 | "with" 13 | ], 14 | "disallowMixedSpacesAndTabs": true, 15 | "disallowMultipleLineBreaks": true, 16 | "disallowMultipleLineStrings": true, 17 | "disallowNewlineBeforeBlockStatements": true, 18 | "disallowQuotedKeysInObjects": true, 19 | "disallowSpaceAfterObjectKeys": true, 20 | "disallowSpaceAfterPrefixUnaryOperators": true, 21 | "disallowSpaceBeforeBinaryOperators": [ 22 | "," 23 | ], 24 | "disallowSpaceBeforePostfixUnaryOperators": true, 25 | "disallowSpacesInAnonymousFunctionExpression": { 26 | "beforeOpeningRoundBrace": true 27 | }, 28 | "disallowSpacesInFunctionDeclaration": { 29 | "beforeOpeningRoundBrace": true 30 | }, 31 | "disallowSpacesInFunctionExpression": { 32 | "beforeOpeningRoundBrace": true 33 | }, 34 | "disallowSpacesInNamedFunctionExpression": { 35 | "beforeOpeningRoundBrace": true 36 | }, 37 | "disallowSpacesInsideArrayBrackets": "all", 38 | "disallowSpacesInsideObjectBrackets": "all", 39 | "disallowSpacesInsideParentheses": true, 40 | "disallowTrailingComma": true, 41 | "disallowTrailingWhitespace": true, 42 | "disallowYodaConditions": true, 43 | "maximumLineLength": { 44 | "allowComments": true, 45 | "allowRegex": true, 46 | "value": 160 47 | }, 48 | "requireBlocksOnNewline": 1, 49 | "requireCamelCaseOrUpperCaseIdentifiers": true, 50 | "requireCapitalizedConstructors": true, 51 | "requireCommaBeforeLineBreak": true, 52 | "requireCurlyBraces": [ 53 | "if", 54 | "else", 55 | "for", 56 | "while", 57 | "do", 58 | "try", 59 | "catch" 60 | ], 61 | "requireDotNotation": true, 62 | "requireLineFeedAtFileEnd": true, 63 | "requireMultipleVarDecl": "onevar", 64 | "requireOperatorBeforeLineBreak": true, 65 | "requireParenthesesAroundIIFE": true, 66 | "requireSpaceAfterBinaryOperators": true, 67 | "requireSpaceAfterKeywords": [ 68 | "if", 69 | "else", 70 | "for", 71 | "while", 72 | "do", 73 | "switch", 74 | "case", 75 | "return", 76 | "try", 77 | "catch", 78 | "typeof" 79 | ], 80 | "requireSpaceBeforeBinaryOperators": [ 81 | "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", 82 | "&=", "|=", "^=", "+=", 83 | 84 | "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&", 85 | "|", "^", "&&", "||", "===", "==", ">=", 86 | "<=", "<", ">", "!=", "!==" 87 | ], 88 | "requireSpaceBeforeBlockStatements": true, 89 | "requireSpacesInConditionalExpression": true, 90 | "requireSpacesInFunctionExpression": { 91 | "beforeOpeningCurlyBrace": true 92 | }, 93 | "safeContextKeyword": "_this", 94 | "validateIndentation": 4, 95 | "validateJSDoc": { 96 | "checkParamNames": true, 97 | "requireParamTypes": true 98 | }, 99 | "validateQuoteMarks": "'" 100 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | .travis.yml 3 | gulpfile.js 4 | docs -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | before_install: 5 | - sudo apt-get install libcairo2-dev libjpeg8-dev libpango1.0-dev libgif-dev build-essential g++ 6 | - sudo apt-get install graphviz 7 | install: 8 | - npm install -g gulp 9 | - npm install 10 | script: 11 | - npm run test 12 | after_success: 13 | - npm run coveralls 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## 0.0.6 - 2018-06-12 5 | ### Features 6 | - largeGraph option 7 | 8 | ## 0.0.5 - 2016-10-28 9 | ### Features 10 | - support .component API 11 | ### Changed 12 | - colorScheme move from 'paired12' to 'set312' 13 | - all shapes are filled with borders 14 | - use three different shapes for filters, directives and components 15 | 16 | ## 0.0.4 - 2016-05-30 17 | ### Features 18 | - filter modules with prefixes. 19 | 20 | ## 0.0.3 - 2015-03-17 21 | ### Fixed 22 | - issues on Linux. 23 | 24 | ## 0.0.2 - 2015-02-26 25 | ### Changed 26 | - README documentation cleaner. 27 | 28 | ### Fixed 29 | - issues on Windows. 30 | 31 | ## 0.0.1 - 2015-02-18 32 | - Initial release 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gulp-angular-architecture-graph 2 | [![Dependency Status][depstat-image]][depstat-url] [![devdependencies][devdepstat-image]][devdepstat-url] [![Build status][build-image]][build-url] [![NPM version][npm-image]][npm-url] [![Coverage Status][coverage-image]][coverage-url] [![Codacy Badge][codacy-image]][codacy-url] 3 | 4 | [depstat-url]: https://david-dm.org/vogloblinsky/gulp-angular-architecture-graph 5 | [depstat-image]: https://david-dm.org/vogloblinsky/gulp-angular-architecture-graph.svg 6 | [devdepstat-url]: https://david-dm.org/vogloblinsky/gulp-angular-architecture-graph#info=devDependencies 7 | [devdepstat-image]: https://david-dm.org/vogloblinsky/gulp-angular-architecture-graph/dev-status.png 8 | [build-url]: https://travis-ci.org/vogloblinsky/gulp-angular-architecture-graph 9 | [build-image]: https://travis-ci.org/vogloblinsky/gulp-angular-architecture-graph.svg?branch=master 10 | [npm-url]: http://badge.fury.io/js/gulp-angular-architecture-graph 11 | [npm-image]: https://badge.fury.io/js/gulp-angular-architecture-graph.svg 12 | [coverage-url]: https://coveralls.io/r/vogloblinsky/gulp-angular-architecture-graph?branch=master 13 | [coverage-image]: https://coveralls.io/repos/vogloblinsky/gulp-angular-architecture-graph/badge.svg?branch=master 14 | [codacy-url]: https://www.codacy.com/public/vincentogloblinsky/gulp-angular-architecture-graph 15 | [codacy-image]: https://www.codacy.com/project/badge/8d00b4d7b46a465fbf8a79dea9013d39 16 | 17 | Generate modules dependencies graph. 18 | Port of https://github.com/lucalanca/grunt-angular-architecture-graph 19 | 20 | ![angular-ui/ui-router state module](https://raw.githubusercontent.com/vogloblinsky/gulp-angular-architecture-graph/master/docs/images/ui.router.state.png "angular-ui/ui-router Dependencies graph") 21 | 22 | ## Getting Started 23 | 24 | ### 0 - Install graphviz 25 | 26 | #### OS X 27 | 28 | - [**graphviz**](http://www.graphviz.org/) 29 | 30 | if running OS X and using homebrew, simply execute: 31 | 32 | ``` 33 | brew install graphviz 34 | ``` 35 | 36 | #### Windows 7 37 | 38 | The windows installer of graphviz: [graphviz-X.XX.msi](http://www.graphviz.org/Download..php) 39 | 40 | **Remember** to set the graphviz bin directory in your PATH. e.g. ```C:\Program Files (x86)\GraphvizX.XX\bin```. 41 | 42 | #### Manjaro 0.8.11 (arch linux) 43 | 44 | Install via `yaourt` the graphviz package e.g.: `yaourt graphviz`. 45 | 46 | ### 1 - Install plugin in your project 47 | 48 | This plugin requires Gulp `~3.8.7` 49 | 50 | If you haven't used [Gulp](http://gulpjs.com/) before, be sure to check out the [Getting Started](https://github.com/gulpjs/gulp/blob/master/docs/getting-started.md) guide, as it explains how to create a [Gulpfile](https://github.com/gulpjs/gulp#sample-gulpfilejs) as well as install and use Gulp plugins. Once you're familiar with that process, you may install this plugin with this command: 51 | 52 | ```shell 53 | npm install --save-dev gulp-angular-architecture-graph 54 | ``` 55 | 56 | Once the plugin has been installed, it may be injected inside your Gulpfile with this line of JavaScript: 57 | 58 | ```js 59 | var ngGraph = require('gulp-angular-architecture-graph'); 60 | 61 | gulp.task('default', function(){ 62 | gulp.src('src/js/*.js', '!src/js/external/*') 63 | .pipe(ngGraph({ 64 | dest: 'architecture' 65 | })); 66 | }); 67 | ``` 68 | 69 | execute the task and the diagrams will be in the output folder, in this example it is in the folder ```architecture```. 70 | 71 | ## Demo 72 | 73 | ![legend](https://raw.githubusercontent.com/vogloblinsky/gulp-angular-architecture-graph/master/docs/images/legend.png "Generated Graph Legend") 74 | 75 | - ui-router overview diagram 76 | ![angular-ui/ui-router overview](https://raw.githubusercontent.com/vogloblinsky/gulp-angular-architecture-graph/master/docs/images/all.ui-router.png "angular-ui/ui-router Dependencies graph") 77 | 78 | ## Options 79 | 80 | ### options.dest 81 | Type: `String` 82 | Default value: `architecture` 83 | 84 | A string value that define the output directory. 85 | 86 | ```js 87 | dest: 'out' 88 | ``` 89 | 90 | ### options.hideAngularServices 91 | Type: `Boolean` 92 | Default value: `true` 93 | 94 | A boolean value that shows angular services (e.g. $http, $q) as dependencies when set to false. 95 | 96 | ```js 97 | hideAngularServices: false 98 | ``` 99 | 100 | ### options.largeGraph 101 | Type: `Boolean` 102 | Default value: `false` 103 | 104 | A boolean value that switch the rendering engine from dot for small projects to sdfp for large projects. 105 | dot engine render horizontal graph, sfdp render square graph. 106 | 107 | ```js 108 | largeGraph: false 109 | ``` 110 | 111 | ### options.shapeModules 112 | Type: `String` 113 | Default value: `component` 114 | 115 | A string value that allows you to change the default shape used for 116 | 117 | * module 118 | 119 | nodes. 120 | 121 | ```js 122 | shapeModules: 'triangle' 123 | ``` 124 | 125 | ### options.shapeFactories 126 | Type: `String` 127 | Default value: `ellipse` 128 | 129 | A string value that allows you to change the default shape used for 130 | 131 | * Provider 132 | * Controller 133 | * Service 134 | * Factory 135 | * Injected Service 136 | 137 | nodes. 138 | 139 | ```js 140 | shapeFactories: 'house' 141 | ``` 142 | 143 | ### options.shapeFilters 144 | Type: `String` 145 | Default value: `rectangle` 146 | 147 | A string value that allows you to change the default shape used for filters nodes. 148 | 149 | ```js 150 | shapeFilters: 'house' 151 | ``` 152 | 153 | ### options.shapeDirectives 154 | Type: `String` 155 | Default value: `note` 156 | 157 | A string value that allows you to change the default shape used for directives nodes. 158 | 159 | ```js 160 | shapeDirectives: 'trapezium' 161 | ``` 162 | 163 | ### options.shapeComponents 164 | Type: `String` 165 | Default value: `folder` 166 | 167 | A string value that allows you to change the default shape used for components nodes. 168 | 169 | ```js 170 | shapeComponents: 'trapezium' 171 | ``` 172 | 173 | Available graphviz shapes are shown [here](http://www.graphviz.org/doc/info/shapes.html) 174 | 175 | ### options.colorScheme 176 | Type: `String` 177 | Default value: `set312` 178 | 179 | A string value that allows you to change the graph colour scheme. You currently need to choose a scheme with at least 9 colours to ensure that all nodes 180 | are coloured. Colour schemes which include white or very pale colours will cause some nodes to be hard to see or appear invisible against the white background 181 | 182 | ```js 183 | colorScheme: 'set19' 184 | ``` 185 | 186 | Available graphviz colour schemes are shown [here](http://www.graphviz.org/doc/info/colors.html) 187 | 188 | ### options.filterModulesPrefixes 189 | Type: `Array` 190 | Default value: `[]` 191 | 192 | An array containing strings of module name prefixes to remove from graph 193 | ```js 194 | filterModulesPrefixes: ['ng', 'ui', 'formly', 'angular'] 195 | ``` 196 | 197 | ## List of Contributors 198 | 199 | - vogloblinsky (current maintainer) 200 | - lucalanca (initial creator of grunt version) 201 | - carlo-colombo (initial creator of the project) 202 | - manekinekko 203 | 204 | ## Release notes 205 | 206 | [CHANGELOG.md](https://github.com/vogloblinsky/gulp-angular-architecture-graph/blob/master/CHANGELOG.md) 207 | -------------------------------------------------------------------------------- /docs/images/all.ui-router.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vogloblinsky/gulp-angular-architecture-graph/04d0b53be4297050aa9d1d240f9db3ea5e9651ca/docs/images/all.ui-router.png -------------------------------------------------------------------------------- /docs/images/legend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vogloblinsky/gulp-angular-architecture-graph/04d0b53be4297050aa9d1d240f9db3ea5e9651ca/docs/images/legend.png -------------------------------------------------------------------------------- /docs/images/ui.router.state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vogloblinsky/gulp-angular-architecture-graph/04d0b53be4297050aa9d1d240f9db3ea5e9651ca/docs/images/ui.router.state.png -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | clean = require('gulp-clean'), 3 | ngGraph = require('./index.js'), 4 | 5 | tmpDir = 'tmp'; 6 | 7 | gulp.task('clean', function() { 8 | return gulp.src(tmpDir, {read: false}) 9 | .pipe(clean()); 10 | }); 11 | 12 | gulp.task('default', ['clean'], function() { 13 | gulp.src(['test/fixtures/ui-router.js']) 14 | .pipe(ngGraph({ 15 | dest: tmpDir 16 | })); 17 | }); 18 | -------------------------------------------------------------------------------- /helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var architectureGraph = require('angular-architecture-graph'), 4 | dot = require('dot'), 5 | os = require('os'), 6 | path = require('path'), 7 | fs = require('fs-extra'), 8 | Q = require('q'), 9 | file = require('file'), 10 | process = require('child_process'); 11 | 12 | dot.templateSettings.strip = false; 13 | 14 | module.exports = function(gutil) { 15 | 16 | var basePath = 'node_modules/gulp-angular-architecture-graph/'; 17 | 18 | if (!fs.existsSync(basePath)) { 19 | basePath = ''; 20 | } 21 | 22 | var templateFiles = { 23 | legend: '', 24 | all: '', 25 | modules: '', 26 | module: '' 27 | }, 28 | 29 | readFile = function(file) { 30 | var deferred = Q.defer(); 31 | fs.readFile(file, 'utf8', function(err, data) { 32 | if (err) { 33 | deferred.reject(err); 34 | } 35 | deferred.resolve(data); 36 | }); 37 | return deferred.promise; 38 | }, 39 | 40 | writeToFile = function(filename, data) { 41 | var deferred = Q.defer(); 42 | fs.outputFile(filename, data, function(err) { 43 | if (err) { 44 | console.log(err); 45 | } else { 46 | deferred.resolve(); 47 | } 48 | }); 49 | return deferred.promise; 50 | }, 51 | 52 | readTemplateFiles = function() { 53 | return Q.all([ 54 | readFile(basePath + 'templates/legend.def'), 55 | readFile(basePath + 'templates/all.def'), 56 | readFile(basePath + 'templates/modules.def'), 57 | readFile(basePath + 'templates/module.def') 58 | ]); 59 | }, 60 | 61 | cleanPath = function(pathParam) { 62 | return (os.platform() === 'win32' || os.platform() === 'win64') ? pathParam.replace('/', '\\') : pathParam; 63 | }, 64 | 65 | templates = { 66 | legendTemplate: {}, 67 | allTemplate: {}, 68 | modulesTemplate: {}, 69 | moduleTemplate: {} 70 | }, 71 | 72 | preprocessTemplates = function(options) { 73 | var deferred = Q.defer(); 74 | readTemplateFiles().then(function(datas) { 75 | 76 | templateFiles.legend = datas[0]; 77 | templateFiles.all = datas[1]; 78 | templateFiles.modules = datas[2]; 79 | templateFiles.module = datas[3]; 80 | 81 | // Replace placeholders. 82 | [ 83 | 'legend', 84 | 'all', 85 | 'modules', 86 | 'module' 87 | ].forEach(function(file) { 88 | templateFiles[file] = templateFiles[file] 89 | .replace(/\{1\}/g, options.shapeModules) 90 | .replace(/\{2\}/g, options.shapeFactories) 91 | .replace(/\{3\}/g, options.shapeDirectives) 92 | .replace(/\{4\}/g, options.shapeComponents) 93 | .replace(/\{5\}/g, options.shapeFilters) 94 | .replace(/\{scheme\}/g, options.colorScheme); 95 | }); 96 | 97 | // Prime the templates object. 98 | templates.legendTemplate = dot.template(templateFiles.legend); 99 | templates.allTemplate = dot.template(templateFiles.all); 100 | templates.modulesTemplate = dot.template(templateFiles.modules); 101 | templates.moduleTemplate = dot.template(templateFiles.module); 102 | 103 | deferred.resolve(); 104 | }); 105 | return deferred.promise; 106 | }, 107 | 108 | preprocessOutputDirs = function(options) { 109 | var deferred = Q.defer(); 110 | fs.mkdirs(options.dest, function() { 111 | fs.mkdir(options.dest + '/dot', function() { 112 | fs.mkdir(options.dest + '/png', function() { 113 | fs.mkdir(options.dest + '/png/modules', function() { 114 | deferred.resolve(); 115 | }); 116 | }); 117 | }); 118 | }); 119 | return deferred.promise; 120 | }, 121 | 122 | parseSrcFile = function(file) { 123 | return { 124 | id: path.basename(file.path), 125 | text: file.contents.toString('utf8') 126 | }; 127 | }, 128 | 129 | analyseFiles = function(file, options) { 130 | var graph = architectureGraph(file, options), 131 | i = 0, 132 | _tmp = graph.angular.modules, 133 | len = _tmp.length; 134 | if (!Array.prototype.remove) { 135 | Array.prototype.remove = function(vals, all) { 136 | var i, removedItems = []; 137 | if (!Array.isArray(vals)) vals = [vals]; 138 | for (var j = 0; j < vals.length; j++) { 139 | if (all) { 140 | for (i = this.length; i--;) { 141 | if (this[i] === vals[j]) removedItems.push(this.splice(i, 1)); 142 | } 143 | } else { 144 | i = this.indexOf(vals[j]); 145 | if (i > -1) removedItems.push(this.splice(i, 1)); 146 | } 147 | } 148 | return removedItems; 149 | }; 150 | } 151 | 152 | if (options.filterModulesPrefixes.length > 0) { 153 | for (i; i < len; i++) { 154 | var j = 0, 155 | leng = _tmp[i].modules.length, 156 | name = '', 157 | prefixesToRemove = []; 158 | for (j; j < leng; j++) { 159 | name = _tmp[i].modules[j]; 160 | var k = 0, 161 | lengt = options.filterModulesPrefixes.length; 162 | for (k; k < lengt; k++) { 163 | if (name.indexOf(options.filterModulesPrefixes[k]) === 0) { 164 | prefixesToRemove.push(name); 165 | } 166 | } 167 | } 168 | if (prefixesToRemove.length > 0) { 169 | _tmp[i].modules.remove(prefixesToRemove, true); 170 | } 171 | } 172 | 173 | } 174 | 175 | return graph.angular; 176 | }, 177 | 178 | generateGraphFiles = function(angular, config) { 179 | var deferred = Q.defer(); 180 | generateLegendGraph(config); 181 | generateAllGraph(angular, config); 182 | generateModulesGraph(angular, config); 183 | angular.modules.forEach(function(module) { 184 | generateModuleGraph(module, config); 185 | }); 186 | deferred.resolve(); 187 | return deferred.promise; 188 | }, 189 | 190 | /** 191 | * Graphical functions 192 | */ 193 | 194 | generateLegendGraph = function(config) { 195 | var legendResult = templates.legendTemplate(); 196 | writeToFile(config.dest + '/dot/legend.dot', legendResult); 197 | }, 198 | 199 | generateAllGraph = function(angular, config) { 200 | var allResult = templates.allTemplate({ 201 | modules: angular.modules 202 | }); 203 | writeToFile(config.dest + '/dot/all.dot', allResult); 204 | }, 205 | 206 | generateModulesGraph = function(angular, config) { 207 | var modulesResult = templates.modulesTemplate({ 208 | modules: angular.modules 209 | }); 210 | writeToFile(config.dest + '/dot/modules.dot', modulesResult); 211 | }, 212 | 213 | generateModuleGraph = function(module, config) { 214 | var moduleResult = templates.moduleTemplate(module); 215 | writeToFile(config.dest + '/dot/modules/' + module.name + '.dot', moduleResult); 216 | }, 217 | 218 | renderDotFiles = function(files, config) { 219 | var deferred = Q.defer(), 220 | slash = (os.platform() === 'win32' || os.platform() === 'win64') ? '\\' : '/', 221 | dotsFolder = cleanPath(config.dest) + slash + 'dot', 222 | pngsFolder = cleanPath(config.dest) + slash + 'png'; 223 | //Loop through all dot files generated, and generated a map 'dot':'png' 224 | file.walk(dotsFolder, function(ie, dirPath, dirs, files) { 225 | var i = 0, 226 | len = (files || []).length; 227 | 228 | //TODO : handle subdirectories 229 | 230 | for (i; i < len; i++) { 231 | var engine = 'dot'; 232 | if (typeof config.largeGraph !== 'undefined' && config.largeGraph) { 233 | engine = 'sfdp'; 234 | } 235 | var finalName = files[i].replace(dotsFolder, pngsFolder).replace('.dot', '.png'), 236 | ls = process.spawn(engine, [ 237 | '-Tpng', 238 | files[i], 239 | '-o', 240 | finalName, 241 | '-Goverlap=false', 242 | '-Gsplines=true' 243 | ]); 244 | 245 | ls.stdout.on('data', function(data) { 246 | console.log('stdout: ' + data); 247 | }); 248 | 249 | ls.stderr.on('data', function(data) { 250 | console.log('stderr: ' + data); 251 | }); 252 | 253 | ls.on('close', function(code) { 254 | //Done 255 | }); 256 | } 257 | 258 | deferred.resolve(); 259 | }); 260 | 261 | return deferred.promise; 262 | }; 263 | 264 | return { 265 | preprocessTemplates: preprocessTemplates, 266 | preprocessOutputDirs: preprocessOutputDirs, 267 | parseSrcFile: parseSrcFile, 268 | analyseFiles: analyseFiles, 269 | generateGraphFiles: generateGraphFiles, 270 | renderDotFiles: renderDotFiles 271 | }; 272 | }; 273 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var through = require('through2'), 4 | gutil = require('gulp-util'), 5 | Helpers = require('./helpers')(gutil), 6 | _ = require('lodash'), 7 | 8 | gulpAngularGraph = function(options) { 9 | var _options = { 10 | hideAngularServices: true, 11 | largeGraph: false, 12 | shapeModules: 'component', 13 | shapeFactories: 'ellipse', 14 | shapeFilters: 'rectangle', 15 | shapeDirectives: 'note', 16 | shapeComponents: 'folder', 17 | colorScheme: 'set312', 18 | dest: 'architecture', 19 | filterModulesPrefixes: [] 20 | }, 21 | 22 | _files = []; 23 | 24 | _.merge(_options, options); 25 | 26 | return through.obj(function(file, enc, cb) { 27 | _files.push(Helpers.parseSrcFile(file)); 28 | cb(); 29 | }, function(cb) { 30 | var codebaseArchitecture = Helpers.analyseFiles(_files, _options); 31 | 32 | Helpers.preprocessOutputDirs(_options).then(function() { 33 | Helpers.preprocessTemplates(_options).then(function() { 34 | Helpers.generateGraphFiles(codebaseArchitecture, _options).then(function() { 35 | Helpers.renderDotFiles(_files, _options).then(function() { 36 | cb(); 37 | }); 38 | }); 39 | }); 40 | }); 41 | }); 42 | } 43 | 44 | module.exports = gulpAngularGraph; 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-angular-architecture-graph", 3 | "version": "0.0.6", 4 | "description": "Generate modules dependencies graph", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "gulp && intern-client config=test/intern reporters=lcov", 8 | "coveralls": "cat lcov.info | ./node_modules/coveralls/bin/coveralls.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/vogloblinsky/gulp-angular-architecture-graph.git" 13 | }, 14 | "keywords": [ 15 | "gulp", 16 | "gulpplugin", 17 | "angularjs", 18 | "graph" 19 | ], 20 | "author": "Vincent Ogloblinsky ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/vogloblinsky/gulp-angular-architecture-graph/issues" 24 | }, 25 | "homepage": "https://github.com/vogloblinsky/gulp-angular-architecture-graph", 26 | "engines": { 27 | "node": ">=0.10" 28 | }, 29 | "devDependencies": { 30 | "gulp": "^3.9.1", 31 | "gulp-clean": "^0.4.0", 32 | "coveralls": "^2.11.9", 33 | "intern": "^3.3.1" 34 | }, 35 | "dependencies": { 36 | "angular-architecture-graph": "^0.2.4", 37 | "dot": "^1.0.3", 38 | "through2": "^2.0.1", 39 | "gulp-util": "^3.0.7", 40 | "map-stream": "0.0.7", 41 | "q": "^1.4.1", 42 | "fs-extra": "^6.0.1", 43 | "lodash": "^4.17.10", 44 | "graphviz": "0.0.8", 45 | "file": "^0.2.2" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /templates/all.def: -------------------------------------------------------------------------------- 1 | digraph dependencies { 2 | node[shape="component",style="filled",colorscheme={scheme}] 3 | 4 | node[shape="{1}"] 5 | {{~it.modules :module}} 6 | "{{= module.name }}"[label="{{= module.name }}", fillcolor=1] 7 | {{~}} 8 | 9 | {{~it.modules :module}} 10 | {{~module.modules :dependency}} 11 | "{{= module.name }}" -> "{{= dependency }}" 12 | {{~}} 13 | {{~}} 14 | 15 | node[shape="{2}"] 16 | 17 | 18 | {{~it.modules :module}} 19 | {{~module.providers :provider}} 20 | "{{= provider.name }}"[label="{{= provider.name }}", fillcolor=2] 21 | {{~}} 22 | 23 | {{~module.controllers :controller}} 24 | "{{= controller.name }}"[label="{{= controller.name }}", fillcolor=4] 25 | {{~}} 26 | 27 | {{~module.services :service}} 28 | "{{= service.name }}"[label="{{= service.name }}", fillcolor=5] 29 | {{~}} 30 | 31 | {{~module.factories :factory}} 32 | "{{= factory.name }}"[label="{{= factory.name }}", fillcolor=6] 33 | {{~}} 34 | 35 | 36 | node[shape="{5}"] 37 | {{~module.filters :filter}} 38 | "{{= filter.name }}"[label="{{= filter.name }}", fillcolor=7] 39 | {{~}} 40 | 41 | node[shape="{3}"] 42 | {{~module.directives :directive}} 43 | "{{= directive.name }}"[label="{{= directive.name }}", fillcolor=3] 44 | {{~}} 45 | 46 | node[shape="{4}"] 47 | {{~module.components :component}} 48 | "{{= component.name }}"[label="{{= component.name }}", fillfillcolor=8, style="filled"] 49 | {{~}} 50 | 51 | 52 | node[shape="{2}"] 53 | {{~module.providers :provider}} 54 | "{{= module.name }}" -> "{{= provider.name }}" 55 | {{~}} 56 | 57 | {{~module.directives :directive}} 58 | "{{= module.name }}" -> "{{= directive.name }}" 59 | {{~}} 60 | 61 | {{~module.components :component}} 62 | "{{= module.name }}" -> "{{= component.name }}" 63 | {{~}} 64 | 65 | {{~module.controllers :controller}} 66 | "{{= module.name }}" -> "{{= controller.name }}" 67 | {{~}} 68 | 69 | {{~module.services :service}} 70 | "{{= module.name }}" -> "{{= service.name }}" 71 | {{~}} 72 | 73 | {{~module.factories :factory}} 74 | "{{= module.name }}" -> "{{= factory.name }}" 75 | {{~}} 76 | 77 | {{~module.filters :filter}} 78 | "{{= module.name }}" -> "{{= filter.name }}" 79 | {{~}} 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | node[shape="{2}",style="filled", fillcolor=11] 92 | {{~module.providers :provider}} 93 | {{~provider.deps :dep}} 94 | "{{= dep }}"[label="{{= dep }}"] 95 | "{{= dep }}" -> "{{= provider.name }}" 96 | {{~}} 97 | {{~}} 98 | 99 | {{~module.directives :directive}} 100 | {{~directive.deps :dep}} 101 | "{{= dep }}"[label="{{= dep }}"] 102 | "{{= dep }}" -> "{{= directive.name }}" 103 | {{~}} 104 | {{~}} 105 | 106 | {{~module.components :component}} 107 | {{~component.deps :dep}} 108 | "{{= dep }}"[label="{{= dep }}"] 109 | "{{= dep }}" -> "{{= component.name }}" 110 | {{~}} 111 | {{~}} 112 | 113 | {{~module.controllers :controller}} 114 | {{~controller.deps :dep}} 115 | "{{= dep }}"[label="{{= dep }}"] 116 | "{{= dep }}" -> "{{= controller.name }}" 117 | {{~}} 118 | {{~}} 119 | 120 | {{~module.services :service}} 121 | {{~service.deps :dep}} 122 | "{{= dep }}"[label="{{= dep }}"] 123 | "{{= dep }}" -> "{{= service.name }}" 124 | {{~}} 125 | {{~}} 126 | 127 | {{~module.factories :factory}} 128 | {{~factory.deps :dep}} 129 | "{{= dep }}"[label="{{= dep }}"] 130 | "{{= dep }}" -> "{{= factory.name }}" 131 | {{~}} 132 | {{~}} 133 | 134 | {{~module.filters :filter}} 135 | {{~filter.deps :dep}} 136 | "{{= dep }}"[label="{{= dep }}"] 137 | "{{= dep }}" -> "{{= filter.name }}" 138 | {{~}} 139 | {{~}} 140 | 141 | {{~}} 142 | } 143 | -------------------------------------------------------------------------------- /templates/legend.def: -------------------------------------------------------------------------------- 1 | digraph dependencies { 2 | node[shape="component",style="filled",colorscheme={scheme}] 3 | 4 | 5 | node[shape="{1}"] 6 | "legend.module"[label="Modules", fillcolor=1] 7 | node[shape="{2}"] 8 | "legend.provider" [label="Providers", fillcolor=2] 9 | "legend.controllers"[label="Controllers", fillcolor=4] 10 | "legend.services" [label="Services", fillcolor=5] 11 | "legend.factories" [label="Factories", fillcolor=6] 12 | "legend.injected" [label="Injected Services", fillcolor=11] 13 | node[shape="{3}"] 14 | "legend.directives" [label="Directives", fillcolor=3] 15 | node[shape="{4}"] 16 | "legend.components" [label="Components", fillcolor=8] 17 | node[shape="{5}"] 18 | "legend.filters" [label="Filters", fillcolor=7] 19 | 20 | } 21 | -------------------------------------------------------------------------------- /templates/module.def: -------------------------------------------------------------------------------- 1 | digraph dependencies { 2 | node[shape="component",style="filled",colorscheme={scheme}] 3 | 4 | 5 | node[shape="{1}"] 6 | "{{= it.name }}"[label="{{= it.name }}", fillcolor=1] 7 | 8 | {{~it.modules :dep}} 9 | "{{= dep }}"[label="{{= dep }}", fillcolor=1] 10 | "{{= dep }}" -> "{{= it.name }}" 11 | {{~}} 12 | 13 | 14 | node[shape="{2}"] 15 | {{~it.providers :provider}} 16 | "{{= provider.name }}"[label="{{= provider.name }}", fillcolor=2] 17 | "{{= it.name }}" -> "{{= provider.name }}" 18 | {{~}} 19 | 20 | 21 | {{~it.controllers :controller}} 22 | "{{= controller.name }}"[label="{{= controller.name }}", fillcolor=4] 23 | "{{= it.name }}" -> "{{= controller.name }}" 24 | {{~}} 25 | 26 | {{~it.services :service}} 27 | "{{= service.name }}"[label="{{= service.name }}", fillcolor=5] 28 | "{{= it.name }}" -> "{{= service.name }}" 29 | {{~}} 30 | 31 | {{~it.factories :factory}} 32 | "{{= factory.name }}"[label="{{= factory.name }}", fillcolor=6] 33 | "{{= it.name }}" -> "{{= factory.name }}" 34 | {{~}} 35 | 36 | node[shape="{5}"] 37 | {{~it.filters :filter}} 38 | "{{= filter.name }}"[label="{{= filter.name }}", fillcolor=7] 39 | "{{= it.name }}" -> "{{= filter.name }}" 40 | {{~}} 41 | 42 | node[shape="{3}"] 43 | {{~it.directives :directive}} 44 | "{{= directive.name }}"[label="{{= directive.name }}", fillcolor=3] 45 | "{{= it.name }}" -> "{{= directive.name }}" 46 | {{~}} 47 | 48 | node[shape="{4}"] 49 | {{~it.components :component}} 50 | "{{= component.name }}"[label="{{= component.name }}", fillcolor=8] 51 | "{{= it.name }}" -> "{{= component.name }}" 52 | {{~}} 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | node[shape="{2}",style="filled", fillcolor=11] 61 | {{~it.providers :provider}} 62 | {{~provider.deps :dep}} 63 | "{{= dep }}"[label="{{= dep }}"] 64 | "{{= dep }}" -> "{{= provider.name }}" 65 | {{~}} 66 | {{~}} 67 | 68 | {{~it.directives :directive}} 69 | {{~directive.deps :dep}} 70 | "{{= dep }}"[label="{{= dep }}"] 71 | "{{= dep }}" -> "{{= directive.name }}" 72 | {{~}} 73 | {{~}} 74 | 75 | {{~it.components :component}} 76 | {{~component.deps :dep}} 77 | "{{= dep }}"[label="{{= dep }}"] 78 | "{{= dep }}" -> "{{= component.name }}" 79 | {{~}} 80 | {{~}} 81 | 82 | 83 | {{~it.controllers :controller}} 84 | {{~controller.deps :dep}} 85 | "{{= dep }}"[label="{{= dep }}"] 86 | "{{= dep }}" -> "{{= controller.name }}" 87 | {{~}} 88 | {{~}} 89 | 90 | {{~it.services :service}} 91 | {{~service.deps :dep}} 92 | "{{= dep }}"[label="{{= dep }}"] 93 | "{{= dep }}" -> "{{= service.name }}" 94 | {{~}} 95 | {{~}} 96 | 97 | {{~it.factories :factory}} 98 | {{~factory.deps :dep}} 99 | "{{= dep }}"[label="{{= dep }}"] 100 | "{{= dep }}" -> "{{= factory.name }}" 101 | {{~}} 102 | {{~}} 103 | 104 | {{~it.filters :filter}} 105 | {{~filter.deps :dep}} 106 | "{{= dep }}"[label="{{= dep }}"] 107 | "{{= dep }}" -> "{{= filter.name }}" 108 | {{~}} 109 | {{~}} 110 | 111 | 112 | } 113 | -------------------------------------------------------------------------------- /templates/modules.def: -------------------------------------------------------------------------------- 1 | digraph dependencies { 2 | node[shape="{2}",style="filled",colorscheme={scheme}] 3 | 4 | {{~it.modules :module}} 5 | "{{= module.name }}"[label="{{= module.name }}", fillcolor=1] 6 | {{~}} 7 | 8 | node[shape="{2}",style="filled",fillcolor=2] 9 | {{~it.modules :module}} 10 | {{~module.modules :dependency}} 11 | "{{= dependency }}" -> "{{= module.name }}" 12 | {{~}} 13 | {{~}} 14 | } 15 | -------------------------------------------------------------------------------- /test/angular-architecture-graph.test.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern/chai!expect', 3 | 'intern!bdd', 4 | 'intern/dojo/node!../index.js', 5 | 'intern/dojo/node!path', 6 | 'intern/dojo/node!fs' 7 | ], function(expect, bdd, ngArchitectureGraph, path, fs) { 8 | 9 | var compareFiles = true, 10 | expectedResultFile = 'test/expected/ui.router.dot', 11 | expectedResultFileData = null, 12 | testResultFile = 'tmp/dot/modules/ui.router.dot', 13 | testResultFileData = null; 14 | 15 | bdd.describe('ngArchitectureGraph plugin', function() { 16 | 17 | fs.readFile(expectedResultFile, 'utf8', function(err, dataExpected) { 18 | if (err) { 19 | compareFiles = false; 20 | } 21 | expectedResultFileData = dataExpected; 22 | fs.readFile(testResultFile, 'utf8', function(err, dataTest) { 23 | if (err) { 24 | compareFiles = false; 25 | } 26 | testResultFileData = dataTest; 27 | compareFiles = (expectedResultFileData === testResultFileData); 28 | }); 29 | }); 30 | 31 | bdd.it('should do the job', function() { 32 | expect(compareFiles).to.be.true; 33 | }); 34 | 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/expected/ui.router.dot: -------------------------------------------------------------------------------- 1 | digraph dependencies { 2 | node[shape="component",style="filled",colorscheme=paired12] 3 | 4 | 5 | node[shape="component"] 6 | "ui.router"[label="ui.router", color=1] 7 | 8 | 9 | "ui.router.state"[label="ui.router.state", color=1] 10 | "ui.router.state" -> "ui.router" 11 | 12 | 13 | 14 | node[shape="ellipse"] 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | node[shape="cds"] 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | node[shape="ellipse",style="filled", color=9] 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /test/fixtures/ui-router.js: -------------------------------------------------------------------------------- 1 | /** 2 | * State-based routing for AngularJS 3 | * @version v0.2.11 4 | * @link http://angular-ui.github.com/ 5 | * @license MIT License, http://www.opensource.org/licenses/MIT 6 | */ 7 | 8 | /* commonjs package manager support (eg componentjs) */ 9 | if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports){ 10 | module.exports = "ui.router"; 11 | } 12 | 13 | (function (window, angular, undefined) { 14 | /*jshint globalstrict:true*/ 15 | /*global angular:false*/ 16 | "use strict"; 17 | 18 | var isDefined = angular.isDefined, 19 | isFunction = angular.isFunction, 20 | isString = angular.isString, 21 | isObject = angular.isObject, 22 | isArray = angular.isArray, 23 | forEach = angular.forEach, 24 | extend = angular.extend, 25 | copy = angular.copy; 26 | 27 | function inherit(parent, extra) { 28 | return extend(new (extend(function() {}, { prototype: parent }))(), extra); 29 | } 30 | 31 | function merge(dst) { 32 | forEach(arguments, function(obj) { 33 | if (obj !== dst) { 34 | forEach(obj, function(value, key) { 35 | if (!dst.hasOwnProperty(key)) dst[key] = value; 36 | }); 37 | } 38 | }); 39 | return dst; 40 | } 41 | 42 | /** 43 | * Finds the common ancestor path between two states. 44 | * 45 | * @param {Object} first The first state. 46 | * @param {Object} second The second state. 47 | * @return {Array} Returns an array of state names in descending order, not including the root. 48 | */ 49 | function ancestors(first, second) { 50 | var path = []; 51 | 52 | for (var n in first.path) { 53 | if (first.path[n] !== second.path[n]) break; 54 | path.push(first.path[n]); 55 | } 56 | return path; 57 | } 58 | 59 | /** 60 | * IE8-safe wrapper for `Object.keys()`. 61 | * 62 | * @param {Object} object A JavaScript object. 63 | * @return {Array} Returns the keys of the object as an array. 64 | */ 65 | function objectKeys(object) { 66 | if (Object.keys) { 67 | return Object.keys(object); 68 | } 69 | var result = []; 70 | 71 | angular.forEach(object, function(val, key) { 72 | result.push(key); 73 | }); 74 | return result; 75 | } 76 | 77 | /** 78 | * IE8-safe wrapper for `Array.prototype.indexOf()`. 79 | * 80 | * @param {Array} array A JavaScript array. 81 | * @param {*} value A value to search the array for. 82 | * @return {Number} Returns the array index value of `value`, or `-1` if not present. 83 | */ 84 | function arraySearch(array, value) { 85 | if (Array.prototype.indexOf) { 86 | return array.indexOf(value, Number(arguments[2]) || 0); 87 | } 88 | var len = array.length >>> 0, from = Number(arguments[2]) || 0; 89 | from = (from < 0) ? Math.ceil(from) : Math.floor(from); 90 | 91 | if (from < 0) from += len; 92 | 93 | for (; from < len; from++) { 94 | if (from in array && array[from] === value) return from; 95 | } 96 | return -1; 97 | } 98 | 99 | /** 100 | * Merges a set of parameters with all parameters inherited between the common parents of the 101 | * current state and a given destination state. 102 | * 103 | * @param {Object} currentParams The value of the current state parameters ($stateParams). 104 | * @param {Object} newParams The set of parameters which will be composited with inherited params. 105 | * @param {Object} $current Internal definition of object representing the current state. 106 | * @param {Object} $to Internal definition of object representing state to transition to. 107 | */ 108 | function inheritParams(currentParams, newParams, $current, $to) { 109 | var parents = ancestors($current, $to), parentParams, inherited = {}, inheritList = []; 110 | 111 | for (var i in parents) { 112 | if (!parents[i].params) continue; 113 | parentParams = objectKeys(parents[i].params); 114 | if (!parentParams.length) continue; 115 | 116 | for (var j in parentParams) { 117 | if (arraySearch(inheritList, parentParams[j]) >= 0) continue; 118 | inheritList.push(parentParams[j]); 119 | inherited[parentParams[j]] = currentParams[parentParams[j]]; 120 | } 121 | } 122 | return extend({}, inherited, newParams); 123 | } 124 | 125 | /** 126 | * Performs a non-strict comparison of the subset of two objects, defined by a list of keys. 127 | * 128 | * @param {Object} a The first object. 129 | * @param {Object} b The second object. 130 | * @param {Array} keys The list of keys within each object to compare. If the list is empty or not specified, 131 | * it defaults to the list of keys in `a`. 132 | * @return {Boolean} Returns `true` if the keys match, otherwise `false`. 133 | */ 134 | function equalForKeys(a, b, keys) { 135 | if (!keys) { 136 | keys = []; 137 | for (var n in a) keys.push(n); // Used instead of Object.keys() for IE8 compatibility 138 | } 139 | 140 | for (var i=0; i 225 | * 226 | * 227 | * 228 | * 229 | * 230 | * 231 | * 235 | * 236 | * 237 | * 238 | * 239 | * 240 | */ 241 | angular.module("ui.router", ["ui.router.state"]); 242 | 243 | angular.module("ui.router.compat", ["ui.router"]); 244 | 245 | /** 246 | * @ngdoc object 247 | * @name ui.router.util.$resolve 248 | * 249 | * @requires $q 250 | * @requires $injector 251 | * 252 | * @description 253 | * Manages resolution of (acyclic) graphs of promises. 254 | */ 255 | $Resolve.$inject = ["$q", "$injector"]; 256 | function $Resolve( $q, $injector) { 257 | 258 | var VISIT_IN_PROGRESS = 1, 259 | VISIT_DONE = 2, 260 | NOTHING = {}, 261 | NO_DEPENDENCIES = [], 262 | NO_LOCALS = NOTHING, 263 | NO_PARENT = extend($q.when(NOTHING), { $$promises: NOTHING, $$values: NOTHING }); 264 | 265 | 266 | /** 267 | * @ngdoc function 268 | * @name ui.router.util.$resolve#study 269 | * @methodOf ui.router.util.$resolve 270 | * 271 | * @description 272 | * Studies a set of invocables that are likely to be used multiple times. 273 | *
 274 |    * $resolve.study(invocables)(locals, parent, self)
 275 |    * 
276 | * is equivalent to 277 | *
 278 |    * $resolve.resolve(invocables, locals, parent, self)
 279 |    * 
280 | * but the former is more efficient (in fact `resolve` just calls `study` 281 | * internally). 282 | * 283 | * @param {object} invocables Invocable objects 284 | * @return {function} a function to pass in locals, parent and self 285 | */ 286 | this.study = function (invocables) { 287 | if (!isObject(invocables)) throw new Error("'invocables' must be an object"); 288 | 289 | // Perform a topological sort of invocables to build an ordered plan 290 | var plan = [], cycle = [], visited = {}; 291 | function visit(value, key) { 292 | if (visited[key] === VISIT_DONE) return; 293 | 294 | cycle.push(key); 295 | if (visited[key] === VISIT_IN_PROGRESS) { 296 | cycle.splice(0, cycle.indexOf(key)); 297 | throw new Error("Cyclic dependency: " + cycle.join(" -> ")); 298 | } 299 | visited[key] = VISIT_IN_PROGRESS; 300 | 301 | if (isString(value)) { 302 | plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES); 303 | } else { 304 | var params = $injector.annotate(value); 305 | forEach(params, function (param) { 306 | if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param); 307 | }); 308 | plan.push(key, value, params); 309 | } 310 | 311 | cycle.pop(); 312 | visited[key] = VISIT_DONE; 313 | } 314 | forEach(invocables, visit); 315 | invocables = cycle = visited = null; // plan is all that"s required 316 | 317 | function isResolve(value) { 318 | return isObject(value) && value.then && value.$$promises; 319 | } 320 | 321 | return function (locals, parent, self) { 322 | if (isResolve(locals) && self === undefined) { 323 | self = parent; parent = locals; locals = null; 324 | } 325 | if (!locals) locals = NO_LOCALS; 326 | else if (!isObject(locals)) { 327 | throw new Error("'locals' must be an object"); 328 | } 329 | if (!parent) parent = NO_PARENT; 330 | else if (!isResolve(parent)) { 331 | throw new Error("'parent' must be a promise returned by $resolve.resolve()"); 332 | } 333 | 334 | // To complete the overall resolution, we have to wait for the parent 335 | // promise and for the promise for each invokable in our plan. 336 | var resolution = $q.defer(), 337 | result = resolution.promise, 338 | promises = result.$$promises = {}, 339 | values = extend({}, locals), 340 | wait = 1 + plan.length/3, 341 | merged = false; 342 | 343 | function done() { 344 | // Merge parent values we haven"t got yet and publish our own $$values 345 | if (!--wait) { 346 | if (!merged) merge(values, parent.$$values); 347 | result.$$values = values; 348 | result.$$promises = true; // keep for isResolve() 349 | delete result.$$inheritedValues; 350 | resolution.resolve(values); 351 | } 352 | } 353 | 354 | function fail(reason) { 355 | result.$$failure = reason; 356 | resolution.reject(reason); 357 | } 358 | 359 | // Short-circuit if parent has already failed 360 | if (isDefined(parent.$$failure)) { 361 | fail(parent.$$failure); 362 | return result; 363 | } 364 | 365 | if (parent.$$inheritedValues) { 366 | merge(values, parent.$$inheritedValues); 367 | } 368 | 369 | // Merge parent values if the parent has already resolved, or merge 370 | // parent promises and wait if the parent resolve is still in progress. 371 | if (parent.$$values) { 372 | merged = merge(values, parent.$$values); 373 | result.$$inheritedValues = parent.$$values; 374 | done(); 375 | } else { 376 | if (parent.$$inheritedValues) { 377 | result.$$inheritedValues = parent.$$inheritedValues; 378 | } 379 | extend(promises, parent.$$promises); 380 | parent.then(done, fail); 381 | } 382 | 383 | // Process each invocable in the plan, but ignore any where a local of the same name exists. 384 | for (var i=0, ii=plan.length; i} The template html as a string, or a promise 576 | * for that string. 577 | */ 578 | this.fromUrl = function (url, params) { 579 | if (isFunction(url)) url = url(params); 580 | if (url == null) return null; 581 | else return $http 582 | .get(url, { cache: $templateCache }) 583 | .then(function(response) { return response.data; }); 584 | }; 585 | 586 | /** 587 | * @ngdoc function 588 | * @name ui.router.util.$templateFactory#fromProvider 589 | * @methodOf ui.router.util.$templateFactory 590 | * 591 | * @description 592 | * Creates a template by invoking an injectable provider function. 593 | * 594 | * @param {Function} provider Function to invoke via `$injector.invoke` 595 | * @param {Object} params Parameters for the template. 596 | * @param {Object} locals Locals to pass to `invoke`. Defaults to 597 | * `{ params: params }`. 598 | * @return {string|Promise.} The template html as a string, or a promise 599 | * for that string. 600 | */ 601 | this.fromProvider = function (provider, params, locals) { 602 | return $injector.invoke(provider, null, locals || { params: params }); 603 | }; 604 | } 605 | 606 | angular.module("ui.router.util").service("$templateFactory", $TemplateFactory); 607 | 608 | /** 609 | * @ngdoc object 610 | * @name ui.router.util.type:UrlMatcher 611 | * 612 | * @description 613 | * Matches URLs against patterns and extracts named parameters from the path or the search 614 | * part of the URL. A URL pattern consists of a path pattern, optionally followed by "?" and a list 615 | * of search parameters. Multiple search parameter names are separated by "&". Search parameters 616 | * do not influence whether or not a URL is matched, but their values are passed through into 617 | * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}. 618 | * 619 | * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace 620 | * syntax, which optionally allows a regular expression for the parameter to be specified: 621 | * 622 | * * `":"` name - colon placeholder 623 | * * `"*"` name - catch-all placeholder 624 | * * `"{" name "}"` - curly placeholder 625 | * * `"{" name ":" regexp "}"` - curly placeholder with regexp. Should the regexp itself contain 626 | * curly braces, they must be in matched pairs or escaped with a backslash. 627 | * 628 | * Parameter names may contain only word characters (latin letters, digits, and underscore) and 629 | * must be unique within the pattern (across both path and search parameters). For colon 630 | * placeholders or curly placeholders without an explicit regexp, a path parameter matches any 631 | * number of characters other than "/". For catch-all placeholders the path parameter matches 632 | * any number of characters. 633 | * 634 | * Examples: 635 | * 636 | * * `"/hello/"` - Matches only if the path is exactly "/hello/". There is no special treatment for 637 | * trailing slashes, and patterns have to match the entire path, not just a prefix. 638 | * * `"/user/:id"` - Matches "/user/bob" or "/user/1234!!!" or even "/user/" but not "/user" or 639 | * "/user/bob/details". The second path segment will be captured as the parameter "id". 640 | * * `"/user/{id}"` - Same as the previous example, but using curly brace syntax. 641 | * * `"/user/{id:[^/]*}"` - Same as the previous example. 642 | * * `"/user/{id:[0-9a-fA-F]{1,8}}"` - Similar to the previous example, but only matches if the id 643 | * parameter consists of 1 to 8 hex digits. 644 | * * `"/files/{path:.*}"` - Matches any URL starting with "/files/" and captures the rest of the 645 | * path into the parameter "path". 646 | * * `"/files/*path"` - ditto. 647 | * 648 | * @param {string} pattern The pattern to compile into a matcher. 649 | * @param {Object} config A configuration object hash: 650 | * 651 | * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`. 652 | * * `strict` - `false` if matching against a URL with a trailing slash should be treated as equivalent to a URL without a trailing slash, the default value is `true`. 653 | * 654 | * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any 655 | * URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns 656 | * non-null) will start with this prefix. 657 | * 658 | * @property {string} source The pattern that was passed into the constructor 659 | * 660 | * @property {string} sourcePath The path portion of the source property 661 | * 662 | * @property {string} sourceSearch The search portion of the source property 663 | * 664 | * @property {string} regex The constructed regex that will be used to match against the url when 665 | * it is time to determine which url will match. 666 | * 667 | * @returns {Object} New `UrlMatcher` object 668 | */ 669 | function UrlMatcher(pattern, config) { 670 | config = angular.isObject(config) ? config : {}; 671 | 672 | // Find all placeholders and create a compiled pattern, using either classic or curly syntax: 673 | // "*" name 674 | // ":" name 675 | // "{" name "}" 676 | // "{" name ":" regexp "}" 677 | // The regular expression is somewhat complicated due to the need to allow curly braces 678 | // inside the regular expression. The placeholder regexp breaks down as follows: 679 | // ([:*])(\w+) classic placeholder ($1 / $2) 680 | // \{(\w+)(?:\:( ... ))?\} curly brace placeholder ($3) with optional regexp ... ($4) 681 | // (?: ... | ... | ... )+ the regexp consists of any number of atoms, an atom being either 682 | // [^{}\\]+ - anything other than curly braces or backslash 683 | // \\. - a backslash escape 684 | // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms 685 | var placeholder = /([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g, 686 | compiled = "^", last = 0, m, 687 | segments = this.segments = [], 688 | params = this.params = {}; 689 | 690 | /** 691 | * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the 692 | * default value, which may be the result of an injectable function. 693 | */ 694 | function $value(value) { 695 | /*jshint validthis: true */ 696 | return isDefined(value) ? this.type.decode(value) : $UrlMatcherFactory.$$getDefaultValue(this); 697 | } 698 | 699 | function addParameter(id, type, config) { 700 | if (!/^\w+(-+\w+)*$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'"); 701 | if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'"); 702 | params[id] = extend({ type: type || new Type(), $value: $value }, config); 703 | } 704 | 705 | function quoteRegExp(string, pattern, isOptional) { 706 | var result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&"); 707 | if (!pattern) return result; 708 | var flag = isOptional ? "?" : ""; 709 | return result + flag + "(" + pattern + ")" + flag; 710 | } 711 | 712 | function paramConfig(param) { 713 | if (!config.params || !config.params[param]) return {}; 714 | var cfg = config.params[param]; 715 | return isObject(cfg) ? cfg : { value: cfg }; 716 | } 717 | 718 | this.source = pattern; 719 | 720 | // Split into static segments separated by path parameter placeholders. 721 | // The number of segments is always 1 more than the number of parameters. 722 | var id, regexp, segment, type, cfg; 723 | 724 | while ((m = placeholder.exec(pattern))) { 725 | id = m[2] || m[3]; // IE[78] returns "" for unmatched groups instead of null 726 | regexp = m[4] || (m[1] == "*" ? ".*" : "[^/]*"); 727 | segment = pattern.substring(last, m.index); 728 | type = this.$types[regexp] || new Type({ pattern: new RegExp(regexp) }); 729 | cfg = paramConfig(id); 730 | 731 | if (segment.indexOf("?") >= 0) break; // we"re into the search part 732 | 733 | compiled += quoteRegExp(segment, type.$subPattern(), isDefined(cfg.value)); 734 | addParameter(id, type, cfg); 735 | segments.push(segment); 736 | last = placeholder.lastIndex; 737 | } 738 | segment = pattern.substring(last); 739 | 740 | // Find any search parameter names and remove them from the last segment 741 | var i = segment.indexOf("?"); 742 | 743 | if (i >= 0) { 744 | var search = this.sourceSearch = segment.substring(i); 745 | segment = segment.substring(0, i); 746 | this.sourcePath = pattern.substring(0, last + i); 747 | 748 | // Allow parameters to be separated by "?" as well as "&" to make concat() easier 749 | forEach(search.substring(1).split(/[&?]/), function(key) { 750 | addParameter(key, null, paramConfig(key)); 751 | }); 752 | } else { 753 | this.sourcePath = pattern; 754 | this.sourceSearch = ""; 755 | } 756 | 757 | compiled += quoteRegExp(segment) + (config.strict === false ? "\/?" : "") + "$"; 758 | segments.push(segment); 759 | 760 | this.regexp = new RegExp(compiled, config.caseInsensitive ? "i" : undefined); 761 | this.prefix = segments[0]; 762 | } 763 | 764 | /** 765 | * @ngdoc function 766 | * @name ui.router.util.type:UrlMatcher#concat 767 | * @methodOf ui.router.util.type:UrlMatcher 768 | * 769 | * @description 770 | * Returns a new matcher for a pattern constructed by appending the path part and adding the 771 | * search parameters of the specified pattern to this pattern. The current pattern is not 772 | * modified. This can be understood as creating a pattern for URLs that are relative to (or 773 | * suffixes of) the current pattern. 774 | * 775 | * @example 776 | * The following two matchers are equivalent: 777 | *
 778 |  * new UrlMatcher("/user/{id}?q").concat("/details?date");
 779 |  * new UrlMatcher("/user/{id}/details?q&date");
 780 |  * 
781 | * 782 | * @param {string} pattern The pattern to append. 783 | * @param {Object} config An object hash of the configuration for the matcher. 784 | * @returns {UrlMatcher} A matcher for the concatenated pattern. 785 | */ 786 | UrlMatcher.prototype.concat = function (pattern, config) { 787 | // Because order of search parameters is irrelevant, we can add our own search 788 | // parameters to the end of the new pattern. Parse the new pattern by itself 789 | // and then join the bits together, but it"s much easier to do this on a string level. 790 | return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, config); 791 | }; 792 | 793 | UrlMatcher.prototype.toString = function () { 794 | return this.source; 795 | }; 796 | 797 | /** 798 | * @ngdoc function 799 | * @name ui.router.util.type:UrlMatcher#exec 800 | * @methodOf ui.router.util.type:UrlMatcher 801 | * 802 | * @description 803 | * Tests the specified path against this matcher, and returns an object containing the captured 804 | * parameter values, or null if the path does not match. The returned object contains the values 805 | * of any search parameters that are mentioned in the pattern, but their value may be null if 806 | * they are not present in `searchParams`. This means that search parameters are always treated 807 | * as optional. 808 | * 809 | * @example 810 | *
 811 |  * new UrlMatcher("/user/{id}?q&r").exec("/user/bob", {
 812 |  *   x: "1", q: "hello"
 813 |  * });
 814 |  * // returns { id: "bob", q: "hello", r: null }
 815 |  * 
816 | * 817 | * @param {string} path The URL path to match, e.g. `$location.path()`. 818 | * @param {Object} searchParams URL search parameters, e.g. `$location.search()`. 819 | * @returns {Object} The captured parameter values. 820 | */ 821 | UrlMatcher.prototype.exec = function (path, searchParams) { 822 | var m = this.regexp.exec(path); 823 | if (!m) return null; 824 | searchParams = searchParams || {}; 825 | 826 | var params = this.parameters(), nTotal = params.length, 827 | nPath = this.segments.length - 1, 828 | values = {}, i, cfg, param; 829 | 830 | if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'"); 831 | 832 | for (i = 0; i < nPath; i++) { 833 | param = params[i]; 834 | cfg = this.params[param]; 835 | values[param] = cfg.$value(m[i + 1]); 836 | } 837 | for (/**/; i < nTotal; i++) { 838 | param = params[i]; 839 | cfg = this.params[param]; 840 | values[param] = cfg.$value(searchParams[param]); 841 | } 842 | 843 | return values; 844 | }; 845 | 846 | /** 847 | * @ngdoc function 848 | * @name ui.router.util.type:UrlMatcher#parameters 849 | * @methodOf ui.router.util.type:UrlMatcher 850 | * 851 | * @description 852 | * Returns the names of all path and search parameters of this pattern in an unspecified order. 853 | * 854 | * @returns {Array.} An array of parameter names. Must be treated as read-only. If the 855 | * pattern has no parameters, an empty array is returned. 856 | */ 857 | UrlMatcher.prototype.parameters = function (param) { 858 | if (!isDefined(param)) return objectKeys(this.params); 859 | return this.params[param] || null; 860 | }; 861 | 862 | /** 863 | * @ngdoc function 864 | * @name ui.router.util.type:UrlMatcher#validate 865 | * @methodOf ui.router.util.type:UrlMatcher 866 | * 867 | * @description 868 | * Checks an object hash of parameters to validate their correctness according to the parameter 869 | * types of this `UrlMatcher`. 870 | * 871 | * @param {Object} params The object hash of parameters to validate. 872 | * @returns {boolean} Returns `true` if `params` validates, otherwise `false`. 873 | */ 874 | UrlMatcher.prototype.validates = function (params) { 875 | var result = true, isOptional, cfg, self = this; 876 | 877 | forEach(params, function(val, key) { 878 | if (!self.params[key]) return; 879 | cfg = self.params[key]; 880 | isOptional = !val && isDefined(cfg.value); 881 | result = result && (isOptional || cfg.type.is(val)); 882 | }); 883 | return result; 884 | }; 885 | 886 | /** 887 | * @ngdoc function 888 | * @name ui.router.util.type:UrlMatcher#format 889 | * @methodOf ui.router.util.type:UrlMatcher 890 | * 891 | * @description 892 | * Creates a URL that matches this pattern by substituting the specified values 893 | * for the path and search parameters. Null values for path parameters are 894 | * treated as empty strings. 895 | * 896 | * @example 897 | *
 898 |  * new UrlMatcher("/user/{id}?q").format({ id:"bob", q:"yes" });
 899 |  * // returns "/user/bob?q=yes"
 900 |  * 
901 | * 902 | * @param {Object} values the values to substitute for the parameters in this pattern. 903 | * @returns {string} the formatted URL (path and optionally search part). 904 | */ 905 | UrlMatcher.prototype.format = function (values) { 906 | var segments = this.segments, params = this.parameters(); 907 | 908 | if (!values) return segments.join("").replace("//", "/"); 909 | 910 | var nPath = segments.length - 1, nTotal = params.length, 911 | result = segments[0], i, search, value, param, cfg, array; 912 | 913 | if (!this.validates(values)) return null; 914 | 915 | for (i = 0; i < nPath; i++) { 916 | param = params[i]; 917 | value = values[param]; 918 | cfg = this.params[param]; 919 | 920 | if (!isDefined(value) && (segments[i] === "/" || segments[i + 1] === "/")) continue; 921 | if (value != null) result += encodeURIComponent(cfg.type.encode(value)); 922 | result += segments[i + 1]; 923 | } 924 | 925 | for (/**/; i < nTotal; i++) { 926 | param = params[i]; 927 | value = values[param]; 928 | if (value == null) continue; 929 | array = isArray(value); 930 | 931 | if (array) { 932 | value = value.map(encodeURIComponent).join("&" + param + "="); 933 | } 934 | result += (search ? "&" : "?") + param + "=" + (array ? value : encodeURIComponent(value)); 935 | search = true; 936 | } 937 | return result; 938 | }; 939 | 940 | UrlMatcher.prototype.$types = {}; 941 | 942 | /** 943 | * @ngdoc object 944 | * @name ui.router.util.type:Type 945 | * 946 | * @description 947 | * Implements an interface to define custom parameter types that can be decoded from and encoded to 948 | * string parameters matched in a URL. Used by {@link ui.router.util.type:UrlMatcher `UrlMatcher`} 949 | * objects when matching or formatting URLs, or comparing or validating parameter values. 950 | * 951 | * See {@link ui.router.util.$urlMatcherFactory#methods_type `$urlMatcherFactory#type()`} for more 952 | * information on registering custom types. 953 | * 954 | * @param {Object} config A configuration object hash that includes any method in `Type`"s public 955 | * interface, and/or `pattern`, which should contain a custom regular expression used to match 956 | * string parameters originating from a URL. 957 | * 958 | * @property {RegExp} pattern The regular expression pattern used to match values of this type when 959 | * coming from a substring of a URL. 960 | * 961 | * @returns {Object} Returns a new `Type` object. 962 | */ 963 | function Type(config) { 964 | extend(this, config); 965 | } 966 | 967 | /** 968 | * @ngdoc function 969 | * @name ui.router.util.type:Type#is 970 | * @methodOf ui.router.util.type:Type 971 | * 972 | * @description 973 | * Detects whether a value is of a particular type. Accepts a native (decoded) value 974 | * and determines whether it matches the current `Type` object. 975 | * 976 | * @param {*} val The value to check. 977 | * @param {string} key Optional. If the type check is happening in the context of a specific 978 | * {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the 979 | * parameter in which `val` is stored. Can be used for meta-programming of `Type` objects. 980 | * @returns {Boolean} Returns `true` if the value matches the type, otherwise `false`. 981 | */ 982 | Type.prototype.is = function(val, key) { 983 | return true; 984 | }; 985 | 986 | /** 987 | * @ngdoc function 988 | * @name ui.router.util.type:Type#encode 989 | * @methodOf ui.router.util.type:Type 990 | * 991 | * @description 992 | * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the 993 | * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it 994 | * only needs to be a representation of `val` that has been coerced to a string. 995 | * 996 | * @param {*} val The value to encode. 997 | * @param {string} key The name of the parameter in which `val` is stored. Can be used for 998 | * meta-programming of `Type` objects. 999 | * @returns {string} Returns a string representation of `val` that can be encoded in a URL. 1000 | */ 1001 | Type.prototype.encode = function(val, key) { 1002 | return val; 1003 | }; 1004 | 1005 | /** 1006 | * @ngdoc function 1007 | * @name ui.router.util.type:Type#decode 1008 | * @methodOf ui.router.util.type:Type 1009 | * 1010 | * @description 1011 | * Converts a string URL parameter value to a custom/native value. 1012 | * 1013 | * @param {string} val The URL parameter value to decode. 1014 | * @param {string} key The name of the parameter in which `val` is stored. Can be used for 1015 | * meta-programming of `Type` objects. 1016 | * @returns {*} Returns a custom representation of the URL parameter value. 1017 | */ 1018 | Type.prototype.decode = function(val, key) { 1019 | return val; 1020 | }; 1021 | 1022 | /** 1023 | * @ngdoc function 1024 | * @name ui.router.util.type:Type#equals 1025 | * @methodOf ui.router.util.type:Type 1026 | * 1027 | * @description 1028 | * Determines whether two decoded values are equivalent. 1029 | * 1030 | * @param {*} a A value to compare against. 1031 | * @param {*} b A value to compare against. 1032 | * @returns {Boolean} Returns `true` if the values are equivalent/equal, otherwise `false`. 1033 | */ 1034 | Type.prototype.equals = function(a, b) { 1035 | return a == b; 1036 | }; 1037 | 1038 | Type.prototype.$subPattern = function() { 1039 | var sub = this.pattern.toString(); 1040 | return sub.substr(1, sub.length - 2); 1041 | }; 1042 | 1043 | Type.prototype.pattern = /.*/; 1044 | 1045 | /** 1046 | * @ngdoc object 1047 | * @name ui.router.util.$urlMatcherFactory 1048 | * 1049 | * @description 1050 | * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory 1051 | * is also available to providers under the name `$urlMatcherFactoryProvider`. 1052 | */ 1053 | function $UrlMatcherFactory() { 1054 | 1055 | var isCaseInsensitive = false, isStrictMode = true; 1056 | 1057 | var enqueue = true, typeQueue = [], injector, defaultTypes = { 1058 | int: { 1059 | decode: function(val) { 1060 | return parseInt(val, 10); 1061 | }, 1062 | is: function(val) { 1063 | if (!isDefined(val)) return false; 1064 | return this.decode(val.toString()) === val; 1065 | }, 1066 | pattern: /\d+/ 1067 | }, 1068 | bool: { 1069 | encode: function(val) { 1070 | return val ? 1 : 0; 1071 | }, 1072 | decode: function(val) { 1073 | return parseInt(val, 10) === 0 ? false : true; 1074 | }, 1075 | is: function(val) { 1076 | return val === true || val === false; 1077 | }, 1078 | pattern: /0|1/ 1079 | }, 1080 | string: { 1081 | pattern: /[^\/]*/ 1082 | }, 1083 | date: { 1084 | equals: function (a, b) { 1085 | return a.toISOString() === b.toISOString(); 1086 | }, 1087 | decode: function (val) { 1088 | return new Date(val); 1089 | }, 1090 | encode: function (val) { 1091 | return [ 1092 | val.getFullYear(), 1093 | ("0" + (val.getMonth() + 1)).slice(-2), 1094 | ("0" + val.getDate()).slice(-2) 1095 | ].join("-"); 1096 | }, 1097 | pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/ 1098 | } 1099 | }; 1100 | 1101 | function getDefaultConfig() { 1102 | return { 1103 | strict: isStrictMode, 1104 | caseInsensitive: isCaseInsensitive 1105 | }; 1106 | } 1107 | 1108 | function isInjectable(value) { 1109 | return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1]))); 1110 | } 1111 | 1112 | /** 1113 | * [Internal] Get the default value of a parameter, which may be an injectable function. 1114 | */ 1115 | $UrlMatcherFactory.$$getDefaultValue = function(config) { 1116 | if (!isInjectable(config.value)) return config.value; 1117 | if (!injector) throw new Error("Injectable functions cannot be called at configuration time"); 1118 | return injector.invoke(config.value); 1119 | }; 1120 | 1121 | /** 1122 | * @ngdoc function 1123 | * @name ui.router.util.$urlMatcherFactory#caseInsensitive 1124 | * @methodOf ui.router.util.$urlMatcherFactory 1125 | * 1126 | * @description 1127 | * Defines whether URL matching should be case sensitive (the default behavior), or not. 1128 | * 1129 | * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`; 1130 | */ 1131 | this.caseInsensitive = function(value) { 1132 | isCaseInsensitive = value; 1133 | }; 1134 | 1135 | /** 1136 | * @ngdoc function 1137 | * @name ui.router.util.$urlMatcherFactory#strictMode 1138 | * @methodOf ui.router.util.$urlMatcherFactory 1139 | * 1140 | * @description 1141 | * Defines whether URLs should match trailing slashes, or not (the default behavior). 1142 | * 1143 | * @param {boolean} value `false` to match trailing slashes in URLs, otherwise `true`. 1144 | */ 1145 | this.strictMode = function(value) { 1146 | isStrictMode = value; 1147 | }; 1148 | 1149 | /** 1150 | * @ngdoc function 1151 | * @name ui.router.util.$urlMatcherFactory#compile 1152 | * @methodOf ui.router.util.$urlMatcherFactory 1153 | * 1154 | * @description 1155 | * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern. 1156 | * 1157 | * @param {string} pattern The URL pattern. 1158 | * @param {Object} config The config object hash. 1159 | * @returns {UrlMatcher} The UrlMatcher. 1160 | */ 1161 | this.compile = function (pattern, config) { 1162 | return new UrlMatcher(pattern, extend(getDefaultConfig(), config)); 1163 | }; 1164 | 1165 | /** 1166 | * @ngdoc function 1167 | * @name ui.router.util.$urlMatcherFactory#isMatcher 1168 | * @methodOf ui.router.util.$urlMatcherFactory 1169 | * 1170 | * @description 1171 | * Returns true if the specified object is a `UrlMatcher`, or false otherwise. 1172 | * 1173 | * @param {Object} object The object to perform the type check against. 1174 | * @returns {Boolean} Returns `true` if the object matches the `UrlMatcher` interface, by 1175 | * implementing all the same methods. 1176 | */ 1177 | this.isMatcher = function (o) { 1178 | if (!isObject(o)) return false; 1179 | var result = true; 1180 | 1181 | forEach(UrlMatcher.prototype, function(val, name) { 1182 | if (isFunction(val)) { 1183 | result = result && (isDefined(o[name]) && isFunction(o[name])); 1184 | } 1185 | }); 1186 | return result; 1187 | }; 1188 | 1189 | /** 1190 | * @ngdoc function 1191 | * @name ui.router.util.$urlMatcherFactory#type 1192 | * @methodOf ui.router.util.$urlMatcherFactory 1193 | * 1194 | * @description 1195 | * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to 1196 | * generate URLs with typed parameters. 1197 | * 1198 | * @param {string} name The type name. 1199 | * @param {Object|Function} def The type definition. See 1200 | * {@link ui.router.util.type:Type `Type`} for information on the values accepted. 1201 | * 1202 | * @returns {Object} Returns `$urlMatcherFactoryProvider`. 1203 | * 1204 | * @example 1205 | * This is a simple example of a custom type that encodes and decodes items from an 1206 | * array, using the array index as the URL-encoded value: 1207 | * 1208 | *
1209 |    * var list = ["John", "Paul", "George", "Ringo"];
1210 |    *
1211 |    * $urlMatcherFactoryProvider.type("listItem", {
1212 |    *   encode: function(item) {
1213 |    *     // Represent the list item in the URL using its corresponding index
1214 |    *     return list.indexOf(item);
1215 |    *   },
1216 |    *   decode: function(item) {
1217 |    *     // Look up the list item by index
1218 |    *     return list[parseInt(item, 10)];
1219 |    *   },
1220 |    *   is: function(item) {
1221 |    *     // Ensure the item is valid by checking to see that it appears
1222 |    *     // in the list
1223 |    *     return list.indexOf(item) > -1;
1224 |    *   }
1225 |    * });
1226 |    *
1227 |    * $stateProvider.state("list", {
1228 |    *   url: "/list/{item:listItem}",
1229 |    *   controller: function($scope, $stateParams) {
1230 |    *     console.log($stateParams.item);
1231 |    *   }
1232 |    * });
1233 |    *
1234 |    * // ...
1235 |    *
1236 |    * // Changes URL to "/list/3", logs "Ringo" to the console
1237 |    * $state.go("list", { item: "Ringo" });
1238 |    * 
1239 | * 1240 | * This is a more complex example of a type that relies on dependency injection to 1241 | * interact with services, and uses the parameter name from the URL to infer how to 1242 | * handle encoding and decoding parameter values: 1243 | * 1244 | *
1245 |    * // Defines a custom type that gets a value from a service,
1246 |    * // where each service gets different types of values from
1247 |    * // a backend API:
1248 |    * $urlMatcherFactoryProvider.type("dbObject", function(Users, Posts) {
1249 |    *
1250 |    *   // Matches up services to URL parameter names
1251 |    *   var services = {
1252 |    *     user: Users,
1253 |    *     post: Posts
1254 |    *   };
1255 |    *
1256 |    *   return {
1257 |    *     encode: function(object) {
1258 |    *       // Represent the object in the URL using its unique ID
1259 |    *       return object.id;
1260 |    *     },
1261 |    *     decode: function(value, key) {
1262 |    *       // Look up the object by ID, using the parameter
1263 |    *       // name (key) to call the correct service
1264 |    *       return services[key].findById(value);
1265 |    *     },
1266 |    *     is: function(object, key) {
1267 |    *       // Check that object is a valid dbObject
1268 |    *       return angular.isObject(object) && object.id && services[key];
1269 |    *     }
1270 |    *     equals: function(a, b) {
1271 |    *       // Check the equality of decoded objects by comparing
1272 |    *       // their unique IDs
1273 |    *       return a.id === b.id;
1274 |    *     }
1275 |    *   };
1276 |    * });
1277 |    *
1278 |    * // In a config() block, you can then attach URLs with
1279 |    * // type-annotated parameters:
1280 |    * $stateProvider.state("users", {
1281 |    *   url: "/users",
1282 |    *   // ...
1283 |    * }).state("users.item", {
1284 |    *   url: "/{user:dbObject}",
1285 |    *   controller: function($scope, $stateParams) {
1286 |    *     // $stateParams.user will now be an object returned from
1287 |    *     // the Users service
1288 |    *   },
1289 |    *   // ...
1290 |    * });
1291 |    * 
1292 | */ 1293 | this.type = function (name, def) { 1294 | if (!isDefined(def)) return UrlMatcher.prototype.$types[name]; 1295 | typeQueue.push({ name: name, def: def }); 1296 | if (!enqueue) flushTypeQueue(); 1297 | return this; 1298 | }; 1299 | 1300 | /* No need to document $get, since it returns this */ 1301 | this.$get = ["$injector", function ($injector) { 1302 | injector = $injector; 1303 | enqueue = false; 1304 | UrlMatcher.prototype.$types = {}; 1305 | flushTypeQueue(); 1306 | 1307 | forEach(defaultTypes, function(type, name) { 1308 | if (!UrlMatcher.prototype.$types[name]) UrlMatcher.prototype.$types[name] = new Type(type); 1309 | }); 1310 | return this; 1311 | }]; 1312 | 1313 | // To ensure proper order of operations in object configuration, and to allow internal 1314 | // types to be overridden, `flushTypeQueue()` waits until `$urlMatcherFactory` is injected 1315 | // before actually wiring up and assigning type definitions 1316 | function flushTypeQueue() { 1317 | forEach(typeQueue, function(type) { 1318 | if (UrlMatcher.prototype.$types[type.name]) { 1319 | throw new Error("A type named '" + type.name + "' has already been defined."); 1320 | } 1321 | var def = new Type(isInjectable(type.def) ? injector.invoke(type.def) : type.def); 1322 | UrlMatcher.prototype.$types[type.name] = def; 1323 | }); 1324 | } 1325 | } 1326 | 1327 | // Register as a provider so it"s available to other providers 1328 | angular.module("ui.router.util").provider("$urlMatcherFactory", $UrlMatcherFactory); 1329 | 1330 | /** 1331 | * @ngdoc object 1332 | * @name ui.router.router.$urlRouterProvider 1333 | * 1334 | * @requires ui.router.util.$urlMatcherFactoryProvider 1335 | * @requires $locationProvider 1336 | * 1337 | * @description 1338 | * `$urlRouterProvider` has the responsibility of watching `$location`. 1339 | * When `$location` changes it runs through a list of rules one by one until a 1340 | * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify 1341 | * a url in a state configuration. All urls are compiled into a UrlMatcher object. 1342 | * 1343 | * There are several methods on `$urlRouterProvider` that make it useful to use directly 1344 | * in your module config. 1345 | */ 1346 | $UrlRouterProvider.$inject = ["$locationProvider", "$urlMatcherFactoryProvider"]; 1347 | function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) { 1348 | var rules = [], otherwise = null, interceptDeferred = false, listener; 1349 | 1350 | // Returns a string that is a prefix of all strings matching the RegExp 1351 | function regExpPrefix(re) { 1352 | var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source); 1353 | return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : ""; 1354 | } 1355 | 1356 | // Interpolates matched values into a String.replace()-style pattern 1357 | function interpolate(pattern, match) { 1358 | return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) { 1359 | return match[what === "$" ? 0 : Number(what)]; 1360 | }); 1361 | } 1362 | 1363 | /** 1364 | * @ngdoc function 1365 | * @name ui.router.router.$urlRouterProvider#rule 1366 | * @methodOf ui.router.router.$urlRouterProvider 1367 | * 1368 | * @description 1369 | * Defines rules that are used by `$urlRouterProvider` to find matches for 1370 | * specific URLs. 1371 | * 1372 | * @example 1373 | *
1374 |    * var app = angular.module("app", ["ui.router.router"]);
1375 |    *
1376 |    * app.config(function ($urlRouterProvider) {
1377 |    *   // Here"s an example of how you might allow case insensitive urls
1378 |    *   $urlRouterProvider.rule(function ($injector, $location) {
1379 |    *     var path = $location.path(),
1380 |    *         normalized = path.toLowerCase();
1381 |    *
1382 |    *     if (path !== normalized) {
1383 |    *       return normalized;
1384 |    *     }
1385 |    *   });
1386 |    * });
1387 |    * 
1388 | * 1389 | * @param {object} rule Handler function that takes `$injector` and `$location` 1390 | * services as arguments. You can use them to return a valid path as a string. 1391 | * 1392 | * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance 1393 | */ 1394 | this.rule = function (rule) { 1395 | if (!isFunction(rule)) throw new Error("'rule' must be a function"); 1396 | rules.push(rule); 1397 | return this; 1398 | }; 1399 | 1400 | /** 1401 | * @ngdoc object 1402 | * @name ui.router.router.$urlRouterProvider#otherwise 1403 | * @methodOf ui.router.router.$urlRouterProvider 1404 | * 1405 | * @description 1406 | * Defines a path that is used when an invalid route is requested. 1407 | * 1408 | * @example 1409 | *
1410 |    * var app = angular.module("app", ["ui.router.router"]);
1411 |    *
1412 |    * app.config(function ($urlRouterProvider) {
1413 |    *   // if the path doesn"t match any of the urls you configured
1414 |    *   // otherwise will take care of routing the user to the
1415 |    *   // specified url
1416 |    *   $urlRouterProvider.otherwise("/index");
1417 |    *
1418 |    *   // Example of using function rule as param
1419 |    *   $urlRouterProvider.otherwise(function ($injector, $location) {
1420 |    *     return "/a/valid/url";
1421 |    *   });
1422 |    * });
1423 |    * 
1424 | * 1425 | * @param {string|object} rule The url path you want to redirect to or a function 1426 | * rule that returns the url path. The function version is passed two params: 1427 | * `$injector` and `$location` services, and must return a url string. 1428 | * 1429 | * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance 1430 | */ 1431 | this.otherwise = function (rule) { 1432 | if (isString(rule)) { 1433 | var redirect = rule; 1434 | rule = function () { return redirect; }; 1435 | } 1436 | else if (!isFunction(rule)) throw new Error("'rule' must be a function"); 1437 | otherwise = rule; 1438 | return this; 1439 | }; 1440 | 1441 | 1442 | function handleIfMatch($injector, handler, match) { 1443 | if (!match) return false; 1444 | var result = $injector.invoke(handler, handler, { $match: match }); 1445 | return isDefined(result) ? result : true; 1446 | } 1447 | 1448 | /** 1449 | * @ngdoc function 1450 | * @name ui.router.router.$urlRouterProvider#when 1451 | * @methodOf ui.router.router.$urlRouterProvider 1452 | * 1453 | * @description 1454 | * Registers a handler for a given url matching. if handle is a string, it is 1455 | * treated as a redirect, and is interpolated according to the syntax of match 1456 | * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise). 1457 | * 1458 | * If the handler is a function, it is injectable. It gets invoked if `$location` 1459 | * matches. You have the option of inject the match object as `$match`. 1460 | * 1461 | * The handler can return 1462 | * 1463 | * - **falsy** to indicate that the rule didn"t match after all, then `$urlRouter` 1464 | * will continue trying to find another one that matches. 1465 | * - **string** which is treated as a redirect and passed to `$location.url()` 1466 | * - **void** or any **truthy** value tells `$urlRouter` that the url was handled. 1467 | * 1468 | * @example 1469 | *
1470 |    * var app = angular.module("app", ["ui.router.router"]);
1471 |    *
1472 |    * app.config(function ($urlRouterProvider) {
1473 |    *   $urlRouterProvider.when($state.url, function ($match, $stateParams) {
1474 |    *     if ($state.$current.navigable !== state ||
1475 |    *         !equalForKeys($match, $stateParams) {
1476 |    *      $state.transitionTo(state, $match, false);
1477 |    *     }
1478 |    *   });
1479 |    * });
1480 |    * 
1481 | * 1482 | * @param {string|object} what The incoming path that you want to redirect. 1483 | * @param {string|object} handler The path you want to redirect your user to. 1484 | */ 1485 | this.when = function (what, handler) { 1486 | var redirect, handlerIsString = isString(handler); 1487 | if (isString(what)) what = $urlMatcherFactory.compile(what); 1488 | 1489 | if (!handlerIsString && !isFunction(handler) && !isArray(handler)) 1490 | throw new Error("invalid 'handler' in when()"); 1491 | 1492 | var strategies = { 1493 | matcher: function (what, handler) { 1494 | if (handlerIsString) { 1495 | redirect = $urlMatcherFactory.compile(handler); 1496 | handler = ["$match", function ($match) { return redirect.format($match); }]; 1497 | } 1498 | return extend(function ($injector, $location) { 1499 | return handleIfMatch($injector, handler, what.exec($location.path(), $location.search())); 1500 | }, { 1501 | prefix: isString(what.prefix) ? what.prefix : "" 1502 | }); 1503 | }, 1504 | regex: function (what, handler) { 1505 | if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky"); 1506 | 1507 | if (handlerIsString) { 1508 | redirect = handler; 1509 | handler = ["$match", function ($match) { return interpolate(redirect, $match); }]; 1510 | } 1511 | return extend(function ($injector, $location) { 1512 | return handleIfMatch($injector, handler, what.exec($location.path())); 1513 | }, { 1514 | prefix: regExpPrefix(what) 1515 | }); 1516 | } 1517 | }; 1518 | 1519 | var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp }; 1520 | 1521 | for (var n in check) { 1522 | if (check[n]) return this.rule(strategies[n](what, handler)); 1523 | } 1524 | 1525 | throw new Error("invalid 'what' in when()"); 1526 | }; 1527 | 1528 | /** 1529 | * @ngdoc function 1530 | * @name ui.router.router.$urlRouterProvider#deferIntercept 1531 | * @methodOf ui.router.router.$urlRouterProvider 1532 | * 1533 | * @description 1534 | * Disables (or enables) deferring location change interception. 1535 | * 1536 | * If you wish to customize the behavior of syncing the URL (for example, if you wish to 1537 | * defer a transition but maintain the current URL), call this method at configuration time. 1538 | * Then, at run time, call `$urlRouter.listen()` after you have configured your own 1539 | * `$locationChangeSuccess` event handler. 1540 | * 1541 | * @example 1542 | *
1543 |    * var app = angular.module("app", ["ui.router.router"]);
1544 |    *
1545 |    * app.config(function ($urlRouterProvider) {
1546 |    *
1547 |    *   // Prevent $urlRouter from automatically intercepting URL changes;
1548 |    *   // this allows you to configure custom behavior in between
1549 |    *   // location changes and route synchronization:
1550 |    *   $urlRouterProvider.deferIntercept();
1551 |    *
1552 |    * }).run(function ($rootScope, $urlRouter, UserService) {
1553 |    *
1554 |    *   $rootScope.$on("$locationChangeSuccess", function(e) {
1555 |    *     // UserService is an example service for managing user state
1556 |    *     if (UserService.isLoggedIn()) return;
1557 |    *
1558 |    *     // Prevent $urlRouter"s default handler from firing
1559 |    *     e.preventDefault();
1560 |    *
1561 |    *     UserService.handleLogin().then(function() {
1562 |    *       // Once the user has logged in, sync the current URL
1563 |    *       // to the router:
1564 |    *       $urlRouter.sync();
1565 |    *     });
1566 |    *   });
1567 |    *
1568 |    *   // Configures $urlRouter"s listener *after* your custom listener
1569 |    *   $urlRouter.listen();
1570 |    * });
1571 |    * 
1572 | * 1573 | * @param {boolean} defer Indicates whether to defer location change interception. Passing 1574 | no parameter is equivalent to `true`. 1575 | */ 1576 | this.deferIntercept = function (defer) { 1577 | if (defer === undefined) defer = true; 1578 | interceptDeferred = defer; 1579 | }; 1580 | 1581 | /** 1582 | * @ngdoc object 1583 | * @name ui.router.router.$urlRouter 1584 | * 1585 | * @requires $location 1586 | * @requires $rootScope 1587 | * @requires $injector 1588 | * @requires $browser 1589 | * 1590 | * @description 1591 | * 1592 | */ 1593 | this.$get = $get; 1594 | $get.$inject = ["$location", "$rootScope", "$injector", "$browser"]; 1595 | function $get( $location, $rootScope, $injector, $browser) { 1596 | 1597 | var baseHref = $browser.baseHref(), location = $location.url(); 1598 | 1599 | function appendBasePath(url, isHtml5, absolute) { 1600 | if (baseHref === "/") return url; 1601 | if (isHtml5) return baseHref.slice(0, -1) + url; 1602 | if (absolute) return baseHref.slice(1) + url; 1603 | return url; 1604 | } 1605 | 1606 | // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree 1607 | function update(evt) { 1608 | if (evt && evt.defaultPrevented) return; 1609 | 1610 | function check(rule) { 1611 | var handled = rule($injector, $location); 1612 | 1613 | if (!handled) return false; 1614 | if (isString(handled)) $location.replace().url(handled); 1615 | return true; 1616 | } 1617 | var n = rules.length, i; 1618 | 1619 | for (i = 0; i < n; i++) { 1620 | if (check(rules[i])) return; 1621 | } 1622 | // always check otherwise last to allow dynamic updates to the set of rules 1623 | if (otherwise) check(otherwise); 1624 | } 1625 | 1626 | function listen() { 1627 | listener = listener || $rootScope.$on("$locationChangeSuccess", update); 1628 | return listener; 1629 | } 1630 | 1631 | if (!interceptDeferred) listen(); 1632 | 1633 | return { 1634 | /** 1635 | * @ngdoc function 1636 | * @name ui.router.router.$urlRouter#sync 1637 | * @methodOf ui.router.router.$urlRouter 1638 | * 1639 | * @description 1640 | * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`. 1641 | * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event, 1642 | * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed 1643 | * with the transition by calling `$urlRouter.sync()`. 1644 | * 1645 | * @example 1646 | *
1647 |        * angular.module("app", ["ui.router"])
1648 |        *   .run(function($rootScope, $urlRouter) {
1649 |        *     $rootScope.$on("$locationChangeSuccess", function(evt) {
1650 |        *       // Halt state change from even starting
1651 |        *       evt.preventDefault();
1652 |        *       // Perform custom logic
1653 |        *       var meetsRequirement = ...
1654 |        *       // Continue with the update and state transition if logic allows
1655 |        *       if (meetsRequirement) $urlRouter.sync();
1656 |        *     });
1657 |        * });
1658 |        * 
1659 | */ 1660 | sync: function() { 1661 | update(); 1662 | }, 1663 | 1664 | listen: function() { 1665 | return listen(); 1666 | }, 1667 | 1668 | update: function(read) { 1669 | if (read) { 1670 | location = $location.url(); 1671 | return; 1672 | } 1673 | if ($location.url() === location) return; 1674 | 1675 | $location.url(location); 1676 | $location.replace(); 1677 | }, 1678 | 1679 | push: function(urlMatcher, params, options) { 1680 | $location.url(urlMatcher.format(params || {})); 1681 | if (options && options.replace) $location.replace(); 1682 | }, 1683 | 1684 | /** 1685 | * @ngdoc function 1686 | * @name ui.router.router.$urlRouter#href 1687 | * @methodOf ui.router.router.$urlRouter 1688 | * 1689 | * @description 1690 | * A URL generation method that returns the compiled URL for a given 1691 | * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters. 1692 | * 1693 | * @example 1694 | *
1695 |        * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), {
1696 |        *   person: "bob"
1697 |        * });
1698 |        * // $bob == "/about/bob";
1699 |        * 
1700 | * 1701 | * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate. 1702 | * @param {object=} params An object of parameter values to fill the matcher"s required parameters. 1703 | * @param {object=} options Options object. The options are: 1704 | * 1705 | * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl". 1706 | * 1707 | * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher` 1708 | */ 1709 | href: function(urlMatcher, params, options) { 1710 | if (!urlMatcher.validates(params)) return null; 1711 | 1712 | var isHtml5 = $locationProvider.html5Mode(); 1713 | var url = urlMatcher.format(params); 1714 | options = options || {}; 1715 | 1716 | if (!isHtml5 && url !== null) { 1717 | url = "#" + $locationProvider.hashPrefix() + url; 1718 | } 1719 | url = appendBasePath(url, isHtml5, options.absolute); 1720 | 1721 | if (!options.absolute || !url) { 1722 | return url; 1723 | } 1724 | 1725 | var slash = (!isHtml5 && url ? "/" : ""), port = $location.port(); 1726 | port = (port === 80 || port === 443 ? "" : ":" + port); 1727 | 1728 | return [$location.protocol(), "://", $location.host(), port, slash, url].join(""); 1729 | } 1730 | }; 1731 | } 1732 | } 1733 | 1734 | angular.module("ui.router.router").provider("$urlRouter", $UrlRouterProvider); 1735 | 1736 | /** 1737 | * @ngdoc object 1738 | * @name ui.router.state.$stateProvider 1739 | * 1740 | * @requires ui.router.router.$urlRouterProvider 1741 | * @requires ui.router.util.$urlMatcherFactoryProvider 1742 | * 1743 | * @description 1744 | * The new `$stateProvider` works similar to Angular"s v1 router, but it focuses purely 1745 | * on state. 1746 | * 1747 | * A state corresponds to a "place" in the application in terms of the overall UI and 1748 | * navigation. A state describes (via the controller / template / view properties) what 1749 | * the UI looks like and does at that place. 1750 | * 1751 | * States often have things in common, and the primary way of factoring out these 1752 | * commonalities in this model is via the state hierarchy, i.e. parent/child states aka 1753 | * nested states. 1754 | * 1755 | * The `$stateProvider` provides interfaces to declare these states for your app. 1756 | */ 1757 | $StateProvider.$inject = ["$urlRouterProvider", "$urlMatcherFactoryProvider"]; 1758 | function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { 1759 | 1760 | var root, states = {}, $state, queue = {}, abstractKey = "abstract"; 1761 | 1762 | // Builds state properties from definition passed to registerState() 1763 | var stateBuilder = { 1764 | 1765 | // Derive parent state from a hierarchical name only if "parent" is not explicitly defined. 1766 | // state.children = []; 1767 | // if (parent) parent.children.push(state); 1768 | parent: function(state) { 1769 | if (isDefined(state.parent) && state.parent) return findState(state.parent); 1770 | // regex matches any valid composite state name 1771 | // would match "contact.list" but not "contacts" 1772 | var compositeName = /^(.+)\.[^.]+$/.exec(state.name); 1773 | return compositeName ? findState(compositeName[1]) : root; 1774 | }, 1775 | 1776 | // inherit "data" from parent and override by own values (if any) 1777 | data: function(state) { 1778 | if (state.parent && state.parent.data) { 1779 | state.data = state.self.data = extend({}, state.parent.data, state.data); 1780 | } 1781 | return state.data; 1782 | }, 1783 | 1784 | // Build a URLMatcher if necessary, either via a relative or absolute URL 1785 | url: function(state) { 1786 | var url = state.url, config = { params: state.params || {} }; 1787 | 1788 | if (isString(url)) { 1789 | if (url.charAt(0) == "^") return $urlMatcherFactory.compile(url.substring(1), config); 1790 | return (state.parent.navigable || root).url.concat(url, config); 1791 | } 1792 | 1793 | if (!url || $urlMatcherFactory.isMatcher(url)) return url; 1794 | throw new Error("Invalid url '" + url + "' in state '" + state + "'"); 1795 | }, 1796 | 1797 | // Keep track of the closest ancestor state that has a URL (i.e. is navigable) 1798 | navigable: function(state) { 1799 | return state.url ? state : (state.parent ? state.parent.navigable : null); 1800 | }, 1801 | 1802 | // Derive parameters for this state and ensure they"re a super-set of parent"s parameters 1803 | params: function(state) { 1804 | if (!state.params) { 1805 | return state.url ? state.url.params : state.parent.params; 1806 | } 1807 | return state.params; 1808 | }, 1809 | 1810 | // If there is no explicit multi-view configuration, make one up so we don"t have 1811 | // to handle both cases in the view directive later. Note that having an explicit 1812 | // "views" property will mean the default unnamed view properties are ignored. This 1813 | // is also a good time to resolve view names to absolute names, so everything is a 1814 | // straight lookup at link time. 1815 | views: function(state) { 1816 | var views = {}; 1817 | 1818 | forEach(isDefined(state.views) ? state.views : { "": state }, function (view, name) { 1819 | if (name.indexOf("@") < 0) name += "@" + state.parent.name; 1820 | views[name] = view; 1821 | }); 1822 | return views; 1823 | }, 1824 | 1825 | ownParams: function(state) { 1826 | state.params = state.params || {}; 1827 | 1828 | if (!state.parent) { 1829 | return objectKeys(state.params); 1830 | } 1831 | var paramNames = {}; forEach(state.params, function (v, k) { paramNames[k] = true; }); 1832 | 1833 | forEach(state.parent.params, function (v, k) { 1834 | if (!paramNames[k]) { 1835 | throw new Error("Missing required parameter '" + k + "' in state '" + state.name + "'"); 1836 | } 1837 | paramNames[k] = false; 1838 | }); 1839 | var ownParams = []; 1840 | 1841 | forEach(paramNames, function (own, p) { 1842 | if (own) ownParams.push(p); 1843 | }); 1844 | return ownParams; 1845 | }, 1846 | 1847 | // Keep a full path from the root down to this state as this is needed for state activation. 1848 | path: function(state) { 1849 | return state.parent ? state.parent.path.concat(state) : []; // exclude root from path 1850 | }, 1851 | 1852 | // Speed up $state.contains() as it"s used a lot 1853 | includes: function(state) { 1854 | var includes = state.parent ? extend({}, state.parent.includes) : {}; 1855 | includes[state.name] = true; 1856 | return includes; 1857 | }, 1858 | 1859 | $delegates: {} 1860 | }; 1861 | 1862 | function isRelative(stateName) { 1863 | return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0; 1864 | } 1865 | 1866 | function findState(stateOrName, base) { 1867 | if (!stateOrName) return undefined; 1868 | 1869 | var isStr = isString(stateOrName), 1870 | name = isStr ? stateOrName : stateOrName.name, 1871 | path = isRelative(name); 1872 | 1873 | if (path) { 1874 | if (!base) throw new Error("No reference point given for path '" + name + "'"); 1875 | var rel = name.split("."), i = 0, pathLength = rel.length, current = base; 1876 | 1877 | for (; i < pathLength; i++) { 1878 | if (rel[i] === "" && i === 0) { 1879 | current = base; 1880 | continue; 1881 | } 1882 | if (rel[i] === "^") { 1883 | if (!current.parent) throw new Error("Path '" + name + "' not valid for state '" + base.name + "'"); 1884 | current = current.parent; 1885 | continue; 1886 | } 1887 | break; 1888 | } 1889 | rel = rel.slice(i).join("."); 1890 | name = current.name + (current.name && rel ? "." : "") + rel; 1891 | } 1892 | var state = states[name]; 1893 | 1894 | if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) { 1895 | return state; 1896 | } 1897 | return undefined; 1898 | } 1899 | 1900 | function queueState(parentName, state) { 1901 | if (!queue[parentName]) { 1902 | queue[parentName] = []; 1903 | } 1904 | queue[parentName].push(state); 1905 | } 1906 | 1907 | function registerState(state) { 1908 | // Wrap a new object around the state so we can store our private details easily. 1909 | state = inherit(state, { 1910 | self: state, 1911 | resolve: state.resolve || {}, 1912 | toString: function() { return this.name; } 1913 | }); 1914 | 1915 | var name = state.name; 1916 | if (!isString(name) || name.indexOf("@") >= 0) throw new Error("State must have a valid name"); 1917 | if (states.hasOwnProperty(name)) throw new Error("State '" + name + "' is already defined"); 1918 | 1919 | // Get parent name 1920 | var parentName = (name.indexOf(".") !== -1) ? name.substring(0, name.lastIndexOf(".")) 1921 | : (isString(state.parent)) ? state.parent 1922 | : ""; 1923 | 1924 | // If parent is not registered yet, add state to queue and register later 1925 | if (parentName && !states[parentName]) { 1926 | return queueState(parentName, state.self); 1927 | } 1928 | 1929 | for (var key in stateBuilder) { 1930 | if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]); 1931 | } 1932 | states[name] = state; 1933 | 1934 | // Register the state in the global state list and with $urlRouter if necessary. 1935 | if (!state[abstractKey] && state.url) { 1936 | $urlRouterProvider.when(state.url, ["$match", "$stateParams", function ($match, $stateParams) { 1937 | if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) { 1938 | $state.transitionTo(state, $match, { location: false }); 1939 | } 1940 | }]); 1941 | } 1942 | 1943 | // Register any queued children 1944 | if (queue[name]) { 1945 | for (var i = 0; i < queue[name].length; i++) { 1946 | registerState(queue[name][i]); 1947 | } 1948 | } 1949 | 1950 | return state; 1951 | } 1952 | 1953 | // Checks text to see if it looks like a glob. 1954 | function isGlob (text) { 1955 | return text.indexOf("*") > -1; 1956 | } 1957 | 1958 | // Returns true if glob matches current $state name. 1959 | function doesStateMatchGlob (glob) { 1960 | var globSegments = glob.split("."), 1961 | segments = $state.$current.name.split("."); 1962 | 1963 | //match greedy starts 1964 | if (globSegments[0] === "**") { 1965 | segments = segments.slice(segments.indexOf(globSegments[1])); 1966 | segments.unshift("**"); 1967 | } 1968 | //match greedy ends 1969 | if (globSegments[globSegments.length - 1] === "**") { 1970 | segments.splice(segments.indexOf(globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE); 1971 | segments.push("**"); 1972 | } 1973 | 1974 | if (globSegments.length != segments.length) { 1975 | return false; 1976 | } 1977 | 1978 | //match single stars 1979 | for (var i = 0, l = globSegments.length; i < l; i++) { 1980 | if (globSegments[i] === "*") { 1981 | segments[i] = "*"; 1982 | } 1983 | } 1984 | 1985 | return segments.join("") === globSegments.join(""); 1986 | } 1987 | 1988 | 1989 | // Implicit root state that is always active 1990 | root = registerState({ 1991 | name: "", 1992 | url: "^", 1993 | views: null, 1994 | "abstract": true 1995 | }); 1996 | root.navigable = null; 1997 | 1998 | 1999 | /** 2000 | * @ngdoc function 2001 | * @name ui.router.state.$stateProvider#decorator 2002 | * @methodOf ui.router.state.$stateProvider 2003 | * 2004 | * @description 2005 | * Allows you to extend (carefully) or override (at your own peril) the 2006 | * `stateBuilder` object used internally by `$stateProvider`. This can be used 2007 | * to add custom functionality to ui-router, for example inferring templateUrl 2008 | * based on the state name. 2009 | * 2010 | * When passing only a name, it returns the current (original or decorated) builder 2011 | * function that matches `name`. 2012 | * 2013 | * The builder functions that can be decorated are listed below. Though not all 2014 | * necessarily have a good use case for decoration, that is up to you to decide. 2015 | * 2016 | * In addition, users can attach custom decorators, which will generate new 2017 | * properties within the state"s internal definition. There is currently no clear 2018 | * use-case for this beyond accessing internal states (i.e. $state.$current), 2019 | * however, expect this to become increasingly relevant as we introduce additional 2020 | * meta-programming features. 2021 | * 2022 | * **Warning**: Decorators should not be interdependent because the order of 2023 | * execution of the builder functions in non-deterministic. Builder functions 2024 | * should only be dependent on the state definition object and super function. 2025 | * 2026 | * 2027 | * Existing builder functions and current return values: 2028 | * 2029 | * - **parent** `{object}` - returns the parent state object. 2030 | * - **data** `{object}` - returns state data, including any inherited data that is not 2031 | * overridden by own values (if any). 2032 | * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher} 2033 | * or `null`. 2034 | * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is 2035 | * navigable). 2036 | * - **params** `{object}` - returns an array of state params that are ensured to 2037 | * be a super-set of parent"s params. 2038 | * - **views** `{object}` - returns a views object where each key is an absolute view 2039 | * name (i.e. "viewName@stateName") and each value is the config object 2040 | * (template, controller) for the view. Even when you don"t use the views object 2041 | * explicitly on a state config, one is still created for you internally. 2042 | * So by decorating this builder function you have access to decorating template 2043 | * and controller properties. 2044 | * - **ownParams** `{object}` - returns an array of params that belong to the state, 2045 | * not including any params defined by ancestor states. 2046 | * - **path** `{string}` - returns the full path from the root down to this state. 2047 | * Needed for state activation. 2048 | * - **includes** `{object}` - returns an object that includes every state that 2049 | * would pass a `$state.includes()` test. 2050 | * 2051 | * @example 2052 | *
2053 |    * // Override the internal "views" builder with a function that takes the state
2054 |    * // definition, and a reference to the internal function being overridden:
2055 |    * $stateProvider.decorator("views", function (state, parent) {
2056 |    *   var result = {},
2057 |    *       views = parent(state);
2058 |    *
2059 |    *   angular.forEach(views, function (config, name) {
2060 |    *     var autoName = (state.name + "." + name).replace(".", "/");
2061 |    *     config.templateUrl = config.templateUrl || "/partials/" + autoName + ".html";
2062 |    *     result[name] = config;
2063 |    *   });
2064 |    *   return result;
2065 |    * });
2066 |    *
2067 |    * $stateProvider.state("home", {
2068 |    *   views: {
2069 |    *     "contact.list": { controller: "ListController" },
2070 |    *     "contact.item": { controller: "ItemController" }
2071 |    *   }
2072 |    * });
2073 |    *
2074 |    * // ...
2075 |    *
2076 |    * $state.go("home");
2077 |    * // Auto-populates list and item views with /partials/home/contact/list.html,
2078 |    * // and /partials/home/contact/item.html, respectively.
2079 |    * 
2080 | * 2081 | * @param {string} name The name of the builder function to decorate. 2082 | * @param {object} func A function that is responsible for decorating the original 2083 | * builder function. The function receives two parameters: 2084 | * 2085 | * - `{object}` - state - The state config object. 2086 | * - `{object}` - super - The original builder function. 2087 | * 2088 | * @return {object} $stateProvider - $stateProvider instance 2089 | */ 2090 | this.decorator = decorator; 2091 | function decorator(name, func) { 2092 | /*jshint validthis: true */ 2093 | if (isString(name) && !isDefined(func)) { 2094 | return stateBuilder[name]; 2095 | } 2096 | if (!isFunction(func) || !isString(name)) { 2097 | return this; 2098 | } 2099 | if (stateBuilder[name] && !stateBuilder.$delegates[name]) { 2100 | stateBuilder.$delegates[name] = stateBuilder[name]; 2101 | } 2102 | stateBuilder[name] = func; 2103 | return this; 2104 | } 2105 | 2106 | /** 2107 | * @ngdoc function 2108 | * @name ui.router.state.$stateProvider#state 2109 | * @methodOf ui.router.state.$stateProvider 2110 | * 2111 | * @description 2112 | * Registers a state configuration under a given state name. The stateConfig object 2113 | * has the following acceptable properties. 2114 | * 2115 | * 2116 | * 2117 | * - **`template`** - {string|function=} - html template as a string or a function that returns 2118 | * an html template as a string which should be used by the uiView directives. This property 2119 | * takes precedence over templateUrl. 2120 | * 2121 | * If `template` is a function, it will be called with the following parameters: 2122 | * 2123 | * - {array.<object>} - state parameters extracted from the current $location.path() by 2124 | * applying the current state 2125 | * 2126 | * 2127 | * 2128 | * - **`templateUrl`** - {string|function=} - path or function that returns a path to an html 2129 | * template that should be used by uiView. 2130 | * 2131 | * If `templateUrl` is a function, it will be called with the following parameters: 2132 | * 2133 | * - {array.<object>} - state parameters extracted from the current $location.path() by 2134 | * applying the current state 2135 | * 2136 | * 2137 | * 2138 | * - **`templateProvider`** - {function=} - Provider function that returns HTML content 2139 | * string. 2140 | * 2141 | * 2142 | * 2143 | * - **`controller`** - {string|function=} - Controller fn that should be associated with newly 2144 | * related scope or the name of a registered controller if passed as a string. 2145 | * 2146 | * 2147 | * 2148 | * - **`controllerProvider`** - {function=} - Injectable provider function that returns 2149 | * the actual controller or string. 2150 | * 2151 | * 2152 | * 2153 | * - **`controllerAs`** – {string=} – A controller alias name. If present the controller will be 2154 | * published to scope under the controllerAs name. 2155 | * 2156 | * 2157 | * 2158 | * - **`resolve`** - {object.<string, function>=} - An optional map of dependencies which 2159 | * should be injected into the controller. If any of these dependencies are promises, 2160 | * the router will wait for them all to be resolved or one to be rejected before the 2161 | * controller is instantiated. If all the promises are resolved successfully, the values 2162 | * of the resolved promises are injected and $stateChangeSuccess event is fired. If any 2163 | * of the promises are rejected the $stateChangeError event is fired. The map object is: 2164 | * 2165 | * - key - {string}: name of dependency to be injected into controller 2166 | * - factory - {string|function}: If string then it is alias for service. Otherwise if function, 2167 | * it is injected and return value it treated as dependency. If result is a promise, it is 2168 | * resolved before its value is injected into controller. 2169 | * 2170 | * 2171 | * 2172 | * - **`url`** - {string=} - A url with optional parameters. When a state is navigated or 2173 | * transitioned to, the `$stateParams` service will be populated with any 2174 | * parameters that were passed. 2175 | * 2176 | * 2177 | * 2178 | * - **`params`** - {object=} - An array of parameter names or regular expressions. Only 2179 | * use this within a state if you are not using url. Otherwise you can specify your 2180 | * parameters within the url. When a state is navigated or transitioned to, the 2181 | * $stateParams service will be populated with any parameters that were passed. 2182 | * 2183 | * 2184 | * 2185 | * - **`views`** - {object=} - Use the views property to set up multiple views or to target views 2186 | * manually/explicitly. 2187 | * 2188 | * 2189 | * 2190 | * - **`abstract`** - {boolean=} - An abstract state will never be directly activated, 2191 | * but can provide inherited properties to its common children states. 2192 | * 2193 | * 2194 | * 2195 | * - **`onEnter`** - {object=} - Callback function for when a state is entered. Good way 2196 | * to trigger an action or dispatch an event, such as opening a dialog. 2197 | * If minifying your scripts, make sure to use the `["injection1", "injection2", function(injection1, injection2){}]` syntax. 2198 | * 2199 | * 2200 | * 2201 | * - **`onExit`** - {object=} - Callback function for when a state is exited. Good way to 2202 | * trigger an action or dispatch an event, such as opening a dialog. 2203 | * If minifying your scripts, make sure to use the `["injection1", "injection2", function(injection1, injection2){}]` syntax. 2204 | * 2205 | * 2206 | * 2207 | * - **`reloadOnSearch = true`** - {boolean=} - If `false`, will not retrigger the same state 2208 | * just because a search/query parameter has changed (via $location.search() or $location.hash()). 2209 | * Useful for when you"d like to modify $location.search() without triggering a reload. 2210 | * 2211 | * 2212 | * 2213 | * - **`data`** - {object=} - Arbitrary data object, useful for custom configuration. 2214 | * 2215 | * @example 2216 | *
2217 |    * // Some state name examples
2218 |    *
2219 |    * // stateName can be a single top-level name (must be unique).
2220 |    * $stateProvider.state("home", {});
2221 |    *
2222 |    * // Or it can be a nested state name. This state is a child of the
2223 |    * // above "home" state.
2224 |    * $stateProvider.state("home.newest", {});
2225 |    *
2226 |    * // Nest states as deeply as needed.
2227 |    * $stateProvider.state("home.newest.abc.xyz.inception", {});
2228 |    *
2229 |    * // state() returns $stateProvider, so you can chain state declarations.
2230 |    * $stateProvider
2231 |    *   .state("home", {})
2232 |    *   .state("about", {})
2233 |    *   .state("contacts", {});
2234 |    * 
2235 | * 2236 | * @param {string} name A unique state name, e.g. "home", "about", "contacts". 2237 | * To create a parent/child state use a dot, e.g. "about.sales", "home.newest". 2238 | * @param {object} definition State configuration object. 2239 | */ 2240 | this.state = state; 2241 | function state(name, definition) { 2242 | /*jshint validthis: true */ 2243 | if (isObject(name)) definition = name; 2244 | else definition.name = name; 2245 | registerState(definition); 2246 | return this; 2247 | } 2248 | 2249 | /** 2250 | * @ngdoc object 2251 | * @name ui.router.state.$state 2252 | * 2253 | * @requires $rootScope 2254 | * @requires $q 2255 | * @requires ui.router.state.$view 2256 | * @requires $injector 2257 | * @requires ui.router.util.$resolve 2258 | * @requires ui.router.state.$stateParams 2259 | * @requires ui.router.router.$urlRouter 2260 | * 2261 | * @property {object} params A param object, e.g. {sectionId: section.id)}, that 2262 | * you"d like to test against the current active state. 2263 | * @property {object} current A reference to the state"s config object. However 2264 | * you passed it in. Useful for accessing custom data. 2265 | * @property {object} transition Currently pending transition. A promise that"ll 2266 | * resolve or reject. 2267 | * 2268 | * @description 2269 | * `$state` service is responsible for representing states as well as transitioning 2270 | * between them. It also provides interfaces to ask for current state or even states 2271 | * you"re coming from. 2272 | */ 2273 | this.$get = $get; 2274 | $get.$inject = ["$rootScope", "$q", "$view", "$injector", "$resolve", "$stateParams", "$urlRouter"]; 2275 | function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter) { 2276 | 2277 | var TransitionSuperseded = $q.reject(new Error("transition superseded")); 2278 | var TransitionPrevented = $q.reject(new Error("transition prevented")); 2279 | var TransitionAborted = $q.reject(new Error("transition aborted")); 2280 | var TransitionFailed = $q.reject(new Error("transition failed")); 2281 | 2282 | // Handles the case where a state which is the target of a transition is not found, and the user 2283 | // can optionally retry or defer the transition 2284 | function handleRedirect(redirect, state, params, options) { 2285 | /** 2286 | * @ngdoc event 2287 | * @name ui.router.state.$state#$stateNotFound 2288 | * @eventOf ui.router.state.$state 2289 | * @eventType broadcast on root scope 2290 | * @description 2291 | * Fired when a requested state **cannot be found** using the provided state name during transition. 2292 | * The event is broadcast allowing any handlers a single chance to deal with the error (usually by 2293 | * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler, 2294 | * you can see its three properties in the example. You can use `event.preventDefault()` to abort the 2295 | * transition and the promise returned from `go` will be rejected with a `"transition aborted"` value. 2296 | * 2297 | * @param {Object} event Event object. 2298 | * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties. 2299 | * @param {State} fromState Current state object. 2300 | * @param {Object} fromParams Current state params. 2301 | * 2302 | * @example 2303 | * 2304 | *
2305 |        * // somewhere, assume lazy.state has not been defined
2306 |        * $state.go("lazy.state", {a:1, b:2}, {inherit:false});
2307 |        *
2308 |        * // somewhere else
2309 |        * $scope.$on("$stateNotFound",
2310 |        * function(event, unfoundState, fromState, fromParams){
2311 |        *     console.log(unfoundState.to); // "lazy.state"
2312 |        *     console.log(unfoundState.toParams); // {a:1, b:2}
2313 |        *     console.log(unfoundState.options); // {inherit:false} + default options
2314 |        * })
2315 |        * 
2316 | */ 2317 | var evt = $rootScope.$broadcast("$stateNotFound", redirect, state, params); 2318 | 2319 | if (evt.defaultPrevented) { 2320 | $urlRouter.update(); 2321 | return TransitionAborted; 2322 | } 2323 | 2324 | if (!evt.retry) { 2325 | return null; 2326 | } 2327 | 2328 | // Allow the handler to return a promise to defer state lookup retry 2329 | if (options.$retry) { 2330 | $urlRouter.update(); 2331 | return TransitionFailed; 2332 | } 2333 | var retryTransition = $state.transition = $q.when(evt.retry); 2334 | 2335 | retryTransition.then(function() { 2336 | if (retryTransition !== $state.transition) return TransitionSuperseded; 2337 | redirect.options.$retry = true; 2338 | return $state.transitionTo(redirect.to, redirect.toParams, redirect.options); 2339 | }, function() { 2340 | return TransitionAborted; 2341 | }); 2342 | $urlRouter.update(); 2343 | 2344 | return retryTransition; 2345 | } 2346 | 2347 | root.locals = { resolve: null, globals: { $stateParams: {} } }; 2348 | 2349 | $state = { 2350 | params: {}, 2351 | current: root.self, 2352 | $current: root, 2353 | transition: null 2354 | }; 2355 | 2356 | /** 2357 | * @ngdoc function 2358 | * @name ui.router.state.$state#reload 2359 | * @methodOf ui.router.state.$state 2360 | * 2361 | * @description 2362 | * A method that force reloads the current state. All resolves are re-resolved, events are not re-fired, 2363 | * and controllers reinstantiated (bug with controllers reinstantiating right now, fixing soon). 2364 | * 2365 | * @example 2366 | *
2367 |      * var app angular.module("app", ["ui.router"]);
2368 |      *
2369 |      * app.controller("ctrl", function ($scope, $state) {
2370 |      *   $scope.reload = function(){
2371 |      *     $state.reload();
2372 |      *   }
2373 |      * });
2374 |      * 
2375 | * 2376 | * `reload()` is just an alias for: 2377 | *
2378 |      * $state.transitionTo($state.current, $stateParams, {
2379 |      *   reload: true, inherit: false, notify: false
2380 |      * });
2381 |      * 
2382 | */ 2383 | $state.reload = function reload() { 2384 | $state.transitionTo($state.current, $stateParams, { reload: true, inherit: false, notify: false }); 2385 | }; 2386 | 2387 | /** 2388 | * @ngdoc function 2389 | * @name ui.router.state.$state#go 2390 | * @methodOf ui.router.state.$state 2391 | * 2392 | * @description 2393 | * Convenience method for transitioning to a new state. `$state.go` calls 2394 | * `$state.transitionTo` internally but automatically sets options to 2395 | * `{ location: true, inherit: true, relative: $state.$current, notify: true }`. 2396 | * This allows you to easily use an absolute or relative to path and specify 2397 | * only the parameters you"d like to update (while letting unspecified parameters 2398 | * inherit from the currently active ancestor states). 2399 | * 2400 | * @example 2401 | *
2402 |      * var app = angular.module("app", ["ui.router"]);
2403 |      *
2404 |      * app.controller("ctrl", function ($scope, $state) {
2405 |      *   $scope.changeState = function () {
2406 |      *     $state.go("contact.detail");
2407 |      *   };
2408 |      * });
2409 |      * 
2410 | * 2411 | * 2412 | * @param {string} to Absolute state name or relative state path. Some examples: 2413 | * 2414 | * - `$state.go("contact.detail")` - will go to the `contact.detail` state 2415 | * - `$state.go("^")` - will go to a parent state 2416 | * - `$state.go("^.sibling")` - will go to a sibling state 2417 | * - `$state.go(".child.grandchild")` - will go to grandchild state 2418 | * 2419 | * @param {object=} params A map of the parameters that will be sent to the state, 2420 | * will populate $stateParams. Any parameters that are not specified will be inherited from currently 2421 | * defined parameters. This allows, for example, going to a sibling state that shares parameters 2422 | * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e. 2423 | * transitioning to a sibling will get you the parameters for all parents, transitioning to a child 2424 | * will get you all current parameters, etc. 2425 | * @param {object=} options Options object. The options are: 2426 | * 2427 | * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false` 2428 | * will not. If string, must be `"replace"`, which will update url and also replace last history record. 2429 | * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url. 2430 | * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g "^"), 2431 | * defines which state to be relative from. 2432 | * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events. 2433 | * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params 2434 | * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you"d 2435 | * use this when you want to force a reload when *everything* is the same, including search params. 2436 | * 2437 | * @returns {promise} A promise representing the state of the new transition. 2438 | * 2439 | * Possible success values: 2440 | * 2441 | * - $state.current 2442 | * 2443 | *
Possible rejection values: 2444 | * 2445 | * - "transition superseded" - when a newer transition has been started after this one 2446 | * - "transition prevented" - when `event.preventDefault()` has been called in a `$stateChangeStart` listener 2447 | * - "transition aborted" - when `event.preventDefault()` has been called in a `$stateNotFound` listener or 2448 | * when a `$stateNotFound` `event.retry` promise errors. 2449 | * - "transition failed" - when a state has been unsuccessfully found after 2 tries. 2450 | * - *resolve error* - when an error has occurred with a `resolve` 2451 | * 2452 | */ 2453 | $state.go = function go(to, params, options) { 2454 | return $state.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options)); 2455 | }; 2456 | 2457 | /** 2458 | * @ngdoc function 2459 | * @name ui.router.state.$state#transitionTo 2460 | * @methodOf ui.router.state.$state 2461 | * 2462 | * @description 2463 | * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go} 2464 | * uses `transitionTo` internally. `$state.go` is recommended in most situations. 2465 | * 2466 | * @example 2467 | *
2468 |      * var app = angular.module("app", ["ui.router"]);
2469 |      *
2470 |      * app.controller("ctrl", function ($scope, $state) {
2471 |      *   $scope.changeState = function () {
2472 |      *     $state.transitionTo("contact.detail");
2473 |      *   };
2474 |      * });
2475 |      * 
2476 | * 2477 | * @param {string} to State name. 2478 | * @param {object=} toParams A map of the parameters that will be sent to the state, 2479 | * will populate $stateParams. 2480 | * @param {object=} options Options object. The options are: 2481 | * 2482 | * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false` 2483 | * will not. If string, must be `"replace"`, which will update url and also replace last history record. 2484 | * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url. 2485 | * - **`relative`** - {object=}, When transitioning with relative path (e.g "^"), 2486 | * defines which state to be relative from. 2487 | * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events. 2488 | * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params 2489 | * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you"d 2490 | * use this when you want to force a reload when *everything* is the same, including search params. 2491 | * 2492 | * @returns {promise} A promise representing the state of the new transition. See 2493 | * {@link ui.router.state.$state#methods_go $state.go}. 2494 | */ 2495 | $state.transitionTo = function transitionTo(to, toParams, options) { 2496 | toParams = toParams || {}; 2497 | options = extend({ 2498 | location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false 2499 | }, options || {}); 2500 | 2501 | var from = $state.$current, fromParams = $state.params, fromPath = from.path; 2502 | var evt, toState = findState(to, options.relative); 2503 | 2504 | if (!isDefined(toState)) { 2505 | var redirect = { to: to, toParams: toParams, options: options }; 2506 | var redirectResult = handleRedirect(redirect, from.self, fromParams, options); 2507 | 2508 | if (redirectResult) { 2509 | return redirectResult; 2510 | } 2511 | 2512 | // Always retry once if the $stateNotFound was not prevented 2513 | // (handles either redirect changed or state lazy-definition) 2514 | to = redirect.to; 2515 | toParams = redirect.toParams; 2516 | options = redirect.options; 2517 | toState = findState(to, options.relative); 2518 | 2519 | if (!isDefined(toState)) { 2520 | if (!options.relative) throw new Error("No such state '" + to + "'"); 2521 | throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'"); 2522 | } 2523 | } 2524 | if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'"); 2525 | if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState); 2526 | to = toState; 2527 | 2528 | var toPath = to.path; 2529 | 2530 | // Starting from the root of the path, keep all levels that haven"t changed 2531 | var keep = 0, state = toPath[keep], locals = root.locals, toLocals = []; 2532 | 2533 | if (!options.reload) { 2534 | while (state && state === fromPath[keep] && equalForKeys(toParams, fromParams, state.ownParams)) { 2535 | locals = toLocals[keep] = state.locals; 2536 | keep++; 2537 | state = toPath[keep]; 2538 | } 2539 | } 2540 | 2541 | // If we"re going to the same state and all locals are kept, we"ve got nothing to do. 2542 | // But clear "transition", as we still want to cancel any other pending transitions. 2543 | // TODO: We may not want to bump "transition" if we"re called from a location change 2544 | // that we"ve initiated ourselves, because we might accidentally abort a legitimate 2545 | // transition initiated from code? 2546 | if (shouldTriggerReload(to, from, locals, options)) { 2547 | if (to.self.reloadOnSearch !== false) $urlRouter.update(); 2548 | $state.transition = null; 2549 | return $q.when($state.current); 2550 | } 2551 | 2552 | // Filter parameters before we pass them to event handlers etc. 2553 | toParams = filterByKeys(objectKeys(to.params), toParams || {}); 2554 | 2555 | // Broadcast start event and cancel the transition if requested 2556 | if (options.notify) { 2557 | /** 2558 | * @ngdoc event 2559 | * @name ui.router.state.$state#$stateChangeStart 2560 | * @eventOf ui.router.state.$state 2561 | * @eventType broadcast on root scope 2562 | * @description 2563 | * Fired when the state transition **begins**. You can use `event.preventDefault()` 2564 | * to prevent the transition from happening and then the transition promise will be 2565 | * rejected with a `"transition prevented"` value. 2566 | * 2567 | * @param {Object} event Event object. 2568 | * @param {State} toState The state being transitioned to. 2569 | * @param {Object} toParams The params supplied to the `toState`. 2570 | * @param {State} fromState The current state, pre-transition. 2571 | * @param {Object} fromParams The params supplied to the `fromState`. 2572 | * 2573 | * @example 2574 | * 2575 | *
2576 |          * $rootScope.$on("$stateChangeStart",
2577 |          * function(event, toState, toParams, fromState, fromParams){
2578 |          *     event.preventDefault();
2579 |          *     // transitionTo() promise will be rejected with
2580 |          *     // a "transition prevented" error
2581 |          * })
2582 |          * 
2583 | */ 2584 | if ($rootScope.$broadcast("$stateChangeStart", to.self, toParams, from.self, fromParams).defaultPrevented) { 2585 | $urlRouter.update(); 2586 | return TransitionPrevented; 2587 | } 2588 | } 2589 | 2590 | // Resolve locals for the remaining states, but don"t update any global state just 2591 | // yet -- if anything fails to resolve the current state needs to remain untouched. 2592 | // We also set up an inheritance chain for the locals here. This allows the view directive 2593 | // to quickly look up the correct definition for each view in the current state. Even 2594 | // though we create the locals object itself outside resolveState(), it is initially 2595 | // empty and gets filled asynchronously. We need to keep track of the promise for the 2596 | // (fully resolved) current locals, and pass this down the chain. 2597 | var resolved = $q.when(locals); 2598 | 2599 | for (var l = keep; l < toPath.length; l++, state = toPath[l]) { 2600 | locals = toLocals[l] = inherit(locals); 2601 | resolved = resolveState(state, toParams, state === to, resolved, locals); 2602 | } 2603 | 2604 | // Once everything is resolved, we are ready to perform the actual transition 2605 | // and return a promise for the new state. We also keep track of what the 2606 | // current promise is, so that we can detect overlapping transitions and 2607 | // keep only the outcome of the last transition. 2608 | var transition = $state.transition = resolved.then(function () { 2609 | var l, entering, exiting; 2610 | 2611 | if ($state.transition !== transition) return TransitionSuperseded; 2612 | 2613 | // Exit "from" states not kept 2614 | for (l = fromPath.length - 1; l >= keep; l--) { 2615 | exiting = fromPath[l]; 2616 | if (exiting.self.onExit) { 2617 | $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals); 2618 | } 2619 | exiting.locals = null; 2620 | } 2621 | 2622 | // Enter "to" states not kept 2623 | for (l = keep; l < toPath.length; l++) { 2624 | entering = toPath[l]; 2625 | entering.locals = toLocals[l]; 2626 | if (entering.self.onEnter) { 2627 | $injector.invoke(entering.self.onEnter, entering.self, entering.locals.globals); 2628 | } 2629 | } 2630 | 2631 | // Run it again, to catch any transitions in callbacks 2632 | if ($state.transition !== transition) return TransitionSuperseded; 2633 | 2634 | // Update globals in $state 2635 | $state.$current = to; 2636 | $state.current = to.self; 2637 | $state.params = toParams; 2638 | copy($state.params, $stateParams); 2639 | $state.transition = null; 2640 | 2641 | if (options.location && to.navigable) { 2642 | $urlRouter.push(to.navigable.url, to.navigable.locals.globals.$stateParams, { 2643 | replace: options.location === "replace" 2644 | }); 2645 | } 2646 | 2647 | if (options.notify) { 2648 | /** 2649 | * @ngdoc event 2650 | * @name ui.router.state.$state#$stateChangeSuccess 2651 | * @eventOf ui.router.state.$state 2652 | * @eventType broadcast on root scope 2653 | * @description 2654 | * Fired once the state transition is **complete**. 2655 | * 2656 | * @param {Object} event Event object. 2657 | * @param {State} toState The state being transitioned to. 2658 | * @param {Object} toParams The params supplied to the `toState`. 2659 | * @param {State} fromState The current state, pre-transition. 2660 | * @param {Object} fromParams The params supplied to the `fromState`. 2661 | */ 2662 | $rootScope.$broadcast("$stateChangeSuccess", to.self, toParams, from.self, fromParams); 2663 | } 2664 | $urlRouter.update(true); 2665 | 2666 | return $state.current; 2667 | }, function (error) { 2668 | if ($state.transition !== transition) return TransitionSuperseded; 2669 | 2670 | $state.transition = null; 2671 | /** 2672 | * @ngdoc event 2673 | * @name ui.router.state.$state#$stateChangeError 2674 | * @eventOf ui.router.state.$state 2675 | * @eventType broadcast on root scope 2676 | * @description 2677 | * Fired when an **error occurs** during transition. It"s important to note that if you 2678 | * have any errors in your resolve functions (javascript errors, non-existent services, etc) 2679 | * they will not throw traditionally. You must listen for this $stateChangeError event to 2680 | * catch **ALL** errors. 2681 | * 2682 | * @param {Object} event Event object. 2683 | * @param {State} toState The state being transitioned to. 2684 | * @param {Object} toParams The params supplied to the `toState`. 2685 | * @param {State} fromState The current state, pre-transition. 2686 | * @param {Object} fromParams The params supplied to the `fromState`. 2687 | * @param {Error} error The resolve error object. 2688 | */ 2689 | evt = $rootScope.$broadcast("$stateChangeError", to.self, toParams, from.self, fromParams, error); 2690 | 2691 | if (!evt.defaultPrevented) { 2692 | $urlRouter.update(); 2693 | } 2694 | 2695 | return $q.reject(error); 2696 | }); 2697 | 2698 | return transition; 2699 | }; 2700 | 2701 | /** 2702 | * @ngdoc function 2703 | * @name ui.router.state.$state#is 2704 | * @methodOf ui.router.state.$state 2705 | * 2706 | * @description 2707 | * Similar to {@link ui.router.state.$state#methods_includes $state.includes}, 2708 | * but only checks for the full state name. If params is supplied then it will be 2709 | * tested for strict equality against the current active params object, so all params 2710 | * must match with none missing and no extras. 2711 | * 2712 | * @example 2713 | *
2714 |      * $state.$current.name = "contacts.details.item";
2715 |      *
2716 |      * // absolute name
2717 |      * $state.is("contact.details.item"); // returns true
2718 |      * $state.is(contactDetailItemStateObject); // returns true
2719 |      *
2720 |      * // relative name (. and ^), typically from a template
2721 |      * // E.g. from the "contacts.details" template
2722 |      * 
Item
2723 | *
2724 | * 2725 | * @param {string|object} stateName The state name (absolute or relative) or state object you"d like to check. 2726 | * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you"d like 2727 | * to test against the current active state. 2728 | * @returns {boolean} Returns true if it is the state. 2729 | */ 2730 | $state.is = function is(stateOrName, params) { 2731 | var state = findState(stateOrName); 2732 | 2733 | if (!isDefined(state)) { 2734 | return undefined; 2735 | } 2736 | 2737 | if ($state.$current !== state) { 2738 | return false; 2739 | } 2740 | 2741 | return isDefined(params) && params !== null ? angular.equals($stateParams, params) : true; 2742 | }; 2743 | 2744 | /** 2745 | * @ngdoc function 2746 | * @name ui.router.state.$state#includes 2747 | * @methodOf ui.router.state.$state 2748 | * 2749 | * @description 2750 | * A method to determine if the current active state is equal to or is the child of the 2751 | * state stateName. If any params are passed then they will be tested for a match as well. 2752 | * Not all the parameters need to be passed, just the ones you"d like to test for equality. 2753 | * 2754 | * @example 2755 | * Partial and relative names 2756 | *
2757 |      * $state.$current.name = "contacts.details.item";
2758 |      *
2759 |      * // Using partial names
2760 |      * $state.includes("contacts"); // returns true
2761 |      * $state.includes("contacts.details"); // returns true
2762 |      * $state.includes("contacts.details.item"); // returns true
2763 |      * $state.includes("contacts.list"); // returns false
2764 |      * $state.includes("about"); // returns false
2765 |      *
2766 |      * // Using relative names (. and ^), typically from a template
2767 |      * // E.g. from the "contacts.details" template
2768 |      * 
Item
2769 | *
2770 | * 2771 | * Basic globbing patterns 2772 | *
2773 |      * $state.$current.name = "contacts.details.item.url";
2774 |      *
2775 |      * $state.includes("*.details.*.*"); // returns true
2776 |      * $state.includes("*.details.**"); // returns true
2777 |      * $state.includes("**.item.**"); // returns true
2778 |      * $state.includes("*.details.item.url"); // returns true
2779 |      * $state.includes("*.details.*.url"); // returns true
2780 |      * $state.includes("*.details.*"); // returns false
2781 |      * $state.includes("item.**"); // returns false
2782 |      * 
2783 | * 2784 | * @param {string} stateOrName A partial name, relative name, or glob pattern 2785 | * to be searched for within the current state name. 2786 | * @param {object} params A param object, e.g. `{sectionId: section.id}`, 2787 | * that you"d like to test against the current active state. 2788 | * @returns {boolean} Returns true if it does include the state 2789 | */ 2790 | $state.includes = function includes(stateOrName, params) { 2791 | if (isString(stateOrName) && isGlob(stateOrName)) { 2792 | if (!doesStateMatchGlob(stateOrName)) { 2793 | return false; 2794 | } 2795 | stateOrName = $state.$current.name; 2796 | } 2797 | var state = findState(stateOrName); 2798 | 2799 | if (!isDefined(state)) { 2800 | return undefined; 2801 | } 2802 | if (!isDefined($state.$current.includes[state.name])) { 2803 | return false; 2804 | } 2805 | return equalForKeys(params, $stateParams); 2806 | }; 2807 | 2808 | 2809 | /** 2810 | * @ngdoc function 2811 | * @name ui.router.state.$state#href 2812 | * @methodOf ui.router.state.$state 2813 | * 2814 | * @description 2815 | * A url generation method that returns the compiled url for the given state populated with the given params. 2816 | * 2817 | * @example 2818 | *
2819 |      * expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob");
2820 |      * 
2821 | * 2822 | * @param {string|object} stateOrName The state name or state object you"d like to generate a url from. 2823 | * @param {object=} params An object of parameter values to fill the state"s required parameters. 2824 | * @param {object=} options Options object. The options are: 2825 | * 2826 | * - **`lossy`** - {boolean=true} - If true, and if there is no url associated with the state provided in the 2827 | * first parameter, then the constructed href url will be built from the first navigable ancestor (aka 2828 | * ancestor with a valid url). 2829 | * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url. 2830 | * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g "^"), 2831 | * defines which state to be relative from. 2832 | * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl". 2833 | * 2834 | * @returns {string} compiled state url 2835 | */ 2836 | $state.href = function href(stateOrName, params, options) { 2837 | options = extend({ 2838 | lossy: true, 2839 | inherit: true, 2840 | absolute: false, 2841 | relative: $state.$current 2842 | }, options || {}); 2843 | 2844 | var state = findState(stateOrName, options.relative); 2845 | 2846 | if (!isDefined(state)) return null; 2847 | if (options.inherit) params = inheritParams($stateParams, params || {}, $state.$current, state); 2848 | 2849 | var nav = (state && options.lossy) ? state.navigable : state; 2850 | 2851 | if (!nav || !nav.url) { 2852 | return null; 2853 | } 2854 | return $urlRouter.href(nav.url, filterByKeys(objectKeys(state.params), params || {}), { 2855 | absolute: options.absolute 2856 | }); 2857 | }; 2858 | 2859 | /** 2860 | * @ngdoc function 2861 | * @name ui.router.state.$state#get 2862 | * @methodOf ui.router.state.$state 2863 | * 2864 | * @description 2865 | * Returns the state configuration object for any specific state or all states. 2866 | * 2867 | * @param {string|Sbject=} stateOrName (absolute or relative) If provided, will only get the config for 2868 | * the requested state. If not provided, returns an array of ALL state configs. 2869 | * @returns {Object|Array} State configuration object or array of all objects. 2870 | */ 2871 | $state.get = function (stateOrName, context) { 2872 | if (arguments.length === 0) return objectKeys(states).map(function(name) { return states[name].self; }); 2873 | var state = findState(stateOrName, context); 2874 | return (state && state.self) ? state.self : null; 2875 | }; 2876 | 2877 | function resolveState(state, params, paramsAreFiltered, inherited, dst) { 2878 | // Make a restricted $stateParams with only the parameters that apply to this state if 2879 | // necessary. In addition to being available to the controller and onEnter/onExit callbacks, 2880 | // we also need $stateParams to be available for any $injector calls we make during the 2881 | // dependency resolution process. 2882 | var $stateParams = (paramsAreFiltered) ? params : filterByKeys(objectKeys(state.params), params); 2883 | var locals = { $stateParams: $stateParams }; 2884 | 2885 | // Resolve "global" dependencies for the state, i.e. those not specific to a view. 2886 | // We"re also including $stateParams in this; that way the parameters are restricted 2887 | // to the set that should be visible to the state, and are independent of when we update 2888 | // the global $state and $stateParams values. 2889 | dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state); 2890 | var promises = [dst.resolve.then(function (globals) { 2891 | dst.globals = globals; 2892 | })]; 2893 | if (inherited) promises.push(inherited); 2894 | 2895 | // Resolve template and dependencies for all views. 2896 | forEach(state.views, function (view, name) { 2897 | var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {}); 2898 | injectables.$template = [ function () { 2899 | return $view.load(name, { view: view, locals: locals, params: $stateParams }) || ""; 2900 | }]; 2901 | 2902 | promises.push($resolve.resolve(injectables, locals, dst.resolve, state).then(function (result) { 2903 | // References to the controller (only instantiated at link time) 2904 | if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) { 2905 | var injectLocals = angular.extend({}, injectables, locals); 2906 | result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals); 2907 | } else { 2908 | result.$$controller = view.controller; 2909 | } 2910 | // Provide access to the state itself for internal use 2911 | result.$$state = state; 2912 | result.$$controllerAs = view.controllerAs; 2913 | dst[name] = result; 2914 | })); 2915 | }); 2916 | 2917 | // Wait for all the promises and then return the activation object 2918 | return $q.all(promises).then(function (values) { 2919 | return dst; 2920 | }); 2921 | } 2922 | 2923 | return $state; 2924 | } 2925 | 2926 | function shouldTriggerReload(to, from, locals, options) { 2927 | if (to === from && ((locals === from.locals && !options.reload) || (to.self.reloadOnSearch === false))) { 2928 | return true; 2929 | } 2930 | } 2931 | } 2932 | 2933 | angular.module("ui.router.state") 2934 | .value("$stateParams", {}) 2935 | .provider("$state", $StateProvider); 2936 | 2937 | 2938 | $ViewProvider.$inject = []; 2939 | function $ViewProvider() { 2940 | 2941 | this.$get = $get; 2942 | /** 2943 | * @ngdoc object 2944 | * @name ui.router.state.$view 2945 | * 2946 | * @requires ui.router.util.$templateFactory 2947 | * @requires $rootScope 2948 | * 2949 | * @description 2950 | * 2951 | */ 2952 | $get.$inject = ["$rootScope", "$templateFactory"]; 2953 | function $get( $rootScope, $templateFactory) { 2954 | return { 2955 | // $view.load("full.viewName", { template: ..., controller: ..., resolve: ..., async: false, params: ... }) 2956 | /** 2957 | * @ngdoc function 2958 | * @name ui.router.state.$view#load 2959 | * @methodOf ui.router.state.$view 2960 | * 2961 | * @description 2962 | * 2963 | * @param {string} name name 2964 | * @param {object} options option object. 2965 | */ 2966 | load: function load(name, options) { 2967 | var result, defaults = { 2968 | template: null, controller: null, view: null, locals: null, notify: true, async: true, params: {} 2969 | }; 2970 | options = extend(defaults, options); 2971 | 2972 | if (options.view) { 2973 | result = $templateFactory.fromConfig(options.view, options.params, options.locals); 2974 | } 2975 | if (result && options.notify) { 2976 | /** 2977 | * @ngdoc event 2978 | * @name ui.router.state.$state#$viewContentLoading 2979 | * @eventOf ui.router.state.$view 2980 | * @eventType broadcast on root scope 2981 | * @description 2982 | * 2983 | * Fired once the view **begins loading**, *before* the DOM is rendered. 2984 | * 2985 | * @param {Object} event Event object. 2986 | * @param {Object} viewConfig The view config properties (template, controller, etc). 2987 | * 2988 | * @example 2989 | * 2990 | *
2991 |          * $scope.$on("$viewContentLoading",
2992 |          * function(event, viewConfig){
2993 |          *     // Access to all the view config properties.
2994 |          *     // and one special property "targetView"
2995 |          *     // viewConfig.targetView
2996 |          * });
2997 |          * 
2998 | */ 2999 | $rootScope.$broadcast("$viewContentLoading", options); 3000 | } 3001 | return result; 3002 | } 3003 | }; 3004 | } 3005 | } 3006 | 3007 | angular.module("ui.router.state").provider("$view", $ViewProvider); 3008 | 3009 | /** 3010 | * @ngdoc object 3011 | * @name ui.router.state.$uiViewScrollProvider 3012 | * 3013 | * @description 3014 | * Provider that returns the {@link ui.router.state.$uiViewScroll} service function. 3015 | */ 3016 | function $ViewScrollProvider() { 3017 | 3018 | var useAnchorScroll = false; 3019 | 3020 | /** 3021 | * @ngdoc function 3022 | * @name ui.router.state.$uiViewScrollProvider#useAnchorScroll 3023 | * @methodOf ui.router.state.$uiViewScrollProvider 3024 | * 3025 | * @description 3026 | * Reverts back to using the core [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) service for 3027 | * scrolling based on the url anchor. 3028 | */ 3029 | this.useAnchorScroll = function () { 3030 | useAnchorScroll = true; 3031 | }; 3032 | 3033 | /** 3034 | * @ngdoc object 3035 | * @name ui.router.state.$uiViewScroll 3036 | * 3037 | * @requires $anchorScroll 3038 | * @requires $timeout 3039 | * 3040 | * @description 3041 | * When called with a jqLite element, it scrolls the element into view (after a 3042 | * `$timeout` so the DOM has time to refresh). 3043 | * 3044 | * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor, 3045 | * this can be enabled by calling {@link ui.router.state.$uiViewScrollProvider#methods_useAnchorScroll `$uiViewScrollProvider.useAnchorScroll()`}. 3046 | */ 3047 | this.$get = ["$anchorScroll", "$timeout", function ($anchorScroll, $timeout) { 3048 | if (useAnchorScroll) { 3049 | return $anchorScroll; 3050 | } 3051 | 3052 | return function ($element) { 3053 | $timeout(function () { 3054 | $element[0].scrollIntoView(); 3055 | }, 0, false); 3056 | }; 3057 | }]; 3058 | } 3059 | 3060 | angular.module("ui.router.state").provider("$uiViewScroll", $ViewScrollProvider); 3061 | 3062 | /** 3063 | * @ngdoc directive 3064 | * @name ui.router.state.directive:ui-view 3065 | * 3066 | * @requires ui.router.state.$state 3067 | * @requires $compile 3068 | * @requires $controller 3069 | * @requires $injector 3070 | * @requires ui.router.state.$uiViewScroll 3071 | * @requires $document 3072 | * 3073 | * @restrict ECA 3074 | * 3075 | * @description 3076 | * The ui-view directive tells $state where to place your templates. 3077 | * 3078 | * @param {string=} ui-view A view name. The name should be unique amongst the other views in the 3079 | * same state. You can have views of the same name that live in different states. 3080 | * 3081 | * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window 3082 | * when a view is populated. By default, $anchorScroll is overridden by ui-router"s custom scroll 3083 | * service, {@link ui.router.state.$uiViewScroll}. This custom service let"s you 3084 | * scroll ui-view elements into view when they are populated during a state activation. 3085 | * 3086 | * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) 3087 | * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.* 3088 | * 3089 | * @param {string=} onload Expression to evaluate whenever the view updates. 3090 | * 3091 | * @example 3092 | * A view can be unnamed or named. 3093 | *
3094 |  * 
3095 |  * 
3096 | * 3097 | * 3098 | *
3099 | *
3100 | * 3101 | * You can only have one unnamed view within any template (or root html). If you are only using a 3102 | * single view and it is unnamed then you can populate it like so: 3103 | *
3104 |  * 
3105 | * $stateProvider.state("home", { 3106 | * template: "

HELLO!

" 3107 | * }) 3108 | *
3109 | * 3110 | * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#views `views`} 3111 | * config property, by name, in this case an empty name: 3112 | *
3113 |  * $stateProvider.state("home", {
3114 |  *   views: {
3115 |  *     "": {
3116 |  *       template: "

HELLO!

" 3117 | * } 3118 | * } 3119 | * }) 3120 | *
3121 | * 3122 | * But typically you"ll only use the views property if you name your view or have more than one view 3123 | * in the same template. There"s not really a compelling reason to name a view if its the only one, 3124 | * but you could if you wanted, like so: 3125 | *
3126 |  * 
3127 | *
3128 | *
3129 |  * $stateProvider.state("home", {
3130 |  *   views: {
3131 |  *     "main": {
3132 |  *       template: "

HELLO!

" 3133 | * } 3134 | * } 3135 | * }) 3136 | *
3137 | * 3138 | * Really though, you"ll use views to set up multiple views: 3139 | *
3140 |  * 
3141 | *
3142 | *
3143 | *
3144 | * 3145 | *
3146 |  * $stateProvider.state("home", {
3147 |  *   views: {
3148 |  *     "": {
3149 |  *       template: "

HELLO!

" 3150 | * }, 3151 | * "chart": { 3152 | * template: "" 3153 | * }, 3154 | * "data": { 3155 | * template: "" 3156 | * } 3157 | * } 3158 | * }) 3159 | *
3160 | * 3161 | * Examples for `autoscroll`: 3162 | * 3163 | *
3164 |  * 
3166 |  * 
3167 |  *
3168 |  * 
3170 |  * 
3171 |  * 
3172 |  * 
3173 |  * 
3174 | */ 3175 | $ViewDirective.$inject = ["$state", "$injector", "$uiViewScroll"]; 3176 | function $ViewDirective( $state, $injector, $uiViewScroll) { 3177 | 3178 | function getService() { 3179 | return ($injector.has) ? function(service) { 3180 | return $injector.has(service) ? $injector.get(service) : null; 3181 | } : function(service) { 3182 | try { 3183 | return $injector.get(service); 3184 | } catch (e) { 3185 | return null; 3186 | } 3187 | }; 3188 | } 3189 | 3190 | var service = getService(), 3191 | $animator = service("$animator"), 3192 | $animate = service("$animate"); 3193 | 3194 | // Returns a set of DOM manipulation functions based on which Angular version 3195 | // it should use 3196 | function getRenderer(attrs, scope) { 3197 | var statics = function() { 3198 | return { 3199 | enter: function (element, target, cb) { target.after(element); cb(); }, 3200 | leave: function (element, cb) { element.remove(); cb(); } 3201 | }; 3202 | }; 3203 | 3204 | if ($animate) { 3205 | return { 3206 | enter: function(element, target, cb) { $animate.enter(element, null, target, cb); }, 3207 | leave: function(element, cb) { $animate.leave(element, cb); } 3208 | }; 3209 | } 3210 | 3211 | if ($animator) { 3212 | var animate = $animator && $animator(scope, attrs); 3213 | 3214 | return { 3215 | enter: function(element, target, cb) {animate.enter(element, null, target); cb(); }, 3216 | leave: function(element, cb) { animate.leave(element); cb(); } 3217 | }; 3218 | } 3219 | 3220 | return statics(); 3221 | } 3222 | 3223 | var directive = { 3224 | restrict: "ECA", 3225 | terminal: true, 3226 | priority: 400, 3227 | transclude: "element", 3228 | compile: function (tElement, tAttrs, $transclude) { 3229 | return function (scope, $element, attrs) { 3230 | var previousEl, currentEl, currentScope, latestLocals, 3231 | onloadExp = attrs.onload || "", 3232 | autoScrollExp = attrs.autoscroll, 3233 | renderer = getRenderer(attrs, scope); 3234 | 3235 | scope.$on("$stateChangeSuccess", function() { 3236 | updateView(false); 3237 | }); 3238 | scope.$on("$viewContentLoading", function() { 3239 | updateView(false); 3240 | }); 3241 | 3242 | updateView(true); 3243 | 3244 | function cleanupLastView() { 3245 | if (previousEl) { 3246 | previousEl.remove(); 3247 | previousEl = null; 3248 | } 3249 | 3250 | if (currentScope) { 3251 | currentScope.$destroy(); 3252 | currentScope = null; 3253 | } 3254 | 3255 | if (currentEl) { 3256 | renderer.leave(currentEl, function() { 3257 | previousEl = null; 3258 | }); 3259 | 3260 | previousEl = currentEl; 3261 | currentEl = null; 3262 | } 3263 | } 3264 | 3265 | function updateView(firstTime) { 3266 | var newScope, 3267 | name = getUiViewName(attrs, $element.inheritedData("$uiView")), 3268 | previousLocals = name && $state.$current && $state.$current.locals[name]; 3269 | 3270 | if (!firstTime && previousLocals === latestLocals) return; // nothing to do 3271 | newScope = scope.$new(); 3272 | latestLocals = $state.$current.locals[name]; 3273 | 3274 | var clone = $transclude(newScope, function(clone) { 3275 | renderer.enter(clone, $element, function onUiViewEnter() { 3276 | if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) { 3277 | $uiViewScroll(clone); 3278 | } 3279 | }); 3280 | cleanupLastView(); 3281 | }); 3282 | 3283 | currentEl = clone; 3284 | currentScope = newScope; 3285 | /** 3286 | * @ngdoc event 3287 | * @name ui.router.state.directive:ui-view#$viewContentLoaded 3288 | * @eventOf ui.router.state.directive:ui-view 3289 | * @eventType emits on ui-view directive scope 3290 | * @description * 3291 | * Fired once the view is **loaded**, *after* the DOM is rendered. 3292 | * 3293 | * @param {Object} event Event object. 3294 | */ 3295 | currentScope.$emit("$viewContentLoaded"); 3296 | currentScope.$eval(onloadExp); 3297 | } 3298 | }; 3299 | } 3300 | }; 3301 | 3302 | return directive; 3303 | } 3304 | 3305 | $ViewDirectiveFill.$inject = ["$compile", "$controller", "$state"]; 3306 | function $ViewDirectiveFill ($compile, $controller, $state) { 3307 | return { 3308 | restrict: "ECA", 3309 | priority: -400, 3310 | compile: function (tElement) { 3311 | var initial = tElement.html(); 3312 | return function (scope, $element, attrs) { 3313 | var current = $state.$current, 3314 | name = getUiViewName(attrs, $element.inheritedData("$uiView")), 3315 | locals = current && current.locals[name]; 3316 | 3317 | if (! locals) { 3318 | return; 3319 | } 3320 | 3321 | $element.data("$uiView", { name: name, state: locals.$$state }); 3322 | $element.html(locals.$template ? locals.$template : initial); 3323 | 3324 | var link = $compile($element.contents()); 3325 | 3326 | if (locals.$$controller) { 3327 | locals.$scope = scope; 3328 | var controller = $controller(locals.$$controller, locals); 3329 | if (locals.$$controllerAs) { 3330 | scope[locals.$$controllerAs] = controller; 3331 | } 3332 | $element.data("$ngControllerController", controller); 3333 | $element.children().data("$ngControllerController", controller); 3334 | } 3335 | 3336 | link(scope); 3337 | }; 3338 | } 3339 | }; 3340 | } 3341 | 3342 | /** 3343 | * Shared ui-view code for both directives: 3344 | * Given attributes and inherited $uiView data, return the view"s name 3345 | */ 3346 | function getUiViewName(attrs, inherited) { 3347 | var name = attrs.uiView || attrs.name || ""; 3348 | return name.indexOf("@") >= 0 ? name : (name + "@" + (inherited ? inherited.state.name : "")); 3349 | } 3350 | 3351 | angular.module("ui.router.state").directive("uiView", $ViewDirective); 3352 | angular.module("ui.router.state").directive("uiView", $ViewDirectiveFill); 3353 | 3354 | function parseStateRef(ref, current) { 3355 | var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed; 3356 | if (preparsed) ref = current + "(" + preparsed[1] + ")"; 3357 | parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/); 3358 | if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'"); 3359 | return { state: parsed[1], paramExpr: parsed[3] || null }; 3360 | } 3361 | 3362 | function stateContext(el) { 3363 | var stateData = el.parent().inheritedData("$uiView"); 3364 | 3365 | if (stateData && stateData.state && stateData.state.name) { 3366 | return stateData.state; 3367 | } 3368 | } 3369 | 3370 | /** 3371 | * @ngdoc directive 3372 | * @name ui.router.state.directive:ui-sref 3373 | * 3374 | * @requires ui.router.state.$state 3375 | * @requires $timeout 3376 | * 3377 | * @restrict A 3378 | * 3379 | * @description 3380 | * A directive that binds a link (`` tag) to a state. If the state has an associated 3381 | * URL, the directive will automatically generate & update the `href` attribute via 3382 | * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking 3383 | * the link will trigger a state transition with optional parameters. 3384 | * 3385 | * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be 3386 | * handled natively by the browser. 3387 | * 3388 | * You can also use relative state paths within ui-sref, just like the relative 3389 | * paths passed to `$state.go()`. You just need to be aware that the path is relative 3390 | * to the state that the link lives in, in other words the state that loaded the 3391 | * template containing the link. 3392 | * 3393 | * You can specify options to pass to {@link ui.router.state.$state#go $state.go()} 3394 | * using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`, 3395 | * and `reload`. 3396 | * 3397 | * @example 3398 | * Here"s an example of how you"d use ui-sref and how it would compile. If you have the 3399 | * following template: 3400 | *
3401 |  * Home | About | Next page
3402 |  *
3403 |  * 
3408 |  * 
3409 | * 3410 | * Then the compiled html would be (assuming Html5Mode is off and current state is contacts): 3411 | *
3412 |  * Home | About | Next page
3413 |  *
3414 |  * 
    3415 | *
  • 3416 | * Joe 3417 | *
  • 3418 | *
  • 3419 | * Alice 3420 | *
  • 3421 | *
  • 3422 | * Bob 3423 | *
  • 3424 | *
3425 | * 3426 | * Home 3427 | *
3428 | * 3429 | * @param {string} ui-sref "stateName" can be any valid absolute or relative state 3430 | * @param {Object} ui-sref-opts options to pass to {@link ui.router.state.$state#go $state.go()} 3431 | */ 3432 | $StateRefDirective.$inject = ["$state", "$timeout"]; 3433 | function $StateRefDirective($state, $timeout) { 3434 | var allowedOptions = ["location", "inherit", "reload"]; 3435 | 3436 | return { 3437 | restrict: "A", 3438 | require: ["?^uiSrefActive", "?^uiSrefActiveEq"], 3439 | link: function(scope, element, attrs, uiSrefActive) { 3440 | var ref = parseStateRef(attrs.uiSref, $state.current.name); 3441 | var params = null, url = null, base = stateContext(element) || $state.$current; 3442 | var isForm = element[0].nodeName === "FORM"; 3443 | var attr = isForm ? "action" : "href", nav = true; 3444 | 3445 | var options = { relative: base, inherit: true }; 3446 | var optionsOverride = scope.$eval(attrs.uiSrefOpts) || {}; 3447 | 3448 | angular.forEach(allowedOptions, function(option) { 3449 | if (option in optionsOverride) { 3450 | options[option] = optionsOverride[option]; 3451 | } 3452 | }); 3453 | 3454 | var update = function(newVal) { 3455 | if (newVal) params = newVal; 3456 | if (!nav) return; 3457 | 3458 | var newHref = $state.href(ref.state, params, options); 3459 | 3460 | var activeDirective = uiSrefActive[1] || uiSrefActive[0]; 3461 | if (activeDirective) { 3462 | activeDirective.$$setStateInfo(ref.state, params); 3463 | } 3464 | if (newHref === null) { 3465 | nav = false; 3466 | return false; 3467 | } 3468 | element[0][attr] = newHref; 3469 | }; 3470 | 3471 | if (ref.paramExpr) { 3472 | scope.$watch(ref.paramExpr, function(newVal, oldVal) { 3473 | if (newVal !== params) update(newVal); 3474 | }, true); 3475 | params = scope.$eval(ref.paramExpr); 3476 | } 3477 | update(); 3478 | 3479 | if (isForm) return; 3480 | 3481 | element.bind("click", function(e) { 3482 | var button = e.which || e.button; 3483 | if ( !(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || element.attr("target")) ) { 3484 | // HACK: This is to allow ng-clicks to be processed before the transition is initiated: 3485 | var transition = $timeout(function() { 3486 | $state.go(ref.state, params, options); 3487 | }); 3488 | e.preventDefault(); 3489 | 3490 | e.preventDefault = function() { 3491 | $timeout.cancel(transition); 3492 | }; 3493 | } 3494 | }); 3495 | } 3496 | }; 3497 | } 3498 | 3499 | /** 3500 | * @ngdoc directive 3501 | * @name ui.router.state.directive:ui-sref-active 3502 | * 3503 | * @requires ui.router.state.$state 3504 | * @requires ui.router.state.$stateParams 3505 | * @requires $interpolate 3506 | * 3507 | * @restrict A 3508 | * 3509 | * @description 3510 | * A directive working alongside ui-sref to add classes to an element when the 3511 | * related ui-sref directive"s state is active, and removing them when it is inactive. 3512 | * The primary use-case is to simplify the special appearance of navigation menus 3513 | * relying on `ui-sref`, by having the "active" state"s menu button appear different, 3514 | * distinguishing it from the inactive menu items. 3515 | * 3516 | * ui-sref-active can live on the same element as ui-sref or on a parent element. The first 3517 | * ui-sref-active found at the same level or above the ui-sref will be used. 3518 | * 3519 | * Will activate when the ui-sref"s target state or any child state is active. If you 3520 | * need to activate only when the ui-sref target state is active and *not* any of 3521 | * it"s children, then you will use 3522 | * {@link ui.router.state.directive:ui-sref-active-eq ui-sref-active-eq} 3523 | * 3524 | * @example 3525 | * Given the following template: 3526 | *
3527 |  * 
3532 |  * 
3533 | * 3534 | * 3535 | * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins", 3536 | * the resulting HTML will appear as (note the "active" class): 3537 | *
3538 |  * 
3543 |  * 
3544 | * 3545 | * The class name is interpolated **once** during the directives link time (any further changes to the 3546 | * interpolated value are ignored). 3547 | * 3548 | * Multiple classes may be specified in a space-separated format: 3549 | *
3550 |  * 
    3551 | *
  • 3552 | * link 3553 | *
  • 3554 | *
3555 | *
3556 | */ 3557 | 3558 | /** 3559 | * @ngdoc directive 3560 | * @name ui.router.state.directive:ui-sref-active-eq 3561 | * 3562 | * @requires ui.router.state.$state 3563 | * @requires ui.router.state.$stateParams 3564 | * @requires $interpolate 3565 | * 3566 | * @restrict A 3567 | * 3568 | * @description 3569 | * The same as {@link ui.router.state.directive:ui-sref-active ui-sref-active} but will will only activate 3570 | * when the exact target state used in the `ui-sref` is active; no child states. 3571 | * 3572 | */ 3573 | $StateRefActiveDirective.$inject = ["$state", "$stateParams", "$interpolate"]; 3574 | function $StateRefActiveDirective($state, $stateParams, $interpolate) { 3575 | return { 3576 | restrict: "A", 3577 | controller: ["$scope", "$element", "$attrs", function ($scope, $element, $attrs) { 3578 | var state, params, activeClass; 3579 | 3580 | // There probably isn"t much point in $observing this 3581 | // uiSrefActive and uiSrefActiveEq share the same directive object with some 3582 | // slight difference in logic routing 3583 | activeClass = $interpolate($attrs.uiSrefActiveEq || $attrs.uiSrefActive || "", false)($scope); 3584 | 3585 | // Allow uiSref to communicate with uiSrefActive[Equals] 3586 | this.$$setStateInfo = function (newState, newParams) { 3587 | state = $state.get(newState, stateContext($element)); 3588 | params = newParams; 3589 | update(); 3590 | }; 3591 | 3592 | $scope.$on("$stateChangeSuccess", update); 3593 | 3594 | // Update route state 3595 | function update() { 3596 | if (isMatch()) { 3597 | $element.addClass(activeClass); 3598 | } else { 3599 | $element.removeClass(activeClass); 3600 | } 3601 | } 3602 | 3603 | function isMatch() { 3604 | if (typeof $attrs.uiSrefActiveEq !== "undefined") { 3605 | return $state.$current.self === state && matchesParams(); 3606 | } else { 3607 | return $state.includes(state.name) && matchesParams(); 3608 | } 3609 | } 3610 | 3611 | function matchesParams() { 3612 | return !params || equalForKeys(params, $stateParams); 3613 | } 3614 | }] 3615 | }; 3616 | } 3617 | 3618 | angular.module("ui.router.state") 3619 | .directive("uiSref", $StateRefDirective) 3620 | .directive("uiSrefActive", $StateRefActiveDirective) 3621 | .directive("uiSrefActiveEq", $StateRefActiveDirective); 3622 | 3623 | /** 3624 | * @ngdoc filter 3625 | * @name ui.router.state.filter:isState 3626 | * 3627 | * @requires ui.router.state.$state 3628 | * 3629 | * @description 3630 | * Translates to {@link ui.router.state.$state#methods_is $state.is("stateName")}. 3631 | */ 3632 | $IsStateFilter.$inject = ["$state"]; 3633 | function $IsStateFilter($state) { 3634 | return function(state) { 3635 | return $state.is(state); 3636 | }; 3637 | } 3638 | 3639 | /** 3640 | * @ngdoc filter 3641 | * @name ui.router.state.filter:includedByState 3642 | * 3643 | * @requires ui.router.state.$state 3644 | * 3645 | * @description 3646 | * Translates to {@link ui.router.state.$state#methods_includes $state.includes("fullOrPartialStateName")}. 3647 | */ 3648 | $IncludedByStateFilter.$inject = ["$state"]; 3649 | function $IncludedByStateFilter($state) { 3650 | return function(state) { 3651 | return $state.includes(state); 3652 | }; 3653 | } 3654 | 3655 | angular.module("ui.router.state") 3656 | .filter("isState", $IsStateFilter) 3657 | .filter("includedByState", $IncludedByStateFilter); 3658 | })({}, angular); 3659 | -------------------------------------------------------------------------------- /test/intern.js: -------------------------------------------------------------------------------- 1 | // Learn more about configuring this file at . 2 | // These default settings work OK for most people. The options that *must* be changed below are the 3 | // packages, suites, excludeInstrumentation, and (if you want functional tests) functionalSuites. 4 | define({ 5 | // The desired AMD loader to use when running unit tests (client.html/client.js). Omit to use the default Dojo 6 | // loader 7 | // Non-functional test suite(s) to run in each browser 8 | suites: [ 9 | 'test/angular-architecture-graph.test' 10 | ], 11 | 12 | // A regular expression matching URLs to files that should not be included in code coverage analysis 13 | excludeInstrumentation: /^(?:test|node_modules|docs|templates)\// 14 | }); 15 | --------------------------------------------------------------------------------