state.js 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374
  1. /**
  2. * @ngdoc object
  3. * @name ui.router.state.$stateProvider
  4. *
  5. * @requires ui.router.router.$urlRouterProvider
  6. * @requires ui.router.util.$urlMatcherFactoryProvider
  7. *
  8. * @description
  9. * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely
  10. * on state.
  11. *
  12. * A state corresponds to a "place" in the application in terms of the overall UI and
  13. * navigation. A state describes (via the controller / template / view properties) what
  14. * the UI looks like and does at that place.
  15. *
  16. * States often have things in common, and the primary way of factoring out these
  17. * commonalities in this model is via the state hierarchy, i.e. parent/child states aka
  18. * nested states.
  19. *
  20. * The `$stateProvider` provides interfaces to declare these states for your app.
  21. */
  22. $StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider'];
  23. function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
  24. var root, states = {}, $state, queue = {}, abstractKey = 'abstract';
  25. // Builds state properties from definition passed to registerState()
  26. var stateBuilder = {
  27. // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined.
  28. // state.children = [];
  29. // if (parent) parent.children.push(state);
  30. parent: function(state) {
  31. if (isDefined(state.parent) && state.parent) return findState(state.parent);
  32. // regex matches any valid composite state name
  33. // would match "contact.list" but not "contacts"
  34. var compositeName = /^(.+)\.[^.]+$/.exec(state.name);
  35. return compositeName ? findState(compositeName[1]) : root;
  36. },
  37. // inherit 'data' from parent and override by own values (if any)
  38. data: function(state) {
  39. if (state.parent && state.parent.data) {
  40. state.data = state.self.data = extend({}, state.parent.data, state.data);
  41. }
  42. return state.data;
  43. },
  44. // Build a URLMatcher if necessary, either via a relative or absolute URL
  45. url: function(state) {
  46. var url = state.url, config = { params: state.params || {} };
  47. if (isString(url)) {
  48. if (url.charAt(0) == '^') return $urlMatcherFactory.compile(url.substring(1), config);
  49. return (state.parent.navigable || root).url.concat(url, config);
  50. }
  51. if (!url || $urlMatcherFactory.isMatcher(url)) return url;
  52. throw new Error("Invalid url '" + url + "' in state '" + state + "'");
  53. },
  54. // Keep track of the closest ancestor state that has a URL (i.e. is navigable)
  55. navigable: function(state) {
  56. return state.url ? state : (state.parent ? state.parent.navigable : null);
  57. },
  58. // Own parameters for this state. state.url.params is already built at this point. Create and add non-url params
  59. ownParams: function(state) {
  60. var params = state.url && state.url.params || new $$UMFP.ParamSet();
  61. forEach(state.params || {}, function(config, id) {
  62. if (!params[id]) params[id] = new $$UMFP.Param(id, null, config, "config");
  63. });
  64. return params;
  65. },
  66. // Derive parameters for this state and ensure they're a super-set of parent's parameters
  67. params: function(state) {
  68. return state.parent && state.parent.params ? extend(state.parent.params.$$new(), state.ownParams) : new $$UMFP.ParamSet();
  69. },
  70. // If there is no explicit multi-view configuration, make one up so we don't have
  71. // to handle both cases in the view directive later. Note that having an explicit
  72. // 'views' property will mean the default unnamed view properties are ignored. This
  73. // is also a good time to resolve view names to absolute names, so everything is a
  74. // straight lookup at link time.
  75. views: function(state) {
  76. var views = {};
  77. forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) {
  78. if (name.indexOf('@') < 0) name += '@' + state.parent.name;
  79. views[name] = view;
  80. });
  81. return views;
  82. },
  83. // Keep a full path from the root down to this state as this is needed for state activation.
  84. path: function(state) {
  85. return state.parent ? state.parent.path.concat(state) : []; // exclude root from path
  86. },
  87. // Speed up $state.contains() as it's used a lot
  88. includes: function(state) {
  89. var includes = state.parent ? extend({}, state.parent.includes) : {};
  90. includes[state.name] = true;
  91. return includes;
  92. },
  93. $delegates: {}
  94. };
  95. function isRelative(stateName) {
  96. return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0;
  97. }
  98. function findState(stateOrName, base) {
  99. if (!stateOrName) return undefined;
  100. var isStr = isString(stateOrName),
  101. name = isStr ? stateOrName : stateOrName.name,
  102. path = isRelative(name);
  103. if (path) {
  104. if (!base) throw new Error("No reference point given for path '" + name + "'");
  105. base = findState(base);
  106. var rel = name.split("."), i = 0, pathLength = rel.length, current = base;
  107. for (; i < pathLength; i++) {
  108. if (rel[i] === "" && i === 0) {
  109. current = base;
  110. continue;
  111. }
  112. if (rel[i] === "^") {
  113. if (!current.parent) throw new Error("Path '" + name + "' not valid for state '" + base.name + "'");
  114. current = current.parent;
  115. continue;
  116. }
  117. break;
  118. }
  119. rel = rel.slice(i).join(".");
  120. name = current.name + (current.name && rel ? "." : "") + rel;
  121. }
  122. var state = states[name];
  123. if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) {
  124. return state;
  125. }
  126. return undefined;
  127. }
  128. function queueState(parentName, state) {
  129. if (!queue[parentName]) {
  130. queue[parentName] = [];
  131. }
  132. queue[parentName].push(state);
  133. }
  134. function flushQueuedChildren(parentName) {
  135. var queued = queue[parentName] || [];
  136. while(queued.length) {
  137. registerState(queued.shift());
  138. }
  139. }
  140. function registerState(state) {
  141. // Wrap a new object around the state so we can store our private details easily.
  142. state = inherit(state, {
  143. self: state,
  144. resolve: state.resolve || {},
  145. toString: function() { return this.name; }
  146. });
  147. var name = state.name;
  148. if (!isString(name) || name.indexOf('@') >= 0) throw new Error("State must have a valid name");
  149. if (states.hasOwnProperty(name)) throw new Error("State '" + name + "'' is already defined");
  150. // Get parent name
  151. var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.'))
  152. : (isString(state.parent)) ? state.parent
  153. : (isObject(state.parent) && isString(state.parent.name)) ? state.parent.name
  154. : '';
  155. // If parent is not registered yet, add state to queue and register later
  156. if (parentName && !states[parentName]) {
  157. return queueState(parentName, state.self);
  158. }
  159. for (var key in stateBuilder) {
  160. if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]);
  161. }
  162. states[name] = state;
  163. // Register the state in the global state list and with $urlRouter if necessary.
  164. if (!state[abstractKey] && state.url) {
  165. $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) {
  166. if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) {
  167. $state.transitionTo(state, $match, { inherit: true, location: false });
  168. }
  169. }]);
  170. }
  171. // Register any queued children
  172. flushQueuedChildren(name);
  173. return state;
  174. }
  175. // Checks text to see if it looks like a glob.
  176. function isGlob (text) {
  177. return text.indexOf('*') > -1;
  178. }
  179. // Returns true if glob matches current $state name.
  180. function doesStateMatchGlob (glob) {
  181. var globSegments = glob.split('.'),
  182. segments = $state.$current.name.split('.');
  183. //match greedy starts
  184. if (globSegments[0] === '**') {
  185. segments = segments.slice(indexOf(segments, globSegments[1]));
  186. segments.unshift('**');
  187. }
  188. //match greedy ends
  189. if (globSegments[globSegments.length - 1] === '**') {
  190. segments.splice(indexOf(segments, globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE);
  191. segments.push('**');
  192. }
  193. if (globSegments.length != segments.length) {
  194. return false;
  195. }
  196. //match single stars
  197. for (var i = 0, l = globSegments.length; i < l; i++) {
  198. if (globSegments[i] === '*') {
  199. segments[i] = '*';
  200. }
  201. }
  202. return segments.join('') === globSegments.join('');
  203. }
  204. // Implicit root state that is always active
  205. root = registerState({
  206. name: '',
  207. url: '^',
  208. views: null,
  209. 'abstract': true
  210. });
  211. root.navigable = null;
  212. /**
  213. * @ngdoc function
  214. * @name ui.router.state.$stateProvider#decorator
  215. * @methodOf ui.router.state.$stateProvider
  216. *
  217. * @description
  218. * Allows you to extend (carefully) or override (at your own peril) the
  219. * `stateBuilder` object used internally by `$stateProvider`. This can be used
  220. * to add custom functionality to ui-router, for example inferring templateUrl
  221. * based on the state name.
  222. *
  223. * When passing only a name, it returns the current (original or decorated) builder
  224. * function that matches `name`.
  225. *
  226. * The builder functions that can be decorated are listed below. Though not all
  227. * necessarily have a good use case for decoration, that is up to you to decide.
  228. *
  229. * In addition, users can attach custom decorators, which will generate new
  230. * properties within the state's internal definition. There is currently no clear
  231. * use-case for this beyond accessing internal states (i.e. $state.$current),
  232. * however, expect this to become increasingly relevant as we introduce additional
  233. * meta-programming features.
  234. *
  235. * **Warning**: Decorators should not be interdependent because the order of
  236. * execution of the builder functions in non-deterministic. Builder functions
  237. * should only be dependent on the state definition object and super function.
  238. *
  239. *
  240. * Existing builder functions and current return values:
  241. *
  242. * - **parent** `{object}` - returns the parent state object.
  243. * - **data** `{object}` - returns state data, including any inherited data that is not
  244. * overridden by own values (if any).
  245. * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher}
  246. * or `null`.
  247. * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is
  248. * navigable).
  249. * - **params** `{object}` - returns an array of state params that are ensured to
  250. * be a super-set of parent's params.
  251. * - **views** `{object}` - returns a views object where each key is an absolute view
  252. * name (i.e. "viewName@stateName") and each value is the config object
  253. * (template, controller) for the view. Even when you don't use the views object
  254. * explicitly on a state config, one is still created for you internally.
  255. * So by decorating this builder function you have access to decorating template
  256. * and controller properties.
  257. * - **ownParams** `{object}` - returns an array of params that belong to the state,
  258. * not including any params defined by ancestor states.
  259. * - **path** `{string}` - returns the full path from the root down to this state.
  260. * Needed for state activation.
  261. * - **includes** `{object}` - returns an object that includes every state that
  262. * would pass a `$state.includes()` test.
  263. *
  264. * @example
  265. * <pre>
  266. * // Override the internal 'views' builder with a function that takes the state
  267. * // definition, and a reference to the internal function being overridden:
  268. * $stateProvider.decorator('views', function (state, parent) {
  269. * var result = {},
  270. * views = parent(state);
  271. *
  272. * angular.forEach(views, function (config, name) {
  273. * var autoName = (state.name + '.' + name).replace('.', '/');
  274. * config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
  275. * result[name] = config;
  276. * });
  277. * return result;
  278. * });
  279. *
  280. * $stateProvider.state('home', {
  281. * views: {
  282. * 'contact.list': { controller: 'ListController' },
  283. * 'contact.item': { controller: 'ItemController' }
  284. * }
  285. * });
  286. *
  287. * // ...
  288. *
  289. * $state.go('home');
  290. * // Auto-populates list and item views with /partials/home/contact/list.html,
  291. * // and /partials/home/contact/item.html, respectively.
  292. * </pre>
  293. *
  294. * @param {string} name The name of the builder function to decorate.
  295. * @param {object} func A function that is responsible for decorating the original
  296. * builder function. The function receives two parameters:
  297. *
  298. * - `{object}` - state - The state config object.
  299. * - `{object}` - super - The original builder function.
  300. *
  301. * @return {object} $stateProvider - $stateProvider instance
  302. */
  303. this.decorator = decorator;
  304. function decorator(name, func) {
  305. /*jshint validthis: true */
  306. if (isString(name) && !isDefined(func)) {
  307. return stateBuilder[name];
  308. }
  309. if (!isFunction(func) || !isString(name)) {
  310. return this;
  311. }
  312. if (stateBuilder[name] && !stateBuilder.$delegates[name]) {
  313. stateBuilder.$delegates[name] = stateBuilder[name];
  314. }
  315. stateBuilder[name] = func;
  316. return this;
  317. }
  318. /**
  319. * @ngdoc function
  320. * @name ui.router.state.$stateProvider#state
  321. * @methodOf ui.router.state.$stateProvider
  322. *
  323. * @description
  324. * Registers a state configuration under a given state name. The stateConfig object
  325. * has the following acceptable properties.
  326. *
  327. * @param {string} name A unique state name, e.g. "home", "about", "contacts".
  328. * To create a parent/child state use a dot, e.g. "about.sales", "home.newest".
  329. * @param {object} stateConfig State configuration object.
  330. * @param {string|function=} stateConfig.template
  331. * <a id='template'></a>
  332. * html template as a string or a function that returns
  333. * an html template as a string which should be used by the uiView directives. This property
  334. * takes precedence over templateUrl.
  335. *
  336. * If `template` is a function, it will be called with the following parameters:
  337. *
  338. * - {array.&lt;object&gt;} - state parameters extracted from the current $location.path() by
  339. * applying the current state
  340. *
  341. * <pre>template:
  342. * "<h1>inline template definition</h1>" +
  343. * "<div ui-view></div>"</pre>
  344. * <pre>template: function(params) {
  345. * return "<h1>generated template</h1>"; }</pre>
  346. * </div>
  347. *
  348. * @param {string|function=} stateConfig.templateUrl
  349. * <a id='templateUrl'></a>
  350. *
  351. * path or function that returns a path to an html
  352. * template that should be used by uiView.
  353. *
  354. * If `templateUrl` is a function, it will be called with the following parameters:
  355. *
  356. * - {array.&lt;object&gt;} - state parameters extracted from the current $location.path() by
  357. * applying the current state
  358. *
  359. * <pre>templateUrl: "home.html"</pre>
  360. * <pre>templateUrl: function(params) {
  361. * return myTemplates[params.pageId]; }</pre>
  362. *
  363. * @param {function=} stateConfig.templateProvider
  364. * <a id='templateProvider'></a>
  365. * Provider function that returns HTML content string.
  366. * <pre> templateProvider:
  367. * function(MyTemplateService, params) {
  368. * return MyTemplateService.getTemplate(params.pageId);
  369. * }</pre>
  370. *
  371. * @param {string|function=} stateConfig.controller
  372. * <a id='controller'></a>
  373. *
  374. * Controller fn that should be associated with newly
  375. * related scope or the name of a registered controller if passed as a string.
  376. * Optionally, the ControllerAs may be declared here.
  377. * <pre>controller: "MyRegisteredController"</pre>
  378. * <pre>controller:
  379. * "MyRegisteredController as fooCtrl"}</pre>
  380. * <pre>controller: function($scope, MyService) {
  381. * $scope.data = MyService.getData(); }</pre>
  382. *
  383. * @param {function=} stateConfig.controllerProvider
  384. * <a id='controllerProvider'></a>
  385. *
  386. * Injectable provider function that returns the actual controller or string.
  387. * <pre>controllerProvider:
  388. * function(MyResolveData) {
  389. * if (MyResolveData.foo)
  390. * return "FooCtrl"
  391. * else if (MyResolveData.bar)
  392. * return "BarCtrl";
  393. * else return function($scope) {
  394. * $scope.baz = "Qux";
  395. * }
  396. * }</pre>
  397. *
  398. * @param {string=} stateConfig.controllerAs
  399. * <a id='controllerAs'></a>
  400. *
  401. * A controller alias name. If present the controller will be
  402. * published to scope under the controllerAs name.
  403. * <pre>controllerAs: "myCtrl"</pre>
  404. *
  405. * @param {object=} stateConfig.resolve
  406. * <a id='resolve'></a>
  407. *
  408. * An optional map&lt;string, function&gt; of dependencies which
  409. * should be injected into the controller. If any of these dependencies are promises,
  410. * the router will wait for them all to be resolved before the controller is instantiated.
  411. * If all the promises are resolved successfully, the $stateChangeSuccess event is fired
  412. * and the values of the resolved promises are injected into any controllers that reference them.
  413. * If any of the promises are rejected the $stateChangeError event is fired.
  414. *
  415. * The map object is:
  416. *
  417. * - key - {string}: name of dependency to be injected into controller
  418. * - factory - {string|function}: If string then it is alias for service. Otherwise if function,
  419. * it is injected and return value it treated as dependency. If result is a promise, it is
  420. * resolved before its value is injected into controller.
  421. *
  422. * <pre>resolve: {
  423. * myResolve1:
  424. * function($http, $stateParams) {
  425. * return $http.get("/api/foos/"+stateParams.fooID);
  426. * }
  427. * }</pre>
  428. *
  429. * @param {string=} stateConfig.url
  430. * <a id='url'></a>
  431. *
  432. * A url fragment with optional parameters. When a state is navigated or
  433. * transitioned to, the `$stateParams` service will be populated with any
  434. * parameters that were passed.
  435. *
  436. * examples:
  437. * <pre>url: "/home"
  438. * url: "/users/:userid"
  439. * url: "/books/{bookid:[a-zA-Z_-]}"
  440. * url: "/books/{categoryid:int}"
  441. * url: "/books/{publishername:string}/{categoryid:int}"
  442. * url: "/messages?before&after"
  443. * url: "/messages?{before:date}&{after:date}"</pre>
  444. * url: "/messages/:mailboxid?{before:date}&{after:date}"
  445. *
  446. * @param {object=} stateConfig.views
  447. * <a id='views'></a>
  448. * an optional map&lt;string, object&gt; which defined multiple views, or targets views
  449. * manually/explicitly.
  450. *
  451. * Examples:
  452. *
  453. * Targets three named `ui-view`s in the parent state's template
  454. * <pre>views: {
  455. * header: {
  456. * controller: "headerCtrl",
  457. * templateUrl: "header.html"
  458. * }, body: {
  459. * controller: "bodyCtrl",
  460. * templateUrl: "body.html"
  461. * }, footer: {
  462. * controller: "footCtrl",
  463. * templateUrl: "footer.html"
  464. * }
  465. * }</pre>
  466. *
  467. * Targets named `ui-view="header"` from grandparent state 'top''s template, and named `ui-view="body" from parent state's template.
  468. * <pre>views: {
  469. * 'header@top': {
  470. * controller: "msgHeaderCtrl",
  471. * templateUrl: "msgHeader.html"
  472. * }, 'body': {
  473. * controller: "messagesCtrl",
  474. * templateUrl: "messages.html"
  475. * }
  476. * }</pre>
  477. *
  478. * @param {boolean=} [stateConfig.abstract=false]
  479. * <a id='abstract'></a>
  480. * An abstract state will never be directly activated,
  481. * but can provide inherited properties to its common children states.
  482. * <pre>abstract: true</pre>
  483. *
  484. * @param {function=} stateConfig.onEnter
  485. * <a id='onEnter'></a>
  486. *
  487. * Callback function for when a state is entered. Good way
  488. * to trigger an action or dispatch an event, such as opening a dialog.
  489. * If minifying your scripts, make sure to explictly annotate this function,
  490. * because it won't be automatically annotated by your build tools.
  491. *
  492. * <pre>onEnter: function(MyService, $stateParams) {
  493. * MyService.foo($stateParams.myParam);
  494. * }</pre>
  495. *
  496. * @param {function=} stateConfig.onExit
  497. * <a id='onExit'></a>
  498. *
  499. * Callback function for when a state is exited. Good way to
  500. * trigger an action or dispatch an event, such as opening a dialog.
  501. * If minifying your scripts, make sure to explictly annotate this function,
  502. * because it won't be automatically annotated by your build tools.
  503. *
  504. * <pre>onExit: function(MyService, $stateParams) {
  505. * MyService.cleanup($stateParams.myParam);
  506. * }</pre>
  507. *
  508. * @param {boolean=} [stateConfig.reloadOnSearch=true]
  509. * <a id='reloadOnSearch'></a>
  510. *
  511. * If `false`, will not retrigger the same state
  512. * just because a search/query parameter has changed (via $location.search() or $location.hash()).
  513. * Useful for when you'd like to modify $location.search() without triggering a reload.
  514. * <pre>reloadOnSearch: false</pre>
  515. *
  516. * @param {object=} stateConfig.data
  517. * <a id='data'></a>
  518. *
  519. * Arbitrary data object, useful for custom configuration. The parent state's `data` is
  520. * prototypally inherited. In other words, adding a data property to a state adds it to
  521. * the entire subtree via prototypal inheritance.
  522. *
  523. * <pre>data: {
  524. * requiredRole: 'foo'
  525. * } </pre>
  526. *
  527. * @param {object=} stateConfig.params
  528. * <a id='params'></a>
  529. *
  530. * A map which optionally configures parameters declared in the `url`, or
  531. * defines additional non-url parameters. For each parameter being
  532. * configured, add a configuration object keyed to the name of the parameter.
  533. *
  534. * Each parameter configuration object may contain the following properties:
  535. *
  536. * - ** value ** - {object|function=}: specifies the default value for this
  537. * parameter. This implicitly sets this parameter as optional.
  538. *
  539. * When UI-Router routes to a state and no value is
  540. * specified for this parameter in the URL or transition, the
  541. * default value will be used instead. If `value` is a function,
  542. * it will be injected and invoked, and the return value used.
  543. *
  544. * *Note*: `undefined` is treated as "no default value" while `null`
  545. * is treated as "the default value is `null`".
  546. *
  547. * *Shorthand*: If you only need to configure the default value of the
  548. * parameter, you may use a shorthand syntax. In the **`params`**
  549. * map, instead mapping the param name to a full parameter configuration
  550. * object, simply set map it to the default parameter value, e.g.:
  551. *
  552. * <pre>// define a parameter's default value
  553. * params: {
  554. * param1: { value: "defaultValue" }
  555. * }
  556. * // shorthand default values
  557. * params: {
  558. * param1: "defaultValue",
  559. * param2: "param2Default"
  560. * }</pre>
  561. *
  562. * - ** array ** - {boolean=}: *(default: false)* If true, the param value will be
  563. * treated as an array of values. If you specified a Type, the value will be
  564. * treated as an array of the specified Type. Note: query parameter values
  565. * default to a special `"auto"` mode.
  566. *
  567. * For query parameters in `"auto"` mode, if multiple values for a single parameter
  568. * are present in the URL (e.g.: `/foo?bar=1&bar=2&bar=3`) then the values
  569. * are mapped to an array (e.g.: `{ foo: [ '1', '2', '3' ] }`). However, if
  570. * only one value is present (e.g.: `/foo?bar=1`) then the value is treated as single
  571. * value (e.g.: `{ foo: '1' }`).
  572. *
  573. * <pre>params: {
  574. * param1: { array: true }
  575. * }</pre>
  576. *
  577. * - ** squash ** - {bool|string=}: `squash` configures how a default parameter value is represented in the URL when
  578. * the current parameter value is the same as the default value. If `squash` is not set, it uses the
  579. * configured default squash policy.
  580. * (See {@link ui.router.util.$urlMatcherFactory#methods_defaultSquashPolicy `defaultSquashPolicy()`})
  581. *
  582. * There are three squash settings:
  583. *
  584. * - false: The parameter's default value is not squashed. It is encoded and included in the URL
  585. * - true: The parameter's default value is omitted from the URL. If the parameter is preceeded and followed
  586. * by slashes in the state's `url` declaration, then one of those slashes are omitted.
  587. * This can allow for cleaner looking URLs.
  588. * - `"<arbitrary string>"`: The parameter's default value is replaced with an arbitrary placeholder of your choice.
  589. *
  590. * <pre>params: {
  591. * param1: {
  592. * value: "defaultId",
  593. * squash: true
  594. * } }
  595. * // squash "defaultValue" to "~"
  596. * params: {
  597. * param1: {
  598. * value: "defaultValue",
  599. * squash: "~"
  600. * } }
  601. * </pre>
  602. *
  603. *
  604. * @example
  605. * <pre>
  606. * // Some state name examples
  607. *
  608. * // stateName can be a single top-level name (must be unique).
  609. * $stateProvider.state("home", {});
  610. *
  611. * // Or it can be a nested state name. This state is a child of the
  612. * // above "home" state.
  613. * $stateProvider.state("home.newest", {});
  614. *
  615. * // Nest states as deeply as needed.
  616. * $stateProvider.state("home.newest.abc.xyz.inception", {});
  617. *
  618. * // state() returns $stateProvider, so you can chain state declarations.
  619. * $stateProvider
  620. * .state("home", {})
  621. * .state("about", {})
  622. * .state("contacts", {});
  623. * </pre>
  624. *
  625. */
  626. this.state = state;
  627. function state(name, definition) {
  628. /*jshint validthis: true */
  629. if (isObject(name)) definition = name;
  630. else definition.name = name;
  631. registerState(definition);
  632. return this;
  633. }
  634. /**
  635. * @ngdoc object
  636. * @name ui.router.state.$state
  637. *
  638. * @requires $rootScope
  639. * @requires $q
  640. * @requires ui.router.state.$view
  641. * @requires $injector
  642. * @requires ui.router.util.$resolve
  643. * @requires ui.router.state.$stateParams
  644. * @requires ui.router.router.$urlRouter
  645. *
  646. * @property {object} params A param object, e.g. {sectionId: section.id)}, that
  647. * you'd like to test against the current active state.
  648. * @property {object} current A reference to the state's config object. However
  649. * you passed it in. Useful for accessing custom data.
  650. * @property {object} transition Currently pending transition. A promise that'll
  651. * resolve or reject.
  652. *
  653. * @description
  654. * `$state` service is responsible for representing states as well as transitioning
  655. * between them. It also provides interfaces to ask for current state or even states
  656. * you're coming from.
  657. */
  658. this.$get = $get;
  659. $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter', '$location', '$urlMatcherFactory'];
  660. function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter, $location, $urlMatcherFactory) {
  661. var TransitionSuperseded = $q.reject(new Error('transition superseded'));
  662. var TransitionPrevented = $q.reject(new Error('transition prevented'));
  663. var TransitionAborted = $q.reject(new Error('transition aborted'));
  664. var TransitionFailed = $q.reject(new Error('transition failed'));
  665. // Handles the case where a state which is the target of a transition is not found, and the user
  666. // can optionally retry or defer the transition
  667. function handleRedirect(redirect, state, params, options) {
  668. /**
  669. * @ngdoc event
  670. * @name ui.router.state.$state#$stateNotFound
  671. * @eventOf ui.router.state.$state
  672. * @eventType broadcast on root scope
  673. * @description
  674. * Fired when a requested state **cannot be found** using the provided state name during transition.
  675. * The event is broadcast allowing any handlers a single chance to deal with the error (usually by
  676. * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler,
  677. * you can see its three properties in the example. You can use `event.preventDefault()` to abort the
  678. * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value.
  679. *
  680. * @param {Object} event Event object.
  681. * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties.
  682. * @param {State} fromState Current state object.
  683. * @param {Object} fromParams Current state params.
  684. *
  685. * @example
  686. *
  687. * <pre>
  688. * // somewhere, assume lazy.state has not been defined
  689. * $state.go("lazy.state", {a:1, b:2}, {inherit:false});
  690. *
  691. * // somewhere else
  692. * $scope.$on('$stateNotFound',
  693. * function(event, unfoundState, fromState, fromParams){
  694. * console.log(unfoundState.to); // "lazy.state"
  695. * console.log(unfoundState.toParams); // {a:1, b:2}
  696. * console.log(unfoundState.options); // {inherit:false} + default options
  697. * })
  698. * </pre>
  699. */
  700. var evt = $rootScope.$broadcast('$stateNotFound', redirect, state, params);
  701. if (evt.defaultPrevented) {
  702. $urlRouter.update();
  703. return TransitionAborted;
  704. }
  705. if (!evt.retry) {
  706. return null;
  707. }
  708. // Allow the handler to return a promise to defer state lookup retry
  709. if (options.$retry) {
  710. $urlRouter.update();
  711. return TransitionFailed;
  712. }
  713. var retryTransition = $state.transition = $q.when(evt.retry);
  714. retryTransition.then(function() {
  715. if (retryTransition !== $state.transition) return TransitionSuperseded;
  716. redirect.options.$retry = true;
  717. return $state.transitionTo(redirect.to, redirect.toParams, redirect.options);
  718. }, function() {
  719. return TransitionAborted;
  720. });
  721. $urlRouter.update();
  722. return retryTransition;
  723. }
  724. root.locals = { resolve: null, globals: { $stateParams: {} } };
  725. $state = {
  726. params: {},
  727. current: root.self,
  728. $current: root,
  729. transition: null
  730. };
  731. /**
  732. * @ngdoc function
  733. * @name ui.router.state.$state#reload
  734. * @methodOf ui.router.state.$state
  735. *
  736. * @description
  737. * A method that force reloads the current state. All resolves are re-resolved, events are not re-fired,
  738. * and controllers reinstantiated (bug with controllers reinstantiating right now, fixing soon).
  739. *
  740. * @example
  741. * <pre>
  742. * var app angular.module('app', ['ui.router']);
  743. *
  744. * app.controller('ctrl', function ($scope, $state) {
  745. * $scope.reload = function(){
  746. * $state.reload();
  747. * }
  748. * });
  749. * </pre>
  750. *
  751. * `reload()` is just an alias for:
  752. * <pre>
  753. * $state.transitionTo($state.current, $stateParams, {
  754. * reload: true, inherit: false, notify: true
  755. * });
  756. * </pre>
  757. *
  758. * @returns {promise} A promise representing the state of the new transition. See
  759. * {@link ui.router.state.$state#methods_go $state.go}.
  760. */
  761. $state.reload = function reload() {
  762. return $state.transitionTo($state.current, $stateParams, { reload: true, inherit: false, notify: true });
  763. };
  764. /**
  765. * @ngdoc function
  766. * @name ui.router.state.$state#go
  767. * @methodOf ui.router.state.$state
  768. *
  769. * @description
  770. * Convenience method for transitioning to a new state. `$state.go` calls
  771. * `$state.transitionTo` internally but automatically sets options to
  772. * `{ location: true, inherit: true, relative: $state.$current, notify: true }`.
  773. * This allows you to easily use an absolute or relative to path and specify
  774. * only the parameters you'd like to update (while letting unspecified parameters
  775. * inherit from the currently active ancestor states).
  776. *
  777. * @example
  778. * <pre>
  779. * var app = angular.module('app', ['ui.router']);
  780. *
  781. * app.controller('ctrl', function ($scope, $state) {
  782. * $scope.changeState = function () {
  783. * $state.go('contact.detail');
  784. * };
  785. * });
  786. * </pre>
  787. * <img src='../ngdoc_assets/StateGoExamples.png'/>
  788. *
  789. * @param {string} to Absolute state name or relative state path. Some examples:
  790. *
  791. * - `$state.go('contact.detail')` - will go to the `contact.detail` state
  792. * - `$state.go('^')` - will go to a parent state
  793. * - `$state.go('^.sibling')` - will go to a sibling state
  794. * - `$state.go('.child.grandchild')` - will go to grandchild state
  795. *
  796. * @param {object=} params A map of the parameters that will be sent to the state,
  797. * will populate $stateParams. Any parameters that are not specified will be inherited from currently
  798. * defined parameters. This allows, for example, going to a sibling state that shares parameters
  799. * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e.
  800. * transitioning to a sibling will get you the parameters for all parents, transitioning to a child
  801. * will get you all current parameters, etc.
  802. * @param {object=} options Options object. The options are:
  803. *
  804. * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
  805. * will not. If string, must be `"replace"`, which will update url and also replace last history record.
  806. * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
  807. * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
  808. * defines which state to be relative from.
  809. * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
  810. * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params
  811. * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
  812. * use this when you want to force a reload when *everything* is the same, including search params.
  813. *
  814. * @returns {promise} A promise representing the state of the new transition.
  815. *
  816. * Possible success values:
  817. *
  818. * - $state.current
  819. *
  820. * <br/>Possible rejection values:
  821. *
  822. * - 'transition superseded' - when a newer transition has been started after this one
  823. * - 'transition prevented' - when `event.preventDefault()` has been called in a `$stateChangeStart` listener
  824. * - 'transition aborted' - when `event.preventDefault()` has been called in a `$stateNotFound` listener or
  825. * when a `$stateNotFound` `event.retry` promise errors.
  826. * - 'transition failed' - when a state has been unsuccessfully found after 2 tries.
  827. * - *resolve error* - when an error has occurred with a `resolve`
  828. *
  829. */
  830. $state.go = function go(to, params, options) {
  831. return $state.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options));
  832. };
  833. /**
  834. * @ngdoc function
  835. * @name ui.router.state.$state#transitionTo
  836. * @methodOf ui.router.state.$state
  837. *
  838. * @description
  839. * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go}
  840. * uses `transitionTo` internally. `$state.go` is recommended in most situations.
  841. *
  842. * @example
  843. * <pre>
  844. * var app = angular.module('app', ['ui.router']);
  845. *
  846. * app.controller('ctrl', function ($scope, $state) {
  847. * $scope.changeState = function () {
  848. * $state.transitionTo('contact.detail');
  849. * };
  850. * });
  851. * </pre>
  852. *
  853. * @param {string} to State name.
  854. * @param {object=} toParams A map of the parameters that will be sent to the state,
  855. * will populate $stateParams.
  856. * @param {object=} options Options object. The options are:
  857. *
  858. * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
  859. * will not. If string, must be `"replace"`, which will update url and also replace last history record.
  860. * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url.
  861. * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'),
  862. * defines which state to be relative from.
  863. * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
  864. * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params
  865. * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
  866. * use this when you want to force a reload when *everything* is the same, including search params.
  867. *
  868. * @returns {promise} A promise representing the state of the new transition. See
  869. * {@link ui.router.state.$state#methods_go $state.go}.
  870. */
  871. $state.transitionTo = function transitionTo(to, toParams, options) {
  872. toParams = toParams || {};
  873. options = extend({
  874. location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false
  875. }, options || {});
  876. var from = $state.$current, fromParams = $state.params, fromPath = from.path;
  877. var evt, toState = findState(to, options.relative);
  878. if (!isDefined(toState)) {
  879. var redirect = { to: to, toParams: toParams, options: options };
  880. var redirectResult = handleRedirect(redirect, from.self, fromParams, options);
  881. if (redirectResult) {
  882. return redirectResult;
  883. }
  884. // Always retry once if the $stateNotFound was not prevented
  885. // (handles either redirect changed or state lazy-definition)
  886. to = redirect.to;
  887. toParams = redirect.toParams;
  888. options = redirect.options;
  889. toState = findState(to, options.relative);
  890. if (!isDefined(toState)) {
  891. if (!options.relative) throw new Error("No such state '" + to + "'");
  892. throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'");
  893. }
  894. }
  895. if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'");
  896. if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState);
  897. if (!toState.params.$$validates(toParams)) return TransitionFailed;
  898. toParams = toState.params.$$values(toParams);
  899. to = toState;
  900. var toPath = to.path;
  901. // Starting from the root of the path, keep all levels that haven't changed
  902. var keep = 0, state = toPath[keep], locals = root.locals, toLocals = [];
  903. if (!options.reload) {
  904. while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) {
  905. locals = toLocals[keep] = state.locals;
  906. keep++;
  907. state = toPath[keep];
  908. }
  909. }
  910. // If we're going to the same state and all locals are kept, we've got nothing to do.
  911. // But clear 'transition', as we still want to cancel any other pending transitions.
  912. // TODO: We may not want to bump 'transition' if we're called from a location change
  913. // that we've initiated ourselves, because we might accidentally abort a legitimate
  914. // transition initiated from code?
  915. if (shouldTriggerReload(to, from, locals, options)) {
  916. if (to.self.reloadOnSearch !== false) $urlRouter.update();
  917. $state.transition = null;
  918. return $q.when($state.current);
  919. }
  920. // Filter parameters before we pass them to event handlers etc.
  921. toParams = filterByKeys(to.params.$$keys(), toParams || {});
  922. // Broadcast start event and cancel the transition if requested
  923. if (options.notify) {
  924. /**
  925. * @ngdoc event
  926. * @name ui.router.state.$state#$stateChangeStart
  927. * @eventOf ui.router.state.$state
  928. * @eventType broadcast on root scope
  929. * @description
  930. * Fired when the state transition **begins**. You can use `event.preventDefault()`
  931. * to prevent the transition from happening and then the transition promise will be
  932. * rejected with a `'transition prevented'` value.
  933. *
  934. * @param {Object} event Event object.
  935. * @param {State} toState The state being transitioned to.
  936. * @param {Object} toParams The params supplied to the `toState`.
  937. * @param {State} fromState The current state, pre-transition.
  938. * @param {Object} fromParams The params supplied to the `fromState`.
  939. *
  940. * @example
  941. *
  942. * <pre>
  943. * $rootScope.$on('$stateChangeStart',
  944. * function(event, toState, toParams, fromState, fromParams){
  945. * event.preventDefault();
  946. * // transitionTo() promise will be rejected with
  947. * // a 'transition prevented' error
  948. * })
  949. * </pre>
  950. */
  951. if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams).defaultPrevented) {
  952. $urlRouter.update();
  953. return TransitionPrevented;
  954. }
  955. }
  956. // Resolve locals for the remaining states, but don't update any global state just
  957. // yet -- if anything fails to resolve the current state needs to remain untouched.
  958. // We also set up an inheritance chain for the locals here. This allows the view directive
  959. // to quickly look up the correct definition for each view in the current state. Even
  960. // though we create the locals object itself outside resolveState(), it is initially
  961. // empty and gets filled asynchronously. We need to keep track of the promise for the
  962. // (fully resolved) current locals, and pass this down the chain.
  963. var resolved = $q.when(locals);
  964. for (var l = keep; l < toPath.length; l++, state = toPath[l]) {
  965. locals = toLocals[l] = inherit(locals);
  966. resolved = resolveState(state, toParams, state === to, resolved, locals, options);
  967. }
  968. // Once everything is resolved, we are ready to perform the actual transition
  969. // and return a promise for the new state. We also keep track of what the
  970. // current promise is, so that we can detect overlapping transitions and
  971. // keep only the outcome of the last transition.
  972. var transition = $state.transition = resolved.then(function () {
  973. var l, entering, exiting;
  974. if ($state.transition !== transition) return TransitionSuperseded;
  975. // Exit 'from' states not kept
  976. for (l = fromPath.length - 1; l >= keep; l--) {
  977. exiting = fromPath[l];
  978. if (exiting.self.onExit) {
  979. $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals);
  980. }
  981. exiting.locals = null;
  982. }
  983. // Enter 'to' states not kept
  984. for (l = keep; l < toPath.length; l++) {
  985. entering = toPath[l];
  986. entering.locals = toLocals[l];
  987. if (entering.self.onEnter) {
  988. $injector.invoke(entering.self.onEnter, entering.self, entering.locals.globals);
  989. }
  990. }
  991. // Run it again, to catch any transitions in callbacks
  992. if ($state.transition !== transition) return TransitionSuperseded;
  993. // Update globals in $state
  994. $state.$current = to;
  995. $state.current = to.self;
  996. $state.params = toParams;
  997. copy($state.params, $stateParams);
  998. $state.transition = null;
  999. if (options.location && to.navigable) {
  1000. $urlRouter.push(to.navigable.url, to.navigable.locals.globals.$stateParams, {
  1001. $$avoidResync: true, replace: options.location === 'replace'
  1002. });
  1003. }
  1004. if (options.notify) {
  1005. /**
  1006. * @ngdoc event
  1007. * @name ui.router.state.$state#$stateChangeSuccess
  1008. * @eventOf ui.router.state.$state
  1009. * @eventType broadcast on root scope
  1010. * @description
  1011. * Fired once the state transition is **complete**.
  1012. *
  1013. * @param {Object} event Event object.
  1014. * @param {State} toState The state being transitioned to.
  1015. * @param {Object} toParams The params supplied to the `toState`.
  1016. * @param {State} fromState The current state, pre-transition.
  1017. * @param {Object} fromParams The params supplied to the `fromState`.
  1018. */
  1019. $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams);
  1020. }
  1021. $urlRouter.update(true);
  1022. return $state.current;
  1023. }, function (error) {
  1024. if ($state.transition !== transition) return TransitionSuperseded;
  1025. $state.transition = null;
  1026. /**
  1027. * @ngdoc event
  1028. * @name ui.router.state.$state#$stateChangeError
  1029. * @eventOf ui.router.state.$state
  1030. * @eventType broadcast on root scope
  1031. * @description
  1032. * Fired when an **error occurs** during transition. It's important to note that if you
  1033. * have any errors in your resolve functions (javascript errors, non-existent services, etc)
  1034. * they will not throw traditionally. You must listen for this $stateChangeError event to
  1035. * catch **ALL** errors.
  1036. *
  1037. * @param {Object} event Event object.
  1038. * @param {State} toState The state being transitioned to.
  1039. * @param {Object} toParams The params supplied to the `toState`.
  1040. * @param {State} fromState The current state, pre-transition.
  1041. * @param {Object} fromParams The params supplied to the `fromState`.
  1042. * @param {Error} error The resolve error object.
  1043. */
  1044. evt = $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error);
  1045. if (!evt.defaultPrevented) {
  1046. $urlRouter.update();
  1047. }
  1048. return $q.reject(error);
  1049. });
  1050. return transition;
  1051. };
  1052. /**
  1053. * @ngdoc function
  1054. * @name ui.router.state.$state#is
  1055. * @methodOf ui.router.state.$state
  1056. *
  1057. * @description
  1058. * Similar to {@link ui.router.state.$state#methods_includes $state.includes},
  1059. * but only checks for the full state name. If params is supplied then it will be
  1060. * tested for strict equality against the current active params object, so all params
  1061. * must match with none missing and no extras.
  1062. *
  1063. * @example
  1064. * <pre>
  1065. * $state.$current.name = 'contacts.details.item';
  1066. *
  1067. * // absolute name
  1068. * $state.is('contact.details.item'); // returns true
  1069. * $state.is(contactDetailItemStateObject); // returns true
  1070. *
  1071. * // relative name (. and ^), typically from a template
  1072. * // E.g. from the 'contacts.details' template
  1073. * <div ng-class="{highlighted: $state.is('.item')}">Item</div>
  1074. * </pre>
  1075. *
  1076. * @param {string|object} stateOrName The state name (absolute or relative) or state object you'd like to check.
  1077. * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like
  1078. * to test against the current active state.
  1079. * @param {object=} options An options object. The options are:
  1080. *
  1081. * - **`relative`** - {string|object} - If `stateOrName` is a relative state name and `options.relative` is set, .is will
  1082. * test relative to `options.relative` state (or name).
  1083. *
  1084. * @returns {boolean} Returns true if it is the state.
  1085. */
  1086. $state.is = function is(stateOrName, params, options) {
  1087. options = extend({ relative: $state.$current }, options || {});
  1088. var state = findState(stateOrName, options.relative);
  1089. if (!isDefined(state)) { return undefined; }
  1090. if ($state.$current !== state) { return false; }
  1091. return params ? equalForKeys(state.params.$$values(params), $stateParams) : true;
  1092. };
  1093. /**
  1094. * @ngdoc function
  1095. * @name ui.router.state.$state#includes
  1096. * @methodOf ui.router.state.$state
  1097. *
  1098. * @description
  1099. * A method to determine if the current active state is equal to or is the child of the
  1100. * state stateName. If any params are passed then they will be tested for a match as well.
  1101. * Not all the parameters need to be passed, just the ones you'd like to test for equality.
  1102. *
  1103. * @example
  1104. * Partial and relative names
  1105. * <pre>
  1106. * $state.$current.name = 'contacts.details.item';
  1107. *
  1108. * // Using partial names
  1109. * $state.includes("contacts"); // returns true
  1110. * $state.includes("contacts.details"); // returns true
  1111. * $state.includes("contacts.details.item"); // returns true
  1112. * $state.includes("contacts.list"); // returns false
  1113. * $state.includes("about"); // returns false
  1114. *
  1115. * // Using relative names (. and ^), typically from a template
  1116. * // E.g. from the 'contacts.details' template
  1117. * <div ng-class="{highlighted: $state.includes('.item')}">Item</div>
  1118. * </pre>
  1119. *
  1120. * Basic globbing patterns
  1121. * <pre>
  1122. * $state.$current.name = 'contacts.details.item.url';
  1123. *
  1124. * $state.includes("*.details.*.*"); // returns true
  1125. * $state.includes("*.details.**"); // returns true
  1126. * $state.includes("**.item.**"); // returns true
  1127. * $state.includes("*.details.item.url"); // returns true
  1128. * $state.includes("*.details.*.url"); // returns true
  1129. * $state.includes("*.details.*"); // returns false
  1130. * $state.includes("item.**"); // returns false
  1131. * </pre>
  1132. *
  1133. * @param {string} stateOrName A partial name, relative name, or glob pattern
  1134. * to be searched for within the current state name.
  1135. * @param {object=} params A param object, e.g. `{sectionId: section.id}`,
  1136. * that you'd like to test against the current active state.
  1137. * @param {object=} options An options object. The options are:
  1138. *
  1139. * - **`relative`** - {string|object=} - If `stateOrName` is a relative state reference and `options.relative` is set,
  1140. * .includes will test relative to `options.relative` state (or name).
  1141. *
  1142. * @returns {boolean} Returns true if it does include the state
  1143. */
  1144. $state.includes = function includes(stateOrName, params, options) {
  1145. options = extend({ relative: $state.$current }, options || {});
  1146. if (isString(stateOrName) && isGlob(stateOrName)) {
  1147. if (!doesStateMatchGlob(stateOrName)) {
  1148. return false;
  1149. }
  1150. stateOrName = $state.$current.name;
  1151. }
  1152. var state = findState(stateOrName, options.relative);
  1153. if (!isDefined(state)) { return undefined; }
  1154. if (!isDefined($state.$current.includes[state.name])) { return false; }
  1155. return params ? equalForKeys(state.params.$$values(params), $stateParams, objectKeys(params)) : true;
  1156. };
  1157. /**
  1158. * @ngdoc function
  1159. * @name ui.router.state.$state#href
  1160. * @methodOf ui.router.state.$state
  1161. *
  1162. * @description
  1163. * A url generation method that returns the compiled url for the given state populated with the given params.
  1164. *
  1165. * @example
  1166. * <pre>
  1167. * expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob");
  1168. * </pre>
  1169. *
  1170. * @param {string|object} stateOrName The state name or state object you'd like to generate a url from.
  1171. * @param {object=} params An object of parameter values to fill the state's required parameters.
  1172. * @param {object=} options Options object. The options are:
  1173. *
  1174. * - **`lossy`** - {boolean=true} - If true, and if there is no url associated with the state provided in the
  1175. * first parameter, then the constructed href url will be built from the first navigable ancestor (aka
  1176. * ancestor with a valid url).
  1177. * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
  1178. * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
  1179. * defines which state to be relative from.
  1180. * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
  1181. *
  1182. * @returns {string} compiled state url
  1183. */
  1184. $state.href = function href(stateOrName, params, options) {
  1185. options = extend({
  1186. lossy: true,
  1187. inherit: true,
  1188. absolute: false,
  1189. relative: $state.$current
  1190. }, options || {});
  1191. var state = findState(stateOrName, options.relative);
  1192. if (!isDefined(state)) return null;
  1193. if (options.inherit) params = inheritParams($stateParams, params || {}, $state.$current, state);
  1194. var nav = (state && options.lossy) ? state.navigable : state;
  1195. if (!nav || nav.url === undefined || nav.url === null) {
  1196. return null;
  1197. }
  1198. return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys(), params || {}), {
  1199. absolute: options.absolute
  1200. });
  1201. };
  1202. /**
  1203. * @ngdoc function
  1204. * @name ui.router.state.$state#get
  1205. * @methodOf ui.router.state.$state
  1206. *
  1207. * @description
  1208. * Returns the state configuration object for any specific state or all states.
  1209. *
  1210. * @param {string|object=} stateOrName (absolute or relative) If provided, will only get the config for
  1211. * the requested state. If not provided, returns an array of ALL state configs.
  1212. * @param {string|object=} context When stateOrName is a relative state reference, the state will be retrieved relative to context.
  1213. * @returns {Object|Array} State configuration object or array of all objects.
  1214. */
  1215. $state.get = function (stateOrName, context) {
  1216. if (arguments.length === 0) return map(objectKeys(states), function(name) { return states[name].self; });
  1217. var state = findState(stateOrName, context || $state.$current);
  1218. return (state && state.self) ? state.self : null;
  1219. };
  1220. function resolveState(state, params, paramsAreFiltered, inherited, dst, options) {
  1221. // Make a restricted $stateParams with only the parameters that apply to this state if
  1222. // necessary. In addition to being available to the controller and onEnter/onExit callbacks,
  1223. // we also need $stateParams to be available for any $injector calls we make during the
  1224. // dependency resolution process.
  1225. var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params);
  1226. var locals = { $stateParams: $stateParams };
  1227. // Resolve 'global' dependencies for the state, i.e. those not specific to a view.
  1228. // We're also including $stateParams in this; that way the parameters are restricted
  1229. // to the set that should be visible to the state, and are independent of when we update
  1230. // the global $state and $stateParams values.
  1231. dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);
  1232. var promises = [dst.resolve.then(function (globals) {
  1233. dst.globals = globals;
  1234. })];
  1235. if (inherited) promises.push(inherited);
  1236. // Resolve template and dependencies for all views.
  1237. forEach(state.views, function (view, name) {
  1238. var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {});
  1239. injectables.$template = [ function () {
  1240. return $view.load(name, { view: view, locals: locals, params: $stateParams, notify: options.notify }) || '';
  1241. }];
  1242. promises.push($resolve.resolve(injectables, locals, dst.resolve, state).then(function (result) {
  1243. // References to the controller (only instantiated at link time)
  1244. if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) {
  1245. var injectLocals = angular.extend({}, injectables, locals);
  1246. result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals);
  1247. } else {
  1248. result.$$controller = view.controller;
  1249. }
  1250. // Provide access to the state itself for internal use
  1251. result.$$state = state;
  1252. result.$$controllerAs = view.controllerAs;
  1253. dst[name] = result;
  1254. }));
  1255. });
  1256. // Wait for all the promises and then return the activation object
  1257. return $q.all(promises).then(function (values) {
  1258. return dst;
  1259. });
  1260. }
  1261. return $state;
  1262. }
  1263. function shouldTriggerReload(to, from, locals, options) {
  1264. if (to === from && ((locals === from.locals && !options.reload) || (to.self.reloadOnSearch === false))) {
  1265. return true;
  1266. }
  1267. }
  1268. }
  1269. angular.module('ui.router.state')
  1270. .value('$stateParams', {})
  1271. .provider('$state', $StateProvider);