bastion 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. checksums.yaml +15 -0
  2. data/.jshintrc +35 -0
  3. data/Gruntfile.js +24 -0
  4. data/LICENSE +339 -0
  5. data/README.md +8 -0
  6. data/Rakefile +53 -0
  7. data/app/assets/javascripts/bastion/auth/auth.module.js +87 -0
  8. data/app/assets/javascripts/bastion/auth/authorization.service.js +48 -0
  9. data/app/assets/javascripts/bastion/bastion-bootstrap.js +32 -0
  10. data/app/assets/javascripts/bastion/bastion-resource.factory.js +39 -0
  11. data/app/assets/javascripts/bastion/bastion.js +52 -0
  12. data/app/assets/javascripts/bastion/bastion.module.js +213 -0
  13. data/app/assets/javascripts/bastion/i18n/README +9 -0
  14. data/app/assets/javascripts/bastion/i18n/i18n.module.js +24 -0
  15. data/app/assets/javascripts/bastion/i18n/katello.pot +968 -0
  16. data/app/assets/javascripts/bastion/i18n/locale/README +1 -0
  17. data/app/assets/javascripts/bastion/i18n/translate.service.js +14 -0
  18. data/app/assets/javascripts/bastion/i18n/translations.js +3 -0
  19. data/app/assets/javascripts/bastion/i18n/zanata.xml +28 -0
  20. data/app/assets/javascripts/bastion/incubator/alch-alert.directive.js +67 -0
  21. data/app/assets/javascripts/bastion/incubator/alch-container-scroll.directive.js +44 -0
  22. data/app/assets/javascripts/bastion/incubator/alch-dropdown.directive.js +28 -0
  23. data/app/assets/javascripts/bastion/incubator/alch-edit.directive.js +361 -0
  24. data/app/assets/javascripts/bastion/incubator/alch-flyout.directive.js +19 -0
  25. data/app/assets/javascripts/bastion/incubator/alch-form-buttons.directive.js +60 -0
  26. data/app/assets/javascripts/bastion/incubator/alch-form-group.directive.js +87 -0
  27. data/app/assets/javascripts/bastion/incubator/alch-infinite-scroll.directive.js +84 -0
  28. data/app/assets/javascripts/bastion/incubator/alch-menu.directive.js +56 -0
  29. data/app/assets/javascripts/bastion/incubator/alch-modal.directive.js +80 -0
  30. data/app/assets/javascripts/bastion/incubator/alch-save-control.directive.js +42 -0
  31. data/app/assets/javascripts/bastion/incubator/alch-table.directive.js +295 -0
  32. data/app/assets/javascripts/bastion/incubator/format/alch-format.module.js +21 -0
  33. data/app/assets/javascripts/bastion/incubator/format/array-to-string.filter.js +30 -0
  34. data/app/assets/javascripts/bastion/incubator/format/boolean-to-yes-no.filter.js +34 -0
  35. data/app/assets/javascripts/bastion/incubator/format/capitalize.filter.js +32 -0
  36. data/app/assets/javascripts/bastion/incubator/format/key-value-to-string.filter.js +40 -0
  37. data/app/assets/javascripts/bastion/incubator/format/unlimitedFilter.filter.js +35 -0
  38. data/app/assets/javascripts/bastion/incubator/stylesheets/alch-edit.scss +34 -0
  39. data/app/assets/javascripts/bastion/incubator/stylesheets/header.scss +207 -0
  40. data/app/assets/javascripts/bastion/incubator/views/alch-alert.html +6 -0
  41. data/app/assets/javascripts/bastion/incubator/views/alch-dropdown.html +19 -0
  42. data/app/assets/javascripts/bastion/incubator/views/alch-edit-add-item.html +21 -0
  43. data/app/assets/javascripts/bastion/incubator/views/alch-edit-add-remove-cancel.html +28 -0
  44. data/app/assets/javascripts/bastion/incubator/views/alch-edit-checkbox.html +6 -0
  45. data/app/assets/javascripts/bastion/incubator/views/alch-edit-custom.html +8 -0
  46. data/app/assets/javascripts/bastion/incubator/views/alch-edit-multiselect.html +17 -0
  47. data/app/assets/javascripts/bastion/incubator/views/alch-edit-save-cancel.html +20 -0
  48. data/app/assets/javascripts/bastion/incubator/views/alch-edit-select.html +10 -0
  49. data/app/assets/javascripts/bastion/incubator/views/alch-edit-text.html +6 -0
  50. data/app/assets/javascripts/bastion/incubator/views/alch-edit-textarea.html +6 -0
  51. data/app/assets/javascripts/bastion/incubator/views/alch-edit.html +18 -0
  52. data/app/assets/javascripts/bastion/incubator/views/alch-flyout.html +10 -0
  53. data/app/assets/javascripts/bastion/incubator/views/alch-form-buttons.html +10 -0
  54. data/app/assets/javascripts/bastion/incubator/views/alch-form-group.html +9 -0
  55. data/app/assets/javascripts/bastion/incubator/views/alch-menu.html +18 -0
  56. data/app/assets/javascripts/bastion/incubator/views/alch-modal-remove.html +13 -0
  57. data/app/assets/javascripts/bastion/incubator/views/alch-save-control.html +18 -0
  58. data/app/assets/javascripts/bastion/layouts/details-nutupane.html +73 -0
  59. data/app/assets/javascripts/bastion/layouts/nutupane.html +63 -0
  60. data/app/assets/javascripts/bastion/layouts/select-all-results.html +9 -0
  61. data/app/assets/javascripts/bastion/menu/menu-expander.service.js +51 -0
  62. data/app/assets/javascripts/bastion/menu/menu.module.js +26 -0
  63. data/app/assets/javascripts/bastion/utils/as.filter.js +33 -0
  64. data/app/assets/javascripts/bastion/utils/form-utils.service.js +53 -0
  65. data/app/assets/javascripts/bastion/utils/utils.module.js +21 -0
  66. data/app/assets/javascripts/bastion/widgets/current-tasks.directive.js +67 -0
  67. data/app/assets/javascripts/bastion/widgets/nutupane-table.directive.js +59 -0
  68. data/app/assets/javascripts/bastion/widgets/nutupane.factory.js +316 -0
  69. data/app/assets/javascripts/bastion/widgets/page-title.directive.js +61 -0
  70. data/app/assets/javascripts/bastion/widgets/page-title.service.js +42 -0
  71. data/app/assets/javascripts/bastion/widgets/path-selector.directive.js +109 -0
  72. data/app/assets/javascripts/bastion/widgets/views/current-tasks.html +23 -0
  73. data/app/assets/javascripts/bastion/widgets/views/path-selector.html +11 -0
  74. data/app/assets/javascripts/bastion/widgets/widgets.module.js +24 -0
  75. data/app/assets/stylesheets/bastion/animations.less +15 -0
  76. data/app/assets/stylesheets/bastion/bastion.less +160 -0
  77. data/app/assets/stylesheets/bastion/forms.less +41 -0
  78. data/app/assets/stylesheets/bastion/gpg-keys.less +36 -0
  79. data/app/assets/stylesheets/bastion/helpers.less +6 -0
  80. data/app/assets/stylesheets/bastion/mixins.less +15 -0
  81. data/app/assets/stylesheets/bastion/nutupane.less +382 -0
  82. data/app/assets/stylesheets/bastion/overrides.less +54 -0
  83. data/app/assets/stylesheets/bastion/path-selector.less +123 -0
  84. data/app/assets/stylesheets/bastion/systems.less +35 -0
  85. data/app/assets/stylesheets/bastion/tasks.less +23 -0
  86. data/app/assets/stylesheets/bastion/typography.less +31 -0
  87. data/app/assets/stylesheets/bastion/variables.less +3 -0
  88. data/app/controllers/bastion/bastion_controller.rb +24 -0
  89. data/app/views/bastion/layouts/application.html.erb +32 -0
  90. data/app/views/bastion/layouts/application_ie.html.erb +5 -0
  91. data/bastion.js +26 -0
  92. data/bower.json +94 -0
  93. data/config/routes.rb +25 -0
  94. data/config/routes/mount_engine.rb +3 -0
  95. data/grunt/bower.js +20 -0
  96. data/grunt/htmlhint.js +15 -0
  97. data/grunt/jshint.js +9 -0
  98. data/grunt/karma.js +83 -0
  99. data/lib/bastion.rb +17 -0
  100. data/lib/bastion/engine.rb +37 -0
  101. data/lib/bastion/version.rb +3 -0
  102. data/package.json +29 -0
  103. data/test/auth/authorization.service.test.js +63 -0
  104. data/test/bastion/bastion-resource.factory.test.js +31 -0
  105. data/test/bastion/test-constants.js +30 -0
  106. data/test/i18n/translate.service.test.js +31 -0
  107. data/test/incubator/alch-alert.directive.test.js +84 -0
  108. data/test/incubator/alch-container-scroll.directive.test.js +68 -0
  109. data/test/incubator/alch-dropdown.directive.test.js +113 -0
  110. data/test/incubator/alch-edit.directive.test.js +320 -0
  111. data/test/incubator/alch-flyout.directive.test.js +73 -0
  112. data/test/incubator/alch-form-buttons.directive.test.js +55 -0
  113. data/test/incubator/alch-form-group.directive.test.js +76 -0
  114. data/test/incubator/alch-infinite-scroll.directive.test.js +123 -0
  115. data/test/incubator/alch-menu.directive.test.js +94 -0
  116. data/test/incubator/alch-modal.directive.test.js +81 -0
  117. data/test/incubator/alch-table.directive.test.js +270 -0
  118. data/test/incubator/format/array-to-string.filter.test.js +38 -0
  119. data/test/incubator/format/boolean-to-yes-no.filter.test.js +41 -0
  120. data/test/incubator/format/capitalize.filter.test.js +35 -0
  121. data/test/incubator/format/key-value-to-string.filter.test.js +60 -0
  122. data/test/incubator/format/unlimited-filter.filter.test.js +31 -0
  123. data/test/incubator/nutupane-table.directive.test.js +69 -0
  124. data/test/incubator/nutupane.factory.test.js +307 -0
  125. data/test/menu/menu-expander.service.test.js +74 -0
  126. data/test/test-mocks.module.js +268 -0
  127. data/test/utils/as.filter.test.js +33 -0
  128. data/test/utils/form-utils.service.test.js +52 -0
  129. data/test/widgets/page-title.directive.test.js +54 -0
  130. data/test/widgets/page-title.service.test.js +51 -0
  131. data/test/widgets/path-selector.directive.test.js +136 -0
  132. data/vendor/assets/fonts/bastion/bootstrap/glyphicons-halflings-regular.eot +0 -0
  133. data/vendor/assets/fonts/bastion/bootstrap/glyphicons-halflings-regular.svg +229 -0
  134. data/vendor/assets/fonts/bastion/bootstrap/glyphicons-halflings-regular.ttf +0 -0
  135. data/vendor/assets/fonts/bastion/bootstrap/glyphicons-halflings-regular.woff +0 -0
  136. data/vendor/assets/fonts/bastion/font-awesome/FontAwesome.otf +0 -0
  137. data/vendor/assets/fonts/bastion/font-awesome/fontawesome-webfont.eot +0 -0
  138. data/vendor/assets/fonts/bastion/font-awesome/fontawesome-webfont.svg +399 -0
  139. data/vendor/assets/fonts/bastion/font-awesome/fontawesome-webfont.ttf +0 -0
  140. data/vendor/assets/fonts/bastion/font-awesome/fontawesome-webfont.woff +0 -0
  141. data/vendor/assets/fonts/bastion/rcue/OpenSans-Bold-webfont.eot +0 -0
  142. data/vendor/assets/fonts/bastion/rcue/OpenSans-Bold-webfont.svg +146 -0
  143. data/vendor/assets/fonts/bastion/rcue/OpenSans-Bold-webfont.ttf +0 -0
  144. data/vendor/assets/fonts/bastion/rcue/OpenSans-Bold-webfont.woff +0 -0
  145. data/vendor/assets/fonts/bastion/rcue/OpenSans-BoldItalic-webfont.eot +0 -0
  146. data/vendor/assets/fonts/bastion/rcue/OpenSans-BoldItalic-webfont.svg +146 -0
  147. data/vendor/assets/fonts/bastion/rcue/OpenSans-BoldItalic-webfont.ttf +0 -0
  148. data/vendor/assets/fonts/bastion/rcue/OpenSans-BoldItalic-webfont.woff +0 -0
  149. data/vendor/assets/fonts/bastion/rcue/OpenSans-ExtraBold-webfont.eot +0 -0
  150. data/vendor/assets/fonts/bastion/rcue/OpenSans-ExtraBold-webfont.svg +146 -0
  151. data/vendor/assets/fonts/bastion/rcue/OpenSans-ExtraBold-webfont.ttf +0 -0
  152. data/vendor/assets/fonts/bastion/rcue/OpenSans-ExtraBold-webfont.woff +0 -0
  153. data/vendor/assets/fonts/bastion/rcue/OpenSans-ExtraBoldItalic-webfont.eot +0 -0
  154. data/vendor/assets/fonts/bastion/rcue/OpenSans-ExtraBoldItalic-webfont.svg +146 -0
  155. data/vendor/assets/fonts/bastion/rcue/OpenSans-ExtraBoldItalic-webfont.ttf +0 -0
  156. data/vendor/assets/fonts/bastion/rcue/OpenSans-ExtraBoldItalic-webfont.woff +0 -0
  157. data/vendor/assets/fonts/bastion/rcue/OpenSans-Italic-webfont.eot +0 -0
  158. data/vendor/assets/fonts/bastion/rcue/OpenSans-Italic-webfont.svg +146 -0
  159. data/vendor/assets/fonts/bastion/rcue/OpenSans-Italic-webfont.ttf +0 -0
  160. data/vendor/assets/fonts/bastion/rcue/OpenSans-Italic-webfont.woff +0 -0
  161. data/vendor/assets/fonts/bastion/rcue/OpenSans-Light-webfont.eot +0 -0
  162. data/vendor/assets/fonts/bastion/rcue/OpenSans-Light-webfont.svg +146 -0
  163. data/vendor/assets/fonts/bastion/rcue/OpenSans-Light-webfont.ttf +0 -0
  164. data/vendor/assets/fonts/bastion/rcue/OpenSans-Light-webfont.woff +0 -0
  165. data/vendor/assets/fonts/bastion/rcue/OpenSans-LightItalic-webfont.eot +0 -0
  166. data/vendor/assets/fonts/bastion/rcue/OpenSans-LightItalic-webfont.svg +146 -0
  167. data/vendor/assets/fonts/bastion/rcue/OpenSans-LightItalic-webfont.ttf +0 -0
  168. data/vendor/assets/fonts/bastion/rcue/OpenSans-LightItalic-webfont.woff +0 -0
  169. data/vendor/assets/fonts/bastion/rcue/OpenSans-Regular-webfont.eot +0 -0
  170. data/vendor/assets/fonts/bastion/rcue/OpenSans-Regular-webfont.svg +146 -0
  171. data/vendor/assets/fonts/bastion/rcue/OpenSans-Regular-webfont.ttf +0 -0
  172. data/vendor/assets/fonts/bastion/rcue/OpenSans-Regular-webfont.woff +0 -0
  173. data/vendor/assets/fonts/bastion/rcue/OpenSans-Semibold-webfont.eot +0 -0
  174. data/vendor/assets/fonts/bastion/rcue/OpenSans-Semibold-webfont.svg +146 -0
  175. data/vendor/assets/fonts/bastion/rcue/OpenSans-Semibold-webfont.ttf +0 -0
  176. data/vendor/assets/fonts/bastion/rcue/OpenSans-Semibold-webfont.woff +0 -0
  177. data/vendor/assets/fonts/bastion/rcue/OpenSans-SemiboldItalic-webfont.eot +0 -0
  178. data/vendor/assets/fonts/bastion/rcue/OpenSans-SemiboldItalic-webfont.svg +146 -0
  179. data/vendor/assets/fonts/bastion/rcue/OpenSans-SemiboldItalic-webfont.ttf +0 -0
  180. data/vendor/assets/fonts/bastion/rcue/OpenSans-SemiboldItalic-webfont.woff +0 -0
  181. data/vendor/assets/fonts/bastion/rcue/Overpass-Bold-webfont.eot +0 -0
  182. data/vendor/assets/fonts/bastion/rcue/Overpass-Bold-webfont.svg +454 -0
  183. data/vendor/assets/fonts/bastion/rcue/Overpass-Bold-webfont.ttf +0 -0
  184. data/vendor/assets/fonts/bastion/rcue/Overpass-Bold-webfont.woff +0 -0
  185. data/vendor/assets/fonts/bastion/rcue/Overpass-Regular-webfont.eot +0 -0
  186. data/vendor/assets/fonts/bastion/rcue/Overpass-Regular-webfont.svg +454 -0
  187. data/vendor/assets/fonts/bastion/rcue/Overpass-Regular-webfont.ttf +0 -0
  188. data/vendor/assets/fonts/bastion/rcue/Overpass-Regular-webfont.woff +0 -0
  189. data/vendor/assets/javascripts/bastion/alchemy/alchemy.js +20 -0
  190. data/vendor/assets/javascripts/bastion/angular-animate/angular-animate.js +1226 -0
  191. data/vendor/assets/javascripts/bastion/angular-blocks/angular-blocks.js +79 -0
  192. data/vendor/assets/javascripts/bastion/angular-bootstrap/ui-bootstrap-tpls.js +3677 -0
  193. data/vendor/assets/javascripts/bastion/angular-bootstrap/ui-bootstrap.js +3427 -0
  194. data/vendor/assets/javascripts/bastion/angular-gettext/angular-gettext.js +345 -0
  195. data/vendor/assets/javascripts/bastion/angular-resource/angular-resource.js +594 -0
  196. data/vendor/assets/javascripts/bastion/angular-route/angular-route.js +920 -0
  197. data/vendor/assets/javascripts/bastion/angular-sanitize/angular-sanitize.js +622 -0
  198. data/vendor/assets/javascripts/bastion/angular-ui-bootstrap/datepicker.js +447 -0
  199. data/vendor/assets/javascripts/bastion/angular-ui-bootstrap/timepicker.js +232 -0
  200. data/vendor/assets/javascripts/bastion/angular-ui-router/angular-ui-router.js +3223 -0
  201. data/vendor/assets/javascripts/bastion/angular-uuid4/angular-uuid4.js +21 -0
  202. data/vendor/assets/javascripts/bastion/angular/angular.js +20560 -0
  203. data/vendor/assets/javascripts/bastion/es5-shim/es5-shim.js +1216 -0
  204. data/vendor/assets/javascripts/bastion/json3/json3.js +861 -0
  205. data/vendor/assets/javascripts/bastion/ngUpload/ng-upload.js +205 -0
  206. data/vendor/assets/javascripts/bastion/underscore/underscore.js +1276 -0
  207. data/vendor/assets/stylesheets/bastion/alchemy/_colors.scss +99 -0
  208. data/vendor/assets/stylesheets/bastion/alchemy/_media_object.scss +22 -0
  209. data/vendor/assets/stylesheets/bastion/alchemy/_mixins.scss +24 -0
  210. data/vendor/assets/stylesheets/bastion/alchemy/_normalize.scss +396 -0
  211. data/vendor/assets/stylesheets/bastion/alchemy/_vars.scss +31 -0
  212. data/vendor/assets/stylesheets/bastion/alchemy/alchemy.scss +8 -0
  213. data/vendor/assets/stylesheets/bastion/bootstrap/alerts.less +67 -0
  214. data/vendor/assets/stylesheets/bastion/bootstrap/badges.less +51 -0
  215. data/vendor/assets/stylesheets/bastion/bootstrap/bootstrap.less +49 -0
  216. data/vendor/assets/stylesheets/bastion/bootstrap/breadcrumbs.less +23 -0
  217. data/vendor/assets/stylesheets/bastion/bootstrap/button-groups.less +227 -0
  218. data/vendor/assets/stylesheets/bastion/bootstrap/buttons.less +155 -0
  219. data/vendor/assets/stylesheets/bastion/bootstrap/carousel.less +232 -0
  220. data/vendor/assets/stylesheets/bastion/bootstrap/close.less +33 -0
  221. data/vendor/assets/stylesheets/bastion/bootstrap/code.less +53 -0
  222. data/vendor/assets/stylesheets/bastion/bootstrap/component-animations.less +29 -0
  223. data/vendor/assets/stylesheets/bastion/bootstrap/dropdowns.less +187 -0
  224. data/vendor/assets/stylesheets/bastion/bootstrap/forms.less +375 -0
  225. data/vendor/assets/stylesheets/bastion/bootstrap/glyphicons.less +237 -0
  226. data/vendor/assets/stylesheets/bastion/bootstrap/grid.less +79 -0
  227. data/vendor/assets/stylesheets/bastion/bootstrap/input-groups.less +136 -0
  228. data/vendor/assets/stylesheets/bastion/bootstrap/jumbotron.less +46 -0
  229. data/vendor/assets/stylesheets/bastion/bootstrap/labels.less +64 -0
  230. data/vendor/assets/stylesheets/bastion/bootstrap/list-group.less +88 -0
  231. data/vendor/assets/stylesheets/bastion/bootstrap/media.less +56 -0
  232. data/vendor/assets/stylesheets/bastion/bootstrap/mixins.less +845 -0
  233. data/vendor/assets/stylesheets/bastion/bootstrap/modals.less +129 -0
  234. data/vendor/assets/stylesheets/bastion/bootstrap/navbar.less +612 -0
  235. data/vendor/assets/stylesheets/bastion/bootstrap/navs.less +242 -0
  236. data/vendor/assets/stylesheets/bastion/bootstrap/normalize.less +406 -0
  237. data/vendor/assets/stylesheets/bastion/bootstrap/pager.less +55 -0
  238. data/vendor/assets/stylesheets/bastion/bootstrap/pagination.less +85 -0
  239. data/vendor/assets/stylesheets/bastion/bootstrap/panels.less +182 -0
  240. data/vendor/assets/stylesheets/bastion/bootstrap/popovers.less +133 -0
  241. data/vendor/assets/stylesheets/bastion/bootstrap/print.less +105 -0
  242. data/vendor/assets/stylesheets/bastion/bootstrap/progress-bars.less +80 -0
  243. data/vendor/assets/stylesheets/bastion/bootstrap/responsive-utilities.less +209 -0
  244. data/vendor/assets/stylesheets/bastion/bootstrap/scaffolding.less +119 -0
  245. data/vendor/assets/stylesheets/bastion/bootstrap/tables.less +231 -0
  246. data/vendor/assets/stylesheets/bastion/bootstrap/theme.less +247 -0
  247. data/vendor/assets/stylesheets/bastion/bootstrap/thumbnails.less +36 -0
  248. data/vendor/assets/stylesheets/bastion/bootstrap/tooltip.less +95 -0
  249. data/vendor/assets/stylesheets/bastion/bootstrap/type.less +281 -0
  250. data/vendor/assets/stylesheets/bastion/bootstrap/utilities.less +56 -0
  251. data/vendor/assets/stylesheets/bastion/bootstrap/variables.less +642 -0
  252. data/vendor/assets/stylesheets/bastion/bootstrap/wells.less +29 -0
  253. data/vendor/assets/stylesheets/bastion/font-awesome/less/bootstrap.less +84 -0
  254. data/vendor/assets/stylesheets/bastion/font-awesome/less/core.less +129 -0
  255. data/vendor/assets/stylesheets/bastion/font-awesome/less/extras.less +93 -0
  256. data/vendor/assets/stylesheets/bastion/font-awesome/less/font-awesome-ie7.less +1953 -0
  257. data/vendor/assets/stylesheets/bastion/font-awesome/less/font-awesome.less +33 -0
  258. data/vendor/assets/stylesheets/bastion/font-awesome/less/icons.less +381 -0
  259. data/vendor/assets/stylesheets/bastion/font-awesome/less/mixins.less +48 -0
  260. data/vendor/assets/stylesheets/bastion/font-awesome/less/path.less +14 -0
  261. data/vendor/assets/stylesheets/bastion/font-awesome/less/variables.less +735 -0
  262. data/vendor/assets/stylesheets/bastion/font-awesome/scss/_bootstrap.scss +84 -0
  263. data/vendor/assets/stylesheets/bastion/font-awesome/scss/_core.scss +129 -0
  264. data/vendor/assets/stylesheets/bastion/font-awesome/scss/_extras.scss +93 -0
  265. data/vendor/assets/stylesheets/bastion/font-awesome/scss/_icons.scss +381 -0
  266. data/vendor/assets/stylesheets/bastion/font-awesome/scss/_mixins.scss +48 -0
  267. data/vendor/assets/stylesheets/bastion/font-awesome/scss/_path.scss +14 -0
  268. data/vendor/assets/stylesheets/bastion/font-awesome/scss/_variables.scss +734 -0
  269. data/vendor/assets/stylesheets/bastion/font-awesome/scss/font-awesome-ie7.scss +1953 -0
  270. data/vendor/assets/stylesheets/bastion/font-awesome/scss/font-awesome.scss +33 -0
  271. data/vendor/assets/stylesheets/bastion/rcue/buttons.less +44 -0
  272. data/vendor/assets/stylesheets/bastion/rcue/fonts.less +52 -0
  273. data/vendor/assets/stylesheets/bastion/rcue/forms.less +19 -0
  274. data/vendor/assets/stylesheets/bastion/rcue/mixins.less +50 -0
  275. data/vendor/assets/stylesheets/bastion/rcue/navbar.less +481 -0
  276. data/vendor/assets/stylesheets/bastion/rcue/rcue.less +15 -0
  277. data/vendor/assets/stylesheets/bastion/rcue/variables.less +47 -0
  278. metadata +376 -0
@@ -0,0 +1,3427 @@
1
+ /*
2
+ * angular-ui-bootstrap
3
+ * http://angular-ui.github.io/bootstrap/
4
+
5
+ * Version: 0.10.0 - 2014-01-13
6
+ * License: MIT
7
+ */
8
+ angular.module("ui.bootstrap", ["ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);
9
+ angular.module('ui.bootstrap.transition', [])
10
+
11
+ /**
12
+ * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete.
13
+ * @param {DOMElement} element The DOMElement that will be animated.
14
+ * @param {string|object|function} trigger The thing that will cause the transition to start:
15
+ * - As a string, it represents the css class to be added to the element.
16
+ * - As an object, it represents a hash of style attributes to be applied to the element.
17
+ * - As a function, it represents a function to be called that will cause the transition to occur.
18
+ * @return {Promise} A promise that is resolved when the transition finishes.
19
+ */
20
+ .factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) {
21
+
22
+ var $transition = function(element, trigger, options) {
23
+ options = options || {};
24
+ var deferred = $q.defer();
25
+ var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"];
26
+
27
+ var transitionEndHandler = function(event) {
28
+ $rootScope.$apply(function() {
29
+ element.unbind(endEventName, transitionEndHandler);
30
+ deferred.resolve(element);
31
+ });
32
+ };
33
+
34
+ if (endEventName) {
35
+ element.bind(endEventName, transitionEndHandler);
36
+ }
37
+
38
+ // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
39
+ $timeout(function() {
40
+ if ( angular.isString(trigger) ) {
41
+ element.addClass(trigger);
42
+ } else if ( angular.isFunction(trigger) ) {
43
+ trigger(element);
44
+ } else if ( angular.isObject(trigger) ) {
45
+ element.css(trigger);
46
+ }
47
+ //If browser does not support transitions, instantly resolve
48
+ if ( !endEventName ) {
49
+ deferred.resolve(element);
50
+ }
51
+ });
52
+
53
+ // Add our custom cancel function to the promise that is returned
54
+ // We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
55
+ // i.e. it will therefore never raise a transitionEnd event for that transition
56
+ deferred.promise.cancel = function() {
57
+ if ( endEventName ) {
58
+ element.unbind(endEventName, transitionEndHandler);
59
+ }
60
+ deferred.reject('Transition cancelled');
61
+ };
62
+
63
+ return deferred.promise;
64
+ };
65
+
66
+ // Work out the name of the transitionEnd event
67
+ var transElement = document.createElement('trans');
68
+ var transitionEndEventNames = {
69
+ 'WebkitTransition': 'webkitTransitionEnd',
70
+ 'MozTransition': 'transitionend',
71
+ 'OTransition': 'oTransitionEnd',
72
+ 'transition': 'transitionend'
73
+ };
74
+ var animationEndEventNames = {
75
+ 'WebkitTransition': 'webkitAnimationEnd',
76
+ 'MozTransition': 'animationend',
77
+ 'OTransition': 'oAnimationEnd',
78
+ 'transition': 'animationend'
79
+ };
80
+ function findEndEventName(endEventNames) {
81
+ for (var name in endEventNames){
82
+ if (transElement.style[name] !== undefined) {
83
+ return endEventNames[name];
84
+ }
85
+ }
86
+ }
87
+ $transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
88
+ $transition.animationEndEventName = findEndEventName(animationEndEventNames);
89
+ return $transition;
90
+ }]);
91
+
92
+ angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition'])
93
+
94
+ .directive('collapse', ['$transition', function ($transition, $timeout) {
95
+
96
+ return {
97
+ link: function (scope, element, attrs) {
98
+
99
+ var initialAnimSkip = true;
100
+ var currentTransition;
101
+
102
+ function doTransition(change) {
103
+ var newTransition = $transition(element, change);
104
+ if (currentTransition) {
105
+ currentTransition.cancel();
106
+ }
107
+ currentTransition = newTransition;
108
+ newTransition.then(newTransitionDone, newTransitionDone);
109
+ return newTransition;
110
+
111
+ function newTransitionDone() {
112
+ // Make sure it's this transition, otherwise, leave it alone.
113
+ if (currentTransition === newTransition) {
114
+ currentTransition = undefined;
115
+ }
116
+ }
117
+ }
118
+
119
+ function expand() {
120
+ if (initialAnimSkip) {
121
+ initialAnimSkip = false;
122
+ expandDone();
123
+ } else {
124
+ element.removeClass('collapse').addClass('collapsing');
125
+ doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone);
126
+ }
127
+ }
128
+
129
+ function expandDone() {
130
+ element.removeClass('collapsing');
131
+ element.addClass('collapse in');
132
+ element.css({height: 'auto'});
133
+ }
134
+
135
+ function collapse() {
136
+ if (initialAnimSkip) {
137
+ initialAnimSkip = false;
138
+ collapseDone();
139
+ element.css({height: 0});
140
+ } else {
141
+ // CSS transitions don't work with height: auto, so we have to manually change the height to a specific value
142
+ element.css({ height: element[0].scrollHeight + 'px' });
143
+ //trigger reflow so a browser realizes that height was updated from auto to a specific value
144
+ var x = element[0].offsetWidth;
145
+
146
+ element.removeClass('collapse in').addClass('collapsing');
147
+
148
+ doTransition({ height: 0 }).then(collapseDone);
149
+ }
150
+ }
151
+
152
+ function collapseDone() {
153
+ element.removeClass('collapsing');
154
+ element.addClass('collapse');
155
+ }
156
+
157
+ scope.$watch(attrs.collapse, function (shouldCollapse) {
158
+ if (shouldCollapse) {
159
+ collapse();
160
+ } else {
161
+ expand();
162
+ }
163
+ });
164
+ }
165
+ };
166
+ }]);
167
+
168
+ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
169
+
170
+ .constant('accordionConfig', {
171
+ closeOthers: true
172
+ })
173
+
174
+ .controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) {
175
+
176
+ // This array keeps track of the accordion groups
177
+ this.groups = [];
178
+
179
+ // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
180
+ this.closeOthers = function(openGroup) {
181
+ var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
182
+ if ( closeOthers ) {
183
+ angular.forEach(this.groups, function (group) {
184
+ if ( group !== openGroup ) {
185
+ group.isOpen = false;
186
+ }
187
+ });
188
+ }
189
+ };
190
+
191
+ // This is called from the accordion-group directive to add itself to the accordion
192
+ this.addGroup = function(groupScope) {
193
+ var that = this;
194
+ this.groups.push(groupScope);
195
+
196
+ groupScope.$on('$destroy', function (event) {
197
+ that.removeGroup(groupScope);
198
+ });
199
+ };
200
+
201
+ // This is called from the accordion-group directive when to remove itself
202
+ this.removeGroup = function(group) {
203
+ var index = this.groups.indexOf(group);
204
+ if ( index !== -1 ) {
205
+ this.groups.splice(this.groups.indexOf(group), 1);
206
+ }
207
+ };
208
+
209
+ }])
210
+
211
+ // The accordion directive simply sets up the directive controller
212
+ // and adds an accordion CSS class to itself element.
213
+ .directive('accordion', function () {
214
+ return {
215
+ restrict:'EA',
216
+ controller:'AccordionController',
217
+ transclude: true,
218
+ replace: false,
219
+ templateUrl: 'template/accordion/accordion.html'
220
+ };
221
+ })
222
+
223
+ // The accordion-group directive indicates a block of html that will expand and collapse in an accordion
224
+ .directive('accordionGroup', ['$parse', function($parse) {
225
+ return {
226
+ require:'^accordion', // We need this directive to be inside an accordion
227
+ restrict:'EA',
228
+ transclude:true, // It transcludes the contents of the directive into the template
229
+ replace: true, // The element containing the directive will be replaced with the template
230
+ templateUrl:'template/accordion/accordion-group.html',
231
+ scope:{ heading:'@' }, // Create an isolated scope and interpolate the heading attribute onto this scope
232
+ controller: function() {
233
+ this.setHeading = function(element) {
234
+ this.heading = element;
235
+ };
236
+ },
237
+ link: function(scope, element, attrs, accordionCtrl) {
238
+ var getIsOpen, setIsOpen;
239
+
240
+ accordionCtrl.addGroup(scope);
241
+
242
+ scope.isOpen = false;
243
+
244
+ if ( attrs.isOpen ) {
245
+ getIsOpen = $parse(attrs.isOpen);
246
+ setIsOpen = getIsOpen.assign;
247
+
248
+ scope.$parent.$watch(getIsOpen, function(value) {
249
+ scope.isOpen = !!value;
250
+ });
251
+ }
252
+
253
+ scope.$watch('isOpen', function(value) {
254
+ if ( value ) {
255
+ accordionCtrl.closeOthers(scope);
256
+ }
257
+ if ( setIsOpen ) {
258
+ setIsOpen(scope.$parent, value);
259
+ }
260
+ });
261
+ }
262
+ };
263
+ }])
264
+
265
+ // Use accordion-heading below an accordion-group to provide a heading containing HTML
266
+ // <accordion-group>
267
+ // <accordion-heading>Heading containing HTML - <img src="..."></accordion-heading>
268
+ // </accordion-group>
269
+ .directive('accordionHeading', function() {
270
+ return {
271
+ restrict: 'EA',
272
+ transclude: true, // Grab the contents to be used as the heading
273
+ template: '', // In effect remove this element!
274
+ replace: true,
275
+ require: '^accordionGroup',
276
+ compile: function(element, attr, transclude) {
277
+ return function link(scope, element, attr, accordionGroupCtrl) {
278
+ // Pass the heading to the accordion-group controller
279
+ // so that it can be transcluded into the right place in the template
280
+ // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
281
+ accordionGroupCtrl.setHeading(transclude(scope, function() {}));
282
+ };
283
+ }
284
+ };
285
+ })
286
+
287
+ // Use in the accordion-group template to indicate where you want the heading to be transcluded
288
+ // You must provide the property on the accordion-group controller that will hold the transcluded element
289
+ // <div class="accordion-group">
290
+ // <div class="accordion-heading" ><a ... accordion-transclude="heading">...</a></div>
291
+ // ...
292
+ // </div>
293
+ .directive('accordionTransclude', function() {
294
+ return {
295
+ require: '^accordionGroup',
296
+ link: function(scope, element, attr, controller) {
297
+ scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) {
298
+ if ( heading ) {
299
+ element.html('');
300
+ element.append(heading);
301
+ }
302
+ });
303
+ }
304
+ };
305
+ });
306
+
307
+ angular.module("ui.bootstrap.alert", [])
308
+
309
+ .controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) {
310
+ $scope.closeable = 'close' in $attrs;
311
+ }])
312
+
313
+ .directive('alert', function () {
314
+ return {
315
+ restrict:'EA',
316
+ controller:'AlertController',
317
+ templateUrl:'template/alert/alert.html',
318
+ transclude:true,
319
+ replace:true,
320
+ scope: {
321
+ type: '=',
322
+ close: '&'
323
+ }
324
+ };
325
+ });
326
+
327
+ angular.module('ui.bootstrap.bindHtml', [])
328
+
329
+ .directive('bindHtmlUnsafe', function () {
330
+ return function (scope, element, attr) {
331
+ element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe);
332
+ scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) {
333
+ element.html(value || '');
334
+ });
335
+ };
336
+ });
337
+ angular.module('ui.bootstrap.buttons', [])
338
+
339
+ .constant('buttonConfig', {
340
+ activeClass: 'active',
341
+ toggleEvent: 'click'
342
+ })
343
+
344
+ .controller('ButtonsController', ['buttonConfig', function(buttonConfig) {
345
+ this.activeClass = buttonConfig.activeClass || 'active';
346
+ this.toggleEvent = buttonConfig.toggleEvent || 'click';
347
+ }])
348
+
349
+ .directive('btnRadio', function () {
350
+ return {
351
+ require: ['btnRadio', 'ngModel'],
352
+ controller: 'ButtonsController',
353
+ link: function (scope, element, attrs, ctrls) {
354
+ var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
355
+
356
+ //model -> UI
357
+ ngModelCtrl.$render = function () {
358
+ element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio)));
359
+ };
360
+
361
+ //ui->model
362
+ element.bind(buttonsCtrl.toggleEvent, function () {
363
+ if (!element.hasClass(buttonsCtrl.activeClass)) {
364
+ scope.$apply(function () {
365
+ ngModelCtrl.$setViewValue(scope.$eval(attrs.btnRadio));
366
+ ngModelCtrl.$render();
367
+ });
368
+ }
369
+ });
370
+ }
371
+ };
372
+ })
373
+
374
+ .directive('btnCheckbox', function () {
375
+ return {
376
+ require: ['btnCheckbox', 'ngModel'],
377
+ controller: 'ButtonsController',
378
+ link: function (scope, element, attrs, ctrls) {
379
+ var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
380
+
381
+ function getTrueValue() {
382
+ return getCheckboxValue(attrs.btnCheckboxTrue, true);
383
+ }
384
+
385
+ function getFalseValue() {
386
+ return getCheckboxValue(attrs.btnCheckboxFalse, false);
387
+ }
388
+
389
+ function getCheckboxValue(attributeValue, defaultValue) {
390
+ var val = scope.$eval(attributeValue);
391
+ return angular.isDefined(val) ? val : defaultValue;
392
+ }
393
+
394
+ //model -> UI
395
+ ngModelCtrl.$render = function () {
396
+ element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
397
+ };
398
+
399
+ //ui->model
400
+ element.bind(buttonsCtrl.toggleEvent, function () {
401
+ scope.$apply(function () {
402
+ ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
403
+ ngModelCtrl.$render();
404
+ });
405
+ });
406
+ }
407
+ };
408
+ });
409
+
410
+ /**
411
+ * @ngdoc overview
412
+ * @name ui.bootstrap.carousel
413
+ *
414
+ * @description
415
+ * AngularJS version of an image carousel.
416
+ *
417
+ */
418
+ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
419
+ .controller('CarouselController', ['$scope', '$timeout', '$transition', '$q', function ($scope, $timeout, $transition, $q) {
420
+ var self = this,
421
+ slides = self.slides = [],
422
+ currentIndex = -1,
423
+ currentTimeout, isPlaying;
424
+ self.currentSlide = null;
425
+
426
+ var destroyed = false;
427
+ /* direction: "prev" or "next" */
428
+ self.select = function(nextSlide, direction) {
429
+ var nextIndex = slides.indexOf(nextSlide);
430
+ //Decide direction if it's not given
431
+ if (direction === undefined) {
432
+ direction = nextIndex > currentIndex ? "next" : "prev";
433
+ }
434
+ if (nextSlide && nextSlide !== self.currentSlide) {
435
+ if ($scope.$currentTransition) {
436
+ $scope.$currentTransition.cancel();
437
+ //Timeout so ng-class in template has time to fix classes for finished slide
438
+ $timeout(goNext);
439
+ } else {
440
+ goNext();
441
+ }
442
+ }
443
+ function goNext() {
444
+ // Scope has been destroyed, stop here.
445
+ if (destroyed) { return; }
446
+ //If we have a slide to transition from and we have a transition type and we're allowed, go
447
+ if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) {
448
+ //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime
449
+ nextSlide.$element.addClass(direction);
450
+ var reflow = nextSlide.$element[0].offsetWidth; //force reflow
451
+
452
+ //Set all other slides to stop doing their stuff for the new transition
453
+ angular.forEach(slides, function(slide) {
454
+ angular.extend(slide, {direction: '', entering: false, leaving: false, active: false});
455
+ });
456
+ angular.extend(nextSlide, {direction: direction, active: true, entering: true});
457
+ angular.extend(self.currentSlide||{}, {direction: direction, leaving: true});
458
+
459
+ $scope.$currentTransition = $transition(nextSlide.$element, {});
460
+ //We have to create new pointers inside a closure since next & current will change
461
+ (function(next,current) {
462
+ $scope.$currentTransition.then(
463
+ function(){ transitionDone(next, current); },
464
+ function(){ transitionDone(next, current); }
465
+ );
466
+ }(nextSlide, self.currentSlide));
467
+ } else {
468
+ transitionDone(nextSlide, self.currentSlide);
469
+ }
470
+ self.currentSlide = nextSlide;
471
+ currentIndex = nextIndex;
472
+ //every time you change slides, reset the timer
473
+ restartTimer();
474
+ }
475
+ function transitionDone(next, current) {
476
+ angular.extend(next, {direction: '', active: true, leaving: false, entering: false});
477
+ angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false});
478
+ $scope.$currentTransition = null;
479
+ }
480
+ };
481
+ $scope.$on('$destroy', function () {
482
+ destroyed = true;
483
+ });
484
+
485
+ /* Allow outside people to call indexOf on slides array */
486
+ self.indexOfSlide = function(slide) {
487
+ return slides.indexOf(slide);
488
+ };
489
+
490
+ $scope.next = function() {
491
+ var newIndex = (currentIndex + 1) % slides.length;
492
+
493
+ //Prevent this user-triggered transition from occurring if there is already one in progress
494
+ if (!$scope.$currentTransition) {
495
+ return self.select(slides[newIndex], 'next');
496
+ }
497
+ };
498
+
499
+ $scope.prev = function() {
500
+ var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1;
501
+
502
+ //Prevent this user-triggered transition from occurring if there is already one in progress
503
+ if (!$scope.$currentTransition) {
504
+ return self.select(slides[newIndex], 'prev');
505
+ }
506
+ };
507
+
508
+ $scope.select = function(slide) {
509
+ self.select(slide);
510
+ };
511
+
512
+ $scope.isActive = function(slide) {
513
+ return self.currentSlide === slide;
514
+ };
515
+
516
+ $scope.slides = function() {
517
+ return slides;
518
+ };
519
+
520
+ $scope.$watch('interval', restartTimer);
521
+ $scope.$on('$destroy', resetTimer);
522
+
523
+ function restartTimer() {
524
+ resetTimer();
525
+ var interval = +$scope.interval;
526
+ if (!isNaN(interval) && interval>=0) {
527
+ currentTimeout = $timeout(timerFn, interval);
528
+ }
529
+ }
530
+
531
+ function resetTimer() {
532
+ if (currentTimeout) {
533
+ $timeout.cancel(currentTimeout);
534
+ currentTimeout = null;
535
+ }
536
+ }
537
+
538
+ function timerFn() {
539
+ if (isPlaying) {
540
+ $scope.next();
541
+ restartTimer();
542
+ } else {
543
+ $scope.pause();
544
+ }
545
+ }
546
+
547
+ $scope.play = function() {
548
+ if (!isPlaying) {
549
+ isPlaying = true;
550
+ restartTimer();
551
+ }
552
+ };
553
+ $scope.pause = function() {
554
+ if (!$scope.noPause) {
555
+ isPlaying = false;
556
+ resetTimer();
557
+ }
558
+ };
559
+
560
+ self.addSlide = function(slide, element) {
561
+ slide.$element = element;
562
+ slides.push(slide);
563
+ //if this is the first slide or the slide is set to active, select it
564
+ if(slides.length === 1 || slide.active) {
565
+ self.select(slides[slides.length-1]);
566
+ if (slides.length == 1) {
567
+ $scope.play();
568
+ }
569
+ } else {
570
+ slide.active = false;
571
+ }
572
+ };
573
+
574
+ self.removeSlide = function(slide) {
575
+ //get the index of the slide inside the carousel
576
+ var index = slides.indexOf(slide);
577
+ slides.splice(index, 1);
578
+ if (slides.length > 0 && slide.active) {
579
+ if (index >= slides.length) {
580
+ self.select(slides[index-1]);
581
+ } else {
582
+ self.select(slides[index]);
583
+ }
584
+ } else if (currentIndex > index) {
585
+ currentIndex--;
586
+ }
587
+ };
588
+
589
+ }])
590
+
591
+ /**
592
+ * @ngdoc directive
593
+ * @name ui.bootstrap.carousel.directive:carousel
594
+ * @restrict EA
595
+ *
596
+ * @description
597
+ * Carousel is the outer container for a set of image 'slides' to showcase.
598
+ *
599
+ * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide.
600
+ * @param {boolean=} noTransition Whether to disable transitions on the carousel.
601
+ * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover).
602
+ *
603
+ * @example
604
+ <example module="ui.bootstrap">
605
+ <file name="index.html">
606
+ <carousel>
607
+ <slide>
608
+ <img src="http://placekitten.com/150/150" style="margin:auto;">
609
+ <div class="carousel-caption">
610
+ <p>Beautiful!</p>
611
+ </div>
612
+ </slide>
613
+ <slide>
614
+ <img src="http://placekitten.com/100/150" style="margin:auto;">
615
+ <div class="carousel-caption">
616
+ <p>D'aww!</p>
617
+ </div>
618
+ </slide>
619
+ </carousel>
620
+ </file>
621
+ <file name="demo.css">
622
+ .carousel-indicators {
623
+ top: auto;
624
+ bottom: 15px;
625
+ }
626
+ </file>
627
+ </example>
628
+ */
629
+ .directive('carousel', [function() {
630
+ return {
631
+ restrict: 'EA',
632
+ transclude: true,
633
+ replace: true,
634
+ controller: 'CarouselController',
635
+ require: 'carousel',
636
+ templateUrl: 'template/carousel/carousel.html',
637
+ scope: {
638
+ interval: '=',
639
+ noTransition: '=',
640
+ noPause: '='
641
+ }
642
+ };
643
+ }])
644
+
645
+ /**
646
+ * @ngdoc directive
647
+ * @name ui.bootstrap.carousel.directive:slide
648
+ * @restrict EA
649
+ *
650
+ * @description
651
+ * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element.
652
+ *
653
+ * @param {boolean=} active Model binding, whether or not this slide is currently active.
654
+ *
655
+ * @example
656
+ <example module="ui.bootstrap">
657
+ <file name="index.html">
658
+ <div ng-controller="CarouselDemoCtrl">
659
+ <carousel>
660
+ <slide ng-repeat="slide in slides" active="slide.active">
661
+ <img ng-src="{{slide.image}}" style="margin:auto;">
662
+ <div class="carousel-caption">
663
+ <h4>Slide {{$index}}</h4>
664
+ <p>{{slide.text}}</p>
665
+ </div>
666
+ </slide>
667
+ </carousel>
668
+ <div class="row-fluid">
669
+ <div class="span6">
670
+ <ul>
671
+ <li ng-repeat="slide in slides">
672
+ <button class="btn btn-mini" ng-class="{'btn-info': !slide.active, 'btn-success': slide.active}" ng-disabled="slide.active" ng-click="slide.active = true">select</button>
673
+ {{$index}}: {{slide.text}}
674
+ </li>
675
+ </ul>
676
+ <a class="btn" ng-click="addSlide()">Add Slide</a>
677
+ </div>
678
+ <div class="span6">
679
+ Interval, in milliseconds: <input type="number" ng-model="myInterval">
680
+ <br />Enter a negative number to stop the interval.
681
+ </div>
682
+ </div>
683
+ </div>
684
+ </file>
685
+ <file name="script.js">
686
+ function CarouselDemoCtrl($scope) {
687
+ $scope.myInterval = 5000;
688
+ var slides = $scope.slides = [];
689
+ $scope.addSlide = function() {
690
+ var newWidth = 200 + ((slides.length + (25 * slides.length)) % 150);
691
+ slides.push({
692
+ image: 'http://placekitten.com/' + newWidth + '/200',
693
+ text: ['More','Extra','Lots of','Surplus'][slides.length % 4] + ' '
694
+ ['Cats', 'Kittys', 'Felines', 'Cutes'][slides.length % 4]
695
+ });
696
+ };
697
+ for (var i=0; i<4; i++) $scope.addSlide();
698
+ }
699
+ </file>
700
+ <file name="demo.css">
701
+ .carousel-indicators {
702
+ top: auto;
703
+ bottom: 15px;
704
+ }
705
+ </file>
706
+ </example>
707
+ */
708
+
709
+ .directive('slide', ['$parse', function($parse) {
710
+ return {
711
+ require: '^carousel',
712
+ restrict: 'EA',
713
+ transclude: true,
714
+ replace: true,
715
+ templateUrl: 'template/carousel/slide.html',
716
+ scope: {
717
+ },
718
+ link: function (scope, element, attrs, carouselCtrl) {
719
+ //Set up optional 'active' = binding
720
+ if (attrs.active) {
721
+ var getActive = $parse(attrs.active);
722
+ var setActive = getActive.assign;
723
+ var lastValue = scope.active = getActive(scope.$parent);
724
+ scope.$watch(function parentActiveWatch() {
725
+ var parentActive = getActive(scope.$parent);
726
+
727
+ if (parentActive !== scope.active) {
728
+ // we are out of sync and need to copy
729
+ if (parentActive !== lastValue) {
730
+ // parent changed and it has precedence
731
+ lastValue = scope.active = parentActive;
732
+ } else {
733
+ // if the parent can be assigned then do so
734
+ setActive(scope.$parent, parentActive = lastValue = scope.active);
735
+ }
736
+ }
737
+ return parentActive;
738
+ });
739
+ }
740
+
741
+ carouselCtrl.addSlide(scope, element);
742
+ //when the scope is destroyed then remove the slide from the current slides array
743
+ scope.$on('$destroy', function() {
744
+ carouselCtrl.removeSlide(scope);
745
+ });
746
+
747
+ scope.$watch('active', function(active) {
748
+ if (active) {
749
+ carouselCtrl.select(scope);
750
+ }
751
+ });
752
+ }
753
+ };
754
+ }]);
755
+
756
+ angular.module('ui.bootstrap.position', [])
757
+
758
+ /**
759
+ * A set of utility methods that can be use to retrieve position of DOM elements.
760
+ * It is meant to be used where we need to absolute-position DOM elements in
761
+ * relation to other, existing elements (this is the case for tooltips, popovers,
762
+ * typeahead suggestions etc.).
763
+ */
764
+ .factory('$position', ['$document', '$window', function ($document, $window) {
765
+
766
+ function getStyle(el, cssprop) {
767
+ if (el.currentStyle) { //IE
768
+ return el.currentStyle[cssprop];
769
+ } else if ($window.getComputedStyle) {
770
+ return $window.getComputedStyle(el)[cssprop];
771
+ }
772
+ // finally try and get inline style
773
+ return el.style[cssprop];
774
+ }
775
+
776
+ /**
777
+ * Checks if a given element is statically positioned
778
+ * @param element - raw DOM element
779
+ */
780
+ function isStaticPositioned(element) {
781
+ return (getStyle(element, "position") || 'static' ) === 'static';
782
+ }
783
+
784
+ /**
785
+ * returns the closest, non-statically positioned parentOffset of a given element
786
+ * @param element
787
+ */
788
+ var parentOffsetEl = function (element) {
789
+ var docDomEl = $document[0];
790
+ var offsetParent = element.offsetParent || docDomEl;
791
+ while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
792
+ offsetParent = offsetParent.offsetParent;
793
+ }
794
+ return offsetParent || docDomEl;
795
+ };
796
+
797
+ return {
798
+ /**
799
+ * Provides read-only equivalent of jQuery's position function:
800
+ * http://api.jquery.com/position/
801
+ */
802
+ position: function (element) {
803
+ var elBCR = this.offset(element);
804
+ var offsetParentBCR = { top: 0, left: 0 };
805
+ var offsetParentEl = parentOffsetEl(element[0]);
806
+ if (offsetParentEl != $document[0]) {
807
+ offsetParentBCR = this.offset(angular.element(offsetParentEl));
808
+ offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
809
+ offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
810
+ }
811
+
812
+ var boundingClientRect = element[0].getBoundingClientRect();
813
+ return {
814
+ width: boundingClientRect.width || element.prop('offsetWidth'),
815
+ height: boundingClientRect.height || element.prop('offsetHeight'),
816
+ top: elBCR.top - offsetParentBCR.top,
817
+ left: elBCR.left - offsetParentBCR.left
818
+ };
819
+ },
820
+
821
+ /**
822
+ * Provides read-only equivalent of jQuery's offset function:
823
+ * http://api.jquery.com/offset/
824
+ */
825
+ offset: function (element) {
826
+ var boundingClientRect = element[0].getBoundingClientRect();
827
+ return {
828
+ width: boundingClientRect.width || element.prop('offsetWidth'),
829
+ height: boundingClientRect.height || element.prop('offsetHeight'),
830
+ top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop),
831
+ left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft)
832
+ };
833
+ }
834
+ };
835
+ }]);
836
+
837
+ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.position'])
838
+
839
+ .constant('datepickerConfig', {
840
+ dayFormat: 'dd',
841
+ monthFormat: 'MMMM',
842
+ yearFormat: 'yyyy',
843
+ dayHeaderFormat: 'EEE',
844
+ dayTitleFormat: 'MMMM yyyy',
845
+ monthTitleFormat: 'yyyy',
846
+ showWeeks: true,
847
+ startingDay: 0,
848
+ yearRange: 20,
849
+ minDate: null,
850
+ maxDate: null
851
+ })
852
+
853
+ .controller('DatepickerController', ['$scope', '$attrs', 'dateFilter', 'datepickerConfig', function($scope, $attrs, dateFilter, dtConfig) {
854
+ var format = {
855
+ day: getValue($attrs.dayFormat, dtConfig.dayFormat),
856
+ month: getValue($attrs.monthFormat, dtConfig.monthFormat),
857
+ year: getValue($attrs.yearFormat, dtConfig.yearFormat),
858
+ dayHeader: getValue($attrs.dayHeaderFormat, dtConfig.dayHeaderFormat),
859
+ dayTitle: getValue($attrs.dayTitleFormat, dtConfig.dayTitleFormat),
860
+ monthTitle: getValue($attrs.monthTitleFormat, dtConfig.monthTitleFormat)
861
+ },
862
+ startingDay = getValue($attrs.startingDay, dtConfig.startingDay),
863
+ yearRange = getValue($attrs.yearRange, dtConfig.yearRange);
864
+
865
+ this.minDate = dtConfig.minDate ? new Date(dtConfig.minDate) : null;
866
+ this.maxDate = dtConfig.maxDate ? new Date(dtConfig.maxDate) : null;
867
+
868
+ function getValue(value, defaultValue) {
869
+ return angular.isDefined(value) ? $scope.$parent.$eval(value) : defaultValue;
870
+ }
871
+
872
+ function getDaysInMonth( year, month ) {
873
+ return new Date(year, month, 0).getDate();
874
+ }
875
+
876
+ function getDates(startDate, n) {
877
+ var dates = new Array(n);
878
+ var current = startDate, i = 0;
879
+ while (i < n) {
880
+ dates[i++] = new Date(current);
881
+ current.setDate( current.getDate() + 1 );
882
+ }
883
+ return dates;
884
+ }
885
+
886
+ function makeDate(date, format, isSelected, isSecondary) {
887
+ return { date: date, label: dateFilter(date, format), selected: !!isSelected, secondary: !!isSecondary };
888
+ }
889
+
890
+ this.modes = [
891
+ {
892
+ name: 'day',
893
+ getVisibleDates: function(date, selected) {
894
+ var year = date.getFullYear(), month = date.getMonth(), firstDayOfMonth = new Date(year, month, 1);
895
+ var difference = startingDay - firstDayOfMonth.getDay(),
896
+ numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference,
897
+ firstDate = new Date(firstDayOfMonth), numDates = 0;
898
+
899
+ if ( numDisplayedFromPreviousMonth > 0 ) {
900
+ firstDate.setDate( - numDisplayedFromPreviousMonth + 1 );
901
+ numDates += numDisplayedFromPreviousMonth; // Previous
902
+ }
903
+ numDates += getDaysInMonth(year, month + 1); // Current
904
+ numDates += (7 - numDates % 7) % 7; // Next
905
+
906
+ var days = getDates(firstDate, numDates), labels = new Array(7);
907
+ for (var i = 0; i < numDates; i ++) {
908
+ var dt = new Date(days[i]);
909
+ days[i] = makeDate(dt, format.day, (selected && selected.getDate() === dt.getDate() && selected.getMonth() === dt.getMonth() && selected.getFullYear() === dt.getFullYear()), dt.getMonth() !== month);
910
+ }
911
+ for (var j = 0; j < 7; j++) {
912
+ labels[j] = dateFilter(days[j].date, format.dayHeader);
913
+ }
914
+ return { objects: days, title: dateFilter(date, format.dayTitle), labels: labels };
915
+ },
916
+ compare: function(date1, date2) {
917
+ return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) );
918
+ },
919
+ split: 7,
920
+ step: { months: 1 }
921
+ },
922
+ {
923
+ name: 'month',
924
+ getVisibleDates: function(date, selected) {
925
+ var months = new Array(12), year = date.getFullYear();
926
+ for ( var i = 0; i < 12; i++ ) {
927
+ var dt = new Date(year, i, 1);
928
+ months[i] = makeDate(dt, format.month, (selected && selected.getMonth() === i && selected.getFullYear() === year));
929
+ }
930
+ return { objects: months, title: dateFilter(date, format.monthTitle) };
931
+ },
932
+ compare: function(date1, date2) {
933
+ return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() );
934
+ },
935
+ split: 3,
936
+ step: { years: 1 }
937
+ },
938
+ {
939
+ name: 'year',
940
+ getVisibleDates: function(date, selected) {
941
+ var years = new Array(yearRange), year = date.getFullYear(), startYear = parseInt((year - 1) / yearRange, 10) * yearRange + 1;
942
+ for ( var i = 0; i < yearRange; i++ ) {
943
+ var dt = new Date(startYear + i, 0, 1);
944
+ years[i] = makeDate(dt, format.year, (selected && selected.getFullYear() === dt.getFullYear()));
945
+ }
946
+ return { objects: years, title: [years[0].label, years[yearRange - 1].label].join(' - ') };
947
+ },
948
+ compare: function(date1, date2) {
949
+ return date1.getFullYear() - date2.getFullYear();
950
+ },
951
+ split: 5,
952
+ step: { years: yearRange }
953
+ }
954
+ ];
955
+
956
+ this.isDisabled = function(date, mode) {
957
+ var currentMode = this.modes[mode || 0];
958
+ return ((this.minDate && currentMode.compare(date, this.minDate) < 0) || (this.maxDate && currentMode.compare(date, this.maxDate) > 0) || ($scope.dateDisabled && $scope.dateDisabled({date: date, mode: currentMode.name})));
959
+ };
960
+ }])
961
+
962
+ .directive( 'datepicker', ['dateFilter', '$parse', 'datepickerConfig', '$log', function (dateFilter, $parse, datepickerConfig, $log) {
963
+ return {
964
+ restrict: 'EA',
965
+ replace: true,
966
+ templateUrl: 'template/datepicker/datepicker.html',
967
+ scope: {
968
+ dateDisabled: '&'
969
+ },
970
+ require: ['datepicker', '?^ngModel'],
971
+ controller: 'DatepickerController',
972
+ link: function(scope, element, attrs, ctrls) {
973
+ var datepickerCtrl = ctrls[0], ngModel = ctrls[1];
974
+
975
+ if (!ngModel) {
976
+ return; // do nothing if no ng-model
977
+ }
978
+
979
+ // Configuration parameters
980
+ var mode = 0, selected = new Date(), showWeeks = datepickerConfig.showWeeks;
981
+
982
+ if (attrs.showWeeks) {
983
+ scope.$parent.$watch($parse(attrs.showWeeks), function(value) {
984
+ showWeeks = !! value;
985
+ updateShowWeekNumbers();
986
+ });
987
+ } else {
988
+ updateShowWeekNumbers();
989
+ }
990
+
991
+ if (attrs.min) {
992
+ scope.$parent.$watch($parse(attrs.min), function(value) {
993
+ datepickerCtrl.minDate = value ? new Date(value) : null;
994
+ refill();
995
+ });
996
+ }
997
+ if (attrs.max) {
998
+ scope.$parent.$watch($parse(attrs.max), function(value) {
999
+ datepickerCtrl.maxDate = value ? new Date(value) : null;
1000
+ refill();
1001
+ });
1002
+ }
1003
+
1004
+ function updateShowWeekNumbers() {
1005
+ scope.showWeekNumbers = mode === 0 && showWeeks;
1006
+ }
1007
+
1008
+ // Split array into smaller arrays
1009
+ function split(arr, size) {
1010
+ var arrays = [];
1011
+ while (arr.length > 0) {
1012
+ arrays.push(arr.splice(0, size));
1013
+ }
1014
+ return arrays;
1015
+ }
1016
+
1017
+ function refill( updateSelected ) {
1018
+ var date = null, valid = true;
1019
+
1020
+ if ( ngModel.$modelValue ) {
1021
+ date = new Date( ngModel.$modelValue );
1022
+
1023
+ if ( isNaN(date) ) {
1024
+ valid = false;
1025
+ $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
1026
+ } else if ( updateSelected ) {
1027
+ selected = date;
1028
+ }
1029
+ }
1030
+ ngModel.$setValidity('date', valid);
1031
+
1032
+ var currentMode = datepickerCtrl.modes[mode], data = currentMode.getVisibleDates(selected, date);
1033
+ angular.forEach(data.objects, function(obj) {
1034
+ obj.disabled = datepickerCtrl.isDisabled(obj.date, mode);
1035
+ });
1036
+
1037
+ ngModel.$setValidity('date-disabled', (!date || !datepickerCtrl.isDisabled(date)));
1038
+
1039
+ scope.rows = split(data.objects, currentMode.split);
1040
+ scope.labels = data.labels || [];
1041
+ scope.title = data.title;
1042
+ }
1043
+
1044
+ function setMode(value) {
1045
+ mode = value;
1046
+ updateShowWeekNumbers();
1047
+ refill();
1048
+ }
1049
+
1050
+ ngModel.$render = function() {
1051
+ refill( true );
1052
+ };
1053
+
1054
+ scope.select = function( date ) {
1055
+ if ( mode === 0 ) {
1056
+ var dt = ngModel.$modelValue ? new Date( ngModel.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0);
1057
+ dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() );
1058
+ ngModel.$setViewValue( dt );
1059
+ refill( true );
1060
+ } else {
1061
+ selected = date;
1062
+ setMode( mode - 1 );
1063
+ }
1064
+ };
1065
+ scope.move = function(direction) {
1066
+ var step = datepickerCtrl.modes[mode].step;
1067
+ selected.setMonth( selected.getMonth() + direction * (step.months || 0) );
1068
+ selected.setFullYear( selected.getFullYear() + direction * (step.years || 0) );
1069
+ refill();
1070
+ };
1071
+ scope.toggleMode = function() {
1072
+ setMode( (mode + 1) % datepickerCtrl.modes.length );
1073
+ };
1074
+ scope.getWeekNumber = function(row) {
1075
+ return ( mode === 0 && scope.showWeekNumbers && row.length === 7 ) ? getISO8601WeekNumber(row[0].date) : null;
1076
+ };
1077
+
1078
+ function getISO8601WeekNumber(date) {
1079
+ var checkDate = new Date(date);
1080
+ checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
1081
+ var time = checkDate.getTime();
1082
+ checkDate.setMonth(0); // Compare with Jan 1
1083
+ checkDate.setDate(1);
1084
+ return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
1085
+ }
1086
+ }
1087
+ };
1088
+ }])
1089
+
1090
+ .constant('datepickerPopupConfig', {
1091
+ dateFormat: 'yyyy-MM-dd',
1092
+ currentText: 'Today',
1093
+ toggleWeeksText: 'Weeks',
1094
+ clearText: 'Clear',
1095
+ closeText: 'Done',
1096
+ closeOnDateSelection: true,
1097
+ appendToBody: false,
1098
+ showButtonBar: true
1099
+ })
1100
+
1101
+ .directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'datepickerPopupConfig', 'datepickerConfig',
1102
+ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupConfig, datepickerConfig) {
1103
+ return {
1104
+ restrict: 'EA',
1105
+ require: 'ngModel',
1106
+ link: function(originalScope, element, attrs, ngModel) {
1107
+ var scope = originalScope.$new(), // create a child scope so we are not polluting original one
1108
+ dateFormat,
1109
+ closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? originalScope.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection,
1110
+ appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? originalScope.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody;
1111
+
1112
+ attrs.$observe('datepickerPopup', function(value) {
1113
+ dateFormat = value || datepickerPopupConfig.dateFormat;
1114
+ ngModel.$render();
1115
+ });
1116
+
1117
+ scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? originalScope.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar;
1118
+
1119
+ originalScope.$on('$destroy', function() {
1120
+ $popup.remove();
1121
+ scope.$destroy();
1122
+ });
1123
+
1124
+ attrs.$observe('currentText', function(text) {
1125
+ scope.currentText = angular.isDefined(text) ? text : datepickerPopupConfig.currentText;
1126
+ });
1127
+ attrs.$observe('toggleWeeksText', function(text) {
1128
+ scope.toggleWeeksText = angular.isDefined(text) ? text : datepickerPopupConfig.toggleWeeksText;
1129
+ });
1130
+ attrs.$observe('clearText', function(text) {
1131
+ scope.clearText = angular.isDefined(text) ? text : datepickerPopupConfig.clearText;
1132
+ });
1133
+ attrs.$observe('closeText', function(text) {
1134
+ scope.closeText = angular.isDefined(text) ? text : datepickerPopupConfig.closeText;
1135
+ });
1136
+
1137
+ var getIsOpen, setIsOpen;
1138
+ if ( attrs.isOpen ) {
1139
+ getIsOpen = $parse(attrs.isOpen);
1140
+ setIsOpen = getIsOpen.assign;
1141
+
1142
+ originalScope.$watch(getIsOpen, function updateOpen(value) {
1143
+ scope.isOpen = !! value;
1144
+ });
1145
+ }
1146
+ scope.isOpen = getIsOpen ? getIsOpen(originalScope) : false; // Initial state
1147
+
1148
+ function setOpen( value ) {
1149
+ if (setIsOpen) {
1150
+ setIsOpen(originalScope, !!value);
1151
+ } else {
1152
+ scope.isOpen = !!value;
1153
+ }
1154
+ }
1155
+
1156
+ var documentClickBind = function(event) {
1157
+ if (scope.isOpen && event.target !== element[0]) {
1158
+ scope.$apply(function() {
1159
+ setOpen(false);
1160
+ });
1161
+ }
1162
+ };
1163
+
1164
+ var elementFocusBind = function() {
1165
+ scope.$apply(function() {
1166
+ setOpen( true );
1167
+ });
1168
+ };
1169
+
1170
+ // popup element used to display calendar
1171
+ var popupEl = angular.element('<div datepicker-popup-wrap><div datepicker></div></div>');
1172
+ popupEl.attr({
1173
+ 'ng-model': 'date',
1174
+ 'ng-change': 'dateSelection()'
1175
+ });
1176
+ var datepickerEl = angular.element(popupEl.children()[0]),
1177
+ datepickerOptions = {};
1178
+ if (attrs.datepickerOptions) {
1179
+ datepickerOptions = originalScope.$eval(attrs.datepickerOptions);
1180
+ datepickerEl.attr(angular.extend({}, datepickerOptions));
1181
+ }
1182
+
1183
+ // TODO: reverse from dateFilter string to Date object
1184
+ function parseDate(viewValue) {
1185
+ if (!viewValue) {
1186
+ ngModel.$setValidity('date', true);
1187
+ return null;
1188
+ } else if (angular.isDate(viewValue)) {
1189
+ ngModel.$setValidity('date', true);
1190
+ return viewValue;
1191
+ } else if (angular.isString(viewValue)) {
1192
+ var date = new Date(viewValue);
1193
+ if (isNaN(date)) {
1194
+ ngModel.$setValidity('date', false);
1195
+ return undefined;
1196
+ } else {
1197
+ ngModel.$setValidity('date', true);
1198
+ return date;
1199
+ }
1200
+ } else {
1201
+ ngModel.$setValidity('date', false);
1202
+ return undefined;
1203
+ }
1204
+ }
1205
+ ngModel.$parsers.unshift(parseDate);
1206
+
1207
+ // Inner change
1208
+ scope.dateSelection = function(dt) {
1209
+ if (angular.isDefined(dt)) {
1210
+ scope.date = dt;
1211
+ }
1212
+ ngModel.$setViewValue(scope.date);
1213
+ ngModel.$render();
1214
+
1215
+ if (closeOnDateSelection) {
1216
+ setOpen( false );
1217
+ }
1218
+ };
1219
+
1220
+ element.bind('input change keyup', function() {
1221
+ scope.$apply(function() {
1222
+ scope.date = ngModel.$modelValue;
1223
+ });
1224
+ });
1225
+
1226
+ // Outter change
1227
+ ngModel.$render = function() {
1228
+ var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : '';
1229
+ element.val(date);
1230
+ scope.date = ngModel.$modelValue;
1231
+ };
1232
+
1233
+ function addWatchableAttribute(attribute, scopeProperty, datepickerAttribute) {
1234
+ if (attribute) {
1235
+ originalScope.$watch($parse(attribute), function(value){
1236
+ scope[scopeProperty] = value;
1237
+ });
1238
+ datepickerEl.attr(datepickerAttribute || scopeProperty, scopeProperty);
1239
+ }
1240
+ }
1241
+ addWatchableAttribute(attrs.min, 'min');
1242
+ addWatchableAttribute(attrs.max, 'max');
1243
+ if (attrs.showWeeks) {
1244
+ addWatchableAttribute(attrs.showWeeks, 'showWeeks', 'show-weeks');
1245
+ } else {
1246
+ scope.showWeeks = 'show-weeks' in datepickerOptions ? datepickerOptions['show-weeks'] : datepickerConfig.showWeeks;
1247
+ datepickerEl.attr('show-weeks', 'showWeeks');
1248
+ }
1249
+ if (attrs.dateDisabled) {
1250
+ datepickerEl.attr('date-disabled', attrs.dateDisabled);
1251
+ }
1252
+
1253
+ function updatePosition() {
1254
+ scope.position = appendToBody ? $position.offset(element) : $position.position(element);
1255
+ scope.position.top = scope.position.top + element.prop('offsetHeight');
1256
+ }
1257
+
1258
+ var documentBindingInitialized = false, elementFocusInitialized = false;
1259
+ scope.$watch('isOpen', function(value) {
1260
+ if (value) {
1261
+ updatePosition();
1262
+ $document.bind('click', documentClickBind);
1263
+ if(elementFocusInitialized) {
1264
+ element.unbind('focus', elementFocusBind);
1265
+ }
1266
+ element[0].focus();
1267
+ documentBindingInitialized = true;
1268
+ } else {
1269
+ if(documentBindingInitialized) {
1270
+ $document.unbind('click', documentClickBind);
1271
+ }
1272
+ element.bind('focus', elementFocusBind);
1273
+ elementFocusInitialized = true;
1274
+ }
1275
+
1276
+ if ( setIsOpen ) {
1277
+ setIsOpen(originalScope, value);
1278
+ }
1279
+ });
1280
+
1281
+ scope.today = function() {
1282
+ scope.dateSelection(new Date());
1283
+ };
1284
+ scope.clear = function() {
1285
+ scope.dateSelection(null);
1286
+ };
1287
+
1288
+ var $popup = $compile(popupEl)(scope);
1289
+ if ( appendToBody ) {
1290
+ $document.find('body').append($popup);
1291
+ } else {
1292
+ element.after($popup);
1293
+ }
1294
+ }
1295
+ };
1296
+ }])
1297
+
1298
+ .directive('datepickerPopupWrap', function() {
1299
+ return {
1300
+ restrict:'EA',
1301
+ replace: true,
1302
+ transclude: true,
1303
+ templateUrl: 'template/datepicker/popup.html',
1304
+ link:function (scope, element, attrs) {
1305
+ element.bind('click', function(event) {
1306
+ event.preventDefault();
1307
+ event.stopPropagation();
1308
+ });
1309
+ }
1310
+ };
1311
+ });
1312
+
1313
+ /*
1314
+ * dropdownToggle - Provides dropdown menu functionality in place of bootstrap js
1315
+ * @restrict class or attribute
1316
+ * @example:
1317
+ <li class="dropdown">
1318
+ <a class="dropdown-toggle">My Dropdown Menu</a>
1319
+ <ul class="dropdown-menu">
1320
+ <li ng-repeat="choice in dropChoices">
1321
+ <a ng-href="{{choice.href}}">{{choice.text}}</a>
1322
+ </li>
1323
+ </ul>
1324
+ </li>
1325
+ */
1326
+
1327
+ angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', ['$document', '$location', function ($document, $location) {
1328
+ var openElement = null,
1329
+ closeMenu = angular.noop;
1330
+ return {
1331
+ restrict: 'CA',
1332
+ link: function(scope, element, attrs) {
1333
+ scope.$watch('$location.path', function() { closeMenu(); });
1334
+ element.parent().bind('click', function() { closeMenu(); });
1335
+ element.bind('click', function (event) {
1336
+
1337
+ var elementWasOpen = (element === openElement);
1338
+
1339
+ event.preventDefault();
1340
+ event.stopPropagation();
1341
+
1342
+ if (!!openElement) {
1343
+ closeMenu();
1344
+ }
1345
+
1346
+ if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) {
1347
+ element.parent().addClass('open');
1348
+ openElement = element;
1349
+ closeMenu = function (event) {
1350
+ if (event) {
1351
+ event.preventDefault();
1352
+ event.stopPropagation();
1353
+ }
1354
+ $document.unbind('click', closeMenu);
1355
+ element.parent().removeClass('open');
1356
+ closeMenu = angular.noop;
1357
+ openElement = null;
1358
+ };
1359
+ $document.bind('click', closeMenu);
1360
+ }
1361
+ });
1362
+ }
1363
+ };
1364
+ }]);
1365
+
1366
+ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
1367
+
1368
+ /**
1369
+ * A helper, internal data structure that acts as a map but also allows getting / removing
1370
+ * elements in the LIFO order
1371
+ */
1372
+ .factory('$$stackedMap', function () {
1373
+ return {
1374
+ createNew: function () {
1375
+ var stack = [];
1376
+
1377
+ return {
1378
+ add: function (key, value) {
1379
+ stack.push({
1380
+ key: key,
1381
+ value: value
1382
+ });
1383
+ },
1384
+ get: function (key) {
1385
+ for (var i = 0; i < stack.length; i++) {
1386
+ if (key == stack[i].key) {
1387
+ return stack[i];
1388
+ }
1389
+ }
1390
+ },
1391
+ keys: function() {
1392
+ var keys = [];
1393
+ for (var i = 0; i < stack.length; i++) {
1394
+ keys.push(stack[i].key);
1395
+ }
1396
+ return keys;
1397
+ },
1398
+ top: function () {
1399
+ return stack[stack.length - 1];
1400
+ },
1401
+ remove: function (key) {
1402
+ var idx = -1;
1403
+ for (var i = 0; i < stack.length; i++) {
1404
+ if (key == stack[i].key) {
1405
+ idx = i;
1406
+ break;
1407
+ }
1408
+ }
1409
+ return stack.splice(idx, 1)[0];
1410
+ },
1411
+ removeTop: function () {
1412
+ return stack.splice(stack.length - 1, 1)[0];
1413
+ },
1414
+ length: function () {
1415
+ return stack.length;
1416
+ }
1417
+ };
1418
+ }
1419
+ };
1420
+ })
1421
+
1422
+ /**
1423
+ * A helper directive for the $modal service. It creates a backdrop element.
1424
+ */
1425
+ .directive('modalBackdrop', ['$timeout', function ($timeout) {
1426
+ return {
1427
+ restrict: 'EA',
1428
+ replace: true,
1429
+ templateUrl: 'template/modal/backdrop.html',
1430
+ link: function (scope) {
1431
+
1432
+ scope.animate = false;
1433
+
1434
+ //trigger CSS transitions
1435
+ $timeout(function () {
1436
+ scope.animate = true;
1437
+ });
1438
+ }
1439
+ };
1440
+ }])
1441
+
1442
+ .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
1443
+ return {
1444
+ restrict: 'EA',
1445
+ scope: {
1446
+ index: '@',
1447
+ animate: '='
1448
+ },
1449
+ replace: true,
1450
+ transclude: true,
1451
+ templateUrl: 'template/modal/window.html',
1452
+ link: function (scope, element, attrs) {
1453
+ scope.windowClass = attrs.windowClass || '';
1454
+
1455
+ $timeout(function () {
1456
+ // trigger CSS transitions
1457
+ scope.animate = true;
1458
+ // focus a freshly-opened modal
1459
+ element[0].focus();
1460
+ });
1461
+
1462
+ scope.close = function (evt) {
1463
+ var modal = $modalStack.getTop();
1464
+ if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) {
1465
+ evt.preventDefault();
1466
+ evt.stopPropagation();
1467
+ $modalStack.dismiss(modal.key, 'backdrop click');
1468
+ }
1469
+ };
1470
+ }
1471
+ };
1472
+ }])
1473
+
1474
+ .factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap',
1475
+ function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) {
1476
+
1477
+ var OPENED_MODAL_CLASS = 'modal-open';
1478
+
1479
+ var backdropDomEl, backdropScope;
1480
+ var openedWindows = $$stackedMap.createNew();
1481
+ var $modalStack = {};
1482
+
1483
+ function backdropIndex() {
1484
+ var topBackdropIndex = -1;
1485
+ var opened = openedWindows.keys();
1486
+ for (var i = 0; i < opened.length; i++) {
1487
+ if (openedWindows.get(opened[i]).value.backdrop) {
1488
+ topBackdropIndex = i;
1489
+ }
1490
+ }
1491
+ return topBackdropIndex;
1492
+ }
1493
+
1494
+ $rootScope.$watch(backdropIndex, function(newBackdropIndex){
1495
+ if (backdropScope) {
1496
+ backdropScope.index = newBackdropIndex;
1497
+ }
1498
+ });
1499
+
1500
+ function removeModalWindow(modalInstance) {
1501
+
1502
+ var body = $document.find('body').eq(0);
1503
+ var modalWindow = openedWindows.get(modalInstance).value;
1504
+
1505
+ //clean up the stack
1506
+ openedWindows.remove(modalInstance);
1507
+
1508
+ //remove window DOM element
1509
+ removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, checkRemoveBackdrop);
1510
+ body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0);
1511
+ }
1512
+
1513
+ function checkRemoveBackdrop() {
1514
+ //remove backdrop if no longer needed
1515
+ if (backdropDomEl && backdropIndex() == -1) {
1516
+ var backdropScopeRef = backdropScope;
1517
+ removeAfterAnimate(backdropDomEl, backdropScope, 150, function () {
1518
+ backdropScopeRef.$destroy();
1519
+ backdropScopeRef = null;
1520
+ });
1521
+ backdropDomEl = undefined;
1522
+ backdropScope = undefined;
1523
+ }
1524
+ }
1525
+
1526
+ function removeAfterAnimate(domEl, scope, emulateTime, done) {
1527
+ // Closing animation
1528
+ scope.animate = false;
1529
+
1530
+ var transitionEndEventName = $transition.transitionEndEventName;
1531
+ if (transitionEndEventName) {
1532
+ // transition out
1533
+ var timeout = $timeout(afterAnimating, emulateTime);
1534
+
1535
+ domEl.bind(transitionEndEventName, function () {
1536
+ $timeout.cancel(timeout);
1537
+ afterAnimating();
1538
+ scope.$apply();
1539
+ });
1540
+ } else {
1541
+ // Ensure this call is async
1542
+ $timeout(afterAnimating, 0);
1543
+ }
1544
+
1545
+ function afterAnimating() {
1546
+ if (afterAnimating.done) {
1547
+ return;
1548
+ }
1549
+ afterAnimating.done = true;
1550
+
1551
+ domEl.remove();
1552
+ if (done) {
1553
+ done();
1554
+ }
1555
+ }
1556
+ }
1557
+
1558
+ $document.bind('keydown', function (evt) {
1559
+ var modal;
1560
+
1561
+ if (evt.which === 27) {
1562
+ modal = openedWindows.top();
1563
+ if (modal && modal.value.keyboard) {
1564
+ $rootScope.$apply(function () {
1565
+ $modalStack.dismiss(modal.key);
1566
+ });
1567
+ }
1568
+ }
1569
+ });
1570
+
1571
+ $modalStack.open = function (modalInstance, modal) {
1572
+
1573
+ openedWindows.add(modalInstance, {
1574
+ deferred: modal.deferred,
1575
+ modalScope: modal.scope,
1576
+ backdrop: modal.backdrop,
1577
+ keyboard: modal.keyboard
1578
+ });
1579
+
1580
+ var body = $document.find('body').eq(0),
1581
+ currBackdropIndex = backdropIndex();
1582
+
1583
+ if (currBackdropIndex >= 0 && !backdropDomEl) {
1584
+ backdropScope = $rootScope.$new(true);
1585
+ backdropScope.index = currBackdropIndex;
1586
+ backdropDomEl = $compile('<div modal-backdrop></div>')(backdropScope);
1587
+ body.append(backdropDomEl);
1588
+ }
1589
+
1590
+ var angularDomEl = angular.element('<div modal-window></div>');
1591
+ angularDomEl.attr('window-class', modal.windowClass);
1592
+ angularDomEl.attr('index', openedWindows.length() - 1);
1593
+ angularDomEl.attr('animate', 'animate');
1594
+ angularDomEl.html(modal.content);
1595
+
1596
+ var modalDomEl = $compile(angularDomEl)(modal.scope);
1597
+ openedWindows.top().value.modalDomEl = modalDomEl;
1598
+ body.append(modalDomEl);
1599
+ body.addClass(OPENED_MODAL_CLASS);
1600
+ };
1601
+
1602
+ $modalStack.close = function (modalInstance, result) {
1603
+ var modalWindow = openedWindows.get(modalInstance).value;
1604
+ if (modalWindow) {
1605
+ modalWindow.deferred.resolve(result);
1606
+ removeModalWindow(modalInstance);
1607
+ }
1608
+ };
1609
+
1610
+ $modalStack.dismiss = function (modalInstance, reason) {
1611
+ var modalWindow = openedWindows.get(modalInstance).value;
1612
+ if (modalWindow) {
1613
+ modalWindow.deferred.reject(reason);
1614
+ removeModalWindow(modalInstance);
1615
+ }
1616
+ };
1617
+
1618
+ $modalStack.dismissAll = function (reason) {
1619
+ var topModal = this.getTop();
1620
+ while (topModal) {
1621
+ this.dismiss(topModal.key, reason);
1622
+ topModal = this.getTop();
1623
+ }
1624
+ };
1625
+
1626
+ $modalStack.getTop = function () {
1627
+ return openedWindows.top();
1628
+ };
1629
+
1630
+ return $modalStack;
1631
+ }])
1632
+
1633
+ .provider('$modal', function () {
1634
+
1635
+ var $modalProvider = {
1636
+ options: {
1637
+ backdrop: true, //can be also false or 'static'
1638
+ keyboard: true
1639
+ },
1640
+ $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack',
1641
+ function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) {
1642
+
1643
+ var $modal = {};
1644
+
1645
+ function getTemplatePromise(options) {
1646
+ return options.template ? $q.when(options.template) :
1647
+ $http.get(options.templateUrl, {cache: $templateCache}).then(function (result) {
1648
+ return result.data;
1649
+ });
1650
+ }
1651
+
1652
+ function getResolvePromises(resolves) {
1653
+ var promisesArr = [];
1654
+ angular.forEach(resolves, function (value, key) {
1655
+ if (angular.isFunction(value) || angular.isArray(value)) {
1656
+ promisesArr.push($q.when($injector.invoke(value)));
1657
+ }
1658
+ });
1659
+ return promisesArr;
1660
+ }
1661
+
1662
+ $modal.open = function (modalOptions) {
1663
+
1664
+ var modalResultDeferred = $q.defer();
1665
+ var modalOpenedDeferred = $q.defer();
1666
+
1667
+ //prepare an instance of a modal to be injected into controllers and returned to a caller
1668
+ var modalInstance = {
1669
+ result: modalResultDeferred.promise,
1670
+ opened: modalOpenedDeferred.promise,
1671
+ close: function (result) {
1672
+ $modalStack.close(modalInstance, result);
1673
+ },
1674
+ dismiss: function (reason) {
1675
+ $modalStack.dismiss(modalInstance, reason);
1676
+ }
1677
+ };
1678
+
1679
+ //merge and clean up options
1680
+ modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
1681
+ modalOptions.resolve = modalOptions.resolve || {};
1682
+
1683
+ //verify options
1684
+ if (!modalOptions.template && !modalOptions.templateUrl) {
1685
+ throw new Error('One of template or templateUrl options is required.');
1686
+ }
1687
+
1688
+ var templateAndResolvePromise =
1689
+ $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));
1690
+
1691
+
1692
+ templateAndResolvePromise.then(function resolveSuccess(tplAndVars) {
1693
+
1694
+ var modalScope = (modalOptions.scope || $rootScope).$new();
1695
+ modalScope.$close = modalInstance.close;
1696
+ modalScope.$dismiss = modalInstance.dismiss;
1697
+
1698
+ var ctrlInstance, ctrlLocals = {};
1699
+ var resolveIter = 1;
1700
+
1701
+ //controllers
1702
+ if (modalOptions.controller) {
1703
+ ctrlLocals.$scope = modalScope;
1704
+ ctrlLocals.$modalInstance = modalInstance;
1705
+ angular.forEach(modalOptions.resolve, function (value, key) {
1706
+ ctrlLocals[key] = tplAndVars[resolveIter++];
1707
+ });
1708
+
1709
+ ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
1710
+ }
1711
+
1712
+ $modalStack.open(modalInstance, {
1713
+ scope: modalScope,
1714
+ deferred: modalResultDeferred,
1715
+ content: tplAndVars[0],
1716
+ backdrop: modalOptions.backdrop,
1717
+ keyboard: modalOptions.keyboard,
1718
+ windowClass: modalOptions.windowClass
1719
+ });
1720
+
1721
+ }, function resolveError(reason) {
1722
+ modalResultDeferred.reject(reason);
1723
+ });
1724
+
1725
+ templateAndResolvePromise.then(function () {
1726
+ modalOpenedDeferred.resolve(true);
1727
+ }, function () {
1728
+ modalOpenedDeferred.reject(false);
1729
+ });
1730
+
1731
+ return modalInstance;
1732
+ };
1733
+
1734
+ return $modal;
1735
+ }]
1736
+ };
1737
+
1738
+ return $modalProvider;
1739
+ });
1740
+
1741
+ angular.module('ui.bootstrap.pagination', [])
1742
+
1743
+ .controller('PaginationController', ['$scope', '$attrs', '$parse', '$interpolate', function ($scope, $attrs, $parse, $interpolate) {
1744
+ var self = this,
1745
+ setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
1746
+
1747
+ this.init = function(defaultItemsPerPage) {
1748
+ if ($attrs.itemsPerPage) {
1749
+ $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) {
1750
+ self.itemsPerPage = parseInt(value, 10);
1751
+ $scope.totalPages = self.calculateTotalPages();
1752
+ });
1753
+ } else {
1754
+ this.itemsPerPage = defaultItemsPerPage;
1755
+ }
1756
+ };
1757
+
1758
+ this.noPrevious = function() {
1759
+ return this.page === 1;
1760
+ };
1761
+ this.noNext = function() {
1762
+ return this.page === $scope.totalPages;
1763
+ };
1764
+
1765
+ this.isActive = function(page) {
1766
+ return this.page === page;
1767
+ };
1768
+
1769
+ this.calculateTotalPages = function() {
1770
+ var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage);
1771
+ return Math.max(totalPages || 0, 1);
1772
+ };
1773
+
1774
+ this.getAttributeValue = function(attribute, defaultValue, interpolate) {
1775
+ return angular.isDefined(attribute) ? (interpolate ? $interpolate(attribute)($scope.$parent) : $scope.$parent.$eval(attribute)) : defaultValue;
1776
+ };
1777
+
1778
+ this.render = function() {
1779
+ this.page = parseInt($scope.page, 10) || 1;
1780
+ if (this.page > 0 && this.page <= $scope.totalPages) {
1781
+ $scope.pages = this.getPages(this.page, $scope.totalPages);
1782
+ }
1783
+ };
1784
+
1785
+ $scope.selectPage = function(page) {
1786
+ if ( ! self.isActive(page) && page > 0 && page <= $scope.totalPages) {
1787
+ $scope.page = page;
1788
+ $scope.onSelectPage({ page: page });
1789
+ }
1790
+ };
1791
+
1792
+ $scope.$watch('page', function() {
1793
+ self.render();
1794
+ });
1795
+
1796
+ $scope.$watch('totalItems', function() {
1797
+ $scope.totalPages = self.calculateTotalPages();
1798
+ });
1799
+
1800
+ $scope.$watch('totalPages', function(value) {
1801
+ setNumPages($scope.$parent, value); // Readonly variable
1802
+
1803
+ if ( self.page > value ) {
1804
+ $scope.selectPage(value);
1805
+ } else {
1806
+ self.render();
1807
+ }
1808
+ });
1809
+ }])
1810
+
1811
+ .constant('paginationConfig', {
1812
+ itemsPerPage: 10,
1813
+ boundaryLinks: false,
1814
+ directionLinks: true,
1815
+ firstText: 'First',
1816
+ previousText: 'Previous',
1817
+ nextText: 'Next',
1818
+ lastText: 'Last',
1819
+ rotate: true
1820
+ })
1821
+
1822
+ .directive('pagination', ['$parse', 'paginationConfig', function($parse, config) {
1823
+ return {
1824
+ restrict: 'EA',
1825
+ scope: {
1826
+ page: '=',
1827
+ totalItems: '=',
1828
+ onSelectPage:' &'
1829
+ },
1830
+ controller: 'PaginationController',
1831
+ templateUrl: 'template/pagination/pagination.html',
1832
+ replace: true,
1833
+ link: function(scope, element, attrs, paginationCtrl) {
1834
+
1835
+ // Setup configuration parameters
1836
+ var maxSize,
1837
+ boundaryLinks = paginationCtrl.getAttributeValue(attrs.boundaryLinks, config.boundaryLinks ),
1838
+ directionLinks = paginationCtrl.getAttributeValue(attrs.directionLinks, config.directionLinks ),
1839
+ firstText = paginationCtrl.getAttributeValue(attrs.firstText, config.firstText, true),
1840
+ previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
1841
+ nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
1842
+ lastText = paginationCtrl.getAttributeValue(attrs.lastText, config.lastText, true),
1843
+ rotate = paginationCtrl.getAttributeValue(attrs.rotate, config.rotate);
1844
+
1845
+ paginationCtrl.init(config.itemsPerPage);
1846
+
1847
+ if (attrs.maxSize) {
1848
+ scope.$parent.$watch($parse(attrs.maxSize), function(value) {
1849
+ maxSize = parseInt(value, 10);
1850
+ paginationCtrl.render();
1851
+ });
1852
+ }
1853
+
1854
+ // Create page object used in template
1855
+ function makePage(number, text, isActive, isDisabled) {
1856
+ return {
1857
+ number: number,
1858
+ text: text,
1859
+ active: isActive,
1860
+ disabled: isDisabled
1861
+ };
1862
+ }
1863
+
1864
+ paginationCtrl.getPages = function(currentPage, totalPages) {
1865
+ var pages = [];
1866
+
1867
+ // Default page limits
1868
+ var startPage = 1, endPage = totalPages;
1869
+ var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages );
1870
+
1871
+ // recompute if maxSize
1872
+ if ( isMaxSized ) {
1873
+ if ( rotate ) {
1874
+ // Current page is displayed in the middle of the visible ones
1875
+ startPage = Math.max(currentPage - Math.floor(maxSize/2), 1);
1876
+ endPage = startPage + maxSize - 1;
1877
+
1878
+ // Adjust if limit is exceeded
1879
+ if (endPage > totalPages) {
1880
+ endPage = totalPages;
1881
+ startPage = endPage - maxSize + 1;
1882
+ }
1883
+ } else {
1884
+ // Visible pages are paginated with maxSize
1885
+ startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1;
1886
+
1887
+ // Adjust last page if limit is exceeded
1888
+ endPage = Math.min(startPage + maxSize - 1, totalPages);
1889
+ }
1890
+ }
1891
+
1892
+ // Add page number links
1893
+ for (var number = startPage; number <= endPage; number++) {
1894
+ var page = makePage(number, number, paginationCtrl.isActive(number), false);
1895
+ pages.push(page);
1896
+ }
1897
+
1898
+ // Add links to move between page sets
1899
+ if ( isMaxSized && ! rotate ) {
1900
+ if ( startPage > 1 ) {
1901
+ var previousPageSet = makePage(startPage - 1, '...', false, false);
1902
+ pages.unshift(previousPageSet);
1903
+ }
1904
+
1905
+ if ( endPage < totalPages ) {
1906
+ var nextPageSet = makePage(endPage + 1, '...', false, false);
1907
+ pages.push(nextPageSet);
1908
+ }
1909
+ }
1910
+
1911
+ // Add previous & next links
1912
+ if (directionLinks) {
1913
+ var previousPage = makePage(currentPage - 1, previousText, false, paginationCtrl.noPrevious());
1914
+ pages.unshift(previousPage);
1915
+
1916
+ var nextPage = makePage(currentPage + 1, nextText, false, paginationCtrl.noNext());
1917
+ pages.push(nextPage);
1918
+ }
1919
+
1920
+ // Add first & last links
1921
+ if (boundaryLinks) {
1922
+ var firstPage = makePage(1, firstText, false, paginationCtrl.noPrevious());
1923
+ pages.unshift(firstPage);
1924
+
1925
+ var lastPage = makePage(totalPages, lastText, false, paginationCtrl.noNext());
1926
+ pages.push(lastPage);
1927
+ }
1928
+
1929
+ return pages;
1930
+ };
1931
+ }
1932
+ };
1933
+ }])
1934
+
1935
+ .constant('pagerConfig', {
1936
+ itemsPerPage: 10,
1937
+ previousText: '« Previous',
1938
+ nextText: 'Next »',
1939
+ align: true
1940
+ })
1941
+
1942
+ .directive('pager', ['pagerConfig', function(config) {
1943
+ return {
1944
+ restrict: 'EA',
1945
+ scope: {
1946
+ page: '=',
1947
+ totalItems: '=',
1948
+ onSelectPage:' &'
1949
+ },
1950
+ controller: 'PaginationController',
1951
+ templateUrl: 'template/pagination/pager.html',
1952
+ replace: true,
1953
+ link: function(scope, element, attrs, paginationCtrl) {
1954
+
1955
+ // Setup configuration parameters
1956
+ var previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
1957
+ nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
1958
+ align = paginationCtrl.getAttributeValue(attrs.align, config.align);
1959
+
1960
+ paginationCtrl.init(config.itemsPerPage);
1961
+
1962
+ // Create page object used in template
1963
+ function makePage(number, text, isDisabled, isPrevious, isNext) {
1964
+ return {
1965
+ number: number,
1966
+ text: text,
1967
+ disabled: isDisabled,
1968
+ previous: ( align && isPrevious ),
1969
+ next: ( align && isNext )
1970
+ };
1971
+ }
1972
+
1973
+ paginationCtrl.getPages = function(currentPage) {
1974
+ return [
1975
+ makePage(currentPage - 1, previousText, paginationCtrl.noPrevious(), true, false),
1976
+ makePage(currentPage + 1, nextText, paginationCtrl.noNext(), false, true)
1977
+ ];
1978
+ };
1979
+ }
1980
+ };
1981
+ }]);
1982
+
1983
+ /**
1984
+ * The following features are still outstanding: animation as a
1985
+ * function, placement as a function, inside, support for more triggers than
1986
+ * just mouse enter/leave, html tooltips, and selector delegation.
1987
+ */
1988
+ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] )
1989
+
1990
+ /**
1991
+ * The $tooltip service creates tooltip- and popover-like directives as well as
1992
+ * houses global options for them.
1993
+ */
1994
+ .provider( '$tooltip', function () {
1995
+ // The default options tooltip and popover.
1996
+ var defaultOptions = {
1997
+ placement: 'top',
1998
+ animation: true,
1999
+ popupDelay: 0
2000
+ };
2001
+
2002
+ // Default hide triggers for each show trigger
2003
+ var triggerMap = {
2004
+ 'mouseenter': 'mouseleave',
2005
+ 'click': 'click',
2006
+ 'focus': 'blur'
2007
+ };
2008
+
2009
+ // The options specified to the provider globally.
2010
+ var globalOptions = {};
2011
+
2012
+ /**
2013
+ * `options({})` allows global configuration of all tooltips in the
2014
+ * application.
2015
+ *
2016
+ * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
2017
+ * // place tooltips left instead of top by default
2018
+ * $tooltipProvider.options( { placement: 'left' } );
2019
+ * });
2020
+ */
2021
+ this.options = function( value ) {
2022
+ angular.extend( globalOptions, value );
2023
+ };
2024
+
2025
+ /**
2026
+ * This allows you to extend the set of trigger mappings available. E.g.:
2027
+ *
2028
+ * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
2029
+ */
2030
+ this.setTriggers = function setTriggers ( triggers ) {
2031
+ angular.extend( triggerMap, triggers );
2032
+ };
2033
+
2034
+ /**
2035
+ * This is a helper function for translating camel-case to snake-case.
2036
+ */
2037
+ function snake_case(name){
2038
+ var regexp = /[A-Z]/g;
2039
+ var separator = '-';
2040
+ return name.replace(regexp, function(letter, pos) {
2041
+ return (pos ? separator : '') + letter.toLowerCase();
2042
+ });
2043
+ }
2044
+
2045
+ /**
2046
+ * Returns the actual instance of the $tooltip service.
2047
+ * TODO support multiple triggers
2048
+ */
2049
+ this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) {
2050
+ return function $tooltip ( type, prefix, defaultTriggerShow ) {
2051
+ var options = angular.extend( {}, defaultOptions, globalOptions );
2052
+
2053
+ /**
2054
+ * Returns an object of show and hide triggers.
2055
+ *
2056
+ * If a trigger is supplied,
2057
+ * it is used to show the tooltip; otherwise, it will use the `trigger`
2058
+ * option passed to the `$tooltipProvider.options` method; else it will
2059
+ * default to the trigger supplied to this directive factory.
2060
+ *
2061
+ * The hide trigger is based on the show trigger. If the `trigger` option
2062
+ * was passed to the `$tooltipProvider.options` method, it will use the
2063
+ * mapped trigger from `triggerMap` or the passed trigger if the map is
2064
+ * undefined; otherwise, it uses the `triggerMap` value of the show
2065
+ * trigger; else it will just use the show trigger.
2066
+ */
2067
+ function getTriggers ( trigger ) {
2068
+ var show = trigger || options.trigger || defaultTriggerShow;
2069
+ var hide = triggerMap[show] || show;
2070
+ return {
2071
+ show: show,
2072
+ hide: hide
2073
+ };
2074
+ }
2075
+
2076
+ var directiveName = snake_case( type );
2077
+
2078
+ var startSym = $interpolate.startSymbol();
2079
+ var endSym = $interpolate.endSymbol();
2080
+ var template =
2081
+ '<div '+ directiveName +'-popup '+
2082
+ 'title="'+startSym+'tt_title'+endSym+'" '+
2083
+ 'content="'+startSym+'tt_content'+endSym+'" '+
2084
+ 'placement="'+startSym+'tt_placement'+endSym+'" '+
2085
+ 'animation="tt_animation" '+
2086
+ 'is-open="tt_isOpen"'+
2087
+ '>'+
2088
+ '</div>';
2089
+
2090
+ return {
2091
+ restrict: 'EA',
2092
+ scope: true,
2093
+ compile: function (tElem, tAttrs) {
2094
+ var tooltipLinker = $compile( template );
2095
+
2096
+ return function link ( scope, element, attrs ) {
2097
+ var tooltip;
2098
+ var transitionTimeout;
2099
+ var popupTimeout;
2100
+ var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false;
2101
+ var triggers = getTriggers( undefined );
2102
+ var hasRegisteredTriggers = false;
2103
+ var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']);
2104
+
2105
+ var positionTooltip = function (){
2106
+ var position,
2107
+ ttWidth,
2108
+ ttHeight,
2109
+ ttPosition;
2110
+ // Get the position of the directive element.
2111
+ position = appendToBody ? $position.offset( element ) : $position.position( element );
2112
+
2113
+ // Get the height and width of the tooltip so we can center it.
2114
+ ttWidth = tooltip.prop( 'offsetWidth' );
2115
+ ttHeight = tooltip.prop( 'offsetHeight' );
2116
+
2117
+ // Calculate the tooltip's top and left coordinates to center it with
2118
+ // this directive.
2119
+ switch ( scope.tt_placement ) {
2120
+ case 'right':
2121
+ ttPosition = {
2122
+ top: position.top + position.height / 2 - ttHeight / 2,
2123
+ left: position.left + position.width
2124
+ };
2125
+ break;
2126
+ case 'bottom':
2127
+ ttPosition = {
2128
+ top: position.top + position.height,
2129
+ left: position.left + position.width / 2 - ttWidth / 2
2130
+ };
2131
+ break;
2132
+ case 'left':
2133
+ ttPosition = {
2134
+ top: position.top + position.height / 2 - ttHeight / 2,
2135
+ left: position.left - ttWidth
2136
+ };
2137
+ break;
2138
+ default:
2139
+ ttPosition = {
2140
+ top: position.top - ttHeight,
2141
+ left: position.left + position.width / 2 - ttWidth / 2
2142
+ };
2143
+ break;
2144
+ }
2145
+
2146
+ ttPosition.top += 'px';
2147
+ ttPosition.left += 'px';
2148
+
2149
+ // Now set the calculated positioning.
2150
+ tooltip.css( ttPosition );
2151
+
2152
+ };
2153
+
2154
+ // By default, the tooltip is not open.
2155
+ // TODO add ability to start tooltip opened
2156
+ scope.tt_isOpen = false;
2157
+
2158
+ function toggleTooltipBind () {
2159
+ if ( ! scope.tt_isOpen ) {
2160
+ showTooltipBind();
2161
+ } else {
2162
+ hideTooltipBind();
2163
+ }
2164
+ }
2165
+
2166
+ // Show the tooltip with delay if specified, otherwise show it immediately
2167
+ function showTooltipBind() {
2168
+ if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) {
2169
+ return;
2170
+ }
2171
+ if ( scope.tt_popupDelay ) {
2172
+ popupTimeout = $timeout( show, scope.tt_popupDelay, false );
2173
+ popupTimeout.then(function(reposition){reposition();});
2174
+ } else {
2175
+ show()();
2176
+ }
2177
+ }
2178
+
2179
+ function hideTooltipBind () {
2180
+ scope.$apply(function () {
2181
+ hide();
2182
+ });
2183
+ }
2184
+
2185
+ // Show the tooltip popup element.
2186
+ function show() {
2187
+
2188
+
2189
+ // Don't show empty tooltips.
2190
+ if ( ! scope.tt_content ) {
2191
+ return angular.noop;
2192
+ }
2193
+
2194
+ createTooltip();
2195
+
2196
+ // If there is a pending remove transition, we must cancel it, lest the
2197
+ // tooltip be mysteriously removed.
2198
+ if ( transitionTimeout ) {
2199
+ $timeout.cancel( transitionTimeout );
2200
+ }
2201
+
2202
+ // Set the initial positioning.
2203
+ tooltip.css({ top: 0, left: 0, display: 'block' });
2204
+
2205
+ // Now we add it to the DOM because need some info about it. But it's not
2206
+ // visible yet anyway.
2207
+ if ( appendToBody ) {
2208
+ $document.find( 'body' ).append( tooltip );
2209
+ } else {
2210
+ element.after( tooltip );
2211
+ }
2212
+
2213
+ positionTooltip();
2214
+
2215
+ // And show the tooltip.
2216
+ scope.tt_isOpen = true;
2217
+ scope.$digest(); // digest required as $apply is not called
2218
+
2219
+ // Return positioning function as promise callback for correct
2220
+ // positioning after draw.
2221
+ return positionTooltip;
2222
+ }
2223
+
2224
+ // Hide the tooltip popup element.
2225
+ function hide() {
2226
+ // First things first: we don't show it anymore.
2227
+ scope.tt_isOpen = false;
2228
+
2229
+ //if tooltip is going to be shown after delay, we must cancel this
2230
+ $timeout.cancel( popupTimeout );
2231
+
2232
+ // And now we remove it from the DOM. However, if we have animation, we
2233
+ // need to wait for it to expire beforehand.
2234
+ // FIXME: this is a placeholder for a port of the transitions library.
2235
+ if ( scope.tt_animation ) {
2236
+ transitionTimeout = $timeout(removeTooltip, 500);
2237
+ } else {
2238
+ removeTooltip();
2239
+ }
2240
+ }
2241
+
2242
+ function createTooltip() {
2243
+ // There can only be one tooltip element per directive shown at once.
2244
+ if (tooltip) {
2245
+ removeTooltip();
2246
+ }
2247
+ tooltip = tooltipLinker(scope, function () {});
2248
+
2249
+ // Get contents rendered into the tooltip
2250
+ scope.$digest();
2251
+ }
2252
+
2253
+ function removeTooltip() {
2254
+ if (tooltip) {
2255
+ tooltip.remove();
2256
+ tooltip = null;
2257
+ }
2258
+ }
2259
+
2260
+ /**
2261
+ * Observe the relevant attributes.
2262
+ */
2263
+ attrs.$observe( type, function ( val ) {
2264
+ scope.tt_content = val;
2265
+
2266
+ if (!val && scope.tt_isOpen ) {
2267
+ hide();
2268
+ }
2269
+ });
2270
+
2271
+ attrs.$observe( prefix+'Title', function ( val ) {
2272
+ scope.tt_title = val;
2273
+ });
2274
+
2275
+ attrs.$observe( prefix+'Placement', function ( val ) {
2276
+ scope.tt_placement = angular.isDefined( val ) ? val : options.placement;
2277
+ });
2278
+
2279
+ attrs.$observe( prefix+'PopupDelay', function ( val ) {
2280
+ var delay = parseInt( val, 10 );
2281
+ scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay;
2282
+ });
2283
+
2284
+ var unregisterTriggers = function() {
2285
+ if (hasRegisteredTriggers) {
2286
+ element.unbind( triggers.show, showTooltipBind );
2287
+ element.unbind( triggers.hide, hideTooltipBind );
2288
+ }
2289
+ };
2290
+
2291
+ attrs.$observe( prefix+'Trigger', function ( val ) {
2292
+ unregisterTriggers();
2293
+
2294
+ triggers = getTriggers( val );
2295
+
2296
+ if ( triggers.show === triggers.hide ) {
2297
+ element.bind( triggers.show, toggleTooltipBind );
2298
+ } else {
2299
+ element.bind( triggers.show, showTooltipBind );
2300
+ element.bind( triggers.hide, hideTooltipBind );
2301
+ }
2302
+
2303
+ hasRegisteredTriggers = true;
2304
+ });
2305
+
2306
+ var animation = scope.$eval(attrs[prefix + 'Animation']);
2307
+ scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation;
2308
+
2309
+ attrs.$observe( prefix+'AppendToBody', function ( val ) {
2310
+ appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody;
2311
+ });
2312
+
2313
+ // if a tooltip is attached to <body> we need to remove it on
2314
+ // location change as its parent scope will probably not be destroyed
2315
+ // by the change.
2316
+ if ( appendToBody ) {
2317
+ scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () {
2318
+ if ( scope.tt_isOpen ) {
2319
+ hide();
2320
+ }
2321
+ });
2322
+ }
2323
+
2324
+ // Make sure tooltip is destroyed and removed.
2325
+ scope.$on('$destroy', function onDestroyTooltip() {
2326
+ $timeout.cancel( transitionTimeout );
2327
+ $timeout.cancel( popupTimeout );
2328
+ unregisterTriggers();
2329
+ removeTooltip();
2330
+ });
2331
+ };
2332
+ }
2333
+ };
2334
+ };
2335
+ }];
2336
+ })
2337
+
2338
+ .directive( 'tooltipPopup', function () {
2339
+ return {
2340
+ restrict: 'EA',
2341
+ replace: true,
2342
+ scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
2343
+ templateUrl: 'template/tooltip/tooltip-popup.html'
2344
+ };
2345
+ })
2346
+
2347
+ .directive( 'tooltip', [ '$tooltip', function ( $tooltip ) {
2348
+ return $tooltip( 'tooltip', 'tooltip', 'mouseenter' );
2349
+ }])
2350
+
2351
+ .directive( 'tooltipHtmlUnsafePopup', function () {
2352
+ return {
2353
+ restrict: 'EA',
2354
+ replace: true,
2355
+ scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
2356
+ templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html'
2357
+ };
2358
+ })
2359
+
2360
+ .directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) {
2361
+ return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' );
2362
+ }]);
2363
+
2364
+ /**
2365
+ * The following features are still outstanding: popup delay, animation as a
2366
+ * function, placement as a function, inside, support for more triggers than
2367
+ * just mouse enter/leave, html popovers, and selector delegatation.
2368
+ */
2369
+ angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] )
2370
+
2371
+ .directive( 'popoverPopup', function () {
2372
+ return {
2373
+ restrict: 'EA',
2374
+ replace: true,
2375
+ scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' },
2376
+ templateUrl: 'template/popover/popover.html'
2377
+ };
2378
+ })
2379
+
2380
+ .directive( 'popover', [ '$tooltip', function ( $tooltip ) {
2381
+ return $tooltip( 'popover', 'popover', 'click' );
2382
+ }]);
2383
+
2384
+ angular.module('ui.bootstrap.progressbar', ['ui.bootstrap.transition'])
2385
+
2386
+ .constant('progressConfig', {
2387
+ animate: true,
2388
+ max: 100
2389
+ })
2390
+
2391
+ .controller('ProgressController', ['$scope', '$attrs', 'progressConfig', '$transition', function($scope, $attrs, progressConfig, $transition) {
2392
+ var self = this,
2393
+ bars = [],
2394
+ max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max,
2395
+ animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
2396
+
2397
+ this.addBar = function(bar, element) {
2398
+ var oldValue = 0, index = bar.$parent.$index;
2399
+ if ( angular.isDefined(index) && bars[index] ) {
2400
+ oldValue = bars[index].value;
2401
+ }
2402
+ bars.push(bar);
2403
+
2404
+ this.update(element, bar.value, oldValue);
2405
+
2406
+ bar.$watch('value', function(value, oldValue) {
2407
+ if (value !== oldValue) {
2408
+ self.update(element, value, oldValue);
2409
+ }
2410
+ });
2411
+
2412
+ bar.$on('$destroy', function() {
2413
+ self.removeBar(bar);
2414
+ });
2415
+ };
2416
+
2417
+ // Update bar element width
2418
+ this.update = function(element, newValue, oldValue) {
2419
+ var percent = this.getPercentage(newValue);
2420
+
2421
+ if (animate) {
2422
+ element.css('width', this.getPercentage(oldValue) + '%');
2423
+ $transition(element, {width: percent + '%'});
2424
+ } else {
2425
+ element.css({'transition': 'none', 'width': percent + '%'});
2426
+ }
2427
+ };
2428
+
2429
+ this.removeBar = function(bar) {
2430
+ bars.splice(bars.indexOf(bar), 1);
2431
+ };
2432
+
2433
+ this.getPercentage = function(value) {
2434
+ return Math.round(100 * value / max);
2435
+ };
2436
+ }])
2437
+
2438
+ .directive('progress', function() {
2439
+ return {
2440
+ restrict: 'EA',
2441
+ replace: true,
2442
+ transclude: true,
2443
+ controller: 'ProgressController',
2444
+ require: 'progress',
2445
+ scope: {},
2446
+ template: '<div class="progress" ng-transclude></div>'
2447
+ //templateUrl: 'template/progressbar/progress.html' // Works in AngularJS 1.2
2448
+ };
2449
+ })
2450
+
2451
+ .directive('bar', function() {
2452
+ return {
2453
+ restrict: 'EA',
2454
+ replace: true,
2455
+ transclude: true,
2456
+ require: '^progress',
2457
+ scope: {
2458
+ value: '=',
2459
+ type: '@'
2460
+ },
2461
+ templateUrl: 'template/progressbar/bar.html',
2462
+ link: function(scope, element, attrs, progressCtrl) {
2463
+ progressCtrl.addBar(scope, element);
2464
+ }
2465
+ };
2466
+ })
2467
+
2468
+ .directive('progressbar', function() {
2469
+ return {
2470
+ restrict: 'EA',
2471
+ replace: true,
2472
+ transclude: true,
2473
+ controller: 'ProgressController',
2474
+ scope: {
2475
+ value: '=',
2476
+ type: '@'
2477
+ },
2478
+ templateUrl: 'template/progressbar/progressbar.html',
2479
+ link: function(scope, element, attrs, progressCtrl) {
2480
+ progressCtrl.addBar(scope, angular.element(element.children()[0]));
2481
+ }
2482
+ };
2483
+ });
2484
+ angular.module('ui.bootstrap.rating', [])
2485
+
2486
+ .constant('ratingConfig', {
2487
+ max: 5,
2488
+ stateOn: null,
2489
+ stateOff: null
2490
+ })
2491
+
2492
+ .controller('RatingController', ['$scope', '$attrs', '$parse', 'ratingConfig', function($scope, $attrs, $parse, ratingConfig) {
2493
+
2494
+ this.maxRange = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max;
2495
+ this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
2496
+ this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
2497
+
2498
+ this.createRateObjects = function(states) {
2499
+ var defaultOptions = {
2500
+ stateOn: this.stateOn,
2501
+ stateOff: this.stateOff
2502
+ };
2503
+
2504
+ for (var i = 0, n = states.length; i < n; i++) {
2505
+ states[i] = angular.extend({ index: i }, defaultOptions, states[i]);
2506
+ }
2507
+ return states;
2508
+ };
2509
+
2510
+ // Get objects used in template
2511
+ $scope.range = angular.isDefined($attrs.ratingStates) ? this.createRateObjects(angular.copy($scope.$parent.$eval($attrs.ratingStates))): this.createRateObjects(new Array(this.maxRange));
2512
+
2513
+ $scope.rate = function(value) {
2514
+ if ( $scope.value !== value && !$scope.readonly ) {
2515
+ $scope.value = value;
2516
+ }
2517
+ };
2518
+
2519
+ $scope.enter = function(value) {
2520
+ if ( ! $scope.readonly ) {
2521
+ $scope.val = value;
2522
+ }
2523
+ $scope.onHover({value: value});
2524
+ };
2525
+
2526
+ $scope.reset = function() {
2527
+ $scope.val = angular.copy($scope.value);
2528
+ $scope.onLeave();
2529
+ };
2530
+
2531
+ $scope.$watch('value', function(value) {
2532
+ $scope.val = value;
2533
+ });
2534
+
2535
+ $scope.readonly = false;
2536
+ if ($attrs.readonly) {
2537
+ $scope.$parent.$watch($parse($attrs.readonly), function(value) {
2538
+ $scope.readonly = !!value;
2539
+ });
2540
+ }
2541
+ }])
2542
+
2543
+ .directive('rating', function() {
2544
+ return {
2545
+ restrict: 'EA',
2546
+ scope: {
2547
+ value: '=',
2548
+ onHover: '&',
2549
+ onLeave: '&'
2550
+ },
2551
+ controller: 'RatingController',
2552
+ templateUrl: 'template/rating/rating.html',
2553
+ replace: true
2554
+ };
2555
+ });
2556
+
2557
+ /**
2558
+ * @ngdoc overview
2559
+ * @name ui.bootstrap.tabs
2560
+ *
2561
+ * @description
2562
+ * AngularJS version of the tabs directive.
2563
+ */
2564
+
2565
+ angular.module('ui.bootstrap.tabs', [])
2566
+
2567
+ .controller('TabsetController', ['$scope', function TabsetCtrl($scope) {
2568
+ var ctrl = this,
2569
+ tabs = ctrl.tabs = $scope.tabs = [];
2570
+
2571
+ ctrl.select = function(tab) {
2572
+ angular.forEach(tabs, function(tab) {
2573
+ tab.active = false;
2574
+ });
2575
+ tab.active = true;
2576
+ };
2577
+
2578
+ ctrl.addTab = function addTab(tab) {
2579
+ tabs.push(tab);
2580
+ if (tabs.length === 1 || tab.active) {
2581
+ ctrl.select(tab);
2582
+ }
2583
+ };
2584
+
2585
+ ctrl.removeTab = function removeTab(tab) {
2586
+ var index = tabs.indexOf(tab);
2587
+ //Select a new tab if the tab to be removed is selected
2588
+ if (tab.active && tabs.length > 1) {
2589
+ //If this is the last tab, select the previous tab. else, the next tab.
2590
+ var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1;
2591
+ ctrl.select(tabs[newActiveIndex]);
2592
+ }
2593
+ tabs.splice(index, 1);
2594
+ };
2595
+ }])
2596
+
2597
+ /**
2598
+ * @ngdoc directive
2599
+ * @name ui.bootstrap.tabs.directive:tabset
2600
+ * @restrict EA
2601
+ *
2602
+ * @description
2603
+ * Tabset is the outer container for the tabs directive
2604
+ *
2605
+ * @param {boolean=} vertical Whether or not to use vertical styling for the tabs.
2606
+ * @param {boolean=} justified Whether or not to use justified styling for the tabs.
2607
+ *
2608
+ * @example
2609
+ <example module="ui.bootstrap">
2610
+ <file name="index.html">
2611
+ <tabset>
2612
+ <tab heading="Tab 1"><b>First</b> Content!</tab>
2613
+ <tab heading="Tab 2"><i>Second</i> Content!</tab>
2614
+ </tabset>
2615
+ <hr />
2616
+ <tabset vertical="true">
2617
+ <tab heading="Vertical Tab 1"><b>First</b> Vertical Content!</tab>
2618
+ <tab heading="Vertical Tab 2"><i>Second</i> Vertical Content!</tab>
2619
+ </tabset>
2620
+ <tabset justified="true">
2621
+ <tab heading="Justified Tab 1"><b>First</b> Justified Content!</tab>
2622
+ <tab heading="Justified Tab 2"><i>Second</i> Justified Content!</tab>
2623
+ </tabset>
2624
+ </file>
2625
+ </example>
2626
+ */
2627
+ .directive('tabset', function() {
2628
+ return {
2629
+ restrict: 'EA',
2630
+ transclude: true,
2631
+ replace: true,
2632
+ scope: {},
2633
+ controller: 'TabsetController',
2634
+ templateUrl: 'template/tabs/tabset.html',
2635
+ link: function(scope, element, attrs) {
2636
+ scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
2637
+ scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
2638
+ scope.type = angular.isDefined(attrs.type) ? scope.$parent.$eval(attrs.type) : 'tabs';
2639
+ }
2640
+ };
2641
+ })
2642
+
2643
+ /**
2644
+ * @ngdoc directive
2645
+ * @name ui.bootstrap.tabs.directive:tab
2646
+ * @restrict EA
2647
+ *
2648
+ * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}.
2649
+ * @param {string=} select An expression to evaluate when the tab is selected.
2650
+ * @param {boolean=} active A binding, telling whether or not this tab is selected.
2651
+ * @param {boolean=} disabled A binding, telling whether or not this tab is disabled.
2652
+ *
2653
+ * @description
2654
+ * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}.
2655
+ *
2656
+ * @example
2657
+ <example module="ui.bootstrap">
2658
+ <file name="index.html">
2659
+ <div ng-controller="TabsDemoCtrl">
2660
+ <button class="btn btn-small" ng-click="items[0].active = true">
2661
+ Select item 1, using active binding
2662
+ </button>
2663
+ <button class="btn btn-small" ng-click="items[1].disabled = !items[1].disabled">
2664
+ Enable/disable item 2, using disabled binding
2665
+ </button>
2666
+ <br />
2667
+ <tabset>
2668
+ <tab heading="Tab 1">First Tab</tab>
2669
+ <tab select="alertMe()">
2670
+ <tab-heading><i class="icon-bell"></i> Alert me!</tab-heading>
2671
+ Second Tab, with alert callback and html heading!
2672
+ </tab>
2673
+ <tab ng-repeat="item in items"
2674
+ heading="{{item.title}}"
2675
+ disabled="item.disabled"
2676
+ active="item.active">
2677
+ {{item.content}}
2678
+ </tab>
2679
+ </tabset>
2680
+ </div>
2681
+ </file>
2682
+ <file name="script.js">
2683
+ function TabsDemoCtrl($scope) {
2684
+ $scope.items = [
2685
+ { title:"Dynamic Title 1", content:"Dynamic Item 0" },
2686
+ { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true }
2687
+ ];
2688
+
2689
+ $scope.alertMe = function() {
2690
+ setTimeout(function() {
2691
+ alert("You've selected the alert tab!");
2692
+ });
2693
+ };
2694
+ };
2695
+ </file>
2696
+ </example>
2697
+ */
2698
+
2699
+ /**
2700
+ * @ngdoc directive
2701
+ * @name ui.bootstrap.tabs.directive:tabHeading
2702
+ * @restrict EA
2703
+ *
2704
+ * @description
2705
+ * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element.
2706
+ *
2707
+ * @example
2708
+ <example module="ui.bootstrap">
2709
+ <file name="index.html">
2710
+ <tabset>
2711
+ <tab>
2712
+ <tab-heading><b>HTML</b> in my titles?!</tab-heading>
2713
+ And some content, too!
2714
+ </tab>
2715
+ <tab>
2716
+ <tab-heading><i class="icon-heart"></i> Icon heading?!?</tab-heading>
2717
+ That's right.
2718
+ </tab>
2719
+ </tabset>
2720
+ </file>
2721
+ </example>
2722
+ */
2723
+ .directive('tab', ['$parse', function($parse) {
2724
+ return {
2725
+ require: '^tabset',
2726
+ restrict: 'EA',
2727
+ replace: true,
2728
+ templateUrl: 'template/tabs/tab.html',
2729
+ transclude: true,
2730
+ scope: {
2731
+ heading: '@',
2732
+ onSelect: '&select', //This callback is called in contentHeadingTransclude
2733
+ //once it inserts the tab's content into the dom
2734
+ onDeselect: '&deselect'
2735
+ },
2736
+ controller: function() {
2737
+ //Empty controller so other directives can require being 'under' a tab
2738
+ },
2739
+ compile: function(elm, attrs, transclude) {
2740
+ return function postLink(scope, elm, attrs, tabsetCtrl) {
2741
+ var getActive, setActive;
2742
+ if (attrs.active) {
2743
+ getActive = $parse(attrs.active);
2744
+ setActive = getActive.assign;
2745
+ scope.$parent.$watch(getActive, function updateActive(value, oldVal) {
2746
+ // Avoid re-initializing scope.active as it is already initialized
2747
+ // below. (watcher is called async during init with value ===
2748
+ // oldVal)
2749
+ if (value !== oldVal) {
2750
+ scope.active = !!value;
2751
+ }
2752
+ });
2753
+ scope.active = getActive(scope.$parent);
2754
+ } else {
2755
+ setActive = getActive = angular.noop;
2756
+ }
2757
+
2758
+ scope.$watch('active', function(active) {
2759
+ // Note this watcher also initializes and assigns scope.active to the
2760
+ // attrs.active expression.
2761
+ setActive(scope.$parent, active);
2762
+ if (active) {
2763
+ tabsetCtrl.select(scope);
2764
+ scope.onSelect();
2765
+ } else {
2766
+ scope.onDeselect();
2767
+ }
2768
+ });
2769
+
2770
+ scope.disabled = false;
2771
+ if ( attrs.disabled ) {
2772
+ scope.$parent.$watch($parse(attrs.disabled), function(value) {
2773
+ scope.disabled = !! value;
2774
+ });
2775
+ }
2776
+
2777
+ scope.select = function() {
2778
+ if ( ! scope.disabled ) {
2779
+ scope.active = true;
2780
+ }
2781
+ };
2782
+
2783
+ tabsetCtrl.addTab(scope);
2784
+ scope.$on('$destroy', function() {
2785
+ tabsetCtrl.removeTab(scope);
2786
+ });
2787
+
2788
+
2789
+ //We need to transclude later, once the content container is ready.
2790
+ //when this link happens, we're inside a tab heading.
2791
+ scope.$transcludeFn = transclude;
2792
+ };
2793
+ }
2794
+ };
2795
+ }])
2796
+
2797
+ .directive('tabHeadingTransclude', [function() {
2798
+ return {
2799
+ restrict: 'A',
2800
+ require: '^tab',
2801
+ link: function(scope, elm, attrs, tabCtrl) {
2802
+ scope.$watch('headingElement', function updateHeadingElement(heading) {
2803
+ if (heading) {
2804
+ elm.html('');
2805
+ elm.append(heading);
2806
+ }
2807
+ });
2808
+ }
2809
+ };
2810
+ }])
2811
+
2812
+ .directive('tabContentTransclude', function() {
2813
+ return {
2814
+ restrict: 'A',
2815
+ require: '^tabset',
2816
+ link: function(scope, elm, attrs) {
2817
+ var tab = scope.$eval(attrs.tabContentTransclude);
2818
+
2819
+ //Now our tab is ready to be transcluded: both the tab heading area
2820
+ //and the tab content area are loaded. Transclude 'em both.
2821
+ tab.$transcludeFn(tab.$parent, function(contents) {
2822
+ angular.forEach(contents, function(node) {
2823
+ if (isTabHeading(node)) {
2824
+ //Let tabHeadingTransclude know.
2825
+ tab.headingElement = node;
2826
+ } else {
2827
+ elm.append(node);
2828
+ }
2829
+ });
2830
+ });
2831
+ }
2832
+ };
2833
+ function isTabHeading(node) {
2834
+ return node.tagName && (
2835
+ node.hasAttribute('tab-heading') ||
2836
+ node.hasAttribute('data-tab-heading') ||
2837
+ node.tagName.toLowerCase() === 'tab-heading' ||
2838
+ node.tagName.toLowerCase() === 'data-tab-heading'
2839
+ );
2840
+ }
2841
+ })
2842
+
2843
+ ;
2844
+
2845
+ angular.module('ui.bootstrap.timepicker', [])
2846
+
2847
+ .constant('timepickerConfig', {
2848
+ hourStep: 1,
2849
+ minuteStep: 1,
2850
+ showMeridian: true,
2851
+ meridians: null,
2852
+ readonlyInput: false,
2853
+ mousewheel: true
2854
+ })
2855
+
2856
+ .directive('timepicker', ['$parse', '$log', 'timepickerConfig', '$locale', function ($parse, $log, timepickerConfig, $locale) {
2857
+ return {
2858
+ restrict: 'EA',
2859
+ require:'?^ngModel',
2860
+ replace: true,
2861
+ scope: {},
2862
+ templateUrl: 'template/timepicker/timepicker.html',
2863
+ link: function(scope, element, attrs, ngModel) {
2864
+ if ( !ngModel ) {
2865
+ return; // do nothing if no ng-model
2866
+ }
2867
+
2868
+ var selected = new Date(),
2869
+ meridians = angular.isDefined(attrs.meridians) ? scope.$parent.$eval(attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS;
2870
+
2871
+ var hourStep = timepickerConfig.hourStep;
2872
+ if (attrs.hourStep) {
2873
+ scope.$parent.$watch($parse(attrs.hourStep), function(value) {
2874
+ hourStep = parseInt(value, 10);
2875
+ });
2876
+ }
2877
+
2878
+ var minuteStep = timepickerConfig.minuteStep;
2879
+ if (attrs.minuteStep) {
2880
+ scope.$parent.$watch($parse(attrs.minuteStep), function(value) {
2881
+ minuteStep = parseInt(value, 10);
2882
+ });
2883
+ }
2884
+
2885
+ // 12H / 24H mode
2886
+ scope.showMeridian = timepickerConfig.showMeridian;
2887
+ if (attrs.showMeridian) {
2888
+ scope.$parent.$watch($parse(attrs.showMeridian), function(value) {
2889
+ scope.showMeridian = !!value;
2890
+
2891
+ if ( ngModel.$error.time ) {
2892
+ // Evaluate from template
2893
+ var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
2894
+ if (angular.isDefined( hours ) && angular.isDefined( minutes )) {
2895
+ selected.setHours( hours );
2896
+ refresh();
2897
+ }
2898
+ } else {
2899
+ updateTemplate();
2900
+ }
2901
+ });
2902
+ }
2903
+
2904
+ // Get scope.hours in 24H mode if valid
2905
+ function getHoursFromTemplate ( ) {
2906
+ var hours = parseInt( scope.hours, 10 );
2907
+ var valid = ( scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24);
2908
+ if ( !valid ) {
2909
+ return undefined;
2910
+ }
2911
+
2912
+ if ( scope.showMeridian ) {
2913
+ if ( hours === 12 ) {
2914
+ hours = 0;
2915
+ }
2916
+ if ( scope.meridian === meridians[1] ) {
2917
+ hours = hours + 12;
2918
+ }
2919
+ }
2920
+ return hours;
2921
+ }
2922
+
2923
+ function getMinutesFromTemplate() {
2924
+ var minutes = parseInt(scope.minutes, 10);
2925
+ return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined;
2926
+ }
2927
+
2928
+ function pad( value ) {
2929
+ return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value;
2930
+ }
2931
+
2932
+ // Input elements
2933
+ var inputs = element.find('input'), hoursInputEl = inputs.eq(0), minutesInputEl = inputs.eq(1);
2934
+
2935
+ // Respond on mousewheel spin
2936
+ var mousewheel = (angular.isDefined(attrs.mousewheel)) ? scope.$eval(attrs.mousewheel) : timepickerConfig.mousewheel;
2937
+ if ( mousewheel ) {
2938
+
2939
+ var isScrollingUp = function(e) {
2940
+ if (e.originalEvent) {
2941
+ e = e.originalEvent;
2942
+ }
2943
+ //pick correct delta variable depending on event
2944
+ var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY;
2945
+ return (e.detail || delta > 0);
2946
+ };
2947
+
2948
+ hoursInputEl.bind('mousewheel wheel', function(e) {
2949
+ scope.$apply( (isScrollingUp(e)) ? scope.incrementHours() : scope.decrementHours() );
2950
+ e.preventDefault();
2951
+ });
2952
+
2953
+ minutesInputEl.bind('mousewheel wheel', function(e) {
2954
+ scope.$apply( (isScrollingUp(e)) ? scope.incrementMinutes() : scope.decrementMinutes() );
2955
+ e.preventDefault();
2956
+ });
2957
+ }
2958
+
2959
+ scope.readonlyInput = (angular.isDefined(attrs.readonlyInput)) ? scope.$eval(attrs.readonlyInput) : timepickerConfig.readonlyInput;
2960
+ if ( ! scope.readonlyInput ) {
2961
+
2962
+ var invalidate = function(invalidHours, invalidMinutes) {
2963
+ ngModel.$setViewValue( null );
2964
+ ngModel.$setValidity('time', false);
2965
+ if (angular.isDefined(invalidHours)) {
2966
+ scope.invalidHours = invalidHours;
2967
+ }
2968
+ if (angular.isDefined(invalidMinutes)) {
2969
+ scope.invalidMinutes = invalidMinutes;
2970
+ }
2971
+ };
2972
+
2973
+ scope.updateHours = function() {
2974
+ var hours = getHoursFromTemplate();
2975
+
2976
+ if ( angular.isDefined(hours) ) {
2977
+ selected.setHours( hours );
2978
+ refresh( 'h' );
2979
+ } else {
2980
+ invalidate(true);
2981
+ }
2982
+ };
2983
+
2984
+ hoursInputEl.bind('blur', function(e) {
2985
+ if ( !scope.validHours && scope.hours < 10) {
2986
+ scope.$apply( function() {
2987
+ scope.hours = pad( scope.hours );
2988
+ });
2989
+ }
2990
+ });
2991
+
2992
+ scope.updateMinutes = function() {
2993
+ var minutes = getMinutesFromTemplate();
2994
+
2995
+ if ( angular.isDefined(minutes) ) {
2996
+ selected.setMinutes( minutes );
2997
+ refresh( 'm' );
2998
+ } else {
2999
+ invalidate(undefined, true);
3000
+ }
3001
+ };
3002
+
3003
+ minutesInputEl.bind('blur', function(e) {
3004
+ if ( !scope.invalidMinutes && scope.minutes < 10 ) {
3005
+ scope.$apply( function() {
3006
+ scope.minutes = pad( scope.minutes );
3007
+ });
3008
+ }
3009
+ });
3010
+ } else {
3011
+ scope.updateHours = angular.noop;
3012
+ scope.updateMinutes = angular.noop;
3013
+ }
3014
+
3015
+ ngModel.$render = function() {
3016
+ var date = ngModel.$modelValue ? new Date( ngModel.$modelValue ) : null;
3017
+
3018
+ if ( isNaN(date) ) {
3019
+ ngModel.$setValidity('time', false);
3020
+ $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
3021
+ } else {
3022
+ if ( date ) {
3023
+ selected = date;
3024
+ }
3025
+ makeValid();
3026
+ updateTemplate();
3027
+ }
3028
+ };
3029
+
3030
+ // Call internally when we know that model is valid.
3031
+ function refresh( keyboardChange ) {
3032
+ makeValid();
3033
+ ngModel.$setViewValue( new Date(selected) );
3034
+ updateTemplate( keyboardChange );
3035
+ }
3036
+
3037
+ function makeValid() {
3038
+ ngModel.$setValidity('time', true);
3039
+ scope.invalidHours = false;
3040
+ scope.invalidMinutes = false;
3041
+ }
3042
+
3043
+ function updateTemplate( keyboardChange ) {
3044
+ var hours = selected.getHours(), minutes = selected.getMinutes();
3045
+
3046
+ if ( scope.showMeridian ) {
3047
+ hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system
3048
+ }
3049
+ scope.hours = keyboardChange === 'h' ? hours : pad(hours);
3050
+ scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes);
3051
+ scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
3052
+ }
3053
+
3054
+ function addMinutes( minutes ) {
3055
+ var dt = new Date( selected.getTime() + minutes * 60000 );
3056
+ selected.setHours( dt.getHours(), dt.getMinutes() );
3057
+ refresh();
3058
+ }
3059
+
3060
+ scope.incrementHours = function() {
3061
+ addMinutes( hourStep * 60 );
3062
+ };
3063
+ scope.decrementHours = function() {
3064
+ addMinutes( - hourStep * 60 );
3065
+ };
3066
+ scope.incrementMinutes = function() {
3067
+ addMinutes( minuteStep );
3068
+ };
3069
+ scope.decrementMinutes = function() {
3070
+ addMinutes( - minuteStep );
3071
+ };
3072
+ scope.toggleMeridian = function() {
3073
+ addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) );
3074
+ };
3075
+ }
3076
+ };
3077
+ }]);
3078
+
3079
+ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml'])
3080
+
3081
+ /**
3082
+ * A helper service that can parse typeahead's syntax (string provided by users)
3083
+ * Extracted to a separate service for ease of unit testing
3084
+ */
3085
+ .factory('typeaheadParser', ['$parse', function ($parse) {
3086
+
3087
+ // 00000111000000000000022200000000000000003333333333333330000000000044000
3088
+ var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;
3089
+
3090
+ return {
3091
+ parse:function (input) {
3092
+
3093
+ var match = input.match(TYPEAHEAD_REGEXP), modelMapper, viewMapper, source;
3094
+ if (!match) {
3095
+ throw new Error(
3096
+ "Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" +
3097
+ " but got '" + input + "'.");
3098
+ }
3099
+
3100
+ return {
3101
+ itemName:match[3],
3102
+ source:$parse(match[4]),
3103
+ viewMapper:$parse(match[2] || match[1]),
3104
+ modelMapper:$parse(match[1])
3105
+ };
3106
+ }
3107
+ };
3108
+ }])
3109
+
3110
+ .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser',
3111
+ function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) {
3112
+
3113
+ var HOT_KEYS = [9, 13, 27, 38, 40];
3114
+
3115
+ return {
3116
+ require:'ngModel',
3117
+ link:function (originalScope, element, attrs, modelCtrl) {
3118
+
3119
+ //SUPPORTED ATTRIBUTES (OPTIONS)
3120
+
3121
+ //minimal no of characters that needs to be entered before typeahead kicks-in
3122
+ var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;
3123
+
3124
+ //minimal wait time after last character typed before typehead kicks-in
3125
+ var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
3126
+
3127
+ //should it restrict model values to the ones selected from the popup only?
3128
+ var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
3129
+
3130
+ //binding to a variable that indicates if matches are being retrieved asynchronously
3131
+ var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
3132
+
3133
+ //a callback executed when a match is selected
3134
+ var onSelectCallback = $parse(attrs.typeaheadOnSelect);
3135
+
3136
+ var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
3137
+
3138
+ var appendToBody = attrs.typeaheadAppendToBody ? $parse(attrs.typeaheadAppendToBody) : false;
3139
+
3140
+ //INTERNAL VARIABLES
3141
+
3142
+ //model setter executed upon match selection
3143
+ var $setModelValue = $parse(attrs.ngModel).assign;
3144
+
3145
+ //expressions used by typeahead
3146
+ var parserResult = typeaheadParser.parse(attrs.typeahead);
3147
+
3148
+ var hasFocus;
3149
+
3150
+ //pop-up element used to display matches
3151
+ var popUpEl = angular.element('<div typeahead-popup></div>');
3152
+ popUpEl.attr({
3153
+ matches: 'matches',
3154
+ active: 'activeIdx',
3155
+ select: 'select(activeIdx)',
3156
+ query: 'query',
3157
+ position: 'position'
3158
+ });
3159
+ //custom item template
3160
+ if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
3161
+ popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
3162
+ }
3163
+
3164
+ //create a child scope for the typeahead directive so we are not polluting original scope
3165
+ //with typeahead-specific data (matches, query etc.)
3166
+ var scope = originalScope.$new();
3167
+ originalScope.$on('$destroy', function(){
3168
+ scope.$destroy();
3169
+ });
3170
+
3171
+ var resetMatches = function() {
3172
+ scope.matches = [];
3173
+ scope.activeIdx = -1;
3174
+ };
3175
+
3176
+ var getMatchesAsync = function(inputValue) {
3177
+
3178
+ var locals = {$viewValue: inputValue};
3179
+ isLoadingSetter(originalScope, true);
3180
+ $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
3181
+
3182
+ //it might happen that several async queries were in progress if a user were typing fast
3183
+ //but we are interested only in responses that correspond to the current view value
3184
+ if (inputValue === modelCtrl.$viewValue && hasFocus) {
3185
+ if (matches.length > 0) {
3186
+
3187
+ scope.activeIdx = 0;
3188
+ scope.matches.length = 0;
3189
+
3190
+ //transform labels
3191
+ for(var i=0; i<matches.length; i++) {
3192
+ locals[parserResult.itemName] = matches[i];
3193
+ scope.matches.push({
3194
+ label: parserResult.viewMapper(scope, locals),
3195
+ model: matches[i]
3196
+ });
3197
+ }
3198
+
3199
+ scope.query = inputValue;
3200
+ //position pop-up with matches - we need to re-calculate its position each time we are opening a window
3201
+ //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
3202
+ //due to other elements being rendered
3203
+ scope.position = appendToBody ? $position.offset(element) : $position.position(element);
3204
+ scope.position.top = scope.position.top + element.prop('offsetHeight');
3205
+
3206
+ } else {
3207
+ resetMatches();
3208
+ }
3209
+ isLoadingSetter(originalScope, false);
3210
+ }
3211
+ }, function(){
3212
+ resetMatches();
3213
+ isLoadingSetter(originalScope, false);
3214
+ });
3215
+ };
3216
+
3217
+ resetMatches();
3218
+
3219
+ //we need to propagate user's query so we can higlight matches
3220
+ scope.query = undefined;
3221
+
3222
+ //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
3223
+ var timeoutPromise;
3224
+
3225
+ //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
3226
+ //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
3227
+ modelCtrl.$parsers.unshift(function (inputValue) {
3228
+
3229
+ hasFocus = true;
3230
+
3231
+ if (inputValue && inputValue.length >= minSearch) {
3232
+ if (waitTime > 0) {
3233
+ if (timeoutPromise) {
3234
+ $timeout.cancel(timeoutPromise);//cancel previous timeout
3235
+ }
3236
+ timeoutPromise = $timeout(function () {
3237
+ getMatchesAsync(inputValue);
3238
+ }, waitTime);
3239
+ } else {
3240
+ getMatchesAsync(inputValue);
3241
+ }
3242
+ } else {
3243
+ isLoadingSetter(originalScope, false);
3244
+ resetMatches();
3245
+ }
3246
+
3247
+ if (isEditable) {
3248
+ return inputValue;
3249
+ } else {
3250
+ if (!inputValue) {
3251
+ // Reset in case user had typed something previously.
3252
+ modelCtrl.$setValidity('editable', true);
3253
+ return inputValue;
3254
+ } else {
3255
+ modelCtrl.$setValidity('editable', false);
3256
+ return undefined;
3257
+ }
3258
+ }
3259
+ });
3260
+
3261
+ modelCtrl.$formatters.push(function (modelValue) {
3262
+
3263
+ var candidateViewValue, emptyViewValue;
3264
+ var locals = {};
3265
+
3266
+ if (inputFormatter) {
3267
+
3268
+ locals['$model'] = modelValue;
3269
+ return inputFormatter(originalScope, locals);
3270
+
3271
+ } else {
3272
+
3273
+ //it might happen that we don't have enough info to properly render input value
3274
+ //we need to check for this situation and simply return model value if we can't apply custom formatting
3275
+ locals[parserResult.itemName] = modelValue;
3276
+ candidateViewValue = parserResult.viewMapper(originalScope, locals);
3277
+ locals[parserResult.itemName] = undefined;
3278
+ emptyViewValue = parserResult.viewMapper(originalScope, locals);
3279
+
3280
+ return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue;
3281
+ }
3282
+ });
3283
+
3284
+ scope.select = function (activeIdx) {
3285
+ //called from within the $digest() cycle
3286
+ var locals = {};
3287
+ var model, item;
3288
+
3289
+ locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
3290
+ model = parserResult.modelMapper(originalScope, locals);
3291
+ $setModelValue(originalScope, model);
3292
+ modelCtrl.$setValidity('editable', true);
3293
+
3294
+ onSelectCallback(originalScope, {
3295
+ $item: item,
3296
+ $model: model,
3297
+ $label: parserResult.viewMapper(originalScope, locals)
3298
+ });
3299
+
3300
+ resetMatches();
3301
+
3302
+ //return focus to the input element if a mach was selected via a mouse click event
3303
+ element[0].focus();
3304
+ };
3305
+
3306
+ //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
3307
+ element.bind('keydown', function (evt) {
3308
+
3309
+ //typeahead is open and an "interesting" key was pressed
3310
+ if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
3311
+ return;
3312
+ }
3313
+
3314
+ evt.preventDefault();
3315
+
3316
+ if (evt.which === 40) {
3317
+ scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
3318
+ scope.$digest();
3319
+
3320
+ } else if (evt.which === 38) {
3321
+ scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1;
3322
+ scope.$digest();
3323
+
3324
+ } else if (evt.which === 13 || evt.which === 9) {
3325
+ scope.$apply(function () {
3326
+ scope.select(scope.activeIdx);
3327
+ });
3328
+
3329
+ } else if (evt.which === 27) {
3330
+ evt.stopPropagation();
3331
+
3332
+ resetMatches();
3333
+ scope.$digest();
3334
+ }
3335
+ });
3336
+
3337
+ element.bind('blur', function (evt) {
3338
+ hasFocus = false;
3339
+ });
3340
+
3341
+ // Keep reference to click handler to unbind it.
3342
+ var dismissClickHandler = function (evt) {
3343
+ if (element[0] !== evt.target) {
3344
+ resetMatches();
3345
+ scope.$digest();
3346
+ }
3347
+ };
3348
+
3349
+ $document.bind('click', dismissClickHandler);
3350
+
3351
+ originalScope.$on('$destroy', function(){
3352
+ $document.unbind('click', dismissClickHandler);
3353
+ });
3354
+
3355
+ var $popup = $compile(popUpEl)(scope);
3356
+ if ( appendToBody ) {
3357
+ $document.find('body').append($popup);
3358
+ } else {
3359
+ element.after($popup);
3360
+ }
3361
+ }
3362
+ };
3363
+
3364
+ }])
3365
+
3366
+ .directive('typeaheadPopup', function () {
3367
+ return {
3368
+ restrict:'EA',
3369
+ scope:{
3370
+ matches:'=',
3371
+ query:'=',
3372
+ active:'=',
3373
+ position:'=',
3374
+ select:'&'
3375
+ },
3376
+ replace:true,
3377
+ templateUrl:'template/typeahead/typeahead-popup.html',
3378
+ link:function (scope, element, attrs) {
3379
+
3380
+ scope.templateUrl = attrs.templateUrl;
3381
+
3382
+ scope.isOpen = function () {
3383
+ return scope.matches.length > 0;
3384
+ };
3385
+
3386
+ scope.isActive = function (matchIdx) {
3387
+ return scope.active == matchIdx;
3388
+ };
3389
+
3390
+ scope.selectActive = function (matchIdx) {
3391
+ scope.active = matchIdx;
3392
+ };
3393
+
3394
+ scope.selectMatch = function (activeIdx) {
3395
+ scope.select({activeIdx:activeIdx});
3396
+ };
3397
+ }
3398
+ };
3399
+ })
3400
+
3401
+ .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) {
3402
+ return {
3403
+ restrict:'EA',
3404
+ scope:{
3405
+ index:'=',
3406
+ match:'=',
3407
+ query:'='
3408
+ },
3409
+ link:function (scope, element, attrs) {
3410
+ var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html';
3411
+ $http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){
3412
+ element.replaceWith($compile(tplContent.trim())(scope));
3413
+ });
3414
+ }
3415
+ };
3416
+ }])
3417
+
3418
+ .filter('typeaheadHighlight', function() {
3419
+
3420
+ function escapeRegexp(queryToEscape) {
3421
+ return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
3422
+ }
3423
+
3424
+ return function(matchItem, query) {
3425
+ return query ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem;
3426
+ };
3427
+ });