diff --git a/README.md b/README.md index 520438b01..a10ab4a1e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ #### The de-facto solution to flexible routing with nested views --- -**[Download 0.2.8](http://angular-ui.github.io/ui-router/release/angular-ui-router.js)** (or **[Minified](http://angular-ui.github.io/ui-router/release/angular-ui-router.min.js)**) **|** +**[Download 0.2.10](http://angular-ui.github.io/ui-router/release/angular-ui-router.js)** (or **[Minified](http://angular-ui.github.io/ui-router/release/angular-ui-router.min.js)**) **|** **[Learn](#resources) |** **[API](http://angular-ui.github.io/ui-router/site) |** **[Discuss](https://groups.google.com/forum/#!categories/angular-ui/router) |** diff --git a/bower.json b/bower.json index 3c260812b..9b8f00f35 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "angular-ui-router", - "version": "0.2.8", + "version": "0.2.10", "main": "./release/angular-ui-router.js", "dependencies": { "angular": ">= 1.0.8" diff --git a/component.json b/component.json index 76495b8c1..d1beda99a 100644 --- a/component.json +++ b/component.json @@ -1,6 +1,6 @@ { "name": "angular-ui-router", - "version": "0.2.8", + "version": "0.2.10", "description": "State-based routing for AngularJS", "keywords": [ "angular", diff --git a/package.json b/package.json index 49039a325..4c363104a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "angular-ui-router", "description": "State-based routing for AngularJS", - "version": "0.2.8", + "version": "0.2.10", "homepage": "http://angular-ui.github.com/", "contributors": [ { @@ -41,10 +41,10 @@ "devDependencies": { "grunt": "~0.4.1", "grunt-contrib-concat": "~0.3.0", - "grunt-contrib-uglify": "~0.2.7", - "grunt-contrib-jshint": "~0.7.1", + "grunt-contrib-uglify": "~0.4.0", + "grunt-contrib-jshint": "~0.8.0", "grunt-contrib-watch": "~0.5.3", - "grunt-contrib-connect": "~0.5.0", + "grunt-contrib-connect": "~0.7.1", "grunt-contrib-clean": "~0.5.0", "grunt-karma": "~0.6.2", "jsdoc": "git://github.com/jsdoc3/jsdoc.git#v3.2.2", @@ -59,8 +59,8 @@ "karma-coffee-preprocessor": "~0.1.0", "karma": "~0.10.4", "karma-phantomjs-launcher": "~0.1.0", - "load-grunt-tasks": "~0.2.0", - "grunt-conventional-changelog": "~1.0.0", + "load-grunt-tasks": "~0.4.0", + "grunt-conventional-changelog": "~1.1.0", "grunt-ngdocs": "~0.1.7" }, "main": "release/angular-ui-router" diff --git a/release/angular-ui-router.js b/release/angular-ui-router.js index 4a0e8157a..3faa6bb3c 100644 --- a/release/angular-ui-router.js +++ b/release/angular-ui-router.js @@ -1,6 +1,6 @@ /** * State-based routing for AngularJS - * @version v0.2.8 + * @version v0.2.10 * @link http://angular-ui.github.com/ * @license MIT License, http://www.opensource.org/licenses/MIT */ @@ -175,12 +175,15 @@ function filterByKeys(keys, values) { }); return filtered; } - /** * @ngdoc overview * @name ui.router.util * * @description + * # ui.router.util sub-module + * + * This module is a dependency of other sub-modules. Do not include this module as a dependency + * in your angular app (use {@link ui.router} module instead). * */ angular.module('ui.router.util', ['ng']); @@ -192,19 +195,26 @@ angular.module('ui.router.util', ['ng']); * @requires ui.router.util * * @description + * # ui.router.router sub-module * + * This module is a dependency of other sub-modules. Do not include this module as a dependency + * in your angular app (use {@link ui.router} module instead). */ angular.module('ui.router.router', ['ui.router.util']); /** * @ngdoc overview - * @name ui.router.router + * @name ui.router.state * * @requires ui.router.router * @requires ui.router.util * * @description + * # ui.router.state sub-module * + * This module is a dependency of the main ui.router module. Do not include this module as a dependency + * in your angular app (use {@link ui.router} module instead). + * */ angular.module('ui.router.state', ['ui.router.router', 'ui.router.util']); @@ -215,18 +225,37 @@ angular.module('ui.router.state', ['ui.router.router', 'ui.router.util']); * @requires ui.router.state * * @description + * # ui.router + * + * ## The main module for ui.router + * There are several sub-modules included with the ui.router module, however only this module is needed + * as a dependency within your angular app. The other modules are for organization purposes. * + * The modules are: + * * ui.router - the main "umbrella" module + * * ui.router.router - + * + * *You'll need to include **only** this module as the dependency within your angular app.* + * + *
+ * 
+ * 
+ * 
+ *   
+ *   
+ *   
+ *   
+ * 
+ * 
+ * 
+ * 
+ * 
*/ angular.module('ui.router', ['ui.router.state']); -/** - * @ngdoc overview - * @name ui.router.compat - * - * @requires ui.router - * - * @description - * - */ + angular.module('ui.router.compat', ['ui.router']); /** @@ -584,19 +613,23 @@ function $TemplateFactory( $http, $templateCache, $injector) { angular.module('ui.router.util').service('$templateFactory', $TemplateFactory); /** + * @ngdoc object + * @name ui.router.util.type:UrlMatcher + * + * @description * Matches URLs against patterns and extracts named parameters from the path or the search * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list * of search parameters. Multiple search parameter names are separated by '&'. Search parameters * do not influence whether or not a URL is matched, but their values are passed through into - * the matched parameters returned by {@link UrlMatcher#exec exec}. + * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}. * * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace * syntax, which optionally allows a regular expression for the parameter to be specified: * - * * ':' name - colon placeholder - * * '*' name - catch-all placeholder - * * '{' name '}' - curly placeholder - * * '{' name ':' regexp '}' - curly placeholder with regexp. Should the regexp itself contain + * * `':'` name - colon placeholder + * * `'*'` name - catch-all placeholder + * * `'{' name '}'` - curly placeholder + * * `'{' name ':' regexp '}'` - curly placeholder with regexp. Should the regexp itself contain * curly braces, they must be in matched pairs or escaped with a backslash. * * Parameter names may contain only word characters (latin letters, digits, and underscore) and @@ -605,26 +638,36 @@ angular.module('ui.router.util').service('$templateFactory', $TemplateFactory); * number of characters other than '/'. For catch-all placeholders the path parameter matches * any number of characters. * - * ### Examples + * Examples: * - * * '/hello/' - Matches only if the path is exactly '/hello/'. There is no special treatment for + * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for * trailing slashes, and patterns have to match the entire path, not just a prefix. - * * '/user/:id' - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or + * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or * '/user/bob/details'. The second path segment will be captured as the parameter 'id'. - * * '/user/{id}' - Same as the previous example, but using curly brace syntax. - * * '/user/{id:[^/]*}' - Same as the previous example. - * * '/user/{id:[0-9a-fA-F]{1,8}}' - Similar to the previous example, but only matches if the id + * * `'/user/{id}'` - Same as the previous example, but using curly brace syntax. + * * `'/user/{id:[^/]*}'` - Same as the previous example. + * * `'/user/{id:[0-9a-fA-F]{1,8}}'` - Similar to the previous example, but only matches if the id * parameter consists of 1 to 8 hex digits. - * * '/files/{path:.*}' - Matches any URL starting with '/files/' and captures the rest of the + * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the * path into the parameter 'path'. - * * '/files/*path' - ditto. + * * `'/files/*path'` - ditto. * - * @constructor * @param {string} pattern the pattern to compile into a matcher. * * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any - * URL matching this matcher (i.e. any string for which {@link UrlMatcher#exec exec()} returns + * URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns * non-null) will start with this prefix. + * + * @property {string} source The pattern that was passed into the contructor + * + * @property {string} sourcePath The path portion of the source property + * + * @property {string} sourceSearch The search portion of the source property + * + * @property {string} regex The constructed regex that will be used to match against the url when + * it is time to determine which url will match. + * + * @returns {Object} New UrlMatcher object */ function UrlMatcher(pattern) { @@ -695,12 +738,17 @@ function UrlMatcher(pattern) { } /** + * @ngdoc function + * @name ui.router.util.type:UrlMatcher#concat + * @methodOf ui.router.util.type:UrlMatcher + * + * @description * Returns a new matcher for a pattern constructed by appending the path part and adding the * search parameters of the specified pattern to this pattern. The current pattern is not * modified. This can be understood as creating a pattern for URLs that are relative to (or * suffixes of) the current pattern. * - * ### Example + * @example * The following two matchers are equivalent: * ``` * new UrlMatcher('/user/{id}?q').concat('/details?date'); @@ -708,7 +756,7 @@ function UrlMatcher(pattern) { * ``` * * @param {string} pattern The pattern to append. - * @return {UrlMatcher} A matcher for the concatenated pattern. + * @returns {ui.router.util.type:UrlMatcher} A matcher for the concatenated pattern. */ UrlMatcher.prototype.concat = function (pattern) { // Because order of search parameters is irrelevant, we can add our own search @@ -722,13 +770,18 @@ UrlMatcher.prototype.toString = function () { }; /** + * @ngdoc function + * @name ui.router.util.type:UrlMatcher#exec + * @methodOf ui.router.util.type:UrlMatcher + * + * @description * Tests the specified path against this matcher, and returns an object containing the captured * parameter values, or null if the path does not match. The returned object contains the values * of any search parameters that are mentioned in the pattern, but their value may be null if * they are not present in `searchParams`. This means that search parameters are always treated * as optional. * - * ### Example + * @example * ``` * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', { x:'1', q:'hello' }); * // returns { id:'bob', q:'hello', r:null } @@ -736,7 +789,7 @@ UrlMatcher.prototype.toString = function () { * * @param {string} path The URL path to match, e.g. `$location.path()`. * @param {Object} searchParams URL search parameters, e.g. `$location.search()`. - * @return {Object} The captured parameter values. + * @returns {Object} The captured parameter values. */ UrlMatcher.prototype.exec = function (path, searchParams) { var m = this.regexp.exec(path); @@ -755,8 +808,14 @@ UrlMatcher.prototype.exec = function (path, searchParams) { }; /** + * @ngdoc function + * @name ui.router.util.type:UrlMatcher#parameters + * @methodOf ui.router.util.type:UrlMatcher + * + * @description * Returns the names of all path and search parameters of this pattern in an unspecified order. - * @return {Array.} An array of parameter names. Must be treated as read-only. If the + * + * @returns {Array.} An array of parameter names. Must be treated as read-only. If the * pattern has no parameters, an empty array is returned. */ UrlMatcher.prototype.parameters = function () { @@ -764,18 +823,23 @@ UrlMatcher.prototype.parameters = function () { }; /** + * @ngdoc function + * @name ui.router.util.type:UrlMatcher#format + * @methodOf ui.router.util.type:UrlMatcher + * + * @description * Creates a URL that matches this pattern by substituting the specified values * for the path and search parameters. Null values for path parameters are * treated as empty strings. * - * ### Example + * @example * ``` * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' }); * // returns '/user/bob?q=yes' * ``` * * @param {Object} values the values to substitute for the parameters in this pattern. - * @return {string} the formatted URL (path and optionally search part). + * @returns {string} the formatted URL (path and optionally search part). */ UrlMatcher.prototype.format = function (values) { var segments = this.segments, params = this.params; @@ -801,37 +865,49 @@ UrlMatcher.prototype.format = function (values) { return result; }; + + /** - * Service. Factory for {@link UrlMatcher} instances. The factory is also available to providers + * @ngdoc object + * @name ui.router.util.$urlMatcherFactory + * + * @description + * Factory for {@link ui.router.util.type:UrlMatcher} instances. The factory is also available to providers * under the name `$urlMatcherFactoryProvider`. - * @constructor - * @name $urlMatcherFactory */ function $UrlMatcherFactory() { + /** - * Creates a {@link UrlMatcher} for the specified pattern. - * @function - * @name $urlMatcherFactory#compile - * @methodOf $urlMatcherFactory + * @ngdoc function + * @name ui.router.util.$urlMatcherFactory#compile + * @methodOf ui.router.util.$urlMatcherFactory + * + * @description + * Creates a {@link ui.router.util.type:UrlMatcher} for the specified pattern. + * * @param {string} pattern The URL pattern. - * @return {UrlMatcher} The UrlMatcher. + * @returns {ui.router.util.type:UrlMatcher} The UrlMatcher. */ this.compile = function (pattern) { return new UrlMatcher(pattern); }; /** + * @ngdoc function + * @name ui.router.util.$urlMatcherFactory#isMatcher + * @methodOf ui.router.util.$urlMatcherFactory + * + * @description * Returns true if the specified object is a UrlMatcher, or false otherwise. - * @function - * @name $urlMatcherFactory#isMatcher - * @methodOf $urlMatcherFactory - * @param {Object} o - * @return {boolean} + * + * @param {Object} object The object to perform the type check against. + * @returns {Boolean} Returns `true` if the object has the following functions: `exec`, `format`, and `concat`. */ this.isMatcher = function (o) { return isObject(o) && isFunction(o.exec) && isFunction(o.format) && isFunction(o.concat); }; - + + /* No need to document $get, since it returns this */ this.$get = function () { return this; }; @@ -1334,6 +1410,41 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ return state; } + // Checks text to see if it looks like a glob. + function isGlob (text) { + return text.indexOf('*') > -1; + } + + // Returns true if glob matches current $state name. + function doesStateMatchGlob (glob) { + var globSegments = glob.split('.'), + segments = $state.$current.name.split('.'); + + //match greedy starts + if (globSegments[0] === '**') { + segments = segments.slice(segments.indexOf(globSegments[1])); + segments.unshift('**'); + } + //match greedy ends + if (globSegments[globSegments.length - 1] === '**') { + segments.splice(segments.indexOf(globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE); + segments.push('**'); + } + + if (globSegments.length != segments.length) { + return false; + } + + //match single stars + for (var i = 0, l = globSegments.length; i < l; i++) { + if (globSegments[i] === '*') { + segments[i] = '*'; + } + } + + return segments.join('') === globSegments.join(''); + } + // Implicit root state that is always active root = registerState({ @@ -1369,31 +1480,31 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ * meta-programming features. * * **Warning**: Decorators should not be interdependent because the order of - * execution of the builder functions in nondeterministic. Builder functions + * execution of the builder functions in non-deterministic. Builder functions * should only be dependent on the state definition object and super function. * * * Existing builder functions and current return values: * - * - parent - `{object}` - returns the parent state object. - * - data - `{object}` - returns state data, including any inherited data that is not + * - **parent** `{object}` - returns the parent state object. + * - **data** `{object}` - returns state data, including any inherited data that is not * overridden by own values (if any). - * - url - `{object}` - returns a UrlMatcher or null. - * - navigable - returns closest ancestor state that has a URL (aka is + * - **url** `{object}` - returns a {link ui.router.util.type:UrlMatcher} or null. + * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is * navigable). - * - params - `{object}` - returns an array of state params that are ensured to + * - **params** `{object}` - returns an array of state params that are ensured to * be a super-set of parent's params. - * - views - `{object}` - returns a views object where each key is an absolute view + * - **views** `{object}` - returns a views object where each key is an absolute view * name (i.e. "viewName@stateName") and each value is the config object * (template, controller) for the view. Even when you don't use the views object * explicitly on a state config, one is still created for you internally. * So by decorating this builder function you have access to decorating template * and controller properties. - * - ownParams - `{object}` - returns an array of params that belong to the state, + * - **ownParams** `{object}` - returns an array of params that belong to the state, * not including any params defined by ancestor states. - * - path - `{string}` - returns the full path from the root down to this state. + * - **path** `{string}` - returns the full path from the root down to this state. * Needed for state activation. - * - includes - `{object}` - returns an object that includes every state that + * - **includes** `{object}` - returns an object that includes every state that * would pass a '$state.includes()' test. * * @example @@ -1459,61 +1570,114 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ * @description * Registers a state configuration under a given state name. The stateConfig object * has the following acceptable properties. - * - * - [`template`, `templateUrl`, `templateProvider`] - There are three ways to setup - * your templates. * - * - `{string|object}` - template - String HTML content, or function that returns an HTML - * string. - * - `{string}` - templateUrl - String URL path to template file OR function, - * that returns URL path string. - * - `{object}` - templateProvider - Provider function that returns HTML content + * + * + * - **`template`** - {string|function=} - html template as a string or a function that returns + * an html template as a string which should be used by the uiView directives. This property + * takes precedence over templateUrl. + * + * If `template` is a function, it will be called with the following parameters: + * + * - {array.<object>} - state parameters extracted from the current $location.path() by + * applying the current state + * + * + * + * - **`templateUrl`** - {string|function=} - path or function that returns a path to an html + * template that should be used by uiView. + * + * If `templateUrl` is a function, it will be called with the following parameters: + * + * - {array.<object>} - state parameters extracted from the current $location.path() by + * applying the current state + * + * + * + * - **`templateProvider`** - {function=} - Provider function that returns HTML content * string. * - * - [`controller`, `controllerProvider`] - A controller paired to the state. You can - * either use a controller, or a controller provider. + * * - * - `{string|object}` - controller - Controller function or controller name. - * - `{object}` - controllerProvider - Injectable provider function that returns - * the actual controller or string. + * - **`controller`** - {string|function=} - Controller fn that should be associated with newly + * related scope or the name of a registered controller if passed as a string. * - * - `{object}` - resolve - A map of dependencies which should be injected into the - * controller. + * * - * - `{string}` - url - A url with optional parameters. When a state is navigated or + * - **`controllerProvider`** - {function=} - Injectable provider function that returns + * the actual controller or string. + * + * + * + * - **`controllerAs`** – {string=} – A controller alias name. If present the controller will be + * published to scope under the controllerAs name. + * + * + * + * - **`resolve`** - {object.<string, function>=} - An optional map of dependencies which + * should be injected into the controller. If any of these dependencies are promises, + * the router will wait for them all to be resolved or one to be rejected before the + * controller is instantiated. If all the promises are resolved successfully, the values + * of the resolved promises are injected and $stateChangeSuccess event is fired. If any + * of the promises are rejected the $stateChangeError event is fired. The map object is: + * + * - key - {string}: name of dependency to be injected into controller + * - factory - {string|function}: If string then it is alias for service. Otherwise if function, + * it is injected and return value it treated as dependency. If result is a promise, it is + * resolved before its value is injected into controller. + * + * + * + * - **`url`** - {string=} - A url with optional parameters. When a state is navigated or * transitioned to, the `$stateParams` service will be populated with any * parameters that were passed. * - * - `{object}` - params - An array of parameter names or regular expressions. Only + * + * + * - **`params`** - {object=} - An array of parameter names or regular expressions. Only * use this within a state if you are not using url. Otherwise you can specify your * parameters within the url. When a state is navigated or transitioned to, the * $stateParams service will be populated with any parameters that were passed. * - * - `{object}` - views - Use the views property to set up multiple views. - * If you don't need multiple views within a single state this property is not - * needed. Tip: remember that often nested views are more useful and powerful - * than multiple sibling views. + * + * + * - **`views`** - {object=} - Use the views property to set up multiple views or to target views + * manually/explicitly. * - * - `{boolean}` - abstract - An abstract state will never be directly activated, + * + * + * - **`abstract`** - {boolean=} - An abstract state will never be directly activated, * but can provide inherited properties to its common children states. * - * - `{object}` - onEnter - Callback function for when a state is entered. Good way + * + * + * - **`onEnter`** - {object=} - Callback function for when a state is entered. Good way * to trigger an action or dispatch an event, such as opening a dialog. * - * - `{object}` - onExit - Callback function for when a state is exited. Good way to + * + * + * - **`onExit`** - {object=} - Callback function for when a state is exited. Good way to * trigger an action or dispatch an event, such as opening a dialog. * - * - `{object}` - data - Arbitrary data object, useful for custom configuration. + * + * + * - **`reloadOnSearch = true`** - {boolean=} - If `false`, will not retrigger the same state + * just because a search/query parameter has changed (via $location.search() or $location.hash()). + * Useful for when you'd like to modify $location.search() without triggering a reload. + * + * + * + * - **`data`** - {object=} - Arbitrary data object, useful for custom configuration. * * @example *
-   * // The state() method takes a unique stateName (String) and a stateConfig (Object)
-   * $stateProvider.state(stateName, stateConfig);
+   * // Some state name examples
    *
    * // stateName can be a single top-level name (must be unique).
    * $stateProvider.state("home", {});
    *
-   * // Or it can be a nested state name. This state is a child of the above "home" state.
+   * // Or it can be a nested state name. This state is a child of the 
+   * // above "home" state.
    * $stateProvider.state("home.newest", {});
    *
    * // Nest states as deeply as needed.
@@ -1528,7 +1692,7 @@ function $StateProvider(   $urlRouterProvider,   $urlMatcherFactory,           $
    *
    * @param {string} name A unique state name, e.g. "home", "about", "contacts". 
    * To create a parent/child state use a dot, e.g. "about.sales", "home.newest".
-   * @param {object} definition State configuratino object.
+   * @param {object} definition State configuration object.
    */
   this.state = state;
   function state(name, definition) {
@@ -1564,14 +1728,15 @@ function $StateProvider(   $urlRouterProvider,   $urlMatcherFactory,           $
    */
   // $urlRouter is injected just to ensure it gets instantiated
   this.$get = $get;
-  $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$location', '$urlRouter'];
-  function $get(   $rootScope,   $q,   $view,   $injector,   $resolve,   $stateParams,   $location,   $urlRouter) {
+  $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$location', '$urlRouter', '$browser'];
+  function $get(   $rootScope,   $q,   $view,   $injector,   $resolve,   $stateParams,   $location,   $urlRouter,   $browser) {
 
     var TransitionSuperseded = $q.reject(new Error('transition superseded'));
     var TransitionPrevented = $q.reject(new Error('transition prevented'));
     var TransitionAborted = $q.reject(new Error('transition aborted'));
     var TransitionFailed = $q.reject(new Error('transition failed'));
     var currentLocation = $location.url();
+    var baseHref = $browser.baseHref();
 
     function syncUrl() {
       if ($location.url() !== currentLocation) {
@@ -1594,14 +1759,24 @@ function $StateProvider(   $urlRouterProvider,   $urlMatcherFactory,           $
      * @methodOf ui.router.state.$state
      *
      * @description
-     * Reloads the current state by re-transitioning to it.
+     * A method that force reloads the current state. All resolves are re-resolved, events are not re-fired, 
+     * and controllers reinstantiated (bug with controllers reinstantiating right now, fixing soon).
      *
      * @example
      * 
-     * var app angular.module('app', ['ui.router.state']);
+     * var app angular.module('app', ['ui.router']);
      *
-     * app.controller('ctrl', function ($state) {
-     *   $state.reload();
+     * app.controller('ctrl', function ($scope, $state) {
+     *   $scope.reload = function(){
+     *     $state.reload();
+     *   }
+     * });
+     * 
+ * + * `reload()` is just an alias for: + *
+     * $state.transitionTo($state.current, $stateParams, { 
+     *   reload: true, inherit: false, notify: false 
      * });
      * 
*/ @@ -1620,18 +1795,11 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ * `{ location: true, inherit: true, relative: $state.$current, notify: true }`. * This allows you to easily use an absolute or relative to path and specify * only the parameters you'd like to update (while letting unspecified parameters - * inherit from the current state. - * - * Some examples: - * - * - `$state.go('contact.detail')` - will go to the `contact.detail` state - * - `$state.go('^')` - will go to a parent state - * - `$state.go('^.sibling')` - will go to a sibling state - * - `$state.go('.child.grandchild')` - will go to grandchild state + * inherit from the currently active ancestor states). * * @example *
-     * var app = angular.module('app', ['ui.router.state']);
+     * var app = angular.module('app', ['ui.router']);
      *
      * app.controller('ctrl', function ($scope, $state) {
      *   $scope.changeState = function () {
@@ -1639,11 +1807,48 @@ function $StateProvider(   $urlRouterProvider,   $urlMatcherFactory,           $
      *   };
      * });
      * 
+ * + * + * @param {string} to Absolute state name or relative state path. Some examples: + * + * - `$state.go('contact.detail')` - will go to the `contact.detail` state + * - `$state.go('^')` - will go to a parent state + * - `$state.go('^.sibling')` - will go to a sibling state + * - `$state.go('.child.grandchild')` - will go to grandchild state + * + * @param {object=} params A map of the parameters that will be sent to the state, + * will populate $stateParams. Any parameters that are not specified will be inherited from currently + * defined parameters. This allows, for example, going to a sibling state that shares parameters + * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e. + * transitioning to a sibling will get you the parameters for all parents, transitioning to a child + * will get you all current parameters, etc. + * @param {object=} options Options object. The options are: + * + * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false` + * will not. If string, must be `"replace"`, which will update url and also replace last history record. + * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url. + * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'), + * defines which state to be relative from. + * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events. + * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params + * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd + * use this when you want to force a reload when *everything* is the same, including search params. + * + * @returns {promise} A promise representing the state of the new transition. + * + * Possible success values: + * + * - $state.current + * + *
Possible rejection values: + * + * - 'transition superseded' - when a newer transition has been started after this one + * - 'transition prevented' - when `event.preventDefault()` has been called in a `$stateChangeStart` listener + * - 'transition aborted' - when `event.preventDefault()` has been called in a `$stateNotFound` listener or + * when a `$stateNotFound` `event.retry` promise errors. + * - 'transition failed' - when a state has been unsuccessfully found after 2 tries. + * - *resolve error* - when an error has occurred with a `resolve` * - * @param {string} to Absolute State Name or Relative State Path. - * @param {object} params A map of the parameters that will be sent to the state, - * will populate $stateParams. - * @param {object} options If Object is passed, object is an options hash. */ $state.go = function go(to, params, options) { return this.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options)); @@ -1660,7 +1865,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ * * @example *
-     * var app = angular.module('app', ['ui.router.state']);
+     * var app = angular.module('app', ['ui.router']);
      *
      * app.controller('ctrl', function ($scope, $state) {
      *   $scope.changeState = function () {
@@ -1669,10 +1874,23 @@ function $StateProvider(   $urlRouterProvider,   $urlMatcherFactory,           $
      * });
      * 
* - * @param {string} to Absolute State Name or Relative State Path. - * @param {object} params A map of the parameters that will be sent to the state, + * @param {string} to State name. + * @param {object=} toParams A map of the parameters that will be sent to the state, * will populate $stateParams. - * @param {object} options If Object is passed, object is an options hash. + * @param {object=} options Options object. The options are: + * + * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false` + * will not. If string, must be `"replace"`, which will update url and also replace last history record. + * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url. + * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'), + * defines which state to be relative from. + * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events. + * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params + * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd + * use this when you want to force a reload when *everything* is the same, including search params. + * + * @returns {promise} A promise representing the state of the new transition. See + * {@link ui.router.state.$state#methods_go $state.go}. */ $state.transitionTo = function transitionTo(to, toParams, options) { toParams = toParams || {}; @@ -1686,6 +1904,39 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ if (!isDefined(toState)) { // Broadcast not found event and abort the transition if prevented var redirect = { to: to, toParams: toParams, options: options }; + + /** + * @ngdoc event + * @name ui.router.state.$state#$stateNotFound + * @eventOf ui.router.state.$state + * @eventType broadcast on root scope + * @description + * Fired when a requested state **cannot be found** using the provided state name during transition. + * The event is broadcast allowing any handlers a single chance to deal with the error (usually by + * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler, + * you can see its three properties in the example. You can use `event.preventDefault()` to abort the + * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value. + * + * @param {Object} event Event object. + * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties. + * @param {State} fromState Current state object. + * @param {Object} fromParams Current state params. + * + * @example + * + *
+         * // somewhere, assume lazy.state has not been defined
+         * $state.go("lazy.state", {a:1, b:2}, {inherit:false});
+         *
+         * // somewhere else
+         * $scope.$on('$stateNotFound',
+         * function(event, unfoundState, fromState, fromParams){
+         *     console.log(unfoundState.to); // "lazy.state"
+         *     console.log(unfoundState.toParams); // {a:1, b:2}
+         *     console.log(unfoundState.options); // {inherit:false} + default options
+         * })
+         * 
+ */ evt = $rootScope.$broadcast('$stateNotFound', redirect, from.self, fromParams); if (evt.defaultPrevented) { syncUrl(); @@ -1751,6 +2002,33 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ // Broadcast start event and cancel the transition if requested if (options.notify) { + /** + * @ngdoc event + * @name ui.router.state.$state#$stateChangeStart + * @eventOf ui.router.state.$state + * @eventType broadcast on root scope + * @description + * Fired when the state transition **begins**. You can use `event.preventDefault()` + * to prevent the transition from happening and then the transition promise will be + * rejected with a `'transition prevented'` value. + * + * @param {Object} event Event object. + * @param {State} toState The state being transitioned to. + * @param {Object} toParams The params supplied to the `toState`. + * @param {State} fromState The current state, pre-transition. + * @param {Object} fromParams The params supplied to the `fromState`. + * + * @example + * + *
+         * $rootScope.$on('$stateChangeStart',
+         * function(event, toState, toParams, fromState, fromParams){
+         *     event.preventDefault();
+         *     // transitionTo() promise will be rejected with
+         *     // a 'transition prevented' error
+         * })
+         * 
+ */ evt = $rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams); if (evt.defaultPrevented) { syncUrl(); @@ -1819,6 +2097,20 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ } if (options.notify) { + /** + * @ngdoc event + * @name ui.router.state.$state#$stateChangeSuccess + * @eventOf ui.router.state.$state + * @eventType broadcast on root scope + * @description + * Fired once the state transition is **complete**. + * + * @param {Object} event Event object. + * @param {State} toState The state being transitioned to. + * @param {Object} toParams The params supplied to the `toState`. + * @param {State} fromState The current state, pre-transition. + * @param {Object} fromParams The params supplied to the `fromState`. + */ $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams); } currentLocation = $location.url(); @@ -1828,6 +2120,24 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ if ($state.transition !== transition) return TransitionSuperseded; $state.transition = null; + /** + * @ngdoc event + * @name ui.router.state.$state#$stateChangeError + * @eventOf ui.router.state.$state + * @eventType broadcast on root scope + * @description + * Fired when an **error occurs** during transition. It's important to note that if you + * have any errors in your resolve functions (javascript errors, non-existent services, etc) + * they will not throw traditionally. You must listen for this $stateChangeError event to + * catch **ALL** errors. + * + * @param {Object} event Event object. + * @param {State} toState The state being transitioned to. + * @param {Object} toParams The params supplied to the `toState`. + * @param {State} fromState The current state, pre-transition. + * @param {Object} fromParams The params supplied to the `fromState`. + * @param {Error} error The resolve error object. + */ $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error); syncUrl(); @@ -1857,9 +2167,9 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ *
* * @param {string|object} stateName The state name or state object you'd like to check. - * @param {object} params A param object, e.g. `{sectionId: section.id}`, that you'd like + * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like * to test against the current active state. - * @returns {boolean} Returns true or false whether its the state or not. + * @returns {boolean} Returns true if it is the state. */ $state.is = function is(stateOrName, params) { var state = findState(stateOrName); @@ -1887,6 +2197,8 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ * * @example *
+     * $state.$current.name = 'contacts.details.item';
+     *
      * $state.includes("contacts"); // returns true
      * $state.includes("contacts.details"); // returns true
      * $state.includes("contacts.details.item"); // returns true
@@ -1894,12 +2206,37 @@ function $StateProvider(   $urlRouterProvider,   $urlMatcherFactory,           $
      * $state.includes("about"); // returns false
      * 
* + * @description + * Basic globing patterns will also work. + * + * @example + *
+     * $state.$current.name = 'contacts.details.item.url';
+     *
+     * $state.includes("*.details.*.*"); // returns true
+     * $state.includes("*.details.**"); // returns true
+     * $state.includes("**.item.**"); // returns true
+     * $state.includes("*.details.item.url"); // returns true
+     * $state.includes("*.details.*.url"); // returns true
+     * $state.includes("*.details.*"); // returns false
+     * $state.includes("item.**"); // returns false
+     * 
+ * * @param {string} stateOrName A partial name to be searched for within the current state name. * @param {object} params A param object, e.g. `{sectionId: section.id}`, * that you'd like to test against the current active state. - * @returns {boolean} True or false + * @returns {boolean} Returns true if it does include the state */ + $state.includes = function includes(stateOrName, params) { + if (isString(stateOrName) && isGlob(stateOrName)) { + if (doesStateMatchGlob(stateOrName)) { + stateOrName = $state.$current.name; + } else { + return false; + } + } + var state = findState(stateOrName); if (!isDefined(state)) { return undefined; @@ -1918,6 +2255,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ return validParams; }; + /** * @ngdoc function * @name ui.router.state.$state#href @@ -1932,8 +2270,18 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ * * * @param {string|object} stateOrName The state name or state object you'd like to generate a url from. - * @param {object} params An object of parameter values to fill the state's required parameters. - * @returns {string} url + * @param {object=} params An object of parameter values to fill the state's required parameters. + * @param {object=} options Options object. The options are: + * + * - **`lossy`** - {boolean=true} - If true, and if there is no url associated with the state provided in the + * first parameter, then the constructed href url will be built from the first navigable ancestor (aka + * ancestor with a valid url). + * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url. + * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'), + * defines which state to be relative from. + * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl". + * + * @returns {string} compiled state url */ $state.href = function href(stateOrName, params, options) { options = extend({ lossy: true, inherit: false, absolute: false, relative: $state.$current }, options || {}); @@ -1946,6 +2294,15 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ if (!$locationProvider.html5Mode() && url) { url = "#" + $locationProvider.hashPrefix() + url; } + + if (baseHref !== '/') { + if ($locationProvider.html5Mode()) { + url = baseHref.slice(0, -1) + url; + } else if (options.absolute){ + url = baseHref.slice(1) + url; + } + } + if (options.absolute && url) { url = $location.protocol() + '://' + $location.host() + @@ -1962,13 +2319,11 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ * @methodOf ui.router.state.$state * * @description - * Returns the state configuration object for any state by passing the name - * as a string. Without any arguments it'll return a array of all configured - * state objects. + * Returns the state configuration object for any specific state or all states. * - * @param {string|object} stateOrName The name of the state for which you'd like - * to get the original state configuration object for. - * @returns {object} State configuration object or array of all objects. + * @param {string|object=} stateOrName If provided, will only get the config for + * the requested state. If not provided, returns an array of ALL state configs. + * @returns {object|array} State configuration object or array of all objects. */ $state.get = function (stateOrName, context) { if (!isDefined(stateOrName)) { @@ -2015,6 +2370,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ } // Provide access to the state itself for internal use result.$$state = state; + result.$$controllerAs = view.controllerAs; dst[name] = result; })); }); @@ -2078,6 +2434,29 @@ function $ViewProvider() { result = $templateFactory.fromConfig(options.view, options.params, options.locals); } if (result && options.notify) { + /** + * @ngdoc event + * @name ui.router.state.$state#$viewContentLoading + * @eventOf ui.router.state.$view + * @eventType broadcast on root scope + * @description + * + * Fired once the view **begins loading**, *before* the DOM is rendered. + * + * @param {Object} event Event object. + * @param {Object} viewConfig The view config properties (template, controller, etc). + * + * @example + * + *
+         * $scope.$on('$viewContentLoading',
+         * function(event, viewConfig){
+         *     // Access to all the view config properties.
+         *     // and one special property 'targetView'
+         *     // viewConfig.targetView
+         * });
+         * 
+ */ $rootScope.$broadcast('$viewContentLoading', options); } return result; @@ -2090,26 +2469,42 @@ angular.module('ui.router.state').provider('$view', $ViewProvider); /** * @ngdoc object - * @name ui.router.state.$uiViewScroll - * - * @requires $anchorScroll - * @requires $timeout + * @name ui.router.state.$uiViewScrollProvider * * @description - * When called with a jqLite element, it scrolls the element into view (after a - * `$timeout` so the DOM has time to refresh). - * - * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor, - * this can be enabled by calling `$uiViewScrollProvider.useAnchorScroll()`. + * Provider that returns the {@link ui.router.state.$uiViewScroll} service function. */ function $ViewScrollProvider() { var useAnchorScroll = false; + /** + * @ngdoc function + * @name ui.router.state.$uiViewScrollProvider#useAnchorScroll + * @methodOf ui.router.state.$uiViewScrollProvider + * + * @description + * Reverts back to using the core [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) service for + * scrolling based on the url anchor. + */ this.useAnchorScroll = function () { useAnchorScroll = true; }; + /** + * @ngdoc object + * @name ui.router.state.$uiViewScroll + * + * @requires $anchorScroll + * @requires $timeout + * + * @description + * When called with a jqLite element, it scrolls the element into view (after a + * `$timeout` so the DOM has time to refresh). + * + * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor, + * this can be enabled by calling {@link ui.router.state.$uiViewScrollProvider#methods_useAnchorScroll `$uiViewScrollProvider.useAnchorScroll()`}. + */ this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) { if (useAnchorScroll) { return $anchorScroll; @@ -2127,23 +2522,119 @@ angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider) /** * @ngdoc directive - * @name ui.router.state.diretive.ui-view + * @name ui.router.state.directive:ui-view * * @requires ui.router.state.$state * @requires $compile * @requires $controller * @requires $injector + * @requires ui.router.state.$uiViewScroll + * @requires $document * * @restrict ECA * * @description * The ui-view directive tells $state where to place your templates. - * A view can be unnamed or named. * - * @param {string} ui-view A view name. + * @param {string=} ui-view A view name. The name should be unique amongst the other views in the + * same state. You can have views of the same name that live in different states. + * + * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window + * when a view is populated. By default, $anchorScroll is overridden by ui-router's custom scroll + * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you + * scroll ui-view elements into view when they are populated during a state activation. + * + * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) + * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.* + * + * @param {string=} onload Expression to evaluate whenever the view updates. + * + * @example + * A view can be unnamed or named. + *
+ * 
+ * 
+ * + * + *
+ *
+ * + * You can only have one unnamed view within any template (or root html). If you are only using a + * single view and it is unnamed then you can populate it like so: + *
+ * 
+ * $stateProvider.state("home", { + * template: "

HELLO!

" + * }) + *
+ * + * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#views `views`} + * config property, by name, in this case an empty name: + *
+ * $stateProvider.state("home", {
+ *   views: {
+ *     "": {
+ *       template: "

HELLO!

" + * } + * } + * }) + *
+ * + * But typically you'll only use the views property if you name your view or have more than one view + * in the same template. There's not really a compelling reason to name a view if its the only one, + * but you could if you wanted, like so: + *
+ * 
+ *
+ *
+ * $stateProvider.state("home", {
+ *   views: {
+ *     "main": {
+ *       template: "

HELLO!

" + * } + * } + * }) + *
+ * + * Really though, you'll use views to set up multiple views: + *
+ * 
+ *
+ *
+ *
+ * + *
+ * $stateProvider.state("home", {
+ *   views: {
+ *     "": {
+ *       template: "

HELLO!

" + * }, + * "chart": { + * template: "" + * }, + * "data": { + * template: "" + * } + * } + * }) + *
+ * + * Examples for `autoscroll`: + * + *
+ * 
+ * 
+ *
+ * 
+ * 
+ * 
+ * 
+ * 
*/ -$ViewDirective.$inject = ['$state', '$compile', '$controller', '$injector', '$uiViewScroll', '$document']; -function $ViewDirective( $state, $compile, $controller, $injector, $uiViewScroll, $document) { +$ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll']; +function $ViewDirective( $state, $injector, $uiViewScroll) { function getService() { return ($injector.has) ? function(service) { @@ -2157,153 +2648,168 @@ function $ViewDirective( $state, $compile, $controller, $injector, $ui }; } - var viewIsUpdating = false, - service = getService(), + var service = getService(), $animator = service('$animator'), $animate = service('$animate'); - // Returns a set of DOM manipulation functions based on whether animation - // should be performed - function getRenderer(element, attrs, scope) { + // Returns a set of DOM manipulation functions based on which Angular version + // it should use + function getRenderer(attrs, scope) { var statics = function() { return { - leave: function (element) { element.remove(); }, - enter: function (element, parent, anchor) { anchor.after(element); } + enter: function (element, target, cb) { target.after(element); cb(); }, + leave: function (element, cb) { element.remove(); cb(); } }; }; if ($animate) { - return function(shouldAnimate) { - return !shouldAnimate ? statics() : { - enter: function(element, parent, anchor) { $animate.enter(element, null, anchor); }, - leave: function(element) { $animate.leave(element, function() { element.remove(); }); } - }; + return { + enter: function(element, target, cb) { $animate.enter(element, null, target, cb); }, + leave: function(element, cb) { $animate.leave(element, cb); } }; } if ($animator) { var animate = $animator && $animator(scope, attrs); - return function(shouldAnimate) { - return !shouldAnimate ? statics() : { - enter: function(element, parent, anchor) { animate.enter(element, parent); }, - leave: function(element) { animate.leave(element.contents(), element); } - }; + return { + enter: function(element, target, cb) {animate.enter(element, null, target); cb(); }, + leave: function(element, cb) { animate.leave(element); cb(); } }; } - return statics; + return statics(); } var directive = { restrict: 'ECA', - compile: function (element, attrs) { - var initial = element.html(), - isDefault = true, - anchor = angular.element($document[0].createComment(' ui-view-anchor ')), - parentEl = element.parent(); - - element.prepend(anchor); - - return function ($scope) { - var inherited = parentEl.inheritedData('$uiView'); - - var currentScope, currentEl, viewLocals, - name = attrs[directive.name] || attrs.name || '', - onloadExp = attrs.onload || '', - autoscrollExp = attrs.autoscroll, - renderer = getRenderer(element, attrs, $scope); - - if (name.indexOf('@') < 0) name = name + '@' + (inherited ? inherited.state.name : ''); - var view = { name: name, state: null }; - - var eventHook = function () { - if (viewIsUpdating) return; - viewIsUpdating = true; - - try { updateView(true); } catch (e) { - viewIsUpdating = false; - throw e; - } - viewIsUpdating = false; - }; - - $scope.$on('$stateChangeSuccess', eventHook); - $scope.$on('$viewContentLoading', eventHook); + terminal: true, + priority: 400, + transclude: 'element', + compile: function (tElement, tAttrs, $transclude) { + return function (scope, $element, attrs) { + var previousEl, currentEl, currentScope, latestLocals, + onloadExp = attrs.onload || '', + autoScrollExp = attrs.autoscroll, + renderer = getRenderer(attrs, scope); + + scope.$on('$stateChangeSuccess', function() { + updateView(false); + }); + scope.$on('$viewContentLoading', function() { + updateView(false); + }); - updateView(false); + updateView(true); function cleanupLastView() { - if (currentEl) { - renderer(true).leave(currentEl); - currentEl = null; + if (previousEl) { + previousEl.remove(); + previousEl = null; } if (currentScope) { currentScope.$destroy(); currentScope = null; } - } - function updateView(shouldAnimate) { - var locals = $state.$current && $state.$current.locals[name]; + if (currentEl) { + renderer.leave(currentEl, function() { + previousEl = null; + }); - if (isDefault) { - isDefault = false; - element.replaceWith(anchor); + previousEl = currentEl; + currentEl = null; } + } - if (!locals) { - cleanupLastView(); - currentEl = element.clone(); - currentEl.html(initial); - renderer(shouldAnimate).enter(currentEl, parentEl, anchor); + function updateView(firstTime) { + var newScope = scope.$new(), + name = currentEl && currentEl.data('$uiViewName'), + previousLocals = name && $state.$current && $state.$current.locals[name]; - currentScope = $scope.$new(); - $compile(currentEl.contents())(currentScope); - return; - } + if (!firstTime && previousLocals === latestLocals) return; // nothing to do - if (locals === viewLocals) return; // nothing to do - - cleanupLastView(); + var clone = $transclude(newScope, function(clone) { + renderer.enter(clone, $element, function onUiViewEnter() { + if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) { + $uiViewScroll(clone); + } + }); + cleanupLastView(); + }); - currentEl = element.clone(); - currentEl.html(locals.$template ? locals.$template : initial); - renderer(true).enter(currentEl, parentEl, anchor); + latestLocals = $state.$current.locals[clone.data('$uiViewName')]; + + currentEl = clone; + currentScope = newScope; + /** + * @ngdoc event + * @name ui.router.state.directive:ui-view#$viewContentLoaded + * @eventOf ui.router.state.directive:ui-view + * @eventType emits on ui-view directive scope + * @description * + * Fired once the view is **loaded**, *after* the DOM is rendered. + * + * @param {Object} event Event object. + */ + currentScope.$emit('$viewContentLoaded'); + currentScope.$eval(onloadExp); + } + }; + } + }; - currentEl.data('$uiView', view); + return directive; +} - viewLocals = locals; - view.state = locals.$$state; +$ViewDirectiveFill.$inject = ['$compile', '$controller', '$state']; +function $ViewDirectiveFill ($compile, $controller, $state) { + return { + restrict: 'ECA', + priority: -400, + compile: function (tElement) { + var initial = tElement.html(); + return function (scope, $element, attrs) { + var name = attrs.uiView || attrs.name || '', + inherited = $element.inheritedData('$uiView'); + + if (name.indexOf('@') < 0) { + name = name + '@' + (inherited ? inherited.state.name : ''); + } - var link = $compile(currentEl.contents()); + $element.data('$uiViewName', name); - currentScope = $scope.$new(); + var current = $state.$current, + locals = current && current.locals[name]; - if (locals.$$controller) { - locals.$scope = currentScope; - var controller = $controller(locals.$$controller, locals); - currentEl.children().data('$ngControllerController', controller); - } + if (! locals) { + return; + } - link(currentScope); + $element.data('$uiView', { name: name, state: locals.$$state }); + $element.html(locals.$template ? locals.$template : initial); - currentScope.$emit('$viewContentLoaded'); - if (onloadExp) currentScope.$eval(onloadExp); + var link = $compile($element.contents()); - if (!angular.isDefined(autoscrollExp) || !autoscrollExp || $scope.$eval(autoscrollExp)) { - $uiViewScroll(currentEl); + if (locals.$$controller) { + locals.$scope = scope; + var controller = $controller(locals.$$controller, locals); + if (locals.$$controllerAs) { + scope[locals.$$controllerAs] = controller; } + $element.data('$ngControllerController', controller); + $element.children().data('$ngControllerController', controller); } + + link(scope); }; } }; - - return directive; } angular.module('ui.router.state').directive('uiView', $ViewDirective); +angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill); function parseStateRef(ref) { var parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/); @@ -2342,21 +2848,49 @@ function stateContext(el) { * to the state that the link lives in, in other words the state that loaded the * template containing the link. * + * You can specify options to pass to {@link ui.router.state.$state#go $state.go()} + * using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`, + * and `reload`. + * * @example + * Here's an example of how you'd use ui-sref and how it would compile. If you have the + * following template: *
  * Home | About
- *
+ * 
  * 
+ * 
+ * + * Then the compiled html would be (assuming Html5Mode is off): + *
+ * Home | About
+ * 
+ * 
+ *
+ * Home
  * 
* * @param {string} ui-sref 'stateName' can be any valid absolute or relative state + * @param {Object} ui-sref-opts options to pass to {@link ui.router.state.$state#go $state.go()} */ $StateRefDirective.$inject = ['$state', '$timeout']; function $StateRefDirective($state, $timeout) { + var allowedOptions = ['location', 'inherit', 'reload']; + return { restrict: 'A', require: '?^uiSrefActive', @@ -2366,11 +2900,21 @@ function $StateRefDirective($state, $timeout) { var isForm = element[0].nodeName === "FORM"; var attr = isForm ? "action" : "href", nav = true; + var options = { + relative: base + }; + var optionsOverride = scope.$eval(attrs.uiSrefOpts) || {}; + angular.forEach(allowedOptions, function(option) { + if (option in optionsOverride) { + options[option] = optionsOverride[option]; + } + }); + var update = function(newVal) { if (newVal) params = newVal; if (!nav) return; - var newHref = $state.href(ref.state, params, { relative: base }); + var newHref = $state.href(ref.state, params, options); if (uiSrefActive) { uiSrefActive.$$setStateInfo(ref.state, params); @@ -2394,10 +2938,10 @@ function $StateRefDirective($state, $timeout) { element.bind("click", function(e) { var button = e.which || e.button; - if ((button === 0 || button == 1) && !e.ctrlKey && !e.metaKey && !e.shiftKey && !element.attr('target')) { + if ( !(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || element.attr('target')) ) { // HACK: This is to allow ng-clicks to be processed before the transition is initiated: $timeout(function() { - $state.go(ref.state, params, { relative: base }); + $state.go(ref.state, params, options); }); e.preventDefault(); } @@ -2424,12 +2968,34 @@ function $StateRefDirective($state, $timeout) { * distinguishing it from the inactive menu items. * * @example + * Given the following template: + *
+ * 
+ * 
+ * + * When the app state is "app.user", and contains the state parameter "user" with value "bilbobaggins", + * the resulting HTML will appear as (note the 'active' class): *
  * 
+ * 
+ * + * The class name is interpolated **once** during the directives link time (any further changes to the + * interpolated value are ignored). + * + * Multiple classes may be specified in a space-separated format: + *
+ * 
    + *
  • + * link + *
  • *
*
*/ @@ -2479,7 +3045,7 @@ angular.module('ui.router.state') * @requires ui.router.state.$state * * @description - * Translates to {@link ui.router.state.$state#is $state.is("stateName")}. + * Translates to {@link ui.router.state.$state#methods_is $state.is("stateName")}. */ $IsStateFilter.$inject = ['$state']; function $IsStateFilter($state) { @@ -2490,12 +3056,12 @@ function $IsStateFilter($state) { /** * @ngdoc filter - * @name ui.router.state.filter:includeByState + * @name ui.router.state.filter:includedByState * * @requires ui.router.state.$state * * @description - * Translates to {@link ui.router.state.$state#includes $state.includes()}. + * Translates to {@link ui.router.state.$state#methods_includes $state.includes('fullOrPartialStateName')}. */ $IncludedByStateFilter.$inject = ['$state']; function $IncludedByStateFilter($state) { @@ -2508,7 +3074,7 @@ angular.module('ui.router.state') .filter('isState', $IsStateFilter) .filter('includedByState', $IncludedByStateFilter); -/** +/* * @ngdoc object * @name ui.router.compat.$routeProvider * @@ -2545,7 +3111,7 @@ function $RouteProvider( $stateProvider, $urlRouterProvider) { } this.when = when; - /** + /* * @ngdoc function * @name ui.router.compat.$routeProvider#when * @methodOf ui.router.compat.$routeProvider @@ -2601,7 +3167,7 @@ function $RouteProvider( $stateProvider, $urlRouterProvider) { return this; } - /** + /* * @ngdoc object * @name ui.router.compat.$route * diff --git a/release/angular-ui-router.min.js b/release/angular-ui-router.min.js index b3e0d9bb9..f065ecc96 100644 --- a/release/angular-ui-router.min.js +++ b/release/angular-ui-router.min.js @@ -1,7 +1,7 @@ /** * State-based routing for AngularJS - * @version v0.2.8 + * @version v0.2.10 * @link http://angular-ui.github.com/ * @license MIT License, http://www.opensource.org/licenses/MIT */ -"undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports="ui.router"),function(a,b,c){"use strict";function d(a,b){return H(new(H(function(){},{prototype:a})),b)}function e(a){return G(arguments,function(b){b!==a&&G(b,function(b,c){a.hasOwnProperty(c)||(a[c]=b)})}),a}function f(a,b){var c=[];for(var d in a.path){if(a.path[d]!==b.path[d])break;c.push(a.path[d])}return c}function g(a,b){if(Array.prototype.indexOf)return a.indexOf(b,Number(arguments[2])||0);var c=a.length>>>0,d=Number(arguments[2])||0;for(d=0>d?Math.ceil(d):Math.floor(d),0>d&&(d+=c);c>d;d++)if(d in a&&a[d]===b)return d;return-1}function h(a,b,c,d){var e,h=f(c,d),i={},j=[];for(var k in h)if(h[k].params&&h[k].params.length){e=h[k].params;for(var l in e)g(j,e[l])>=0||(j.push(e[l]),i[e[l]]=a[e[l]])}return H({},i,b)}function i(a,b){var c={};return G(a,function(a){var d=b[a];c[a]=null!=d?String(d):null}),c}function j(a,b,c){if(!c){c=[];for(var d in a)c.push(d)}for(var e=0;e "));if(o[c]=d,D(a))m.push(c,[function(){return b.get(a)}],h);else{var e=b.annotate(a);G(e,function(a){a!==c&&g.hasOwnProperty(a)&&k(g[a],a)}),m.push(c,a,e)}n.pop(),o[c]=f}}function l(a){return E(a)&&a.then&&a.$$promises}if(!E(g))throw new Error("'invocables' must be an object");var m=[],n=[],o={};return G(g,k),g=n=o=null,function(d,f,g){function h(){--s||(t||e(r,f.$$values),p.$$values=r,p.$$promises=!0,o.resolve(r))}function k(a){p.$$failure=a,o.reject(a)}function n(c,e,f){function i(a){l.reject(a),k(a)}function j(){if(!B(p.$$failure))try{l.resolve(b.invoke(e,g,r)),l.promise.then(function(a){r[c]=a,h()},i)}catch(a){i(a)}}var l=a.defer(),m=0;G(f,function(a){q.hasOwnProperty(a)&&!d.hasOwnProperty(a)&&(m++,q[a].then(function(b){r[a]=b,--m||j()},i))}),m||j(),q[c]=l.promise}if(l(d)&&g===c&&(g=f,f=d,d=null),d){if(!E(d))throw new Error("'locals' must be an object")}else d=i;if(f){if(!l(f))throw new Error("'parent' must be a promise returned by $resolve.resolve()")}else f=j;var o=a.defer(),p=o.promise,q=p.$$promises={},r=H({},d),s=1+m.length/3,t=!1;if(B(f.$$failure))return k(f.$$failure),p;f.$$values?(t=e(r,f.$$values),h()):(H(q,f.$$promises),f.then(h,k));for(var u=0,v=m.length;v>u;u+=3)d.hasOwnProperty(m[u])?h():n(m[u],m[u+1],m[u+2]);return p}},this.resolve=function(a,b,c,d){return this.study(a)(b,c,d)}}function m(a,b,c){this.fromConfig=function(a,b,c){return B(a.template)?this.fromString(a.template,b):B(a.templateUrl)?this.fromUrl(a.templateUrl,b):B(a.templateProvider)?this.fromProvider(a.templateProvider,b,c):null},this.fromString=function(a,b){return C(a)?a(b):a},this.fromUrl=function(c,d){return C(c)&&(c=c(d)),null==c?null:a.get(c,{cache:b}).then(function(a){return a.data})},this.fromProvider=function(a,b,d){return c.invoke(a,null,d||{params:b})}}function n(a){function b(b){if(!/^\w+(-+\w+)*$/.test(b))throw new Error("Invalid parameter name '"+b+"' in pattern '"+a+"'");if(f[b])throw new Error("Duplicate parameter name '"+b+"' in pattern '"+a+"'");f[b]=!0,j.push(b)}function c(a){return a.replace(/[\\\[\]\^$*+?.()|{}]/g,"\\$&")}var d,e=/([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,f={},g="^",h=0,i=this.segments=[],j=this.params=[];this.source=a;for(var k,l,m;(d=e.exec(a))&&(k=d[2]||d[3],l=d[4]||("*"==d[1]?".*":"[^/]*"),m=a.substring(h,d.index),!(m.indexOf("?")>=0));)g+=c(m)+"("+l+")",b(k),i.push(m),h=e.lastIndex;m=a.substring(h);var n=m.indexOf("?");if(n>=0){var o=this.sourceSearch=m.substring(n);m=m.substring(0,n),this.sourcePath=a.substring(0,h+n),G(o.substring(1).split(/[&?]/),b)}else this.sourcePath=a,this.sourceSearch="";g+=c(m)+"$",i.push(m),this.regexp=new RegExp(g),this.prefix=i[0]}function o(){this.compile=function(a){return new n(a)},this.isMatcher=function(a){return E(a)&&C(a.exec)&&C(a.format)&&C(a.concat)},this.$get=function(){return this}}function p(a){function b(a){var b=/^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(a.source);return null!=b?b[1].replace(/\\(.)/g,"$1"):""}function c(a,b){return a.replace(/\$(\$|\d{1,2})/,function(a,c){return b["$"===c?0:Number(c)]})}function d(a,b,c){if(!c)return!1;var d=a.invoke(b,b,{$match:c});return B(d)?d:!0}var e=[],f=null;this.rule=function(a){if(!C(a))throw new Error("'rule' must be a function");return e.push(a),this},this.otherwise=function(a){if(D(a)){var b=a;a=function(){return b}}else if(!C(a))throw new Error("'rule' must be a function");return f=a,this},this.when=function(e,f){var g,h=D(f);if(D(e)&&(e=a.compile(e)),!h&&!C(f)&&!F(f))throw new Error("invalid 'handler' in when()");var i={matcher:function(b,c){return h&&(g=a.compile(c),c=["$match",function(a){return g.format(a)}]),H(function(a,e){return d(a,c,b.exec(e.path(),e.search()))},{prefix:D(b.prefix)?b.prefix:""})},regex:function(a,e){if(a.global||a.sticky)throw new Error("when() RegExp must not be global or sticky");return h&&(g=e,e=["$match",function(a){return c(g,a)}]),H(function(b,c){return d(b,e,a.exec(c.path()))},{prefix:b(a)})}},j={matcher:a.isMatcher(e),regex:e instanceof RegExp};for(var k in j)if(j[k])return this.rule(i[k](e,f));throw new Error("invalid 'what' in when()")},this.$get=["$location","$rootScope","$injector",function(a,b,c){function d(b){function d(b){var d=b(c,a);return d?(D(d)&&a.replace().url(d),!0):!1}if(!b||!b.defaultPrevented){var g,h=e.length;for(g=0;h>g;g++)if(d(e[g]))return;f&&d(f)}}return b.$on("$locationChangeSuccess",d),{sync:function(){d()}}}]}function q(a,e,f){function g(a){return 0===a.indexOf(".")||0===a.indexOf("^")}function l(a,b){var d=D(a),e=d?a:a.name,f=g(e);if(f){if(!b)throw new Error("No reference point given for path '"+e+"'");for(var h=e.split("."),i=0,j=h.length,k=b;j>i;i++)if(""!==h[i]||0!==i){if("^"!==h[i])break;if(!k.parent)throw new Error("Path '"+e+"' not valid for state '"+b.name+"'");k=k.parent}else k=b;h=h.slice(i).join("."),e=k.name+(k.name&&h?".":"")+h}var l=u[e];return!l||!d&&(d||l!==a&&l.self!==a)?c:l}function m(a,b){v[a]||(v[a]=[]),v[a].push(b)}function n(b){b=d(b,{self:b,resolve:b.resolve||{},toString:function(){return this.name}});var c=b.name;if(!D(c)||c.indexOf("@")>=0)throw new Error("State must have a valid name");if(u.hasOwnProperty(c))throw new Error("State '"+c+"'' is already defined");var e=-1!==c.indexOf(".")?c.substring(0,c.lastIndexOf(".")):D(b.parent)?b.parent:"";if(e&&!u[e])return m(e,b.self);for(var f in x)C(x[f])&&(b[f]=x[f](b,x.$delegates[f]));if(u[c]=b,!b[w]&&b.url&&a.when(b.url,["$match","$stateParams",function(a,c){t.$current.navigable==b&&j(a,c)||t.transitionTo(b,a,{location:!1})}]),v[c])for(var g=0;g=G;d--)g=u[d],g.self.onExit&&m.invoke(g.self.onExit,g.self,g.locals.globals),g.locals=null;for(d=G;dd;d++)h[e[d]]=c[d+1];for(;f>d;d++)h[e[d]]=b[e[d]];return h},n.prototype.parameters=function(){return this.params},n.prototype.format=function(a){var b=this.segments,c=this.params;if(!a)return b.join("");var d,e,f,g=b.length-1,h=c.length,i=b[0];for(d=0;g>d;d++)f=a[c[d]],null!=f&&(i+=encodeURIComponent(f)),i+=b[d+1];for(;h>d;d++)f=a[c[d]],null!=f&&(i+=(e?"&":"?")+c[d]+"="+encodeURIComponent(f),e=!0);return i},b.module("ui.router.util").provider("$urlMatcherFactory",o),p.$inject=["$urlMatcherFactoryProvider"],b.module("ui.router.router").provider("$urlRouter",p),q.$inject=["$urlRouterProvider","$urlMatcherFactoryProvider","$locationProvider"],b.module("ui.router.state").value("$stateParams",{}).provider("$state",q),r.$inject=[],b.module("ui.router.state").provider("$view",r),b.module("ui.router.state").provider("$uiViewScroll",s),t.$inject=["$state","$compile","$controller","$injector","$uiViewScroll","$document"],b.module("ui.router.state").directive("uiView",t),w.$inject=["$state","$timeout"],x.$inject=["$state","$stateParams","$interpolate"],b.module("ui.router.state").directive("uiSref",w).directive("uiSrefActive",x),y.$inject=["$state"],z.$inject=["$state"],b.module("ui.router.state").filter("isState",y).filter("includedByState",z),A.$inject=["$stateProvider","$urlRouterProvider"],b.module("ui.router.compat").provider("$route",A).directive("ngView",t)}(window,window.angular); \ No newline at end of file +"undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports="ui.router"),function(a,b,c){"use strict";function d(a,b){return I(new(I(function(){},{prototype:a})),b)}function e(a){return H(arguments,function(b){b!==a&&H(b,function(b,c){a.hasOwnProperty(c)||(a[c]=b)})}),a}function f(a,b){var c=[];for(var d in a.path){if(a.path[d]!==b.path[d])break;c.push(a.path[d])}return c}function g(a,b){if(Array.prototype.indexOf)return a.indexOf(b,Number(arguments[2])||0);var c=a.length>>>0,d=Number(arguments[2])||0;for(d=0>d?Math.ceil(d):Math.floor(d),0>d&&(d+=c);c>d;d++)if(d in a&&a[d]===b)return d;return-1}function h(a,b,c,d){var e,h=f(c,d),i={},j=[];for(var k in h)if(h[k].params&&h[k].params.length){e=h[k].params;for(var l in e)g(j,e[l])>=0||(j.push(e[l]),i[e[l]]=a[e[l]])}return I({},i,b)}function i(a,b){var c={};return H(a,function(a){var d=b[a];c[a]=null!=d?String(d):null}),c}function j(a,b,c){if(!c){c=[];for(var d in a)c.push(d)}for(var e=0;e "));if(o[c]=d,E(a))m.push(c,[function(){return b.get(a)}],h);else{var e=b.annotate(a);H(e,function(a){a!==c&&g.hasOwnProperty(a)&&k(g[a],a)}),m.push(c,a,e)}n.pop(),o[c]=f}}function l(a){return F(a)&&a.then&&a.$$promises}if(!F(g))throw new Error("'invocables' must be an object");var m=[],n=[],o={};return H(g,k),g=n=o=null,function(d,f,g){function h(){--s||(t||e(r,f.$$values),p.$$values=r,p.$$promises=!0,o.resolve(r))}function k(a){p.$$failure=a,o.reject(a)}function n(c,e,f){function i(a){l.reject(a),k(a)}function j(){if(!C(p.$$failure))try{l.resolve(b.invoke(e,g,r)),l.promise.then(function(a){r[c]=a,h()},i)}catch(a){i(a)}}var l=a.defer(),m=0;H(f,function(a){q.hasOwnProperty(a)&&!d.hasOwnProperty(a)&&(m++,q[a].then(function(b){r[a]=b,--m||j()},i))}),m||j(),q[c]=l.promise}if(l(d)&&g===c&&(g=f,f=d,d=null),d){if(!F(d))throw new Error("'locals' must be an object")}else d=i;if(f){if(!l(f))throw new Error("'parent' must be a promise returned by $resolve.resolve()")}else f=j;var o=a.defer(),p=o.promise,q=p.$$promises={},r=I({},d),s=1+m.length/3,t=!1;if(C(f.$$failure))return k(f.$$failure),p;f.$$values?(t=e(r,f.$$values),h()):(I(q,f.$$promises),f.then(h,k));for(var u=0,v=m.length;v>u;u+=3)d.hasOwnProperty(m[u])?h():n(m[u],m[u+1],m[u+2]);return p}},this.resolve=function(a,b,c,d){return this.study(a)(b,c,d)}}function m(a,b,c){this.fromConfig=function(a,b,c){return C(a.template)?this.fromString(a.template,b):C(a.templateUrl)?this.fromUrl(a.templateUrl,b):C(a.templateProvider)?this.fromProvider(a.templateProvider,b,c):null},this.fromString=function(a,b){return D(a)?a(b):a},this.fromUrl=function(c,d){return D(c)&&(c=c(d)),null==c?null:a.get(c,{cache:b}).then(function(a){return a.data})},this.fromProvider=function(a,b,d){return c.invoke(a,null,d||{params:b})}}function n(a){function b(b){if(!/^\w+(-+\w+)*$/.test(b))throw new Error("Invalid parameter name '"+b+"' in pattern '"+a+"'");if(f[b])throw new Error("Duplicate parameter name '"+b+"' in pattern '"+a+"'");f[b]=!0,j.push(b)}function c(a){return a.replace(/[\\\[\]\^$*+?.()|{}]/g,"\\$&")}var d,e=/([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,f={},g="^",h=0,i=this.segments=[],j=this.params=[];this.source=a;for(var k,l,m;(d=e.exec(a))&&(k=d[2]||d[3],l=d[4]||("*"==d[1]?".*":"[^/]*"),m=a.substring(h,d.index),!(m.indexOf("?")>=0));)g+=c(m)+"("+l+")",b(k),i.push(m),h=e.lastIndex;m=a.substring(h);var n=m.indexOf("?");if(n>=0){var o=this.sourceSearch=m.substring(n);m=m.substring(0,n),this.sourcePath=a.substring(0,h+n),H(o.substring(1).split(/[&?]/),b)}else this.sourcePath=a,this.sourceSearch="";g+=c(m)+"$",i.push(m),this.regexp=new RegExp(g),this.prefix=i[0]}function o(){this.compile=function(a){return new n(a)},this.isMatcher=function(a){return F(a)&&D(a.exec)&&D(a.format)&&D(a.concat)},this.$get=function(){return this}}function p(a){function b(a){var b=/^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(a.source);return null!=b?b[1].replace(/\\(.)/g,"$1"):""}function c(a,b){return a.replace(/\$(\$|\d{1,2})/,function(a,c){return b["$"===c?0:Number(c)]})}function d(a,b,c){if(!c)return!1;var d=a.invoke(b,b,{$match:c});return C(d)?d:!0}var e=[],f=null;this.rule=function(a){if(!D(a))throw new Error("'rule' must be a function");return e.push(a),this},this.otherwise=function(a){if(E(a)){var b=a;a=function(){return b}}else if(!D(a))throw new Error("'rule' must be a function");return f=a,this},this.when=function(e,f){var g,h=E(f);if(E(e)&&(e=a.compile(e)),!h&&!D(f)&&!G(f))throw new Error("invalid 'handler' in when()");var i={matcher:function(b,c){return h&&(g=a.compile(c),c=["$match",function(a){return g.format(a)}]),I(function(a,e){return d(a,c,b.exec(e.path(),e.search()))},{prefix:E(b.prefix)?b.prefix:""})},regex:function(a,e){if(a.global||a.sticky)throw new Error("when() RegExp must not be global or sticky");return h&&(g=e,e=["$match",function(a){return c(g,a)}]),I(function(b,c){return d(b,e,a.exec(c.path()))},{prefix:b(a)})}},j={matcher:a.isMatcher(e),regex:e instanceof RegExp};for(var k in j)if(j[k])return this.rule(i[k](e,f));throw new Error("invalid 'what' in when()")},this.$get=["$location","$rootScope","$injector",function(a,b,c){function d(b){function d(b){var d=b(c,a);return d?(E(d)&&a.replace().url(d),!0):!1}if(!b||!b.defaultPrevented){var g,h=e.length;for(g=0;h>g;g++)if(d(e[g]))return;f&&d(f)}}return b.$on("$locationChangeSuccess",d),{sync:function(){d()}}}]}function q(a,e,f){function g(a){return 0===a.indexOf(".")||0===a.indexOf("^")}function l(a,b){var d=E(a),e=d?a:a.name,f=g(e);if(f){if(!b)throw new Error("No reference point given for path '"+e+"'");for(var h=e.split("."),i=0,j=h.length,k=b;j>i;i++)if(""!==h[i]||0!==i){if("^"!==h[i])break;if(!k.parent)throw new Error("Path '"+e+"' not valid for state '"+b.name+"'");k=k.parent}else k=b;h=h.slice(i).join("."),e=k.name+(k.name&&h?".":"")+h}var l=w[e];return!l||!d&&(d||l!==a&&l.self!==a)?c:l}function m(a,b){x[a]||(x[a]=[]),x[a].push(b)}function n(b){b=d(b,{self:b,resolve:b.resolve||{},toString:function(){return this.name}});var c=b.name;if(!E(c)||c.indexOf("@")>=0)throw new Error("State must have a valid name");if(w.hasOwnProperty(c))throw new Error("State '"+c+"'' is already defined");var e=-1!==c.indexOf(".")?c.substring(0,c.lastIndexOf(".")):E(b.parent)?b.parent:"";if(e&&!w[e])return m(e,b.self);for(var f in z)D(z[f])&&(b[f]=z[f](b,z.$delegates[f]));if(w[c]=b,!b[y]&&b.url&&a.when(b.url,["$match","$stateParams",function(a,c){v.$current.navigable==b&&j(a,c)||v.transitionTo(b,a,{location:!1})}]),x[c])for(var g=0;g-1}function p(a){var b=a.split("."),c=v.$current.name.split(".");if("**"===b[0]&&(c=c.slice(c.indexOf(b[1])),c.unshift("**")),"**"===b[b.length-1]&&(c.splice(c.indexOf(b[b.length-2])+1,Number.MAX_VALUE),c.push("**")),b.length!=c.length)return!1;for(var d=0,e=b.length;e>d;d++)"*"===b[d]&&(c[d]="*");return c.join("")===b.join("")}function q(a,b){return E(a)&&!C(b)?z[a]:D(b)&&E(a)?(z[a]&&!z.$delegates[a]&&(z.$delegates[a]=z[a]),z[a]=b,this):this}function r(a,b){return F(a)?b=a:b.name=a,n(b),this}function s(a,e,g,m,n,q,r,s,x){function z(){r.url()!==M&&(r.url(M),r.replace())}function A(a,c,d,f,h){var i=d?c:k(a.params,c),j={$stateParams:i};h.resolve=n.resolve(a.resolve,j,h.resolve,a);var l=[h.resolve.then(function(a){h.globals=a})];return f&&l.push(f),H(a.views,function(c,d){var e=c.resolve&&c.resolve!==a.resolve?c.resolve:{};e.$template=[function(){return g.load(d,{view:c,locals:j,params:i,notify:!1})||""}],l.push(n.resolve(e,j,h.resolve,a).then(function(f){if(D(c.controllerProvider)||G(c.controllerProvider)){var g=b.extend({},e,j);f.$$controller=m.invoke(c.controllerProvider,null,g)}else f.$$controller=c.controller;f.$$state=a,f.$$controllerAs=c.controllerAs,h[d]=f}))}),e.all(l).then(function(){return h})}var B=e.reject(new Error("transition superseded")),F=e.reject(new Error("transition prevented")),K=e.reject(new Error("transition aborted")),L=e.reject(new Error("transition failed")),M=r.url(),N=x.baseHref();return u.locals={resolve:null,globals:{$stateParams:{}}},v={params:{},current:u.self,$current:u,transition:null},v.reload=function(){v.transitionTo(v.current,q,{reload:!0,inherit:!1,notify:!1})},v.go=function(a,b,c){return this.transitionTo(a,b,I({inherit:!0,relative:v.$current},c))},v.transitionTo=function(b,c,f){c=c||{},f=I({location:!0,inherit:!1,relative:null,notify:!0,reload:!1,$retry:!1},f||{});var g,k=v.$current,n=v.params,o=k.path,p=l(b,f.relative);if(!C(p)){var s={to:b,toParams:c,options:f};if(g=a.$broadcast("$stateNotFound",s,k.self,n),g.defaultPrevented)return z(),K;if(g.retry){if(f.$retry)return z(),L;var w=v.transition=e.when(g.retry);return w.then(function(){return w!==v.transition?B:(s.options.$retry=!0,v.transitionTo(s.to,s.toParams,s.options))},function(){return K}),z(),w}if(b=s.to,c=s.toParams,f=s.options,p=l(b,f.relative),!C(p)){if(f.relative)throw new Error("Could not resolve '"+b+"' from state '"+f.relative+"'");throw new Error("No such state '"+b+"'")}}if(p[y])throw new Error("Cannot transition to abstract state '"+b+"'");f.inherit&&(c=h(q,c||{},v.$current,p)),b=p;var x,D,E=b.path,G=u.locals,H=[];for(x=0,D=E[x];D&&D===o[x]&&j(c,n,D.ownParams)&&!f.reload;x++,D=E[x])G=H[x]=D.locals;if(t(b,k,G,f))return b.self.reloadOnSearch!==!1&&z(),v.transition=null,e.when(v.current);if(c=i(b.params,c||{}),f.notify&&(g=a.$broadcast("$stateChangeStart",b.self,c,k.self,n),g.defaultPrevented))return z(),F;for(var N=e.when(G),O=x;O=x;d--)g=o[d],g.self.onExit&&m.invoke(g.self.onExit,g.self,g.locals.globals),g.locals=null;for(d=x;d1||b.ctrlKey||b.metaKey||b.shiftKey||f.attr("target")||(c(function(){a.go(i.state,j,o)}),b.preventDefault())})}}}function y(a,b,c){return{restrict:"A",controller:["$scope","$element","$attrs",function(d,e,f){function g(){a.$current.self===i&&h()?e.addClass(l):e.removeClass(l)}function h(){return!k||j(k,b)}var i,k,l;l=c(f.uiSrefActive||"",!1)(d),this.$$setStateInfo=function(b,c){i=a.get(b,w(e)),k=c,g()},d.$on("$stateChangeSuccess",g)}]}}function z(a){return function(b){return a.is(b)}}function A(a){return function(b){return a.includes(b)}}function B(a,b){function e(a){this.locals=a.locals.globals,this.params=this.locals.$stateParams}function f(){this.locals=null,this.params=null}function g(c,g){if(null!=g.redirectTo){var h,j=g.redirectTo;if(E(j))h=j;else{if(!D(j))throw new Error("Invalid 'redirectTo' in when()");h=function(a,b){return j(a,b.path(),b.search())}}b.when(c,h)}else a.state(d(g,{parent:null,name:"route:"+encodeURIComponent(c),url:c,onEnter:e,onExit:f}));return i.push(g),this}function h(a,b,d){function e(a){return""!==a.name?a:c}var f={routes:i,params:d,current:c};return b.$on("$stateChangeStart",function(a,c,d,f){b.$broadcast("$routeChangeStart",e(c),e(f))}),b.$on("$stateChangeSuccess",function(a,c,d,g){f.current=e(c),b.$broadcast("$routeChangeSuccess",e(c),e(g)),J(d,f.params)}),b.$on("$stateChangeError",function(a,c,d,f,g,h){b.$broadcast("$routeChangeError",e(c),e(f),h)}),f}var i=[];e.$inject=["$$state"],this.when=g,this.$get=h,h.$inject=["$state","$rootScope","$routeParams"]}var C=b.isDefined,D=b.isFunction,E=b.isString,F=b.isObject,G=b.isArray,H=b.forEach,I=b.extend,J=b.copy;b.module("ui.router.util",["ng"]),b.module("ui.router.router",["ui.router.util"]),b.module("ui.router.state",["ui.router.router","ui.router.util"]),b.module("ui.router",["ui.router.state"]),b.module("ui.router.compat",["ui.router"]),l.$inject=["$q","$injector"],b.module("ui.router.util").service("$resolve",l),m.$inject=["$http","$templateCache","$injector"],b.module("ui.router.util").service("$templateFactory",m),n.prototype.concat=function(a){return new n(this.sourcePath+a+this.sourceSearch)},n.prototype.toString=function(){return this.source},n.prototype.exec=function(a,b){var c=this.regexp.exec(a);if(!c)return null;var d,e=this.params,f=e.length,g=this.segments.length-1,h={};if(g!==c.length-1)throw new Error("Unbalanced capture group in route '"+this.source+"'");for(d=0;g>d;d++)h[e[d]]=c[d+1];for(;f>d;d++)h[e[d]]=b[e[d]];return h},n.prototype.parameters=function(){return this.params},n.prototype.format=function(a){var b=this.segments,c=this.params;if(!a)return b.join("");var d,e,f,g=b.length-1,h=c.length,i=b[0];for(d=0;g>d;d++)f=a[c[d]],null!=f&&(i+=encodeURIComponent(f)),i+=b[d+1];for(;h>d;d++)f=a[c[d]],null!=f&&(i+=(e?"&":"?")+c[d]+"="+encodeURIComponent(f),e=!0);return i},b.module("ui.router.util").provider("$urlMatcherFactory",o),p.$inject=["$urlMatcherFactoryProvider"],b.module("ui.router.router").provider("$urlRouter",p),q.$inject=["$urlRouterProvider","$urlMatcherFactoryProvider","$locationProvider"],b.module("ui.router.state").value("$stateParams",{}).provider("$state",q),r.$inject=[],b.module("ui.router.state").provider("$view",r),b.module("ui.router.state").provider("$uiViewScroll",s),t.$inject=["$state","$injector","$uiViewScroll"],u.$inject=["$compile","$controller","$state"],b.module("ui.router.state").directive("uiView",t),b.module("ui.router.state").directive("uiView",u),x.$inject=["$state","$timeout"],y.$inject=["$state","$stateParams","$interpolate"],b.module("ui.router.state").directive("uiSref",x).directive("uiSrefActive",y),z.$inject=["$state"],A.$inject=["$state"],b.module("ui.router.state").filter("isState",z).filter("includedByState",A),B.$inject=["$stateProvider","$urlRouterProvider"],b.module("ui.router.compat").provider("$route",B).directive("ngView",t)}(window,window.angular); \ No newline at end of file diff --git a/src/view.js b/src/view.js index 9ce848e3e..f19a3c569 100644 --- a/src/view.js +++ b/src/view.js @@ -43,7 +43,6 @@ function $ViewProvider() { * @eventOf ui.router.state.$view * @eventType broadcast on root scope * @description - * **Known Bug:** This doesn't appear to be working (v0.2.8). * * Fired once the view **begins loading**, *before* the DOM is rendered. *