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
@@ -1,5 +1,7 @@
1
- function parseStateRef(ref) {
2
- var parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
1
+ function parseStateRef(ref, current) {
2
+ var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed;
3
+ if (preparsed) ref = current + '(' + preparsed[1] + ')';
4
+ parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
3
5
  if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'");
4
6
  return { state: parsed[1], paramExpr: parsed[3] || null };
5
7
  }
@@ -43,7 +45,7 @@ function stateContext(el) {
43
45
  * Here's an example of how you'd use ui-sref and how it would compile. If you have the
44
46
  * following template:
45
47
  * <pre>
46
- * <a ui-sref="home">Home</a> | <a ui-sref="about">About</a>
48
+ * <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a>
47
49
  *
48
50
  * <ul>
49
51
  * <li ng-repeat="contact in contacts">
@@ -52,9 +54,9 @@ function stateContext(el) {
52
54
  * </ul>
53
55
  * </pre>
54
56
  *
55
- * Then the compiled html would be (assuming Html5Mode is off):
57
+ * Then the compiled html would be (assuming Html5Mode is off and current state is contacts):
56
58
  * <pre>
57
- * <a href="#/home" ui-sref="home">Home</a> | <a href="#/about" ui-sref="about">About</a>
59
+ * <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>
58
60
  *
59
61
  * <ul>
60
62
  * <li ng-repeat="contact in contacts">
@@ -76,21 +78,24 @@ function stateContext(el) {
76
78
  */
77
79
  $StateRefDirective.$inject = ['$state', '$timeout'];
78
80
  function $StateRefDirective($state, $timeout) {
79
- var allowedOptions = ['location', 'inherit', 'reload'];
81
+ var allowedOptions = ['location', 'inherit', 'reload', 'absolute'];
80
82
 
81
83
  return {
82
84
  restrict: 'A',
83
- require: '?^uiSrefActive',
85
+ require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
84
86
  link: function(scope, element, attrs, uiSrefActive) {
85
- var ref = parseStateRef(attrs.uiSref);
87
+ var ref = parseStateRef(attrs.uiSref, $state.current.name);
86
88
  var params = null, url = null, base = stateContext(element) || $state.$current;
89
+ // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
90
+ var hrefKind = Object.prototype.toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
91
+ 'xlink:href' : 'href';
92
+ var newHref = null, isAnchor = element.prop("tagName").toUpperCase() === "A";
87
93
  var isForm = element[0].nodeName === "FORM";
88
- var attr = isForm ? "action" : "href", nav = true;
94
+ var attr = isForm ? "action" : hrefKind, nav = true;
89
95
 
90
- var options = {
91
- relative: base
92
- };
96
+ var options = { relative: base, inherit: true };
93
97
  var optionsOverride = scope.$eval(attrs.uiSrefOpts) || {};
98
+
94
99
  angular.forEach(allowedOptions, function(option) {
95
100
  if (option in optionsOverride) {
96
101
  options[option] = optionsOverride[option];
@@ -98,26 +103,27 @@ function $StateRefDirective($state, $timeout) {
98
103
  });
99
104
 
100
105
  var update = function(newVal) {
101
- if (newVal) params = newVal;
106
+ if (newVal) params = angular.copy(newVal);
102
107
  if (!nav) return;
103
108
 
104
- var newHref = $state.href(ref.state, params, options);
109
+ newHref = $state.href(ref.state, params, options);
105
110
 
106
- if (uiSrefActive) {
107
- uiSrefActive.$$setStateInfo(ref.state, params);
111
+ var activeDirective = uiSrefActive[1] || uiSrefActive[0];
112
+ if (activeDirective) {
113
+ activeDirective.$$addStateInfo(ref.state, params);
108
114
  }
109
- if (!newHref) {
115
+ if (newHref === null) {
110
116
  nav = false;
111
117
  return false;
112
118
  }
113
- element[0][attr] = newHref;
119
+ attrs.$set(attr, newHref);
114
120
  };
115
121
 
116
122
  if (ref.paramExpr) {
117
123
  scope.$watch(ref.paramExpr, function(newVal, oldVal) {
118
124
  if (newVal !== params) update(newVal);
119
125
  }, true);
120
- params = scope.$eval(ref.paramExpr);
126
+ params = angular.copy(scope.$eval(ref.paramExpr));
121
127
  }
122
128
  update();
123
129
 
@@ -127,10 +133,17 @@ function $StateRefDirective($state, $timeout) {
127
133
  var button = e.which || e.button;
128
134
  if ( !(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || element.attr('target')) ) {
129
135
  // HACK: This is to allow ng-clicks to be processed before the transition is initiated:
130
- $timeout(function() {
136
+ var transition = $timeout(function() {
131
137
  $state.go(ref.state, params, options);
132
138
  });
133
139
  e.preventDefault();
140
+
141
+ // if the state has no URL, ignore one preventDefault from the <a> directive.
142
+ var ignorePreventDefaultCount = isAnchor && !newHref ? 1: 0;
143
+ e.preventDefault = function() {
144
+ if (ignorePreventDefaultCount-- <= 0)
145
+ $timeout.cancel(transition);
146
+ };
134
147
  }
135
148
  });
136
149
  }
@@ -148,12 +161,20 @@ function $StateRefDirective($state, $timeout) {
148
161
  * @restrict A
149
162
  *
150
163
  * @description
151
- * A directive working alongside ui-sref to add classes to an element when the
164
+ * A directive working alongside ui-sref to add classes to an element when the
152
165
  * related ui-sref directive's state is active, and removing them when it is inactive.
153
- * The primary use-case is to simplify the special appearance of navigation menus
166
+ * The primary use-case is to simplify the special appearance of navigation menus
154
167
  * relying on `ui-sref`, by having the "active" state's menu button appear different,
155
168
  * distinguishing it from the inactive menu items.
156
169
  *
170
+ * ui-sref-active can live on the same element as ui-sref or on a parent element. The first
171
+ * ui-sref-active found at the same level or above the ui-sref will be used.
172
+ *
173
+ * Will activate when the ui-sref's target state or any child state is active. If you
174
+ * need to activate only when the ui-sref target state is active and *not* any of
175
+ * it's children, then you will use
176
+ * {@link ui.router.state.directive:ui-sref-active-eq ui-sref-active-eq}
177
+ *
157
178
  * @example
158
179
  * Given the following template:
159
180
  * <pre>
@@ -163,8 +184,9 @@ function $StateRefDirective($state, $timeout) {
163
184
  * </li>
164
185
  * </ul>
165
186
  * </pre>
166
- *
167
- * When the app state is "app.user", and contains the state parameter "user" with value "bilbobaggins",
187
+ *
188
+ *
189
+ * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins",
168
190
  * the resulting HTML will appear as (note the 'active' class):
169
191
  * <pre>
170
192
  * <ul>
@@ -173,10 +195,10 @@ function $StateRefDirective($state, $timeout) {
173
195
  * </li>
174
196
  * </ul>
175
197
  * </pre>
176
- *
177
- * The class name is interpolated **once** during the directives link time (any further changes to the
178
- * interpolated value are ignored).
179
- *
198
+ *
199
+ * The class name is interpolated **once** during the directives link time (any further changes to the
200
+ * interpolated value are ignored).
201
+ *
180
202
  * Multiple classes may be specified in a space-separated format:
181
203
  * <pre>
182
204
  * <ul>
@@ -186,20 +208,43 @@ function $StateRefDirective($state, $timeout) {
186
208
  * </ul>
187
209
  * </pre>
188
210
  */
189
- $StateActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
190
- function $StateActiveDirective($state, $stateParams, $interpolate) {
191
- return {
211
+
212
+ /**
213
+ * @ngdoc directive
214
+ * @name ui.router.state.directive:ui-sref-active-eq
215
+ *
216
+ * @requires ui.router.state.$state
217
+ * @requires ui.router.state.$stateParams
218
+ * @requires $interpolate
219
+ *
220
+ * @restrict A
221
+ *
222
+ * @description
223
+ * The same as {@link ui.router.state.directive:ui-sref-active ui-sref-active} but will only activate
224
+ * when the exact target state used in the `ui-sref` is active; no child states.
225
+ *
226
+ */
227
+ $StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
228
+ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
229
+ return {
192
230
  restrict: "A",
193
- controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
194
- var state, params, activeClass;
231
+ controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
232
+ var states = [], activeClass;
195
233
 
196
234
  // There probably isn't much point in $observing this
197
- activeClass = $interpolate($attrs.uiSrefActive || '', false)($scope);
235
+ // uiSrefActive and uiSrefActiveEq share the same directive object with some
236
+ // slight difference in logic routing
237
+ activeClass = $interpolate($attrs.uiSrefActiveEq || $attrs.uiSrefActive || '', false)($scope);
238
+
239
+ // Allow uiSref to communicate with uiSrefActive[Equals]
240
+ this.$$addStateInfo = function (newState, newParams) {
241
+ var state = $state.get(newState, stateContext($element));
242
+
243
+ states.push({
244
+ state: state || { name: newState },
245
+ params: newParams
246
+ });
198
247
 
199
- // Allow uiSref to communicate with uiSrefActive
200
- this.$$setStateInfo = function(newState, newParams) {
201
- state = $state.get(newState, stateContext($element));
202
- params = newParams;
203
248
  update();
204
249
  };
205
250
 
@@ -207,15 +252,28 @@ function $StateActiveDirective($state, $stateParams, $interpolate) {
207
252
 
208
253
  // Update route state
209
254
  function update() {
210
- if ($state.$current.self === state && matchesParams()) {
255
+ if (anyMatch()) {
211
256
  $element.addClass(activeClass);
212
257
  } else {
213
258
  $element.removeClass(activeClass);
214
259
  }
215
260
  }
216
261
 
217
- function matchesParams() {
218
- return !params || equalForKeys(params, $stateParams);
262
+ function anyMatch() {
263
+ for (var i = 0; i < states.length; i++) {
264
+ if (isMatch(states[i].state, states[i].params)) {
265
+ return true;
266
+ }
267
+ }
268
+ return false;
269
+ }
270
+
271
+ function isMatch(state, params) {
272
+ if (typeof $attrs.uiSrefActiveEq !== 'undefined') {
273
+ return $state.is(state.name, params);
274
+ } else {
275
+ return $state.includes(state.name, params);
276
+ }
219
277
  }
220
278
  }]
221
279
  };
@@ -223,4 +281,5 @@ function $StateActiveDirective($state, $stateParams, $interpolate) {
223
281
 
224
282
  angular.module('ui.router.state')
225
283
  .directive('uiSref', $StateRefDirective)
226
- .directive('uiSrefActive', $StateActiveDirective);
284
+ .directive('uiSrefActive', $StateRefActiveDirective)
285
+ .directive('uiSrefActiveEq', $StateRefActiveDirective);
@@ -9,9 +9,11 @@
9
9
  */
10
10
  $IsStateFilter.$inject = ['$state'];
11
11
  function $IsStateFilter($state) {
12
- return function(state) {
12
+ var isFilter = function (state) {
13
13
  return $state.is(state);
14
14
  };
15
+ isFilter.$stateful = true;
16
+ return isFilter;
15
17
  }
16
18
 
17
19
  /**
@@ -25,9 +27,11 @@ function $IsStateFilter($state) {
25
27
  */
26
28
  $IncludedByStateFilter.$inject = ['$state'];
27
29
  function $IncludedByStateFilter($state) {
28
- return function(state) {
30
+ var includesFilter = function (state) {
29
31
  return $state.includes(state);
30
32
  };
33
+ includesFilter.$stateful = true;
34
+ return includesFilter;
31
35
  }
32
36
 
33
37
  angular.module('ui.router.state')
@@ -83,13 +83,13 @@ function $TemplateFactory( $http, $templateCache, $injector) {
83
83
  if (isFunction(url)) url = url(params);
84
84
  if (url == null) return null;
85
85
  else return $http
86
- .get(url, { cache: $templateCache })
86
+ .get(url, { cache: $templateCache, headers: { Accept: 'text/html' }})
87
87
  .then(function(response) { return response.data; });
88
88
  };
89
89
 
90
90
  /**
91
91
  * @ngdoc function
92
- * @name ui.router.util.$templateFactory#fromUrl
92
+ * @name ui.router.util.$templateFactory#fromProvider
93
93
  * @methodOf ui.router.util.$templateFactory
94
94
  *
95
95
  * @description
@@ -1,3 +1,5 @@
1
+ var $$UMFP; // reference to $UrlMatcherFactoryProvider
2
+
1
3
  /**
2
4
  * @ngdoc object
3
5
  * @name ui.router.util.type:UrlMatcher
@@ -8,24 +10,24 @@
8
10
  * of search parameters. Multiple search parameter names are separated by '&'. Search parameters
9
11
  * do not influence whether or not a URL is matched, but their values are passed through into
10
12
  * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}.
11
- *
13
+ *
12
14
  * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace
13
15
  * syntax, which optionally allows a regular expression for the parameter to be specified:
14
16
  *
15
17
  * * `':'` name - colon placeholder
16
18
  * * `'*'` name - catch-all placeholder
17
19
  * * `'{' name '}'` - curly placeholder
18
- * * `'{' name ':' regexp '}'` - curly placeholder with regexp. Should the regexp itself contain
19
- * curly braces, they must be in matched pairs or escaped with a backslash.
20
+ * * `'{' name ':' regexp|type '}'` - curly placeholder with regexp or type name. Should the
21
+ * regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash.
20
22
  *
21
23
  * Parameter names may contain only word characters (latin letters, digits, and underscore) and
22
- * must be unique within the pattern (across both path and search parameters). For colon
24
+ * must be unique within the pattern (across both path and search parameters). For colon
23
25
  * placeholders or curly placeholders without an explicit regexp, a path parameter matches any
24
26
  * number of characters other than '/'. For catch-all placeholders the path parameter matches
25
27
  * any number of characters.
26
- *
28
+ *
27
29
  * Examples:
28
- *
30
+ *
29
31
  * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for
30
32
  * trailing slashes, and patterns have to match the entire path, not just a prefix.
31
33
  * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or
@@ -37,26 +39,34 @@
37
39
  * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the
38
40
  * path into the parameter 'path'.
39
41
  * * `'/files/*path'` - ditto.
42
+ * * `'/calendar/{start:date}'` - Matches "/calendar/2014-11-12" (because the pattern defined
43
+ * in the built-in `date` Type matches `2014-11-12`) and provides a Date object in $stateParams.start
44
+ *
45
+ * @param {string} pattern The pattern to compile into a matcher.
46
+ * @param {Object} config A configuration object hash:
47
+ * @param {Object=} parentMatcher Used to concatenate the pattern/config onto
48
+ * an existing UrlMatcher
40
49
  *
41
- * @param {string} pattern the pattern to compile into a matcher.
42
- * @param {bool} caseInsensitiveMatch true if url matching should be case insensitive, otherwise false, the default value (for backward compatibility) is false.
50
+ * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`.
51
+ * * `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`.
43
52
  *
44
53
  * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any
45
54
  * URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns
46
55
  * non-null) will start with this prefix.
47
56
  *
48
- * @property {string} source The pattern that was passed into the contructor
57
+ * @property {string} source The pattern that was passed into the constructor
49
58
  *
50
59
  * @property {string} sourcePath The path portion of the source property
51
60
  *
52
61
  * @property {string} sourceSearch The search portion of the source property
53
62
  *
54
- * @property {string} regex The constructed regex that will be used to match against the url when
63
+ * @property {string} regex The constructed regex that will be used to match against the url when
55
64
  * it is time to determine which url will match.
56
65
  *
57
- * @returns {Object} New UrlMatcher object
66
+ * @returns {Object} New `UrlMatcher` object
58
67
  */
59
- function UrlMatcher(pattern, caseInsensitiveMatch) {
68
+ function UrlMatcher(pattern, config, parentMatcher) {
69
+ config = extend({ params: {} }, isObject(config) ? config : {});
60
70
 
61
71
  // Find all placeholders and create a compiled pattern, using either classic or curly syntax:
62
72
  // '*' name
@@ -65,68 +75,96 @@ function UrlMatcher(pattern, caseInsensitiveMatch) {
65
75
  // '{' name ':' regexp '}'
66
76
  // The regular expression is somewhat complicated due to the need to allow curly braces
67
77
  // inside the regular expression. The placeholder regexp breaks down as follows:
68
- // ([:*])(\w+) classic placeholder ($1 / $2)
69
- // \{(\w+)(?:\:( ... ))?\} curly brace placeholder ($3) with optional regexp ... ($4)
70
- // (?: ... | ... | ... )+ the regexp consists of any number of atoms, an atom being either
71
- // [^{}\\]+ - anything other than curly braces or backslash
72
- // \\. - a backslash escape
73
- // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms
74
- var placeholder = /([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
75
- names = {}, compiled = '^', last = 0, m,
78
+ // ([:*])([\w\[\]]+) - classic placeholder ($1 / $2) (search version has - for snake-case)
79
+ // \{([\w\[\]]+)(?:\:( ... ))?\} - curly brace placeholder ($3) with optional regexp/type ... ($4) (search version has - for snake-case
80
+ // (?: ... | ... | ... )+ - the regexp consists of any number of atoms, an atom being either
81
+ // [^{}\\]+ - anything other than curly braces or backslash
82
+ // \\. - a backslash escape
83
+ // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms
84
+ var placeholder = /([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
85
+ searchPlaceholder = /([:]?)([\w\[\]-]+)|\{([\w\[\]-]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
86
+ compiled = '^', last = 0, m,
76
87
  segments = this.segments = [],
77
- params = this.params = [];
88
+ parentParams = parentMatcher ? parentMatcher.params : {},
89
+ params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet(),
90
+ paramNames = [];
78
91
 
79
- function addParameter(id) {
80
- if (!/^\w+(-+\w+)*$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'");
81
- if (names[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'");
82
- names[id] = true;
83
- params.push(id);
92
+ function addParameter(id, type, config, location) {
93
+ paramNames.push(id);
94
+ if (parentParams[id]) return parentParams[id];
95
+ if (!/^\w+(-+\w+)*(?:\[\])?$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'");
96
+ if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'");
97
+ params[id] = new $$UMFP.Param(id, type, config, location);
98
+ return params[id];
84
99
  }
85
100
 
86
- function quoteRegExp(string) {
87
- return string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&");
101
+ function quoteRegExp(string, pattern, squash, optional) {
102
+ var surroundPattern = ['',''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&");
103
+ if (!pattern) return result;
104
+ switch(squash) {
105
+ case false: surroundPattern = ['(', ')' + (optional ? "?" : "")]; break;
106
+ case true: surroundPattern = ['?(', ')?']; break;
107
+ default: surroundPattern = ['(' + squash + "|", ')?']; break;
108
+ }
109
+ return result + surroundPattern[0] + pattern + surroundPattern[1];
88
110
  }
89
111
 
90
112
  this.source = pattern;
91
113
 
92
114
  // Split into static segments separated by path parameter placeholders.
93
115
  // The number of segments is always 1 more than the number of parameters.
94
- var id, regexp, segment;
116
+ function matchDetails(m, isSearch) {
117
+ var id, regexp, segment, type, cfg, arrayMode;
118
+ id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null
119
+ cfg = config.params[id];
120
+ segment = pattern.substring(last, m.index);
121
+ regexp = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null);
122
+ type = $$UMFP.type(regexp || "string") || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) });
123
+ return {
124
+ id: id, regexp: regexp, segment: segment, type: type, cfg: cfg
125
+ };
126
+ }
127
+
128
+ var p, param, segment;
95
129
  while ((m = placeholder.exec(pattern))) {
96
- id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null
97
- regexp = m[4] || (m[1] == '*' ? '.*' : '[^/]*');
98
- segment = pattern.substring(last, m.index);
99
- if (segment.indexOf('?') >= 0) break; // we're into the search part
100
- compiled += quoteRegExp(segment) + '(' + regexp + ')';
101
- addParameter(id);
102
- segments.push(segment);
130
+ p = matchDetails(m, false);
131
+ if (p.segment.indexOf('?') >= 0) break; // we're into the search part
132
+
133
+ param = addParameter(p.id, p.type, p.cfg, "path");
134
+ compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional);
135
+ segments.push(p.segment);
103
136
  last = placeholder.lastIndex;
104
137
  }
105
138
  segment = pattern.substring(last);
106
139
 
107
140
  // Find any search parameter names and remove them from the last segment
108
141
  var i = segment.indexOf('?');
142
+
109
143
  if (i >= 0) {
110
144
  var search = this.sourceSearch = segment.substring(i);
111
145
  segment = segment.substring(0, i);
112
- this.sourcePath = pattern.substring(0, last+i);
146
+ this.sourcePath = pattern.substring(0, last + i);
113
147
 
114
- // Allow parameters to be separated by '?' as well as '&' to make concat() easier
115
- forEach(search.substring(1).split(/[&?]/), addParameter);
148
+ if (search.length > 0) {
149
+ last = 0;
150
+ while ((m = searchPlaceholder.exec(search))) {
151
+ p = matchDetails(m, true);
152
+ param = addParameter(p.id, p.type, p.cfg, "search");
153
+ last = placeholder.lastIndex;
154
+ // check if ?&
155
+ }
156
+ }
116
157
  } else {
117
158
  this.sourcePath = pattern;
118
159
  this.sourceSearch = '';
119
160
  }
120
161
 
121
- compiled += quoteRegExp(segment) + '$';
162
+ compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$';
122
163
  segments.push(segment);
123
- if(caseInsensitiveMatch){
124
- this.regexp = new RegExp(compiled, 'i');
125
- }else{
126
- this.regexp = new RegExp(compiled);
127
- }
128
-
164
+
165
+ this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined);
129
166
  this.prefix = segments[0];
167
+ this.$$paramNames = paramNames;
130
168
  }
131
169
 
132
170
  /**
@@ -142,19 +180,25 @@ function UrlMatcher(pattern, caseInsensitiveMatch) {
142
180
  *
143
181
  * @example
144
182
  * The following two matchers are equivalent:
145
- * ```
183
+ * <pre>
146
184
  * new UrlMatcher('/user/{id}?q').concat('/details?date');
147
185
  * new UrlMatcher('/user/{id}/details?q&date');
148
- * ```
186
+ * </pre>
149
187
  *
150
188
  * @param {string} pattern The pattern to append.
151
- * @returns {ui.router.util.type:UrlMatcher} A matcher for the concatenated pattern.
189
+ * @param {Object} config An object hash of the configuration for the matcher.
190
+ * @returns {UrlMatcher} A matcher for the concatenated pattern.
152
191
  */
153
- UrlMatcher.prototype.concat = function (pattern) {
192
+ UrlMatcher.prototype.concat = function (pattern, config) {
154
193
  // Because order of search parameters is irrelevant, we can add our own search
155
194
  // parameters to the end of the new pattern. Parse the new pattern by itself
156
195
  // and then join the bits together, but it's much easier to do this on a string level.
157
- return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch);
196
+ var defaultConfig = {
197
+ caseInsensitive: $$UMFP.caseInsensitive(),
198
+ strict: $$UMFP.strictMode(),
199
+ squash: $$UMFP.defaultSquashPolicy()
200
+ };
201
+ return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, extend(defaultConfig, config), this);
158
202
  };
159
203
 
160
204
  UrlMatcher.prototype.toString = function () {
@@ -174,10 +218,12 @@ UrlMatcher.prototype.toString = function () {
174
218
  * as optional.
175
219
  *
176
220
  * @example
177
- * ```
178
- * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', { x:'1', q:'hello' });
179
- * // returns { id:'bob', q:'hello', r:null }
180
- * ```
221
+ * <pre>
222
+ * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', {
223
+ * x: '1', q: 'hello'
224
+ * });
225
+ * // returns { id: 'bob', q: 'hello', r: null }
226
+ * </pre>
181
227
  *
182
228
  * @param {string} path The URL path to match, e.g. `$location.path()`.
183
229
  * @param {Object} searchParams URL search parameters, e.g. `$location.search()`.
@@ -186,15 +232,38 @@ UrlMatcher.prototype.toString = function () {
186
232
  UrlMatcher.prototype.exec = function (path, searchParams) {
187
233
  var m = this.regexp.exec(path);
188
234
  if (!m) return null;
235
+ searchParams = searchParams || {};
189
236
 
190
- var params = this.params, nTotal = params.length,
191
- nPath = this.segments.length-1,
192
- values = {}, i;
237
+ var paramNames = this.parameters(), nTotal = paramNames.length,
238
+ nPath = this.segments.length - 1,
239
+ values = {}, i, j, cfg, paramName;
193
240
 
194
241
  if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'");
195
242
 
196
- for (i=0; i<nPath; i++) values[params[i]] = m[i+1];
197
- for (/**/; i<nTotal; i++) values[params[i]] = searchParams[params[i]];
243
+ function decodePathArray(string) {
244
+ function reverseString(str) { return str.split("").reverse().join(""); }
245
+ function unquoteDashes(str) { return str.replace(/\\-/g, "-"); }
246
+
247
+ var split = reverseString(string).split(/-(?!\\)/);
248
+ var allReversed = map(split, reverseString);
249
+ return map(allReversed, unquoteDashes).reverse();
250
+ }
251
+
252
+ for (i = 0; i < nPath; i++) {
253
+ paramName = paramNames[i];
254
+ var param = this.params[paramName];
255
+ var paramVal = m[i+1];
256
+ // if the param value matches a pre-replace pair, replace the value before decoding.
257
+ for (j = 0; j < param.replace; j++) {
258
+ if (param.replace[j].from === paramVal) paramVal = param.replace[j].to;
259
+ }
260
+ if (paramVal && param.array === true) paramVal = decodePathArray(paramVal);
261
+ values[paramName] = param.value(paramVal);
262
+ }
263
+ for (/**/; i < nTotal; i++) {
264
+ paramName = paramNames[i];
265
+ values[paramName] = this.params[paramName].value(searchParams[paramName]);
266
+ }
198
267
 
199
268
  return values;
200
269
  };
@@ -206,12 +275,29 @@ UrlMatcher.prototype.exec = function (path, searchParams) {
206
275
  *
207
276
  * @description
208
277
  * Returns the names of all path and search parameters of this pattern in an unspecified order.
209
- *
278
+ *
210
279
  * @returns {Array.<string>} An array of parameter names. Must be treated as read-only. If the
211
280
  * pattern has no parameters, an empty array is returned.
212
281
  */
213
- UrlMatcher.prototype.parameters = function () {
214
- return this.params;
282
+ UrlMatcher.prototype.parameters = function (param) {
283
+ if (!isDefined(param)) return this.$$paramNames;
284
+ return this.params[param] || null;
285
+ };
286
+
287
+ /**
288
+ * @ngdoc function
289
+ * @name ui.router.util.type:UrlMatcher#validate
290
+ * @methodOf ui.router.util.type:UrlMatcher
291
+ *
292
+ * @description
293
+ * Checks an object hash of parameters to validate their correctness according to the parameter
294
+ * types of this `UrlMatcher`.
295
+ *
296
+ * @param {Object} params The object hash of parameters to validate.
297
+ * @returns {boolean} Returns `true` if `params` validates, otherwise `false`.
298
+ */
299
+ UrlMatcher.prototype.validates = function (params) {
300
+ return this.params.$$validates(params);
215
301
  };
216
302
 
217
303
  /**
@@ -225,31 +311,54 @@ UrlMatcher.prototype.parameters = function () {
225
311
  * treated as empty strings.
226
312
  *
227
313
  * @example
228
- * ```
314
+ * <pre>
229
315
  * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' });
230
316
  * // returns '/user/bob?q=yes'
231
- * ```
317
+ * </pre>
232
318
  *
233
319
  * @param {Object} values the values to substitute for the parameters in this pattern.
234
320
  * @returns {string} the formatted URL (path and optionally search part).
235
321
  */
236
322
  UrlMatcher.prototype.format = function (values) {
237
- var segments = this.segments, params = this.params;
238
- if (!values) return segments.join('');
323
+ values = values || {};
324
+ var segments = this.segments, params = this.parameters(), paramset = this.params;
325
+ if (!this.validates(values)) return null;
239
326
 
240
- var nPath = segments.length-1, nTotal = params.length,
241
- result = segments[0], i, search, value;
327
+ var i, search = false, nPath = segments.length - 1, nTotal = params.length, result = segments[0];
242
328
 
243
- for (i=0; i<nPath; i++) {
244
- value = values[params[i]];
245
- // TODO: Maybe we should throw on null here? It's not really good style to use '' and null interchangeabley
246
- if (value != null) result += encodeURIComponent(value);
247
- result += segments[i+1];
329
+ function encodeDashes(str) { // Replace dashes with encoded "\-"
330
+ return encodeURIComponent(str).replace(/-/g, function(c) { return '%5C%' + c.charCodeAt(0).toString(16).toUpperCase(); });
248
331
  }
249
- for (/**/; i<nTotal; i++) {
250
- value = values[params[i]];
251
- if (value != null) {
252
- result += (search ? '&' : '?') + params[i] + '=' + encodeURIComponent(value);
332
+
333
+ for (i = 0; i < nTotal; i++) {
334
+ var isPathParam = i < nPath;
335
+ var name = params[i], param = paramset[name], value = param.value(values[name]);
336
+ var isDefaultValue = param.isOptional && param.type.equals(param.value(), value);
337
+ var squash = isDefaultValue ? param.squash : false;
338
+ var encoded = param.type.encode(value);
339
+
340
+ if (isPathParam) {
341
+ var nextSegment = segments[i + 1];
342
+ if (squash === false) {
343
+ if (encoded != null) {
344
+ if (isArray(encoded)) {
345
+ result += map(encoded, encodeDashes).join("-");
346
+ } else {
347
+ result += encodeURIComponent(encoded);
348
+ }
349
+ }
350
+ result += nextSegment;
351
+ } else if (squash === true) {
352
+ var capture = result.match(/\/$/) ? /\/?(.*)/ : /(.*)/;
353
+ result += nextSegment.match(capture)[1];
354
+ } else if (isString(squash)) {
355
+ result += squash + nextSegment;
356
+ }
357
+ } else {
358
+ if (encoded == null || (isDefaultValue && squash !== false)) continue;
359
+ if (!isArray(encoded)) encoded = [ encoded ];
360
+ encoded = map(encoded, encodeURIComponent).join('&' + name + '=');
361
+ result += (search ? '&' : '?') + (name + '=' + encoded);
253
362
  search = true;
254
363
  }
255
364
  }
@@ -257,6 +366,194 @@ UrlMatcher.prototype.format = function (values) {
257
366
  return result;
258
367
  };
259
368
 
369
+ /**
370
+ * @ngdoc object
371
+ * @name ui.router.util.type:Type
372
+ *
373
+ * @description
374
+ * Implements an interface to define custom parameter types that can be decoded from and encoded to
375
+ * string parameters matched in a URL. Used by {@link ui.router.util.type:UrlMatcher `UrlMatcher`}
376
+ * objects when matching or formatting URLs, or comparing or validating parameter values.
377
+ *
378
+ * See {@link ui.router.util.$urlMatcherFactory#methods_type `$urlMatcherFactory#type()`} for more
379
+ * information on registering custom types.
380
+ *
381
+ * @param {Object} config A configuration object which contains the custom type definition. The object's
382
+ * properties will override the default methods and/or pattern in `Type`'s public interface.
383
+ * @example
384
+ * <pre>
385
+ * {
386
+ * decode: function(val) { return parseInt(val, 10); },
387
+ * encode: function(val) { return val && val.toString(); },
388
+ * equals: function(a, b) { return this.is(a) && a === b; },
389
+ * is: function(val) { return angular.isNumber(val) isFinite(val) && val % 1 === 0; },
390
+ * pattern: /\d+/
391
+ * }
392
+ * </pre>
393
+ *
394
+ * @property {RegExp} pattern The regular expression pattern used to match values of this type when
395
+ * coming from a substring of a URL.
396
+ *
397
+ * @returns {Object} Returns a new `Type` object.
398
+ */
399
+ function Type(config) {
400
+ extend(this, config);
401
+ }
402
+
403
+ /**
404
+ * @ngdoc function
405
+ * @name ui.router.util.type:Type#is
406
+ * @methodOf ui.router.util.type:Type
407
+ *
408
+ * @description
409
+ * Detects whether a value is of a particular type. Accepts a native (decoded) value
410
+ * and determines whether it matches the current `Type` object.
411
+ *
412
+ * @param {*} val The value to check.
413
+ * @param {string} key Optional. If the type check is happening in the context of a specific
414
+ * {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the
415
+ * parameter in which `val` is stored. Can be used for meta-programming of `Type` objects.
416
+ * @returns {Boolean} Returns `true` if the value matches the type, otherwise `false`.
417
+ */
418
+ Type.prototype.is = function(val, key) {
419
+ return true;
420
+ };
421
+
422
+ /**
423
+ * @ngdoc function
424
+ * @name ui.router.util.type:Type#encode
425
+ * @methodOf ui.router.util.type:Type
426
+ *
427
+ * @description
428
+ * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the
429
+ * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it
430
+ * only needs to be a representation of `val` that has been coerced to a string.
431
+ *
432
+ * @param {*} val The value to encode.
433
+ * @param {string} key The name of the parameter in which `val` is stored. Can be used for
434
+ * meta-programming of `Type` objects.
435
+ * @returns {string} Returns a string representation of `val` that can be encoded in a URL.
436
+ */
437
+ Type.prototype.encode = function(val, key) {
438
+ return val;
439
+ };
440
+
441
+ /**
442
+ * @ngdoc function
443
+ * @name ui.router.util.type:Type#decode
444
+ * @methodOf ui.router.util.type:Type
445
+ *
446
+ * @description
447
+ * Converts a parameter value (from URL string or transition param) to a custom/native value.
448
+ *
449
+ * @param {string} val The URL parameter value to decode.
450
+ * @param {string} key The name of the parameter in which `val` is stored. Can be used for
451
+ * meta-programming of `Type` objects.
452
+ * @returns {*} Returns a custom representation of the URL parameter value.
453
+ */
454
+ Type.prototype.decode = function(val, key) {
455
+ return val;
456
+ };
457
+
458
+ /**
459
+ * @ngdoc function
460
+ * @name ui.router.util.type:Type#equals
461
+ * @methodOf ui.router.util.type:Type
462
+ *
463
+ * @description
464
+ * Determines whether two decoded values are equivalent.
465
+ *
466
+ * @param {*} a A value to compare against.
467
+ * @param {*} b A value to compare against.
468
+ * @returns {Boolean} Returns `true` if the values are equivalent/equal, otherwise `false`.
469
+ */
470
+ Type.prototype.equals = function(a, b) {
471
+ return a == b;
472
+ };
473
+
474
+ Type.prototype.$subPattern = function() {
475
+ var sub = this.pattern.toString();
476
+ return sub.substr(1, sub.length - 2);
477
+ };
478
+
479
+ Type.prototype.pattern = /.*/;
480
+
481
+ Type.prototype.toString = function() { return "{Type:" + this.name + "}"; };
482
+
483
+ /** Given an encoded string, or a decoded object, returns a decoded object */
484
+ Type.prototype.$normalize = function(val) {
485
+ return this.is(val) ? val : this.decode(val);
486
+ };
487
+
488
+ /*
489
+ * Wraps an existing custom Type as an array of Type, depending on 'mode'.
490
+ * e.g.:
491
+ * - urlmatcher pattern "/path?{queryParam[]:int}"
492
+ * - url: "/path?queryParam=1&queryParam=2
493
+ * - $stateParams.queryParam will be [1, 2]
494
+ * if `mode` is "auto", then
495
+ * - url: "/path?queryParam=1 will create $stateParams.queryParam: 1
496
+ * - url: "/path?queryParam=1&queryParam=2 will create $stateParams.queryParam: [1, 2]
497
+ */
498
+ Type.prototype.$asArray = function(mode, isSearch) {
499
+ if (!mode) return this;
500
+ if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only");
501
+
502
+ function ArrayType(type, mode) {
503
+ function bindTo(type, callbackName) {
504
+ return function() {
505
+ return type[callbackName].apply(type, arguments);
506
+ };
507
+ }
508
+
509
+ // Wrap non-array value as array
510
+ function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); }
511
+ // Unwrap array value for "auto" mode. Return undefined for empty array.
512
+ function arrayUnwrap(val) {
513
+ switch(val.length) {
514
+ case 0: return undefined;
515
+ case 1: return mode === "auto" ? val[0] : val;
516
+ default: return val;
517
+ }
518
+ }
519
+ function falsey(val) { return !val; }
520
+
521
+ // Wraps type (.is/.encode/.decode) functions to operate on each value of an array
522
+ function arrayHandler(callback, allTruthyMode) {
523
+ return function handleArray(val) {
524
+ val = arrayWrap(val);
525
+ var result = map(val, callback);
526
+ if (allTruthyMode === true)
527
+ return filter(result, falsey).length === 0;
528
+ return arrayUnwrap(result);
529
+ };
530
+ }
531
+
532
+ // Wraps type (.equals) functions to operate on each value of an array
533
+ function arrayEqualsHandler(callback) {
534
+ return function handleArray(val1, val2) {
535
+ var left = arrayWrap(val1), right = arrayWrap(val2);
536
+ if (left.length !== right.length) return false;
537
+ for (var i = 0; i < left.length; i++) {
538
+ if (!callback(left[i], right[i])) return false;
539
+ }
540
+ return true;
541
+ };
542
+ }
543
+
544
+ this.encode = arrayHandler(bindTo(type, 'encode'));
545
+ this.decode = arrayHandler(bindTo(type, 'decode'));
546
+ this.is = arrayHandler(bindTo(type, 'is'), true);
547
+ this.equals = arrayEqualsHandler(bindTo(type, 'equals'));
548
+ this.pattern = type.pattern;
549
+ this.$normalize = arrayHandler(bindTo(type, '$normalize'));
550
+ this.name = type.name;
551
+ this.$arrayMode = mode;
552
+ }
553
+
554
+ return new ArrayType(this, mode);
555
+ };
556
+
260
557
 
261
558
 
262
559
  /**
@@ -264,25 +561,147 @@ UrlMatcher.prototype.format = function (values) {
264
561
  * @name ui.router.util.$urlMatcherFactory
265
562
  *
266
563
  * @description
267
- * Factory for {@link ui.router.util.type:UrlMatcher} instances. The factory is also available to providers
268
- * under the name `$urlMatcherFactoryProvider`.
564
+ * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory
565
+ * is also available to providers under the name `$urlMatcherFactoryProvider`.
269
566
  */
270
567
  function $UrlMatcherFactory() {
568
+ $$UMFP = this;
569
+
570
+ var isCaseInsensitive = false, isStrictMode = true, defaultSquashPolicy = false;
271
571
 
272
- var useCaseInsensitiveMatch = false;
572
+ function valToString(val) { return val != null ? val.toString().replace(/\//g, "%2F") : val; }
573
+ function valFromString(val) { return val != null ? val.toString().replace(/%2F/g, "/") : val; }
574
+
575
+ var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = {
576
+ string: {
577
+ encode: valToString,
578
+ decode: valFromString,
579
+ // TODO: in 1.0, make string .is() return false if value is undefined/null by default.
580
+ // In 0.2.x, string params are optional by default for backwards compat
581
+ is: function(val) { return val == null || !isDefined(val) || typeof val === "string"; },
582
+ pattern: /[^/]*/
583
+ },
584
+ int: {
585
+ encode: valToString,
586
+ decode: function(val) { return parseInt(val, 10); },
587
+ is: function(val) { return isDefined(val) && this.decode(val.toString()) === val; },
588
+ pattern: /\d+/
589
+ },
590
+ bool: {
591
+ encode: function(val) { return val ? 1 : 0; },
592
+ decode: function(val) { return parseInt(val, 10) !== 0; },
593
+ is: function(val) { return val === true || val === false; },
594
+ pattern: /0|1/
595
+ },
596
+ date: {
597
+ encode: function (val) {
598
+ if (!this.is(val))
599
+ return undefined;
600
+ return [ val.getFullYear(),
601
+ ('0' + (val.getMonth() + 1)).slice(-2),
602
+ ('0' + val.getDate()).slice(-2)
603
+ ].join("-");
604
+ },
605
+ decode: function (val) {
606
+ if (this.is(val)) return val;
607
+ var match = this.capture.exec(val);
608
+ return match ? new Date(match[1], match[2] - 1, match[3]) : undefined;
609
+ },
610
+ is: function(val) { return val instanceof Date && !isNaN(val.valueOf()); },
611
+ equals: function (a, b) { return this.is(a) && this.is(b) && a.toISOString() === b.toISOString(); },
612
+ pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/,
613
+ capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/
614
+ },
615
+ json: {
616
+ encode: angular.toJson,
617
+ decode: angular.fromJson,
618
+ is: angular.isObject,
619
+ equals: angular.equals,
620
+ pattern: /[^/]*/
621
+ },
622
+ any: { // does not encode/decode
623
+ encode: angular.identity,
624
+ decode: angular.identity,
625
+ equals: angular.equals,
626
+ pattern: /.*/
627
+ }
628
+ };
629
+
630
+ function getDefaultConfig() {
631
+ return {
632
+ strict: isStrictMode,
633
+ caseInsensitive: isCaseInsensitive
634
+ };
635
+ }
636
+
637
+ function isInjectable(value) {
638
+ return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1])));
639
+ }
640
+
641
+ /**
642
+ * [Internal] Get the default value of a parameter, which may be an injectable function.
643
+ */
644
+ $UrlMatcherFactory.$$getDefaultValue = function(config) {
645
+ if (!isInjectable(config.value)) return config.value;
646
+ if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
647
+ return injector.invoke(config.value);
648
+ };
273
649
 
274
650
  /**
275
651
  * @ngdoc function
276
- * @name ui.router.util.$urlMatcherFactory#caseInsensitiveMatch
652
+ * @name ui.router.util.$urlMatcherFactory#caseInsensitive
277
653
  * @methodOf ui.router.util.$urlMatcherFactory
278
654
  *
279
655
  * @description
280
- * Define if url matching should be case sensistive, the default behavior, or not.
281
- *
282
- * @param {bool} value false to match URL in a case sensitive manner; otherwise true;
656
+ * Defines whether URL matching should be case sensitive (the default behavior), or not.
657
+ *
658
+ * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`;
659
+ * @returns {boolean} the current value of caseInsensitive
283
660
  */
284
- this.caseInsensitiveMatch = function(value){
285
- useCaseInsensitiveMatch = value;
661
+ this.caseInsensitive = function(value) {
662
+ if (isDefined(value))
663
+ isCaseInsensitive = value;
664
+ return isCaseInsensitive;
665
+ };
666
+
667
+ /**
668
+ * @ngdoc function
669
+ * @name ui.router.util.$urlMatcherFactory#strictMode
670
+ * @methodOf ui.router.util.$urlMatcherFactory
671
+ *
672
+ * @description
673
+ * Defines whether URLs should match trailing slashes, or not (the default behavior).
674
+ *
675
+ * @param {boolean=} value `false` to match trailing slashes in URLs, otherwise `true`.
676
+ * @returns {boolean} the current value of strictMode
677
+ */
678
+ this.strictMode = function(value) {
679
+ if (isDefined(value))
680
+ isStrictMode = value;
681
+ return isStrictMode;
682
+ };
683
+
684
+ /**
685
+ * @ngdoc function
686
+ * @name ui.router.util.$urlMatcherFactory#defaultSquashPolicy
687
+ * @methodOf ui.router.util.$urlMatcherFactory
688
+ *
689
+ * @description
690
+ * Sets the default behavior when generating or matching URLs with default parameter values.
691
+ *
692
+ * @param {string} value A string that defines the default parameter URL squashing behavior.
693
+ * `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL
694
+ * `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the
695
+ * parameter is surrounded by slashes, squash (remove) one slash from the URL
696
+ * any other string, e.g. "~": When generating an href with a default parameter value, squash (remove)
697
+ * the parameter value from the URL and replace it with this string.
698
+ */
699
+ this.defaultSquashPolicy = function(value) {
700
+ if (!isDefined(value)) return defaultSquashPolicy;
701
+ if (value !== true && value !== false && !isString(value))
702
+ throw new Error("Invalid squash policy: " + value + ". Valid policies: false, true, arbitrary-string");
703
+ defaultSquashPolicy = value;
704
+ return value;
286
705
  };
287
706
 
288
707
  /**
@@ -291,13 +710,14 @@ function $UrlMatcherFactory() {
291
710
  * @methodOf ui.router.util.$urlMatcherFactory
292
711
  *
293
712
  * @description
294
- * Creates a {@link ui.router.util.type:UrlMatcher} for the specified pattern.
295
- *
713
+ * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern.
714
+ *
296
715
  * @param {string} pattern The URL pattern.
297
- * @returns {ui.router.util.type:UrlMatcher} The UrlMatcher.
716
+ * @param {Object} config The config object hash.
717
+ * @returns {UrlMatcher} The UrlMatcher.
298
718
  */
299
- this.compile = function (pattern) {
300
- return new UrlMatcher(pattern, useCaseInsensitiveMatch);
719
+ this.compile = function (pattern, config) {
720
+ return new UrlMatcher(pattern, extend(getDefaultConfig(), config));
301
721
  };
302
722
 
303
723
  /**
@@ -306,20 +726,325 @@ function $UrlMatcherFactory() {
306
726
  * @methodOf ui.router.util.$urlMatcherFactory
307
727
  *
308
728
  * @description
309
- * Returns true if the specified object is a UrlMatcher, or false otherwise.
729
+ * Returns true if the specified object is a `UrlMatcher`, or false otherwise.
310
730
  *
311
731
  * @param {Object} object The object to perform the type check against.
312
- * @returns {Boolean} Returns `true` if the object has the following functions: `exec`, `format`, and `concat`.
732
+ * @returns {Boolean} Returns `true` if the object matches the `UrlMatcher` interface, by
733
+ * implementing all the same methods.
313
734
  */
314
735
  this.isMatcher = function (o) {
315
- return isObject(o) && isFunction(o.exec) && isFunction(o.format) && isFunction(o.concat);
736
+ if (!isObject(o)) return false;
737
+ var result = true;
738
+
739
+ forEach(UrlMatcher.prototype, function(val, name) {
740
+ if (isFunction(val)) {
741
+ result = result && (isDefined(o[name]) && isFunction(o[name]));
742
+ }
743
+ });
744
+ return result;
745
+ };
746
+
747
+ /**
748
+ * @ngdoc function
749
+ * @name ui.router.util.$urlMatcherFactory#type
750
+ * @methodOf ui.router.util.$urlMatcherFactory
751
+ *
752
+ * @description
753
+ * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to
754
+ * generate URLs with typed parameters.
755
+ *
756
+ * @param {string} name The type name.
757
+ * @param {Object|Function} definition The type definition. See
758
+ * {@link ui.router.util.type:Type `Type`} for information on the values accepted.
759
+ * @param {Object|Function} definitionFn (optional) A function that is injected before the app
760
+ * runtime starts. The result of this function is merged into the existing `definition`.
761
+ * See {@link ui.router.util.type:Type `Type`} for information on the values accepted.
762
+ *
763
+ * @returns {Object} Returns `$urlMatcherFactoryProvider`.
764
+ *
765
+ * @example
766
+ * This is a simple example of a custom type that encodes and decodes items from an
767
+ * array, using the array index as the URL-encoded value:
768
+ *
769
+ * <pre>
770
+ * var list = ['John', 'Paul', 'George', 'Ringo'];
771
+ *
772
+ * $urlMatcherFactoryProvider.type('listItem', {
773
+ * encode: function(item) {
774
+ * // Represent the list item in the URL using its corresponding index
775
+ * return list.indexOf(item);
776
+ * },
777
+ * decode: function(item) {
778
+ * // Look up the list item by index
779
+ * return list[parseInt(item, 10)];
780
+ * },
781
+ * is: function(item) {
782
+ * // Ensure the item is valid by checking to see that it appears
783
+ * // in the list
784
+ * return list.indexOf(item) > -1;
785
+ * }
786
+ * });
787
+ *
788
+ * $stateProvider.state('list', {
789
+ * url: "/list/{item:listItem}",
790
+ * controller: function($scope, $stateParams) {
791
+ * console.log($stateParams.item);
792
+ * }
793
+ * });
794
+ *
795
+ * // ...
796
+ *
797
+ * // Changes URL to '/list/3', logs "Ringo" to the console
798
+ * $state.go('list', { item: "Ringo" });
799
+ * </pre>
800
+ *
801
+ * This is a more complex example of a type that relies on dependency injection to
802
+ * interact with services, and uses the parameter name from the URL to infer how to
803
+ * handle encoding and decoding parameter values:
804
+ *
805
+ * <pre>
806
+ * // Defines a custom type that gets a value from a service,
807
+ * // where each service gets different types of values from
808
+ * // a backend API:
809
+ * $urlMatcherFactoryProvider.type('dbObject', {}, function(Users, Posts) {
810
+ *
811
+ * // Matches up services to URL parameter names
812
+ * var services = {
813
+ * user: Users,
814
+ * post: Posts
815
+ * };
816
+ *
817
+ * return {
818
+ * encode: function(object) {
819
+ * // Represent the object in the URL using its unique ID
820
+ * return object.id;
821
+ * },
822
+ * decode: function(value, key) {
823
+ * // Look up the object by ID, using the parameter
824
+ * // name (key) to call the correct service
825
+ * return services[key].findById(value);
826
+ * },
827
+ * is: function(object, key) {
828
+ * // Check that object is a valid dbObject
829
+ * return angular.isObject(object) && object.id && services[key];
830
+ * }
831
+ * equals: function(a, b) {
832
+ * // Check the equality of decoded objects by comparing
833
+ * // their unique IDs
834
+ * return a.id === b.id;
835
+ * }
836
+ * };
837
+ * });
838
+ *
839
+ * // In a config() block, you can then attach URLs with
840
+ * // type-annotated parameters:
841
+ * $stateProvider.state('users', {
842
+ * url: "/users",
843
+ * // ...
844
+ * }).state('users.item', {
845
+ * url: "/{user:dbObject}",
846
+ * controller: function($scope, $stateParams) {
847
+ * // $stateParams.user will now be an object returned from
848
+ * // the Users service
849
+ * },
850
+ * // ...
851
+ * });
852
+ * </pre>
853
+ */
854
+ this.type = function (name, definition, definitionFn) {
855
+ if (!isDefined(definition)) return $types[name];
856
+ if ($types.hasOwnProperty(name)) throw new Error("A type named '" + name + "' has already been defined.");
857
+
858
+ $types[name] = new Type(extend({ name: name }, definition));
859
+ if (definitionFn) {
860
+ typeQueue.push({ name: name, def: definitionFn });
861
+ if (!enqueue) flushTypeQueue();
862
+ }
863
+ return this;
316
864
  };
317
-
865
+
866
+ // `flushTypeQueue()` waits until `$urlMatcherFactory` is injected before invoking the queued `definitionFn`s
867
+ function flushTypeQueue() {
868
+ while(typeQueue.length) {
869
+ var type = typeQueue.shift();
870
+ if (type.pattern) throw new Error("You cannot override a type's .pattern at runtime.");
871
+ angular.extend($types[type.name], injector.invoke(type.def));
872
+ }
873
+ }
874
+
875
+ // Register default types. Store them in the prototype of $types.
876
+ forEach(defaultTypes, function(type, name) { $types[name] = new Type(extend({name: name}, type)); });
877
+ $types = inherit($types, {});
878
+
318
879
  /* No need to document $get, since it returns this */
319
- this.$get = function () {
880
+ this.$get = ['$injector', function ($injector) {
881
+ injector = $injector;
882
+ enqueue = false;
883
+ flushTypeQueue();
884
+
885
+ forEach(defaultTypes, function(type, name) {
886
+ if (!$types[name]) $types[name] = new Type(type);
887
+ });
320
888
  return this;
889
+ }];
890
+
891
+ this.Param = function Param(id, type, config, location) {
892
+ var self = this;
893
+ config = unwrapShorthand(config);
894
+ type = getType(config, type, location);
895
+ var arrayMode = getArrayMode();
896
+ type = arrayMode ? type.$asArray(arrayMode, location === "search") : type;
897
+ if (type.name === "string" && !arrayMode && location === "path" && config.value === undefined)
898
+ config.value = ""; // for 0.2.x; in 0.3.0+ do not automatically default to ""
899
+ var isOptional = config.value !== undefined;
900
+ var squash = getSquashPolicy(config, isOptional);
901
+ var replace = getReplace(config, arrayMode, isOptional, squash);
902
+
903
+ function unwrapShorthand(config) {
904
+ var keys = isObject(config) ? objectKeys(config) : [];
905
+ var isShorthand = indexOf(keys, "value") === -1 && indexOf(keys, "type") === -1 &&
906
+ indexOf(keys, "squash") === -1 && indexOf(keys, "array") === -1;
907
+ if (isShorthand) config = { value: config };
908
+ config.$$fn = isInjectable(config.value) ? config.value : function () { return config.value; };
909
+ return config;
910
+ }
911
+
912
+ function getType(config, urlType, location) {
913
+ if (config.type && urlType) throw new Error("Param '"+id+"' has two type configurations.");
914
+ if (urlType) return urlType;
915
+ if (!config.type) return (location === "config" ? $types.any : $types.string);
916
+ return config.type instanceof Type ? config.type : new Type(config.type);
917
+ }
918
+
919
+ // array config: param name (param[]) overrides default settings. explicit config overrides param name.
920
+ function getArrayMode() {
921
+ var arrayDefaults = { array: (location === "search" ? "auto" : false) };
922
+ var arrayParamNomenclature = id.match(/\[\]$/) ? { array: true } : {};
923
+ return extend(arrayDefaults, arrayParamNomenclature, config).array;
924
+ }
925
+
926
+ /**
927
+ * returns false, true, or the squash value to indicate the "default parameter url squash policy".
928
+ */
929
+ function getSquashPolicy(config, isOptional) {
930
+ var squash = config.squash;
931
+ if (!isOptional || squash === false) return false;
932
+ if (!isDefined(squash) || squash == null) return defaultSquashPolicy;
933
+ if (squash === true || isString(squash)) return squash;
934
+ throw new Error("Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string");
935
+ }
936
+
937
+ function getReplace(config, arrayMode, isOptional, squash) {
938
+ var replace, configuredKeys, defaultPolicy = [
939
+ { from: "", to: (isOptional || arrayMode ? undefined : "") },
940
+ { from: null, to: (isOptional || arrayMode ? undefined : "") }
941
+ ];
942
+ replace = isArray(config.replace) ? config.replace : [];
943
+ if (isString(squash))
944
+ replace.push({ from: squash, to: undefined });
945
+ configuredKeys = map(replace, function(item) { return item.from; } );
946
+ return filter(defaultPolicy, function(item) { return indexOf(configuredKeys, item.from) === -1; }).concat(replace);
947
+ }
948
+
949
+ /**
950
+ * [Internal] Get the default value of a parameter, which may be an injectable function.
951
+ */
952
+ function $$getDefaultValue() {
953
+ if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
954
+ var defaultValue = injector.invoke(config.$$fn);
955
+ if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue))
956
+ throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")");
957
+ return defaultValue;
958
+ }
959
+
960
+ /**
961
+ * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the
962
+ * default value, which may be the result of an injectable function.
963
+ */
964
+ function $value(value) {
965
+ function hasReplaceVal(val) { return function(obj) { return obj.from === val; }; }
966
+ function $replace(value) {
967
+ var replacement = map(filter(self.replace, hasReplaceVal(value)), function(obj) { return obj.to; });
968
+ return replacement.length ? replacement[0] : value;
969
+ }
970
+ value = $replace(value);
971
+ return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value);
972
+ }
973
+
974
+ function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; }
975
+
976
+ extend(this, {
977
+ id: id,
978
+ type: type,
979
+ location: location,
980
+ array: arrayMode,
981
+ squash: squash,
982
+ replace: replace,
983
+ isOptional: isOptional,
984
+ value: $value,
985
+ dynamic: undefined,
986
+ config: config,
987
+ toString: toString
988
+ });
321
989
  };
990
+
991
+ function ParamSet(params) {
992
+ extend(this, params || {});
993
+ }
994
+
995
+ ParamSet.prototype = {
996
+ $$new: function() {
997
+ return inherit(this, extend(new ParamSet(), { $$parent: this}));
998
+ },
999
+ $$keys: function () {
1000
+ var keys = [], chain = [], parent = this,
1001
+ ignore = objectKeys(ParamSet.prototype);
1002
+ while (parent) { chain.push(parent); parent = parent.$$parent; }
1003
+ chain.reverse();
1004
+ forEach(chain, function(paramset) {
1005
+ forEach(objectKeys(paramset), function(key) {
1006
+ if (indexOf(keys, key) === -1 && indexOf(ignore, key) === -1) keys.push(key);
1007
+ });
1008
+ });
1009
+ return keys;
1010
+ },
1011
+ $$values: function(paramValues) {
1012
+ var values = {}, self = this;
1013
+ forEach(self.$$keys(), function(key) {
1014
+ values[key] = self[key].value(paramValues && paramValues[key]);
1015
+ });
1016
+ return values;
1017
+ },
1018
+ $$equals: function(paramValues1, paramValues2) {
1019
+ var equal = true, self = this;
1020
+ forEach(self.$$keys(), function(key) {
1021
+ var left = paramValues1 && paramValues1[key], right = paramValues2 && paramValues2[key];
1022
+ if (!self[key].type.equals(left, right)) equal = false;
1023
+ });
1024
+ return equal;
1025
+ },
1026
+ $$validates: function $$validate(paramValues) {
1027
+ var keys = this.$$keys(), i, param, rawVal, normalized, encoded;
1028
+ for (i = 0; i < keys.length; i++) {
1029
+ param = this[keys[i]];
1030
+ rawVal = paramValues[keys[i]];
1031
+ if ((rawVal === undefined || rawVal === null) && param.isOptional)
1032
+ break; // There was no parameter value, but the param is optional
1033
+ normalized = param.type.$normalize(rawVal);
1034
+ if (!param.type.is(normalized))
1035
+ return false; // The value was not of the correct Type, and could not be decoded to the correct Type
1036
+ encoded = param.type.encode(normalized);
1037
+ if (angular.isString(encoded) && !param.type.pattern.exec(encoded))
1038
+ return false; // The value was of the correct type, but when encoded, did not match the Type's regexp
1039
+ }
1040
+ return true;
1041
+ },
1042
+ $$parent: undefined
1043
+ };
1044
+
1045
+ this.ParamSet = ParamSet;
322
1046
  }
323
1047
 
324
1048
  // Register as a provider so it's available to other providers
325
1049
  angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory);
1050
+ angular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]);