praxis 0.16.1 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (399) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +2 -1
  5. data/CHANGELOG.md +41 -0
  6. data/CONTRIBUTING.md +3 -0
  7. data/lib/api_browser/Gruntfile.js +20 -4
  8. data/lib/api_browser/app/bower_components/angular-mocks/.bower.json +6 -6
  9. data/lib/api_browser/app/bower_components/angular-mocks/README.md +11 -5
  10. data/lib/api_browser/app/bower_components/angular-mocks/angular-mocks.js +475 -216
  11. data/lib/api_browser/app/bower_components/angular-mocks/bower.json +2 -2
  12. data/lib/api_browser/app/bower_components/angular-mocks/ngAnimateMock.js +2 -0
  13. data/lib/api_browser/app/bower_components/angular-mocks/ngMock.js +2 -0
  14. data/lib/api_browser/app/bower_components/angular-mocks/ngMockE2E.js +2 -0
  15. data/lib/api_browser/app/bower_components/angular-mocks/package.json +1 -1
  16. data/lib/api_browser/app/bower_components/angular-sanitize/.bower.json +8 -8
  17. data/lib/api_browser/app/bower_components/angular-sanitize/README.md +19 -5
  18. data/lib/api_browser/app/bower_components/angular-sanitize/angular-sanitize.js +186 -127
  19. data/lib/api_browser/app/bower_components/angular-sanitize/angular-sanitize.min.js +12 -10
  20. data/lib/api_browser/app/bower_components/angular-sanitize/angular-sanitize.min.js.map +3 -3
  21. data/lib/api_browser/app/bower_components/angular-sanitize/bower.json +3 -2
  22. data/lib/api_browser/app/bower_components/angular-sanitize/index.js +2 -0
  23. data/lib/api_browser/app/bower_components/angular-sanitize/package.json +26 -0
  24. data/lib/api_browser/app/bower_components/angular-ui-bootstrap-bower/.bower.json +15 -8
  25. data/lib/api_browser/app/bower_components/angular-ui-bootstrap-bower/bower.json +11 -3
  26. data/lib/api_browser/app/bower_components/angular-ui-bootstrap-bower/ui-bootstrap-csp.css +6 -0
  27. data/lib/api_browser/app/bower_components/angular-ui-bootstrap-bower/ui-bootstrap-tpls.js +1177 -453
  28. data/lib/api_browser/app/bower_components/angular-ui-bootstrap-bower/ui-bootstrap-tpls.min.js +4 -4
  29. data/lib/api_browser/app/bower_components/angular-ui-bootstrap-bower/ui-bootstrap.js +1066 -404
  30. data/lib/api_browser/app/bower_components/angular-ui-bootstrap-bower/ui-bootstrap.min.js +3 -3
  31. data/lib/api_browser/app/bower_components/angular-ui-router/.bower.json +5 -6
  32. data/lib/api_browser/app/bower_components/angular-ui-router/CHANGELOG.md +208 -3
  33. data/lib/api_browser/app/bower_components/angular-ui-router/CONTRIBUTING.md +65 -0
  34. data/lib/api_browser/app/bower_components/angular-ui-router/LICENSE +1 -1
  35. data/lib/api_browser/app/bower_components/angular-ui-router/README.md +36 -71
  36. data/lib/api_browser/app/bower_components/angular-ui-router/api/angular-ui-router.d.ts +126 -0
  37. data/lib/api_browser/app/bower_components/angular-ui-router/bower.json +1 -1
  38. data/lib/api_browser/app/bower_components/angular-ui-router/release/angular-ui-router.js +1902 -755
  39. data/lib/api_browser/app/bower_components/angular-ui-router/release/angular-ui-router.min.js +2 -2
  40. data/lib/api_browser/app/bower_components/angular-ui-router/src/common.js +69 -23
  41. data/lib/api_browser/app/bower_components/angular-ui-router/src/resolve.js +15 -5
  42. data/lib/api_browser/app/bower_components/angular-ui-router/src/state.js +556 -295
  43. data/lib/api_browser/app/bower_components/angular-ui-router/src/stateDirectives.js +101 -42
  44. data/lib/api_browser/app/bower_components/angular-ui-router/src/stateFilters.js +6 -2
  45. data/lib/api_browser/app/bower_components/angular-ui-router/src/templateFactory.js +2 -2
  46. data/lib/api_browser/app/bower_components/angular-ui-router/src/urlMatcherFactory.js +822 -97
  47. data/lib/api_browser/app/bower_components/angular-ui-router/src/urlRouter.js +274 -120
  48. data/lib/api_browser/app/bower_components/angular-ui-router/src/viewDirective.js +33 -20
  49. data/lib/api_browser/app/bower_components/angular-ui-router/src/viewScroll.js +1 -1
  50. data/lib/api_browser/app/bower_components/angular/.bower.json +5 -5
  51. data/lib/api_browser/app/bower_components/angular/README.md +2 -5
  52. data/lib/api_browser/app/bower_components/angular/angular-csp.css +5 -8
  53. data/lib/api_browser/app/bower_components/angular/angular.js +12975 -6996
  54. data/lib/api_browser/app/bower_components/angular/angular.min.js +285 -213
  55. data/lib/api_browser/app/bower_components/angular/angular.min.js.gzip +0 -0
  56. data/lib/api_browser/app/bower_components/angular/angular.min.js.map +3 -3
  57. data/lib/api_browser/app/bower_components/angular/bower.json +1 -1
  58. data/lib/api_browser/app/bower_components/angular/index.js +2 -0
  59. data/lib/api_browser/app/bower_components/angular/package.json +2 -2
  60. data/lib/api_browser/app/bower_components/bootstrap-sass/.bower.json +31 -16
  61. data/lib/api_browser/app/bower_components/bootstrap-sass/CHANGELOG.md +108 -0
  62. data/lib/api_browser/app/bower_components/bootstrap-sass/CONTRIBUTING.md +55 -37
  63. data/lib/api_browser/app/bower_components/bootstrap-sass/README.md +147 -206
  64. data/lib/api_browser/app/bower_components/bootstrap-sass/bower.json +19 -8
  65. data/lib/api_browser/app/bower_components/bootstrap-sass/{dist/fonts → vendor/assets/fonts/bootstrap}/glyphicons-halflings-regular.eot +0 -0
  66. data/lib/api_browser/app/bower_components/bootstrap-sass/{dist/fonts → vendor/assets/fonts/bootstrap}/glyphicons-halflings-regular.svg +0 -0
  67. data/lib/api_browser/app/bower_components/bootstrap-sass/{dist/fonts → vendor/assets/fonts/bootstrap}/glyphicons-halflings-regular.ttf +0 -0
  68. data/lib/api_browser/app/bower_components/bootstrap-sass/{dist/fonts → vendor/assets/fonts/bootstrap}/glyphicons-halflings-regular.woff +0 -0
  69. data/lib/api_browser/app/bower_components/bootstrap-sass/vendor/assets/javascripts/bootstrap.js +12 -0
  70. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/affix.js +1 -1
  71. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/alert.js +1 -1
  72. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/button.js +11 -5
  73. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/carousel.js +5 -5
  74. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/collapse.js +1 -1
  75. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/dropdown.js +5 -5
  76. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/modal.js +1 -1
  77. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/popover.js +1 -1
  78. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/scrollspy.js +2 -2
  79. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/tab.js +1 -1
  80. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/tooltip.js +1 -1
  81. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/transition.js +1 -1
  82. data/lib/api_browser/app/bower_components/bootstrap-sass/vendor/assets/stylesheets/bootstrap.scss +1 -0
  83. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_alerts.scss +0 -0
  84. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_badges.scss +6 -6
  85. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_breadcrumbs.scss +0 -0
  86. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_button-groups.scss +7 -33
  87. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_buttons.scss +2 -5
  88. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_carousel.scss +1 -0
  89. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_close.scss +0 -0
  90. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_code.scss +0 -0
  91. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_component-animations.scss +0 -0
  92. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_dropdowns.scss +3 -8
  93. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_forms.scss +11 -0
  94. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_glyphicons.scss +5 -5
  95. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_grid.scss +12 -26
  96. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_input-groups.scss +1 -1
  97. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_jumbotron.scss +8 -2
  98. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_labels.scss +6 -0
  99. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_list-group.scss +0 -0
  100. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_media.scss +0 -0
  101. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_mixins.scss +38 -51
  102. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_modals.scss +2 -5
  103. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_navbar.scss +41 -53
  104. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_navs.scss +0 -20
  105. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_normalize.scss +0 -0
  106. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_pager.scss +0 -0
  107. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_pagination.scss +0 -0
  108. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_panels.scss +11 -1
  109. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_popovers.scss +0 -0
  110. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_print.scss +0 -0
  111. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_progress-bars.scss +0 -12
  112. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_responsive-utilities.scss +0 -0
  113. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_scaffolding.scss +0 -0
  114. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_tables.scss +5 -18
  115. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_theme.scss +2 -2
  116. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_thumbnails.scss +9 -3
  117. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_tooltip.scss +0 -0
  118. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_type.scss +54 -52
  119. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_utilities.scss +0 -0
  120. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_variables.scss +20 -11
  121. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_wells.scss +0 -0
  122. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/bootstrap.scss +0 -0
  123. data/lib/api_browser/app/bower_components/lodash/.bower.json +9 -13
  124. data/lib/api_browser/app/bower_components/lodash/LICENSE.txt +3 -3
  125. data/lib/api_browser/app/bower_components/lodash/bower.json +4 -7
  126. data/lib/api_browser/app/bower_components/lodash/lodash.js +12235 -0
  127. data/lib/api_browser/app/bower_components/lodash/lodash.min.js +98 -0
  128. data/lib/api_browser/app/index.html +0 -1
  129. data/lib/api_browser/app/js/app.js +2 -5
  130. data/lib/api_browser/app/js/controllers/action.js +21 -37
  131. data/lib/api_browser/app/js/controllers/controller.js +23 -1
  132. data/lib/api_browser/app/js/controllers/menu.js +46 -14
  133. data/lib/api_browser/app/js/controllers/type.js +2 -9
  134. data/lib/api_browser/app/js/directives/attribute_description.js +15 -5
  135. data/lib/api_browser/app/js/directives/attribute_table.js +6 -6
  136. data/lib/api_browser/app/js/directives/fixed_if_fits.js +20 -0
  137. data/lib/api_browser/app/js/directives/no_container.js +6 -6
  138. data/lib/api_browser/app/js/directives/type_placeholder.js +21 -0
  139. data/lib/api_browser/app/js/factories/Configuration.js +13 -0
  140. data/lib/api_browser/app/js/factories/Documentation.js +0 -3
  141. data/lib/api_browser/app/js/factories/normalize_attributes.js +19 -0
  142. data/lib/api_browser/app/js/factories/template_for.js +113 -0
  143. data/lib/api_browser/app/sass/modules/_body.scss +26 -4
  144. data/lib/api_browser/app/sass/modules/_sidebar.scss +68 -1
  145. data/lib/api_browser/app/sass/praxis.scss +1 -5
  146. data/lib/api_browser/app/sass/variables/_bootstrap-variables.scss +13 -4
  147. data/lib/api_browser/app/views/action.html +13 -17
  148. data/lib/api_browser/app/views/controller.html +32 -4
  149. data/lib/api_browser/app/views/directives/attribute_description.html +1 -1
  150. data/lib/api_browser/app/views/directives/attribute_description/{_default.html → default.html} +0 -0
  151. data/lib/api_browser/app/views/directives/attribute_description/example.html +13 -0
  152. data/lib/api_browser/app/views/directives/attribute_description/headers.html +8 -0
  153. data/lib/api_browser/app/views/directives/attribute_description/member_options.html +4 -0
  154. data/lib/api_browser/app/views/directives/attribute_description/values.html +14 -0
  155. data/lib/api_browser/app/views/directives/attribute_table.html +2 -2
  156. data/lib/api_browser/app/views/home.html +1 -3
  157. data/lib/api_browser/app/views/layout.html +3 -36
  158. data/lib/api_browser/app/views/menu.html +45 -0
  159. data/lib/api_browser/app/views/navbar.html +1 -1
  160. data/lib/api_browser/app/views/type.html +2 -2
  161. data/lib/api_browser/app/views/type/{_details.html → details.html} +6 -6
  162. data/lib/api_browser/app/views/types/embedded/default.html +10 -0
  163. data/lib/api_browser/app/views/types/embedded/links.html +11 -0
  164. data/lib/api_browser/app/views/types/embedded/struct.html +2 -0
  165. data/lib/api_browser/app/views/types/label/link.html +1 -0
  166. data/lib/api_browser/app/views/types/label/primitive.html +1 -0
  167. data/lib/api_browser/app/views/types/label/primitive_collection.html +1 -0
  168. data/lib/api_browser/app/views/types/label/type.html +1 -0
  169. data/lib/api_browser/app/views/types/label/type_collection.html +1 -0
  170. data/lib/api_browser/app/views/{directives/request_body/_default.html → types/standalone/default.html} +1 -1
  171. data/lib/api_browser/app/views/types/standalone/struct.html +1 -0
  172. data/lib/api_browser/bower.json +9 -9
  173. data/lib/api_browser/package.json +1 -1
  174. data/lib/praxis.rb +10 -4
  175. data/lib/praxis/action_definition.rb +16 -4
  176. data/lib/praxis/action_definition/headers_dsl_compiler.rb +5 -2
  177. data/lib/praxis/api_definition.rb +3 -1
  178. data/lib/praxis/api_general_info.rb +49 -5
  179. data/lib/praxis/application.rb +12 -4
  180. data/lib/praxis/bootloader.rb +1 -0
  181. data/lib/praxis/bootloader_stages/environment.rb +2 -0
  182. data/lib/praxis/bootloader_stages/routing.rb +1 -1
  183. data/lib/praxis/bootloader_stages/subgroup_loader.rb +1 -0
  184. data/lib/praxis/exceptions/validation.rb +7 -0
  185. data/lib/praxis/handlers/plain.rb +16 -0
  186. data/lib/praxis/handlers/xml.rb +4 -4
  187. data/lib/praxis/links.rb +13 -3
  188. data/lib/praxis/media_type_identifier.rb +3 -0
  189. data/lib/praxis/multipart/parser.rb +41 -48
  190. data/lib/praxis/multipart/part.rb +196 -3
  191. data/lib/praxis/request.rb +14 -11
  192. data/lib/praxis/request_stages/request_stage.rb +4 -0
  193. data/lib/praxis/request_stages/response.rb +10 -9
  194. data/lib/praxis/request_stages/validate.rb +1 -7
  195. data/lib/praxis/request_stages/validate_params_and_headers.rb +30 -5
  196. data/lib/praxis/request_stages/validate_payload.rb +14 -5
  197. data/lib/praxis/resource_definition.rb +117 -15
  198. data/lib/praxis/response.rb +6 -5
  199. data/lib/praxis/response_definition.rb +51 -5
  200. data/lib/praxis/responses/http.rb +5 -0
  201. data/lib/praxis/responses/multipart_ok.rb +51 -0
  202. data/lib/praxis/responses/validation_error.rb +7 -7
  203. data/lib/praxis/restful_doc_generator.rb +9 -4
  204. data/lib/praxis/route.rb +3 -2
  205. data/lib/praxis/router.rb +26 -16
  206. data/lib/praxis/router/rack.rb +51 -0
  207. data/lib/praxis/router/simple.rb +146 -0
  208. data/lib/praxis/routing_config.rb +2 -2
  209. data/lib/praxis/trait.rb +1 -1
  210. data/lib/praxis/types/fuzzy_hash.rb +49 -0
  211. data/lib/praxis/types/media_type_common.rb +1 -1
  212. data/lib/praxis/types/multipart.rb +47 -12
  213. data/lib/praxis/types/multipart_array.rb +320 -0
  214. data/lib/praxis/types/multipart_array/part_definition.rb +52 -0
  215. data/lib/praxis/validation_handler.rb +10 -0
  216. data/lib/praxis/version.rb +2 -2
  217. data/praxis.gemspec +3 -3
  218. data/spec/api_browser/directives/type_placeholder_spec.js +134 -0
  219. data/spec/api_browser/factories/normalize_attributes_spec.js +97 -0
  220. data/spec/api_browser/factories/template_for_spec.js +67 -0
  221. data/spec/functional_spec.rb +111 -45
  222. data/spec/praxis/action_definition_spec.rb +31 -7
  223. data/spec/praxis/api_definition_spec.rb +2 -2
  224. data/spec/praxis/api_general_info_spec.rb +25 -0
  225. data/spec/praxis/application_spec.rb +24 -11
  226. data/spec/praxis/handlers/xml_spec.rb +55 -33
  227. data/spec/praxis/links_spec.rb +18 -1
  228. data/spec/praxis/media_type_collection_spec.rb +1 -1
  229. data/spec/praxis/media_type_spec.rb +2 -2
  230. data/spec/praxis/multipart/parser_spec.rb +21 -13
  231. data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +1 -1
  232. data/spec/praxis/request_spec.rb +52 -24
  233. data/spec/praxis/{request_stages_action_spec.rb → request_stages/action_spec.rb} +1 -1
  234. data/spec/praxis/{request_stage_spec.rb → request_stages/request_stage_spec.rb} +0 -0
  235. data/spec/praxis/{request_stages_validate_spec.rb → request_stages/validate_spec.rb} +1 -1
  236. data/spec/praxis/resource_definition_spec.rb +30 -4
  237. data/spec/praxis/response_definition_spec.rb +60 -19
  238. data/spec/praxis/response_spec.rb +2 -2
  239. data/spec/praxis/responses/validation_error_spec.rb +33 -16
  240. data/spec/praxis/route_spec.rb +4 -2
  241. data/spec/praxis/router_spec.rb +28 -12
  242. data/spec/praxis/routing_config_spec.rb +11 -5
  243. data/spec/praxis/types/collection_spec.rb +1 -1
  244. data/spec/praxis/types/fuzzy_hash_spec.rb +20 -0
  245. data/spec/praxis/types/multipart_array/part_definition_spec.rb +5 -0
  246. data/spec/praxis/types/multipart_array_spec.rb +334 -0
  247. data/spec/praxis/types/multipart_spec.rb +14 -5
  248. data/spec/spec_app/app/controllers/instances.rb +20 -10
  249. data/spec/spec_app/app/controllers/volumes.rb +8 -4
  250. data/spec/spec_app/app/responses/bulk_response.rb +0 -6
  251. data/spec/spec_app/config/environment.rb +13 -0
  252. data/spec/spec_app/design/api.rb +7 -10
  253. data/spec/spec_app/design/media_types/instance.rb +3 -1
  254. data/spec/spec_app/design/resources/instances.rb +50 -41
  255. data/spec/spec_app/design/resources/volume_snapshots.rb +39 -0
  256. data/spec/spec_app/design/resources/volumes.rb +11 -6
  257. data/spec/spec_helper.rb +3 -1
  258. metadata +125 -218
  259. data/lib/api_browser/app/bower_components/angular-ui-router/src/compat.js +0 -146
  260. data/lib/api_browser/app/bower_components/bootstrap-sass/CNAME +0 -1
  261. data/lib/api_browser/app/bower_components/bootstrap-sass/DOCS-LICENSE +0 -319
  262. data/lib/api_browser/app/bower_components/bootstrap-sass/Gemfile +0 -5
  263. data/lib/api_browser/app/bower_components/bootstrap-sass/Gemfile.lock +0 -14
  264. data/lib/api_browser/app/bower_components/bootstrap-sass/Gruntfile.js +0 -244
  265. data/lib/api_browser/app/bower_components/bootstrap-sass/LICENSE +0 -176
  266. data/lib/api_browser/app/bower_components/bootstrap-sass/LICENSE-MIT +0 -21
  267. data/lib/api_browser/app/bower_components/bootstrap-sass/Rakefile +0 -44
  268. data/lib/api_browser/app/bower_components/bootstrap-sass/_config.yml +0 -25
  269. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/ads.html +0 -1
  270. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/footer.html +0 -34
  271. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/header.html +0 -42
  272. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/nav-about.html +0 -12
  273. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/nav-components.html +0 -137
  274. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/nav-css.html +0 -99
  275. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/nav-customize.html +0 -40
  276. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/nav-getting-started.html +0 -44
  277. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/nav-javascript.html +0 -88
  278. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/nav-main.html +0 -37
  279. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/old-bs-docs.html +0 -8
  280. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/social-buttons.html +0 -16
  281. data/lib/api_browser/app/bower_components/bootstrap-sass/_layouts/default.html +0 -79
  282. data/lib/api_browser/app/bower_components/bootstrap-sass/_layouts/home.html +0 -47
  283. data/lib/api_browser/app/bower_components/bootstrap-sass/about.html +0 -93
  284. data/lib/api_browser/app/bower_components/bootstrap-sass/browserstack.json +0 -37
  285. data/lib/api_browser/app/bower_components/bootstrap-sass/components.html +0 -3689
  286. data/lib/api_browser/app/bower_components/bootstrap-sass/composer.json +0 -28
  287. data/lib/api_browser/app/bower_components/bootstrap-sass/css.html +0 -2674
  288. data/lib/api_browser/app/bower_components/bootstrap-sass/customize.html +0 -1715
  289. data/lib/api_browser/app/bower_components/bootstrap-sass/dist/css/bootstrap-theme.css +0 -427
  290. data/lib/api_browser/app/bower_components/bootstrap-sass/dist/css/bootstrap-theme.min.css +0 -1
  291. data/lib/api_browser/app/bower_components/bootstrap-sass/dist/css/bootstrap.css +0 -6350
  292. data/lib/api_browser/app/bower_components/bootstrap-sass/dist/css/bootstrap.min.css +0 -1
  293. data/lib/api_browser/app/bower_components/bootstrap-sass/dist/js/bootstrap.js +0 -2002
  294. data/lib/api_browser/app/bower_components/bootstrap-sass/dist/js/bootstrap.min.js +0 -9
  295. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/css/docs.css +0 -1195
  296. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/css/pygments-manni.css +0 -66
  297. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/ico/apple-touch-icon-144-precomposed.png +0 -0
  298. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/ico/favicon.png +0 -0
  299. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/js/application.js +0 -103
  300. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/js/customizer.js +0 -332
  301. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/js/filesaver.js +0 -169
  302. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/js/holder.js +0 -404
  303. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/js/ie8-responsive-file-warning.js +0 -12
  304. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/js/jszip.js +0 -1467
  305. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/js/less.js +0 -9
  306. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/js/raw-files.js +0 -3
  307. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/js/uglify.js +0 -14
  308. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/carousel/carousel.css +0 -148
  309. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/carousel/index.html +0 -206
  310. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/grid/grid.css +0 -28
  311. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/grid/index.html +0 -148
  312. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/jumbotron-narrow/index.html +0 -82
  313. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/jumbotron-narrow/jumbotron-narrow.css +0 -79
  314. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/jumbotron/index.html +0 -99
  315. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/jumbotron/jumbotron.css +0 -5
  316. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/justified-nav/index.html +0 -83
  317. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/justified-nav/justified-nav.css +0 -88
  318. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/navbar-fixed-top/index.html +0 -91
  319. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/navbar-fixed-top/navbar-fixed-top.css +0 -4
  320. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/navbar-static-top/index.html +0 -92
  321. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/navbar-static-top/navbar-static-top.css +0 -7
  322. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/navbar/index.html +0 -88
  323. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/navbar/navbar.css +0 -8
  324. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/non-responsive/index.html +0 -101
  325. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/non-responsive/non-responsive.css +0 -116
  326. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/offcanvas/index.html +0 -130
  327. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/offcanvas/offcanvas.css +0 -50
  328. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/offcanvas/offcanvas.js +0 -5
  329. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/carousel.jpg +0 -0
  330. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/grid.jpg +0 -0
  331. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/jumbotron-narrow.jpg +0 -0
  332. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/jumbotron.jpg +0 -0
  333. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/justified-nav.jpg +0 -0
  334. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/navbar-fixed.jpg +0 -0
  335. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/navbar-static.jpg +0 -0
  336. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/navbar.jpg +0 -0
  337. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/non-responsive.jpg +0 -0
  338. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/offcanvas.jpg +0 -0
  339. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/sign-in.jpg +0 -0
  340. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/starter-template.jpg +0 -0
  341. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/sticky-footer-navbar.jpg +0 -0
  342. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/sticky-footer.jpg +0 -0
  343. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/theme.jpg +0 -0
  344. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/signin/index.html +0 -50
  345. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/signin/signin.css +0 -40
  346. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/starter-template/index.html +0 -68
  347. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/starter-template/starter-template.css +0 -7
  348. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/sticky-footer-navbar/index.html +0 -91
  349. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/sticky-footer-navbar/sticky-footer-navbar.css +0 -45
  350. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/sticky-footer/index.html +0 -55
  351. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/sticky-footer/sticky-footer.css +0 -38
  352. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/theme/index.html +0 -384
  353. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/theme/theme.css +0 -14
  354. data/lib/api_browser/app/bower_components/bootstrap-sass/fonts/glyphicons-halflings-regular.eot +0 -0
  355. data/lib/api_browser/app/bower_components/bootstrap-sass/fonts/glyphicons-halflings-regular.svg +0 -229
  356. data/lib/api_browser/app/bower_components/bootstrap-sass/fonts/glyphicons-halflings-regular.ttf +0 -0
  357. data/lib/api_browser/app/bower_components/bootstrap-sass/fonts/glyphicons-halflings-regular.woff +0 -0
  358. data/lib/api_browser/app/bower_components/bootstrap-sass/getting-started.html +0 -1021
  359. data/lib/api_browser/app/bower_components/bootstrap-sass/index.html +0 -16
  360. data/lib/api_browser/app/bower_components/bootstrap-sass/javascript.html +0 -1983
  361. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/index.html +0 -52
  362. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/affix.js +0 -25
  363. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/alert.js +0 -62
  364. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/button.js +0 -116
  365. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/carousel.js +0 -87
  366. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/collapse.js +0 -164
  367. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/dropdown.js +0 -219
  368. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/modal.js +0 -196
  369. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/phantom.js +0 -69
  370. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/popover.js +0 -133
  371. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/scrollspy.js +0 -37
  372. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/tab.js +0 -86
  373. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/tooltip.js +0 -437
  374. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/transition.js +0 -13
  375. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/vendor/jquery.js +0 -6
  376. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/vendor/qunit.css +0 -232
  377. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/vendor/qunit.js +0 -1510
  378. data/lib/api_browser/app/bower_components/bootstrap-sass/package.json +0 -40
  379. data/lib/api_browser/app/bower_components/lodash/dist/lodash.compat.js +0 -7157
  380. data/lib/api_browser/app/bower_components/lodash/dist/lodash.compat.min.js +0 -61
  381. data/lib/api_browser/app/bower_components/lodash/dist/lodash.js +0 -6785
  382. data/lib/api_browser/app/bower_components/lodash/dist/lodash.min.js +0 -56
  383. data/lib/api_browser/app/bower_components/lodash/dist/lodash.underscore.js +0 -4979
  384. data/lib/api_browser/app/bower_components/lodash/dist/lodash.underscore.min.js +0 -39
  385. data/lib/api_browser/app/js/directives/attribute_table_row.js +0 -17
  386. data/lib/api_browser/app/js/directives/request_body.js +0 -25
  387. data/lib/api_browser/app/js/directives/request_headers.js +0 -17
  388. data/lib/api_browser/app/js/directives/request_parameters.js +0 -17
  389. data/lib/api_browser/app/js/directives/type_label.js +0 -52
  390. data/lib/api_browser/app/js/factories/PayloadTemplates.js +0 -10
  391. data/lib/api_browser/app/js/factories/TemplateProvider.js +0 -45
  392. data/lib/api_browser/app/js/factories/TypeTemplates.js +0 -11
  393. data/lib/api_browser/app/views/directives/attribute_description/_example.html +0 -13
  394. data/lib/api_browser/app/views/directives/attribute_description/_headers.html +0 -8
  395. data/lib/api_browser/app/views/directives/attribute_table_row/_default.html +0 -10
  396. data/lib/api_browser/app/views/directives/attribute_table_row/_links.html +0 -11
  397. data/lib/api_browser/app/views/directives/attribute_table_row/_struct.html +0 -2
  398. data/lib/api_browser/app/views/directives/request_body/_struct.html +0 -1
  399. data/lib/api_browser/app/views/resource/_actions.html +0 -27
@@ -0,0 +1,126 @@
1
+ // Type definitions for Angular JS 1.1.5+ (ui.router module)
2
+ // Project: https://github.com/angular-ui/ui-router
3
+ // Definitions by: Michel Salib <https://github.com/michelsalib>
4
+ // Definitions: https://github.com/borisyankov/DefinitelyTyped
5
+
6
+ declare module ng.ui {
7
+
8
+ interface IState {
9
+ name?: string;
10
+ template?: string;
11
+ templateUrl?: any; // string || () => string
12
+ templateProvider?: any; // () => string || IPromise<string>
13
+ controller?: any;
14
+ controllerAs?: string;
15
+ controllerProvider?: any;
16
+ resolve?: {};
17
+ url?: string;
18
+ params?: any;
19
+ views?: {};
20
+ abstract?: boolean;
21
+ onEnter?: (...args: any[]) => void;
22
+ onExit?: (...args: any[]) => void;
23
+ data?: any;
24
+ reloadOnSearch?: boolean;
25
+ }
26
+
27
+ interface ITypedState<T> extends IState {
28
+ data?: T;
29
+ }
30
+
31
+ interface IStateProvider extends IServiceProvider {
32
+ state(name: string, config: IState): IStateProvider;
33
+ state(config: IState): IStateProvider;
34
+ decorator(name?: string, decorator?: (state: IState, parent: Function) => any): any;
35
+ }
36
+
37
+ interface IUrlMatcher {
38
+ concat(pattern: string): IUrlMatcher;
39
+ exec(path: string, searchParams: {}): {};
40
+ parameters(): string[];
41
+ format(values: {}): string;
42
+ }
43
+
44
+ interface IUrlMatcherFactory {
45
+ compile(pattern: string): IUrlMatcher;
46
+ isMatcher(o: any): boolean;
47
+ }
48
+
49
+ interface IUrlRouterProvider extends IServiceProvider {
50
+ when(whenPath: RegExp, handler: Function): IUrlRouterProvider;
51
+ when(whenPath: RegExp, handler: any[]): IUrlRouterProvider;
52
+ when(whenPath: RegExp, toPath: string): IUrlRouterProvider;
53
+ when(whenPath: IUrlMatcher, hanlder: Function): IUrlRouterProvider;
54
+ when(whenPath: IUrlMatcher, handler: any[]): IUrlRouterProvider;
55
+ when(whenPath: IUrlMatcher, toPath: string): IUrlRouterProvider;
56
+ when(whenPath: string, handler: Function): IUrlRouterProvider;
57
+ when(whenPath: string, handler: any[]): IUrlRouterProvider;
58
+ when(whenPath: string, toPath: string): IUrlRouterProvider;
59
+ otherwise(handler: Function): IUrlRouterProvider;
60
+ otherwise(handler: any[]): IUrlRouterProvider;
61
+ otherwise(path: string): IUrlRouterProvider;
62
+ rule(handler: Function): IUrlRouterProvider;
63
+ rule(handler: any[]): IUrlRouterProvider;
64
+ }
65
+
66
+ interface IStateOptions {
67
+ location?: any;
68
+ inherit?: boolean;
69
+ relative?: IState;
70
+ notify?: boolean;
71
+ reload?: boolean;
72
+ }
73
+
74
+ interface IHrefOptions {
75
+ lossy?: boolean;
76
+ inherit?: boolean;
77
+ relative?: IState;
78
+ absolute?: boolean;
79
+ }
80
+
81
+ interface IStateService {
82
+ go(to: string, params?: {}, options?: IStateOptions): IPromise<any>;
83
+ transitionTo(state: string, params?: {}, updateLocation?: boolean): void;
84
+ transitionTo(state: string, params?: {}, options?: IStateOptions): void;
85
+ includes(state: string, params?: {}): boolean;
86
+ is(state:string, params?: {}): boolean;
87
+ is(state: IState, params?: {}): boolean;
88
+ href(state: IState, params?: {}, options?: IHrefOptions): string;
89
+ href(state: string, params?: {}, options?: IHrefOptions): string;
90
+ get(state: string): IState;
91
+ get(): IState[];
92
+ current: IState;
93
+ params: any;
94
+ reload(): void;
95
+ }
96
+
97
+ interface IStateParamsService {
98
+ [key: string]: any;
99
+ }
100
+
101
+ interface IStateParams {
102
+ [key: string]: any;
103
+ }
104
+
105
+ interface IUrlRouterService {
106
+ /*
107
+ * Triggers an update; the same update that happens when the address bar
108
+ * url changes, aka $locationChangeSuccess.
109
+ *
110
+ * This method is useful when you need to use preventDefault() on the
111
+ * $locationChangeSuccess event, perform some custom logic (route protection,
112
+ * auth, config, redirection, etc) and then finally proceed with the transition
113
+ * by calling $urlRouter.sync().
114
+ *
115
+ */
116
+ sync(): void;
117
+ }
118
+
119
+ interface IUiViewScrollProvider {
120
+ /*
121
+ * Reverts back to using the core $anchorScroll service for scrolling
122
+ * based on the url anchor.
123
+ */
124
+ useAnchorScroll(): void;
125
+ }
126
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "angular-ui-router",
3
- "version": "0.2.10",
3
+ "version": "0.2.15",
4
4
  "main": "./release/angular-ui-router.js",
5
5
  "dependencies": {
6
6
  "angular": ">= 1.0.8"
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * State-based routing for AngularJS
3
- * @version v0.2.10
3
+ * @version v0.2.15
4
4
  * @link http://angular-ui.github.com/
5
5
  * @license MIT License, http://www.opensource.org/licenses/MIT
6
6
  */
@@ -62,13 +62,13 @@ function ancestors(first, second) {
62
62
  * @param {Object} object A JavaScript object.
63
63
  * @return {Array} Returns the keys of the object as an array.
64
64
  */
65
- function keys(object) {
65
+ function objectKeys(object) {
66
66
  if (Object.keys) {
67
67
  return Object.keys(object);
68
68
  }
69
69
  var result = [];
70
70
 
71
- angular.forEach(object, function(val, key) {
71
+ forEach(object, function(val, key) {
72
72
  result.push(key);
73
73
  });
74
74
  return result;
@@ -81,7 +81,7 @@ function keys(object) {
81
81
  * @param {*} value A value to search the array for.
82
82
  * @return {Number} Returns the array index value of `value`, or `-1` if not present.
83
83
  */
84
- function arraySearch(array, value) {
84
+ function indexOf(array, value) {
85
85
  if (Array.prototype.indexOf) {
86
86
  return array.indexOf(value, Number(arguments[2]) || 0);
87
87
  }
@@ -109,11 +109,12 @@ function inheritParams(currentParams, newParams, $current, $to) {
109
109
  var parents = ancestors($current, $to), parentParams, inherited = {}, inheritList = [];
110
110
 
111
111
  for (var i in parents) {
112
- if (!parents[i].params || !parents[i].params.length) continue;
113
- parentParams = parents[i].params;
112
+ if (!parents[i].params) continue;
113
+ parentParams = objectKeys(parents[i].params);
114
+ if (!parentParams.length) continue;
114
115
 
115
116
  for (var j in parentParams) {
116
- if (arraySearch(inheritList, parentParams[j]) >= 0) continue;
117
+ if (indexOf(inheritList, parentParams[j]) >= 0) continue;
117
118
  inheritList.push(parentParams[j]);
118
119
  inherited[parentParams[j]] = currentParams[parentParams[j]];
119
120
  }
@@ -121,23 +122,6 @@ function inheritParams(currentParams, newParams, $current, $to) {
121
122
  return extend({}, inherited, newParams);
122
123
  }
123
124
 
124
- /**
125
- * Normalizes a set of values to string or `null`, filtering them by a list of keys.
126
- *
127
- * @param {Array} keys The list of keys to normalize/return.
128
- * @param {Object} values An object hash of values to normalize.
129
- * @return {Object} Returns an object hash of normalized string values.
130
- */
131
- function normalize(keys, values) {
132
- var normalized = {};
133
-
134
- forEach(keys, function (name) {
135
- var value = values[name];
136
- normalized[name] = (value != null) ? String(value) : null;
137
- });
138
- return normalized;
139
- }
140
-
141
125
  /**
142
126
  * Performs a non-strict comparison of the subset of two objects, defined by a list of keys.
143
127
  *
@@ -175,6 +159,68 @@ function filterByKeys(keys, values) {
175
159
  });
176
160
  return filtered;
177
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
+
178
224
  /**
179
225
  * @ngdoc overview
180
226
  * @name ui.router.util
@@ -301,6 +347,7 @@ function $Resolve( $q, $injector) {
301
347
  */
302
348
  this.study = function (invocables) {
303
349
  if (!isObject(invocables)) throw new Error("'invocables' must be an object");
350
+ var invocableKeys = objectKeys(invocables || {});
304
351
 
305
352
  // Perform a topological sort of invocables to build an ordered plan
306
353
  var plan = [], cycle = [], visited = {};
@@ -309,7 +356,7 @@ function $Resolve( $q, $injector) {
309
356
 
310
357
  cycle.push(key);
311
358
  if (visited[key] === VISIT_IN_PROGRESS) {
312
- cycle.splice(0, cycle.indexOf(key));
359
+ cycle.splice(0, indexOf(cycle, key));
313
360
  throw new Error("Cyclic dependency: " + cycle.join(" -> "));
314
361
  }
315
362
  visited[key] = VISIT_IN_PROGRESS;
@@ -361,7 +408,8 @@ function $Resolve( $q, $injector) {
361
408
  if (!--wait) {
362
409
  if (!merged) merge(values, parent.$$values);
363
410
  result.$$values = values;
364
- result.$$promises = true; // keep for isResolve()
411
+ result.$$promises = result.$$promises || true; // keep for isResolve()
412
+ delete result.$$inheritedValues;
365
413
  resolution.resolve(values);
366
414
  }
367
415
  }
@@ -370,20 +418,28 @@ function $Resolve( $q, $injector) {
370
418
  result.$$failure = reason;
371
419
  resolution.reject(reason);
372
420
  }
373
-
421
+
374
422
  // Short-circuit if parent has already failed
375
423
  if (isDefined(parent.$$failure)) {
376
424
  fail(parent.$$failure);
377
425
  return result;
378
426
  }
379
427
 
428
+ if (parent.$$inheritedValues) {
429
+ merge(values, omit(parent.$$inheritedValues, invocableKeys));
430
+ }
431
+
380
432
  // Merge parent values if the parent has already resolved, or merge
381
433
  // parent promises and wait if the parent resolve is still in progress.
434
+ extend(promises, parent.$$promises);
382
435
  if (parent.$$values) {
383
- merged = merge(values, parent.$$values);
436
+ merged = merge(values, omit(parent.$$values, invocableKeys));
437
+ result.$$inheritedValues = omit(parent.$$values, invocableKeys);
384
438
  done();
385
439
  } else {
386
- extend(promises, parent.$$promises);
440
+ if (parent.$$inheritedValues) {
441
+ result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys);
442
+ }
387
443
  parent.then(done, fail);
388
444
  }
389
445
 
@@ -586,13 +642,13 @@ function $TemplateFactory( $http, $templateCache, $injector) {
586
642
  if (isFunction(url)) url = url(params);
587
643
  if (url == null) return null;
588
644
  else return $http
589
- .get(url, { cache: $templateCache })
645
+ .get(url, { cache: $templateCache, headers: { Accept: 'text/html' }})
590
646
  .then(function(response) { return response.data; });
591
647
  };
592
648
 
593
649
  /**
594
650
  * @ngdoc function
595
- * @name ui.router.util.$templateFactory#fromUrl
651
+ * @name ui.router.util.$templateFactory#fromProvider
596
652
  * @methodOf ui.router.util.$templateFactory
597
653
  *
598
654
  * @description
@@ -612,6 +668,8 @@ function $TemplateFactory( $http, $templateCache, $injector) {
612
668
 
613
669
  angular.module('ui.router.util').service('$templateFactory', $TemplateFactory);
614
670
 
671
+ var $$UMFP; // reference to $UrlMatcherFactoryProvider
672
+
615
673
  /**
616
674
  * @ngdoc object
617
675
  * @name ui.router.util.type:UrlMatcher
@@ -622,24 +680,24 @@ angular.module('ui.router.util').service('$templateFactory', $TemplateFactory);
622
680
  * of search parameters. Multiple search parameter names are separated by '&'. Search parameters
623
681
  * do not influence whether or not a URL is matched, but their values are passed through into
624
682
  * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}.
625
- *
683
+ *
626
684
  * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace
627
685
  * syntax, which optionally allows a regular expression for the parameter to be specified:
628
686
  *
629
687
  * * `':'` name - colon placeholder
630
688
  * * `'*'` name - catch-all placeholder
631
689
  * * `'{' name '}'` - curly placeholder
632
- * * `'{' name ':' regexp '}'` - curly placeholder with regexp. Should the regexp itself contain
633
- * curly braces, they must be in matched pairs or escaped with a backslash.
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.
634
692
  *
635
693
  * Parameter names may contain only word characters (latin letters, digits, and underscore) and
636
- * must be unique within the pattern (across both path and search parameters). For colon
694
+ * must be unique within the pattern (across both path and search parameters). For colon
637
695
  * placeholders or curly placeholders without an explicit regexp, a path parameter matches any
638
696
  * number of characters other than '/'. For catch-all placeholders the path parameter matches
639
697
  * any number of characters.
640
- *
698
+ *
641
699
  * Examples:
642
- *
700
+ *
643
701
  * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for
644
702
  * trailing slashes, and patterns have to match the entire path, not just a prefix.
645
703
  * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or
@@ -651,25 +709,34 @@ angular.module('ui.router.util').service('$templateFactory', $TemplateFactory);
651
709
  * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the
652
710
  * path into the parameter 'path'.
653
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
654
714
  *
655
- * @param {string} pattern the pattern to compile into a matcher.
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`.
656
722
  *
657
723
  * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any
658
724
  * URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns
659
725
  * non-null) will start with this prefix.
660
726
  *
661
- * @property {string} source The pattern that was passed into the contructor
727
+ * @property {string} source The pattern that was passed into the constructor
662
728
  *
663
729
  * @property {string} sourcePath The path portion of the source property
664
730
  *
665
731
  * @property {string} sourceSearch The search portion of the source property
666
732
  *
667
- * @property {string} regex The constructed regex that will be used to match against the url when
733
+ * @property {string} regex The constructed regex that will be used to match against the url when
668
734
  * it is time to determine which url will match.
669
735
  *
670
- * @returns {Object} New UrlMatcher object
736
+ * @returns {Object} New `UrlMatcher` object
671
737
  */
672
- function UrlMatcher(pattern) {
738
+ function UrlMatcher(pattern, config, parentMatcher) {
739
+ config = extend({ params: {} }, isObject(config) ? config : {});
673
740
 
674
741
  // Find all placeholders and create a compiled pattern, using either classic or curly syntax:
675
742
  // '*' name
@@ -678,63 +745,96 @@ function UrlMatcher(pattern) {
678
745
  // '{' name ':' regexp '}'
679
746
  // The regular expression is somewhat complicated due to the need to allow curly braces
680
747
  // inside the regular expression. The placeholder regexp breaks down as follows:
681
- // ([:*])(\w+) classic placeholder ($1 / $2)
682
- // \{(\w+)(?:\:( ... ))?\} curly brace placeholder ($3) with optional regexp ... ($4)
683
- // (?: ... | ... | ... )+ the regexp consists of any number of atoms, an atom being either
684
- // [^{}\\]+ - anything other than curly braces or backslash
685
- // \\. - a backslash escape
686
- // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms
687
- var placeholder = /([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
688
- names = {}, compiled = '^', last = 0, m,
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,
689
757
  segments = this.segments = [],
690
- params = this.params = [];
691
-
692
- function addParameter(id) {
693
- if (!/^\w+(-+\w+)*$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'");
694
- if (names[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'");
695
- names[id] = true;
696
- params.push(id);
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];
697
769
  }
698
770
 
699
- function quoteRegExp(string) {
700
- return string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&");
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];
701
780
  }
702
781
 
703
782
  this.source = pattern;
704
783
 
705
784
  // Split into static segments separated by path parameter placeholders.
706
785
  // The number of segments is always 1 more than the number of parameters.
707
- var id, regexp, segment;
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;
708
799
  while ((m = placeholder.exec(pattern))) {
709
- id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null
710
- regexp = m[4] || (m[1] == '*' ? '.*' : '[^/]*');
711
- segment = pattern.substring(last, m.index);
712
- if (segment.indexOf('?') >= 0) break; // we're into the search part
713
- compiled += quoteRegExp(segment) + '(' + regexp + ')';
714
- addParameter(id);
715
- segments.push(segment);
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);
716
806
  last = placeholder.lastIndex;
717
807
  }
718
808
  segment = pattern.substring(last);
719
809
 
720
810
  // Find any search parameter names and remove them from the last segment
721
811
  var i = segment.indexOf('?');
812
+
722
813
  if (i >= 0) {
723
814
  var search = this.sourceSearch = segment.substring(i);
724
815
  segment = segment.substring(0, i);
725
- this.sourcePath = pattern.substring(0, last+i);
726
-
727
- // Allow parameters to be separated by '?' as well as '&' to make concat() easier
728
- forEach(search.substring(1).split(/[&?]/), addParameter);
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
+ }
729
827
  } else {
730
828
  this.sourcePath = pattern;
731
829
  this.sourceSearch = '';
732
830
  }
733
831
 
734
- compiled += quoteRegExp(segment) + '$';
832
+ compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$';
735
833
  segments.push(segment);
736
- this.regexp = new RegExp(compiled);
834
+
835
+ this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined);
737
836
  this.prefix = segments[0];
837
+ this.$$paramNames = paramNames;
738
838
  }
739
839
 
740
840
  /**
@@ -750,19 +850,25 @@ function UrlMatcher(pattern) {
750
850
  *
751
851
  * @example
752
852
  * The following two matchers are equivalent:
753
- * ```
853
+ * <pre>
754
854
  * new UrlMatcher('/user/{id}?q').concat('/details?date');
755
855
  * new UrlMatcher('/user/{id}/details?q&date');
756
- * ```
856
+ * </pre>
757
857
  *
758
858
  * @param {string} pattern The pattern to append.
759
- * @returns {ui.router.util.type:UrlMatcher} A matcher for the concatenated pattern.
859
+ * @param {Object} config An object hash of the configuration for the matcher.
860
+ * @returns {UrlMatcher} A matcher for the concatenated pattern.
760
861
  */
761
- UrlMatcher.prototype.concat = function (pattern) {
862
+ UrlMatcher.prototype.concat = function (pattern, config) {
762
863
  // Because order of search parameters is irrelevant, we can add our own search
763
864
  // parameters to the end of the new pattern. Parse the new pattern by itself
764
865
  // and then join the bits together, but it's much easier to do this on a string level.
765
- return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch);
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);
766
872
  };
767
873
 
768
874
  UrlMatcher.prototype.toString = function () {
@@ -782,10 +888,12 @@ UrlMatcher.prototype.toString = function () {
782
888
  * as optional.
783
889
  *
784
890
  * @example
785
- * ```
786
- * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', { x:'1', q:'hello' });
787
- * // returns { id:'bob', q:'hello', r:null }
788
- * ```
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>
789
897
  *
790
898
  * @param {string} path The URL path to match, e.g. `$location.path()`.
791
899
  * @param {Object} searchParams URL search parameters, e.g. `$location.search()`.
@@ -794,15 +902,38 @@ UrlMatcher.prototype.toString = function () {
794
902
  UrlMatcher.prototype.exec = function (path, searchParams) {
795
903
  var m = this.regexp.exec(path);
796
904
  if (!m) return null;
905
+ searchParams = searchParams || {};
797
906
 
798
- var params = this.params, nTotal = params.length,
799
- nPath = this.segments.length-1,
800
- values = {}, i;
907
+ var paramNames = this.parameters(), nTotal = paramNames.length,
908
+ nPath = this.segments.length - 1,
909
+ values = {}, i, j, cfg, paramName;
801
910
 
802
911
  if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'");
803
912
 
804
- for (i=0; i<nPath; i++) values[params[i]] = m[i+1];
805
- for (/**/; i<nTotal; i++) values[params[i]] = searchParams[params[i]];
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
+ }
806
937
 
807
938
  return values;
808
939
  };
@@ -814,12 +945,29 @@ UrlMatcher.prototype.exec = function (path, searchParams) {
814
945
  *
815
946
  * @description
816
947
  * Returns the names of all path and search parameters of this pattern in an unspecified order.
817
- *
948
+ *
818
949
  * @returns {Array.<string>} An array of parameter names. Must be treated as read-only. If the
819
950
  * pattern has no parameters, an empty array is returned.
820
951
  */
821
- UrlMatcher.prototype.parameters = function () {
822
- return this.params;
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);
823
971
  };
824
972
 
825
973
  /**
@@ -833,31 +981,54 @@ UrlMatcher.prototype.parameters = function () {
833
981
  * treated as empty strings.
834
982
  *
835
983
  * @example
836
- * ```
984
+ * <pre>
837
985
  * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' });
838
986
  * // returns '/user/bob?q=yes'
839
- * ```
987
+ * </pre>
840
988
  *
841
989
  * @param {Object} values the values to substitute for the parameters in this pattern.
842
990
  * @returns {string} the formatted URL (path and optionally search part).
843
991
  */
844
992
  UrlMatcher.prototype.format = function (values) {
845
- var segments = this.segments, params = this.params;
846
- if (!values) return segments.join('');
993
+ values = values || {};
994
+ var segments = this.segments, params = this.parameters(), paramset = this.params;
995
+ if (!this.validates(values)) return null;
847
996
 
848
- var nPath = segments.length-1, nTotal = params.length,
849
- result = segments[0], i, search, value;
997
+ var i, search = false, nPath = segments.length - 1, nTotal = params.length, result = segments[0];
850
998
 
851
- for (i=0; i<nPath; i++) {
852
- value = values[params[i]];
853
- // TODO: Maybe we should throw on null here? It's not really good style to use '' and null interchangeabley
854
- if (value != null) result += encodeURIComponent(value);
855
- result += segments[i+1];
999
+ function encodeDashes(str) { // Replace dashes with encoded "\-"
1000
+ return encodeURIComponent(str).replace(/-/g, function(c) { return '%5C%' + c.charCodeAt(0).toString(16).toUpperCase(); });
856
1001
  }
857
- for (/**/; i<nTotal; i++) {
858
- value = values[params[i]];
859
- if (value != null) {
860
- result += (search ? '&' : '?') + params[i] + '=' + encodeURIComponent(value);
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);
861
1032
  search = true;
862
1033
  }
863
1034
  }
@@ -865,6 +1036,194 @@ UrlMatcher.prototype.format = function (values) {
865
1036
  return result;
866
1037
  };
867
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
+
868
1227
 
869
1228
 
870
1229
  /**
@@ -872,10 +1231,148 @@ UrlMatcher.prototype.format = function (values) {
872
1231
  * @name ui.router.util.$urlMatcherFactory
873
1232
  *
874
1233
  * @description
875
- * Factory for {@link ui.router.util.type:UrlMatcher} instances. The factory is also available to providers
876
- * under the name `$urlMatcherFactoryProvider`.
1234
+ * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory
1235
+ * is also available to providers under the name `$urlMatcherFactoryProvider`.
877
1236
  */
878
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
+ };
879
1376
 
880
1377
  /**
881
1378
  * @ngdoc function
@@ -883,13 +1380,14 @@ function $UrlMatcherFactory() {
883
1380
  * @methodOf ui.router.util.$urlMatcherFactory
884
1381
  *
885
1382
  * @description
886
- * Creates a {@link ui.router.util.type:UrlMatcher} for the specified pattern.
887
- *
1383
+ * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern.
1384
+ *
888
1385
  * @param {string} pattern The URL pattern.
889
- * @returns {ui.router.util.type:UrlMatcher} The UrlMatcher.
1386
+ * @param {Object} config The config object hash.
1387
+ * @returns {UrlMatcher} The UrlMatcher.
890
1388
  */
891
- this.compile = function (pattern) {
892
- return new UrlMatcher(pattern);
1389
+ this.compile = function (pattern, config) {
1390
+ return new UrlMatcher(pattern, extend(getDefaultConfig(), config));
893
1391
  };
894
1392
 
895
1393
  /**
@@ -898,29 +1396,335 @@ function $UrlMatcherFactory() {
898
1396
  * @methodOf ui.router.util.$urlMatcherFactory
899
1397
  *
900
1398
  * @description
901
- * Returns true if the specified object is a UrlMatcher, or false otherwise.
1399
+ * Returns true if the specified object is a `UrlMatcher`, or false otherwise.
902
1400
  *
903
1401
  * @param {Object} object The object to perform the type check against.
904
- * @returns {Boolean} Returns `true` if the object has the following functions: `exec`, `format`, and `concat`.
1402
+ * @returns {Boolean} Returns `true` if the object matches the `UrlMatcher` interface, by
1403
+ * implementing all the same methods.
905
1404
  */
906
1405
  this.isMatcher = function (o) {
907
- return isObject(o) && isFunction(o.exec) && isFunction(o.format) && isFunction(o.concat);
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;
908
1415
  };
909
-
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
+
910
1549
  /* No need to document $get, since it returns this */
911
- this.$get = function () {
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
+ });
912
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
+ });
913
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;
914
1716
  }
915
1717
 
916
1718
  // Register as a provider so it's available to other providers
917
1719
  angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory);
1720
+ angular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]);
918
1721
 
919
1722
  /**
920
1723
  * @ngdoc object
921
1724
  * @name ui.router.router.$urlRouterProvider
922
1725
  *
923
1726
  * @requires ui.router.util.$urlMatcherFactoryProvider
1727
+ * @requires $locationProvider
924
1728
  *
925
1729
  * @description
926
1730
  * `$urlRouterProvider` has the responsibility of watching `$location`.
@@ -931,10 +1735,9 @@ angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFacto
931
1735
  * There are several methods on `$urlRouterProvider` that make it useful to use directly
932
1736
  * in your module config.
933
1737
  */
934
- $UrlRouterProvider.$inject = ['$urlMatcherFactoryProvider'];
935
- function $UrlRouterProvider( $urlMatcherFactory) {
936
- var rules = [],
937
- otherwise = null;
1738
+ $UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider'];
1739
+ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
1740
+ var rules = [], otherwise = null, interceptDeferred = false, listener;
938
1741
 
939
1742
  // Returns a string that is a prefix of all strings matching the RegExp
940
1743
  function regExpPrefix(re) {
@@ -955,7 +1758,7 @@ function $UrlRouterProvider( $urlMatcherFactory) {
955
1758
  * @methodOf ui.router.router.$urlRouterProvider
956
1759
  *
957
1760
  * @description
958
- * Defines rules that are used by `$urlRouterProvider to find matches for
1761
+ * Defines rules that are used by `$urlRouterProvider` to find matches for
959
1762
  * specific URLs.
960
1763
  *
961
1764
  * @example
@@ -978,14 +1781,13 @@ function $UrlRouterProvider( $urlMatcherFactory) {
978
1781
  * @param {object} rule Handler function that takes `$injector` and `$location`
979
1782
  * services as arguments. You can use them to return a valid path as a string.
980
1783
  *
981
- * @return {object} $urlRouterProvider - $urlRouterProvider instance
1784
+ * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
982
1785
  */
983
- this.rule =
984
- function (rule) {
985
- if (!isFunction(rule)) throw new Error("'rule' must be a function");
986
- rules.push(rule);
987
- return this;
988
- };
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
+ };
989
1791
 
990
1792
  /**
991
1793
  * @ngdoc object
@@ -993,7 +1795,7 @@ function $UrlRouterProvider( $urlMatcherFactory) {
993
1795
  * @methodOf ui.router.router.$urlRouterProvider
994
1796
  *
995
1797
  * @description
996
- * Defines a path that is used when an invalied route is requested.
1798
+ * Defines a path that is used when an invalid route is requested.
997
1799
  *
998
1800
  * @example
999
1801
  * <pre>
@@ -1007,27 +1809,26 @@ function $UrlRouterProvider( $urlMatcherFactory) {
1007
1809
  *
1008
1810
  * // Example of using function rule as param
1009
1811
  * $urlRouterProvider.otherwise(function ($injector, $location) {
1010
- * ...
1812
+ * return '/a/valid/url';
1011
1813
  * });
1012
1814
  * });
1013
1815
  * </pre>
1014
1816
  *
1015
1817
  * @param {string|object} rule The url path you want to redirect to or a function
1016
1818
  * rule that returns the url path. The function version is passed two params:
1017
- * `$injector` and `$location` services.
1819
+ * `$injector` and `$location` services, and must return a url string.
1018
1820
  *
1019
- * @return {object} $urlRouterProvider - $urlRouterProvider instance
1821
+ * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
1020
1822
  */
1021
- this.otherwise =
1022
- function (rule) {
1023
- if (isString(rule)) {
1024
- var redirect = rule;
1025
- rule = function () { return redirect; };
1026
- }
1027
- else if (!isFunction(rule)) throw new Error("'rule' must be a function");
1028
- otherwise = rule;
1029
- return this;
1030
- };
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
+ };
1031
1832
 
1032
1833
 
1033
1834
  function handleIfMatch($injector, handler, match) {
@@ -1043,8 +1844,8 @@ function $UrlRouterProvider( $urlMatcherFactory) {
1043
1844
  *
1044
1845
  * @description
1045
1846
  * Registers a handler for a given url matching. if handle is a string, it is
1046
- * treated as a redirect, and is interpolated according to the syyntax of match
1047
- * (i.e. like String.replace() for RegExp, or like a UrlMatcher pattern otherwise).
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).
1048
1849
  *
1049
1850
  * If the handler is a function, it is injectable. It gets invoked if `$location`
1050
1851
  * matches. You have the option of inject the match object as `$match`.
@@ -1073,52 +1874,102 @@ function $UrlRouterProvider( $urlMatcherFactory) {
1073
1874
  * @param {string|object} what The incoming path that you want to redirect.
1074
1875
  * @param {string|object} handler The path you want to redirect your user to.
1075
1876
  */
1076
- this.when =
1077
- function (what, handler) {
1078
- var redirect, handlerIsString = isString(handler);
1079
- if (isString(what)) what = $urlMatcherFactory.compile(what);
1080
-
1081
- if (!handlerIsString && !isFunction(handler) && !isArray(handler))
1082
- throw new Error("invalid 'handler' in when()");
1083
-
1084
- var strategies = {
1085
- matcher: function (what, handler) {
1086
- if (handlerIsString) {
1087
- redirect = $urlMatcherFactory.compile(handler);
1088
- handler = ['$match', function ($match) { return redirect.format($match); }];
1089
- }
1090
- return extend(function ($injector, $location) {
1091
- return handleIfMatch($injector, handler, what.exec($location.path(), $location.search()));
1092
- }, {
1093
- prefix: isString(what.prefix) ? what.prefix : ''
1094
- });
1095
- },
1096
- regex: function (what, handler) {
1097
- if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky");
1098
-
1099
- if (handlerIsString) {
1100
- redirect = handler;
1101
- handler = ['$match', function ($match) { return interpolate(redirect, $match); }];
1102
- }
1103
- return extend(function ($injector, $location) {
1104
- return handleIfMatch($injector, handler, what.exec($location.path()));
1105
- }, {
1106
- prefix: regExpPrefix(what)
1107
- });
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); }];
1108
1889
  }
1109
- };
1110
-
1111
- var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp };
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");
1112
1898
 
1113
- for (var n in check) {
1114
- if (check[n]) {
1115
- return this.rule(strategies[n](what, handler));
1899
+ if (handlerIsString) {
1900
+ redirect = handler;
1901
+ handler = ['$match', function ($match) { return interpolate(redirect, $match); }];
1116
1902
  }
1903
+ return extend(function ($injector, $location) {
1904
+ return handleIfMatch($injector, handler, what.exec($location.path()));
1905
+ }, {
1906
+ prefix: regExpPrefix(what)
1907
+ });
1117
1908
  }
1118
-
1119
- throw new Error("invalid 'what' in when()");
1120
1909
  };
1121
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
+
1122
1973
  /**
1123
1974
  * @ngdoc object
1124
1975
  * @name ui.router.router.$urlRouter
@@ -1126,66 +1977,172 @@ function $UrlRouterProvider( $urlMatcherFactory) {
1126
1977
  * @requires $location
1127
1978
  * @requires $rootScope
1128
1979
  * @requires $injector
1980
+ * @requires $browser
1129
1981
  *
1130
1982
  * @description
1131
1983
  *
1132
1984
  */
1133
- this.$get =
1134
- [ '$location', '$rootScope', '$injector',
1135
- function ($location, $rootScope, $injector) {
1136
- // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree
1137
- function update(evt) {
1138
- if (evt && evt.defaultPrevented) return;
1139
- function check(rule) {
1140
- var handled = rule($injector, $location);
1141
- if (handled) {
1142
- if (isString(handled)) $location.replace().url(handled);
1143
- return true;
1144
- }
1145
- return false;
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;
1146
2068
  }
1147
- var n=rules.length, i;
1148
- for (i=0; i<n; i++) {
1149
- if (check(rules[i])) return;
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['#'];
1150
2081
  }
1151
- // always check otherwise last to allow dynamic updates to the set of rules
1152
- if (otherwise) check(otherwise);
1153
- }
1154
2082
 
1155
- $rootScope.$on('$locationChangeSuccess', update);
2083
+ $location.url(url);
2084
+ lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined;
2085
+ if (options && options.replace) $location.replace();
2086
+ },
1156
2087
 
1157
- return {
1158
- /**
1159
- * @ngdoc function
1160
- * @name ui.router.router.$urlRouter#sync
1161
- * @methodOf ui.router.router.$urlRouter
1162
- *
1163
- * @description
1164
- * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`.
1165
- * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event,
1166
- * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed
1167
- * with the transition by calling `$urlRouter.sync()`.
1168
- *
1169
- * @example
1170
- * <pre>
1171
- * angular.module('app', ['ui.router']);
1172
- * .run(function($rootScope, $urlRouter) {
1173
- * $rootScope.$on('$locationChangeSuccess', function(evt) {
1174
- * // Halt state change from even starting
1175
- * evt.preventDefault();
1176
- * // Perform custom logic
1177
- * var meetsRequirement = ...
1178
- * // Continue with the update and state transition if logic allows
1179
- * if (meetsRequirement) $urlRouter.sync();
1180
- * });
1181
- * });
1182
- * </pre>
1183
- */
1184
- sync: function () {
1185
- update();
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;
1186
2119
  }
1187
- };
1188
- }];
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
+ }
1189
2146
  }
1190
2147
 
1191
2148
  angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);
@@ -1196,7 +2153,6 @@ angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);
1196
2153
  *
1197
2154
  * @requires ui.router.router.$urlRouterProvider
1198
2155
  * @requires ui.router.util.$urlMatcherFactoryProvider
1199
- * @requires $locationProvider
1200
2156
  *
1201
2157
  * @description
1202
2158
  * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely
@@ -1212,8 +2168,8 @@ angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);
1212
2168
  *
1213
2169
  * The `$stateProvider` provides interfaces to declare these states for your app.
1214
2170
  */
1215
- $StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider', '$locationProvider'];
1216
- function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $locationProvider) {
2171
+ $StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider'];
2172
+ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
1217
2173
 
1218
2174
  var root, states = {}, $state, queue = {}, abstractKey = 'abstract';
1219
2175
 
@@ -1241,18 +2197,14 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1241
2197
 
1242
2198
  // Build a URLMatcher if necessary, either via a relative or absolute URL
1243
2199
  url: function(state) {
1244
- var url = state.url;
2200
+ var url = state.url, config = { params: state.params || {} };
1245
2201
 
1246
2202
  if (isString(url)) {
1247
- if (url.charAt(0) == '^') {
1248
- return $urlMatcherFactory.compile(url.substring(1));
1249
- }
1250
- return (state.parent.navigable || root).url.concat(url);
2203
+ if (url.charAt(0) == '^') return $urlMatcherFactory.compile(url.substring(1), config);
2204
+ return (state.parent.navigable || root).url.concat(url, config);
1251
2205
  }
1252
2206
 
1253
- if ($urlMatcherFactory.isMatcher(url) || url == null) {
1254
- return url;
1255
- }
2207
+ if (!url || $urlMatcherFactory.isMatcher(url)) return url;
1256
2208
  throw new Error("Invalid url '" + url + "' in state '" + state + "'");
1257
2209
  },
1258
2210
 
@@ -1261,14 +2213,18 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1261
2213
  return state.url ? state : (state.parent ? state.parent.navigable : null);
1262
2214
  },
1263
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
+
1264
2225
  // Derive parameters for this state and ensure they're a super-set of parent's parameters
1265
2226
  params: function(state) {
1266
- if (!state.params) {
1267
- return state.url ? state.url.parameters() : state.parent.params;
1268
- }
1269
- if (!isArray(state.params)) throw new Error("Invalid params in state '" + state + "'");
1270
- if (state.url) throw new Error("Both params and url specicified in state '" + state + "'");
1271
- return state.params;
2227
+ return state.parent && state.parent.params ? extend(state.parent.params.$$new(), state.ownParams) : new $$UMFP.ParamSet();
1272
2228
  },
1273
2229
 
1274
2230
  // If there is no explicit multi-view configuration, make one up so we don't have
@@ -1286,26 +2242,6 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1286
2242
  return views;
1287
2243
  },
1288
2244
 
1289
- ownParams: function(state) {
1290
- if (!state.parent) {
1291
- return state.params;
1292
- }
1293
- var paramNames = {}; forEach(state.params, function (p) { paramNames[p] = true; });
1294
-
1295
- forEach(state.parent.params, function (p) {
1296
- if (!paramNames[p]) {
1297
- throw new Error("Missing required parameter '" + p + "' in state '" + state.name + "'");
1298
- }
1299
- paramNames[p] = false;
1300
- });
1301
- var ownParams = [];
1302
-
1303
- forEach(paramNames, function (own, p) {
1304
- if (own) ownParams.push(p);
1305
- });
1306
- return ownParams;
1307
- },
1308
-
1309
2245
  // Keep a full path from the root down to this state as this is needed for state activation.
1310
2246
  path: function(state) {
1311
2247
  return state.parent ? state.parent.path.concat(state) : []; // exclude root from path
@@ -1326,12 +2262,16 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1326
2262
  }
1327
2263
 
1328
2264
  function findState(stateOrName, base) {
2265
+ if (!stateOrName) return undefined;
2266
+
1329
2267
  var isStr = isString(stateOrName),
1330
2268
  name = isStr ? stateOrName : stateOrName.name,
1331
2269
  path = isRelative(name);
1332
2270
 
1333
2271
  if (path) {
1334
2272
  if (!base) throw new Error("No reference point given for path '" + name + "'");
2273
+ base = findState(base);
2274
+
1335
2275
  var rel = name.split("."), i = 0, pathLength = rel.length, current = base;
1336
2276
 
1337
2277
  for (; i < pathLength; i++) {
@@ -1364,6 +2304,13 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1364
2304
  queue[parentName].push(state);
1365
2305
  }
1366
2306
 
2307
+ function flushQueuedChildren(parentName) {
2308
+ var queued = queue[parentName] || [];
2309
+ while(queued.length) {
2310
+ registerState(queued.shift());
2311
+ }
2312
+ }
2313
+
1367
2314
  function registerState(state) {
1368
2315
  // Wrap a new object around the state so we can store our private details easily.
1369
2316
  state = inherit(state, {
@@ -1379,6 +2326,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1379
2326
  // Get parent name
1380
2327
  var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.'))
1381
2328
  : (isString(state.parent)) ? state.parent
2329
+ : (isObject(state.parent) && isString(state.parent.name)) ? state.parent.name
1382
2330
  : '';
1383
2331
 
1384
2332
  // If parent is not registered yet, add state to queue and register later
@@ -1395,17 +2343,13 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1395
2343
  if (!state[abstractKey] && state.url) {
1396
2344
  $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) {
1397
2345
  if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) {
1398
- $state.transitionTo(state, $match, { location: false });
2346
+ $state.transitionTo(state, $match, { inherit: true, location: false });
1399
2347
  }
1400
2348
  }]);
1401
2349
  }
1402
2350
 
1403
2351
  // Register any queued children
1404
- if (queue[name]) {
1405
- for (var i = 0; i < queue[name].length; i++) {
1406
- registerState(queue[name][i]);
1407
- }
1408
- }
2352
+ flushQueuedChildren(name);
1409
2353
 
1410
2354
  return state;
1411
2355
  }
@@ -1420,14 +2364,21 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1420
2364
  var globSegments = glob.split('.'),
1421
2365
  segments = $state.$current.name.split('.');
1422
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
+
1423
2374
  //match greedy starts
1424
2375
  if (globSegments[0] === '**') {
1425
- segments = segments.slice(segments.indexOf(globSegments[1]));
2376
+ segments = segments.slice(indexOf(segments, globSegments[1]));
1426
2377
  segments.unshift('**');
1427
2378
  }
1428
2379
  //match greedy ends
1429
2380
  if (globSegments[globSegments.length - 1] === '**') {
1430
- segments.splice(segments.indexOf(globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE);
2381
+ segments.splice(indexOf(segments, globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE);
1431
2382
  segments.push('**');
1432
2383
  }
1433
2384
 
@@ -1435,13 +2386,6 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1435
2386
  return false;
1436
2387
  }
1437
2388
 
1438
- //match single stars
1439
- for (var i = 0, l = globSegments.length; i < l; i++) {
1440
- if (globSegments[i] === '*') {
1441
- segments[i] = '*';
1442
- }
1443
- }
1444
-
1445
2389
  return segments.join('') === globSegments.join('');
1446
2390
  }
1447
2391
 
@@ -1489,7 +2433,8 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1489
2433
  * - **parent** `{object}` - returns the parent state object.
1490
2434
  * - **data** `{object}` - returns state data, including any inherited data that is not
1491
2435
  * overridden by own values (if any).
1492
- * - **url** `{object}` - returns a {link ui.router.util.type:UrlMatcher} or null.
2436
+ * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher}
2437
+ * or `null`.
1493
2438
  * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is
1494
2439
  * navigable).
1495
2440
  * - **params** `{object}` - returns an array of state params that are ensured to
@@ -1505,17 +2450,17 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1505
2450
  * - **path** `{string}` - returns the full path from the root down to this state.
1506
2451
  * Needed for state activation.
1507
2452
  * - **includes** `{object}` - returns an object that includes every state that
1508
- * would pass a '$state.includes()' test.
2453
+ * would pass a `$state.includes()` test.
1509
2454
  *
1510
2455
  * @example
1511
2456
  * <pre>
1512
2457
  * // Override the internal 'views' builder with a function that takes the state
1513
2458
  * // definition, and a reference to the internal function being overridden:
1514
- * $stateProvider.decorator('views', function ($state, parent) {
2459
+ * $stateProvider.decorator('views', function (state, parent) {
1515
2460
  * var result = {},
1516
2461
  * views = parent(state);
1517
2462
  *
1518
- * angular.forEach(view, function (config, name) {
2463
+ * angular.forEach(views, function (config, name) {
1519
2464
  * var autoName = (state.name + '.' + name).replace('.', '/');
1520
2465
  * config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
1521
2466
  * result[name] = config;
@@ -1571,9 +2516,12 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1571
2516
  * Registers a state configuration under a given state name. The stateConfig object
1572
2517
  * has the following acceptable properties.
1573
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
1574
2523
  * <a id='template'></a>
1575
- *
1576
- * - **`template`** - {string|function=} - html template as a string or a function that returns
2524
+ * html template as a string or a function that returns
1577
2525
  * an html template as a string which should be used by the uiView directives. This property
1578
2526
  * takes precedence over templateUrl.
1579
2527
  *
@@ -1582,9 +2530,17 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1582
2530
  * - {array.&lt;object&gt;} - state parameters extracted from the current $location.path() by
1583
2531
  * applying the current state
1584
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
1585
2541
  * <a id='templateUrl'></a>
1586
2542
  *
1587
- * - **`templateUrl`** - {string|function=} - path or function that returns a path to an html
2543
+ * path or function that returns a path to an html
1588
2544
  * template that should be used by uiView.
1589
2545
  *
1590
2546
  * If `templateUrl` is a function, it will be called with the following parameters:
@@ -1592,82 +2548,261 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1592
2548
  * - {array.&lt;object&gt;} - state parameters extracted from the current $location.path() by
1593
2549
  * applying the current state
1594
2550
  *
1595
- * <a id='templateProvider'></a>
2551
+ * <pre>templateUrl: "home.html"</pre>
2552
+ * <pre>templateUrl: function(params) {
2553
+ * return myTemplates[params.pageId]; }</pre>
1596
2554
  *
1597
- * - **`templateProvider`** - {function=} - Provider function that returns HTML content
1598
- * string.
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>
1599
2562
  *
2563
+ * @param {string|function=} stateConfig.controller
1600
2564
  * <a id='controller'></a>
1601
2565
  *
1602
- * - **`controller`** - {string|function=} - Controller fn that should be associated with newly
2566
+ * Controller fn that should be associated with newly
1603
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>
1604
2574
  *
2575
+ * @param {function=} stateConfig.controllerProvider
1605
2576
  * <a id='controllerProvider'></a>
1606
2577
  *
1607
- * - **`controllerProvider`** - {function=} - Injectable provider function that returns
1608
- * the actual controller or string.
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>
1609
2589
  *
2590
+ * @param {string=} stateConfig.controllerAs
1610
2591
  * <a id='controllerAs'></a>
1611
2592
  *
1612
- * - **`controllerAs`** – {string=} – A controller alias name. If present the controller will be
2593
+ * A controller alias name. If present the controller will be
1613
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.
1614
2600
  *
2601
+ * <pre>parent: 'parentState'</pre>
2602
+ * <pre>parent: parentState // JS variable</pre>
2603
+ *
2604
+ * @param {object=} stateConfig.resolve
1615
2605
  * <a id='resolve'></a>
1616
2606
  *
1617
- * - **`resolve`** - {object.&lt;string, function&gt;=} - An optional map of dependencies which
2607
+ * An optional map&lt;string, function&gt; of dependencies which
1618
2608
  * should be injected into the controller. If any of these dependencies are promises,
1619
- * the router will wait for them all to be resolved or one to be rejected before the
1620
- * controller is instantiated. If all the promises are resolved successfully, the values
1621
- * of the resolved promises are injected and $stateChangeSuccess event is fired. If any
1622
- * of the promises are rejected the $stateChangeError event is fired. The map object is:
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:
1623
2615
  *
1624
2616
  * - key - {string}: name of dependency to be injected into controller
1625
2617
  * - factory - {string|function}: If string then it is alias for service. Otherwise if function,
1626
2618
  * it is injected and return value it treated as dependency. If result is a promise, it is
1627
2619
  * resolved before its value is injected into controller.
1628
2620
  *
1629
- * <a id='url'></a>
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.
1630
2725
  *
1631
- * - **`url`** - {string=} - A url with optional parameters. When a state is navigated or
1632
- * transitioned to, the `$stateParams` service will be populated with any
1633
- * parameters that were passed.
2726
+ * <pre>data: {
2727
+ * requiredRole: 'foo'
2728
+ * } </pre>
1634
2729
  *
2730
+ * @param {object=} stateConfig.params
1635
2731
  * <a id='params'></a>
1636
2732
  *
1637
- * - **`params`** - {object=} - An array of parameter names or regular expressions. Only
1638
- * use this within a state if you are not using url. Otherwise you can specify your
1639
- * parameters within the url. When a state is navigated or transitioned to, the
1640
- * $stateParams service will be populated with any parameters that were passed.
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.
1641
2736
  *
1642
- * <a id='views'></a>
2737
+ * Each parameter configuration object may contain the following properties:
1643
2738
  *
1644
- * - **`views`** - {object=} - Use the views property to set up multiple views or to target views
1645
- * manually/explicitly.
2739
+ * - ** value ** - {object|function=}: specifies the default value for this
2740
+ * parameter. This implicitly sets this parameter as optional.
1646
2741
  *
1647
- * <a id='abstract'></a>
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.
1648
2746
  *
1649
- * - **`abstract`** - {boolean=} - An abstract state will never be directly activated,
1650
- * but can provide inherited properties to its common children states.
2747
+ * *Note*: `undefined` is treated as "no default value" while `null`
2748
+ * is treated as "the default value is `null`".
1651
2749
  *
1652
- * <a id='onEnter'></a>
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.:
1653
2754
  *
1654
- * - **`onEnter`** - {object=} - Callback function for when a state is entered. Good way
1655
- * to trigger an action or dispatch an event, such as opening a dialog.
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>
1656
2764
  *
1657
- * <a id='onExit'></a>
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.
1658
2769
  *
1659
- * - **`onExit`** - {object=} - Callback function for when a state is exited. Good way to
1660
- * trigger an action or dispatch an event, such as opening a dialog.
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' }`).
1661
2775
  *
1662
- * <a id='reloadOnSearch'></a>
2776
+ * <pre>params: {
2777
+ * param1: { array: true }
2778
+ * }</pre>
1663
2779
  *
1664
- * - **`reloadOnSearch = true`** - {boolean=} - If `false`, will not retrigger the same state
1665
- * just because a search/query parameter has changed (via $location.search() or $location.hash()).
1666
- * Useful for when you'd like to modify $location.search() without triggering a reload.
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()`})
1667
2784
  *
1668
- * <a id='data'></a>
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>
1669
2805
  *
1670
- * - **`data`** - {object=} - Arbitrary data object, useful for custom configuration.
1671
2806
  *
1672
2807
  * @example
1673
2808
  * <pre>
@@ -1676,7 +2811,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1676
2811
  * // stateName can be a single top-level name (must be unique).
1677
2812
  * $stateProvider.state("home", {});
1678
2813
  *
1679
- * // Or it can be a nested state name. This state is a child of the
2814
+ * // Or it can be a nested state name. This state is a child of the
1680
2815
  * // above "home" state.
1681
2816
  * $stateProvider.state("home.newest", {});
1682
2817
  *
@@ -1690,9 +2825,6 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1690
2825
  * .state("contacts", {});
1691
2826
  * </pre>
1692
2827
  *
1693
- * @param {string} name A unique state name, e.g. "home", "about", "contacts".
1694
- * To create a parent/child state use a dot, e.g. "about.sales", "home.newest".
1695
- * @param {object} definition State configuration object.
1696
2828
  */
1697
2829
  this.state = state;
1698
2830
  function state(name, definition) {
@@ -1713,6 +2845,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1713
2845
  * @requires $injector
1714
2846
  * @requires ui.router.util.$resolve
1715
2847
  * @requires ui.router.state.$stateParams
2848
+ * @requires ui.router.router.$urlRouter
1716
2849
  *
1717
2850
  * @property {object} params A param object, e.g. {sectionId: section.id)}, that
1718
2851
  * you'd like to test against the current active state.
@@ -1726,26 +2859,82 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1726
2859
  * between them. It also provides interfaces to ask for current state or even states
1727
2860
  * you're coming from.
1728
2861
  */
1729
- // $urlRouter is injected just to ensure it gets instantiated
1730
2862
  this.$get = $get;
1731
- $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$location', '$urlRouter', '$browser'];
1732
- function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $location, $urlRouter, $browser) {
2863
+ $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter', '$location', '$urlMatcherFactory'];
2864
+ function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter, $location, $urlMatcherFactory) {
1733
2865
 
1734
2866
  var TransitionSuperseded = $q.reject(new Error('transition superseded'));
1735
2867
  var TransitionPrevented = $q.reject(new Error('transition prevented'));
1736
2868
  var TransitionAborted = $q.reject(new Error('transition aborted'));
1737
2869
  var TransitionFailed = $q.reject(new Error('transition failed'));
1738
- var currentLocation = $location.url();
1739
- var baseHref = $browser.baseHref();
1740
2870
 
1741
- function syncUrl() {
1742
- if ($location.url() !== currentLocation) {
1743
- $location.url(currentLocation);
1744
- $location.replace();
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;
1745
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;
1746
2934
  }
1747
2935
 
1748
2936
  root.locals = { resolve: null, globals: { $stateParams: {} } };
2937
+
1749
2938
  $state = {
1750
2939
  params: {},
1751
2940
  current: root.self,
@@ -1759,8 +2948,8 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1759
2948
  * @methodOf ui.router.state.$state
1760
2949
  *
1761
2950
  * @description
1762
- * A method that force reloads the current state. All resolves are re-resolved, events are not re-fired,
1763
- * and controllers reinstantiated (bug with controllers reinstantiating right now, fixing soon).
2951
+ * A method that force reloads the current state. All resolves are re-resolved,
2952
+ * controllers reinstantiated, and events re-fired.
1764
2953
  *
1765
2954
  * @example
1766
2955
  * <pre>
@@ -1776,12 +2965,37 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1776
2965
  * `reload()` is just an alias for:
1777
2966
  * <pre>
1778
2967
  * $state.transitionTo($state.current, $stateParams, {
1779
- * reload: true, inherit: false, notify: false
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
1780
2991
  * });
1781
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}.
1782
2996
  */
1783
- $state.reload = function reload() {
1784
- $state.transitionTo($state.current, $stateParams, { reload: true, inherit: false, notify: false });
2997
+ $state.reload = function reload(state) {
2998
+ return $state.transitionTo($state.current, $stateParams, { reload: state || true, inherit: false, notify: true});
1785
2999
  };
1786
3000
 
1787
3001
  /**
@@ -1851,7 +3065,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1851
3065
  *
1852
3066
  */
1853
3067
  $state.go = function go(to, params, options) {
1854
- return this.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options));
3068
+ return $state.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options));
1855
3069
  };
1856
3070
 
1857
3071
  /**
@@ -1885,9 +3099,11 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1885
3099
  * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'),
1886
3100
  * defines which state to be relative from.
1887
3101
  * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
1888
- * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params
3102
+ * - **`reload`** (v0.2.5) - {boolean=false|string=|object=}, If `true` will force transition even if the state or params
1889
3103
  * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
1890
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.
1891
3107
  *
1892
3108
  * @returns {promise} A promise representing the state of the new transition. See
1893
3109
  * {@link ui.router.state.$state#methods_go $state.go}.
@@ -1901,64 +3117,15 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1901
3117
  var from = $state.$current, fromParams = $state.params, fromPath = from.path;
1902
3118
  var evt, toState = findState(to, options.relative);
1903
3119
 
3120
+ // Store the hash param for later (since it will be stripped out by various methods)
3121
+ var hash = toParams['#'];
3122
+
1904
3123
  if (!isDefined(toState)) {
1905
- // Broadcast not found event and abort the transition if prevented
1906
3124
  var redirect = { to: to, toParams: toParams, options: options };
3125
+ var redirectResult = handleRedirect(redirect, from.self, fromParams, options);
1907
3126
 
1908
- /**
1909
- * @ngdoc event
1910
- * @name ui.router.state.$state#$stateNotFound
1911
- * @eventOf ui.router.state.$state
1912
- * @eventType broadcast on root scope
1913
- * @description
1914
- * Fired when a requested state **cannot be found** using the provided state name during transition.
1915
- * The event is broadcast allowing any handlers a single chance to deal with the error (usually by
1916
- * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler,
1917
- * you can see its three properties in the example. You can use `event.preventDefault()` to abort the
1918
- * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value.
1919
- *
1920
- * @param {Object} event Event object.
1921
- * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties.
1922
- * @param {State} fromState Current state object.
1923
- * @param {Object} fromParams Current state params.
1924
- *
1925
- * @example
1926
- *
1927
- * <pre>
1928
- * // somewhere, assume lazy.state has not been defined
1929
- * $state.go("lazy.state", {a:1, b:2}, {inherit:false});
1930
- *
1931
- * // somewhere else
1932
- * $scope.$on('$stateNotFound',
1933
- * function(event, unfoundState, fromState, fromParams){
1934
- * console.log(unfoundState.to); // "lazy.state"
1935
- * console.log(unfoundState.toParams); // {a:1, b:2}
1936
- * console.log(unfoundState.options); // {inherit:false} + default options
1937
- * })
1938
- * </pre>
1939
- */
1940
- evt = $rootScope.$broadcast('$stateNotFound', redirect, from.self, fromParams);
1941
- if (evt.defaultPrevented) {
1942
- syncUrl();
1943
- return TransitionAborted;
1944
- }
1945
-
1946
- // Allow the handler to return a promise to defer state lookup retry
1947
- if (evt.retry) {
1948
- if (options.$retry) {
1949
- syncUrl();
1950
- return TransitionFailed;
1951
- }
1952
- var retryTransition = $state.transition = $q.when(evt.retry);
1953
- retryTransition.then(function() {
1954
- if (retryTransition !== $state.transition) return TransitionSuperseded;
1955
- redirect.options.$retry = true;
1956
- return $state.transitionTo(redirect.to, redirect.toParams, redirect.options);
1957
- }, function() {
1958
- return TransitionAborted;
1959
- });
1960
- syncUrl();
1961
- return retryTransition;
3127
+ if (redirectResult) {
3128
+ return redirectResult;
1962
3129
  }
1963
3130
 
1964
3131
  // Always retry once if the $stateNotFound was not prevented
@@ -1967,38 +3134,68 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1967
3134
  toParams = redirect.toParams;
1968
3135
  options = redirect.options;
1969
3136
  toState = findState(to, options.relative);
3137
+
1970
3138
  if (!isDefined(toState)) {
1971
- if (options.relative) throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'");
1972
- throw new Error("No such state '" + to + "'");
3139
+ if (!options.relative) throw new Error("No such state '" + to + "'");
3140
+ throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'");
1973
3141
  }
1974
3142
  }
1975
3143
  if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'");
1976
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);
1977
3148
  to = toState;
1978
3149
 
1979
3150
  var toPath = to.path;
1980
3151
 
1981
3152
  // Starting from the root of the path, keep all levels that haven't changed
1982
- var keep, state, locals = root.locals, toLocals = [];
1983
- for (keep = 0, state = toPath[keep];
1984
- state && state === fromPath[keep] && equalForKeys(toParams, fromParams, state.ownParams) && !options.reload;
1985
- keep++, state = toPath[keep]) {
1986
- locals = toLocals[keep] = state.locals;
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
+ }
1987
3176
  }
1988
3177
 
1989
3178
  // If we're going to the same state and all locals are kept, we've got nothing to do.
1990
3179
  // But clear 'transition', as we still want to cancel any other pending transitions.
1991
- // TODO: We may not want to bump 'transition' if we're called from a location change that we've initiated ourselves,
1992
- // because we might accidentally abort a legitimate transition initiated from code?
1993
- if (shouldTriggerReload(to, from, locals, options) ) {
1994
- if ( to.self.reloadOnSearch !== false )
1995
- syncUrl();
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
+ }
1996
3193
  $state.transition = null;
1997
3194
  return $q.when($state.current);
1998
3195
  }
1999
3196
 
2000
- // Normalize/filter parameters before we pass them to event handlers etc.
2001
- toParams = normalize(to.params, toParams || {});
3197
+ // Filter parameters before we pass them to event handlers etc.
3198
+ toParams = filterByKeys(to.params.$$keys(), toParams || {});
2002
3199
 
2003
3200
  // Broadcast start event and cancel the transition if requested
2004
3201
  if (options.notify) {
@@ -2029,9 +3226,9 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2029
3226
  * })
2030
3227
  * </pre>
2031
3228
  */
2032
- evt = $rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams);
2033
- if (evt.defaultPrevented) {
2034
- syncUrl();
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();
2035
3232
  return TransitionPrevented;
2036
3233
  }
2037
3234
  }
@@ -2044,9 +3241,10 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2044
3241
  // empty and gets filled asynchronously. We need to keep track of the promise for the
2045
3242
  // (fully resolved) current locals, and pass this down the chain.
2046
3243
  var resolved = $q.when(locals);
2047
- for (var l=keep; l<toPath.length; l++, state=toPath[l]) {
3244
+
3245
+ for (var l = keep; l < toPath.length; l++, state = toPath[l]) {
2048
3246
  locals = toLocals[l] = inherit(locals);
2049
- resolved = resolveState(state, toParams, state===to, resolved, locals);
3247
+ resolved = resolveState(state, toParams, state === to, resolved, locals, options);
2050
3248
  }
2051
3249
 
2052
3250
  // Once everything is resolved, we are ready to perform the actual transition
@@ -2059,7 +3257,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2059
3257
  if ($state.transition !== transition) return TransitionSuperseded;
2060
3258
 
2061
3259
  // Exit 'from' states not kept
2062
- for (l=fromPath.length-1; l>=keep; l--) {
3260
+ for (l = fromPath.length - 1; l >= keep; l--) {
2063
3261
  exiting = fromPath[l];
2064
3262
  if (exiting.self.onExit) {
2065
3263
  $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals);
@@ -2068,7 +3266,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2068
3266
  }
2069
3267
 
2070
3268
  // Enter 'to' states not kept
2071
- for (l=keep; l<toPath.length; l++) {
3269
+ for (l = keep; l < toPath.length; l++) {
2072
3270
  entering = toPath[l];
2073
3271
  entering.locals = toLocals[l];
2074
3272
  if (entering.self.onEnter) {
@@ -2076,6 +3274,9 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2076
3274
  }
2077
3275
  }
2078
3276
 
3277
+ // Re-add the saved hash before we start returning things
3278
+ if (hash) toParams['#'] = hash;
3279
+
2079
3280
  // Run it again, to catch any transitions in callbacks
2080
3281
  if ($state.transition !== transition) return TransitionSuperseded;
2081
3282
 
@@ -2086,14 +3287,10 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2086
3287
  copy($state.params, $stateParams);
2087
3288
  $state.transition = null;
2088
3289
 
2089
- // Update $location
2090
- var toNav = to.navigable;
2091
- if (options.location && toNav) {
2092
- $location.url(toNav.url.format(toNav.locals.globals.$stateParams));
2093
-
2094
- if (options.location === 'replace') {
2095
- $location.replace();
2096
- }
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
+ });
2097
3294
  }
2098
3295
 
2099
3296
  if (options.notify) {
@@ -2113,7 +3310,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2113
3310
  */
2114
3311
  $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams);
2115
3312
  }
2116
- currentLocation = $location.url();
3313
+ $urlRouter.update(true);
2117
3314
 
2118
3315
  return $state.current;
2119
3316
  }, function (error) {
@@ -2138,8 +3335,11 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2138
3335
  * @param {Object} fromParams The params supplied to the `fromState`.
2139
3336
  * @param {Error} error The resolve error object.
2140
3337
  */
2141
- $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error);
2142
- syncUrl();
3338
+ evt = $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error);
3339
+
3340
+ if (!evt.defaultPrevented) {
3341
+ $urlRouter.update();
3342
+ }
2143
3343
 
2144
3344
  return $q.reject(error);
2145
3345
  });
@@ -2154,35 +3354,40 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2154
3354
  *
2155
3355
  * @description
2156
3356
  * Similar to {@link ui.router.state.$state#methods_includes $state.includes},
2157
- * but only checks for the full state name. If params is supplied then it will be
2158
- * tested for strict equality against the current active params object, so all params
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
2159
3359
  * must match with none missing and no extras.
2160
3360
  *
2161
3361
  * @example
2162
3362
  * <pre>
3363
+ * $state.$current.name = 'contacts.details.item';
3364
+ *
3365
+ * // absolute name
2163
3366
  * $state.is('contact.details.item'); // returns true
2164
3367
  * $state.is(contactDetailItemStateObject); // returns true
2165
3368
  *
2166
- * // everything else would return false
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>
2167
3372
  * </pre>
2168
3373
  *
2169
- * @param {string|object} stateName The state name or state object you'd like to check.
2170
- * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like
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
2171
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
+ *
2172
3382
  * @returns {boolean} Returns true if it is the state.
2173
3383
  */
2174
- $state.is = function is(stateOrName, params) {
2175
- var state = findState(stateOrName);
2176
-
2177
- if (!isDefined(state)) {
2178
- return undefined;
2179
- }
2180
-
2181
- if ($state.$current !== state) {
2182
- return false;
2183
- }
3384
+ $state.is = function is(stateOrName, params, options) {
3385
+ options = extend({ relative: $state.$current }, options || {});
3386
+ var state = findState(stateOrName, options.relative);
2184
3387
 
2185
- return isDefined(params) && params !== null ? angular.equals($stateParams, params) : true;
3388
+ if (!isDefined(state)) { return undefined; }
3389
+ if ($state.$current !== state) { return false; }
3390
+ return params ? equalForKeys(state.params.$$values(params), $stateParams) : true;
2186
3391
  };
2187
3392
 
2188
3393
  /**
@@ -2191,25 +3396,28 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2191
3396
  * @methodOf ui.router.state.$state
2192
3397
  *
2193
3398
  * @description
2194
- * A method to determine if the current active state is equal to or is the child of the
3399
+ * A method to determine if the current active state is equal to or is the child of the
2195
3400
  * state stateName. If any params are passed then they will be tested for a match as well.
2196
3401
  * Not all the parameters need to be passed, just the ones you'd like to test for equality.
2197
3402
  *
2198
3403
  * @example
3404
+ * Partial and relative names
2199
3405
  * <pre>
2200
3406
  * $state.$current.name = 'contacts.details.item';
2201
3407
  *
3408
+ * // Using partial names
2202
3409
  * $state.includes("contacts"); // returns true
2203
3410
  * $state.includes("contacts.details"); // returns true
2204
3411
  * $state.includes("contacts.details.item"); // returns true
2205
3412
  * $state.includes("contacts.list"); // returns false
2206
3413
  * $state.includes("about"); // returns false
2207
- * </pre>
2208
3414
  *
2209
- * @description
2210
- * Basic globing patterns will also work.
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>
2211
3419
  *
2212
- * @example
3420
+ * Basic globbing patterns
2213
3421
  * <pre>
2214
3422
  * $state.$current.name = 'contacts.details.item.url';
2215
3423
  *
@@ -2222,37 +3430,30 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2222
3430
  * $state.includes("item.**"); // returns false
2223
3431
  * </pre>
2224
3432
  *
2225
- * @param {string} stateOrName A partial name to be searched for within the current state name.
2226
- * @param {object} params A param object, e.g. `{sectionId: section.id}`,
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}`,
2227
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
+ *
2228
3442
  * @returns {boolean} Returns true if it does include the state
2229
3443
  */
2230
-
2231
- $state.includes = function includes(stateOrName, params) {
3444
+ $state.includes = function includes(stateOrName, params, options) {
3445
+ options = extend({ relative: $state.$current }, options || {});
2232
3446
  if (isString(stateOrName) && isGlob(stateOrName)) {
2233
- if (doesStateMatchGlob(stateOrName)) {
2234
- stateOrName = $state.$current.name;
2235
- } else {
3447
+ if (!doesStateMatchGlob(stateOrName)) {
2236
3448
  return false;
2237
3449
  }
3450
+ stateOrName = $state.$current.name;
2238
3451
  }
2239
3452
 
2240
- var state = findState(stateOrName);
2241
- if (!isDefined(state)) {
2242
- return undefined;
2243
- }
2244
-
2245
- if (!isDefined($state.$current.includes[state.name])) {
2246
- return false;
2247
- }
2248
-
2249
- var validParams = true;
2250
- angular.forEach(params, function(value, key) {
2251
- if (!isDefined($stateParams[key]) || $stateParams[key] !== value) {
2252
- validParams = false;
2253
- }
2254
- });
2255
- return validParams;
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;
2256
3457
  };
2257
3458
 
2258
3459
 
@@ -2276,7 +3477,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2276
3477
  * - **`lossy`** - {boolean=true} - If true, and if there is no url associated with the state provided in the
2277
3478
  * first parameter, then the constructed href url will be built from the first navigable ancestor (aka
2278
3479
  * ancestor with a valid url).
2279
- * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url.
3480
+ * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
2280
3481
  * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
2281
3482
  * defines which state to be relative from.
2282
3483
  * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
@@ -2284,33 +3485,26 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2284
3485
  * @returns {string} compiled state url
2285
3486
  */
2286
3487
  $state.href = function href(stateOrName, params, options) {
2287
- options = extend({ lossy: true, inherit: false, absolute: false, relative: $state.$current }, options || {});
3488
+ options = extend({
3489
+ lossy: true,
3490
+ inherit: true,
3491
+ absolute: false,
3492
+ relative: $state.$current
3493
+ }, options || {});
3494
+
2288
3495
  var state = findState(stateOrName, options.relative);
2289
- if (!isDefined(state)) return null;
2290
3496
 
2291
- params = inheritParams($stateParams, params || {}, $state.$current, state);
3497
+ if (!isDefined(state)) return null;
3498
+ if (options.inherit) params = inheritParams($stateParams, params || {}, $state.$current, state);
3499
+
2292
3500
  var nav = (state && options.lossy) ? state.navigable : state;
2293
- var url = (nav && nav.url) ? nav.url.format(normalize(state.params, params || {})) : null;
2294
- if (!$locationProvider.html5Mode() && url) {
2295
- url = "#" + $locationProvider.hashPrefix() + url;
2296
- }
2297
-
2298
- if (baseHref !== '/') {
2299
- if ($locationProvider.html5Mode()) {
2300
- url = baseHref.slice(0, -1) + url;
2301
- } else if (options.absolute){
2302
- url = baseHref.slice(1) + url;
2303
- }
2304
- }
2305
3501
 
2306
- if (options.absolute && url) {
2307
- url = $location.protocol() + '://' +
2308
- $location.host() +
2309
- ($location.port() == 80 || $location.port() == 443 ? '' : ':' + $location.port()) +
2310
- (!$locationProvider.html5Mode() && url ? '/' : '') +
2311
- url;
3502
+ if (!nav || nav.url === undefined || nav.url === null) {
3503
+ return null;
2312
3504
  }
2313
- return url;
3505
+ return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys().concat('#'), params || {}), {
3506
+ absolute: options.absolute
3507
+ });
2314
3508
  };
2315
3509
 
2316
3510
  /**
@@ -2321,26 +3515,23 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2321
3515
  * @description
2322
3516
  * Returns the state configuration object for any specific state or all states.
2323
3517
  *
2324
- * @param {string|object=} stateOrName If provided, will only get the config for
3518
+ * @param {string|object=} stateOrName (absolute or relative) If provided, will only get the config for
2325
3519
  * the requested state. If not provided, returns an array of ALL state configs.
2326
- * @returns {object|array} State configuration object or array of all objects.
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.
2327
3522
  */
2328
3523
  $state.get = function (stateOrName, context) {
2329
- if (!isDefined(stateOrName)) {
2330
- var list = [];
2331
- forEach(states, function(state) { list.push(state.self); });
2332
- return list;
2333
- }
2334
- var state = findState(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);
2335
3526
  return (state && state.self) ? state.self : null;
2336
3527
  };
2337
3528
 
2338
- function resolveState(state, params, paramsAreFiltered, inherited, dst) {
3529
+ function resolveState(state, params, paramsAreFiltered, inherited, dst, options) {
2339
3530
  // Make a restricted $stateParams with only the parameters that apply to this state if
2340
3531
  // necessary. In addition to being available to the controller and onEnter/onExit callbacks,
2341
3532
  // we also need $stateParams to be available for any $injector calls we make during the
2342
3533
  // dependency resolution process.
2343
- var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params, params);
3534
+ var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params);
2344
3535
  var locals = { $stateParams: $stateParams };
2345
3536
 
2346
3537
  // Resolve 'global' dependencies for the state, i.e. those not specific to a view.
@@ -2348,35 +3539,43 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2348
3539
  // to the set that should be visible to the state, and are independent of when we update
2349
3540
  // the global $state and $stateParams values.
2350
3541
  dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);
2351
- var promises = [ dst.resolve.then(function (globals) {
3542
+ var promises = [dst.resolve.then(function (globals) {
2352
3543
  dst.globals = globals;
2353
- }) ];
3544
+ })];
2354
3545
  if (inherited) promises.push(inherited);
2355
3546
 
2356
- // Resolve template and dependencies for all views.
2357
- forEach(state.views, function (view, name) {
2358
- var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {});
2359
- injectables.$template = [ function () {
2360
- return $view.load(name, { view: view, locals: locals, params: $stateParams, notify: false }) || '';
2361
- }];
2362
-
2363
- promises.push($resolve.resolve(injectables, locals, dst.resolve, state).then(function (result) {
2364
- // References to the controller (only instantiated at link time)
2365
- if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) {
2366
- var injectLocals = angular.extend({}, injectables, locals);
2367
- result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals);
2368
- } else {
2369
- result.$$controller = view.controller;
2370
- }
2371
- // Provide access to the state itself for internal use
2372
- result.$$state = state;
2373
- result.$$controllerAs = view.controllerAs;
2374
- dst[name] = result;
2375
- }));
2376
- });
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
+ }
2377
3576
 
2378
3577
  // Wait for all the promises and then return the activation object
2379
- return $q.all(promises).then(function (values) {
3578
+ return $q.all(promises).then(resolveViews).then(function (values) {
2380
3579
  return dst;
2381
3580
  });
2382
3581
  }
@@ -2384,8 +3583,27 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2384
3583
  return $state;
2385
3584
  }
2386
3585
 
2387
- function shouldTriggerReload(to, from, locals, options) {
2388
- if ( to === from && ((locals === from.locals && !options.reload) || (to.self.reloadOnSearch === false)) ) {
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)))) {
2389
3607
  return true;
2390
3608
  }
2391
3609
  }
@@ -2511,7 +3729,7 @@ function $ViewScrollProvider() {
2511
3729
  }
2512
3730
 
2513
3731
  return function ($element) {
2514
- $timeout(function () {
3732
+ return $timeout(function () {
2515
3733
  $element[0].scrollIntoView();
2516
3734
  }, 0, false);
2517
3735
  };
@@ -2536,7 +3754,7 @@ angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider)
2536
3754
  * @description
2537
3755
  * The ui-view directive tells $state where to place your templates.
2538
3756
  *
2539
- * @param {string=} ui-view A view name. The name should be unique amongst the other views in the
3757
+ * @param {string=} name A view name. The name should be unique amongst the other views in the
2540
3758
  * same state. You can have views of the same name that live in different states.
2541
3759
  *
2542
3760
  * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window
@@ -2633,8 +3851,8 @@ angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider)
2633
3851
  * <ui-view autoscroll='scopeVariable'/>
2634
3852
  * </pre>
2635
3853
  */
2636
- $ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll'];
2637
- function $ViewDirective( $state, $injector, $uiViewScroll) {
3854
+ $ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll', '$interpolate'];
3855
+ function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate) {
2638
3856
 
2639
3857
  function getService() {
2640
3858
  return ($injector.has) ? function(service) {
@@ -2664,8 +3882,14 @@ function $ViewDirective( $state, $injector, $uiViewScroll) {
2664
3882
 
2665
3883
  if ($animate) {
2666
3884
  return {
2667
- enter: function(element, target, cb) { $animate.enter(element, null, target, cb); },
2668
- leave: function(element, cb) { $animate.leave(element, cb); }
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
+ }
2669
3893
  };
2670
3894
  }
2671
3895
 
@@ -2724,14 +3948,20 @@ function $ViewDirective( $state, $injector, $uiViewScroll) {
2724
3948
  }
2725
3949
 
2726
3950
  function updateView(firstTime) {
2727
- var newScope = scope.$new(),
2728
- name = currentEl && currentEl.data('$uiViewName'),
3951
+ var newScope,
3952
+ name = getUiViewName(scope, attrs, $element, $interpolate),
2729
3953
  previousLocals = name && $state.$current && $state.$current.locals[name];
2730
3954
 
2731
3955
  if (!firstTime && previousLocals === latestLocals) return; // nothing to do
3956
+ newScope = scope.$new();
3957
+ latestLocals = $state.$current.locals[name];
2732
3958
 
2733
3959
  var clone = $transclude(newScope, function(clone) {
2734
3960
  renderer.enter(clone, $element, function onUiViewEnter() {
3961
+ if(currentScope) {
3962
+ currentScope.$emit('$viewContentAnimationEnded');
3963
+ }
3964
+
2735
3965
  if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) {
2736
3966
  $uiViewScroll(clone);
2737
3967
  }
@@ -2739,8 +3969,6 @@ function $ViewDirective( $state, $injector, $uiViewScroll) {
2739
3969
  cleanupLastView();
2740
3970
  });
2741
3971
 
2742
- latestLocals = $state.$current.locals[clone.data('$uiViewName')];
2743
-
2744
3972
  currentEl = clone;
2745
3973
  currentScope = newScope;
2746
3974
  /**
@@ -2763,24 +3991,16 @@ function $ViewDirective( $state, $injector, $uiViewScroll) {
2763
3991
  return directive;
2764
3992
  }
2765
3993
 
2766
- $ViewDirectiveFill.$inject = ['$compile', '$controller', '$state'];
2767
- function $ViewDirectiveFill ($compile, $controller, $state) {
3994
+ $ViewDirectiveFill.$inject = ['$compile', '$controller', '$state', '$interpolate'];
3995
+ function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate) {
2768
3996
  return {
2769
3997
  restrict: 'ECA',
2770
3998
  priority: -400,
2771
3999
  compile: function (tElement) {
2772
4000
  var initial = tElement.html();
2773
4001
  return function (scope, $element, attrs) {
2774
- var name = attrs.uiView || attrs.name || '',
2775
- inherited = $element.inheritedData('$uiView');
2776
-
2777
- if (name.indexOf('@') < 0) {
2778
- name = name + '@' + (inherited ? inherited.state.name : '');
2779
- }
2780
-
2781
- $element.data('$uiViewName', name);
2782
-
2783
4002
  var current = $state.$current,
4003
+ name = getUiViewName(scope, attrs, $element, $interpolate),
2784
4004
  locals = current && current.locals[name];
2785
4005
 
2786
4006
  if (! locals) {
@@ -2794,6 +4014,7 @@ function $ViewDirectiveFill ($compile, $controller, $state) {
2794
4014
 
2795
4015
  if (locals.$$controller) {
2796
4016
  locals.$scope = scope;
4017
+ locals.$element = $element;
2797
4018
  var controller = $controller(locals.$$controller, locals);
2798
4019
  if (locals.$$controllerAs) {
2799
4020
  scope[locals.$$controllerAs] = controller;
@@ -2808,11 +4029,23 @@ function $ViewDirectiveFill ($compile, $controller, $state) {
2808
4029
  };
2809
4030
  }
2810
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
+
2811
4042
  angular.module('ui.router.state').directive('uiView', $ViewDirective);
2812
4043
  angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill);
2813
4044
 
2814
- function parseStateRef(ref) {
2815
- var parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
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*(\((.*)\))?$/);
2816
4049
  if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'");
2817
4050
  return { state: parsed[1], paramExpr: parsed[3] || null };
2818
4051
  }
@@ -2856,7 +4089,7 @@ function stateContext(el) {
2856
4089
  * Here's an example of how you'd use ui-sref and how it would compile. If you have the
2857
4090
  * following template:
2858
4091
  * <pre>
2859
- * <a ui-sref="home">Home</a> | <a ui-sref="about">About</a>
4092
+ * <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a>
2860
4093
  *
2861
4094
  * <ul>
2862
4095
  * <li ng-repeat="contact in contacts">
@@ -2865,9 +4098,9 @@ function stateContext(el) {
2865
4098
  * </ul>
2866
4099
  * </pre>
2867
4100
  *
2868
- * Then the compiled html would be (assuming Html5Mode is off):
4101
+ * Then the compiled html would be (assuming Html5Mode is off and current state is contacts):
2869
4102
  * <pre>
2870
- * <a href="#/home" ui-sref="home">Home</a> | <a href="#/about" ui-sref="about">About</a>
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>
2871
4104
  *
2872
4105
  * <ul>
2873
4106
  * <li ng-repeat="contact in contacts">
@@ -2889,21 +4122,24 @@ function stateContext(el) {
2889
4122
  */
2890
4123
  $StateRefDirective.$inject = ['$state', '$timeout'];
2891
4124
  function $StateRefDirective($state, $timeout) {
2892
- var allowedOptions = ['location', 'inherit', 'reload'];
4125
+ var allowedOptions = ['location', 'inherit', 'reload', 'absolute'];
2893
4126
 
2894
4127
  return {
2895
4128
  restrict: 'A',
2896
- require: '?^uiSrefActive',
4129
+ require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
2897
4130
  link: function(scope, element, attrs, uiSrefActive) {
2898
- var ref = parseStateRef(attrs.uiSref);
4131
+ var ref = parseStateRef(attrs.uiSref, $state.current.name);
2899
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";
2900
4137
  var isForm = element[0].nodeName === "FORM";
2901
- var attr = isForm ? "action" : "href", nav = true;
4138
+ var attr = isForm ? "action" : hrefKind, nav = true;
2902
4139
 
2903
- var options = {
2904
- relative: base
2905
- };
4140
+ var options = { relative: base, inherit: true };
2906
4141
  var optionsOverride = scope.$eval(attrs.uiSrefOpts) || {};
4142
+
2907
4143
  angular.forEach(allowedOptions, function(option) {
2908
4144
  if (option in optionsOverride) {
2909
4145
  options[option] = optionsOverride[option];
@@ -2911,26 +4147,27 @@ function $StateRefDirective($state, $timeout) {
2911
4147
  });
2912
4148
 
2913
4149
  var update = function(newVal) {
2914
- if (newVal) params = newVal;
4150
+ if (newVal) params = angular.copy(newVal);
2915
4151
  if (!nav) return;
2916
4152
 
2917
- var newHref = $state.href(ref.state, params, options);
4153
+ newHref = $state.href(ref.state, params, options);
2918
4154
 
2919
- if (uiSrefActive) {
2920
- uiSrefActive.$$setStateInfo(ref.state, params);
4155
+ var activeDirective = uiSrefActive[1] || uiSrefActive[0];
4156
+ if (activeDirective) {
4157
+ activeDirective.$$addStateInfo(ref.state, params);
2921
4158
  }
2922
- if (!newHref) {
4159
+ if (newHref === null) {
2923
4160
  nav = false;
2924
4161
  return false;
2925
4162
  }
2926
- element[0][attr] = newHref;
4163
+ attrs.$set(attr, newHref);
2927
4164
  };
2928
4165
 
2929
4166
  if (ref.paramExpr) {
2930
4167
  scope.$watch(ref.paramExpr, function(newVal, oldVal) {
2931
4168
  if (newVal !== params) update(newVal);
2932
4169
  }, true);
2933
- params = scope.$eval(ref.paramExpr);
4170
+ params = angular.copy(scope.$eval(ref.paramExpr));
2934
4171
  }
2935
4172
  update();
2936
4173
 
@@ -2940,10 +4177,17 @@ function $StateRefDirective($state, $timeout) {
2940
4177
  var button = e.which || e.button;
2941
4178
  if ( !(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || element.attr('target')) ) {
2942
4179
  // HACK: This is to allow ng-clicks to be processed before the transition is initiated:
2943
- $timeout(function() {
4180
+ var transition = $timeout(function() {
2944
4181
  $state.go(ref.state, params, options);
2945
4182
  });
2946
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
+ };
2947
4191
  }
2948
4192
  });
2949
4193
  }
@@ -2961,12 +4205,20 @@ function $StateRefDirective($state, $timeout) {
2961
4205
  * @restrict A
2962
4206
  *
2963
4207
  * @description
2964
- * A directive working alongside ui-sref to add classes to an element when the
4208
+ * A directive working alongside ui-sref to add classes to an element when the
2965
4209
  * related ui-sref directive's state is active, and removing them when it is inactive.
2966
- * The primary use-case is to simplify the special appearance of navigation menus
4210
+ * The primary use-case is to simplify the special appearance of navigation menus
2967
4211
  * relying on `ui-sref`, by having the "active" state's menu button appear different,
2968
4212
  * distinguishing it from the inactive menu items.
2969
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
+ *
2970
4222
  * @example
2971
4223
  * Given the following template:
2972
4224
  * <pre>
@@ -2976,8 +4228,9 @@ function $StateRefDirective($state, $timeout) {
2976
4228
  * </li>
2977
4229
  * </ul>
2978
4230
  * </pre>
2979
- *
2980
- * When the app state is "app.user", and contains the state parameter "user" with value "bilbobaggins",
4231
+ *
4232
+ *
4233
+ * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins",
2981
4234
  * the resulting HTML will appear as (note the 'active' class):
2982
4235
  * <pre>
2983
4236
  * <ul>
@@ -2986,10 +4239,10 @@ function $StateRefDirective($state, $timeout) {
2986
4239
  * </li>
2987
4240
  * </ul>
2988
4241
  * </pre>
2989
- *
2990
- * The class name is interpolated **once** during the directives link time (any further changes to the
2991
- * interpolated value are ignored).
2992
- *
4242
+ *
4243
+ * The class name is interpolated **once** during the directives link time (any further changes to the
4244
+ * interpolated value are ignored).
4245
+ *
2993
4246
  * Multiple classes may be specified in a space-separated format:
2994
4247
  * <pre>
2995
4248
  * <ul>
@@ -2999,20 +4252,43 @@ function $StateRefDirective($state, $timeout) {
2999
4252
  * </ul>
3000
4253
  * </pre>
3001
4254
  */
3002
- $StateActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
3003
- function $StateActiveDirective($state, $stateParams, $interpolate) {
3004
- return {
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 {
3005
4274
  restrict: "A",
3006
- controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
3007
- var state, params, activeClass;
4275
+ controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
4276
+ var states = [], activeClass;
3008
4277
 
3009
4278
  // There probably isn't much point in $observing this
3010
- activeClass = $interpolate($attrs.uiSrefActive || '', false)($scope);
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
+ });
3011
4291
 
3012
- // Allow uiSref to communicate with uiSrefActive
3013
- this.$$setStateInfo = function(newState, newParams) {
3014
- state = $state.get(newState, stateContext($element));
3015
- params = newParams;
3016
4292
  update();
3017
4293
  };
3018
4294
 
@@ -3020,15 +4296,28 @@ function $StateActiveDirective($state, $stateParams, $interpolate) {
3020
4296
 
3021
4297
  // Update route state
3022
4298
  function update() {
3023
- if ($state.$current.self === state && matchesParams()) {
4299
+ if (anyMatch()) {
3024
4300
  $element.addClass(activeClass);
3025
4301
  } else {
3026
4302
  $element.removeClass(activeClass);
3027
4303
  }
3028
4304
  }
3029
4305
 
3030
- function matchesParams() {
3031
- return !params || equalForKeys(params, $stateParams);
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
+ }
3032
4321
  }
3033
4322
  }]
3034
4323
  };
@@ -3036,7 +4325,8 @@ function $StateActiveDirective($state, $stateParams, $interpolate) {
3036
4325
 
3037
4326
  angular.module('ui.router.state')
3038
4327
  .directive('uiSref', $StateRefDirective)
3039
- .directive('uiSrefActive', $StateActiveDirective);
4328
+ .directive('uiSrefActive', $StateRefActiveDirective)
4329
+ .directive('uiSrefActiveEq', $StateRefActiveDirective);
3040
4330
 
3041
4331
  /**
3042
4332
  * @ngdoc filter
@@ -3049,9 +4339,11 @@ angular.module('ui.router.state')
3049
4339
  */
3050
4340
  $IsStateFilter.$inject = ['$state'];
3051
4341
  function $IsStateFilter($state) {
3052
- return function(state) {
4342
+ var isFilter = function (state) {
3053
4343
  return $state.is(state);
3054
4344
  };
4345
+ isFilter.$stateful = true;
4346
+ return isFilter;
3055
4347
  }
3056
4348
 
3057
4349
  /**
@@ -3065,159 +4357,14 @@ function $IsStateFilter($state) {
3065
4357
  */
3066
4358
  $IncludedByStateFilter.$inject = ['$state'];
3067
4359
  function $IncludedByStateFilter($state) {
3068
- return function(state) {
4360
+ var includesFilter = function (state) {
3069
4361
  return $state.includes(state);
3070
4362
  };
4363
+ includesFilter.$stateful = true;
4364
+ return includesFilter;
3071
4365
  }
3072
4366
 
3073
4367
  angular.module('ui.router.state')
3074
4368
  .filter('isState', $IsStateFilter)
3075
4369
  .filter('includedByState', $IncludedByStateFilter);
3076
-
3077
- /*
3078
- * @ngdoc object
3079
- * @name ui.router.compat.$routeProvider
3080
- *
3081
- * @requires ui.router.state.$stateProvider
3082
- * @requires ui.router.router.$urlRouterProvider
3083
- *
3084
- * @description
3085
- * `$routeProvider` of the `ui.router.compat` module overwrites the existing
3086
- * `routeProvider` from the core. This is done to provide compatibility between
3087
- * the UI Router and the core router.
3088
- *
3089
- * It also provides a `when()` method to register routes that map to certain urls.
3090
- * Behind the scenes it actually delegates either to
3091
- * {@link ui.router.router.$urlRouterProvider $urlRouterProvider} or to the
3092
- * {@link ui.router.state.$stateProvider $stateProvider} to postprocess the given
3093
- * router definition object.
3094
- */
3095
- $RouteProvider.$inject = ['$stateProvider', '$urlRouterProvider'];
3096
- function $RouteProvider( $stateProvider, $urlRouterProvider) {
3097
-
3098
- var routes = [];
3099
-
3100
- onEnterRoute.$inject = ['$$state'];
3101
- function onEnterRoute( $$state) {
3102
- /*jshint validthis: true */
3103
- this.locals = $$state.locals.globals;
3104
- this.params = this.locals.$stateParams;
3105
- }
3106
-
3107
- function onExitRoute() {
3108
- /*jshint validthis: true */
3109
- this.locals = null;
3110
- this.params = null;
3111
- }
3112
-
3113
- this.when = when;
3114
- /*
3115
- * @ngdoc function
3116
- * @name ui.router.compat.$routeProvider#when
3117
- * @methodOf ui.router.compat.$routeProvider
3118
- *
3119
- * @description
3120
- * Registers a route with a given route definition object. The route definition
3121
- * object has the same interface the angular core route definition object has.
3122
- *
3123
- * @example
3124
- * <pre>
3125
- * var app = angular.module('app', ['ui.router.compat']);
3126
- *
3127
- * app.config(function ($routeProvider) {
3128
- * $routeProvider.when('home', {
3129
- * controller: function () { ... },
3130
- * templateUrl: 'path/to/template'
3131
- * });
3132
- * });
3133
- * </pre>
3134
- *
3135
- * @param {string} url URL as string
3136
- * @param {object} route Route definition object
3137
- *
3138
- * @return {object} $routeProvider - $routeProvider instance
3139
- */
3140
- function when(url, route) {
3141
- /*jshint validthis: true */
3142
- if (route.redirectTo != null) {
3143
- // Redirect, configure directly on $urlRouterProvider
3144
- var redirect = route.redirectTo, handler;
3145
- if (isString(redirect)) {
3146
- handler = redirect; // leave $urlRouterProvider to handle
3147
- } else if (isFunction(redirect)) {
3148
- // Adapt to $urlRouterProvider API
3149
- handler = function (params, $location) {
3150
- return redirect(params, $location.path(), $location.search());
3151
- };
3152
- } else {
3153
- throw new Error("Invalid 'redirectTo' in when()");
3154
- }
3155
- $urlRouterProvider.when(url, handler);
3156
- } else {
3157
- // Regular route, configure as state
3158
- $stateProvider.state(inherit(route, {
3159
- parent: null,
3160
- name: 'route:' + encodeURIComponent(url),
3161
- url: url,
3162
- onEnter: onEnterRoute,
3163
- onExit: onExitRoute
3164
- }));
3165
- }
3166
- routes.push(route);
3167
- return this;
3168
- }
3169
-
3170
- /*
3171
- * @ngdoc object
3172
- * @name ui.router.compat.$route
3173
- *
3174
- * @requires ui.router.state.$state
3175
- * @requires $rootScope
3176
- * @requires $routeParams
3177
- *
3178
- * @property {object} routes - Array of registered routes.
3179
- * @property {object} params - Current route params as object.
3180
- * @property {string} current - Name of the current route.
3181
- *
3182
- * @description
3183
- * The `$route` service provides interfaces to access defined routes. It also let's
3184
- * you access route params through `$routeParams` service, so you have fully
3185
- * control over all the stuff you would actually get from angular's core `$route`
3186
- * service.
3187
- */
3188
- this.$get = $get;
3189
- $get.$inject = ['$state', '$rootScope', '$routeParams'];
3190
- function $get( $state, $rootScope, $routeParams) {
3191
-
3192
- var $route = {
3193
- routes: routes,
3194
- params: $routeParams,
3195
- current: undefined
3196
- };
3197
-
3198
- function stateAsRoute(state) {
3199
- return (state.name !== '') ? state : undefined;
3200
- }
3201
-
3202
- $rootScope.$on('$stateChangeStart', function (ev, to, toParams, from, fromParams) {
3203
- $rootScope.$broadcast('$routeChangeStart', stateAsRoute(to), stateAsRoute(from));
3204
- });
3205
-
3206
- $rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) {
3207
- $route.current = stateAsRoute(to);
3208
- $rootScope.$broadcast('$routeChangeSuccess', stateAsRoute(to), stateAsRoute(from));
3209
- copy(toParams, $route.params);
3210
- });
3211
-
3212
- $rootScope.$on('$stateChangeError', function (ev, to, toParams, from, fromParams, error) {
3213
- $rootScope.$broadcast('$routeChangeError', stateAsRoute(to), stateAsRoute(from), error);
3214
- });
3215
-
3216
- return $route;
3217
- }
3218
- }
3219
-
3220
- angular.module('ui.router.compat')
3221
- .provider('$route', $RouteProvider)
3222
- .directive('ngView', $ViewDirective);
3223
4370
  })(window, window.angular);