tablo_connect 0.0.2

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