1/** 2 * State-based routing for AngularJS 3 * @version v0.2.18 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) */ 9if (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 18var 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 toJson = angular.toJson; 27 28function inherit(parent, extra) { 29 return extend(new (extend(function() {}, { prototype: parent }))(), extra); 30} 31 32function merge(dst) { 33 forEach(arguments, function(obj) { 34 if (obj !== dst) { 35 forEach(obj, function(value, key) { 36 if (!dst.hasOwnProperty(key)) dst[key] = value; 37 }); 38 } 39 }); 40 return dst; 41} 42 43/** 44 * Finds the common ancestor path between two states. 45 * 46 * @param {Object} first The first state. 47 * @param {Object} second The second state. 48 * @return {Array} Returns an array of state names in descending order, not including the root. 49 */ 50function ancestors(first, second) { 51 var path = []; 52 53 for (var n in first.path) { 54 if (first.path[n] !== second.path[n]) break; 55 path.push(first.path[n]); 56 } 57 return path; 58} 59 60/** 61 * IE8-safe wrapper for `Object.keys()`. 62 * 63 * @param {Object} object A JavaScript object. 64 * @return {Array} Returns the keys of the object as an array. 65 */ 66function objectKeys(object) { 67 if (Object.keys) { 68 return Object.keys(object); 69 } 70 var result = []; 71 72 forEach(object, function(val, key) { 73 result.push(key); 74 }); 75 return result; 76} 77 78/** 79 * IE8-safe wrapper for `Array.prototype.indexOf()`. 80 * 81 * @param {Array} array A JavaScript array. 82 * @param {*} value A value to search the array for. 83 * @return {Number} Returns the array index value of `value`, or `-1` if not present. 84 */ 85function indexOf(array, value) { 86 if (Array.prototype.indexOf) { 87 return array.indexOf(value, Number(arguments[2]) || 0); 88 } 89 var len = array.length >>> 0, from = Number(arguments[2]) || 0; 90 from = (from < 0) ? Math.ceil(from) : Math.floor(from); 91 92 if (from < 0) from += len; 93 94 for (; from < len; from++) { 95 if (from in array && array[from] === value) return from; 96 } 97 return -1; 98} 99 100/** 101 * Merges a set of parameters with all parameters inherited between the common parents of the 102 * current state and a given destination state. 103 * 104 * @param {Object} currentParams The value of the current state parameters ($stateParams). 105 * @param {Object} newParams The set of parameters which will be composited with inherited params. 106 * @param {Object} $current Internal definition of object representing the current state. 107 * @param {Object} $to Internal definition of object representing state to transition to. 108 */ 109function inheritParams(currentParams, newParams, $current, $to) { 110 var parents = ancestors($current, $to), parentParams, inherited = {}, inheritList = []; 111 112 for (var i in parents) { 113 if (!parents[i] || !parents[i].params) continue; 114 parentParams = objectKeys(parents[i].params);
115 if (!parentParams.length) continue; 116 117 for (var j in parentParams) { 118 if (indexOf(inheritList, parentParams[j]) >= 0) continue; 119 inheritList.push(parentParams[j]); 120 inherited[parentParams[j]] = currentParams[parentParams[j]]; 121 } 122 } 123 return extend({}, inherited, newParams); 124} 125 126/** 127 * Performs a non-strict comparison of the subset of two objects, defined by a list of keys. 128 * 129 * @param {Object} a The first object. 130 * @param {Object} b The second object. 131 * @param {Array} keys The list of keys within each object to compare. If the list is empty or not specified, 132 * it defaults to the list of keys in `a`. 133 * @return {Boolean} Returns `true` if the keys match, otherwise `false`. 134 */ 135function equalForKeys(a, b, keys) { 136 if (!keys) { 137 keys = []; 138 for (var n in a) keys.push(n); // Used instead of Object.keys() for IE8 compatibility 139 } 140 141 for (var i=0; i<keys.length; i++) { 142 var k = keys[i]; 143 if (a[k] != b[k]) return false; // Not '===', values aren't necessarily normalized 144 } 145 return true; 146} 147 148/** 149 * Returns the subset of an object, based on a list of keys. 150 * 151 * @param {Array} keys 152 * @param {Object} values 153 * @return {Boolean} Returns a subset of `values`. 154 */ 155function filterByKeys(keys, values) { 156 var filtered = {}; 157 158 forEach(keys, function (name) { 159 filtered[name] = values[name]; 160 }); 161 return filtered; 162} 163 164// like _.indexBy 165// when you know that your index values will be unique, or you want last-one-in to win 166function indexBy(array, propName) { 167 var result = {}; 168 forEach(array, function(item) { 169 result[item[propName]] = item; 170 }); 171 return result; 172} 173 174// extracted from underscore.js 175// Return a copy of the object only containing the whitelisted properties. 176function pick(obj) { 177 var copy = {}; 178 var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1)); 179 forEach(keys, function(key) { 180 if (key in obj) copy[key] = obj[key]; 181 }); 182 return copy; 183} 184 185// extracted from underscore.js 186// Return a copy of the object omitting the blacklisted properties. 187function omit(obj) { 188 var copy = {}; 189 var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1)); 190 for (var key in obj) { 191 if (indexOf(keys, key) == -1) copy[key] = obj[key]; 192 } 193 return copy; 194} 195 196function pluck(collection, key) { 197 var result = isArray(collection) ? [] : {}; 198 199 forEach(collection, function(val, i) { 200 result[i] = isFunction(key) ? key(val) : val[key]; 201 }); 202 return result; 203} 204 205function filter(collection, callback) { 206 var array = isArray(collection); 207 var result = array ? [] : {}; 208 forEach(collection, function(val, i) { 209 if (callback(val, i)) { 210 result[array ? result.length : i] = val; 211 } 212 }); 213 return result; 214} 215 216function map(collection, callback) { 217 var result = isArray(collection) ? [] : {}; 218 219 forEach(collection, function(val, i) { 220 result[i] = callback(val, i); 221 }); 222 return result; 223} 224 225/** 226 * @ngdoc overview 227 * @name ui.router.util 228 * 229 * @description 230 * # ui.router.util sub-module 231 * 232 * This module is a dependency of other sub-modules. Do not include this module as a dependency 233 * in your angular app (use {@link ui.router} module instead). 234 * 235 */ 236angular.module('ui.router.util', ['ng']); 237 238/** 239 * @ngdoc overview 240 * @name ui.router.router 241 * 242 * @requires ui.router.util 243 * 244 * @description 245 * # ui.router.router sub-module 246 * 247 * This module is a dependency of other sub-modules. Do not include this module as a dependency 248 * in your angular app (use {@link ui.router} module instead). 249 */ 250angular.module('ui.router.router', ['ui.router.util']); 251 252/** 253 * @ngdoc overview 254 * @name ui.router.state 255 * 256 * @requires ui.router.router 257 * @requires ui.router.util 258 * 259 * @description 260 * # ui.router.state sub-module 261 * 262 * This module is a dependency of the main ui.router module. Do not include this module as a dependency 263 * in your angular app (use {@link ui.router} module instead). 264 * 265 */ 266angular.module('ui.router.state', ['ui.router.router', 'ui.router.util']); 267 268/** 269 * @ngdoc overview 270 * @name ui.router 271 * 272 * @requires ui.router.state 273 * 274 * @description 275 * # ui.router 276 * 277 * ## The main module for ui.router 278 * There are several sub-modules included with the ui.router module, however only this module is needed 279 * as a dependency within your angular app. The other modules are for organization purposes. 280 * 281 * The modules are: 282 * * ui.router - the main "umbrella" module 283 * * ui.router.router - 284 * 285 * *You'll need to include **only** this module as the dependency within your angular app.* 286 * 287 * <pre> 288 * <!doctype html> 289 * <html ng-app="myApp"> 290 * <head> 291 * <script src="js/angular.js"></script> 292 * <!-- Include the ui-router script --> 293 * <script src="js/angular-ui-router.min.js"></script> 294 * <script> 295 * // ...and add 'ui.router' as a dependency 296 * var myApp = angular.module('myApp', ['ui.router']); 297 * </script> 298 * </head> 299 * <body> 300 * </body> 301 * </html> 302 * </pre> 303 */ 304angular.module('ui.router', ['ui.router.state']); 305 306angular.module('ui.router.compat', ['ui.router']); 307 308/** 309 * @ngdoc object 310 * @name ui.router.util.$resolve 311 * 312 * @requires $q 313 * @requires $injector 314 * 315 * @description 316 * Manages resolution of (acyclic) graphs of promises. 317 */ 318$Resolve.$inject = ['$q', '$injector']; 319function $Resolve( $q, $injector) { 320 321 var VISIT_IN_PROGRESS = 1, 322 VISIT_DONE = 2, 323 NOTHING = {}, 324 NO_DEPENDENCIES = [], 325 NO_LOCALS = NOTHING, 326 NO_PARENT = extend($q.when(NOTHING), { $$promises: NOTHING, $$values: NOTHING }); 327 328 329 /** 330 * @ngdoc function 331 * @name ui.router.util.$resolve#study 332 * @methodOf ui.router.util.$resolve 333 * 334 * @description 335 * Studies a set of invocables that are likely to be used multiple times. 336 * <pre> 337 * $resolve.study(invocables)(locals, parent, self) 338 * </pre> 339 * is equivalent to 340 * <pre> 341 * $resolve.resolve(invocables, locals, parent, self) 342 * </pre> 343 * but the former is more efficient (in fact `resolve` just calls `study` 344 * internally). 345 * 346 * @param {object} invocables Invocable objects 347 * @return {function} a function to pass in locals, parent and self 348 */ 349 this.study = function (invocables) {
350 if (!isObject(invocables)) throw new Error("'invocables' must be an object"); 351 var invocableKeys = objectKeys(invocables || {}); 352 353 // Perform a topological sort of invocables to build an ordered plan 354 var plan = [], cycle = [], visited = {}; 355 function visit(value, key) { 356 if (visited[key] === VISIT_DONE) return; 357 358 cycle.push(key); 359 if (visited[key] === VISIT_IN_PROGRESS) { 360 cycle.splice(0, indexOf(cycle, key)); 361 throw new Error("Cyclic dependency: " + cycle.join(" -> ")); 362 } 363 visited[key] = VISIT_IN_PROGRESS; 364 365 if (isString(value)) { 366 plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES); 367 } else { 368 var params = $injector.annotate(value); 369 forEach(params, function (param) { 370 if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param); 371 }); 372 plan.push(key, value, params); 373 } 374 375 cycle.pop(); 376 visited[key] = VISIT_DONE; 377 } 378 forEach(invocables, visit); 379 invocables = cycle = visited = null; // plan is all that's required 380 381 function isResolve(value) { 382 return isObject(value) && value.then && value.$$promises; 383 } 384 385 return function (locals, parent, self) { 386 if (isResolve(locals) && self === undefined) { 387 self = parent; parent = locals; locals = null; 388 } 389 if (!locals) locals = NO_LOCALS; 390 else if (!isObject(locals)) { 391 throw new Error("'locals' must be an object"); 392 } 393 if (!parent) parent = NO_PARENT; 394 else if (!isResolve(parent)) { 395 throw new Error("'parent' must be a promise returned by $resolve.resolve()"); 396 } 397 398 // To complete the overall resolution, we have to wait for the parent 399 // promise and for the promise for each invokable in our plan. 400 var resolution = $q.defer(), 401 result = resolution.promise, 402 promises = result.$$promises = {}, 403 values = extend({}, locals), 404 wait = 1 + plan.length/3, 405 merged = false; 406 407 function done() { 408 // Merge parent values we haven't got yet and publish our own $$values 409 if (!--wait) { 410 if (!merged) merge(values, parent.$$values); 411 result.$$values = values; 412 result.$$promises = result.$$promises || true; // keep for isResolve() 413 delete result.$$inheritedValues; 414 resolution.resolve(values); 415 } 416 } 417 418 function fail(reason) { 419 result.$$failure = reason; 420 resolution.reject(reason); 421 } 422 423 // Short-circuit if parent has already failed 424 if (isDefined(parent.$$failure)) { 425 fail(parent.$$failure); 426 return result; 427 } 428 429 if (parent.$$inheritedValues) { 430 merge(values, omit(parent.$$inheritedValues, invocableKeys)); 431 } 432 433 // Merge parent values if the parent has already resolved, or merge 434 // parent promises and wait if the parent resolve is still in progress. 435 extend(promises, parent.$$promises); 436 if (parent.$$values) { 437 merged = merge(values, omit(parent.$$values, invocableKeys)); 438 result.$$inheritedValues = omit(parent.$$values, invocableKeys); 439 done(); 440 } else { 441 if (parent.$$inheritedValues) { 442 result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys); 443 } 444 parent.then(done, fail); 445 } 446 447 // Process each invocable in the plan, but ignore any where a local of the same name exists. 448 for (var i=0, ii=plan.length; i<ii; i+=3) { 449 if (locals.hasOwnProperty(plan[i])) done(); 450 else invoke(plan[i], plan[i+1], plan[i+2]); 451 } 452 453 function invoke(key, invocable, params) { 454 // Create a deferred for this invocation. Failures will propagate to the resolution as well. 455 var invocation = $q.defer(), waitParams = 0; 456 function onfailure(reason) { 457 invocation.reject(reason); 458 fail(reason); 459 } 460 // Wait for any parameter that we have a promise for (either from parent or from this 461 // resolve; in that case study() will have made sure it's ordered before us in the plan).
462 forEach(params, function (dep) { 463 if (promises.hasOwnProperty(dep) && !locals.hasOwnProperty(dep)) { 464 waitParams++; 465 promises[dep].then(function (result) { 466 values[dep] = result; 467 if (!(--waitParams)) proceed(); 468 }, onfailure); 469 } 470 }); 471 if (!waitParams) proceed(); 472 function proceed() { 473 if (isDefined(result.$$failure)) return; 474 try { 475 invocation.resolve($injector.invoke(invocable, self, values)); 476 invocation.promise.then(function (result) { 477 values[key] = result; 478 done(); 479 }, onfailure); 480 } catch (e) { 481 onfailure(e); 482 } 483 } 484 // Publish promise synchronously; invocations further down in the plan may depend on it. 485 promises[key] = invocation.promise; 486 } 487 488 return result; 489 }; 490 }; 491 492 /** 493 * @ngdoc function 494 * @name ui.router.util.$resolve#resolve 495 * @methodOf ui.router.util.$resolve 496 * 497 * @description 498 * Resolves a set of invocables. An invocable is a function to be invoked via 499 * `$injector.invoke()`, and can have an arbitrary number of dependencies. 500 * An invocable can either return a value directly, 501 * or a `$q` promise. If a promise is returned it will be resolved and the 502 * resulting value will be used instead. Dependencies of invocables are resolved 503 * (in this order of precedence) 504 * 505 * - from the specified `locals` 506 * - from another invocable that is part of this `$resolve` call 507 * - from an invocable that is inherited from a `parent` call to `$resolve` 508 * (or recursively 509 * - from any ancestor `$resolve` of that parent). 510 * 511 * The return value of `$resolve` is a promise for an object that contains 512 * (in this order of precedence) 513 * 514 * - any `locals` (if specified) 515 * - the resolved return values of all injectables 516 * - any values inherited from a `parent` call to `$resolve` (if specified) 517 * 518 * The promise will resolve after the `parent` promise (if any) and all promises 519 * returned by injectables have been resolved. If any invocable 520 * (or `$injector.invoke`) throws an exception, or if a promise returned by an 521 * invocable is rejected, the `$resolve` promise is immediately rejected with the 522 * same error. A rejection of a `parent` promise (if specified) will likewise be 523 * propagated immediately. Once the `$resolve` promise has been rejected, no 524 * further invocables will be called. 525 * 526 * Cyclic dependencies between invocables are not permitted and will cause `$resolve` 527 * to throw an error. As a special case, an injectable can depend on a parameter 528 * with the same name as the injectable, which will be fulfilled from the `parent` 529 * injectable of the same name. This allows inherited values to be decorated. 530 * Note that in this case any other injectable in the same `$resolve` with the same 531 * dependency would see the decorated value, not the inherited value. 532 * 533 * Note that missing dependencies -- unlike cyclic dependencies -- will cause an 534 * (asynchronous) rejection of the `$resolve` promise rather than a (synchronous) 535 * exception. 536 * 537 * Invocables are invoked eagerly as soon as all dependencies are available. 538 * This is true even for dependencies inherited from a `parent` call to `$resolve`. 539 * 540 * As a special case, an invocable can be a string, in which case it is taken to 541 * be a service name to be passed to `$injector.get()`. This is supported primarily 542 * for backwards-compatibility with the `resolve` property of `$routeProvider` 543 * routes. 544 * 545 * @param {object} invocables functions to invoke or 546 * `$injector` services to fetch. 547 * @param {object} locals values to make available to the injectables 548 * @param {object} parent a promise returned by another call to `$resolve`. 549 * @param {object} self the `this` for the invoked methods 550 * @return {object} Promise for an object that contains the resolved return value 551 * of all invocables, as well as any inherited and local values. 552 */ 553 this.resolve = function (invocables, locals, parent, self) { 554 return this.study(invocables)(locals, parent, self); 555 }; 556} 557 558angular.module('ui.router.util').service('$resolve', $Resolve); 559 560 561/** 562 * @ngdoc object 563 * @name ui.router.util.$templateFactory 564 * 565 * @requires $http 566 * @requires $templateCache 567 * @requires $injector 568 * 569 * @description 570 * Service. Manages loading of templates. 571 */ 572$TemplateFactory.$inject = ['$http', '$templateCache', '$injector']; 573function $TemplateFactory( $http, $templateCache, $injector) { 574 575 /** 576 * @ngdoc function 577 * @name ui.router.util.$templateFactory#fromConfig 578 * @methodOf ui.router.util.$templateFactory 579 * 580 * @description 581 * Creates a template from a configuration object. 582 * 583 * @param {object} config Configuration object for which to load a template. 584 * The following properties are search in the specified order, and the first one 585 * that is defined is used to create the template: 586 * 587 * @param {string|object} config.template html string template or function to 588 * load via {@link ui.router.util.$templateFactory#fromString fromString}.
589 * @param {string|object} config.templateUrl url to load or a function returning 590 * the url to load via {@link ui.router.util.$templateFactory#fromUrl fromUrl}. 591 * @param {Function} config.templateProvider function to invoke via 592 * {@link ui.router.util.$templateFactory#fromProvider fromProvider}. 593 * @param {object} params Parameters to pass to the template function. 594 * @param {object} locals Locals to pass to `invoke` if the template is loaded 595 * via a `templateProvider`. Defaults to `{ params: params }`. 596 * 597 * @return {string|object} The template html as a string, or a promise for 598 * that string,or `null` if no template is configured. 599 */ 600 this.fromConfig = function (config, params, locals) { 601 return ( 602 isDefined(config.template) ? this.fromString(config.template, params) : 603 isDefined(config.templateUrl) ? this.fromUrl(config.templateUrl, params) : 604 isDefined(config.templateProvider) ? this.fromProvider(config.templateProvider, params, locals) : 605 null 606 ); 607 }; 608 609 /** 610 * @ngdoc function 611 * @name ui.router.util.$templateFactory#fromString 612 * @methodOf ui.router.util.$templateFactory 613 * 614 * @description 615 * Creates a template from a string or a function returning a string. 616 * 617 * @param {string|object} template html template as a string or function that 618 * returns an html template as a string. 619 * @param {object} params Parameters to pass to the template function. 620 * 621 * @return {string|object} The template html as a string, or a promise for that 622 * string. 623 */ 624 this.fromString = function (template, params) { 625 return isFunction(template) ? template(params) : template; 626 }; 627 628 /** 629 * @ngdoc function 630 * @name ui.router.util.$templateFactory#fromUrl 631 * @methodOf ui.router.util.$templateFactory 632 * 633 * @description 634 * Loads a template from the a URL via `$http` and `$templateCache`. 635 * 636 * @param {string|Function} url url of the template to load, or a function 637 * that returns a url. 638 * @param {Object} params Parameters to pass to the url function. 639 * @return {string|Promise.<string>} The template html as a string, or a promise 640 * for that string. 641 */ 642 this.fromUrl = function (url, params) { 643 if (isFunction(url)) url = url(params); 644 if (url == null) return null; 645 else return $http 646 .get(url, { cache: $templateCache, headers: { Accept: 'text/html' }}) 647 .then(function(response) { return response.data; }); 648 }; 649 650 /** 651 * @ngdoc function 652 * @name ui.router.util.$templateFactory#fromProvider 653 * @methodOf ui.router.util.$templateFactory 654 * 655 * @description 656 * Creates a template by invoking an injectable provider function. 657 * 658 * @param {Function} provider Function to invoke via `$injector.invoke` 659 * @param {Object} params Parameters for the template. 660 * @param {Object} locals Locals to pass to `invoke`. Defaults to 661 * `{ params: params }`. 662 * @return {string|Promise.<string>} The template html as a string, or a promise 663 * for that string. 664 */ 665 this.fromProvider = function (provider, params, locals) { 666 return $injector.invoke(provider, null, locals || { params: params }); 667 }; 668} 669 670angular.module('ui.router.util').service('$templateFactory', $TemplateFactory); 671 672var $$UMFP; // reference to $UrlMatcherFactoryProvider 673 674/** 675 * @ngdoc object 676 * @name ui.router.util.type:UrlMatcher 677 * 678 * @description 679 * Matches URLs against patterns and extracts named parameters from the path or the search 680 * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list 681 * of search parameters. Multiple search parameter names are separated by '&'. Search parameters 682 * do not influence whether or not a URL is matched, but their values are passed through into 683 * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}. 684 * 685 * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace 686 * syntax, which optionally allows a regular expression for the parameter to be specified: 687 * 688 * * `':'` name - colon placeholder 689 * * `'*'` name - catch-all placeholder 690 * * `'{' name '}'` - curly placeholder 691 * * `'{' name ':' regexp|type '}'` - curly placeholder with regexp or type name. Should the 692 * regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash. 693 * 694 * Parameter names may contain only word characters (latin letters, digits, and underscore) and 695 * must be unique within the pattern (across both path and search parameters). For colon 696 * placeholders or curly placeholders without an explicit regexp, a path parameter matches any 697 * number of characters other than '/'. For catch-all placeholders the path parameter matches 698 * any number of characters. 699 * 700 * Examples: 701 * 702 * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for 703 * trailing slashes, and patterns have to match the entire path, not just a prefix. 704 * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or 705 * '/user/bob/details'. The second path segment will be captured as the parameter 'id'. 706 * * `'/user/{id}'` - Same as the previous example, but using curly brace syntax. 707 * * `'/user/{id:[^/]*}'` - Same as the previous example. 708 * * `'/user/{id:[0-9a-fA-F]{1,8}}'` - Similar to the previous example, but only matches if the id 709 * parameter consists of 1 to 8 hex digits. 710 * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the 711 * path into the parameter 'path'. 712 * * `'/files/*path'` - ditto. 713 * * `'/calendar/{start:date}'` - Matches "/calendar/2014-11-12" (because the pattern defined 714 * in the built-in `date` Type matches `2014-11-12`) and provides a Date object in $stateParams.start 715 * 716 * @param {string} pattern The pattern to compile into a matcher. 717 * @param {Object} config A configuration object hash: 718 * @param {Object=} parentMatcher Used to concatenate the pattern/config onto 719 * an existing UrlMatcher 720 * 721 * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`. 722 * * `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`. 723 * 724 * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any 725 * URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns 726 * non-null) will start with this prefix. 727 * 728 * @property {string} source The pattern that was passed into the constructor 729 * 730 * @property {string} sourcePath The path portion of the source property 731 * 732 * @property {string} sourceSearch The search portion of the source property 733 * 734 * @property {string} regex The constructed regex that will be used to match against the url when 735 * it is time to determine which url will match. 736 * 737 * @returns {Object} New `UrlMatcher` object 738 */ 739function UrlMatcher(pattern, config, parentMatcher) { 740 config = extend({ params: {} }, isObject(config) ? config : {}); 741 742 // Find all placeholders and create a compiled pattern, using either classic or curly syntax: 743 // '*' name 744 // ':' name 745 // '{' name '}' 746 // '{' name ':' regexp '}' 747 // The regular expression is somewhat complicated due to the need to allow curly braces 748 // inside the regular expression. The placeholder regexp breaks down as follows: 749 // ([:*])([\w\[\]]+) - classic placeholder ($1 / $2) (search version has - for snake-case) 750 // \{([\w\[\]]+)(?:\:\s*( ... ))?\} - curly brace placeholder ($3) with optional regexp/type ... ($4) (search version has - for snake-case 751 // (?: ... | ... | ... )+ - the regexp consists of any number of atoms, an atom being either 752 // [^{}\\]+ - anything other than curly braces or backslash 753 // \\. - a backslash escape 754 // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms 755 var placeholder = /([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g, 756 searchPlaceholder = /([:]?)([\w\[\].-]+)|\{([\w\[\].-]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g, 757 compiled = '^', last = 0, m, 758 segments = this.segments = [],
759 parentParams = parentMatcher ? parentMatcher.params : {}, 760 params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet(), 761 paramNames = []; 762 763 function addParameter(id, type, config, location) { 764 paramNames.push(id); 765 if (parentParams[id]) return parentParams[id]; 766 if (!/^\w+([-.]+\w+)*(?:\[\])?$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'"); 767 if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'"); 768 params[id] = new $$UMFP.Param(id, type, config, location); 769 return params[id]; 770 } 771 772 function quoteRegExp(string, pattern, squash, optional) { 773 var surroundPattern = ['',''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&"); 774 if (!pattern) return result; 775 switch(squash) { 776 case false: surroundPattern = ['(', ')' + (optional ? "?" : "")]; break; 777 case true: 778 result = result.replace(/\/$/, ''); 779 surroundPattern = ['(?:\/(', ')|\/)?']; 780 break; 781 default: surroundPattern = ['(' + squash + "|", ')?']; break; 782 } 783 return result + surroundPattern[0] + pattern + surroundPattern[1]; 784 } 785 786 this.source = pattern; 787 788 // Split into static segments separated by path parameter placeholders. 789 // The number of segments is always 1 more than the number of parameters. 790 function matchDetails(m, isSearch) { 791 var id, regexp, segment, type, cfg, arrayMode; 792 id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null 793 cfg = config.params[id]; 794 segment = pattern.substring(last, m.index); 795 regexp = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null); 796 797 if (regexp) { 798 type = $$UMFP.type(regexp) || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) }); 799 } 800 801 return { 802 id: id, regexp: regexp, segment: segment, type: type, cfg: cfg 803 }; 804 } 805 806 var p, param, segment; 807 while ((m = placeholder.exec(pattern))) { 808 p = matchDetails(m, false); 809 if (p.segment.indexOf('?') >= 0) break; // we're into the search part 810 811 param = addParameter(p.id, p.type, p.cfg, "path"); 812 compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional); 813 segments.push(p.segment); 814 last = placeholder.lastIndex; 815 } 816 segment = pattern.substring(last); 817 818 // Find any search parameter names and remove them from the last segment 819 var i = segment.indexOf('?'); 820 821 if (i >= 0) { 822 var search = this.sourceSearch = segment.substring(i); 823 segment = segment.substring(0, i); 824 this.sourcePath = pattern.substring(0, last + i); 825 826 if (search.length > 0) { 827 last = 0; 828 while ((m = searchPlaceholder.exec(search))) { 829 p = matchDetails(m, true); 830 param = addParameter(p.id, p.type, p.cfg, "search"); 831 last = placeholder.lastIndex; 832 // check if ?& 833 } 834 } 835 } else { 836 this.sourcePath = pattern; 837 this.sourceSearch = ''; 838 } 839 840 compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$'; 841 segments.push(segment); 842 843 this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined); 844 this.prefix = segments[0]; 845 this.$$paramNames = paramNames; 846} 847 848/** 849 * @ngdoc function 850 * @name ui.router.util.type:UrlMatcher#concat 851 * @methodOf ui.router.util.type:UrlMatcher 852 * 853 * @description 854 * Returns a new matcher for a pattern constructed by appending the path part and adding the 855 * search parameters of the specified pattern to this pattern. The current pattern is not 856 * modified. This can be understood as creating a pattern for URLs that are relative to (or 857 * suffixes of) the current pattern. 858 * 859 * @example 860 * The following two matchers are equivalent: 861 * <pre> 862 * new UrlMatcher('/user/{id}?q').concat('/details?date'); 863 * new UrlMatcher('/user/{id}/details?q&date'); 864 * </pre> 865 * 866 * @param {string} pattern The pattern to append. 867 * @param {Object} config An object hash of the configuration for the matcher. 868 * @returns {UrlMatcher} A matcher for the concatenated pattern. 869 */ 870UrlMatcher.prototype.concat = function (pattern, config) { 871 // Because order of search parameters is irrelevant, we can add our own search 872 // parameters to the end of the new pattern. Parse the new pattern by itself 873 // and then join the bits together, but it's much easier to do this on a string level. 874 var defaultConfig = { 875 caseInsensitive: $$UMFP.caseInsensitive(), 876 strict: $$UMFP.strictMode(), 877 squash: $$UMFP.defaultSquashPolicy() 878 }; 879 return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, extend(defaultConfig, config), this); 880}; 881 882UrlMatcher.prototype.toString = function () { 883 return this.source; 884}; 885 886/** 887 * @ngdoc function 888 * @name ui.router.util.type:UrlMatcher#exec 889 * @methodOf ui.router.util.type:UrlMatcher 890 * 891 * @description 892 * Tests the specified path against this matcher, and returns an object c
892ontaining the captured 893 * parameter values, or null if the path does not match. The returned object contains the values 894 * of any search parameters that are mentioned in the pattern, but their value may be null if 895 * they are not present in `searchParams`. This means that search parameters are always treated 896 * as optional. 897 * 898 * @example 899 * <pre> 900 * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', { 901 * x: '1', q: 'hello' 902 * }); 903 * // returns { id: 'bob', q: 'hello', r: null } 904 * </pre> 905 * 906 * @param {string} path The URL path to match, e.g. `$location.path()`. 907 * @param {Object} searchParams URL search parameters, e.g. `$location.search()`. 908 * @returns {Object} The captured parameter values. 909 */ 910UrlMatcher.prototype.exec = function (path, searchParams) { 911 var m = this.regexp.exec(path); 912 if (!m) return null; 913 searchParams = searchParams || {}; 914 915 var paramNames = this.parameters(), nTotal = paramNames.length, 916 nPath = this.segments.length - 1, 917 values = {}, i, j, cfg, paramName; 918 919 if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'"); 920 921 function decodePathArray(string) { 922 function reverseString(str) { return str.split("").reverse().join(""); } 923 function unquoteDashes(str) { return str.replace(/\\-/g, "-"); } 924 925 var split = reverseString(string).split(/-(?!\\)/); 926 var allReversed = map(split, reverseString); 927 return map(allReversed, unquoteDashes).reverse(); 928 } 929 930 var param, paramVal; 931 for (i = 0; i < nPath; i++) { 932 paramName = paramNames[i]; 933 param = this.params[paramName]; 934 paramVal = m[i+1]; 935 // if the param value matches a pre-replace pair, replace the value before decoding. 936 for (j = 0; j < param.replace.length; j++) { 937 if (param.replace[j].from === paramVal) paramVal = param.replace[j].to; 938 } 939 if (paramVal && param.array === true) paramVal = decodePathArray(paramVal); 940 if (isDefined(paramVal)) paramVal = param.type.decode(paramVal); 941 values[paramName] = param.value(paramVal); 942 } 943 for (/**/; i < nTotal; i++) { 944 paramName = paramNames[i]; 945 values[paramName] = this.params[paramName].value(searchParams[paramName]); 946 param = this.params[paramName]; 947 paramVal = searchParams[paramName]; 948 for (j = 0; j < param.replace.length; j++) { 949 if (param.replace[j].from === paramVal) paramVal = param.replace[j].to; 950 } 951 if (isDefined(paramVal)) paramVal = param.type.decode(paramVal); 952 values[paramName] = param.value(paramVal); 953 } 954 955 return values; 956}; 957 958/** 959 * @ngdoc function 960 * @name ui.router.util.type:UrlMatcher#parameters 961 * @methodOf ui.router.util.type:UrlMatcher 962 * 963 * @description 964 * Returns the names of all path and search parameters of this pattern in an unspecified order. 965 * 966 * @returns {Array.<string>} An array of parameter names. Must be treated as read-only. If the 967 * pattern has no parameters, an empty array is returned. 968 */ 969UrlMatcher.prototype.parameters = function (param) { 970 if (!isDefined(param)) return this.$$paramNames; 971 return this.params[param] || null; 972}; 973 974/** 975 * @ngdoc function 976 * @name ui.router.util.type:UrlMatcher#validates 977 * @methodOf ui.router.util.type:UrlMatcher 978 * 979 * @description 980 * Checks an object hash of parameters to validate their correctness according to the parameter 981 * types of this `UrlMatcher`. 982 * 983 * @param {Object} params The object hash of parameters to validate. 984 * @returns {boolean} Returns `true` if `params` validates, otherwise `false`. 985 */ 986UrlMatcher.prototype.validates = function (params) { 987 return this.params.$$validates(params); 988}; 989 990/** 991 * @ngdoc function 992 * @name ui.router.util.type:UrlMatcher#format 993 * @methodOf ui.router.util.type:UrlMatcher 994 * 995 * @description 996 * Creates a URL that matches this pattern by substituting the specified values 997 * for the path and search parameters. Null values for path parameters are 998 * treated as empty strings. 999 * 1000 * @example 1001 * <pre> 1002 * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' }); 1003 * // returns '/user/bob?q=yes' 1004 * </pre> 1005 * 1006 * @param {Object} values the values to substitute for the parameters in this pattern. 1007 * @returns {string} the formatted URL (path and optionally search part). 1008 */ 1009UrlMatcher.prototype.format = function (values) { 1010 values = values || {}; 1011 var segments = this.segments, params = this.parameters(), paramset = this.params; 1012 if (!this.validates(values)) return null; 1013 1014 var i, search = false, nPath = segments.length - 1, nTotal = params.length, result = segments[0]; 1015 1016 function encodeDashes(str) { // Replace dashes with encoded "\-" 1017 return encodeURIComponent(str).replace(/-/g, function(c) { return '%5C%' + c.charCo
1017deAt(0).toString(16).toUpperCase(); }); 1018 } 1019 1020 for (i = 0; i < nTotal; i++) { 1021 var isPathParam = i < nPath; 1022 var name = params[i], param = paramset[name], value = param.value(values[name]); 1023 var isDefaultValue = param.isOptional && param.type.equals(param.value(), value); 1024 var squash = isDefaultValue ? param.squash : false; 1025 var encoded = param.type.encode(value); 1026 1027 if (isPathParam) { 1028 var nextSegment = segments[i + 1]; 1029 var isFinalPathParam = i + 1 === nPath; 1030 1031 if (squash === false) { 1032 if (encoded != null) { 1033 if (isArray(encoded)) { 1034 result += map(encoded, encodeDashes).join("-"); 1035 } else { 1036 result += encodeURIComponent(encoded); 1037 } 1038 } 1039 result += nextSegment; 1040 } else if (squash === true) { 1041 var capture = result.match(/\/$/) ? /\/?(.*)/ : /(.*)/; 1042 result += nextSegment.match(capture)[1]; 1043 } else if (isString(squash)) { 1044 result += squash + nextSegment; 1045 } 1046 1047 if (isFinalPathParam && param.squash === true && result.slice(-1) === '/') result = result.slice(0, -1); 1048 } else { 1049 if (encoded == null || (isDefaultValue && squash !== false)) continue; 1050 if (!isArray(encoded)) encoded = [ encoded ]; 1051 if (encoded.length === 0) continue; 1052 encoded = map(encoded, encodeURIComponent).join('&' + name + '='); 1053 result += (search ? '&' : '?') + (name + '=' + encoded); 1054 search = true; 1055 } 1056 } 1057 1058 return result; 1059}; 1060 1061/** 1062 * @ngdoc object 1063 * @name ui.router.util.type:Type 1064 * 1065 * @description 1066 * Implements an interface to define custom parameter types that can be decoded from and encoded to 1067 * string parameters matched in a URL. Used by {@link ui.router.util.type:UrlMatcher `UrlMatcher`} 1068 * objects when matching or formatting URLs, or comparing or validating parameter values. 1069 * 1070 * See {@link ui.router.util.$urlMatcherFactory#methods_type `$urlMatcherFactory#type()`} for more 1071 * information on registering custom types. 1072 * 1073 * @param {Object} config A configuration object which contains the custom type definition. The object's 1074 * properties will override the default methods and/or pattern in `Type`'s public interface. 1075 * @example 1076 * <pre> 1077 * { 1078 * decode: function(val) { return parseInt(val, 10); }, 1079 * encode: function(val) { return val && val.toString(); }, 1080 * equals: function(a, b) { return this.is(a) && a === b; }, 1081 * is: function(val) { return angular.isNumber(val) isFinite(val) && val % 1 === 0; }, 1082 * pattern: /\d+/ 1083 * } 1084 * </pre> 1085 * 1086 * @property {RegExp} pattern The regular expression pattern used to match values of this type when 1087 * coming from a substring of a URL. 1088 * 1089 * @returns {Object} Returns a new `Type` object. 1090 */ 1091function Type(config) { 1092 extend(this, config); 1093} 1094 1095/** 1096 * @ngdoc function 1097 * @name ui.router.util.type:Type#is 1098 * @methodOf ui.router.util.type:Type 1099 * 1100 * @description 1101 * Detects whether a value is of a particular type. Accepts a native (decoded) value 1102 * and determines whether it matches the current `Type` object. 1103 * 1104 * @param {*} val The value to check. 1105 * @param {string} key Optional. If the type check is happening in the context of a specific 1106 * {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the 1107 * parameter in which `val` is stored. Can be used for meta-programming of `Type` objects. 1108 * @returns {Boolean} Returns `true` if the value matches the type, otherwise `false`. 1109 */ 1110Type.prototype.is = function(val, key) { 1111 return true; 1112}; 1113 1114/** 1115 * @ngdoc function 1116 * @name ui.router.util.type:Type#encode 1117 * @methodOf ui.router.util.type:Type 1118 * 1119 * @description 1120 * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the 1121 * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it 1122 * only needs to be a representation of `val` that has been coerced to a string. 1123 * 1124 * @param {*} val The value to encode. 1125 * @param {string} key The name of the parameter in which `val` is stored. Can be used for 1126 * meta-programming of `Type` objects. 1127 * @returns {string} Returns a string representation of `val` that can be encoded in a URL. 1128 */ 1129Type.prototype.encode = function(val, key) { 1130 return val; 1131}; 1132 1133/** 1134 * @ngdoc function 1135 * @name ui.router.util.type:Type#decode 1136 * @methodOf ui.router.util.type:Type 1137 * 1138 * @description 1139 * Converts a parameter value (from URL string or transition param) to a custom/native value. 1140 * 1141 * @param {string} val The URL parameter value to decode. 1142 * @param {string} key The name of the parameter in which `val` is stored. Can be used for 1143 * meta-programming of `Type` objects. 1144 * @returns {*} Returns a custom representation of the URL parameter value. 1145 */ 1146Type.prototype.decode = function(val, key) { 1147 return val; 1148}; 1149 1150/** 1151 * @ngdoc function 1152 * @name ui.router.util.type:Type#equals 1153 * @methodOf ui.router.util.type:Type 1154 * 1155 * @description 1156 * Determines whether two decoded values are equivalent. 1157 * 1158 * @param {*} a A value to compare against. 1159 * @param {*} b A value to compare against. 1160 * @returns {Boolean} Returns `true` if the values are equivalent/equal, otherwise `false`. 1161 */ 1162Type.prototype.equals = function(a, b) { 1163 return a == b; 1164}; 1165 1166Type.prototype.$subPattern = function() { 1167 var sub = this.pattern.toString(); 1168 return sub.substr(1, sub.length - 2); 1169}; 1170 1171Type.prototype.pattern = /.*/; 1172 1173Type.prototype.toString = function() { return "{Type:" + this.name + "}"; }; 1174 1175/** Given an encoded string, or a decoded object, returns a decoded object */ 1176Type.prototype.$normalize = function(val) { 1177 return this.is(val) ? val : this.decode(val); 1178}; 1179 1180/* 1181 * Wraps an existing custom Type as an array of Type, depending on 'mode'. 1182 * e.g.: 1183 * - urlmatcher pattern "/path?{queryParam[]:int}" 1184 * - url: "/path?queryParam=1&queryParam=2 1185 * - $stateParams.queryParam will be [1, 2] 1186 * if `mode` is "auto", then 1187 * - url: "/path?queryParam=1 will create $stateParams.queryParam: 1 1188 * - url: "/path?queryParam=1&queryParam=2 will create $stateParams.queryParam: [1, 2] 1189 */ 1190Type.prototype.$asArray = function(mode, isSearch) { 1191 if (!mode) return this; 1192 if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only"); 1193 1194 function ArrayType(type, mode) { 1195 function bindTo(type, callbackName) { 1196 return function() { 1197 return type[callbackName].apply(type, arguments); 1198 }; 1199 } 1200 1201 // Wrap non-array value as array 1202 function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); } 1203 // Unwrap array value for "auto" mode. Return undefined for empty array. 1204 function arrayUnwrap(val) { 1205 switch(val.length) { 1206 case 0: return undefined; 1207 case 1: return mode === "auto" ? val[0] : val; 1208 default: return val; 1209 } 1210 } 1211 function falsey(val) { return !val; } 1212 1213 // Wraps type (.is/.encode/.decode) functions to operate on each value of an array 1214 function arrayHandler(callback, allTruthyMode) { 1215 return function handleArray(val) { 1216 if (isArray(val) && val.length === 0) return val; 1217 val = arrayWrap(val); 1218 var result = map(val, callback); 1219 if (allTruthyMode === true) 1220 return filter(result, falsey).length === 0; 1221 return arrayUnwrap(result); 1222 }; 1223 } 1224 1225 // Wraps type (.equals) functions to operate on each value of an array 1226 function arrayEqualsHandler(callback) { 1227 return function handleArray(val1, val2) { 1228 var left = arrayWrap(val1), right = arrayWrap(val2); 1229 if (left.length !== right.length) return false;
1230 for (var i = 0; i < left.length; i++) { 1231 if (!callback(left[i], right[i])) return false; 1232 } 1233 return true; 1234 }; 1235 } 1236 1237 this.encode = arrayHandler(bindTo(type, 'encode')); 1238 this.decode = arrayHandler(bindTo(type, 'decode')); 1239 this.is = arrayHandler(bindTo(type, 'is'), true); 1240 this.equals = arrayEqualsHandler(bindTo(type, 'equals')); 1241 this.pattern = type.pattern; 1242 this.$normalize = arrayHandler(bindTo(type, '$normalize')); 1243 this.name = type.name; 1244 this.$arrayMode = mode; 1245 } 1246 1247 return new ArrayType(this, mode); 1248}; 1249 1250 1251 1252/** 1253 * @ngdoc object 1254 * @name ui.router.util.$urlMatcherFactory 1255 * 1256 * @description 1257 * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory 1258 * is also available to providers under the name `$urlMatcherFactoryProvider`. 1259 */ 1260function $UrlMatcherFactory() { 1261 $$UMFP = this; 1262 1263 var isCaseInsensitive = false, isStrictMode = true, defaultSquashPolicy = false; 1264 1265 // Use tildes to pre-encode slashes. 1266 // If the slashes are simply URLEncoded, the browser can choose to pre-decode them, 1267 // and bidirectional encoding/decoding fails. 1268 // Tilde was chosen because it's not a RFC 3986 section 2.2 Reserved Character 1269 function valToString(val) { return val != null ? val.toString().replace(/~/g, "~~").replace(/\//g, "~2F") : val; } 1270 function valFromString(val) { return val != null ? val.toString().replace(/~2F/g, "/").replace(/~~/g, "~") : val; } 1271 1272 var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = { 1273 "string": { 1274 encode: valToString, 1275 decode: valFromString, 1276 // TODO: in 1.0, make string .is() return false if value is undefined/null by default. 1277 // In 0.2.x, string params are optional by default for backwards compat 1278 is: function(val) { return val == null || !isDefined(val) || typeof val === "string"; }, 1279 pattern: /[^/]*/ 1280 }, 1281 "int": { 1282 encode: valToString, 1283 decode: function(val) { return parseInt(val, 10); }, 1284 is: function(val) { return isDefined(val) && this.decode(val.toString()) === val; }, 1285 pattern: /\d+/ 1286 }, 1287 "bool": { 1288 encode: function(val) { return val ? 1 : 0; }, 1289 decode: function(val) { return parseInt(val, 10) !== 0; }, 1290 is: function(val) { return val === true || val === false; }, 1291 pattern: /0|1/ 1292 }, 1293 "date": { 1294 encode: function (val) { 1295 if (!this.is(val)) 1296 return undefined; 1297 return [ val.getFullYear(), 1298 ('0' + (val.getMonth() + 1)).slice(-2), 1299 ('0' + val.getDate()).slice(-2) 1300 ].join("-"); 1301 }, 1302 decode: function (val) { 1303 if (this.is(val)) return val; 1304 var match = this.capture.exec(val); 1305 return match ? new Date(match[1], match[2] - 1, match[3]) : undefined; 1306 }, 1307 is: function(val) { return val instanceof Date && !isNaN(val.valueOf()); }, 1308 equals: function (a, b) { return this.is(a) && this.is(b) && a.toISOString() === b.toISOString(); }, 1309 pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/, 1310 capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/ 1311 }, 1312 "json": { 1313 encode: angular.toJson, 1314 decode: angular.fromJson, 1315 is: angular.isObject, 1316 equals: angular.equals, 1317 pattern: /[^/]*/ 1318 }, 1319 "any": { // does not encode/decode 1320 encode: angular.identity, 1321 decode: angular.identity, 1322 equals: angular.equals, 1323 pattern: /.*/ 1324 } 1325 }; 1326 1327 function getDefaultConfig() { 1328 return { 1329 strict: isStrictMode, 1330 caseInsensitive: isCaseInsensitive 1331 }; 1332 } 1333 1334 function isInjectable(value) { 1335 return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1]))); 1336 } 1337 1338 /** 1339 * [Internal] Get the default value of a parameter, which may be an injectable function. 1340 */ 1341 $UrlMatcherFactory.$$getDefaultValue = function(config) { 1342 if (!isInjectable(config.value)) return config.value; 1343 if (!injector) throw new Error("Injectable functions cannot be called at configuration time"); 1344 return injector.invoke(config.value); 1345 }; 1346 1347 /** 1348 * @ngdoc function 1349 * @name ui.router.util.$urlMatcherFactory#caseInsensitive 1350 * @methodOf ui.router.util.$urlMatcherFactory 1351 * 1352 * @description 1353 * Defines whether URL matching should be case sensitive (the default behavior), or not. 1354 * 1355 * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`; 1356 * @returns {boolean} the current value of caseInsensitive 1357 */ 1358 this.caseInsensitive = function(value) { 1359 if (isDefined(value)) 1360 isCaseInsensitive = value; 1361 return isCaseInsensitive; 1362 }; 1363 1364 /** 1365 * @ngdoc function 1366 * @name ui.router.util.$urlMatcherFactory#strictMode 1367 * @methodOf ui.router.util.$urlMatcherFactory 1368 * 1369 * @description 1370 * Defines whether URLs should match trailing slashes, or not (the default behavior). 1371 * 1372 * @param {boolean=} value `false` to match trailing slashes in URLs, otherwise `true`. 1373 * @returns {boolean} the current value of strictMode 1374 */ 1375 this.strictMode = function(value) { 1376 if (isDefined(value)) 1377 isStrictMode = value; 1378 return isStrictMode; 1379 }; 1380 1381 /** 1382 * @ngdoc function 1383 * @name ui.router.util.$urlMatcherFactory#defaultSquashPolicy 1384 * @methodOf ui.router.util.$urlMatcherFactory 1385 * 1386 * @description 1387 * Sets the default behavior when generating or matching URLs with default parameter values. 1388 * 1389 * @param {string} value A string that defines the default parameter URL squashing behavior. 1390 * `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL 1391 * `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the 1392 * parameter is surrounded by slashes, squash (remove) one slash from the URL 1393 * any other string, e.g. "~": When generating an href with a default parameter value, squash (remove) 1394 * the parameter value from the URL and replace it with this string. 1395 */ 1396 this.defaultSquashPolicy = function(value) { 1397 if (!isDefined(value)) return defaultSquashPolicy; 1398 if (value !== true && value !== false && !isString(value)) 1399 throw new Error("Invalid squash policy: " + value + ". Valid policies: false, true, arbitrary-string"); 1400 defaultSquashPolicy = value; 1401 return value; 1402 }; 1403 1404 /** 1405 * @ngdoc function 1406 * @name ui.router.util.$urlMatcherFactory#compile 1407 * @methodOf ui.router.util.$urlMatcherFactory 1408 * 1409 * @description 1410 * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern. 1411 * 1412 * @param {string} pattern The URL pattern. 1413 * @param {Object} config The config object hash. 1414 * @returns {UrlMatcher} The UrlMatcher. 1415 */ 1416 this.compile = function (pattern, config) { 1417 return new UrlMatcher(pattern, extend(getDefaultConfig(), config)); 1418 }; 1419 1420 /** 1421 * @ngdoc function 1422 * @name ui.router.util.$urlMatcherFactory#isMatcher 1423 * @methodOf ui.router.util.$urlMatcherFactory 1424 * 1425 * @description 1426 * Returns true if the specified object is a `UrlMatcher`, or false otherwise. 1427 * 1428 * @param {Object} object The object to perform the type check against. 1429 * @returns {Boolean} Returns `true` if the object matches the `UrlMatcher` interface, by 1430 * implementing all the same methods. 1431 */ 1432 this.isMatcher = function (o) { 1433 if (!isObject(o)) return false;
1434 var result = true; 1435 1436 forEach(UrlMatcher.prototype, function(val, name) { 1437 if (isFunction(val)) { 1438 result = result && (isDefined(o[name]) && isFunction(o[name])); 1439 } 1440 }); 1441 return result; 1442 }; 1443 1444 /** 1445 * @ngdoc function 1446 * @name ui.router.util.$urlMatcherFactory#type 1447 * @methodOf ui.router.util.$urlMatcherFactory 1448 * 1449 * @description 1450 * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to 1451 * generate URLs with typed parameters. 1452 * 1453 * @param {string} name The type name. 1454 * @param {Object|Function} definition The type definition. See 1455 * {@link ui.router.util.type:Type `Type`} for information on the values accepted. 1456 * @param {Object|Function} definitionFn (optional) A function that is injected before the app 1457 * runtime starts. The result of this function is merged into the existing `definition`. 1458 * See {@link ui.router.util.type:Type `Type`} for information on the values accepted. 1459 * 1460 * @returns {Object} Returns `$urlMatcherFactoryProvider`. 1461 * 1462 * @example 1463 * This is a simple example of a custom type that encodes and decodes items from an 1464 * array, using the array index as the URL-encoded value: 1465 * 1466 * <pre> 1467 * var list = ['John', 'Paul', 'George', 'Ringo']; 1468 * 1469 * $urlMatcherFactoryProvider.type('listItem', { 1470 * encode: function(item) { 1471 * // Represent the list item in the URL using its corresponding index 1472 * return list.indexOf(item); 1473 * }, 1474 * decode: function(item) { 1475 * // Look up the list item by index 1476 * return list[parseInt(item, 10)]; 1477 * }, 1478 * is: function(item) { 1479 * // Ensure the item is valid by checking to see that it appears 1480 * // in the list 1481 * return list.indexOf(item) > -1; 1482 * } 1483 * }); 1484 * 1485 * $stateProvider.state('list', { 1486 * url: "/list/{item:listItem}", 1487 * controller: function($scope, $stateParams) { 1488 * console.log($stateParams.item); 1489 * } 1490 * }); 1491 * 1492 * // ... 1493 * 1494 * // Changes URL to '/list/3', logs "Ringo" to the console 1495 * $state.go('list', { item: "Ringo" }); 1496 * </pre> 1497 * 1498 * This is a more complex example of a type that relies on dependency injection to 1499 * interact with services, and uses the parameter name from the URL to infer how to 1500 * handle encoding and decoding parameter values: 1501 * 1502 * <pre> 1503 * // Defines a custom type that gets a value from a service, 1504 * // where each service gets different types of values from 1505 * // a backend API: 1506 * $urlMatcherFactoryProvider.type('dbObject', {}, function(Users, Posts) { 1507 * 1508 * // Matches up services to URL parameter names 1509 * var services = { 1510 * user: Users, 1511 * post: Posts 1512 * }; 1513 * 1514 * return { 1515 * encode: function(object) { 1516 * // Represent the object in the URL using its unique ID 1517 * return object.id; 1518 * }, 1519 * decode: function(value, key) { 1520 * // Look up the object by ID, using the parameter 1521 * // name (key) to call the correct service 1522 * return services[key].findById(value); 1523 * }, 1524 * is: function(object, key) { 1525 * // Check that object is a valid dbObject 1526 * return angular.isObject(object) && object.id && services[key]; 1527 * } 1528 * equals: function(a, b) { 1529 * // Check the equality of decoded objects by comparing 1530 * // their unique IDs 1531 * return a.id === b.id; 1532 * } 1533 * }; 1534 * }); 1535 * 1536 * // In a config() block, you can then attach URLs with 1537 * // type-annotated parameters: 1538 * $stateProvider.state('users', { 1539 * url: "/users", 1540 * // ... 1541 * }).state('users.item', { 1542 * url: "/{user:dbObject}", 1543 * controller: function($scope, $stateParams) { 1544 * // $stateParams.user will now be an object returned from 1545 * // the Users service 1546 * }, 1547 * // ... 1548 * }); 1549 * </pre> 1550 */ 1551 this.type = function (name, definition, definitionFn) { 1552 if (!isDefined(definition)) return $types[name]; 1553 if ($types.hasOwnProperty(name)) throw new Error("A type named '" + name + "' has already been defined."); 1554 1555 $types[name] = new Type(extend({ name: name }, definition)); 1556 if (definitionFn) { 1557 typeQueue.push({ name: name, def: definitionFn }); 1558 if (!enqueue) flushTypeQueue(); 1559 } 1560 return this; 1561 }; 1562 1563 // `flushTypeQueue()` waits until `$urlMatcherFactory` is injected before invoking the queued `definitionFn`s 1564 function flushTypeQueue() { 1565 while(typeQueue.length) { 1566 var type = typeQueue.shift(); 1567 if (type.pattern) throw new Error("You cannot override a type's .pattern at runtime."); 1568 angular.extend($types[type.name], injector.invoke(type.def)); 1569 } 1570 } 1571 1572 // Register default types. Store them in the prototype of $types. 1573 forEach(defaultTypes, function(type, name) { $types[name] = new Type(extend({name: name}, type)); }); 1574 $types = inherit($types, {}); 1575 1576 /* No need to document $get, since it returns this */ 1577 this.$get = ['$injector', function ($injector) { 1578 injector = $injector; 1579 enqueue = false;
1580 flushTypeQueue(); 1581 1582 forEach(defaultTypes, function(type, name) { 1583 if (!$types[name]) $types[name] = new Type(type); 1584 }); 1585 return this; 1586 }]; 1587 1588 this.Param = function Param(id, type, config, location) { 1589 var self = this; 1590 config = unwrapShorthand(config); 1591 type = getType(config, type, location); 1592 var arrayMode = getArrayMode(); 1593 type = arrayMode ? type.$asArray(arrayMode, location === "search") : type; 1594 if (type.name === "string" && !arrayMode && location === "path" && config.value === undefined) 1595 config.value = ""; // for 0.2.x; in 0.3.0+ do not automatically default to "" 1596 var isOptional = config.value !== undefined; 1597 var squash = getSquashPolicy(config, isOptional); 1598 var replace = getReplace(config, arrayMode, isOptional, squash); 1599 1600 function unwrapShorthand(config) { 1601 var keys = isObject(config) ? objectKeys(config) : []; 1602 var isShorthand = indexOf(keys, "value") === -1 && indexOf(keys, "type") === -1 && 1603 indexOf(keys, "squash") === -1 && indexOf(keys, "array") === -1; 1604 if (isShorthand) config = { value: config }; 1605 config.$$fn = isInjectable(config.value) ? config.value : function () { return config.value; }; 1606 return config; 1607 } 1608 1609 function getType(config, urlType, location) { 1610 if (config.type && urlType) throw new Error("Param '"+id+"' has two type configurations."); 1611 if (urlType) return urlType; 1612 if (!config.type) return (location === "config" ? $types.any : $types.string); 1613 1614 if (angular.isString(config.type)) 1615 return $types[config.type]; 1616 if (config.type instanceof Type) 1617 return config.type; 1618 return new Type(config.type); 1619 } 1620 1621 // array config: param name (param[]) overrides default settings. explicit config overrides param name. 1622 function getArrayMode() { 1623 var arrayDefaults = { array: (location === "search" ? "auto" : false) }; 1624 var arrayParamNomenclature = id.match(/\[\]$/) ? { array: true } : {}; 1625 return extend(arrayDefaults, arrayParamNomenclature, config).array; 1626 } 1627 1628 /** 1629 * returns false, true, or the squash value to indicate the "default parameter url squash policy". 1630 */ 1631 function getSquashPolicy(config, isOptional) { 1632 var squash = config.squash; 1633 if (!isOptional || squash === false) return false; 1634 if (!isDefined(squash) || squash == null) return defaultSquashPolicy; 1635 if (squash === true || isString(squash)) return squash; 1636 throw new Error("Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string"); 1637 } 1638 1639 function getReplace(config, arrayMode, isOptional, squash) { 1640 var replace, configuredKeys, defaultPolicy = [ 1641 { from: "", to: (isOptional || arrayMode ? undefined : "") }, 1642 { from: null, to: (isOptional || arrayMode ? undefined : "") } 1643 ]; 1644 replace = isArray(config.replace) ? config.replace : []; 1645 if (isString(squash)) 1646 replace.push({ from: squash, to: undefined }); 1647 configuredKeys = map(replace, function(item) { return item.from; } ); 1648 return filter(defaultPolicy, function(item) { return indexOf(configuredKeys, item.from) === -1; }).concat(replace); 1649 } 1650 1651 /** 1652 * [Internal] Get the default value of a parameter, which may be an injectable function. 1653 */ 1654 function $$getDefaultValue() { 1655 if (!injector) throw new Error("Injectable functions cannot be called at configuration time"); 1656 var defaultValue = injector.invoke(config.$$fn); 1657 if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue)) 1658 throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")"); 1659 return defaultValue; 1660 } 1661 1662 /** 1663 * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the 1664 * default value, which may be the result of an injectable function. 1665 */ 1666 function $value(value) { 1667 function hasReplaceVal(val) { return function(obj) { return obj.from === val; }; } 1668 function $replace(value) { 1669 var replacement = map(filter(self.replace, hasReplaceVal(value)), function(obj) { return obj.to; }); 1670 return replacement.length ? replacement[0] : value; 1671 } 1672 value = $replace(value); 1673 return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value); 1674 } 1675 1676 function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; } 1677 1678 extend(this, { 1679 id: id, 1680 type: type, 1681 location: location, 1682 array: arrayMode, 1683 squash: squash, 1684 replace: replace, 1685 isOptional: isOptional, 1686 value: $value, 1687 dynamic: undefined, 1688 config: config, 1689 toString: toString 1690 }); 1691 }; 1692 1693 function ParamSet(params) { 1694 extend(this, params || {}); 1695 } 1696 1697 ParamSet.prototype = { 1698 $$new: function() { 1699 return inherit(this, extend(new ParamSet(), { $$parent: this})); 1700 }, 1701 $$keys: function () { 1702 var keys = [], chain = [], parent = this, 1703 ignore = objectKeys(ParamSet.prototype); 1704 while (parent) { chain.push(parent); parent = parent.$$parent; } 1705 chain.reverse();
1706 forEach(chain, function(paramset) { 1707 forEach(objectKeys(paramset), function(key) { 1708 if (indexOf(keys, key) === -1 && indexOf(ignore, key) === -1) keys.push(key); 1709 }); 1710 }); 1711 return keys; 1712 }, 1713 $$values: function(paramValues) { 1714 var values = {}, self = this; 1715 forEach(self.$$keys(), function(key) { 1716 values[key] = self[key].value(paramValues && paramValues[key]); 1717 }); 1718 return values; 1719 }, 1720 $$equals: function(paramValues1, paramValues2) { 1721 var equal = true, self = this; 1722 forEach(self.$$keys(), function(key) { 1723 var left = paramValues1 && paramValues1[key], right = paramValues2 && paramValues2[key]; 1724 if (!self[key].type.equals(left, right)) equal = false; 1725 }); 1726 return equal; 1727 }, 1728 $$validates: function $$validate(paramValues) { 1729 var keys = this.$$keys(), i, param, rawVal, normalized, encoded; 1730 for (i = 0; i < keys.length; i++) { 1731 param = this[keys[i]]; 1732 rawVal = paramValues[keys[i]]; 1733 if ((rawVal === undefined || rawVal === null) && param.isOptional) 1734 break; // There was no parameter value, but the param is optional 1735 normalized = param.type.$normalize(rawVal); 1736 if (!param.type.is(normalized)) 1737 return false; // The value was not of the correct Type, and could not be decoded to the correct Type 1738 encoded = param.type.encode(normalized); 1739 if (angular.isString(encoded) && !param.type.pattern.exec(encoded)) 1740 return false; // The value was of the correct type, but when encoded, did not match the Type's regexp 1741 } 1742 return true; 1743 }, 1744 $$parent: undefined 1745 }; 1746 1747 this.ParamSet = ParamSet; 1748} 1749 1750// Register as a provider so it's available to other providers 1751angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory); 1752angular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]); 1753 1754/** 1755 * @ngdoc object 1756 * @name ui.router.router.$urlRouterProvider 1757 * 1758 * @requires ui.router.util.$urlMatcherFactoryProvider 1759 * @requires $locationProvider 1760 * 1761 * @description 1762 * `$urlRouterProvider` has the responsibility of watching `$location`. 1763 * When `$location` changes it runs through a list of rules one by one until a 1764 * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify 1765 * a url in a state configuration. All urls are compiled into a UrlMatcher object. 1766 * 1767 * There are several methods on `$urlRouterProvider` that make it useful to use directly 1768 * in your module config. 1769 */ 1770$UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider']; 1771function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) { 1772 var rules = [], otherwise = null, interceptDeferred = false, listener; 1773 1774 // Returns a string that is a prefix of all strings matching the RegExp 1775 function regExpPrefix(re) { 1776 var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source); 1777 return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : ''; 1778 } 1779 1780 // Interpolates matched values into a String.replace()-style pattern 1781 function interpolate(pattern, match) { 1782 return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) { 1783 return match[what === '$' ? 0 : Number(what)]; 1784 }); 1785 } 1786 1787 /** 1788 * @ngdoc function 1789 * @name ui.router.router.$urlRouterProvider#rule 1790 * @methodOf ui.router.router.$urlRouterProvider 1791 * 1792 * @description 1793 * Defines rules that are used by `$urlRouterProvider` to find matches for 1794 * specific URLs. 1795 * 1796 * @example 1797 * <pre> 1798 * var app = angular.module('app', ['ui.router.router']); 1799 * 1800 * app.config(function ($urlRouterProvider) { 1801 * // Here's an example of how you might allow case insensitive urls 1802 * $urlRouterProvider.rule(function ($injector, $location) { 1803 * var path = $location.path(), 1804 * normalized = path.toLowerCase(); 1805 * 1806 * if (path !== normalized) { 1807 * return normalized; 1808 * } 1809 * }); 1810 * }); 1811 * </pre> 1812 * 1813 * @param {function} rule Handler function that takes `$injector` and `$location` 1814 * services as arguments. You can use them to return a valid path as a string. 1815 * 1816 * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance 1817 */ 1818 this.rule = function (rule) { 1819 if (!isFunction(rule)) throw new Error("'rule' must be a function"); 1820 rules.push(rule); 1821 return this; 1822 }; 1823 1824 /** 1825 * @ngdoc object 1826 * @name ui.router.router.$urlRouterProvider#otherwise 1827 * @methodOf ui.router.router.$urlRouterProvider 1828 * 1829 * @description
1830 * Defines a path that is used when an invalid route is requested. 1831 * 1832 * @example 1833 * <pre> 1834 * var app = angular.module('app', ['ui.router.router']); 1835 * 1836 * app.config(function ($urlRouterProvider) { 1837 * // if the path doesn't match any of the urls you configured 1838 * // otherwise will take care of routing the user to the 1839 * // specified url 1840 * $urlRouterProvider.otherwise('/index'); 1841 * 1842 * // Example of using function rule as param 1843 * $urlRouterProvider.otherwise(function ($injector, $location) { 1844 * return '/a/valid/url'; 1845 * }); 1846 * }); 1847 * </pre> 1848 * 1849 * @param {string|function} rule The url path you want to redirect to or a function 1850 * rule that returns the url path. The function version is passed two params: 1851 * `$injector` and `$location` services, and must return a url string. 1852 * 1853 * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance 1854 */ 1855 this.otherwise = function (rule) { 1856 if (isString(rule)) { 1857 var redirect = rule; 1858 rule = function () { return redirect; }; 1859 } 1860 else if (!isFunction(rule)) throw new Error("'rule' must be a function"); 1861 otherwise = rule; 1862 return this; 1863 }; 1864 1865 1866 function handleIfMatch($injector, handler, match) { 1867 if (!match) return false; 1868 var result = $injector.invoke(handler, handler, { $match: match }); 1869 return isDefined(result) ? result : true; 1870 } 1871 1872 /** 1873 * @ngdoc function 1874 * @name ui.router.router.$urlRouterProvider#when 1875 * @methodOf ui.router.router.$urlRouterProvider 1876 * 1877 * @description 1878 * Registers a handler for a given url matching. 1879 * 1880 * If the handler is a string, it is 1881 * treated as a redirect, and is interpolated according to the syntax of match 1882 * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise). 1883 * 1884 * If the handler is a function, it is injectable. It gets invoked if `$location` 1885 * matches. You have the option of inject the match object as `$match`. 1886 * 1887 * The handler can return 1888 * 1889 * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter` 1890 * will continue trying to find another one that matches. 1891 * - **string** which is treated as a redirect and passed to `$location.url()` 1892 * - **void** or any **truthy** value tells `$urlRouter` that the url was handled. 1893 * 1894 * @example 1895 * <pre> 1896 * var app = angular.module('app', ['ui.router.router']); 1897 * 1898 * app.config(function ($urlRouterProvider) { 1899 * $urlRouterProvider.when($state.url, function ($match, $stateParams) { 1900 * if ($state.$current.navigable !== state || 1901 * !equalForKeys($match, $stateParams) { 1902 * $state.transitionTo(state, $match, false); 1903 * } 1904 * }); 1905 * }); 1906 * </pre> 1907 * 1908 * @param {string|object} what The incoming path that you want to redirect. 1909 * @param {string|function} handler The path you want to redirect your user to. 1910 */ 1911 this.when = function (what, handler) { 1912 var redirect, handlerIsString = isString(handler); 1913 if (isString(what)) what = $urlMatcherFactory.compile(what); 1914 1915 if (!handlerIsString && !isFunction(handler) && !isArray(handler)) 1916 throw new Error("invalid 'handler' in when()"); 1917 1918 var strategies = { 1919 matcher: function (what, handler) { 1920 if (handlerIsString) { 1921 redirect = $urlMatcherFactory.compile(handler); 1922 handler = ['$match', function ($match) { return redirect.format($match); }]; 1923 } 1924 return extend(function ($injector, $location) { 1925 return handleIfMatch($injector, handler, what.exec($location.path(), $location.search())); 1926 }, { 1927 prefix: isString(what.prefix) ? what.prefix : '' 1928 }); 1929 }, 1930 regex: function (what, handler) { 1931 if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky"); 1932 1933 if (handlerIsString) { 1934 redirect = handler; 1935 handler = ['$match', function ($match) { return interpolate(redirect, $match); }]; 1936 } 1937 return extend(function ($injector, $location) { 1938 return handleIfMatch($injector, handler, what.exec($location.path())); 1939 }, { 1940 prefix: regExpPrefix(what) 1941 }); 1942 } 1943 }; 1944 1945 var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp }; 1946 1947 for (var n in check) { 1948 if (check[n]) return this.rule(strategies[n](what, handler)); 1949 } 1950 1951 throw new Error("invalid 'what' in when()"); 1952 }; 1953 1954 /** 1955 * @ngdoc function 1956 * @name ui.router.router.$urlRouterProvider#deferIntercept 1957 * @methodOf ui.router.router.$urlRouterProvider 1958 * 1959 * @description 1960 * Disables (or enables) deferring location change interception. 1961 * 1962 * If you wish to customize the behavior of syncing the URL (for example, if you wish to 1963 * defer a transition but maintain the current URL), call this method at configuration time. 1964 * Then, at run time, call `$urlRouter.listen()` after you have configured your own 1965 * `$locationChangeSuccess` event handler. 1966 * 1967 * @example 1968 * <pre> 1969 * var app = angular.module('app', ['ui.router.router']); 1970 * 1971 * app.config(function ($urlRouterProvider) { 1972 * 1973 * // Prevent $urlRouter from automatically intercepting URL changes; 1974 * // this allows you to configure custom behavior in between 1975 * // location changes and route synchronization: 1976 * $urlRouterProvider.deferIntercept(); 1977 * 1978 * }).run(function ($rootScope, $urlRouter, UserService) { 1979 * 1980 * $rootScope.$on('$locationChangeSuccess', function(e) { 1981 * // UserService is an example service for managing user state 1982 * if (UserService.isLoggedIn()) return; 1983 * 1984 * // Prevent $urlRouter's default handler from firing 1985 * e.preventDefault(); 1986 * 1987 * UserService.handleLogin().then(function() { 1988 * // Once the user has logged in, sync the current URL 1989 * // to the router: 1990 * $urlRouter.sync(); 1991 * }); 1992 * }); 1993 * 1994 * // Configures $urlRouter's listener *after* your custom listener 1995 * $urlRouter.listen(); 1996 * }); 1997 * </pre> 1998 * 1999 * @param {boolean} defer Indicates whether to defer location change interception. Passing 2000 no parameter is equivalent to `true`. 2001 */ 2002 this.deferIntercept = function (defer) { 2003 if (defer === undefined) defer = true; 2004 interceptDeferred = defer; 2005 }; 2006 2007 /** 2008 * @ngdoc object 2009 * @name ui.router.router.$urlRouter 2010 * 2011 * @requires $location 2012 * @requires $rootScope 2013 * @requires $injector 2014 * @requires $browser 2015 * 2016 * @description 2017 * 2018 */ 2019 this.$get = $get; 2020 $get.$inject = ['$location', '$rootScope', '$injector', '$browser', '$sniffer']; 2021 function $get( $location, $rootScope, $injector, $browser, $sniffer) { 2022 2023 var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl; 2024 2025 function appendBasePath(url, isHtml5, absolute) { 2026 if (baseHref === '/') return url; 2027 if (isHtml5) return baseHref.slice(0, -1) + url; 2028 if (absolute) return baseHref.slice(1) + url; 2029 return url; 2030 } 2031 2032 // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree 2033 function update(evt) { 2034 if (evt && evt.defaultPrevented) return; 2035 var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl; 2036 lastPushedUrl = undefined; 2037 // TODO: Re-implement this in 1.0 for https://github.com/angular-ui/ui-router/issues/1573 2038 //if (ignoreUpdate) return true; 2039 2040 function check(rule) { 2041 var handled = rule($injector, $location); 2042 2043 if (!handled) return false;
2044 if (isString(handled)) $location.replace().url(handled); 2045 return true; 2046 } 2047 var n = rules.length, i; 2048 2049 for (i = 0; i < n; i++) { 2050 if (check(rules[i])) return; 2051 } 2052 // always check otherwise last to allow dynamic updates to the set of rules 2053 if (otherwise) check(otherwise); 2054 } 2055 2056 function listen() { 2057 listener = listener || $rootScope.$on('$locationChangeSuccess', update); 2058 return listener; 2059 } 2060 2061 if (!interceptDeferred) listen(); 2062 2063 return { 2064 /** 2065 * @ngdoc function 2066 * @name ui.router.router.$urlRouter#sync 2067 * @methodOf ui.router.router.$urlRouter 2068 * 2069 * @description 2070 * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`. 2071 * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event, 2072 * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed 2073 * with the transition by calling `$urlRouter.sync()`. 2074 * 2075 * @example 2076 * <pre> 2077 * angular.module('app', ['ui.router']) 2078 * .run(function($rootScope, $urlRouter) { 2079 * $rootScope.$on('$locationChangeSuccess', function(evt) { 2080 * // Halt state change from even starting 2081 * evt.preventDefault(); 2082 * // Perform custom logic 2083 * var meetsRequirement = ... 2084 * // Continue with the update and state transition if logic allows 2085 * if (meetsRequirement) $urlRouter.sync(); 2086 * }); 2087 * }); 2088 * </pre> 2089 */ 2090 sync: function() { 2091 update(); 2092 }, 2093 2094 listen: function() { 2095 return listen(); 2096 }, 2097 2098 update: function(read) { 2099 if (read) { 2100 location = $location.url(); 2101 return; 2102 } 2103 if ($location.url() === location) return; 2104 2105 $location.url(location); 2106 $location.replace(); 2107 }, 2108 2109 push: function(urlMatcher, params, options) { 2110 var url = urlMatcher.format(params || {}); 2111 2112 // Handle the special hash param, if needed 2113 if (url !== null && params && params['#']) { 2114 url += '#' + params['#']; 2115 } 2116 2117 $location.url(url); 2118 lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined; 2119 if (options && options.replace) $location.replace(); 2120 }, 2121 2122 /** 2123 * @ngdoc function 2124 * @name ui.router.router.$urlRouter#href 2125 * @methodOf ui.router.router.$urlRouter 2126 * 2127 * @description 2128 * A URL generation method that returns the compiled URL for a given 2129 * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters. 2130 * 2131 * @example 2132 * <pre> 2133 * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), { 2134 * person: "bob" 2135 * }); 2136 * // $bob == "/about/bob"; 2137 * </pre> 2138 * 2139 * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate. 2140 * @param {object=} params An object of parameter values to fill the matcher's required parameters. 2141 * @param {object=} options Options object. The options are: 2142 * 2143 * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl". 2144 * 2145 * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher` 2146 */ 2147 href: function(urlMatcher, params, options) { 2148 if (!urlMatcher.validates(params)) return null; 2149 2150 var isHtml5 = $locationProvider.html5Mode(); 2151 if (angular.isObject(isHtml5)) { 2152 isHtml5 = isHtml5.enabled; 2153 } 2154 2155 isHtml5 = isHtml5 && $sniffer.history; 2156 2157 var url = urlMatcher.format(params); 2158 options = options || {}; 2159 2160 if (!isHtml5 && url !== null) { 2161 url = "#" + $locationProvider.hashPrefix() + url; 2162 } 2163 2164 // Handle special hash param, if needed 2165 if (url !== null && params && params['#']) { 2166 url += '#' + params['#']; 2167 } 2168 2169 url = appendBasePath(url, isHtml5, options.absolute); 2170 2171 if (!options.absolute || !url) { 2172 return url; 2173 } 2174 2175 var slash = (!isHtml5 && url ? '/' : ''), port = $location.port(); 2176 port = (port === 80 || port === 443 ? '' : ':' + port); 2177 2178 return [$location.protocol(), '://', $location.host(), port, slash, url].join(''); 2179 } 2180 }; 2181 } 2182} 2183 2184angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider); 2185 2186/** 2187 * @ngdoc object 2188 * @name ui.router.state.$stateProvider 2189 * 2190 * @requires ui.router.router.$urlRouterProvider
2191 * @requires ui.router.util.$urlMatcherFactoryProvider 2192 * 2193 * @description 2194 * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely 2195 * on state. 2196 * 2197 * A state corresponds to a "place" in the application in terms of the overall UI and 2198 * navigation. A state describes (via the controller / template / view properties) what 2199 * the UI looks like and does at that place. 2200 * 2201 * States often have things in common, and the primary way of factoring out these 2202 * commonalities in this model is via the state hierarchy, i.e. parent/child states aka 2203 * nested states. 2204 * 2205 * The `$stateProvider` provides interfaces to declare these states for your app. 2206 */ 2207$StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider']; 2208function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { 2209 2210 var root, states = {}, $state, queue = {}, abstractKey = 'abstract'; 2211 2212 // Builds state properties from definition passed to registerState() 2213 var stateBuilder = { 2214 2215 // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined. 2216 // state.children = []; 2217 // if (parent) parent.children.push(state); 2218 parent: function(state) { 2219 if (isDefined(state.parent) && state.parent) return findState(state.parent); 2220 // regex matches any valid composite state name 2221 // would match "contact.list" but not "contacts" 2222 var compositeName = /^(.+)\.[^.]+$/.exec(state.name); 2223 return compositeName ? findState(compositeName[1]) : root; 2224 }, 2225 2226 // inherit 'data' from parent and override by own values (if any) 2227 data: function(state) { 2228 if (state.parent && state.parent.data) { 2229 state.data = state.self.data = inherit(state.parent.data, state.data); 2230 } 2231 return state.data; 2232 }, 2233 2234 // Build a URLMatcher if necessary, either via a relative or absolute URL 2235 url: function(state) { 2236 var url = state.url, config = { params: state.params || {} }; 2237 2238 if (isString(url)) { 2239 if (url.charAt(0) == '^') return $urlMatcherFactory.compile(url.substring(1), config); 2240 return (state.parent.navigable || root).url.concat(url, config); 2241 } 2242 2243 if (!url || $urlMatcherFactory.isMatcher(url)) return url; 2244 throw new Error("Invalid url '" + url + "' in state '" + state + "'"); 2245 }, 2246 2247 // Keep track of the closest ancestor state that has a URL (i.e. is navigable) 2248 navigable: function(state) { 2249 return state.url ? state : (state.parent ? state.parent.navigable : null); 2250 }, 2251 2252 // Own parameters for this state. state.url.params is already built at this point. Create and add non-url params 2253 ownParams: function(state) { 2254 var params = state.url && state.url.params || new $$UMFP.ParamSet(); 2255 forEach(state.params || {}, function(config, id) { 2256 if (!params[id]) params[id] = new $$UMFP.Param(id, null, config, "config"); 2257 }); 2258 return params; 2259 }, 2260 2261 // Derive parameters for this state and ensure they're a super-set of parent's parameters 2262 params: function(state) { 2263 var ownParams = pick(state.ownParams, state.ownParams.$$keys()); 2264 return state.parent && state.parent.params ? extend(state.parent.params.$$new(), ownParams) : new $$UMFP.ParamSet(); 2265 }, 2266 2267 // If there is no explicit multi-view configuration, make one up so we don't have 2268 // to handle both cases in the view directive later. Note that having an explicit 2269 // 'views' property will mean the default unnamed view properties are ignored. This 2270 // is also a good time to resolve view names to absolute names, so everything is a 2271 // straight lookup at link time. 2272 views: function(state) { 2273 var views = {}; 2274 2275 forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) { 2276 if (name.indexOf('@') < 0) name += '@' + state.parent.name; 2277 views[name] = view; 2278 }); 2279 return views; 2280 }, 2281 2282 // Keep a full path from the root down to this state as this is needed for state activation. 2283 path: function(state) { 2284 return state.parent ? state.parent.path.concat(state) : []; // exclude root from path 2285 }, 2286 2287 // Speed up $state.contains() as it's used a lot 2288 includes: function(state) { 2289 var includes = state.parent ? extend({}, state.parent.includes) : {}; 2290 includes[state.name] = true; 2291 return includes; 2292 }, 2293 2294 $delegates: {} 2295 }; 2296 2297 function isRelative(stateName) { 2298 return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0; 2299 } 2300 2301 function findState(stateOrName, base) { 2302 if (!stateOrName) return undefined; 2303 2304 var isStr = isString(stateOrName), 2305 name = isStr ? stateOrName : stateOrName.name, 2306 path = isRelative(name); 2307 2308 if (path) { 2309 if (!base) throw new Error("No reference point given for path '" + name + "'"); 2310 base = findState(base); 2311 2312 var rel = name.split("."), i = 0, pathLength = rel.length, current = base; 2313 2314 for (; i < pathLength; i++) { 2315 if (rel[i] === "" && i === 0) { 2316 current = base; 2317 continue; 2318 } 2319 if (rel[i] === "^") { 2320 if (!current.parent) throw new Error("Path '" + name + "' not valid for state '" + base.name + "'"); 2321 current = current.parent; 2322 continue; 2323 } 2324 break; 2325 } 2326 rel = rel.slice(i).join("."); 2327 name = current.name + (current.name && rel ? "." : "") + rel; 2328 } 2329 var state = states[name]; 2330 2331 if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) { 2332 return state; 2333 } 2334 return undefined; 2335 } 2336 2337 function queueState(parentName, state) { 2338 if (!queue[parentName]) { 2339 queue[parentName] = []; 2340 } 2341 queue[parentName].push(state); 2342 } 2343 2344 function flushQueuedChildren(parentName) { 2345 var queued = queue[parentName] || []; 2346 while(queued.length) { 2347 registerState(queued.shift()); 2348 } 2349 } 2350 2351 function registerState(state) { 2352 // Wrap a new object around the state so we can store our private details easily. 2353 state = inherit(state, { 2354 self: state, 2355 resolve: state.resolve || {}, 2356 toString: function() { return this.name; } 2357 }); 2358 2359 var name = state.name; 2360 if (!isString(name) || name.indexOf('@') >= 0) throw new Error("State must have a valid name"); 2361 if (states.hasOwnProperty(name)) throw new Error("State '" + n
2361ame + "' is already defined"); 2362 2363 // Get parent name 2364 var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.')) 2365 : (isString(state.parent)) ? state.parent 2366 : (isObject(state.parent) && isString(state.parent.name)) ? state.parent.name 2367 : ''; 2368 2369 // If parent is not registered yet, add state to queue and register later 2370 if (parentName && !states[parentName]) { 2371 return queueState(parentName, state.self); 2372 } 2373 2374 for (var key in stateBuilder) { 2375 if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]); 2376 } 2377 states[name] = state; 2378 2379 // Register the state in the global state list and with $urlRouter if necessary. 2380 if (!state[abstractKey] && state.url) { 2381 $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) { 2382 if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) { 2383 $state.transitionTo(state, $match, { inherit: true, location: false }); 2384 } 2385 }]); 2386 } 2387 2388 // Register any queued children 2389 flushQueuedChildren(name); 2390 2391 return state; 2392 } 2393 2394 // Checks text to see if it looks like a glob. 2395 function isGlob (text) { 2396 return text.indexOf('*') > -1; 2397 } 2398 2399 // Returns true if glob matches current $state name. 2400 function doesStateMatchGlob (glob) { 2401 var globSegments = glob.split('.'), 2402 segments = $state.$current.name.split('.'); 2403 2404 //match single stars 2405 for (var i = 0, l = globSegments.length; i < l; i++) { 2406 if (globSegments[i] === '*') { 2407 segments[i] = '*'; 2408 } 2409 } 2410 2411 //match greedy starts 2412 if (globSegments[0] === '**') { 2413 segments = segments.slice(indexOf(segments, globSegments[1])); 2414 segments.unshift('**'); 2415 } 2416 //match greedy ends 2417 if (globSegments[globSegments.length - 1] === '**') { 2418 segments.splice(indexOf(segments, globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE); 2419 segments.push('**'); 2420 } 2421 2422 if (globSegments.length != segments.length) { 2423 return false; 2424 } 2425 2426 return segments.join('') === globSegments.join(''); 2427 } 2428 2429 2430 // Implicit root state that is always active 2431 root = registerState({ 2432 name: '', 2433 url: '^', 2434 views: null, 2435 'abstract': true 2436 }); 2437 root.navigable = null; 2438 2439 2440 /** 2441 * @ngdoc function 2442 * @name ui.router.state.$stateProvider#decorator 2443 * @methodOf ui.router.state.$stateProvider 2444 * 2445 * @description 2446 * Allows you to extend (carefully) or override (at your own peril) the 2447 * `stateBuilder` object used internally by `$stateProvider`. This can be used 2448 * to add custom functionality to ui-router, for example inferring templateUrl 2449 * based on the state name. 2450 * 2451 * When passing only a name, it returns the current (original or decorated) builder 2452 * function that matches `name`. 2453 * 2454 * The builder functions that can be decorated are listed below. Though not all 2455 * necessarily have a good use case for decoration, that is up to you to decide. 2456 * 2457 * In addition, users can attach custom decorators, which will generate new 2458 * properties within the state's internal definition. There is currently no clear 2459 * use-case for this beyond accessing internal states (i.e. $state.$current), 2460 * however, expect this to become increasingly relevant as we introduce additional 2461 * meta-programming features. 2462 * 2463 * **Warning**: Decorators should not be interdependent because the order of 2464 * execution of the builder functions in non-deterministic. Builder functions 2465 * should only be dependent on the state definition object and super function. 2466 * 2467 * 2468 * Existing builder functions and current return values: 2469 * 2470 * - **parent** `{object}` - returns the parent state object. 2471 * - **data** `{object}` - returns state data, including any inherited data that is not 2472 * overridden by own values (if any). 2473 * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher} 2474 * or `null`. 2475 * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is 2476 * navigable). 2477 * - **params** `{object}` - returns an array of state params that are ensured to 2478 * be a super-set of parent's params. 2479 * - **views** `{object}` - returns a views object where each key is an absolute view 2480 * name (i.e. "viewName@stateName") and each value is the config object 2481 * (template, controller) for the view. Even when you don't use the views object 2482 * explicitly on a state config, one is still created for you internally. 2483 * So by decorating this builder function you have access to decorating template 2484 * and controller properties. 2485 * - **ownParams** `{object}` - returns an array of params that belong to the state, 2486 * not including any params defined by ancestor states. 2487 * - **path** `{string}` - returns the full path from the root down to this state. 2488 * Needed for state activation. 2489 * - **includes** `{object}` - returns an object that includes every state that 2490 * would pass a `$state.includes()` test. 2491 * 2492 * @example 2493 * <pre> 2494 * // Override the internal 'views' builder with a function that takes the state 2495 * // definition, and a reference to the internal function being overridden: 2496 * $stateProvider.decorator('views', function (state, parent) { 2497 * var result = {}, 2498 * views = parent(state); 2499 * 2500 * angular.forEach(views, function (config, name) { 2501 * var autoName = (state.name + '.' + name).replace('.', '/'); 2502 * config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html'; 2503 * result[name] = config; 2504 * }); 2505 * return result; 2506 * }); 2507 * 2508 * $stateProvider.state('home', { 2509 * views: { 2510 * 'contact.list': { controller: 'ListController' },
2511 * 'contact.item': { controller: 'ItemController' } 2512 * } 2513 * }); 2514 * 2515 * // ... 2516 * 2517 * $state.go('home'); 2518 * // Auto-populates list and item views with /partials/home/contact/list.html, 2519 * // and /partials/home/contact/item.html, respectively. 2520 * </pre> 2521 * 2522 * @param {string} name The name of the builder function to decorate. 2523 * @param {object} func A function that is responsible for decorating the original 2524 * builder function. The function receives two parameters: 2525 * 2526 * - `{object}` - state - The state config object. 2527 * - `{object}` - super - The original builder function. 2528 * 2529 * @return {object} $stateProvider - $stateProvider instance 2530 */ 2531 this.decorator = decorator; 2532 function decorator(name, func) { 2533 /*jshint validthis: true */ 2534 if (isString(name) && !isDefined(func)) { 2535 return stateBuilder[name]; 2536 } 2537 if (!isFunction(func) || !isString(name)) { 2538 return this; 2539 } 2540 if (stateBuilder[name] && !stateBuilder.$delegates[name]) { 2541 stateBuilder.$delegates[name] = stateBuilder[name]; 2542 } 2543 stateBuilder[name] = func; 2544 return this; 2545 } 2546 2547 /** 2548 * @ngdoc function 2549 * @name ui.router.state.$stateProvider#state 2550 * @methodOf ui.router.state.$stateProvider 2551 * 2552 * @description 2553 * Registers a state configuration under a given state name. The stateConfig object 2554 * has the following acceptable properties. 2555 * 2556 * @param {string} name A unique state name, e.g. "home", "about", "contacts". 2557 * To create a parent/child state use a dot, e.g. "about.sales", "home.newest". 2558 * @param {object} stateConfig State configuration object. 2559 * @param {string|function=} stateConfig.template 2560 * <a id='template'></a> 2561 * html template as a string or a function that returns 2562 * an html template as a string which should be used by the uiView directives. This property 2563 * takes precedence over templateUrl. 2564 * 2565 * If `template` is a function, it will be called with the following parameters: 2566 * 2567 * - {array.<object>} - state parameters extracted from the current $location.path() by 2568 * applying the current state 2569 * 2570 * <pre>template: 2571 * "<h1>inline template definition</h1>" + 2572 * "<div ui-view></div>"</pre> 2573 * <pre>template: function(params) { 2574 * return "<h1>generated template</h1>"; }</pre> 2575 * </div> 2576 * 2577 * @param {string|function=} stateConfig.templateUrl 2578 * <a id='templateUrl'></a> 2579 * 2580 * path or function that returns a path to an html 2581 * template that should be used by uiView. 2582 * 2583 * If `templateUrl` is a function, it will be called with the following parameters: 2584 * 2585 * - {array.<object>} - state parameters extracted from the current $location.path() by 2586 * applying the current state 2587 * 2588 * <pre>templateUrl: "home.html"</pre> 2589 * <pre>templateUrl: function(params) { 2590 * return myTemplates[params.pageId]; }</pre> 2591 * 2592 * @param {function=} stateConfig.templateProvider 2593 * <a id='templateProvider'></a> 2594 * Provider function that returns HTML content string. 2595 * <pre> templateProvider: 2596 * function(MyTemplateService, params) { 2597 * return MyTemplateService.getTemplate(params.pageId); 2598 * }</pre> 2599 * 2600 * @param {string|function=} stateConfig.controller 2601 * <a id='controller'></a> 2602 * 2603 * Controller fn that should be associated with newly 2604 * related scope or the name of a registered controller if passed as a string. 2605 * Optionally, the ControllerAs may be declared here. 2606 * <pre>controller: "MyRegisteredController"</pre> 2607 * <pre>controller: 2608 * "MyRegisteredController as fooCtrl"}</pre> 2609 * <pre>controller: function($scope, MyService) { 2610 * $scope.data = MyService.getData(); }</pre> 2611 * 2612 * @param {function=} stateConfig.controllerProvider 2613 * <a id='controllerProvider'></a> 2614 * 2615 * Injectable provider function that returns the actual controller or string. 2616 * <pre>controllerProvider: 2617 * function(MyResolveData) { 2618 * if (MyResolveData.foo) 2619 * return "FooCtrl" 2620 * else if (MyResolveData.bar) 2621 * return "BarCtrl"; 2622 * else return function($scope) { 2623 * $scope.baz = "Qux"; 2624 * } 2625 * }</pre> 2626 * 2627 * @param {string=} stateConfig.controllerAs 2628 * <a id='controllerAs'></a> 2629 * 2630 * A controller alias name. If present the controller will be 2631 * published to scope under the controllerAs name. 2632 * <pre>controllerAs: "myCtrl"</pre> 2633 * 2634 * @param {string|object=} stateConfig.parent 2635 * <a id='parent'></a> 2636 * Optionally specifies the parent state of this state. 2637 * 2638 * <pre>parent: 'parentState'</pre> 2639 * <pre>parent: parentState // JS variable</pre> 2640 * 2641 * @param {object=} stateConfig.resolve 2642 * <a id='resolve'></a> 2643 * 2644 * An optional map<string, function> of dependencies which 2645 * should be injected into the controller. If any of these dependencies are promises, 2646 * the router will wait for them all to be resolved before the controller is instantiated. 2647 * If all the promises are resolved successfully, the $stateChangeSuccess event is fired 2648 * and the values of the resolved promises are injected into any controllers that reference them. 2649 * If any of the promises are rejected the $stateChangeError event is fired. 2650 * 2651 * The map object is: 2652 * 2653 * - key - {string}: name of dependency to be injected into controller 2654 * - factory - {string|function}: If string then it is alias for service. Otherwise if function, 2655 * it is injected and return value it treated as dependency. If result is a promise, it is 2656 * resolved before its value is injected into controller. 2657 * 2658 * <pre>resolve: { 2659 * myResolve1: 2660 * function($http, $stateParams) { 2661 * return $http.get("/api/foos/"+stateParams.fooID); 2662 * } 2663 * }</pre> 2664 * 2665 * @param {string=} stateConfig.url 2666 * <a id='url'></a> 2667 * 2668 * A url fragment with optional parameters. When a state is navigated or 2669 * transitioned to, the `$stateParams` service will be populated with any 2670 * parameters that were passed. 2671 * 2672 * (See {@link ui.router.util.type:UrlMatcher UrlMatcher} `UrlMatcher`} for 2673 * more details on acceptable patterns ) 2674 * 2675 * examples: 2676 * <pre>url: "/home" 2677 * url: "/users/:userid" 2678 * url: "/books/{bookid:[a-zA-Z_-]}" 2679 * url: "/books/{categoryid:int}" 2680 * url: "/books/{publishername:string}/{categoryid:int}" 2681 * url: "/messages?before&after" 2682 * url: "/messages?{before:date}&{after:date}" 2683 * url: "/messages/:mailboxid?{before:date}&{after:date}" 2684 * </pre> 2685 * 2686 * @param {object=} stateConfig.views 2687 * <a id='views'></a> 2688 * an optional map<string, object> which defined multiple views, or targets views 2689 * manually/explicitly. 2690 * 2691 * Examples: 2692 * 2693 * Targets three named `ui-view`s in the parent state's template 2694 * <pre>views: { 2695 * header: { 2696 * controller: "headerCtrl", 2697 * templateUrl: "header.html" 2698 * }, body: { 2699 * controller: "bodyCtrl", 2700 * templateUrl: "body.html" 2701 * }, footer: { 2702 * controller: "footCtrl", 2703 * templateUrl: "footer.html" 2704 * } 2705 * }</pre> 2706 * 2707 * Targets named `ui-view="header"` from grandparent state 'top''s template, and named `ui-view="body" from parent state's template. 2708 * <pre>views: { 2709 * 'header@top': { 2710 * controller: "msgHeaderCtrl", 2711 * templateUrl: "msgHeader.html" 2712 * }, 'body': { 2713 * controller: "messagesCtrl", 2714 * templateUrl: "messages.html" 2715 * } 2716 * }</pre> 2717 * 2718 * @param {boolean=} [stateConfig.abstract=false] 2719 * <a id='abstract'></a> 2720 * An abstract state will never be directly activated, 2721 * but can provide inherited properties to its common children states. 2722 * <pre>abstract: true</pre> 2723 * 2724 * @param {function=} stateConfig.onEnter 2725 * <a id='onEnter'></a> 2726 * 2727 * Callback function for when a state is entered. Good way 2728 * to trigger an action or dispatch an event, such as opening a dialog. 2729 * If minifying your scripts, make sure to explicitly annotate this function, 2730 * because it won't be automatically annotated by your build tools. 2731 * 2732 * <pre>onEnter: function(MyService, $stateParams) { 2733 * MyService.foo($stateParams.myParam); 2734 * }</pre> 2735 * 2736 * @param {function=} stateConfig.onExit 2737 * <a id='onExit'></a> 2738 * 2739 * Callback function for when a state is exited. Good way to 2740 * trigger an action or dispatch an event, such as opening a dialog. 2741 * If minifying your scripts, make sure to explicitly annotate this function, 2742 * because it won't be automatically annotated by your build tools. 2743 * 2744 * <pre>onExit: function(MyService, $stateParams) { 2745 * MyService.cleanup($stateParams.myParam); 2746 * }</pre> 2747 * 2748 * @param {boolean=} [stateConfig.reloadOnSearch=true] 2749 * <a id='reloadOnSearch'></a> 2750 * 2751 * If `false`, will not retrigger the same state 2752 * just because a search/query parameter has changed (via $location.search() or $location.hash()). 2753 * Useful for when you'd like to modify $location.search() without triggering a reload. 2754 * <pre>reloadOnSearch: false</pre> 2755 * 2756 * @param {object=} stateConfig.data 2757 * <a id='data'></a> 2758 * 2759 * Arbitrary data object, useful for custom configuration. The parent state's `data` is 2760 * prototypally inherited. In other words, adding a data property to a
2760state adds it to 2761 * the entire subtree via prototypal inheritance. 2762 * 2763 * <pre>data: { 2764 * requiredRole: 'foo' 2765 * } </pre> 2766 * 2767 * @param {object=} stateConfig.params 2768 * <a id='params'></a> 2769 * 2770 * A map which optionally configures parameters declared in the `url`, or 2771 * defines additional non-url parameters. For each parameter being 2772 * configured, add a configuration object keyed to the name of the parameter. 2773 * 2774 * Each parameter configuration object may contain the following properties: 2775 * 2776 * - ** value ** - {object|function=}: specifies the default value for this 2777 * parameter. This implicitly sets this parameter as optional. 2778 * 2779 * When UI-Router routes to a state and no value is 2780 * specified for this parameter in the URL or transition, the 2781 * default value will be used instead. If `value` is a function, 2782 * it will be injected and invoked, and the return value used. 2783 * 2784 * *Note*: `undefined` is treated as "no default value" while `null` 2785 * is treated as "the default value is `null`". 2786 * 2787 * *Shorthand*: If you only need to configure the default value of the 2788 * parameter, you may use a shorthand syntax. In the **`params`** 2789 * map, instead mapping the param name to a full parameter configuration 2790 * object, simply set map it to the default parameter value, e.g.: 2791 * 2792 * <pre>// define a parameter's default value 2793 * params: { 2794 * param1: { value: "defaultValue" } 2795 * } 2796 * // shorthand default values 2797 * params: { 2798 * param1: "defaultValue", 2799 * param2: "param2Default" 2800 * }</pre> 2801 * 2802 * - ** array ** - {boolean=}: *(default: false)* If true, the param value will be 2803 * treated as an array of values. If you specified a Type, the value will be 2804 * treated as an array of the specified Type. Note: query parameter values 2805 * default to a special `"auto"` mode. 2806 * 2807 * For query parameters in `"auto"` mode, if multiple values for a single parameter 2808 * are present in the URL (e.g.: `/foo?bar=1&bar=2&bar=3`) then the values 2809 * are mapped to an array (e.g.: `{ foo: [ '1', '2', '3' ] }`). However, if 2810 * only one value is present (e.g.: `/foo?bar=1`) then the value is treated as single 2811 * value (e.g.: `{ foo: '1' }`). 2812 * 2813 * <pre>params: { 2814 * param1: { array: true } 2815 * }</pre> 2816 * 2817 * - ** squash ** - {bool|string=}: `squash` configures how a default parameter value is represented in the URL when 2818 * the current parameter value is the same as the default value. If `squash` is not set, it uses the 2819 * configured default squash policy. 2820 * (See {@link ui.router.util.$urlMatcherFactory#methods_defaultSquashPolicy `defaultSquashPolicy()`}) 2821 * 2822 * There are three squash settings: 2823 * 2824 * - false: The parameter's default value is not squashed. It is encoded and included in the URL 2825 * - true: The parameter's default value is omitted from the URL. If the parameter is preceeded and followed 2826 * by slashes in the state's `url` declaration, then one of those slashes are omitted. 2827 * This can allow for cleaner looking URLs. 2828 * - `"<arbitrary string>"`: The parameter's default value is replaced with an arbitrary placeholder of your choice. 2829 * 2830 * <pre>params: { 2831 * param1: { 2832 * value: "defaultId", 2833 * squash: true 2834 * } } 2835 * // squash "defaultValue" to "~" 2836 * params: { 2837 * param1: { 2838 * value: "defaultValue", 2839 * squash: "~" 2840 * } } 2841 * </pre> 2842 * 2843 * 2844 * @example 2845 * <pre> 2846 * // Some state name examples 2847 * 2848 * // stateName can be a single top-level name (must be unique). 2849 * $stateProvider.state("home", {}); 2850 * 2851 * // Or it can be a nested state name. This state is a child of the 2852 * // above "home" state. 2853 * $stateProvider.state("home.newest", {}); 2854 * 2855 * // Nest states as deeply as needed. 2856 * $stateProvider.state("home.newest.abc.xyz.inception", {}); 2857 * 2858 * // state() returns $stateProvider, so you can chain state declarations. 2859 * $stateProvider 2860 * .state("home", {}) 2861 * .state("about", {}) 2862 * .state("contacts", {}); 2863 * </pre> 2864 * 2865 */ 2866 this.state = state; 2867 function state(name, definition) { 2868 /*jshint validthis: true */ 2869 if (isObject(name)) definition = name; 2870 else definition.name = name; 2871 registerState(definition); 2872 return this; 2873 } 2874 2875 /** 2876 * @ngdoc object 2877 * @name ui.router.state.$state 2878 * 2879 * @requires $rootScope 2880 * @requires $q 2881 * @requires ui.router.state.$view 2882 * @requires $injector 2883 * @requires ui.router.util.$resolve 2884 * @requires ui.router.state.$stateParams 2885 * @requires ui.router.router.$urlRouter 2886 * 2887 * @property {object} params A param object, e.g. {sectionId: section.id)}, that 2888 * you'd like to test against the current active state. 2889 * @property {object} current A reference to the state's config object. However 2890 * you passed it in. Useful for accessing custom data. 2891 * @property {object} transition Currently pending transition. A promise that'll 2892 * resolve or reject. 2893 * 2894 * @description 2895 * `$state` service is responsible for representing states as well as transitioning 2896 * between them. It also provides interfaces to ask for current state or even states 2897 * you're coming from. 2898 */ 2899 this.$get = $get; 2900 $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter', '$location', '$urlMatcherFactory']; 2901 function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter, $location, $urlMatcherFactory) { 2902 2903 var TransitionSuperseded = $q.reject(new Error('transition superseded')); 2904 var TransitionPrevented = $q.reject(new Error('transition prevented')); 2905 var TransitionAborted = $q.reject(new Error('transition aborted')); 2906 var TransitionFailed = $q.reject(new Error('transition failed')); 2907 2908 // Handles the case where a state which is the target of a transition is not found, and the user 2909 // can optionally retry or defer the transition 2910 function handleRedirect(redirect, state, params, options) { 2911 /** 2912 * @ngdoc event 2913 * @name ui.router.state.$state#$stateNotFound 2914 * @eventOf ui.router.state.$state 2915 * @eventType broadcast on root scope
2916 * @description 2917 * Fired when a requested state **cannot be found** using the provided state name during transition. 2918 * The event is broadcast allowing any handlers a single chance to deal with the error (usually by 2919 * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler, 2920 * you can see its three properties in the example. You can use `event.preventDefault()` to abort the 2921 * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value. 2922 * 2923 * @param {Object} event Event object. 2924 * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties. 2925 * @param {State} fromState Current state object. 2926 * @param {Object} fromParams Current state params. 2927 * 2928 * @example 2929 * 2930 * <pre> 2931 * // somewhere, assume lazy.state has not been defined 2932 * $state.go("lazy.state", {a:1, b:2}, {inherit:false}); 2933 * 2934 * // somewhere else 2935 * $scope.$on('$stateNotFound', 2936 * function(event, unfoundState, fromState, fromParams){ 2937 * console.log(unfoundState.to); // "lazy.state" 2938 * console.log(unfoundState.toParams); // {a:1, b:2} 2939 * console.log(unfoundState.options); // {inherit:false} + default options 2940 * }) 2941 * </pre> 2942 */ 2943 var evt = $rootScope.$broadcast('$stateNotFound', redirect, state, params); 2944 2945 if (evt.defaultPrevented) { 2946 $urlRouter.update(); 2947 return TransitionAborted; 2948 } 2949 2950 if (!evt.retry) { 2951 return null; 2952 } 2953 2954 // Allow the handler to return a promise to defer state lookup retry 2955 if (options.$retry) { 2956 $urlRouter.update(); 2957 return TransitionFailed; 2958 } 2959 var retryTransition = $state.transition = $q.when(evt.retry); 2960 2961 retryTransition.then(function() { 2962 if (retryTransition !== $state.transition) return TransitionSuperseded; 2963 redirect.options.$retry = true; 2964 return $state.transitionTo(redirect.to, redirect.toParams, redirect.options); 2965 }, function() { 2966 return TransitionAborted; 2967 }); 2968 $urlRouter.update(); 2969 2970 return retryTransition; 2971 } 2972 2973 root.locals = { resolve: null, globals: { $stateParams: {} } }; 2974 2975 $state = { 2976 params: {}, 2977 current: root.self, 2978 $current: root, 2979 transition: null 2980 }; 2981 2982 /** 2983 * @ngdoc function 2984 * @name ui.router.state.$state#reload 2985 * @methodOf ui.router.state.$state 2986 * 2987 * @description 2988 * A method that force reloads the current state. All resolves are re-resolved, 2989 * controllers reinstantiated, and events re-fired. 2990 * 2991 * @example 2992 * <pre> 2993 * var app angular.module('app', ['ui.router']); 2994 * 2995 * app.controller('ctrl', function ($scope, $state) { 2996 * $scope.reload = function(){ 2997 * $state.reload(); 2998 * } 2999 * }); 3000 * </pre> 3001 * 3002 * `reload()` is just an alias for: 3003 * <pre> 3004 * $state.transitionTo($state.current, $stateParams, { 3005 * reload: true, inherit: false, notify: true 3006 * }); 3007 * </pre> 3008 * 3009 * @param {string=|object=} state - A state name or a state object, which is the root of the resolves to be re-resolved. 3010 * @example 3011 * <pre> 3012 * //assuming app application consists of 3 states: 'contacts', 'contacts.detail', 'contacts.detail.item' 3013 * //and current state is 'contacts.detail.item' 3014 * var app angular.module('app', ['ui.router']); 3015 * 3016 * app.controller('ctrl', function ($scope, $state) { 3017 * $scope.reload = function(){ 3018 * //will reload 'contact.detail' and 'contact.detail.item' states 3019 * $state.reload('contact.detail'); 3020 * } 3021 * }); 3022 * </pre> 3023 * 3024 * `reload()` is just an alias for: 3025 * <pre> 3026 * $state.transitionTo($state.current, $stateParams, { 3027 * reload: true, inherit: false, notify: true 3028 * }); 3029 * </pre> 3030 3031 * @returns {promise} A promise representing the state of the new transition. See 3032 * {@link ui.router.state.$state#methods_go $state.go}. 3033 */ 3034 $state.reload = function reload(state) { 3035 return $state.transitionTo($state.current, $stateParams, { reload: state || true, inherit: false, notify: true}); 3036 }; 3037 3038 /** 3039 * @ngdoc function 3040 * @name ui.router.state.$state#go 3041 * @methodOf ui.router.state.$state 3042 *
3043 * @description 3044 * Convenience method for transitioning to a new state. `$state.go` calls 3045 * `$state.transitionTo` internally but automatically sets options to 3046 * `{ location: true, inherit: true, relative: $state.$current, notify: true }`. 3047 * This allows you to easily use an absolute or relative to path and specify 3048 * only the parameters you'd like to update (while letting unspecified parameters 3049 * inherit from the currently active ancestor states). 3050 * 3051 * @example 3052 * <pre> 3053 * var app = angular.module('app', ['ui.router']); 3054 * 3055 * app.controller('ctrl', function ($scope, $state) { 3056 * $scope.changeState = function () { 3057 * $state.go('contact.detail'); 3058 * }; 3059 * }); 3060 * </pre> 3061 * <img src='../ngdoc_assets/StateGoExamples.png'/> 3062 * 3063 * @param {string} to Absolute state name or relative state path. Some examples: 3064 * 3065 * - `$state.go('contact.detail')` - will go to the `contact.detail` state 3066 * - `$state.go('^')` - will go to a parent state 3067 * - `$state.go('^.sibling')` - will go to a sibling state 3068 * - `$state.go('.child.grandchild')` - will go to grandchild state 3069 * 3070 * @param {object=} params A map of the parameters that will be sent to the state, 3071 * will populate $stateParams. Any parameters that are not specified will be inherited from currently 3072 * defined parameters. Only parameters specified in the state definition can be overridden, new 3073 * parameters will be ignored. This allows, for example, going to a sibling state that shares parameters 3074 * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e. 3075 * transitioning to a sibling will get you the parameters for all parents, transitioning to a child 3076 * will get you all current parameters, etc. 3077 * @param {object=} options Options object. The options are: 3078 * 3079 * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false` 3080 * will not. If string, must be `"replace"`, which will update url and also replace last history record. 3081 * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url. 3082 * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'), 3083 * defines which state to be relative from. 3084 * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events. 3085 * - **`reload`** (v0.2.5) - {boolean=false|string|object}, If `true` will force transition even if no state or params 3086 * have changed. It will reload the resolves and views of the current state and parent states. 3087 * If `reload` is a string (or state object), the state object is fetched (by name, or object reference); and \ 3088 * the transition reloads the resolves and views for that matched state, and all its children states. 3089 * 3090 * @returns {promise} A promise representing the state of the new transition. 3091 * 3092 * Possible success values: 3093 * 3094 * - $state.current 3095 * 3096 * <br/>Possible rejection values: 3097 * 3098 * - 'transition superseded' - when a newer transition has been started after this one 3099 * - 'transition prevented' - when `event.preventDefault()` has been called in a `$stateChangeStart` listener 3100 * - 'transition aborted' - when `event.preventDefault()` has been called in a `$stateNotFound` listener or 3101 * when a `$stateNotFound` `event.retry` promise errors. 3102 * - 'transition failed' - when a state has been unsuccessfully found after 2 tries. 3103 * - *resolve error* - when an error has occurred with a `resolve` 3104 * 3105 */ 3106 $state.go = function go(to, params, options) { 3107 return $state.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options)); 3108 }; 3109 3110 /** 3111 * @ngdoc function 3112 * @name ui.router.state.$state#transitionTo 3113 * @methodOf ui.router.state.$state 3114 * 3115 * @description 3116 * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go} 3117 * uses `transitionTo` internally. `$state.go` is recommended in most situations. 3118 * 3119 * @example 3120 * <pre> 3121 * var app = angular.module('app', ['ui.router']); 3122 * 3123 * app.controller('ctrl', function ($scope, $state) { 3124 * $scope.changeState = function () { 3125 * $state.transitionTo('contact.detail'); 3126 * }; 3127 * }); 3128 * </pre> 3129 * 3130 * @param {string} to State name. 3131 * @param {object=} toParams A map of the parameters that will be sent to the state, 3132 * will populate $stateParams. 3133 * @param {object=} options Options object. The options are: 3134 * 3135 * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false` 3136 * will not. If string, must be `"replace"`, which will update url and also replace last history record. 3137 * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url. 3138 * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'), 3139 * defines which state to be relative from. 3140 * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events. 3141 * - **`reload`** (v0.2.5) - {boolean=false|string=|object=}, If `true` will force transition even if the state or params 3142 * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd 3143 * use this when you want to force a reload when *everything* is the same, including search params. 3144 * if String, then will reload the state with the name given in reload, and any children. 3145 * if Object, then a stateObj is expected, will reload the state found in stateObj, and any children. 3146 * 3147 * @returns {promise} A promise representing the state of the new transition. See 3148 * {@link ui.router.state.$state#methods_go $state.go}. 3149 */
3150 $state.transitionTo = function transitionTo(to, toParams, options) { 3151 toParams = toParams || {}; 3152 options = extend({ 3153 location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false 3154 }, options || {}); 3155 3156 var from = $state.$current, fromParams = $state.params, fromPath = from.path; 3157 var evt, toState = findState(to, options.relative); 3158 3159 // Store the hash param for later (since it will be stripped out by various methods) 3160 var hash = toParams['#']; 3161 3162 if (!isDefined(toState)) { 3163 var redirect = { to: to, toParams: toParams, options: options }; 3164 var redirectResult = handleRedirect(redirect, from.self, fromParams, options); 3165 3166 if (redirectResult) { 3167 return redirectResult; 3168 } 3169 3170 // Always retry once if the $stateNotFound was not prevented 3171 // (handles either redirect changed or state lazy-definition) 3172 to = redirect.to; 3173 toParams = redirect.toParams; 3174 options = redirect.options; 3175 toState = findState(to, options.relative); 3176 3177 if (!isDefined(toState)) { 3178 if (!options.relative) throw new Error("No such state '" + to + "'"); 3179 throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'"); 3180 } 3181 } 3182 if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'"); 3183 if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState); 3184 if (!toState.params.$$validates(toParams)) return TransitionFailed; 3185 3186 toParams = toState.params.$$values(toParams); 3187 to = toState; 3188 3189 var toPath = to.path; 3190 3191 // Starting from the root of the path, keep all levels that haven't changed 3192 var keep = 0, state = toPath[keep], locals = root.locals, toLocals = []; 3193 3194 if (!options.reload) { 3195 while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) { 3196 locals = toLocals[keep] = state.locals; 3197 keep++; 3198 state = toPath[keep]; 3199 } 3200 } else if (isString(options.reload) || isObject(options.reload)) { 3201 if (isObject(options.reload) && !options.reload.name) { 3202 throw new Error('Invalid reload state object'); 3203 } 3204 3205 var reloadState = options.reload === true ? fromPath[0] : findState(options.reload); 3206 if (options.reload && !reloadState) { 3207 throw new Error("No such reload state '" + (isString(options.reload) ? options.reload : options.reload.name) + "'"); 3208 } 3209 3210 while (state && state === fromPath[keep] && state !== reloadState) { 3211 locals = toLocals[keep] = state.locals; 3212 keep++; 3213 state = toPath[keep]; 3214 } 3215 } 3216 3217 // If we're going to the same state and all locals are kept, we've got nothing to do. 3218 // But clear 'transition', as we still want to cancel any other pending transitions. 3219 // TODO: We may not want to bump 'transition' if we're called from a location change 3220 // that we've initiated ourselves, because we might accidentally abort a legitimate 3221 // transition initiated from code? 3222 if (shouldSkipReload(to, toParams, from, fromParams, locals, options)) { 3223 if (hash) toParams['#'] = hash; 3224 $state.params = toParams; 3225 copy($state.params, $stateParams); 3226 copy(filterByKeys(to.params.$$keys(), $stateParams), to.locals.globals.$stateParams); 3227 if (options.location && to.navigable && to.navigable.url) { 3228 $urlRouter.push(to.navigable.url, toParams, { 3229 $$avoidResync: true, replace: options.location === 'replace' 3230 }); 3231 $urlRouter.update(true); 3232 } 3233 $state.transition = null; 3234 return $q.when($state.current); 3235 } 3236 3237 // Filter parameters before we pass them to event handlers etc. 3238 toParams = filterByKeys(to.params.$$keys(), toParams || {}); 3239 3240 // Re-add the saved hash before we start returning things or broadcasting $stateChangeStart 3241 if (hash) toParams['#'] = hash; 3242 3243 // Broadcast start event and cancel the transition if requested 3244 if (options.notify) { 3245 /** 3246 * @ngdoc event 3247 * @name ui.router.state.$state#$stateChangeStart 3248 * @eventOf ui.router.state.$state 3249 * @eventType broadcast on root scope
3250 * @description 3251 * Fired when the state transition **begins**. You can use `event.preventDefault()` 3252 * to prevent the transition from happening and then the transition promise will be 3253 * rejected with a `'transition prevented'` value. 3254 * 3255 * @param {Object} event Event object. 3256 * @param {State} toState The state being transitioned to. 3257 * @param {Object} toParams The params supplied to the `toState`. 3258 * @param {State} fromState The current state, pre-transition. 3259 * @param {Object} fromParams The params supplied to the `fromState`. 3260 * 3261 * @example 3262 * 3263 * <pre> 3264 * $rootScope.$on('$stateChangeStart', 3265 * function(event, toState, toParams, fromState, fromParams){ 3266 * event.preventDefault(); 3267 * // transitionTo() promise will be rejected with 3268 * // a 'transition prevented' error 3269 * }) 3270 * </pre> 3271 */ 3272 if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams, options).defaultPrevented) { 3273 $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams); 3274 //Don't update and resync url if there's been a new transition started. see issue #2238, #600 3275 if ($state.transition == null) $urlRouter.update(); 3276 return TransitionPrevented; 3277 } 3278 } 3279 3280 // Resolve locals for the remaining states, but don't update any global state just 3281 // yet -- if anything fails to resolve the current state needs to remain untouched. 3282 // We also set up an inheritance chain for the locals here. This allows the view directive 3283 // to quickly look up the correct definition for each view in the current state. Even 3284 // though we create the locals object itself outside resolveState(), it is initially 3285 // empty and gets filled asynchronously. We need to keep track of the promise for the 3286 // (fully resolved) current locals, and pass this down the chain. 3287 var resolved = $q.when(locals); 3288 3289 for (var l = keep; l < toPath.length; l++, state = toPath[l]) { 3290 locals = toLocals[l] = inherit(locals); 3291 resolved = resolveState(state, toParams, state === to, resolved, locals, options); 3292 } 3293 3294 // Once everything is resolved, we are ready to perform the actual transition 3295 // and return a promise for the new state. We also keep track of what the 3296 // current promise is, so that we can detect overlapping transitions and 3297 // keep only the outcome of the last transition. 3298 var transition = $state.transition = resolved.then(function () { 3299 var l, entering, exiting; 3300 3301 if ($state.transition !== transition) return TransitionSuperseded; 3302 3303 // Exit 'from' states not kept 3304 for (l = fromPath.length - 1; l >= keep; l--) { 3305 exiting = fromPath[l]; 3306 if (exiting.self.onExit) { 3307 $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals); 3308 } 3309 exiting.locals = null; 3310 } 3311 3312 // Enter 'to' states not kept 3313 for (l = keep; l < toPath.length; l++) { 3314 entering = toPath[l]; 3315 entering.locals = toLocals[l]; 3316 if (entering.self.onEnter) { 3317 $injector.invoke(entering.self.onEnter, entering.self, entering.locals.globals); 3318 } 3319 } 3320 3321 // Run it again, to catch any transitions in callbacks 3322 if ($state.transition !== transition) return TransitionSuperseded; 3323 3324 // Update globals in $state 3325 $state.$current = to; 3326 $state.current = to.self; 3327 $state.params = toParams; 3328 copy($state.params, $stateParams); 3329 $state.transition = null; 3330 3331 if (options.location && to.navigable) { 3332 $urlRouter.push(to.navigable.url, to.navigable.locals.globals.$stateParams, { 3333 $$avoidResync: true, replace: options.location === 'replace' 3334 }); 3335 } 3336 3337 if (options.notify) { 3338 /** 3339 * @ngdoc event 3340 * @name ui.router.state.$state#$stateChangeSuccess 3341 * @eventOf ui.router.state.$state 3342 * @eventType broadcast on root scope
3343 * @description 3344 * Fired once the state transition is **complete**. 3345 * 3346 * @param {Object} event Event object. 3347 * @param {State} toState The state being transitioned to. 3348 * @param {Object} toParams The params supplied to the `toState`. 3349 * @param {State} fromState The current state, pre-transition. 3350 * @param {Object} fromParams The params supplied to the `fromState`. 3351 */ 3352 $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams); 3353 } 3354 $urlRouter.update(true); 3355 3356 return $state.current; 3357 }, function (error) { 3358 if ($state.transition !== transition) return TransitionSuperseded; 3359 3360 $state.transition = null; 3361 /** 3362 * @ngdoc event 3363 * @name ui.router.state.$state#$stateChangeError 3364 * @eventOf ui.router.state.$state 3365 * @eventType broadcast on root scope 3366 * @description 3367 * Fired when an **error occurs** during transition. It's important to note that if you 3368 * have any errors in your resolve functions (javascript errors, non-existent services, etc) 3369 * they will not throw traditionally. You must listen for this $stateChangeError event to 3370 * catch **ALL** errors. 3371 * 3372 * @param {Object} event Event object. 3373 * @param {State} toState The state being transitioned to. 3374 * @param {Object} toParams The params supplied to the `toState`. 3375 * @param {State} fromState The current state, pre-transition. 3376 * @param {Object} fromParams The params supplied to the `fromState`. 3377 * @param {Error} error The resolve error object. 3378 */ 3379 evt = $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error); 3380 3381 if (!evt.defaultPrevented) { 3382 $urlRouter.update(); 3383 } 3384 3385 return $q.reject(error); 3386 }); 3387 3388 return transition; 3389 }; 3390 3391 /** 3392 * @ngdoc function 3393 * @name ui.router.state.$state#is 3394 * @methodOf ui.router.state.$state 3395 * 3396 * @description 3397 * Similar to {@link ui.router.state.$state#methods_includes $state.includes}, 3398 * but only checks for the full state name. If params is supplied then it will be 3399 * tested for strict equality against the current active params object, so all params 3400 * must match with none missing and no extras. 3401 * 3402 * @example 3403 * <pre> 3404 * $state.$current.name = 'contacts.details.item'; 3405 * 3406 * // absolute name 3407 * $state.is('contact.details.item'); // returns true 3408 * $state.is(contactDetailItemStateObject); // returns true 3409 * 3410 * // relative name (. and ^), typically from a template 3411 * // E.g. from the 'contacts.details' template 3412 * <div ng-class="{highlighted: $state.is('.item')}">Item</div> 3413 * </pre> 3414 * 3415 * @param {string|object} stateOrName The state name (absolute or relative) or state object you'd like to check. 3416 * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like 3417 * to test against the current active state. 3418 * @param {object=} options An options object. The options are: 3419 * 3420 * - **`relative`** - {string|object} - If `stateOrName` is a relative state name and `options.relative` is set, .is will 3421 * test relative to `options.relative` state (or name). 3422 * 3423 * @returns {boolean} Returns true if it is the state. 3424 */ 3425 $state.is = function is(stateOrName, params, options) { 3426 options = extend({ relative: $state.$current }, options || {}); 3427 var state = findState(stateOrName, options.relative); 3428 3429 if (!isDefined(state)) { return undefined; } 3430 if ($state.$current !== state) { return false; } 3431 return params ? equalForKeys(state.params.$$values(params), $stateParams) : true; 3432 }; 3433 3434 /** 3435 * @ngdoc function 3436 * @name ui.router.state.$state#includes 3437 * @methodOf ui.router.state.$state 3438 * 3439 * @description 3440 * A method to determine if the current active state is equal to or is the child of the 3441 * state stateName. If any params are passed then they will be tested for a match as well. 3442 * Not all the parameters need to be passed, just the ones you'd like to test for equality. 3443 * 3444 * @example 3445 * Partial and relative names 3446 * <pre> 3447 * $state.$current.name = 'contacts.details.item'; 3448 * 3449 * // Using partial names 3450 * $state.includes("contacts"); // returns true 3451 * $state.includes("contacts.details"); // returns true 3452 * $state.includes("contacts.details.item"); // returns true 3453 * $state.includes("contacts.list"); // returns false 3454 * $state.includes("about"); // returns false 3455 * 3456 * // Using relative names (. and ^), typically from a template 3457 * // E.g. from the 'contacts.details' template 3458 * <div ng-class="{highlighted: $state.includes('.item')}">Item</div> 3459 * </pre> 3460 * 3461 * Basic globbing patterns 3462 * <pre> 3463 * $state.$current.name = 'contacts.details.item.url'; 3464 * 3465 * $state.includes("*.details.*.*"); // returns true 3466 * $state.includes("*.details.**"); // returns true 3467 * $state.includes("**.item.**"); // returns true 3468 * $state.includes("*.details.item.url"); // returns true 3469 * $state.includes("*.details.*.url"); // returns true 3470 * $state.includes("*.details.*"); // returns false 3471 * $state.includes("item.**"); // returns false 3472 * </pre> 3473 * 3474 * @param {string} stateOrName A partial name, relative name, or glob pattern 3475 * to be searched for within the current state name. 3476 * @param {object=} params A param object, e.g. `{sectionId: section.id}`, 3477 * that you'd like to test against the current active state. 3478 * @param {object=} options An options object. The options are: 3479 * 3480 * - **`relative`** - {string|object=} - If `stateOrName` is a relative state reference and `options.relative` is set, 3481 * .includes will test relative to `options.relative` state (or name). 3482 * 3483 * @returns {boolean} Returns true if it does include the state 3484 */ 3485 $state.includes = function includes(stateOrName, params, options) { 3486 options = extend({ relative: $state.$current }, options || {}); 3487 if (isString(stateOrName) && isGlob(stateOrName)) { 3488 if (!doesStateMatchGlob(stateOrName)) { 3489 return false;
3490 } 3491 stateOrName = $state.$current.name; 3492 } 3493 3494 var state = findState(stateOrName, options.relative); 3495 if (!isDefined(state)) { return undefined; } 3496 if (!isDefined($state.$current.includes[state.name])) { return false; } 3497 return params ? equalForKeys(state.params.$$values(params), $stateParams, objectKeys(params)) : true; 3498 }; 3499 3500 3501 /** 3502 * @ngdoc function 3503 * @name ui.router.state.$state#href 3504 * @methodOf ui.router.state.$state 3505 * 3506 * @description 3507 * A url generation method that returns the compiled url for the given state populated with the given params. 3508 * 3509 * @example 3510 * <pre> 3511 * expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob"); 3512 * </pre> 3513 * 3514 * @param {string|object} stateOrName The state name or state object you'd like to generate a url from. 3515 * @param {object=} params An object of parameter values to fill the state's required parameters. 3516 * @param {object=} options Options object. The options are: 3517 * 3518 * - **`lossy`** - {boolean=true} - If true, and if there is no url associated with the state provided in the 3519 * first parameter, then the constructed href url will be built from the first navigable ancestor (aka 3520 * ancestor with a valid url). 3521 * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url. 3522 * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'), 3523 * defines which state to be relative from. 3524 * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl". 3525 * 3526 * @returns {string} compiled state url 3527 */ 3528 $state.href = function href(stateOrName, params, options) { 3529 options = extend({ 3530 lossy: true, 3531 inherit: true, 3532 absolute: false, 3533 relative: $state.$current 3534 }, options || {}); 3535 3536 var state = findState(stateOrName, options.relative); 3537 3538 if (!isDefined(state)) return null; 3539 if (options.inherit) params = inheritParams($stateParams, params || {}, $state.$current, state); 3540 3541 var nav = (state && options.lossy) ? state.navigable : state; 3542 3543 if (!nav || nav.url === undefined || nav.url === null) { 3544 return null; 3545 } 3546 return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys().concat('#'), params || {}), { 3547 absolute: options.absolute 3548 }); 3549 }; 3550 3551 /** 3552 * @ngdoc function 3553 * @name ui.router.state.$state#get 3554 * @methodOf ui.router.state.$state 3555 * 3556 * @description 3557 * Returns the state configuration object for any specific state or all states. 3558 * 3559 * @param {string|object=} stateOrName (absolute or relative) If provided, will only get the config for 3560 * the requested state. If not provided, returns an array of ALL state configs. 3561 * @param {string|object=} context When stateOrName is a relative state reference, the state will be retrieved relative to context. 3562 * @returns {Object|Array} State configuration object or array of all objects. 3563 */ 3564 $state.get = function (stateOrName, context) { 3565 if (arguments.length === 0) return map(objectKeys(states), function(name) { return states[name].self; }); 3566 var state = findState(stateOrName, context || $state.$current); 3567 return (state && state.self) ? state.self : null; 3568 }; 3569 3570 function resolveState(state, params, paramsAreFiltered, inherited, dst, options) { 3571 // Make a restricted $stateParams with only the parameters that apply to this state if 3572 // necessary. In addition to being available to the controller and onEnter/onExit callbacks, 3573 // we also need $stateParams to be available for any $injector calls we make during the 3574 // dependency resolution process. 3575 var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params); 3576 var locals = { $stateParams: $stateParams }; 3577 3578 // Resolve 'global' dependencies for the state, i.e. those not specific to a view. 3579 // We're also including $stateParams in this; that way the parameters are restricted 3580 // to the set that should be visible to the state, and are independent of when we update
3581 // the global $state and $stateParams values. 3582 dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state); 3583 var promises = [dst.resolve.then(function (globals) { 3584 dst.globals = globals; 3585 })]; 3586 if (inherited) promises.push(inherited); 3587 3588 function resolveViews() { 3589 var viewsPromises = []; 3590 3591 // Resolve template and dependencies for all views. 3592 forEach(state.views, function (view, name) { 3593 var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {}); 3594 injectables.$template = [ function () { 3595 return $view.load(name, { view: view, locals: dst.globals, params: $stateParams, notify: options.notify }) || ''; 3596 }]; 3597 3598 viewsPromises.push($resolve.resolve(injectables, dst.globals, dst.resolve, state).then(function (result) { 3599 // References to the controller (only instantiated at link time) 3600 if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) { 3601 var injectLocals = angular.extend({}, injectables, dst.globals); 3602 result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals); 3603 } else { 3604 result.$$controller = view.controller; 3605 } 3606 // Provide access to the state itself for internal use 3607 result.$$state = state; 3608 result.$$controllerAs = view.controllerAs; 3609 dst[name] = result; 3610 })); 3611 }); 3612 3613 return $q.all(viewsPromises).then(function(){ 3614 return dst.globals; 3615 }); 3616 } 3617 3618 // Wait for all the promises and then return the activation object 3619 return $q.all(promises).then(resolveViews).then(function (values) { 3620 return dst; 3621 }); 3622 } 3623 3624 return $state; 3625 } 3626 3627 function shouldSkipReload(to, toParams, from, fromParams, locals, options) { 3628 // Return true if there are no differences in non-search (path/object) params, false if there are differences 3629 function nonSearchParamsEqual(fromAndToState, fromParams, toParams) { 3630 // Identify whether all the parameters that differ between `fromParams` and `toParams` were search params. 3631 function notSearchParam(key) { 3632 return fromAndToState.params[key].location != "search"; 3633 } 3634 var nonQueryParamKeys = fromAndToState.params.$$keys().filter(notSearchParam); 3635 var nonQueryParams = pick.apply({}, [fromAndToState.params].concat(nonQueryParamKeys)); 3636 var nonQueryParamSet = new $$UMFP.ParamSet(nonQueryParams); 3637 return nonQueryParamSet.$$equals(fromParams, toParams); 3638 } 3639 3640 // If reload was not explicitly requested 3641 // and we're transitioning to the same state we're already in 3642 // and the locals didn't change 3643 // or they changed in a way that doesn't merit reloading 3644 // (reloadOnParams:false, or reloadOnSearch.false and only search params changed) 3645 // Then return true. 3646 if (!options.reload && to === from && 3647 (locals === from.locals || (to.self.reloadOnSearch === false && nonSearchParamsEqual(from, fromParams, toParams)))) { 3648 return true; 3649 } 3650 } 3651} 3652 3653angular.module('ui.router.state') 3654 .factory('$stateParams', function () { return {}; }) 3655 .provider('$state', $StateProvider); 3656 3657 3658$ViewProvider.$inject = []; 3659function $ViewProvider() { 3660 3661 this.$get = $get; 3662 /** 3663 * @ngdoc object 3664 * @name ui.router.state.$view 3665 * 3666 * @requires ui.router.util.$templateFactory 3667 * @requires $rootScope 3668 * 3669 * @description 3670 * 3671 */ 3672 $get.$inject = ['$rootScope', '$templateFactory']; 3673 function $get( $rootScope, $templateFactory) { 3674 return { 3675 // $view.load('full.viewName', { template: ..., controller: ..., resolve: ..., async: false, params: ... }) 3676 /** 3677 * @ngdoc function 3678 * @name ui.router.state.$view#load 3679 * @methodOf ui.router.state.$view 3680 * 3681 * @description 3682 * 3683 * @param {string} name name 3684 * @param {object} options option object. 3685 */ 3686 load: function load(name, options) { 3687 var result, defaults = { 3688 template: null, controller: null, view: null, locals: null, notify: true, async: true, params: {} 3689 }; 3690 options = extend(defaults, options); 3691 3692 if (options.view) { 3693 result = $templateFactory.fromConfig(options.view, options.params, options.locals); 3694 } 3695 return result; 3696 } 3697 }; 3698 } 3699} 3700 3701angular.module('ui.router.state').provider('$view', $ViewProvider); 3702 3703/** 3704 * @ngdoc object 3705 * @name ui.router.state.$uiViewScrollProvider 3706 * 3707 * @description 3708 * Provider that returns the {@link ui.router.state.$uiViewScroll} service function. 3709 */ 3710function $ViewScrollProvider() { 3711 3712 var useAnchorScroll = false;
3713 3714 /** 3715 * @ngdoc function 3716 * @name ui.router.state.$uiViewScrollProvider#useAnchorScroll 3717 * @methodOf ui.router.state.$uiViewScrollProvider 3718 * 3719 * @description 3720 * Reverts back to using the core [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) service for 3721 * scrolling based on the url anchor. 3722 */ 3723 this.useAnchorScroll = function () { 3724 useAnchorScroll = true; 3725 }; 3726 3727 /** 3728 * @ngdoc object 3729 * @name ui.router.state.$uiViewScroll 3730 * 3731 * @requires $anchorScroll 3732 * @requires $timeout 3733 * 3734 * @description 3735 * When called with a jqLite element, it scrolls the element into view (after a 3736 * `$timeout` so the DOM has time to refresh). 3737 * 3738 * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor, 3739 * this can be enabled by calling {@link ui.router.state.$uiViewScrollProvider#methods_useAnchorScroll `$uiViewScrollProvider.useAnchorScroll()`}. 3740 */ 3741 this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) { 3742 if (useAnchorScroll) { 3743 return $anchorScroll; 3744 } 3745 3746 return function ($element) { 3747 return $timeout(function () { 3748 $element[0].scrollIntoView(); 3749 }, 0, false); 3750 }; 3751 }]; 3752} 3753 3754angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider); 3755 3756var ngMajorVer = angular.version.major; 3757var ngMinorVer = angular.version.minor; 3758/** 3759 * @ngdoc directive 3760 * @name ui.router.state.directive:ui-view 3761 * 3762 * @requires ui.router.state.$state 3763 * @requires $compile 3764 * @requires $controller 3765 * @requires $injector 3766 * @requires ui.router.state.$uiViewScroll 3767 * @requires $document 3768 * 3769 * @restrict ECA 3770 * 3771 * @description 3772 * The ui-view directive tells $state where to place your templates. 3773 * 3774 * @param {string=} name A view name. The name should be unique amongst the other views in the 3775 * same state. You can have views of the same name that live in different states. 3776 * 3777 * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window 3778 * when a view is populated. By default, $anchorScroll is overridden by ui-router's custom scroll 3779 * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you 3780 * scroll ui-view elements into view when they are populated during a state activation. 3781 * 3782 * @param {string=} noanimation If truthy, the non-animated renderer will be selected (no animations 3783 * will be applied to the ui-view) 3784 * 3785 * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) 3786 * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.* 3787 * 3788 * @param {string=} onload Expression to evaluate whenever the view updates. 3789 * 3790 * @example 3791 * A view can be unnamed or named. 3792 * <pre> 3793 * <!-- Unnamed --> 3794 * <div ui-view></div> 3795 * 3796 * <!-- Named --> 3797 * <div ui-view="viewName"></div> 3798 * </pre> 3799 * 3800 * You can only have one unnamed view within any template (or root html). If you are only using a 3801 * single view and it is unnamed then you can populate it like so: 3802 * <pre> 3803 * <div ui-view></div> 3804 * $stateProvider.state("home", { 3805 * template: "<h1>HELLO!</h1>" 3806 * }) 3807 * </pre> 3808 * 3809 * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#views `views`} 3810 * config property, by name, in this case an empty name: 3811 * <pre> 3812 * $stateProvider.state("home", { 3813 * views: { 3814 * "": { 3815 * template: "<h1>HELLO!</h1>" 3816 * } 3817 * } 3818 * }) 3819 * </pre> 3820 * 3821 * But typically you'll only use the views property if you name your view or have more than one view 3822 * in the same template. There's not really a compelling reason to name a view if its the only one, 3823 * but you could if you wanted, like so: 3824 * <pre> 3825 * <div ui-view="main"></div> 3826 * </pre> 3827 * <pre> 3828 * $stateProvider.state("home", { 3829 * views: { 3830 * "main": { 3831 * template: "<h1>HELLO!</h1>" 3832 * } 3833 * } 3834 * }) 3835 * </pre> 3836 * 3837 * Really though, you'll use views to set up multiple views: 3838 * <pre> 3839 * <div ui-view></div> 3840 * <div ui-view="chart"></div> 3841 * <div ui-view="data"></div> 3842 * </pre> 3843 * 3844 * <pre> 3845 * $stateProvider.state("home", { 3846 * views: { 3847 * "": { 3848 * template: "<h1>HELLO!</h1>" 3849 * }, 3850 * "chart": { 3851 * template: "<chart_thing/>" 3852 * }, 3853 * "data": { 3854 * template: "<data_thing/>" 3855 * } 3856 * } 3857 * }) 3858 * </pre> 3859 * 3860 * Examples for `autoscroll`: 3861 * 3862 * <pre> 3863 * <!-- If autoscroll present with no expression, 3864 * then scroll ui-view into view --> 3865 * <ui-view autoscroll/> 3866 * 3867 * <!-- If autoscroll present with valid expression, 3868 * then scroll ui-view into view if expression evaluates to true --> 3869 * <ui-view autoscroll='true'/> 3870 * <ui-view autoscroll='false'/> 3871 * <ui-view autoscroll='scopeVariable'/> 3872 * </pre> 3873 */ 3874$ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll', '$interpolate']; 3875function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate) { 3876 3877 function getService() { 3878 return ($injector.has) ? function(service) { 3879 return $injector.has(service) ? $injector.get(service) : null; 3880 } : function(service) {
3881 try { 3882 return $injector.get(service); 3883 } catch (e) { 3884 return null; 3885 } 3886 }; 3887 } 3888 3889 var service = getService(), 3890 $animator = service('$animator'), 3891 $animate = service('$animate'); 3892 3893 // Returns a set of DOM manipulation functions based on which Angular version 3894 // it should use 3895 function getRenderer(attrs, scope) { 3896 var statics = { 3897 enter: function (element, target, cb) { target.after(element); cb(); }, 3898 leave: function (element, cb) { element.remove(); cb(); } 3899 }; 3900 3901 if (!!attrs.noanimation) return statics; 3902 3903 function animEnabled(element) { 3904 if (ngMajorVer === 1 && ngMinorVer >= 4) return !!$animate.enabled(element); 3905 if (ngMajorVer === 1 && ngMinorVer >= 2) return !!$animate.enabled(); 3906 return (!!$animator); 3907 } 3908 3909 // ng 1.2+ 3910 if ($animate) { 3911 return { 3912 enter: function(element, target, cb) { 3913 if (!animEnabled(element)) { 3914 statics.enter(element, target, cb); 3915 } else if (angular.version.minor > 2) { 3916 $animate.enter(element, null, target).then(cb); 3917 } else { 3918 $animate.enter(element, null, target, cb); 3919 } 3920 }, 3921 leave: function(element, cb) { 3922 if (!animEnabled(element)) { 3923 statics.leave(element, cb); 3924 } else if (angular.version.minor > 2) { 3925 $animate.leave(element).then(cb); 3926 } else { 3927 $animate.leave(element, cb); 3928 } 3929 } 3930 }; 3931 } 3932 3933 // ng 1.1.5 3934 if ($animator) { 3935 var animate = $animator && $animator(scope, attrs); 3936 3937 return { 3938 enter: function(element, target, cb) {animate.enter(element, null, target); cb(); }, 3939 leave: function(element, cb) { animate.leave(element); cb(); } 3940 }; 3941 } 3942 3943 return statics; 3944 } 3945 3946 var directive = { 3947 restrict: 'ECA', 3948 terminal: true, 3949 priority: 400, 3950 transclude: 'element', 3951 compile: function (tElement, tAttrs, $transclude) { 3952 return function (scope, $element, attrs) { 3953 var previousEl, currentEl, currentScope, latestLocals, 3954 onloadExp = attrs.onload || '', 3955 autoScrollExp = attrs.autoscroll, 3956 renderer = getRenderer(attrs, scope); 3957 3958 scope.$on('$stateChangeSuccess', function() { 3959 updateView(false); 3960 }); 3961 3962 updateView(true); 3963 3964 function cleanupLastView() { 3965 var _previousEl = previousEl; 3966 var _currentScope = currentScope; 3967 3968 if (_currentScope) { 3969 _currentScope._willBeDestroyed = true; 3970 } 3971 3972 function cleanOld() { 3973 if (_previousEl) { 3974 _previousEl.remove(); 3975 } 3976 3977 if (_currentScope) { 3978 _currentScope.$destroy(); 3979 } 3980 } 3981 3982 if (currentEl) { 3983 renderer.leave(currentEl, function() { 3984 cleanOld(); 3985 previousEl = null; 3986 }); 3987 3988 previousEl = currentEl; 3989 } else { 3990 cleanOld(); 3991 previousEl = null; 3992 } 3993 3994 currentEl = null; 3995 currentScope = null; 3996 } 3997 3998 function updateView(firstTime) { 3999 var newScope, 4000 name = getUiViewName(scope, attrs, $element, $interpolate), 4001 previousLocals = name && $state.$current && $state.$current.locals[name]; 4002 4003 if (!firstTime && previousLocals === latestLocals || scope._willBeDestroyed) return; // nothing to do 4004 newScope = scope.$new(); 4005 latestLocals = $state.$current.locals[name]; 4006 4007 /** 4008 * @ngdoc event 4009 * @name ui.router.state.directive:ui-view#$viewContentLoading 4010 * @eventOf ui.router.state.directive:ui-view 4011 * @eventType emits on ui-view directive scope 4012 * @description 4013 * 4014 * Fired once the view **begins loading**, *before* the DOM is rendered. 4015 * 4016 * @param {Object} event Event object. 4017 * @param {string} viewName Name of the view. 4018 */ 4019 newScope.$emit('$viewContentLoading', name); 4020 4021 var clone = $transclude(newScope, function(clone) { 4022 renderer.enter(clone, $element, function onUiViewEnter() { 4023 if(currentScope) { 4024 currentScope.$emit('$viewContentAnimationEnded'); 4025 } 4026 4027 if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) { 4028 $uiViewScroll(clone); 4029 } 4030 }); 4031 cleanupLastView(); 4032 }); 4033 4034 currentEl = clone; 4035 currentScope = newScope; 4036 /** 4037 * @ngdoc event 4038 * @name ui.router.state.directive:ui-view#$viewContentLoaded 4039 * @eventOf ui.router.state.directive:ui-view 4040 * @eventType emits on ui-view directive scope
4041 * @description 4042 * Fired once the view is **loaded**, *after* the DOM is rendered. 4043 * 4044 * @param {Object} event Event object. 4045 * @param {string} viewName Name of the view. 4046 */ 4047 currentScope.$emit('$viewContentLoaded', name); 4048 currentScope.$eval(onloadExp); 4049 } 4050 }; 4051 } 4052 }; 4053 4054 return directive; 4055} 4056 4057$ViewDirectiveFill.$inject = ['$compile', '$controller', '$state', '$interpolate']; 4058function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate) { 4059 return { 4060 restrict: 'ECA', 4061 priority: -400, 4062 compile: function (tElement) { 4063 var initial = tElement.html(); 4064 return function (scope, $element, attrs) { 4065 var current = $state.$current, 4066 name = getUiViewName(scope, attrs, $element, $interpolate), 4067 locals = current && current.locals[name]; 4068 4069 if (! locals) { 4070 return; 4071 } 4072 4073 $element.data('$uiView', { name: name, state: locals.$$state }); 4074 $element.html(locals.$template ? locals.$template : initial); 4075 4076 var link = $compile($element.contents()); 4077 4078 if (locals.$$controller) { 4079 locals.$scope = scope; 4080 locals.$element = $element; 4081 var controller = $controller(locals.$$controller, locals); 4082 if (locals.$$controllerAs) { 4083 scope[locals.$$controllerAs] = controller; 4084 } 4085 $element.data('$ngControllerController', controller); 4086 $element.children().data('$ngControllerController', controller); 4087 } 4088 4089 link(scope); 4090 }; 4091 } 4092 }; 4093} 4094 4095/** 4096 * Shared ui-view code for both directives: 4097 * Given scope, element, and its attributes, return the view's name 4098 */ 4099function getUiViewName(scope, attrs, element, $interpolate) { 4100 var name = $interpolate(attrs.uiView || attrs.name || '')(scope); 4101 var inherited = element.inheritedData('$uiView'); 4102 return name.indexOf('@') >= 0 ? name : (name + '@' + (inherited ? inherited.state.name : '')); 4103} 4104 4105angular.module('ui.router.state').directive('uiView', $ViewDirective); 4106angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill); 4107 4108function parseStateRef(ref, current) { 4109 var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed; 4110 if (preparsed) ref = current + '(' + preparsed[1] + ')'; 4111 parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/); 4112 if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'"); 4113 return { state: parsed[1], paramExpr: parsed[3] || null }; 4114} 4115 4116function stateContext(el) { 4117 var stateData = el.parent().inheritedData('$uiView'); 4118 4119 if (stateData && stateData.state && stateData.state.name) { 4120 return stateData.state; 4121 } 4122} 4123 4124function getTypeInfo(el) { 4125 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 4126 var isSvg = Object.prototype.toString.call(el.prop('href')) === '[object SVGAnimatedString]'; 4127 var isForm = el[0].nodeName === "FORM"; 4128 4129 return { 4130 attr: isForm ? "action" : (isSvg ? 'xlink:href' : 'href'), 4131 isAnchor: el.prop("tagName").toUpperCase() === "A", 4132 clickable: !isForm 4133 }; 4134} 4135 4136function clickHook(el, $state, $timeout, type, current) { 4137 return function(e) { 4138 var button = e.which || e.button, target = current(); 4139 4140 if (!(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || el.attr('target'))) { 4141 // HACK: This is to allow ng-clicks to be processed before the transition is initiated: 4142 var transition = $timeout(function() { 4143 $state.go(target.state, target.params, target.options); 4144 }); 4145 e.preventDefault(); 4146 4147 // if the state has no URL, ignore one preventDefault from the <a> directive. 4148 var ignorePreventDefaultCount = type.isAnchor && !target.href ? 1: 0; 4149 4150 e.preventDefault = function() { 4151 if (ignorePreventDefaultCount-- <= 0) $timeout.cancel(transition); 4152 }; 4153 } 4154 }; 4155} 4156 4157function defaultOpts(el, $state) { 4158 return { relative: stateContext(el) || $state.$current, inherit: true }; 4159} 4160 4161/** 4162 * @ngdoc directive 4163 * @name ui.router.state.directive:ui-sref 4164 * 4165 * @requires ui.router.state.$state 4166 * @requires $timeout 4167 * 4168 * @restrict A 4169 * 4170 * @description 4171 * A directive that binds a link (`<a>` tag) to a state. If the state has an associated 4172 * URL, the directive will automatically generate & update the `href` attribute via 4173 * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking 4174 * the link will trigger a state transition with optional parameters. 4175 * 4176 * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be 4177 * handled natively by the browser. 4178 * 4179 * You can also use relative state paths within ui-sref, just like the relative 4180 * paths passed to `$state.go()`. You just need to be aware that the path is relative 4181 * to the state that the link lives in, in other words the state that loaded the 4182 * template containing the link. 4183 * 4184 * You can specify options to pass to {@link ui.router.state.$state#go $state.go()} 4185 * using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`, 4186 * and `reload`. 4187 * 4188 * @example 4189 * Here's an example of how you'd use ui-sref and how it would compile. If you have the 4190 * following template: 4191 * <pre> 4192 * <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a> 4193 * 4194 * <ul> 4195 * <li ng-repeat="contact in contacts">
4196 * <a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a> 4197 * </li> 4198 * </ul> 4199 * </pre> 4200 * 4201 * Then the compiled html would be (assuming Html5Mode is off and current state is contacts): 4202 * <pre> 4203 * <a href="#/home" ui-sref="home">Home</a> | <a href="#/about" ui-sref="about">About</a> | <a href="#/contacts?page=2" ui-sref="{page: 2}">Next page</a> 4204 * 4205 * <ul> 4206 * <li ng-repeat="contact in contacts"> 4207 * <a href="#/contacts/1" ui-sref="contacts.detail({ id: contact.id })">Joe</a> 4208 * </li> 4209 * <li ng-repeat="contact in contacts"> 4210 * <a href="#/contacts/2" ui-sref="contacts.detail({ id: contact.id })">Alice</a> 4211 * </li> 4212 * <li ng-repeat="contact in contacts"> 4213 * <a href="#/contacts/3" ui-sref="contacts.detail({ id: contact.id })">Bob</a> 4214 * </li> 4215 * </ul> 4216 * 4217 * <a ui-sref="home" ui-sref-opts="{reload: true}">Home</a> 4218 * </pre> 4219 * 4220 * @param {string} ui-sref 'stateName' can be any valid absolute or relative state 4221 * @param {Object} ui-sref-opts options to pass to {@link ui.router.state.$state#go $state.go()} 4222 */ 4223$StateRefDirective.$inject = ['$state', '$timeout']; 4224function $StateRefDirective($state, $timeout) { 4225 return { 4226 restrict: 'A', 4227 require: ['?^uiSrefActive', '?^uiSrefActiveEq'], 4228 link: function(scope, element, attrs, uiSrefActive) { 4229 var ref = parseStateRef(attrs.uiSref, $state.current.name); 4230 var def = { state: ref.state, href: null, params: null }; 4231 var type = getTypeInfo(element); 4232 var active = uiSrefActive[1] || uiSrefActive[0]; 4233 4234 def.options = extend(defaultOpts(element, $state), attrs.uiSrefOpts ? scope.$eval(attrs.uiSrefOpts) : {}); 4235 4236 var update = function(val) { 4237 if (val) def.params = angular.copy(val); 4238 def.href = $state.href(ref.state, def.params, def.options); 4239 4240 if (active) active.$$addStateInfo(ref.state, def.params); 4241 if (def.href !== null) attrs.$set(type.attr, def.href); 4242 }; 4243 4244 if (ref.paramExpr) { 4245 scope.$watch(ref.paramExpr, function(val) { if (val !== def.params) update(val); }, true); 4246 def.params = angular.copy(scope.$eval(ref.paramExpr)); 4247 } 4248 update(); 4249 4250 if (!type.clickable) return; 4251 element.bind("click", clickHook(element, $state, $timeout, type, function() { return def; })); 4252 } 4253 }; 4254} 4255 4256/** 4257 * @ngdoc directive 4258 * @name ui.router.state.directive:ui-state 4259 * 4260 * @requires ui.router.state.uiSref 4261 * 4262 * @restrict A 4263 * 4264 * @description 4265 * Much like ui-sref, but will accept named $scope properties to evaluate for a state definition, 4266 * params and override options. 4267 * 4268 * @param {string} ui-state 'stateName' can be any valid absolute or relative state 4269 * @param {Object} ui-state-params params to pass to {@link ui.router.state.$state#href $state.href()} 4270 * @param {Object} ui-state-opts options to pass to {@link ui.router.state.$state#go $state.go()} 4271 */ 4272$StateRefDynamicDirective.$inject = ['$state', '$timeout']; 4273function $StateRefDynamicDirective($state, $timeout) { 4274 return { 4275 restrict: 'A', 4276 require: ['?^uiSrefActive', '?^uiSrefActiveEq'], 4277 link: function(scope, element, attrs, uiSrefActive) { 4278 var type = getTypeInfo(element); 4279 var active = uiSrefActive[1] || uiSrefActive[0]; 4280 var group = [attrs.uiState, attrs.uiStateParams || null, attrs.uiStateOpts || null]; 4281 var watch = '[' + group.map(function(val) { return val || 'null'; }).join(', ') + ']'; 4282 var def = { state: null, params: null, options: null, href: null }; 4283 4284 function runStateRefLink (group) { 4285 def.state = group[0]; def.params = group[1]; def.options = group[2]; 4286 def.href = $state.href(def.state, def.params, def.options); 4287 4288 if (active) active.$$addStateInfo(def.state, def.params); 4289 if (def.href) attrs.$set(type.attr, def.href); 4290 } 4291 4292 scope.$watch(watch, runStateRefLink, true); 4293 runStateRefLink(scope.$eval(watch)); 4294 4295 if (!type.clickable) return; 4296 element.bind("click", clickHook(element, $state, $timeout, type, function() { return def; })); 4297 } 4298 }; 4299} 4300 4301 4302/** 4303 * @ngdoc directive 4304 * @name ui.router.state.directive:ui-sref-active 4305 * 4306 * @requires ui.router.state.$state 4307 * @requires ui.router.state.$stateParams 4308 * @requires $interpolate 4309 * 4310 * @restrict A 4311 * 4312 * @description 4313 * A directive working alongside ui-sref to add classes to an element when the 4314 * related ui-sref directive's state is active, and removing them when it is inactive. 4315 * The primary use-case is to simplify the special appearance of navigation menus 4316 * relying on `ui-sref`, by having the "active" state's menu button appear different, 4317 * distinguishing it from the inactive menu items. 4318 * 4319 * ui-sref-active can live on the same element as ui-sref or on a parent element. The first 4320 * ui-sref-active found at the same level or above the ui-sref will be used. 4321 * 4322 * Will activate when the ui-sref's target state or any child state is active. If you 4323 * need to activate only when the ui-sref target state is active and *not* any of 4324 * it's children, then you will use 4325 * {@link ui.router.state.directive:ui-sref-active-eq ui-sref-active-eq} 4326 * 4327 * @example 4328 * Given the following template: 4329 * <pre> 4330 * <ul> 4331 * <li ui-sref-active="active" class="item">
4332 * <a href ui-sref="app.user({user: 'bilbobaggins'})">@bilbobaggins</a> 4333 * </li> 4334 * </ul> 4335 * </pre> 4336 * 4337 * 4338 * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins", 4339 * the resulting HTML will appear as (note the 'active' class): 4340 * <pre> 4341 * <ul> 4342 * <li ui-sref-active="active" class="item active"> 4343 * <a ui-sref="app.user({user: 'bilbobaggins'})" href="/users/bilbobaggins">@bilbobaggins</a> 4344 * </li> 4345 * </ul> 4346 * </pre> 4347 * 4348 * The class name is interpolated **once** during the directives link time (any further changes to the 4349 * interpolated value are ignored). 4350 * 4351 * Multiple classes may be specified in a space-separated format: 4352 * <pre> 4353 * <ul> 4354 * <li ui-sref-active='class1 class2 class3'> 4355 * <a ui-sref="app.user">link</a> 4356 * </li> 4357 * </ul> 4358 * </pre> 4359 * 4360 * It is also possible to pass ui-sref-active an expression that evaluates 4361 * to an object hash, whose keys represent active class names and whose 4362 * values represent the respective state names/globs. 4363 * ui-sref-active will match if the current active state **includes** any of 4364 * the specified state names/globs, even the abstract ones. 4365 * 4366 * @Example 4367 * Given the following template, with "admin" being an abstract state: 4368 * <pre> 4369 * <div ui-sref-active="{'active': 'admin.*'}"> 4370 * <a ui-sref-active="active" ui-sref="admin.roles">Roles</a> 4371 * </div> 4372 * </pre> 4373 * 4374 * When the current state is "admin.roles" the "active" class will be applied 4375 * to both the <div> and <a> elements. It is important to note that the state 4376 * names/globs passed to ui-sref-active shadow the state provided by ui-sref. 4377 */ 4378 4379/** 4380 * @ngdoc directive 4381 * @name ui.router.state.directive:ui-sref-active-eq 4382 * 4383 * @requires ui.router.state.$state 4384 * @requires ui.router.state.$stateParams 4385 * @requires $interpolate 4386 * 4387 * @restrict A 4388 * 4389 * @description 4390 * The same as {@link ui.router.state.directive:ui-sref-active ui-sref-active} but will only activate 4391 * when the exact target state used in the `ui-sref` is active; no child states. 4392 * 4393 */ 4394$StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate']; 4395function $StateRefActiveDirective($state, $stateParams, $interpolate) { 4396 return { 4397 restrict: "A", 4398 controller: ['$scope', '$element', '$attrs', '$timeout', function ($scope, $element, $attrs, $timeout) { 4399 var states = [], activeClasses = {}, activeEqClass, uiSrefActive; 4400 4401 // There probably isn't much point in $observing this 4402 // uiSrefActive and uiSrefActiveEq share the same directive object with some 4403 // slight difference in logic routing 4404 activeEqClass = $interpolate($attrs.uiSrefActiveEq || '', false)($scope); 4405 4406 try { 4407 uiSrefActive = $scope.$eval($attrs.uiSrefActive); 4408 } catch (e) { 4409 // Do nothing. uiSrefActive is not a valid expression. 4410 // Fall back to using $interpolate below 4411 } 4412 uiSrefActive = uiSrefActive || $interpolate($attrs.uiSrefActive || '', false)($scope); 4413 if (isObject(uiSrefActive)) { 4414 forEach(uiSrefActive, function(stateOrName, activeClass) { 4415 if (isString(stateOrName)) { 4416 var ref = parseStateRef(stateOrName, $state.current.name); 4417 addState(ref.state, $scope.$eval(ref.paramExpr), activeClass); 4418 } 4419 }); 4420 } 4421 4422 // Allow uiSref to communicate with uiSrefActive[Equals] 4423 this.$$addStateInfo = function (newState, newParams) { 4424 // we already got an explicit state provided by ui-sref-active, so we 4425 // shadow the one that comes from ui-sref 4426 if (isObject(uiSrefActive) && states.length > 0) { 4427 return; 4428 } 4429 addState(newState, newParams, uiSrefActive); 4430 update(); 4431 }; 4432 4433 $scope.$on('$stateChangeSuccess', update); 4434 4435 function addState(stateName, stateParams, activeClass) { 4436 var state = $state.get(stateName, stateContext($element)); 4437 var stateHash = createStateHash(stateName, stateParams); 4438 4439 states.push({ 4440 state: state || { name: stateName }, 4441 params: stateParams, 4442 hash: stateHash 4443 }); 4444 4445 activeClasses[stateHash] = activeClass; 4446 } 4447 4448 /** 4449 * @param {string} state 4450 * @param {Object|string} [params] 4451 * @return {string} 4452 */ 4453 function createStateHash(state, params) { 4454 if (!isString(state)) { 4455 throw new Error('state should be a string'); 4456 } 4457 if (isObject(params)) {
4458 return state + toJson(params); 4459 } 4460 params = $scope.$eval(params); 4461 if (isObject(params)) { 4462 return state + toJson(params); 4463 } 4464 return state; 4465 } 4466 4467 // Update route state 4468 function update() { 4469 for (var i = 0; i < states.length; i++) { 4470 if (anyMatch(states[i].state, states[i].params)) { 4471 addClass($element, activeClasses[states[i].hash]); 4472 } else { 4473 removeClass($element, activeClasses[states[i].hash]); 4474 } 4475 4476 if (exactMatch(states[i].state, states[i].params)) { 4477 addClass($element, activeEqClass); 4478 } else { 4479 removeClass($element, activeEqClass); 4480 } 4481 } 4482 } 4483 4484 function addClass(el, className) { $timeout(function () { el.addClass(className); }); } 4485 function removeClass(el, className) { el.removeClass(className); } 4486 function anyMatch(state, params) { return $state.includes(state.name, params); } 4487 function exactMatch(state, params) { return $state.is(state.name, params); } 4488 4489 update(); 4490 }] 4491 }; 4492} 4493 4494angular.module('ui.router.state') 4495 .directive('uiSref', $StateRefDirective) 4496 .directive('uiSrefActive', $StateRefActiveDirective) 4497 .directive('uiSrefActiveEq', $StateRefActiveDirective) 4498 .directive('uiState', $StateRefDynamicDirective); 4499 4500/** 4501 * @ngdoc filter 4502 * @name ui.router.state.filter:isState 4503 * 4504 * @requires ui.router.state.$state 4505 * 4506 * @description 4507 * Translates to {@link ui.router.state.$state#methods_is $state.is("stateName")}. 4508 */ 4509$IsStateFilter.$inject = ['$state']; 4510function $IsStateFilter($state) { 4511 var isFilter = function (state, params) { 4512 return $state.is(state, params); 4513 }; 4514 isFilter.$stateful = true; 4515 return isFilter; 4516} 4517 4518/** 4519 * @ngdoc filter 4520 * @name ui.router.state.filter:includedByState 4521 * 4522 * @requires ui.router.state.$state 4523 * 4524 * @description 4525 * Translates to {@link ui.router.state.$state#methods_includes $state.includes('fullOrPartialStateName')}. 4526 */ 4527$IncludedByStateFilter.$inject = ['$state']; 4528function $IncludedByStateFilter($state) { 4529 var includesFilter = function (state, params, options) { 4530 return $state.includes(state, params, options); 4531 }; 4532 includesFilter.$stateful = true; 4533 return includesFilter; 4534} 4535 4536angular.module('ui.router.state') 4537 .filter('isState', $IsStateFilter) 4538 .filter('includedByState', $IncludedByStateFilter); 4539})(window, window.angular);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.