rails-angular-strap 2.1.6 → 2.2.1

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 (194) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -10
  3. data/lib/rails-angular-strap/version.rb +1 -1
  4. data/vendor/assets/javascripts/angular-strap.js +1 -1
  5. data/vendor/assets/javascripts/angular-strap.min.js +1 -1
  6. data/vendor/assets/javascripts/angular-strap/angular-strap.js +1405 -1104
  7. data/vendor/assets/javascripts/angular-strap/angular-strap.min.js +4 -5
  8. data/vendor/assets/javascripts/angular-strap/angular-strap.tpl.js +15 -15
  9. data/vendor/assets/javascripts/angular-strap/angular-strap.tpl.min.js +2 -2
  10. metadata +2 -192
  11. data/vendor/assets/javascripts/angular-strap-unstable.js +0 -2
  12. data/vendor/assets/javascripts/angular-strap-unstable.min.js +0 -2
  13. data/vendor/assets/javascripts/angular-strap/angular-strap.min.js.map +0 -1
  14. data/vendor/assets/javascripts/angular-strap/modules/affix.js +0 -249
  15. data/vendor/assets/javascripts/angular-strap/modules/affix.min.js +0 -9
  16. data/vendor/assets/javascripts/angular-strap/modules/affix.min.js.map +0 -1
  17. data/vendor/assets/javascripts/angular-strap/modules/alert.js +0 -120
  18. data/vendor/assets/javascripts/angular-strap/modules/alert.min.js +0 -9
  19. data/vendor/assets/javascripts/angular-strap/modules/alert.min.js.map +0 -1
  20. data/vendor/assets/javascripts/angular-strap/modules/alert.tpl.js +0 -14
  21. data/vendor/assets/javascripts/angular-strap/modules/alert.tpl.min.js +0 -8
  22. data/vendor/assets/javascripts/angular-strap/modules/aside.js +0 -96
  23. data/vendor/assets/javascripts/angular-strap/modules/aside.min.js +0 -9
  24. data/vendor/assets/javascripts/angular-strap/modules/aside.min.js.map +0 -1
  25. data/vendor/assets/javascripts/angular-strap/modules/aside.tpl.js +0 -14
  26. data/vendor/assets/javascripts/angular-strap/modules/aside.tpl.min.js +0 -8
  27. data/vendor/assets/javascripts/angular-strap/modules/button.js +0 -177
  28. data/vendor/assets/javascripts/angular-strap/modules/button.min.js +0 -9
  29. data/vendor/assets/javascripts/angular-strap/modules/button.min.js.map +0 -1
  30. data/vendor/assets/javascripts/angular-strap/modules/collapse.js +0 -273
  31. data/vendor/assets/javascripts/angular-strap/modules/collapse.min.js +0 -9
  32. data/vendor/assets/javascripts/angular-strap/modules/collapse.min.js.map +0 -1
  33. data/vendor/assets/javascripts/angular-strap/modules/date-formatter.js +0 -61
  34. data/vendor/assets/javascripts/angular-strap/modules/date-formatter.min.js +0 -9
  35. data/vendor/assets/javascripts/angular-strap/modules/date-formatter.min.js.map +0 -1
  36. data/vendor/assets/javascripts/angular-strap/modules/date-parser.js +0 -273
  37. data/vendor/assets/javascripts/angular-strap/modules/date-parser.min.js +0 -9
  38. data/vendor/assets/javascripts/angular-strap/modules/date-parser.min.js.map +0 -1
  39. data/vendor/assets/javascripts/angular-strap/modules/datepicker.js +0 -640
  40. data/vendor/assets/javascripts/angular-strap/modules/datepicker.min.js +0 -9
  41. data/vendor/assets/javascripts/angular-strap/modules/datepicker.min.js.map +0 -1
  42. data/vendor/assets/javascripts/angular-strap/modules/datepicker.tpl.js +0 -14
  43. data/vendor/assets/javascripts/angular-strap/modules/datepicker.tpl.min.js +0 -8
  44. data/vendor/assets/javascripts/angular-strap/modules/debounce.js +0 -62
  45. data/vendor/assets/javascripts/angular-strap/modules/debounce.min.js +0 -9
  46. data/vendor/assets/javascripts/angular-strap/modules/debounce.min.js.map +0 -1
  47. data/vendor/assets/javascripts/angular-strap/modules/dimensions.js +0 -156
  48. data/vendor/assets/javascripts/angular-strap/modules/dimensions.min.js +0 -9
  49. data/vendor/assets/javascripts/angular-strap/modules/dimensions.min.js.map +0 -1
  50. data/vendor/assets/javascripts/angular-strap/modules/dropdown.js +0 -149
  51. data/vendor/assets/javascripts/angular-strap/modules/dropdown.min.js +0 -9
  52. data/vendor/assets/javascripts/angular-strap/modules/dropdown.min.js.map +0 -1
  53. data/vendor/assets/javascripts/angular-strap/modules/dropdown.tpl.js +0 -14
  54. data/vendor/assets/javascripts/angular-strap/modules/dropdown.tpl.min.js +0 -8
  55. data/vendor/assets/javascripts/angular-strap/modules/modal.js +0 -349
  56. data/vendor/assets/javascripts/angular-strap/modules/modal.min.js +0 -9
  57. data/vendor/assets/javascripts/angular-strap/modules/modal.min.js.map +0 -1
  58. data/vendor/assets/javascripts/angular-strap/modules/modal.tpl.js +0 -14
  59. data/vendor/assets/javascripts/angular-strap/modules/modal.tpl.min.js +0 -8
  60. data/vendor/assets/javascripts/angular-strap/modules/navbar.js +0 -72
  61. data/vendor/assets/javascripts/angular-strap/modules/navbar.min.js +0 -9
  62. data/vendor/assets/javascripts/angular-strap/modules/navbar.min.js.map +0 -1
  63. data/vendor/assets/javascripts/angular-strap/modules/parse-options.js +0 -76
  64. data/vendor/assets/javascripts/angular-strap/modules/parse-options.min.js +0 -9
  65. data/vendor/assets/javascripts/angular-strap/modules/parse-options.min.js.map +0 -1
  66. data/vendor/assets/javascripts/angular-strap/modules/popover.js +0 -112
  67. data/vendor/assets/javascripts/angular-strap/modules/popover.min.js +0 -9
  68. data/vendor/assets/javascripts/angular-strap/modules/popover.min.js.map +0 -1
  69. data/vendor/assets/javascripts/angular-strap/modules/popover.tpl.js +0 -14
  70. data/vendor/assets/javascripts/angular-strap/modules/popover.tpl.min.js +0 -8
  71. data/vendor/assets/javascripts/angular-strap/modules/raf.js +0 -61
  72. data/vendor/assets/javascripts/angular-strap/modules/raf.min.js +0 -9
  73. data/vendor/assets/javascripts/angular-strap/modules/raf.min.js.map +0 -1
  74. data/vendor/assets/javascripts/angular-strap/modules/scrollspy.js +0 -261
  75. data/vendor/assets/javascripts/angular-strap/modules/scrollspy.min.js +0 -9
  76. data/vendor/assets/javascripts/angular-strap/modules/scrollspy.min.js.map +0 -1
  77. data/vendor/assets/javascripts/angular-strap/modules/select.js +0 -325
  78. data/vendor/assets/javascripts/angular-strap/modules/select.min.js +0 -9
  79. data/vendor/assets/javascripts/angular-strap/modules/select.min.js.map +0 -1
  80. data/vendor/assets/javascripts/angular-strap/modules/select.tpl.js +0 -14
  81. data/vendor/assets/javascripts/angular-strap/modules/select.tpl.min.js +0 -8
  82. data/vendor/assets/javascripts/angular-strap/modules/tab.js +0 -186
  83. data/vendor/assets/javascripts/angular-strap/modules/tab.min.js +0 -9
  84. data/vendor/assets/javascripts/angular-strap/modules/tab.min.js.map +0 -1
  85. data/vendor/assets/javascripts/angular-strap/modules/tab.tpl.js +0 -14
  86. data/vendor/assets/javascripts/angular-strap/modules/tab.tpl.min.js +0 -8
  87. data/vendor/assets/javascripts/angular-strap/modules/timepicker.js +0 -485
  88. data/vendor/assets/javascripts/angular-strap/modules/timepicker.min.js +0 -9
  89. data/vendor/assets/javascripts/angular-strap/modules/timepicker.min.js.map +0 -1
  90. data/vendor/assets/javascripts/angular-strap/modules/timepicker.tpl.js +0 -14
  91. data/vendor/assets/javascripts/angular-strap/modules/timepicker.tpl.min.js +0 -8
  92. data/vendor/assets/javascripts/angular-strap/modules/tooltip.js +0 -690
  93. data/vendor/assets/javascripts/angular-strap/modules/tooltip.min.js +0 -9
  94. data/vendor/assets/javascripts/angular-strap/modules/tooltip.min.js.map +0 -1
  95. data/vendor/assets/javascripts/angular-strap/modules/tooltip.tpl.js +0 -14
  96. data/vendor/assets/javascripts/angular-strap/modules/tooltip.tpl.min.js +0 -8
  97. data/vendor/assets/javascripts/angular-strap/modules/typeahead.js +0 -266
  98. data/vendor/assets/javascripts/angular-strap/modules/typeahead.min.js +0 -9
  99. data/vendor/assets/javascripts/angular-strap/modules/typeahead.min.js.map +0 -1
  100. data/vendor/assets/javascripts/angular-strap/modules/typeahead.tpl.js +0 -14
  101. data/vendor/assets/javascripts/angular-strap/modules/typeahead.tpl.min.js +0 -8
  102. data/vendor/assets/javascripts/angular-strap/unstable/angular-strap.js +0 -5040
  103. data/vendor/assets/javascripts/angular-strap/unstable/angular-strap.min.js +0 -11
  104. data/vendor/assets/javascripts/angular-strap/unstable/angular-strap.min.js.map +0 -1
  105. data/vendor/assets/javascripts/angular-strap/unstable/angular-strap.tpl.js +0 -89
  106. data/vendor/assets/javascripts/angular-strap/unstable/angular-strap.tpl.min.js +0 -8
  107. data/vendor/assets/javascripts/angular-strap/unstable/modules/affix.js +0 -249
  108. data/vendor/assets/javascripts/angular-strap/unstable/modules/affix.min.js +0 -9
  109. data/vendor/assets/javascripts/angular-strap/unstable/modules/affix.min.js.map +0 -1
  110. data/vendor/assets/javascripts/angular-strap/unstable/modules/alert.js +0 -120
  111. data/vendor/assets/javascripts/angular-strap/unstable/modules/alert.min.js +0 -9
  112. data/vendor/assets/javascripts/angular-strap/unstable/modules/alert.min.js.map +0 -1
  113. data/vendor/assets/javascripts/angular-strap/unstable/modules/alert.tpl.js +0 -14
  114. data/vendor/assets/javascripts/angular-strap/unstable/modules/alert.tpl.min.js +0 -8
  115. data/vendor/assets/javascripts/angular-strap/unstable/modules/aside.js +0 -96
  116. data/vendor/assets/javascripts/angular-strap/unstable/modules/aside.min.js +0 -9
  117. data/vendor/assets/javascripts/angular-strap/unstable/modules/aside.min.js.map +0 -1
  118. data/vendor/assets/javascripts/angular-strap/unstable/modules/aside.tpl.js +0 -14
  119. data/vendor/assets/javascripts/angular-strap/unstable/modules/aside.tpl.min.js +0 -8
  120. data/vendor/assets/javascripts/angular-strap/unstable/modules/button.js +0 -177
  121. data/vendor/assets/javascripts/angular-strap/unstable/modules/button.min.js +0 -9
  122. data/vendor/assets/javascripts/angular-strap/unstable/modules/button.min.js.map +0 -1
  123. data/vendor/assets/javascripts/angular-strap/unstable/modules/collapse.js +0 -273
  124. data/vendor/assets/javascripts/angular-strap/unstable/modules/collapse.min.js +0 -9
  125. data/vendor/assets/javascripts/angular-strap/unstable/modules/collapse.min.js.map +0 -1
  126. data/vendor/assets/javascripts/angular-strap/unstable/modules/date-formatter.js +0 -61
  127. data/vendor/assets/javascripts/angular-strap/unstable/modules/date-formatter.min.js +0 -9
  128. data/vendor/assets/javascripts/angular-strap/unstable/modules/date-formatter.min.js.map +0 -1
  129. data/vendor/assets/javascripts/angular-strap/unstable/modules/date-parser.js +0 -273
  130. data/vendor/assets/javascripts/angular-strap/unstable/modules/date-parser.min.js +0 -9
  131. data/vendor/assets/javascripts/angular-strap/unstable/modules/date-parser.min.js.map +0 -1
  132. data/vendor/assets/javascripts/angular-strap/unstable/modules/datepicker.js +0 -640
  133. data/vendor/assets/javascripts/angular-strap/unstable/modules/datepicker.min.js +0 -9
  134. data/vendor/assets/javascripts/angular-strap/unstable/modules/datepicker.min.js.map +0 -1
  135. data/vendor/assets/javascripts/angular-strap/unstable/modules/datepicker.tpl.js +0 -14
  136. data/vendor/assets/javascripts/angular-strap/unstable/modules/datepicker.tpl.min.js +0 -8
  137. data/vendor/assets/javascripts/angular-strap/unstable/modules/debounce.js +0 -62
  138. data/vendor/assets/javascripts/angular-strap/unstable/modules/debounce.min.js +0 -9
  139. data/vendor/assets/javascripts/angular-strap/unstable/modules/debounce.min.js.map +0 -1
  140. data/vendor/assets/javascripts/angular-strap/unstable/modules/dimensions.js +0 -156
  141. data/vendor/assets/javascripts/angular-strap/unstable/modules/dimensions.min.js +0 -9
  142. data/vendor/assets/javascripts/angular-strap/unstable/modules/dimensions.min.js.map +0 -1
  143. data/vendor/assets/javascripts/angular-strap/unstable/modules/dropdown.js +0 -149
  144. data/vendor/assets/javascripts/angular-strap/unstable/modules/dropdown.min.js +0 -9
  145. data/vendor/assets/javascripts/angular-strap/unstable/modules/dropdown.min.js.map +0 -1
  146. data/vendor/assets/javascripts/angular-strap/unstable/modules/dropdown.tpl.js +0 -14
  147. data/vendor/assets/javascripts/angular-strap/unstable/modules/dropdown.tpl.min.js +0 -8
  148. data/vendor/assets/javascripts/angular-strap/unstable/modules/modal.js +0 -357
  149. data/vendor/assets/javascripts/angular-strap/unstable/modules/modal.min.js +0 -9
  150. data/vendor/assets/javascripts/angular-strap/unstable/modules/modal.min.js.map +0 -1
  151. data/vendor/assets/javascripts/angular-strap/unstable/modules/modal.tpl.js +0 -14
  152. data/vendor/assets/javascripts/angular-strap/unstable/modules/modal.tpl.min.js +0 -8
  153. data/vendor/assets/javascripts/angular-strap/unstable/modules/navbar.js +0 -72
  154. data/vendor/assets/javascripts/angular-strap/unstable/modules/navbar.min.js +0 -9
  155. data/vendor/assets/javascripts/angular-strap/unstable/modules/navbar.min.js.map +0 -1
  156. data/vendor/assets/javascripts/angular-strap/unstable/modules/parse-options.js +0 -76
  157. data/vendor/assets/javascripts/angular-strap/unstable/modules/parse-options.min.js +0 -9
  158. data/vendor/assets/javascripts/angular-strap/unstable/modules/parse-options.min.js.map +0 -1
  159. data/vendor/assets/javascripts/angular-strap/unstable/modules/popover.js +0 -112
  160. data/vendor/assets/javascripts/angular-strap/unstable/modules/popover.min.js +0 -9
  161. data/vendor/assets/javascripts/angular-strap/unstable/modules/popover.min.js.map +0 -1
  162. data/vendor/assets/javascripts/angular-strap/unstable/modules/popover.tpl.js +0 -14
  163. data/vendor/assets/javascripts/angular-strap/unstable/modules/popover.tpl.min.js +0 -8
  164. data/vendor/assets/javascripts/angular-strap/unstable/modules/raf.js +0 -61
  165. data/vendor/assets/javascripts/angular-strap/unstable/modules/raf.min.js +0 -9
  166. data/vendor/assets/javascripts/angular-strap/unstable/modules/raf.min.js.map +0 -1
  167. data/vendor/assets/javascripts/angular-strap/unstable/modules/scrollspy.js +0 -261
  168. data/vendor/assets/javascripts/angular-strap/unstable/modules/scrollspy.min.js +0 -9
  169. data/vendor/assets/javascripts/angular-strap/unstable/modules/scrollspy.min.js.map +0 -1
  170. data/vendor/assets/javascripts/angular-strap/unstable/modules/select.js +0 -328
  171. data/vendor/assets/javascripts/angular-strap/unstable/modules/select.min.js +0 -9
  172. data/vendor/assets/javascripts/angular-strap/unstable/modules/select.min.js.map +0 -1
  173. data/vendor/assets/javascripts/angular-strap/unstable/modules/select.tpl.js +0 -14
  174. data/vendor/assets/javascripts/angular-strap/unstable/modules/select.tpl.min.js +0 -8
  175. data/vendor/assets/javascripts/angular-strap/unstable/modules/tab.js +0 -186
  176. data/vendor/assets/javascripts/angular-strap/unstable/modules/tab.min.js +0 -9
  177. data/vendor/assets/javascripts/angular-strap/unstable/modules/tab.min.js.map +0 -1
  178. data/vendor/assets/javascripts/angular-strap/unstable/modules/tab.tpl.js +0 -14
  179. data/vendor/assets/javascripts/angular-strap/unstable/modules/tab.tpl.min.js +0 -8
  180. data/vendor/assets/javascripts/angular-strap/unstable/modules/timepicker.js +0 -485
  181. data/vendor/assets/javascripts/angular-strap/unstable/modules/timepicker.min.js +0 -9
  182. data/vendor/assets/javascripts/angular-strap/unstable/modules/timepicker.min.js.map +0 -1
  183. data/vendor/assets/javascripts/angular-strap/unstable/modules/timepicker.tpl.js +0 -14
  184. data/vendor/assets/javascripts/angular-strap/unstable/modules/timepicker.tpl.min.js +0 -8
  185. data/vendor/assets/javascripts/angular-strap/unstable/modules/tooltip.js +0 -705
  186. data/vendor/assets/javascripts/angular-strap/unstable/modules/tooltip.min.js +0 -9
  187. data/vendor/assets/javascripts/angular-strap/unstable/modules/tooltip.min.js.map +0 -1
  188. data/vendor/assets/javascripts/angular-strap/unstable/modules/tooltip.tpl.js +0 -14
  189. data/vendor/assets/javascripts/angular-strap/unstable/modules/tooltip.tpl.min.js +0 -8
  190. data/vendor/assets/javascripts/angular-strap/unstable/modules/typeahead.js +0 -266
  191. data/vendor/assets/javascripts/angular-strap/unstable/modules/typeahead.min.js +0 -9
  192. data/vendor/assets/javascripts/angular-strap/unstable/modules/typeahead.min.js.map +0 -1
  193. data/vendor/assets/javascripts/angular-strap/unstable/modules/typeahead.tpl.js +0 -14
  194. data/vendor/assets/javascripts/angular-strap/unstable/modules/typeahead.tpl.min.js +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ae1c2d1f1258ca0f790c5c1fd0a4a43aa2480d04
4
- data.tar.gz: 79741d67d5c33f5b1d82f0d7941f51b44c525447
3
+ metadata.gz: fa51e74ad59cb6027fc917a6258aecd3f9a25edc
4
+ data.tar.gz: 1c12640cf47c9e1b71be35956265639aa0eb359f
5
5
  SHA512:
6
- metadata.gz: 5db09fd7cafafd1bd1df6ae67f2e1d62ad80b8110aab8a099edaa2706e3baba55cb0e576af9cc5fb210e7ea351d732c7b720e9b6d970328dd77fb4377e6e8566
7
- data.tar.gz: 4922cbbeabb74ba44d603c751bf70845d8bdd66934919133c4115c943bc866870f3c30c69ac528b933ae293e896a8c121f85633ceff902c91ff4597e77031356
6
+ metadata.gz: 0f8a7c918abd295b8d188415e20459a51e8be3a106ca9f78b60fb7bf3edbe32ea68db280c312853a70686742711958a65cfc0924b00e0cf05f29ed678e6f884e
7
+ data.tar.gz: 186f9a746746d59d2f909847fe9096f16966cf3d15ba5d6533be254fe0b8eb3a075d0a73691296ead381ac450091cf7cc830b40a621224e38f0bf00b5cb9ab5e
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # rails-angular-strap
1
+ # rails-angular-strap <a href="http://badge.fury.io/rb/rails-angular-strap"><img src="https://badge.fury.io/rb/rails-angular-strap.svg" alt="Gem Version" height="18"></a>
2
2
 
3
3
  rails-angular-strap wraps the [AngularStrap](http://mgcrea.github.io/angular-strap/) framework for use in Rails 3.1 and above.
4
4
 
@@ -16,14 +16,6 @@ If you desire to require minified AngularStrap files, add the following:
16
16
 
17
17
  //= require angular-strap.min
18
18
 
19
- Also there are unstable(snapshot) version of AngularStrap, just add the following:
20
-
21
- //= require angular-strap-unstable
22
-
23
- or, minified version:
24
-
25
- //= require angular-strap-unstable.min
26
-
27
19
  ## Versioning
28
20
 
29
- Current version of AngularStrap - 2.1.6
21
+ Current version of AngularStrap - 2.2.1
@@ -1,3 +1,3 @@
1
1
  module AngularStrapRails
2
- VERSION = "2.1.6"
2
+ VERSION = "2.2.1"
3
3
  end
@@ -1,2 +1,2 @@
1
1
  //= require ./angular-strap/angular-strap.js
2
- //= require ./angular-strap/angular-strap.tpl.js
2
+ //= require ./angular-strap/angular-strap.tpl.js
@@ -1,2 +1,2 @@
1
1
  //= require ./angular-strap/angular-strap.min.js
2
- //= require ./angular-strap/angular-strap.tpl.min.js
2
+ //= require ./angular-strap/angular-strap.tpl.min.js
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * angular-strap
3
- * @version v2.1.6 - 2015-01-11
3
+ * @version v2.2.1 - 2015-03-10
4
4
  * @link http://mgcrea.github.io/angular-strap
5
5
  * @author Olivier Louvignes (olivier@mg-crea.com)
6
6
  * @license MIT License, http://www.opensource.org/licenses/MIT
@@ -33,7 +33,8 @@ angular.module('mgcrea.ngStrap.affix', ['mgcrea.ngStrap.helpers.dimensions', 'mg
33
33
  .provider('$affix', function() {
34
34
 
35
35
  var defaults = this.defaults = {
36
- offsetTop: 'auto'
36
+ offsetTop: 'auto',
37
+ inlineStyles: true
37
38
  };
38
39
 
39
40
  this.$get = ["$window", "debounce", "dimensions", function($window, debounce, dimensions) {
@@ -126,11 +127,13 @@ angular.module('mgcrea.ngStrap.affix', ['mgcrea.ngStrap.helpers.dimensions', 'mg
126
127
 
127
128
  if(affix === 'top') {
128
129
  unpin = null;
129
- element.css('position', (options.offsetParent) ? '' : 'relative');
130
130
  if(setWidth) {
131
131
  element.css('width', '');
132
132
  }
133
- element.css('top', '');
133
+ if (options.inlineStyles) {
134
+ element.css('position', (options.offsetParent) ? '' : 'relative');
135
+ element.css('top', '');
136
+ }
134
137
  } else if(affix === 'bottom') {
135
138
  if (options.offsetUnpin) {
136
139
  unpin = -(options.offsetUnpin * 1);
@@ -143,15 +146,19 @@ angular.module('mgcrea.ngStrap.affix', ['mgcrea.ngStrap.helpers.dimensions', 'mg
143
146
  if(setWidth) {
144
147
  element.css('width', '');
145
148
  }
146
- element.css('position', (options.offsetParent) ? '' : 'relative');
147
- element.css('top', (options.offsetParent) ? '' : ((bodyEl[0].offsetHeight - offsetBottom - elementHeight - initialOffsetTop) + 'px'));
149
+ if (options.inlineStyles) {
150
+ element.css('position', (options.offsetParent) ? '' : 'relative');
151
+ element.css('top', (options.offsetParent) ? '' : ((bodyEl[0].offsetHeight - offsetBottom - elementHeight - initialOffsetTop) + 'px'));
152
+ }
148
153
  } else { // affix === 'middle'
149
154
  unpin = null;
150
155
  if(setWidth) {
151
156
  element.css('width', element[0].offsetWidth + 'px');
152
157
  }
153
- element.css('position', 'fixed');
154
- element.css('top', initialAffixTop + 'px');
158
+ if (options.inlineStyles) {
159
+ element.css('position', 'fixed');
160
+ element.css('top', initialAffixTop + 'px');
161
+ }
155
162
  }
156
163
 
157
164
  };
@@ -165,7 +172,9 @@ angular.module('mgcrea.ngStrap.affix', ['mgcrea.ngStrap.helpers.dimensions', 'mg
165
172
  $affix.$parseOffsets = function() {
166
173
  var initialPosition = element.css('position');
167
174
  // Reset position to calculate correct offsetTop
168
- element.css('position', (options.offsetParent) ? '' : 'relative');
175
+ if (options.inlineStyles){
176
+ element.css('position', (options.offsetParent) ? '' : 'relative');
177
+ }
169
178
 
170
179
  if(options.offsetTop) {
171
180
  if(options.offsetTop === 'auto') {
@@ -196,7 +205,9 @@ angular.module('mgcrea.ngStrap.affix', ['mgcrea.ngStrap.helpers.dimensions', 'mg
196
205
  }
197
206
 
198
207
  // Bring back the element's position after calculations
199
- element.css('position', initialPosition);
208
+ if (options.inlineStyles){
209
+ element.css('position', initialPosition);
210
+ }
200
211
  };
201
212
 
202
213
  // Private methods
@@ -244,9 +255,14 @@ angular.module('mgcrea.ngStrap.affix', ['mgcrea.ngStrap.helpers.dimensions', 'mg
244
255
  require: '^?bsAffixTarget',
245
256
  link: function postLink(scope, element, attr, affixTarget) {
246
257
 
247
- var options = {scope: scope, offsetTop: 'auto', target: affixTarget ? affixTarget.$element : angular.element($window)};
248
- angular.forEach(['offsetTop', 'offsetBottom', 'offsetParent', 'offsetUnpin'], function(key) {
249
- if(angular.isDefined(attr[key])) options[key] = attr[key];
258
+ var options = {scope: scope, target: affixTarget ? affixTarget.$element : angular.element($window)};
259
+ angular.forEach(['offsetTop', 'offsetBottom', 'offsetParent', 'offsetUnpin', 'inlineStyles'], function(key) {
260
+ if(angular.isDefined(attr[key])) {
261
+ var option = attr[key];
262
+ if (/true/i.test(option)) option = true;
263
+ if (/false/i.test(option)) option = false;
264
+ options[key] = option;
265
+ }
250
266
  });
251
267
 
252
268
  var affix = $affix(element, options);
@@ -269,71 +285,48 @@ angular.module('mgcrea.ngStrap.affix', ['mgcrea.ngStrap.helpers.dimensions', 'mg
269
285
  };
270
286
  });
271
287
 
272
- // Source: alert.js
273
- // @BUG: following snippet won't compile correctly
274
- // @TODO: submit issue to core
275
- // '<span ng-if="title"><strong ng-bind="title"></strong>&nbsp;</span><span ng-bind-html="content"></span>' +
276
-
277
- angular.module('mgcrea.ngStrap.alert', ['mgcrea.ngStrap.modal'])
288
+ // Source: aside.js
289
+ angular.module('mgcrea.ngStrap.aside', ['mgcrea.ngStrap.modal'])
278
290
 
279
- .provider('$alert', function() {
291
+ .provider('$aside', function() {
280
292
 
281
293
  var defaults = this.defaults = {
282
- animation: 'am-fade',
283
- prefixClass: 'alert',
284
- prefixEvent: 'alert',
285
- placement: null,
286
- template: 'alert/alert.tpl.html',
294
+ animation: 'am-fade-and-slide-right',
295
+ prefixClass: 'aside',
296
+ prefixEvent: 'aside',
297
+ placement: 'right',
298
+ template: 'aside/aside.tpl.html',
299
+ contentTemplate: false,
287
300
  container: false,
288
301
  element: null,
289
- backdrop: false,
302
+ backdrop: true,
290
303
  keyboard: true,
291
- show: true,
292
- // Specific options
293
- duration: false,
294
- type: false,
295
- dismissable: true
304
+ html: false,
305
+ show: true
296
306
  };
297
307
 
298
- this.$get = ["$modal", "$timeout", function($modal, $timeout) {
308
+ this.$get = ["$modal", function($modal) {
299
309
 
300
- function AlertFactory(config) {
310
+ function AsideFactory(config) {
301
311
 
302
- var $alert = {};
312
+ var $aside = {};
303
313
 
304
314
  // Common vars
305
315
  var options = angular.extend({}, defaults, config);
306
316
 
307
- $alert = $modal(options);
308
-
309
- // Support scope as string options [/*title, content, */ type, dismissable]
310
- $alert.$scope.dismissable = !!options.dismissable;
311
- if(options.type) {
312
- $alert.$scope.type = options.type;
313
- }
314
-
315
- // Support auto-close duration
316
- var show = $alert.show;
317
- if(options.duration) {
318
- $alert.show = function() {
319
- show();
320
- $timeout(function() {
321
- $alert.hide();
322
- }, options.duration * 1000);
323
- };
324
- }
317
+ $aside = $modal(options);
325
318
 
326
- return $alert;
319
+ return $aside;
327
320
 
328
321
  }
329
322
 
330
- return AlertFactory;
323
+ return AsideFactory;
331
324
 
332
325
  }];
333
326
 
334
327
  })
335
328
 
336
- .directive('bsAlert', ["$window", "$sce", "$alert", function($window, $sce, $alert) {
329
+ .directive('bsAside', ["$window", "$sce", "$aside", function($window, $sce, $aside) {
337
330
 
338
331
  var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
339
332
 
@@ -341,22 +334,21 @@ angular.module('mgcrea.ngStrap.alert', ['mgcrea.ngStrap.modal'])
341
334
  restrict: 'EAC',
342
335
  scope: true,
343
336
  link: function postLink(scope, element, attr, transclusion) {
344
-
345
337
  // Directive options
346
338
  var options = {scope: scope, element: element, show: false};
347
- angular.forEach(['template', 'placement', 'keyboard', 'html', 'container', 'animation', 'duration', 'dismissable'], function(key) {
339
+ angular.forEach(['template', 'contentTemplate', 'placement', 'backdrop', 'keyboard', 'html', 'container', 'animation'], function(key) {
348
340
  if(angular.isDefined(attr[key])) options[key] = attr[key];
349
341
  });
350
342
 
351
343
  // Support scope as data-attrs
352
- angular.forEach(['title', 'content', 'type'], function(key) {
344
+ angular.forEach(['title', 'content'], function(key) {
353
345
  attr[key] && attr.$observe(key, function(newValue, oldValue) {
354
346
  scope[key] = $sce.trustAsHtml(newValue);
355
347
  });
356
348
  });
357
349
 
358
350
  // Support scope as an object
359
- attr.bsAlert && scope.$watch(attr.bsAlert, function(newValue, oldValue) {
351
+ attr.bsAside && scope.$watch(attr.bsAside, function(newValue, oldValue) {
360
352
  if(angular.isObject(newValue)) {
361
353
  angular.extend(scope, newValue);
362
354
  } else {
@@ -364,17 +356,17 @@ angular.module('mgcrea.ngStrap.alert', ['mgcrea.ngStrap.modal'])
364
356
  }
365
357
  }, true);
366
358
 
367
- // Initialize alert
368
- var alert = $alert(options);
359
+ // Initialize aside
360
+ var aside = $aside(options);
369
361
 
370
362
  // Trigger
371
- element.on(attr.trigger || 'click', alert.toggle);
363
+ element.on(attr.trigger || 'click', aside.toggle);
372
364
 
373
365
  // Garbage collection
374
366
  scope.$on('$destroy', function() {
375
- if (alert) alert.destroy();
367
+ if (aside) aside.destroy();
376
368
  options = null;
377
- alert = null;
369
+ aside = null;
378
370
  });
379
371
 
380
372
  }
@@ -382,48 +374,71 @@ angular.module('mgcrea.ngStrap.alert', ['mgcrea.ngStrap.modal'])
382
374
 
383
375
  }]);
384
376
 
385
- // Source: aside.js
386
- angular.module('mgcrea.ngStrap.aside', ['mgcrea.ngStrap.modal'])
377
+ // Source: alert.js
378
+ // @BUG: following snippet won't compile correctly
379
+ // @TODO: submit issue to core
380
+ // '<span ng-if="title"><strong ng-bind="title"></strong>&nbsp;</span><span ng-bind-html="content"></span>' +
387
381
 
388
- .provider('$aside', function() {
382
+ angular.module('mgcrea.ngStrap.alert', ['mgcrea.ngStrap.modal'])
383
+
384
+ .provider('$alert', function() {
389
385
 
390
386
  var defaults = this.defaults = {
391
- animation: 'am-fade-and-slide-right',
392
- prefixClass: 'aside',
393
- prefixEvent: 'aside',
394
- placement: 'right',
395
- template: 'aside/aside.tpl.html',
396
- contentTemplate: false,
387
+ animation: 'am-fade',
388
+ prefixClass: 'alert',
389
+ prefixEvent: 'alert',
390
+ placement: null,
391
+ template: 'alert/alert.tpl.html',
397
392
  container: false,
398
393
  element: null,
399
- backdrop: true,
394
+ backdrop: false,
400
395
  keyboard: true,
401
- html: false,
402
- show: true
396
+ show: true,
397
+ // Specific options
398
+ duration: false,
399
+ type: false,
400
+ dismissable: true
403
401
  };
404
402
 
405
- this.$get = ["$modal", function($modal) {
403
+ this.$get = ["$modal", "$timeout", function($modal, $timeout) {
406
404
 
407
- function AsideFactory(config) {
405
+ function AlertFactory(config) {
408
406
 
409
- var $aside = {};
407
+ var $alert = {};
410
408
 
411
409
  // Common vars
412
410
  var options = angular.extend({}, defaults, config);
413
411
 
414
- $aside = $modal(options);
412
+ $alert = $modal(options);
415
413
 
416
- return $aside;
414
+ // Support scope as string options [/*title, content, */ type, dismissable]
415
+ $alert.$scope.dismissable = !!options.dismissable;
416
+ if(options.type) {
417
+ $alert.$scope.type = options.type;
418
+ }
419
+
420
+ // Support auto-close duration
421
+ var show = $alert.show;
422
+ if(options.duration) {
423
+ $alert.show = function() {
424
+ show();
425
+ $timeout(function() {
426
+ $alert.hide();
427
+ }, options.duration * 1000);
428
+ };
429
+ }
430
+
431
+ return $alert;
417
432
 
418
433
  }
419
434
 
420
- return AsideFactory;
435
+ return AlertFactory;
421
436
 
422
437
  }];
423
438
 
424
439
  })
425
440
 
426
- .directive('bsAside', ["$window", "$sce", "$aside", function($window, $sce, $aside) {
441
+ .directive('bsAlert', ["$window", "$sce", "$alert", function($window, $sce, $alert) {
427
442
 
428
443
  var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
429
444
 
@@ -431,21 +446,28 @@ angular.module('mgcrea.ngStrap.aside', ['mgcrea.ngStrap.modal'])
431
446
  restrict: 'EAC',
432
447
  scope: true,
433
448
  link: function postLink(scope, element, attr, transclusion) {
449
+
434
450
  // Directive options
435
451
  var options = {scope: scope, element: element, show: false};
436
- angular.forEach(['template', 'contentTemplate', 'placement', 'backdrop', 'keyboard', 'html', 'container', 'animation'], function(key) {
452
+ angular.forEach(['template', 'placement', 'keyboard', 'html', 'container', 'animation', 'duration', 'dismissable'], function(key) {
437
453
  if(angular.isDefined(attr[key])) options[key] = attr[key];
438
454
  });
439
455
 
456
+ // overwrite inherited title value when no value specified
457
+ // fix for angular 1.3.1 531a8de72c439d8ddd064874bf364c00cedabb11
458
+ if (!scope.hasOwnProperty('title')){
459
+ scope.title = '';
460
+ }
461
+
440
462
  // Support scope as data-attrs
441
- angular.forEach(['title', 'content'], function(key) {
463
+ angular.forEach(['title', 'content', 'type'], function(key) {
442
464
  attr[key] && attr.$observe(key, function(newValue, oldValue) {
443
465
  scope[key] = $sce.trustAsHtml(newValue);
444
466
  });
445
467
  });
446
468
 
447
469
  // Support scope as an object
448
- attr.bsAside && scope.$watch(attr.bsAside, function(newValue, oldValue) {
470
+ attr.bsAlert && scope.$watch(attr.bsAlert, function(newValue, oldValue) {
449
471
  if(angular.isObject(newValue)) {
450
472
  angular.extend(scope, newValue);
451
473
  } else {
@@ -453,17 +475,17 @@ angular.module('mgcrea.ngStrap.aside', ['mgcrea.ngStrap.modal'])
453
475
  }
454
476
  }, true);
455
477
 
456
- // Initialize aside
457
- var aside = $aside(options);
478
+ // Initialize alert
479
+ var alert = $alert(options);
458
480
 
459
481
  // Trigger
460
- element.on(attr.trigger || 'click', aside.toggle);
482
+ element.on(attr.trigger || 'click', alert.toggle);
461
483
 
462
484
  // Garbage collection
463
485
  scope.$on('$destroy', function() {
464
- if (aside) aside.destroy();
486
+ if (alert) alert.destroy();
465
487
  options = null;
466
- aside = null;
488
+ alert = null;
467
489
  });
468
490
 
469
491
  }
@@ -614,7 +636,11 @@ angular.module('mgcrea.ngStrap.button', [])
614
636
  var isInput = element[0].nodeName === 'INPUT';
615
637
  var activeElement = isInput ? element.parent() : element;
616
638
 
617
- var value = constantValueRegExp.test(attr.value) ? scope.$eval(attr.value) : attr.value;
639
+ var value;
640
+ attr.$observe('value', function(v) {
641
+ value = constantValueRegExp.test(v) ? scope.$eval(v) : v;
642
+ controller.$render();
643
+ });
618
644
 
619
645
  // model -> view
620
646
  controller.$render = function () {
@@ -929,6 +955,7 @@ angular.module('mgcrea.ngStrap.datepicker', [
929
955
  useNative: false,
930
956
  dateType: 'date',
931
957
  dateFormat: 'shortDate',
958
+ timezone: null,
932
959
  modelDateFormat: null,
933
960
  dayFormat: 'dd',
934
961
  monthFormat: 'MMM',
@@ -1173,7 +1200,7 @@ angular.module('mgcrea.ngStrap.datepicker', [
1173
1200
 
1174
1201
  // Directive options
1175
1202
  var options = {scope: scope, controller: controller};
1176
- angular.forEach(['placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'template', 'autoclose', 'dateType', 'dateFormat', 'modelDateFormat', 'dayFormat', 'strictFormat', 'startWeek', 'startDate', 'useNative', 'lang', 'startView', 'minView', 'iconLeft', 'iconRight', 'daysOfWeekDisabled', 'id'], function(key) {
1203
+ angular.forEach(['placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'template', 'autoclose', 'dateType', 'dateFormat', 'timezone', 'modelDateFormat', 'dayFormat', 'strictFormat', 'startWeek', 'startDate', 'useNative', 'lang', 'startView', 'minView', 'iconLeft', 'iconRight', 'daysOfWeekDisabled', 'id'], function(key) {
1177
1204
  if(angular.isDefined(attr[key])) options[key] = attr[key];
1178
1205
  });
1179
1206
 
@@ -1248,6 +1275,7 @@ angular.module('mgcrea.ngStrap.datepicker', [
1248
1275
  // viewValue -> $parsers -> modelValue
1249
1276
  controller.$parsers.unshift(function(viewValue) {
1250
1277
  // console.warn('$parser("%s"): viewValue=%o', element.attr('ng-model'), viewValue);
1278
+ var date;
1251
1279
  // Null values should correctly reset the model value & validity
1252
1280
  if(!viewValue) {
1253
1281
  controller.$setValidity('date', true);
@@ -1265,16 +1293,20 @@ angular.module('mgcrea.ngStrap.datepicker', [
1265
1293
  } else {
1266
1294
  validateAgainstMinMaxDate(parsedDate);
1267
1295
  }
1296
+
1268
1297
  if(options.dateType === 'string') {
1269
- return formatDate(parsedDate, options.modelDateFormat || options.dateFormat);
1270
- } else if(options.dateType === 'number') {
1271
- return controller.$dateValue.getTime();
1298
+ date = dateParser.timezoneOffsetAdjust(parsedDate, options.timezone, true);
1299
+ return formatDate(date, options.modelDateFormat || options.dateFormat);
1300
+ }
1301
+ date = dateParser.timezoneOffsetAdjust(controller.$dateValue, options.timezone, true);
1302
+ if(options.dateType === 'number') {
1303
+ return date.getTime();
1272
1304
  } else if(options.dateType === 'unix') {
1273
- return controller.$dateValue.getTime() / 1000;
1305
+ return date.getTime() / 1000;
1274
1306
  } else if(options.dateType === 'iso') {
1275
- return controller.$dateValue.toISOString();
1307
+ return date.toISOString();
1276
1308
  } else {
1277
- return new Date(controller.$dateValue);
1309
+ return new Date(date);
1278
1310
  }
1279
1311
  });
1280
1312
 
@@ -1298,7 +1330,7 @@ angular.module('mgcrea.ngStrap.datepicker', [
1298
1330
  // var today = new Date();
1299
1331
  // date = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0, 0);
1300
1332
  // }
1301
- controller.$dateValue = date;
1333
+ controller.$dateValue = dateParser.timezoneOffsetAdjust(date, options.timezone);
1302
1334
  return getDateFormattedString();
1303
1335
  });
1304
1336
 
@@ -1364,7 +1396,6 @@ angular.module('mgcrea.ngStrap.datepicker', [
1364
1396
 
1365
1397
  var startDate = picker.$date || (options.startDate ? dateParser.getDateForAttribute('startDate', options.startDate) : new Date());
1366
1398
  var viewDate = {year: startDate.getFullYear(), month: startDate.getMonth(), date: startDate.getDate()};
1367
- var timezoneOffset = startDate.getTimezoneOffset() * 6e4;
1368
1399
 
1369
1400
  var views = [{
1370
1401
  format: options.dayFormat,
@@ -1382,7 +1413,7 @@ angular.module('mgcrea.ngStrap.datepicker', [
1382
1413
  build: function() {
1383
1414
  var firstDayOfMonth = new Date(viewDate.year, viewDate.month, 1), firstDayOfMonthOffset = firstDayOfMonth.getTimezoneOffset();
1384
1415
  var firstDate = new Date(+firstDayOfMonth - mod(firstDayOfMonth.getDay() - options.startWeek, 7) * 864e5), firstDateOffset = firstDate.getTimezoneOffset();
1385
- var today = new Date().toDateString();
1416
+ var today = dateParser.timezoneOffsetAdjust(new Date(), options.timezone).toDateString();
1386
1417
  // Handle daylight time switch
1387
1418
  if(firstDateOffset !== firstDayOfMonthOffset) firstDate = new Date(+firstDate + (firstDateOffset - firstDayOfMonthOffset) * 60e3);
1388
1419
  var days = [], day;
@@ -1548,6 +1579,7 @@ angular.module('mgcrea.ngStrap.dropdown', ['mgcrea.ngStrap.tooltip'])
1548
1579
  var defaults = this.defaults = {
1549
1580
  animation: 'am-fade',
1550
1581
  prefixClass: 'dropdown',
1582
+ prefixEvent: 'dropdown',
1551
1583
  placement: 'bottom-left',
1552
1584
  template: 'dropdown/dropdown.tpl.html',
1553
1585
  trigger: 'click',
@@ -1730,8 +1762,8 @@ angular.module('mgcrea.ngStrap.helpers.dateFormatter', [])
1730
1762
  return !!splitTimeFormat(timeFormat)[3];
1731
1763
  };
1732
1764
 
1733
- this.formatDate = function(date, format, lang){
1734
- return dateFilter(date, format);
1765
+ this.formatDate = function(date, format, lang, timezone){
1766
+ return dateFilter(date, format, timezone);
1735
1767
  };
1736
1768
 
1737
1769
  }]);
@@ -1867,10 +1899,10 @@ angular.module('mgcrea.ngStrap.helpers.dateParser', [])
1867
1899
  return regex.test(date);
1868
1900
  };
1869
1901
 
1870
- $dateParser.parse = function(value, baseDate, format) {
1902
+ $dateParser.parse = function(value, baseDate, format, timezone) {
1871
1903
  // check for date format special names
1872
1904
  if(format) format = $locale.DATETIME_FORMATS[format] || format;
1873
- if(angular.isDate(value)) value = dateFilter(value, format || $dateParser.$format);
1905
+ if(angular.isDate(value)) value = dateFilter(value, format || $dateParser.$format, timezone);
1874
1906
  var formatRegex = format ? regExpForFormat(format) : regex;
1875
1907
  var formatSetMap = format ? setMapForFormat(format) : setMap;
1876
1908
  var matches = formatRegex.exec(value);
@@ -1945,6 +1977,24 @@ angular.module('mgcrea.ngStrap.helpers.dateParser', [])
1945
1977
  return date;
1946
1978
  };
1947
1979
 
1980
+ /* Correct the date for timezone offset.
1981
+ * @param date (Date) the date to adjust
1982
+ * @param timezone (string) the timezone to adjust for
1983
+ * @param undo (boolean) to add or subtract timezone offset
1984
+ * @return (Date) the corrected date
1985
+ */
1986
+ $dateParser.timezoneOffsetAdjust = function(date, timezone, undo) {
1987
+ if (!date) {
1988
+ return null;
1989
+ }
1990
+ // Right now, only 'UTC' is supported.
1991
+ if (timezone && timezone === 'UTC') {
1992
+ date = new Date(date.getTime());
1993
+ date.setMinutes(date.getMinutes() + (undo?-1:1)*date.getTimezoneOffset());
1994
+ }
1995
+ return date;
1996
+ };
1997
+
1948
1998
  // Private functions
1949
1999
 
1950
2000
  function setMapForFormat(format) {
@@ -2107,6 +2157,69 @@ angular.module('mgcrea.ngStrap.helpers.dimensions', [])
2107
2157
  left: boxRect.left + (window.pageXOffset || docElement.documentElement.scrollLeft) - (docElement.documentElement.clientLeft || 0)
2108
2158
  };
2109
2159
  };
2160
+
2161
+ /**
2162
+ * Provides set equivalent of jQuery's offset function:
2163
+ * @required-by bootstrap-tooltip
2164
+ * @url http://api.jquery.com/offset/
2165
+ * @param element
2166
+ * @param options
2167
+ * @param i
2168
+ */
2169
+ fn.setOffset = function (element, options, i) {
2170
+ var curPosition,
2171
+ curLeft,
2172
+ curCSSTop,
2173
+ curTop,
2174
+ curOffset,
2175
+ curCSSLeft,
2176
+ calculatePosition,
2177
+ position = fn.css(element, 'position'),
2178
+ curElem = angular.element(element),
2179
+ props = {};
2180
+
2181
+ // Set position first, in-case top/left are set even on static elem
2182
+ if (position === 'static') {
2183
+ element.style.position = 'relative';
2184
+ }
2185
+
2186
+ curOffset = fn.offset(element);
2187
+ curCSSTop = fn.css(element, 'top');
2188
+ curCSSLeft = fn.css(element, 'left');
2189
+ calculatePosition = (position === 'absolute' || position === 'fixed') &&
2190
+ (curCSSTop + curCSSLeft).indexOf('auto') > -1;
2191
+
2192
+ // Need to be able to calculate position if either
2193
+ // top or left is auto and position is either absolute or fixed
2194
+ if (calculatePosition) {
2195
+ curPosition = fn.position(element);
2196
+ curTop = curPosition.top;
2197
+ curLeft = curPosition.left;
2198
+ } else {
2199
+ curTop = parseFloat(curCSSTop) || 0;
2200
+ curLeft = parseFloat(curCSSLeft) || 0;
2201
+ }
2202
+
2203
+ if (angular.isFunction(options)) {
2204
+ options = options.call(element, i, curOffset);
2205
+ }
2206
+
2207
+ if (options.top !== null ) {
2208
+ props.top = (options.top - curOffset.top) + curTop;
2209
+ }
2210
+ if ( options.left !== null ) {
2211
+ props.left = (options.left - curOffset.left) + curLeft;
2212
+ }
2213
+
2214
+ if ('using' in options) {
2215
+ options.using.call(curElem, props);
2216
+ } else {
2217
+ curElem.css({
2218
+ top: props.top + 'px',
2219
+ left: props.left + 'px'
2220
+ });
2221
+ }
2222
+ };
2110
2223
 
2111
2224
  /**
2112
2225
  * Provides read-only equivalent of jQuery's position function
@@ -2415,6 +2528,7 @@ angular.module('mgcrea.ngStrap.modal', ['mgcrea.ngStrap.helpers.dimensions'])
2415
2528
  // Fetch, compile then initialize modal
2416
2529
  var modalLinker, modalElement;
2417
2530
  var backdropElement = angular.element('<div class="' + options.prefixClass + '-backdrop"/>');
2531
+ backdropElement.css({position:'fixed', top:'0px', left:'0px', bottom:'0px', right:'0px', 'z-index': 1038});
2418
2532
  $modal.$promise.then(function(template) {
2419
2533
  if(angular.isObject(template)) template = template.data;
2420
2534
  if(options.html) template = template.replace(htmlReplaceRegExp, 'ng-bind-html="');
@@ -2454,9 +2568,6 @@ angular.module('mgcrea.ngStrap.modal', ['mgcrea.ngStrap.helpers.dimensions'])
2454
2568
  $modal.show = function() {
2455
2569
  if($modal.$isShown) return;
2456
2570
 
2457
- if(scope.$emit(options.prefixEvent + '.show.before', $modal).defaultPrevented) {
2458
- return;
2459
- }
2460
2571
  var parent, after;
2461
2572
  if(angular.isElement(options.container)) {
2462
2573
  parent = options.container;
@@ -2474,6 +2585,10 @@ angular.module('mgcrea.ngStrap.modal', ['mgcrea.ngStrap.helpers.dimensions'])
2474
2585
  // Fetch a cloned element linked from template
2475
2586
  modalElement = $modal.$element = modalLinker(scope, function(clonedElement, scope) {});
2476
2587
 
2588
+ if(scope.$emit(options.prefixEvent + '.show.before', $modal).defaultPrevented) {
2589
+ return;
2590
+ }
2591
+
2477
2592
  // Set the initial positioning.
2478
2593
  modalElement.css({display: 'block'}).addClass(options.placement);
2479
2594
 
@@ -2607,13 +2722,8 @@ angular.module('mgcrea.ngStrap.modal', ['mgcrea.ngStrap.helpers.dimensions'])
2607
2722
  var fetchPromises = {};
2608
2723
  function fetchTemplate(template) {
2609
2724
  if(fetchPromises[template]) return fetchPromises[template];
2610
- return (fetchPromises[template] = $q.when($templateCache.get(template) || $http.get(template))
2611
- .then(function(res) {
2612
- if(angular.isObject(res)) {
2613
- $templateCache.put(template, res.data);
2614
- return res.data;
2615
- }
2616
- return res;
2725
+ return (fetchPromises[template] = $http.get(template, {cache: $templateCache}).then(function(res) {
2726
+ return res.data;
2617
2727
  }));
2618
2728
  }
2619
2729
 
@@ -2632,10 +2742,20 @@ angular.module('mgcrea.ngStrap.modal', ['mgcrea.ngStrap.helpers.dimensions'])
2632
2742
 
2633
2743
  // Directive options
2634
2744
  var options = {scope: scope, element: element, show: false};
2635
- angular.forEach(['template', 'contentTemplate', 'placement', 'backdrop', 'keyboard', 'html', 'container', 'animation', 'id'], function(key) {
2745
+ angular.forEach(['template', 'contentTemplate', 'placement', 'container', 'animation', 'id'], function(key) {
2636
2746
  if(angular.isDefined(attr[key])) options[key] = attr[key];
2637
2747
  });
2638
2748
 
2749
+ // use string regex match for boolean values
2750
+ var falseValueRegExp = /^(false|0|)$/;
2751
+ angular.forEach(['keyboard', 'html'], function(key) {
2752
+ if(angular.isDefined(attr[key])) options[key] = !falseValueRegExp.test(attr[key]);
2753
+ });
2754
+
2755
+ if(angular.isDefined(attr.backdrop)) {
2756
+ options.backdrop = falseValueRegExp.test(attr.backdrop) ? false : attr.backdrop;
2757
+ }
2758
+
2639
2759
  // Support scope as data-attrs
2640
2760
  angular.forEach(['title', 'content'], function(key) {
2641
2761
  attr[key] && attr.$observe(key, function(newValue, oldValue) {
@@ -2717,7 +2837,7 @@ angular.module('mgcrea.ngStrap.navbar', [])
2717
2837
  if(options.strict) {
2718
2838
  pattern = '^' + pattern + '$';
2719
2839
  }
2720
- var regexp = new RegExp(pattern, ['i']);
2840
+ var regexp = new RegExp(pattern, 'i');
2721
2841
 
2722
2842
  if(regexp.test(newValue)) {
2723
2843
  liElement.addClass(options.activeClass);
@@ -2824,6 +2944,12 @@ angular.module('mgcrea.ngStrap.popover', ['mgcrea.ngStrap.tooltip'])
2824
2944
  if(angular.isString(newValue)) newValue = !!newValue.match(/true|,?(popover),?/i);
2825
2945
  newValue === true ? popover.show() : popover.hide();
2826
2946
  });
2947
+
2948
+ // Viewport support
2949
+ attr.viewport && scope.$watch(attr.viewport, function (newValue) {
2950
+ if(!popover || !angular.isDefined(newValue)) return;
2951
+ popover.setViewport(newValue);
2952
+ });
2827
2953
 
2828
2954
  // Initialize popover
2829
2955
  var popover = $popover(element, options);
@@ -2840,205 +2966,470 @@ angular.module('mgcrea.ngStrap.popover', ['mgcrea.ngStrap.tooltip'])
2840
2966
 
2841
2967
  }]);
2842
2968
 
2843
- // Source: select.js
2844
- angular.module('mgcrea.ngStrap.select', ['mgcrea.ngStrap.tooltip', 'mgcrea.ngStrap.helpers.parseOptions'])
2969
+ // Source: scrollspy.js
2970
+ angular.module('mgcrea.ngStrap.scrollspy', ['mgcrea.ngStrap.helpers.debounce', 'mgcrea.ngStrap.helpers.dimensions'])
2845
2971
 
2846
- .provider('$select', function() {
2972
+ .provider('$scrollspy', function() {
2973
+
2974
+ // Pool of registered spies
2975
+ var spies = this.$$spies = {};
2847
2976
 
2848
2977
  var defaults = this.defaults = {
2849
- animation: 'am-fade',
2850
- prefixClass: 'select',
2851
- prefixEvent: '$select',
2852
- placement: 'bottom-left',
2853
- template: 'select/select.tpl.html',
2854
- trigger: 'focus',
2855
- container: false,
2856
- keyboard: true,
2857
- html: false,
2858
- delay: 0,
2859
- multiple: false,
2860
- allNoneButtons: false,
2861
- sort: true,
2862
- caretHtml: '&nbsp;<span class="caret"></span>',
2863
- placeholder: 'Choose among the following...',
2864
- allText: 'All',
2865
- noneText: 'None',
2866
- maxLength: 3,
2867
- maxLengthHtml: 'selected',
2868
- iconCheckmark: 'glyphicon glyphicon-ok'
2978
+ debounce: 150,
2979
+ throttle: 100,
2980
+ offset: 100
2869
2981
  };
2870
2982
 
2871
- this.$get = ["$window", "$document", "$rootScope", "$tooltip", "$timeout", function($window, $document, $rootScope, $tooltip, $timeout) {
2983
+ this.$get = ["$window", "$document", "$rootScope", "dimensions", "debounce", "throttle", function($window, $document, $rootScope, dimensions, debounce, throttle) {
2872
2984
 
2985
+ var windowEl = angular.element($window);
2986
+ var docEl = angular.element($document.prop('documentElement'));
2873
2987
  var bodyEl = angular.element($window.document.body);
2874
- var isNative = /(ip(a|o)d|iphone|android)/ig.test($window.navigator.userAgent);
2875
- var isTouch = ('createTouch' in $window.document) && isNative;
2876
2988
 
2877
- function SelectFactory(element, controller, config) {
2989
+ // Helper functions
2878
2990
 
2879
- var $select = {};
2991
+ function nodeName(element, name) {
2992
+ return element[0].nodeName && element[0].nodeName.toLowerCase() === name.toLowerCase();
2993
+ }
2994
+
2995
+ function ScrollSpyFactory(config) {
2880
2996
 
2881
2997
  // Common vars
2882
2998
  var options = angular.extend({}, defaults, config);
2999
+ if(!options.element) options.element = bodyEl;
3000
+ var isWindowSpy = nodeName(options.element, 'body');
3001
+ var scrollEl = isWindowSpy ? windowEl : options.element;
3002
+ var scrollId = isWindowSpy ? 'window' : options.id;
2883
3003
 
2884
- $select = $tooltip(element, options);
2885
- var scope = $select.$scope;
3004
+ // Use existing spy
3005
+ if(spies[scrollId]) {
3006
+ spies[scrollId].$$count++;
3007
+ return spies[scrollId];
3008
+ }
2886
3009
 
2887
- scope.$matches = [];
2888
- scope.$activeIndex = 0;
2889
- scope.$isMultiple = options.multiple;
2890
- scope.$showAllNoneButtons = options.allNoneButtons && options.multiple;
2891
- scope.$iconCheckmark = options.iconCheckmark;
2892
- scope.$allText = options.allText;
2893
- scope.$noneText = options.noneText;
3010
+ var $scrollspy = {};
2894
3011
 
2895
- scope.$activate = function(index) {
2896
- scope.$$postDigest(function() {
2897
- $select.activate(index);
2898
- });
2899
- };
3012
+ // Private vars
3013
+ var unbindViewContentLoaded, unbindIncludeContentLoaded;
3014
+ var trackedElements = $scrollspy.$trackedElements = [];
3015
+ var sortedElements = [];
3016
+ var activeTarget;
3017
+ var debouncedCheckPosition;
3018
+ var throttledCheckPosition;
3019
+ var debouncedCheckOffsets;
3020
+ var viewportHeight;
3021
+ var scrollTop;
2900
3022
 
2901
- scope.$select = function(index, evt) {
2902
- scope.$$postDigest(function() {
2903
- $select.select(index);
2904
- });
2905
- };
3023
+ $scrollspy.init = function() {
2906
3024
 
2907
- scope.$isVisible = function() {
2908
- return $select.$isVisible();
2909
- };
3025
+ // Setup internal ref counter
3026
+ this.$$count = 1;
2910
3027
 
2911
- scope.$isActive = function(index) {
2912
- return $select.$isActive(index);
2913
- };
3028
+ // Bind events
3029
+ debouncedCheckPosition = debounce(this.checkPosition, options.debounce);
3030
+ throttledCheckPosition = throttle(this.checkPosition, options.throttle);
3031
+ scrollEl.on('click', this.checkPositionWithEventLoop);
3032
+ windowEl.on('resize', debouncedCheckPosition);
3033
+ scrollEl.on('scroll', throttledCheckPosition);
2914
3034
 
2915
- scope.$selectAll = function () {
2916
- for (var i = 0; i < scope.$matches.length; i++) {
2917
- if (!scope.$isActive(i)) {
2918
- scope.$select(i);
2919
- }
3035
+ debouncedCheckOffsets = debounce(this.checkOffsets, options.debounce);
3036
+ unbindViewContentLoaded = $rootScope.$on('$viewContentLoaded', debouncedCheckOffsets);
3037
+ unbindIncludeContentLoaded = $rootScope.$on('$includeContentLoaded', debouncedCheckOffsets);
3038
+ debouncedCheckOffsets();
3039
+
3040
+ // Register spy for reuse
3041
+ if(scrollId) {
3042
+ spies[scrollId] = $scrollspy;
2920
3043
  }
3044
+
2921
3045
  };
2922
3046
 
2923
- scope.$selectNone = function () {
2924
- for (var i = 0; i < scope.$matches.length; i++) {
2925
- if (scope.$isActive(i)) {
2926
- scope.$select(i);
2927
- }
3047
+ $scrollspy.destroy = function() {
3048
+
3049
+ // Check internal ref counter
3050
+ this.$$count--;
3051
+ if(this.$$count > 0) {
3052
+ return;
3053
+ }
3054
+
3055
+ // Unbind events
3056
+ scrollEl.off('click', this.checkPositionWithEventLoop);
3057
+ windowEl.off('resize', debouncedCheckPosition);
3058
+ scrollEl.off('scroll', throttledCheckPosition);
3059
+ unbindViewContentLoaded();
3060
+ unbindIncludeContentLoaded();
3061
+ if (scrollId) {
3062
+ delete spies[scrollId];
2928
3063
  }
2929
3064
  };
2930
3065
 
2931
- // Public methods
3066
+ $scrollspy.checkPosition = function() {
2932
3067
 
2933
- $select.update = function(matches) {
2934
- scope.$matches = matches;
2935
- $select.$updateActiveIndex();
2936
- };
3068
+ // Not ready yet
3069
+ if(!sortedElements.length) return;
2937
3070
 
2938
- $select.activate = function(index) {
2939
- if(options.multiple) {
2940
- scope.$activeIndex.sort();
2941
- $select.$isActive(index) ? scope.$activeIndex.splice(scope.$activeIndex.indexOf(index), 1) : scope.$activeIndex.push(index);
2942
- if(options.sort) scope.$activeIndex.sort();
2943
- } else {
2944
- scope.$activeIndex = index;
3071
+ // Calculate the scroll position
3072
+ scrollTop = (isWindowSpy ? $window.pageYOffset : scrollEl.prop('scrollTop')) || 0;
3073
+
3074
+ // Calculate the viewport height for use by the components
3075
+ viewportHeight = Math.max($window.innerHeight, docEl.prop('clientHeight'));
3076
+
3077
+ // Activate first element if scroll is smaller
3078
+ if(scrollTop < sortedElements[0].offsetTop && activeTarget !== sortedElements[0].target) {
3079
+ return $scrollspy.$activateElement(sortedElements[0]);
2945
3080
  }
2946
- return scope.$activeIndex;
3081
+
3082
+ // Activate proper element
3083
+ for (var i = sortedElements.length; i--;) {
3084
+ if(angular.isUndefined(sortedElements[i].offsetTop) || sortedElements[i].offsetTop === null) continue;
3085
+ if(activeTarget === sortedElements[i].target) continue;
3086
+ if(scrollTop < sortedElements[i].offsetTop) continue;
3087
+ if(sortedElements[i + 1] && scrollTop > sortedElements[i + 1].offsetTop) continue;
3088
+ return $scrollspy.$activateElement(sortedElements[i]);
3089
+ }
3090
+
2947
3091
  };
2948
3092
 
2949
- $select.select = function(index) {
2950
- var value = scope.$matches[index].value;
2951
- scope.$apply(function() {
2952
- $select.activate(index);
2953
- if(options.multiple) {
2954
- controller.$setViewValue(scope.$activeIndex.map(function(index) {
2955
- return scope.$matches[index].value;
2956
- }));
2957
- } else {
2958
- controller.$setViewValue(value);
2959
- // Hide if single select
2960
- $select.hide();
2961
- }
2962
- });
2963
- // Emit event
2964
- scope.$emit(options.prefixEvent + '.select', value, index, $select);
3093
+ $scrollspy.checkPositionWithEventLoop = function() {
3094
+ // IE 9 throws an error if we use 'this' instead of '$scrollspy'
3095
+ // in this setTimeout call
3096
+ setTimeout($scrollspy.checkPosition, 1);
2965
3097
  };
2966
3098
 
2967
3099
  // Protected methods
2968
3100
 
2969
- $select.$updateActiveIndex = function() {
2970
- if(controller.$modelValue && scope.$matches.length) {
2971
- if(options.multiple && angular.isArray(controller.$modelValue)) {
2972
- scope.$activeIndex = controller.$modelValue.map(function(value) {
2973
- return $select.$getIndex(value);
2974
- });
2975
- } else {
2976
- scope.$activeIndex = $select.$getIndex(controller.$modelValue);
3101
+ $scrollspy.$activateElement = function(element) {
3102
+ if(activeTarget) {
3103
+ var activeElement = $scrollspy.$getTrackedElement(activeTarget);
3104
+ if(activeElement) {
3105
+ activeElement.source.removeClass('active');
3106
+ if(nodeName(activeElement.source, 'li') && nodeName(activeElement.source.parent().parent(), 'li')) {
3107
+ activeElement.source.parent().parent().removeClass('active');
3108
+ }
2977
3109
  }
2978
- } else if(scope.$activeIndex >= scope.$matches.length) {
2979
- scope.$activeIndex = options.multiple ? [] : 0;
3110
+ }
3111
+ activeTarget = element.target;
3112
+ element.source.addClass('active');
3113
+ if(nodeName(element.source, 'li') && nodeName(element.source.parent().parent(), 'li')) {
3114
+ element.source.parent().parent().addClass('active');
2980
3115
  }
2981
3116
  };
2982
3117
 
2983
- $select.$isVisible = function() {
2984
- if(!options.minLength || !controller) {
2985
- return scope.$matches.length;
2986
- }
2987
- // minLength support
2988
- return scope.$matches.length && controller.$viewValue.length >= options.minLength;
3118
+ $scrollspy.$getTrackedElement = function(target) {
3119
+ return trackedElements.filter(function(obj) {
3120
+ return obj.target === target;
3121
+ })[0];
2989
3122
  };
2990
3123
 
2991
- $select.$isActive = function(index) {
2992
- if(options.multiple) {
2993
- return scope.$activeIndex.indexOf(index) !== -1;
2994
- } else {
2995
- return scope.$activeIndex === index;
2996
- }
3124
+ // Track offsets behavior
3125
+
3126
+ $scrollspy.checkOffsets = function() {
3127
+
3128
+ angular.forEach(trackedElements, function(trackedElement) {
3129
+ var targetElement = document.querySelector(trackedElement.target);
3130
+ trackedElement.offsetTop = targetElement ? dimensions.offset(targetElement).top : null;
3131
+ if(options.offset && trackedElement.offsetTop !== null) trackedElement.offsetTop -= options.offset * 1;
3132
+ });
3133
+
3134
+ sortedElements = trackedElements
3135
+ .filter(function(el) {
3136
+ return el.offsetTop !== null;
3137
+ })
3138
+ .sort(function(a, b) {
3139
+ return a.offsetTop - b.offsetTop;
3140
+ });
3141
+
3142
+ debouncedCheckPosition();
3143
+
2997
3144
  };
2998
3145
 
2999
- $select.$getIndex = function(value) {
3000
- var l = scope.$matches.length, i = l;
3001
- if(!l) return;
3002
- for(i = l; i--;) {
3003
- if(scope.$matches[i].value === value) break;
3004
- }
3005
- if(i < 0) return;
3006
- return i;
3146
+ $scrollspy.trackElement = function(target, source) {
3147
+ trackedElements.push({target: target, source: source});
3007
3148
  };
3008
3149
 
3009
- $select.$onMouseDown = function(evt) {
3010
- // Prevent blur on mousedown on .dropdown-menu
3011
- evt.preventDefault();
3012
- evt.stopPropagation();
3013
- // Emulate click for mobile devices
3014
- if(isTouch) {
3015
- var targetEl = angular.element(evt.target);
3016
- targetEl.triggerHandler('click');
3150
+ $scrollspy.untrackElement = function(target, source) {
3151
+ var toDelete;
3152
+ for (var i = trackedElements.length; i--;) {
3153
+ if(trackedElements[i].target === target && trackedElements[i].source === source) {
3154
+ toDelete = i;
3155
+ break;
3156
+ }
3017
3157
  }
3158
+ trackedElements = trackedElements.splice(toDelete, 1);
3018
3159
  };
3019
3160
 
3020
- $select.$onKeyDown = function(evt) {
3021
- if (!/(9|13|38|40)/.test(evt.keyCode)) return;
3022
- evt.preventDefault();
3023
- evt.stopPropagation();
3161
+ $scrollspy.activate = function(i) {
3162
+ trackedElements[i].addClass('active');
3163
+ };
3024
3164
 
3025
- // Select with enter
3026
- if(!options.multiple && (evt.keyCode === 13 || evt.keyCode === 9)) {
3027
- return $select.select(scope.$activeIndex);
3028
- }
3165
+ // Initialize plugin
3029
3166
 
3030
- // Navigate with keyboard
3031
- if(evt.keyCode === 38 && scope.$activeIndex > 0) scope.$activeIndex--;
3032
- else if(evt.keyCode === 40 && scope.$activeIndex < scope.$matches.length - 1) scope.$activeIndex++;
3033
- else if(angular.isUndefined(scope.$activeIndex)) scope.$activeIndex = 0;
3034
- scope.$digest();
3035
- };
3167
+ $scrollspy.init();
3168
+ return $scrollspy;
3036
3169
 
3037
- // Overrides
3170
+ }
3038
3171
 
3039
- var _show = $select.show;
3040
- $select.show = function() {
3041
- _show();
3172
+ return ScrollSpyFactory;
3173
+
3174
+ }];
3175
+
3176
+ })
3177
+
3178
+ .directive('bsScrollspy', ["$rootScope", "debounce", "dimensions", "$scrollspy", function($rootScope, debounce, dimensions, $scrollspy) {
3179
+
3180
+ return {
3181
+ restrict: 'EAC',
3182
+ link: function postLink(scope, element, attr) {
3183
+
3184
+ var options = {scope: scope};
3185
+ angular.forEach(['offset', 'target'], function(key) {
3186
+ if(angular.isDefined(attr[key])) options[key] = attr[key];
3187
+ });
3188
+
3189
+ var scrollspy = $scrollspy(options);
3190
+ scrollspy.trackElement(options.target, element);
3191
+
3192
+ scope.$on('$destroy', function() {
3193
+ if (scrollspy) {
3194
+ scrollspy.untrackElement(options.target, element);
3195
+ scrollspy.destroy();
3196
+ }
3197
+ options = null;
3198
+ scrollspy = null;
3199
+ });
3200
+
3201
+ }
3202
+ };
3203
+
3204
+ }])
3205
+
3206
+
3207
+ .directive('bsScrollspyList', ["$rootScope", "debounce", "dimensions", "$scrollspy", function($rootScope, debounce, dimensions, $scrollspy) {
3208
+
3209
+ return {
3210
+ restrict: 'A',
3211
+ compile: function postLink(element, attr) {
3212
+ var children = element[0].querySelectorAll('li > a[href]');
3213
+ angular.forEach(children, function(child) {
3214
+ var childEl = angular.element(child);
3215
+ childEl.parent().attr('bs-scrollspy', '').attr('data-target', childEl.attr('href'));
3216
+ });
3217
+ }
3218
+
3219
+ };
3220
+
3221
+ }]);
3222
+
3223
+ // Source: select.js
3224
+ angular.module('mgcrea.ngStrap.select', ['mgcrea.ngStrap.tooltip', 'mgcrea.ngStrap.helpers.parseOptions'])
3225
+
3226
+ .provider('$select', function() {
3227
+
3228
+ var defaults = this.defaults = {
3229
+ animation: 'am-fade',
3230
+ prefixClass: 'select',
3231
+ prefixEvent: '$select',
3232
+ placement: 'bottom-left',
3233
+ template: 'select/select.tpl.html',
3234
+ trigger: 'focus',
3235
+ container: false,
3236
+ keyboard: true,
3237
+ html: false,
3238
+ delay: 0,
3239
+ multiple: false,
3240
+ allNoneButtons: false,
3241
+ sort: true,
3242
+ caretHtml: '&nbsp;<span class="caret"></span>',
3243
+ placeholder: 'Choose among the following...',
3244
+ allText: 'All',
3245
+ noneText: 'None',
3246
+ maxLength: 3,
3247
+ maxLengthHtml: 'selected',
3248
+ iconCheckmark: 'glyphicon glyphicon-ok'
3249
+ };
3250
+
3251
+ this.$get = ["$window", "$document", "$rootScope", "$tooltip", "$timeout", function($window, $document, $rootScope, $tooltip, $timeout) {
3252
+
3253
+ var bodyEl = angular.element($window.document.body);
3254
+ var isNative = /(ip(a|o)d|iphone|android)/ig.test($window.navigator.userAgent);
3255
+ var isTouch = ('createTouch' in $window.document) && isNative;
3256
+
3257
+ function SelectFactory(element, controller, config) {
3258
+
3259
+ var $select = {};
3260
+
3261
+ // Common vars
3262
+ var options = angular.extend({}, defaults, config);
3263
+
3264
+ // parse sort option value to support attribute as string
3265
+ // when binded to interpolated value
3266
+ options.sort = options.sort.toString().match(/true|1/i);
3267
+
3268
+ $select = $tooltip(element, options);
3269
+ var scope = $select.$scope;
3270
+
3271
+ scope.$matches = [];
3272
+ scope.$activeIndex = -1;
3273
+ scope.$isMultiple = options.multiple;
3274
+ scope.$showAllNoneButtons = options.allNoneButtons && options.multiple;
3275
+ scope.$iconCheckmark = options.iconCheckmark;
3276
+ scope.$allText = options.allText;
3277
+ scope.$noneText = options.noneText;
3278
+
3279
+ scope.$activate = function(index) {
3280
+ scope.$$postDigest(function() {
3281
+ $select.activate(index);
3282
+ });
3283
+ };
3284
+
3285
+ scope.$select = function(index, evt) {
3286
+ scope.$$postDigest(function() {
3287
+ $select.select(index);
3288
+ });
3289
+ };
3290
+
3291
+ scope.$isVisible = function() {
3292
+ return $select.$isVisible();
3293
+ };
3294
+
3295
+ scope.$isActive = function(index) {
3296
+ return $select.$isActive(index);
3297
+ };
3298
+
3299
+ scope.$selectAll = function () {
3300
+ for (var i = 0; i < scope.$matches.length; i++) {
3301
+ if (!scope.$isActive(i)) {
3302
+ scope.$select(i);
3303
+ }
3304
+ }
3305
+ };
3306
+
3307
+ scope.$selectNone = function () {
3308
+ for (var i = 0; i < scope.$matches.length; i++) {
3309
+ if (scope.$isActive(i)) {
3310
+ scope.$select(i);
3311
+ }
3312
+ }
3313
+ };
3314
+
3315
+ // Public methods
3316
+
3317
+ $select.update = function(matches) {
3318
+ scope.$matches = matches;
3319
+ $select.$updateActiveIndex();
3320
+ };
3321
+
3322
+ $select.activate = function(index) {
3323
+ if(options.multiple) {
3324
+ $select.$isActive(index) ? scope.$activeIndex.splice(scope.$activeIndex.indexOf(index), 1) : scope.$activeIndex.push(index);
3325
+ if(options.sort) scope.$activeIndex.sort();
3326
+ } else {
3327
+ scope.$activeIndex = index;
3328
+ }
3329
+ return scope.$activeIndex;
3330
+ };
3331
+
3332
+ $select.select = function(index) {
3333
+ var value = scope.$matches[index].value;
3334
+ scope.$apply(function() {
3335
+ $select.activate(index);
3336
+ if(options.multiple) {
3337
+ controller.$setViewValue(scope.$activeIndex.map(function(index) {
3338
+ return scope.$matches[index].value;
3339
+ }));
3340
+ } else {
3341
+ controller.$setViewValue(value);
3342
+ // Hide if single select
3343
+ $select.hide();
3344
+ }
3345
+ });
3346
+ // Emit event
3347
+ scope.$emit(options.prefixEvent + '.select', value, index, $select);
3348
+ };
3349
+
3350
+ // Protected methods
3351
+
3352
+ $select.$updateActiveIndex = function() {
3353
+ if(controller.$modelValue && scope.$matches.length) {
3354
+ if(options.multiple && angular.isArray(controller.$modelValue)) {
3355
+ scope.$activeIndex = controller.$modelValue.map(function(value) {
3356
+ return $select.$getIndex(value);
3357
+ });
3358
+ } else {
3359
+ scope.$activeIndex = $select.$getIndex(controller.$modelValue);
3360
+ }
3361
+ } else if(scope.$activeIndex >= scope.$matches.length) {
3362
+ scope.$activeIndex = options.multiple ? [] : 0;
3363
+ }
3364
+ };
3365
+
3366
+ $select.$isVisible = function() {
3367
+ if(!options.minLength || !controller) {
3368
+ return scope.$matches.length;
3369
+ }
3370
+ // minLength support
3371
+ return scope.$matches.length && controller.$viewValue.length >= options.minLength;
3372
+ };
3373
+
3374
+ $select.$isActive = function(index) {
3375
+ if(options.multiple) {
3376
+ return scope.$activeIndex.indexOf(index) !== -1;
3377
+ } else {
3378
+ return scope.$activeIndex === index;
3379
+ }
3380
+ };
3381
+
3382
+ $select.$getIndex = function(value) {
3383
+ var l = scope.$matches.length, i = l;
3384
+ if(!l) return;
3385
+ for(i = l; i--;) {
3386
+ if(scope.$matches[i].value === value) break;
3387
+ }
3388
+ if(i < 0) return;
3389
+ return i;
3390
+ };
3391
+
3392
+ $select.$onMouseDown = function(evt) {
3393
+ // Prevent blur on mousedown on .dropdown-menu
3394
+ evt.preventDefault();
3395
+ evt.stopPropagation();
3396
+ // Emulate click for mobile devices
3397
+ if(isTouch) {
3398
+ var targetEl = angular.element(evt.target);
3399
+ targetEl.triggerHandler('click');
3400
+ }
3401
+ };
3402
+
3403
+ $select.$onKeyDown = function(evt) {
3404
+ if (!/(9|13|38|40)/.test(evt.keyCode)) return;
3405
+ evt.preventDefault();
3406
+ evt.stopPropagation();
3407
+
3408
+ // release focus on tab
3409
+ if (options.multiple && evt.keyCode === 9) {
3410
+ return $select.hide();
3411
+ }
3412
+
3413
+ // Select with enter
3414
+ if(!options.multiple && (evt.keyCode === 13 || evt.keyCode === 9)) {
3415
+ return $select.select(scope.$activeIndex);
3416
+ }
3417
+
3418
+ if (!options.multiple) {
3419
+ // Navigate with keyboard
3420
+ if(evt.keyCode === 38 && scope.$activeIndex > 0) scope.$activeIndex--;
3421
+ else if(evt.keyCode === 38 && scope.$activeIndex < 0) scope.$activeIndex = scope.$matches.length - 1;
3422
+ else if(evt.keyCode === 40 && scope.$activeIndex < scope.$matches.length - 1) scope.$activeIndex++;
3423
+ else if(angular.isUndefined(scope.$activeIndex)) scope.$activeIndex = 0;
3424
+ scope.$digest();
3425
+ }
3426
+ };
3427
+
3428
+ // Overrides
3429
+
3430
+ var _show = $select.show;
3431
+ $select.show = function() {
3432
+ _show();
3042
3433
  if(options.multiple) {
3043
3434
  $select.$element.addClass('select-multiple');
3044
3435
  }
@@ -3054,6 +3445,9 @@ angular.module('mgcrea.ngStrap.select', ['mgcrea.ngStrap.tooltip', 'mgcrea.ngStr
3054
3445
 
3055
3446
  var _hide = $select.hide;
3056
3447
  $select.hide = function() {
3448
+ if(!options.multiple && !controller.$modelValue) {
3449
+ scope.$activeIndex = -1;
3450
+ }
3057
3451
  $select.$element.off(isTouch ? 'touchstart' : 'mousedown', $select.$onMouseDown);
3058
3452
  if(options.keyboard) {
3059
3453
  element.off('keydown', $select.$onKeyDown);
@@ -3083,7 +3477,7 @@ angular.module('mgcrea.ngStrap.select', ['mgcrea.ngStrap.tooltip', 'mgcrea.ngStr
3083
3477
 
3084
3478
  // Directive options
3085
3479
  var options = {scope: scope, placeholder: defaults.placeholder};
3086
- angular.forEach(['placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'template', 'placeholder', 'multiple', 'allNoneButtons', 'maxLength', 'maxLengthHtml', 'allText', 'noneText', 'iconCheckmark', 'autoClose', 'id'], function(key) {
3480
+ angular.forEach(['placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'template', 'placeholder', 'multiple', 'allNoneButtons', 'maxLength', 'maxLengthHtml', 'allText', 'noneText', 'iconCheckmark', 'autoClose', 'id', 'sort', 'caretHtml'], function(key) {
3087
3481
  if(angular.isDefined(attr[key])) options[key] = attr[key];
3088
3482
  });
3089
3483
 
@@ -3095,13 +3489,13 @@ angular.module('mgcrea.ngStrap.select', ['mgcrea.ngStrap.tooltip', 'mgcrea.ngStr
3095
3489
  inputEl.after(element);
3096
3490
  }
3097
3491
 
3098
- // Build proper ngOptions
3099
- var parsedOptions = $parseOptions(attr.ngOptions);
3492
+ // Build proper bsOptions
3493
+ var parsedOptions = $parseOptions(attr.bsOptions);
3100
3494
 
3101
3495
  // Initialize select
3102
3496
  var select = $select(element, controller, options);
3103
3497
 
3104
- // Watch ngOptions values before filtering for changes
3498
+ // Watch bsOptions values before filtering for changes
3105
3499
  var watchedOptions = parsedOptions.$match[7].replace(/\|.+/, '').trim();
3106
3500
  scope.$watch(watchedOptions, function(newValue, oldValue) {
3107
3501
  // console.warn('scope.$watch(%s)', watchedOptions, newValue, oldValue);
@@ -3137,7 +3531,7 @@ angular.module('mgcrea.ngStrap.select', ['mgcrea.ngStrap.tooltip', 'mgcrea.ngStr
3137
3531
  index = select.$getIndex(controller.$modelValue);
3138
3532
  selected = angular.isDefined(index) ? select.$scope.$matches[index].label : false;
3139
3533
  }
3140
- element.html((selected ? selected : options.placeholder) + defaults.caretHtml);
3534
+ element.html((selected ? selected : options.placeholder) + (options.caretHtml ? options.caretHtml : defaults.caretHtml));
3141
3535
  };
3142
3536
 
3143
3537
  if(options.multiple){
@@ -3313,6 +3707,10 @@ angular.module('mgcrea.ngStrap.tab', [])
3313
3707
  element.addClass(bsTabsCtrl.$options.animation);
3314
3708
  }
3315
3709
 
3710
+ attrs.$observe('disabled', function(newValue, oldValue) {
3711
+ scope.disabled = scope.$eval(newValue);
3712
+ });
3713
+
3316
3714
  // Push pane to parent bsTabs controller
3317
3715
  bsTabsCtrl.$push(scope);
3318
3716
 
@@ -3337,599 +3735,684 @@ angular.module('mgcrea.ngStrap.tab', [])
3337
3735
 
3338
3736
  }]);
3339
3737
 
3340
- // Source: scrollspy.js
3341
- angular.module('mgcrea.ngStrap.scrollspy', ['mgcrea.ngStrap.helpers.debounce', 'mgcrea.ngStrap.helpers.dimensions'])
3738
+ // Source: timepicker.js
3739
+ angular.module('mgcrea.ngStrap.timepicker', [
3740
+ 'mgcrea.ngStrap.helpers.dateParser',
3741
+ 'mgcrea.ngStrap.helpers.dateFormatter',
3742
+ 'mgcrea.ngStrap.tooltip'])
3342
3743
 
3343
- .provider('$scrollspy', function() {
3344
-
3345
- // Pool of registered spies
3346
- var spies = this.$$spies = {};
3744
+ .provider('$timepicker', function() {
3347
3745
 
3348
3746
  var defaults = this.defaults = {
3349
- debounce: 150,
3350
- throttle: 100,
3351
- offset: 100
3747
+ animation: 'am-fade',
3748
+ prefixClass: 'timepicker',
3749
+ placement: 'bottom-left',
3750
+ template: 'timepicker/timepicker.tpl.html',
3751
+ trigger: 'focus',
3752
+ container: false,
3753
+ keyboard: true,
3754
+ html: false,
3755
+ delay: 0,
3756
+ // lang: $locale.id,
3757
+ useNative: true,
3758
+ timeType: 'date',
3759
+ timeFormat: 'shortTime',
3760
+ timezone: null,
3761
+ modelTimeFormat: null,
3762
+ autoclose: false,
3763
+ minTime: -Infinity,
3764
+ maxTime: +Infinity,
3765
+ length: 5,
3766
+ hourStep: 1,
3767
+ minuteStep: 5,
3768
+ roundDisplay: false,
3769
+ iconUp: 'glyphicon glyphicon-chevron-up',
3770
+ iconDown: 'glyphicon glyphicon-chevron-down',
3771
+ arrowBehavior: 'pager'
3352
3772
  };
3353
3773
 
3354
- this.$get = ["$window", "$document", "$rootScope", "dimensions", "debounce", "throttle", function($window, $document, $rootScope, dimensions, debounce, throttle) {
3774
+ this.$get = ["$window", "$document", "$rootScope", "$sce", "$dateFormatter", "$tooltip", "$timeout", function($window, $document, $rootScope, $sce, $dateFormatter, $tooltip, $timeout) {
3355
3775
 
3356
- var windowEl = angular.element($window);
3357
- var docEl = angular.element($document.prop('documentElement'));
3358
3776
  var bodyEl = angular.element($window.document.body);
3777
+ var isNative = /(ip(a|o)d|iphone|android)/ig.test($window.navigator.userAgent);
3778
+ var isTouch = ('createTouch' in $window.document) && isNative;
3779
+ if(!defaults.lang) defaults.lang = $dateFormatter.getDefaultLocale();
3359
3780
 
3360
- // Helper functions
3361
-
3362
- function nodeName(element, name) {
3363
- return element[0].nodeName && element[0].nodeName.toLowerCase() === name.toLowerCase();
3364
- }
3781
+ function timepickerFactory(element, controller, config) {
3365
3782
 
3366
- function ScrollSpyFactory(config) {
3783
+ var $timepicker = $tooltip(element, angular.extend({}, defaults, config));
3784
+ var parentScope = config.scope;
3785
+ var options = $timepicker.$options;
3786
+ var scope = $timepicker.$scope;
3367
3787
 
3368
- // Common vars
3369
- var options = angular.extend({}, defaults, config);
3370
- if(!options.element) options.element = bodyEl;
3371
- var isWindowSpy = nodeName(options.element, 'body');
3372
- var scrollEl = isWindowSpy ? windowEl : options.element;
3373
- var scrollId = isWindowSpy ? 'window' : options.id;
3788
+ var lang = options.lang;
3789
+ var formatDate = function(date, format, timezone) {
3790
+ return $dateFormatter.formatDate(date, format, lang, timezone);
3791
+ };
3374
3792
 
3375
- // Use existing spy
3376
- if(spies[scrollId]) {
3377
- spies[scrollId].$$count++;
3378
- return spies[scrollId];
3793
+ function floorMinutes(time)
3794
+ {
3795
+ // coeff used to floor current time to nearest minuteStep interval
3796
+ var coeff = 1000 * 60 * options.minuteStep;
3797
+ return new Date(Math.floor(time.getTime() / coeff) * coeff);
3379
3798
  }
3380
3799
 
3381
- var $scrollspy = {};
3382
-
3383
- // Private vars
3384
- var unbindViewContentLoaded, unbindIncludeContentLoaded;
3385
- var trackedElements = $scrollspy.$trackedElements = [];
3386
- var sortedElements = [];
3387
- var activeTarget;
3388
- var debouncedCheckPosition;
3389
- var throttledCheckPosition;
3390
- var debouncedCheckOffsets;
3391
- var viewportHeight;
3392
- var scrollTop;
3800
+ // View vars
3393
3801
 
3394
- $scrollspy.init = function() {
3802
+ var selectedIndex = 0;
3803
+ var defaultDate = options.roundDisplay ? floorMinutes(new Date()) : new Date();
3804
+ var startDate = controller.$dateValue || defaultDate;
3805
+ var viewDate = {hour: startDate.getHours(), meridian: startDate.getHours() < 12, minute: startDate.getMinutes(), second: startDate.getSeconds(), millisecond: startDate.getMilliseconds()};
3395
3806
 
3396
- // Setup internal ref counter
3397
- this.$$count = 1;
3807
+ var format = $dateFormatter.getDatetimeFormat(options.timeFormat, lang);
3398
3808
 
3399
- // Bind events
3400
- debouncedCheckPosition = debounce(this.checkPosition, options.debounce);
3401
- throttledCheckPosition = throttle(this.checkPosition, options.throttle);
3402
- scrollEl.on('click', this.checkPositionWithEventLoop);
3403
- windowEl.on('resize', debouncedCheckPosition);
3404
- scrollEl.on('scroll', throttledCheckPosition);
3809
+ var hoursFormat = $dateFormatter.hoursFormat(format),
3810
+ timeSeparator = $dateFormatter.timeSeparator(format),
3811
+ minutesFormat = $dateFormatter.minutesFormat(format),
3812
+ showAM = $dateFormatter.showAM(format);
3405
3813
 
3406
- debouncedCheckOffsets = debounce(this.checkOffsets, options.debounce);
3407
- unbindViewContentLoaded = $rootScope.$on('$viewContentLoaded', debouncedCheckOffsets);
3408
- unbindIncludeContentLoaded = $rootScope.$on('$includeContentLoaded', debouncedCheckOffsets);
3409
- debouncedCheckOffsets();
3814
+ scope.$iconUp = options.iconUp;
3815
+ scope.$iconDown = options.iconDown;
3410
3816
 
3411
- // Register spy for reuse
3412
- if(scrollId) {
3413
- spies[scrollId] = $scrollspy;
3414
- }
3817
+ // Scope methods
3415
3818
 
3819
+ scope.$select = function(date, index) {
3820
+ $timepicker.select(date, index);
3821
+ };
3822
+ scope.$moveIndex = function(value, index) {
3823
+ $timepicker.$moveIndex(value, index);
3824
+ };
3825
+ scope.$switchMeridian = function(date) {
3826
+ $timepicker.switchMeridian(date);
3416
3827
  };
3417
3828
 
3418
- $scrollspy.destroy = function() {
3829
+ // Public methods
3419
3830
 
3420
- // Check internal ref counter
3421
- this.$$count--;
3422
- if(this.$$count > 0) {
3423
- return;
3831
+ $timepicker.update = function(date) {
3832
+ // console.warn('$timepicker.update() newValue=%o', date);
3833
+ if(angular.isDate(date) && !isNaN(date.getTime())) {
3834
+ $timepicker.$date = date;
3835
+ angular.extend(viewDate, {hour: date.getHours(), minute: date.getMinutes(), second: date.getSeconds(), millisecond: date.getMilliseconds()});
3836
+ $timepicker.$build();
3837
+ } else if(!$timepicker.$isBuilt) {
3838
+ $timepicker.$build();
3424
3839
  }
3840
+ };
3425
3841
 
3426
- // Unbind events
3427
- scrollEl.off('click', this.checkPositionWithEventLoop);
3428
- windowEl.off('resize', debouncedCheckPosition);
3429
- scrollEl.off('scroll', throttledCheckPosition);
3430
- unbindViewContentLoaded();
3431
- unbindIncludeContentLoaded();
3432
- if (scrollId) {
3433
- delete spies[scrollId];
3842
+ $timepicker.select = function(date, index, keep) {
3843
+ // console.warn('$timepicker.select', date, scope.$mode);
3844
+ if(!controller.$dateValue || isNaN(controller.$dateValue.getTime())) controller.$dateValue = new Date(1970, 0, 1);
3845
+ if(!angular.isDate(date)) date = new Date(date);
3846
+ if(index === 0) controller.$dateValue.setHours(date.getHours());
3847
+ else if(index === 1) controller.$dateValue.setMinutes(date.getMinutes());
3848
+ controller.$setViewValue(angular.copy(controller.$dateValue));
3849
+ controller.$render();
3850
+ if(options.autoclose && !keep) {
3851
+ $timeout(function() { $timepicker.hide(true); });
3434
3852
  }
3435
3853
  };
3436
3854
 
3437
- $scrollspy.checkPosition = function() {
3438
-
3439
- // Not ready yet
3440
- if(!sortedElements.length) return;
3441
-
3442
- // Calculate the scroll position
3443
- scrollTop = (isWindowSpy ? $window.pageYOffset : scrollEl.prop('scrollTop')) || 0;
3855
+ $timepicker.switchMeridian = function(date) {
3856
+ if (!controller.$dateValue || isNaN(controller.$dateValue.getTime())) {
3857
+ return;
3858
+ }
3859
+ var hours = (date || controller.$dateValue).getHours();
3860
+ controller.$dateValue.setHours(hours < 12 ? hours + 12 : hours - 12);
3861
+ controller.$setViewValue(angular.copy(controller.$dateValue));
3862
+ controller.$render();
3863
+ };
3444
3864
 
3445
- // Calculate the viewport height for use by the components
3446
- viewportHeight = Math.max($window.innerHeight, docEl.prop('clientHeight'));
3865
+ // Protected methods
3447
3866
 
3448
- // Activate first element if scroll is smaller
3449
- if(scrollTop < sortedElements[0].offsetTop && activeTarget !== sortedElements[0].target) {
3450
- return $scrollspy.$activateElement(sortedElements[0]);
3867
+ $timepicker.$build = function() {
3868
+ // console.warn('$timepicker.$build() viewDate=%o', viewDate);
3869
+ var i, midIndex = scope.midIndex = parseInt(options.length / 2, 10);
3870
+ var hours = [], hour;
3871
+ for(i = 0; i < options.length; i++) {
3872
+ hour = new Date(1970, 0, 1, viewDate.hour - (midIndex - i) * options.hourStep);
3873
+ hours.push({date: hour, label: formatDate(hour, hoursFormat), selected: $timepicker.$date && $timepicker.$isSelected(hour, 0), disabled: $timepicker.$isDisabled(hour, 0)});
3874
+ }
3875
+ var minutes = [], minute;
3876
+ for(i = 0; i < options.length; i++) {
3877
+ minute = new Date(1970, 0, 1, 0, viewDate.minute - (midIndex - i) * options.minuteStep);
3878
+ minutes.push({date: minute, label: formatDate(minute, minutesFormat), selected: $timepicker.$date && $timepicker.$isSelected(minute, 1), disabled: $timepicker.$isDisabled(minute, 1)});
3451
3879
  }
3452
3880
 
3453
- // Activate proper element
3454
- for (var i = sortedElements.length; i--;) {
3455
- if(angular.isUndefined(sortedElements[i].offsetTop) || sortedElements[i].offsetTop === null) continue;
3456
- if(activeTarget === sortedElements[i].target) continue;
3457
- if(scrollTop < sortedElements[i].offsetTop) continue;
3458
- if(sortedElements[i + 1] && scrollTop > sortedElements[i + 1].offsetTop) continue;
3459
- return $scrollspy.$activateElement(sortedElements[i]);
3881
+ var rows = [];
3882
+ for(i = 0; i < options.length; i++) {
3883
+ rows.push([hours[i], minutes[i]]);
3460
3884
  }
3885
+ scope.rows = rows;
3886
+ scope.showAM = showAM;
3887
+ scope.isAM = ($timepicker.$date || hours[midIndex].date).getHours() < 12;
3888
+ scope.timeSeparator = timeSeparator;
3889
+ $timepicker.$isBuilt = true;
3890
+ };
3461
3891
 
3892
+ $timepicker.$isSelected = function(date, index) {
3893
+ if(!$timepicker.$date) return false;
3894
+ else if(index === 0) {
3895
+ return date.getHours() === $timepicker.$date.getHours();
3896
+ } else if(index === 1) {
3897
+ return date.getMinutes() === $timepicker.$date.getMinutes();
3898
+ }
3462
3899
  };
3463
3900
 
3464
- $scrollspy.checkPositionWithEventLoop = function() {
3465
- // IE 9 throws an error if we use 'this' instead of '$scrollspy'
3466
- // in this setTimeout call
3467
- setTimeout($scrollspy.checkPosition, 1);
3901
+ $timepicker.$isDisabled = function(date, index) {
3902
+ var selectedTime;
3903
+ if(index === 0) {
3904
+ selectedTime = date.getTime() + viewDate.minute * 6e4;
3905
+ } else if(index === 1) {
3906
+ selectedTime = date.getTime() + viewDate.hour * 36e5;
3907
+ }
3908
+ return selectedTime < options.minTime * 1 || selectedTime > options.maxTime * 1;
3468
3909
  };
3469
3910
 
3470
- // Protected methods
3911
+ scope.$arrowAction = function (value, index) {
3912
+ if (options.arrowBehavior === 'picker') {
3913
+ $timepicker.$setTimeByStep(value,index);
3914
+ } else {
3915
+ $timepicker.$moveIndex(value,index);
3916
+ }
3917
+ };
3471
3918
 
3472
- $scrollspy.$activateElement = function(element) {
3473
- if(activeTarget) {
3474
- var activeElement = $scrollspy.$getTrackedElement(activeTarget);
3475
- if(activeElement) {
3476
- activeElement.source.removeClass('active');
3477
- if(nodeName(activeElement.source, 'li') && nodeName(activeElement.source.parent().parent(), 'li')) {
3478
- activeElement.source.parent().parent().removeClass('active');
3479
- }
3480
- }
3919
+ $timepicker.$setTimeByStep = function(value, index) {
3920
+ var newDate = new Date($timepicker.$date);
3921
+ var hours = newDate.getHours(), hoursLength = formatDate(newDate, hoursFormat).length;
3922
+ var minutes = newDate.getMinutes(), minutesLength = formatDate(newDate, minutesFormat).length;
3923
+ if (index === 0) {
3924
+ newDate.setHours(hours - (parseInt(options.hourStep, 10) * value));
3481
3925
  }
3482
- activeTarget = element.target;
3483
- element.source.addClass('active');
3484
- if(nodeName(element.source, 'li') && nodeName(element.source.parent().parent(), 'li')) {
3485
- element.source.parent().parent().addClass('active');
3926
+ else {
3927
+ newDate.setMinutes(minutes - (parseInt(options.minuteStep, 10) * value));
3486
3928
  }
3929
+ $timepicker.select(newDate, index, true);
3487
3930
  };
3488
3931
 
3489
- $scrollspy.$getTrackedElement = function(target) {
3490
- return trackedElements.filter(function(obj) {
3491
- return obj.target === target;
3492
- })[0];
3932
+ $timepicker.$moveIndex = function(value, index) {
3933
+ var targetDate;
3934
+ if(index === 0) {
3935
+ targetDate = new Date(1970, 0, 1, viewDate.hour + (value * options.length), viewDate.minute);
3936
+ angular.extend(viewDate, {hour: targetDate.getHours()});
3937
+ } else if(index === 1) {
3938
+ targetDate = new Date(1970, 0, 1, viewDate.hour, viewDate.minute + (value * options.length * options.minuteStep));
3939
+ angular.extend(viewDate, {minute: targetDate.getMinutes()});
3940
+ }
3941
+ $timepicker.$build();
3493
3942
  };
3494
3943
 
3495
- // Track offsets behavior
3944
+ $timepicker.$onMouseDown = function(evt) {
3945
+ // Prevent blur on mousedown on .dropdown-menu
3946
+ if(evt.target.nodeName.toLowerCase() !== 'input') evt.preventDefault();
3947
+ evt.stopPropagation();
3948
+ // Emulate click for mobile devices
3949
+ if(isTouch) {
3950
+ var targetEl = angular.element(evt.target);
3951
+ if(targetEl[0].nodeName.toLowerCase() !== 'button') {
3952
+ targetEl = targetEl.parent();
3953
+ }
3954
+ targetEl.triggerHandler('click');
3955
+ }
3956
+ };
3496
3957
 
3497
- $scrollspy.checkOffsets = function() {
3958
+ $timepicker.$onKeyDown = function(evt) {
3959
+ if (!/(38|37|39|40|13)/.test(evt.keyCode) || evt.shiftKey || evt.altKey) return;
3960
+ evt.preventDefault();
3961
+ evt.stopPropagation();
3498
3962
 
3499
- angular.forEach(trackedElements, function(trackedElement) {
3500
- var targetElement = document.querySelector(trackedElement.target);
3501
- trackedElement.offsetTop = targetElement ? dimensions.offset(targetElement).top : null;
3502
- if(options.offset && trackedElement.offsetTop !== null) trackedElement.offsetTop -= options.offset * 1;
3503
- });
3963
+ // Close on enter
3964
+ if(evt.keyCode === 13) return $timepicker.hide(true);
3504
3965
 
3505
- sortedElements = trackedElements
3506
- .filter(function(el) {
3507
- return el.offsetTop !== null;
3508
- })
3509
- .sort(function(a, b) {
3510
- return a.offsetTop - b.offsetTop;
3511
- });
3966
+ // Navigate with keyboard
3967
+ var newDate = new Date($timepicker.$date);
3968
+ var hours = newDate.getHours(), hoursLength = formatDate(newDate, hoursFormat).length;
3969
+ var minutes = newDate.getMinutes(), minutesLength = formatDate(newDate, minutesFormat).length;
3970
+ var lateralMove = /(37|39)/.test(evt.keyCode);
3971
+ var count = 2 + showAM * 1;
3512
3972
 
3513
- debouncedCheckPosition();
3973
+ // Navigate indexes (left, right)
3974
+ if (lateralMove) {
3975
+ if(evt.keyCode === 37) selectedIndex = selectedIndex < 1 ? count - 1 : selectedIndex - 1;
3976
+ else if(evt.keyCode === 39) selectedIndex = selectedIndex < count - 1 ? selectedIndex + 1 : 0;
3977
+ }
3514
3978
 
3979
+ // Update values (up, down)
3980
+ var selectRange = [0, hoursLength];
3981
+ if(selectedIndex === 0) {
3982
+ if(evt.keyCode === 38) newDate.setHours(hours - parseInt(options.hourStep, 10));
3983
+ else if(evt.keyCode === 40) newDate.setHours(hours + parseInt(options.hourStep, 10));
3984
+ // re-calculate hours length because we have changed hours value
3985
+ hoursLength = formatDate(newDate, hoursFormat).length;
3986
+ selectRange = [0, hoursLength];
3987
+ } else if(selectedIndex === 1) {
3988
+ if(evt.keyCode === 38) newDate.setMinutes(minutes - parseInt(options.minuteStep, 10));
3989
+ else if(evt.keyCode === 40) newDate.setMinutes(minutes + parseInt(options.minuteStep, 10));
3990
+ // re-calculate minutes length because we have changes minutes value
3991
+ minutesLength = formatDate(newDate, minutesFormat).length;
3992
+ selectRange = [hoursLength + 1, hoursLength + 1 + minutesLength];
3993
+ } else if(selectedIndex === 2) {
3994
+ if(!lateralMove) $timepicker.switchMeridian();
3995
+ selectRange = [hoursLength + 1 + minutesLength + 1, hoursLength + 1 + minutesLength + 3];
3996
+ }
3997
+ $timepicker.select(newDate, selectedIndex, true);
3998
+ createSelection(selectRange[0], selectRange[1]);
3999
+ parentScope.$digest();
3515
4000
  };
3516
4001
 
3517
- $scrollspy.trackElement = function(target, source) {
3518
- trackedElements.push({target: target, source: source});
4002
+ // Private
4003
+
4004
+ function createSelection(start, end) {
4005
+ if(element[0].createTextRange) {
4006
+ var selRange = element[0].createTextRange();
4007
+ selRange.collapse(true);
4008
+ selRange.moveStart('character', start);
4009
+ selRange.moveEnd('character', end);
4010
+ selRange.select();
4011
+ } else if(element[0].setSelectionRange) {
4012
+ element[0].setSelectionRange(start, end);
4013
+ } else if(angular.isUndefined(element[0].selectionStart)) {
4014
+ element[0].selectionStart = start;
4015
+ element[0].selectionEnd = end;
4016
+ }
4017
+ }
4018
+
4019
+ function focusElement() {
4020
+ element[0].focus();
4021
+ }
4022
+
4023
+ // Overrides
4024
+
4025
+ var _init = $timepicker.init;
4026
+ $timepicker.init = function() {
4027
+ if(isNative && options.useNative) {
4028
+ element.prop('type', 'time');
4029
+ element.css('-webkit-appearance', 'textfield');
4030
+ return;
4031
+ } else if(isTouch) {
4032
+ element.prop('type', 'text');
4033
+ element.attr('readonly', 'true');
4034
+ element.on('click', focusElement);
4035
+ }
4036
+ _init();
3519
4037
  };
3520
4038
 
3521
- $scrollspy.untrackElement = function(target, source) {
3522
- var toDelete;
3523
- for (var i = trackedElements.length; i--;) {
3524
- if(trackedElements[i].target === target && trackedElements[i].source === source) {
3525
- toDelete = i;
3526
- break;
3527
- }
4039
+ var _destroy = $timepicker.destroy;
4040
+ $timepicker.destroy = function() {
4041
+ if(isNative && options.useNative) {
4042
+ element.off('click', focusElement);
3528
4043
  }
3529
- trackedElements = trackedElements.splice(toDelete, 1);
4044
+ _destroy();
3530
4045
  };
3531
4046
 
3532
- $scrollspy.activate = function(i) {
3533
- trackedElements[i].addClass('active');
4047
+ var _show = $timepicker.show;
4048
+ $timepicker.show = function() {
4049
+ _show();
4050
+ // use timeout to hookup the events to prevent
4051
+ // event bubbling from being processed imediately.
4052
+ $timeout(function() {
4053
+ $timepicker.$element.on(isTouch ? 'touchstart' : 'mousedown', $timepicker.$onMouseDown);
4054
+ if(options.keyboard) {
4055
+ element.on('keydown', $timepicker.$onKeyDown);
4056
+ }
4057
+ }, 0, false);
3534
4058
  };
3535
4059
 
3536
- // Initialize plugin
4060
+ var _hide = $timepicker.hide;
4061
+ $timepicker.hide = function(blur) {
4062
+ if(!$timepicker.$isShown) return;
4063
+ $timepicker.$element.off(isTouch ? 'touchstart' : 'mousedown', $timepicker.$onMouseDown);
4064
+ if(options.keyboard) {
4065
+ element.off('keydown', $timepicker.$onKeyDown);
4066
+ }
4067
+ _hide(blur);
4068
+ };
3537
4069
 
3538
- $scrollspy.init();
3539
- return $scrollspy;
4070
+ return $timepicker;
3540
4071
 
3541
4072
  }
3542
4073
 
3543
- return ScrollSpyFactory;
4074
+ timepickerFactory.defaults = defaults;
4075
+ return timepickerFactory;
3544
4076
 
3545
4077
  }];
3546
4078
 
3547
4079
  })
3548
4080
 
3549
- .directive('bsScrollspy', ["$rootScope", "debounce", "dimensions", "$scrollspy", function($rootScope, debounce, dimensions, $scrollspy) {
4081
+
4082
+ .directive('bsTimepicker', ["$window", "$parse", "$q", "$dateFormatter", "$dateParser", "$timepicker", function($window, $parse, $q, $dateFormatter, $dateParser, $timepicker) {
4083
+
4084
+ var defaults = $timepicker.defaults;
4085
+ var isNative = /(ip(a|o)d|iphone|android)/ig.test($window.navigator.userAgent);
4086
+ var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
3550
4087
 
3551
4088
  return {
3552
4089
  restrict: 'EAC',
3553
- link: function postLink(scope, element, attr) {
4090
+ require: 'ngModel',
4091
+ link: function postLink(scope, element, attr, controller) {
3554
4092
 
3555
- var options = {scope: scope};
3556
- angular.forEach(['offset', 'target'], function(key) {
4093
+ // Directive options
4094
+ var options = {scope: scope, controller: controller};
4095
+ angular.forEach(['placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'template', 'autoclose', 'timeType', 'timeFormat', 'timezone', 'modelTimeFormat', 'useNative', 'hourStep', 'minuteStep', 'length', 'arrowBehavior', 'iconUp', 'iconDown', 'id'], function(key) {
3557
4096
  if(angular.isDefined(attr[key])) options[key] = attr[key];
3558
4097
  });
3559
4098
 
3560
- var scrollspy = $scrollspy(options);
3561
- scrollspy.trackElement(options.target, element);
3562
-
3563
- scope.$on('$destroy', function() {
3564
- if (scrollspy) {
3565
- scrollspy.untrackElement(options.target, element);
3566
- scrollspy.destroy();
3567
- }
3568
- options = null;
3569
- scrollspy = null;
4099
+ // use string regex match for boolean values
4100
+ var falseValueRegExp = /^(false|0|)$/;
4101
+ angular.forEach(['roundDisplay'], function(key) {
4102
+ if(angular.isDefined(attr[key])) options[key] = !falseValueRegExp.test(attr[key]);
3570
4103
  });
3571
4104
 
3572
- }
3573
- };
4105
+ // Visibility binding support
4106
+ attr.bsShow && scope.$watch(attr.bsShow, function(newValue, oldValue) {
4107
+ if(!timepicker || !angular.isDefined(newValue)) return;
4108
+ if(angular.isString(newValue)) newValue = !!newValue.match(/true|,?(timepicker),?/i);
4109
+ newValue === true ? timepicker.show() : timepicker.hide();
4110
+ });
3574
4111
 
3575
- }])
4112
+ // Initialize timepicker
4113
+ if(isNative && (options.useNative || defaults.useNative)) options.timeFormat = 'HH:mm';
4114
+ var timepicker = $timepicker(element, controller, options);
4115
+ options = timepicker.$options;
3576
4116
 
4117
+ var lang = options.lang;
4118
+ var formatDate = function(date, format, timezone) {
4119
+ return $dateFormatter.formatDate(date, format, lang, timezone);
4120
+ };
3577
4121
 
3578
- .directive('bsScrollspyList', ["$rootScope", "debounce", "dimensions", "$scrollspy", function($rootScope, debounce, dimensions, $scrollspy) {
4122
+ // Initialize parser
4123
+ var dateParser = $dateParser({format: options.timeFormat, lang: lang});
3579
4124
 
3580
- return {
3581
- restrict: 'A',
3582
- compile: function postLink(element, attr) {
3583
- var children = element[0].querySelectorAll('li > a[href]');
3584
- angular.forEach(children, function(child) {
3585
- var childEl = angular.element(child);
3586
- childEl.parent().attr('bs-scrollspy', '').attr('data-target', childEl.attr('href'));
4125
+ // Observe attributes for changes
4126
+ angular.forEach(['minTime', 'maxTime'], function(key) {
4127
+ // console.warn('attr.$observe(%s)', key, attr[key]);
4128
+ angular.isDefined(attr[key]) && attr.$observe(key, function(newValue) {
4129
+ timepicker.$options[key] = dateParser.getTimeForAttribute(key, newValue);
4130
+ !isNaN(timepicker.$options[key]) && timepicker.$build();
4131
+ validateAgainstMinMaxTime(controller.$dateValue);
4132
+ });
3587
4133
  });
3588
- }
3589
-
3590
- };
3591
4134
 
3592
- }]);
4135
+ // Watch model for changes
4136
+ scope.$watch(attr.ngModel, function(newValue, oldValue) {
4137
+ // console.warn('scope.$watch(%s)', attr.ngModel, newValue, oldValue, controller.$dateValue);
4138
+ timepicker.update(controller.$dateValue);
4139
+ }, true);
3593
4140
 
3594
- // Source: timepicker.js
3595
- angular.module('mgcrea.ngStrap.timepicker', [
3596
- 'mgcrea.ngStrap.helpers.dateParser',
3597
- 'mgcrea.ngStrap.helpers.dateFormatter',
3598
- 'mgcrea.ngStrap.tooltip'])
4141
+ function validateAgainstMinMaxTime(parsedTime) {
4142
+ if (!angular.isDate(parsedTime)) return;
4143
+ var isMinValid = isNaN(options.minTime) || new Date(parsedTime.getTime()).setFullYear(1970, 0, 1) >= options.minTime;
4144
+ var isMaxValid = isNaN(options.maxTime) || new Date(parsedTime.getTime()).setFullYear(1970, 0, 1) <= options.maxTime;
4145
+ var isValid = isMinValid && isMaxValid;
4146
+ controller.$setValidity('date', isValid);
4147
+ controller.$setValidity('min', isMinValid);
4148
+ controller.$setValidity('max', isMaxValid);
4149
+ // Only update the model when we have a valid date
4150
+ if(!isValid) {
4151
+ return;
4152
+ }
4153
+ controller.$dateValue = parsedTime;
4154
+ }
3599
4155
 
3600
- .provider('$timepicker', function() {
4156
+ // viewValue -> $parsers -> modelValue
4157
+ controller.$parsers.unshift(function(viewValue) {
4158
+ // console.warn('$parser("%s"): viewValue=%o', element.attr('ng-model'), viewValue);
4159
+ var date;
4160
+ // Null values should correctly reset the model value & validity
4161
+ if(!viewValue) {
4162
+ // BREAKING CHANGE:
4163
+ // return null (not undefined) when input value is empty, so angularjs 1.3
4164
+ // ngModelController can go ahead and run validators, like ngRequired
4165
+ controller.$setValidity('date', true);
4166
+ return null;
4167
+ }
4168
+ var parsedTime = angular.isDate(viewValue) ? viewValue : dateParser.parse(viewValue, controller.$dateValue);
4169
+ if(!parsedTime || isNaN(parsedTime.getTime())) {
4170
+ controller.$setValidity('date', false);
4171
+ // return undefined, causes ngModelController to
4172
+ // invalidate model value
4173
+ return;
4174
+ } else {
4175
+ validateAgainstMinMaxTime(parsedTime);
4176
+ }
3601
4177
 
3602
- var defaults = this.defaults = {
3603
- animation: 'am-fade',
3604
- prefixClass: 'timepicker',
3605
- placement: 'bottom-left',
3606
- template: 'timepicker/timepicker.tpl.html',
4178
+ if(options.timeType === 'string') {
4179
+ date = dateParser.timezoneOffsetAdjust(parsedTime, options.timezone, true);
4180
+ return formatDate(date, options.modelTimeFormat || options.timeFormat);
4181
+ }
4182
+ date = dateParser.timezoneOffsetAdjust(controller.$dateValue, options.timezone, true);
4183
+ if(options.timeType === 'number') {
4184
+ return date.getTime();
4185
+ } else if(options.timeType === 'unix') {
4186
+ return date.getTime() / 1000;
4187
+ } else if(options.timeType === 'iso') {
4188
+ return date.toISOString();
4189
+ } else {
4190
+ return new Date(date);
4191
+ }
4192
+ });
4193
+
4194
+ // modelValue -> $formatters -> viewValue
4195
+ controller.$formatters.push(function(modelValue) {
4196
+ // console.warn('$formatter("%s"): modelValue=%o (%o)', element.attr('ng-model'), modelValue, typeof modelValue);
4197
+ var date;
4198
+ if(angular.isUndefined(modelValue) || modelValue === null) {
4199
+ date = NaN;
4200
+ } else if(angular.isDate(modelValue)) {
4201
+ date = modelValue;
4202
+ } else if(options.timeType === 'string') {
4203
+ date = dateParser.parse(modelValue, null, options.modelTimeFormat);
4204
+ } else if(options.timeType === 'unix') {
4205
+ date = new Date(modelValue * 1000);
4206
+ } else {
4207
+ date = new Date(modelValue);
4208
+ }
4209
+ // Setup default value?
4210
+ // if(isNaN(date.getTime())) date = new Date(new Date().setMinutes(0) + 36e5);
4211
+ controller.$dateValue = dateParser.timezoneOffsetAdjust(date, options.timezone);
4212
+ return getTimeFormattedString();
4213
+ });
4214
+
4215
+ // viewValue -> element
4216
+ controller.$render = function() {
4217
+ // console.warn('$render("%s"): viewValue=%o', element.attr('ng-model'), controller.$viewValue);
4218
+ element.val(getTimeFormattedString());
4219
+ };
4220
+
4221
+ function getTimeFormattedString() {
4222
+ return !controller.$dateValue || isNaN(controller.$dateValue.getTime()) ? '' : formatDate(controller.$dateValue, options.timeFormat);
4223
+ }
4224
+
4225
+ // Garbage collection
4226
+ scope.$on('$destroy', function() {
4227
+ if (timepicker) timepicker.destroy();
4228
+ options = null;
4229
+ timepicker = null;
4230
+ });
4231
+
4232
+ }
4233
+ };
4234
+
4235
+ }]);
4236
+
4237
+ // Source: typeahead.js
4238
+ angular.module('mgcrea.ngStrap.typeahead', ['mgcrea.ngStrap.tooltip', 'mgcrea.ngStrap.helpers.parseOptions'])
4239
+
4240
+ .provider('$typeahead', function() {
4241
+
4242
+ var defaults = this.defaults = {
4243
+ animation: 'am-fade',
4244
+ prefixClass: 'typeahead',
4245
+ prefixEvent: '$typeahead',
4246
+ placement: 'bottom-left',
4247
+ template: 'typeahead/typeahead.tpl.html',
3607
4248
  trigger: 'focus',
3608
4249
  container: false,
3609
4250
  keyboard: true,
3610
4251
  html: false,
3611
4252
  delay: 0,
3612
- // lang: $locale.id,
3613
- useNative: true,
3614
- timeType: 'date',
3615
- timeFormat: 'shortTime',
3616
- modelTimeFormat: null,
3617
- autoclose: false,
3618
- minTime: -Infinity,
3619
- maxTime: +Infinity,
3620
- length: 5,
3621
- hourStep: 1,
3622
- minuteStep: 5,
3623
- iconUp: 'glyphicon glyphicon-chevron-up',
3624
- iconDown: 'glyphicon glyphicon-chevron-down',
3625
- arrowBehavior: 'pager'
4253
+ minLength: 1,
4254
+ filter: 'filter',
4255
+ limit: 6,
4256
+ autoSelect: false,
4257
+ comparator: ''
3626
4258
  };
3627
4259
 
3628
- this.$get = ["$window", "$document", "$rootScope", "$sce", "$dateFormatter", "$tooltip", "$timeout", function($window, $document, $rootScope, $sce, $dateFormatter, $tooltip, $timeout) {
4260
+ this.$get = ["$window", "$rootScope", "$tooltip", "$timeout", function($window, $rootScope, $tooltip, $timeout) {
3629
4261
 
3630
4262
  var bodyEl = angular.element($window.document.body);
3631
- var isNative = /(ip(a|o)d|iphone|android)/ig.test($window.navigator.userAgent);
3632
- var isTouch = ('createTouch' in $window.document) && isNative;
3633
- if(!defaults.lang) defaults.lang = $dateFormatter.getDefaultLocale();
3634
4263
 
3635
- function timepickerFactory(element, controller, config) {
3636
-
3637
- var $timepicker = $tooltip(element, angular.extend({}, defaults, config));
3638
- var parentScope = config.scope;
3639
- var options = $timepicker.$options;
3640
- var scope = $timepicker.$scope;
3641
-
3642
- var lang = options.lang;
3643
- var formatDate = function(date, format) {
3644
- return $dateFormatter.formatDate(date, format, lang);
3645
- };
3646
-
3647
- // View vars
3648
-
3649
- var selectedIndex = 0;
3650
- var startDate = controller.$dateValue || new Date();
3651
- var viewDate = {hour: startDate.getHours(), meridian: startDate.getHours() < 12, minute: startDate.getMinutes(), second: startDate.getSeconds(), millisecond: startDate.getMilliseconds()};
3652
-
3653
- var format = $dateFormatter.getDatetimeFormat(options.timeFormat, lang);
4264
+ function TypeaheadFactory(element, controller, config) {
3654
4265
 
3655
- var hoursFormat = $dateFormatter.hoursFormat(format),
3656
- timeSeparator = $dateFormatter.timeSeparator(format),
3657
- minutesFormat = $dateFormatter.minutesFormat(format),
3658
- showAM = $dateFormatter.showAM(format);
4266
+ var $typeahead = {};
3659
4267
 
3660
- scope.$iconUp = options.iconUp;
3661
- scope.$iconDown = options.iconDown;
4268
+ // Common vars
4269
+ var options = angular.extend({}, defaults, config);
3662
4270
 
3663
- // Scope methods
4271
+ $typeahead = $tooltip(element, options);
4272
+ var parentScope = config.scope;
4273
+ var scope = $typeahead.$scope;
3664
4274
 
3665
- scope.$select = function(date, index) {
3666
- $timepicker.select(date, index);
3667
- };
3668
- scope.$moveIndex = function(value, index) {
3669
- $timepicker.$moveIndex(value, index);
3670
- };
3671
- scope.$switchMeridian = function(date) {
3672
- $timepicker.switchMeridian(date);
4275
+ scope.$resetMatches = function(){
4276
+ scope.$matches = [];
4277
+ scope.$activeIndex = options.autoSelect ? 0 : -1; // If set to 0, the first match will be highlighted
3673
4278
  };
4279
+ scope.$resetMatches();
3674
4280
 
3675
- // Public methods
3676
-
3677
- $timepicker.update = function(date) {
3678
- // console.warn('$timepicker.update() newValue=%o', date);
3679
- if(angular.isDate(date) && !isNaN(date.getTime())) {
3680
- $timepicker.$date = date;
3681
- angular.extend(viewDate, {hour: date.getHours(), minute: date.getMinutes(), second: date.getSeconds(), millisecond: date.getMilliseconds()});
3682
- $timepicker.$build();
3683
- } else if(!$timepicker.$isBuilt) {
3684
- $timepicker.$build();
3685
- }
4281
+ scope.$activate = function(index) {
4282
+ scope.$$postDigest(function() {
4283
+ $typeahead.activate(index);
4284
+ });
3686
4285
  };
3687
4286
 
3688
- $timepicker.select = function(date, index, keep) {
3689
- // console.warn('$timepicker.select', date, scope.$mode);
3690
- if(!controller.$dateValue || isNaN(controller.$dateValue.getTime())) controller.$dateValue = new Date(1970, 0, 1);
3691
- if(!angular.isDate(date)) date = new Date(date);
3692
- if(index === 0) controller.$dateValue.setHours(date.getHours());
3693
- else if(index === 1) controller.$dateValue.setMinutes(date.getMinutes());
3694
- controller.$setViewValue(angular.copy(controller.$dateValue));
3695
- controller.$render();
3696
- if(options.autoclose && !keep) {
3697
- $timeout(function() { $timepicker.hide(true); });
3698
- }
4287
+ scope.$select = function(index, evt) {
4288
+ scope.$$postDigest(function() {
4289
+ $typeahead.select(index);
4290
+ });
3699
4291
  };
3700
4292
 
3701
- $timepicker.switchMeridian = function(date) {
3702
- if (!controller.$dateValue || isNaN(controller.$dateValue.getTime())) {
3703
- return;
3704
- }
3705
- var hours = (date || controller.$dateValue).getHours();
3706
- controller.$dateValue.setHours(hours < 12 ? hours + 12 : hours - 12);
3707
- controller.$setViewValue(angular.copy(controller.$dateValue));
3708
- controller.$render();
4293
+ scope.$isVisible = function() {
4294
+ return $typeahead.$isVisible();
3709
4295
  };
3710
4296
 
3711
- // Protected methods
3712
-
3713
- $timepicker.$build = function() {
3714
- // console.warn('$timepicker.$build() viewDate=%o', viewDate);
3715
- var i, midIndex = scope.midIndex = parseInt(options.length / 2, 10);
3716
- var hours = [], hour;
3717
- for(i = 0; i < options.length; i++) {
3718
- hour = new Date(1970, 0, 1, viewDate.hour - (midIndex - i) * options.hourStep);
3719
- hours.push({date: hour, label: formatDate(hour, hoursFormat), selected: $timepicker.$date && $timepicker.$isSelected(hour, 0), disabled: $timepicker.$isDisabled(hour, 0)});
3720
- }
3721
- var minutes = [], minute;
3722
- for(i = 0; i < options.length; i++) {
3723
- minute = new Date(1970, 0, 1, 0, viewDate.minute - (midIndex - i) * options.minuteStep);
3724
- minutes.push({date: minute, label: formatDate(minute, minutesFormat), selected: $timepicker.$date && $timepicker.$isSelected(minute, 1), disabled: $timepicker.$isDisabled(minute, 1)});
3725
- }
3726
-
3727
- var rows = [];
3728
- for(i = 0; i < options.length; i++) {
3729
- rows.push([hours[i], minutes[i]]);
3730
- }
3731
- scope.rows = rows;
3732
- scope.showAM = showAM;
3733
- scope.isAM = ($timepicker.$date || hours[midIndex].date).getHours() < 12;
3734
- scope.timeSeparator = timeSeparator;
3735
- $timepicker.$isBuilt = true;
3736
- };
4297
+ // Public methods
3737
4298
 
3738
- $timepicker.$isSelected = function(date, index) {
3739
- if(!$timepicker.$date) return false;
3740
- else if(index === 0) {
3741
- return date.getHours() === $timepicker.$date.getHours();
3742
- } else if(index === 1) {
3743
- return date.getMinutes() === $timepicker.$date.getMinutes();
3744
- }
4299
+ $typeahead.update = function(matches) {
4300
+ scope.$matches = matches;
4301
+ if(scope.$activeIndex >= matches.length) {
4302
+ scope.$activeIndex = options.autoSelect ? 0: -1;
4303
+ }
4304
+
4305
+ // When the placement is not one of the bottom placements, re-calc the positioning
4306
+ // so the results render correctly.
4307
+ if (/^(bottom|bottom-left|bottom-right)$/.test(options.placement)) return;
4308
+
4309
+ // wrap in a $timeout so the results are updated
4310
+ // before repositioning
4311
+ $timeout($typeahead.$applyPlacement);
3745
4312
  };
3746
4313
 
3747
- $timepicker.$isDisabled = function(date, index) {
3748
- var selectedTime;
3749
- if(index === 0) {
3750
- selectedTime = date.getTime() + viewDate.minute * 6e4;
3751
- } else if(index === 1) {
3752
- selectedTime = date.getTime() + viewDate.hour * 36e5;
3753
- }
3754
- return selectedTime < options.minTime * 1 || selectedTime > options.maxTime * 1;
4314
+ $typeahead.activate = function(index) {
4315
+ scope.$activeIndex = index;
3755
4316
  };
3756
4317
 
3757
- scope.$arrowAction = function (value, index) {
3758
- if (options.arrowBehavior === 'picker') {
3759
- $timepicker.$setTimeByStep(value,index);
3760
- } else {
3761
- $timepicker.$moveIndex(value,index);
3762
- }
4318
+ $typeahead.select = function(index) {
4319
+ var value = scope.$matches[index].value;
4320
+ // console.log('$setViewValue', value);
4321
+ controller.$setViewValue(value);
4322
+ controller.$render();
4323
+ scope.$resetMatches();
4324
+ if(parentScope) parentScope.$digest();
4325
+ // Emit event
4326
+ scope.$emit(options.prefixEvent + '.select', value, index, $typeahead);
3763
4327
  };
3764
4328
 
3765
- $timepicker.$setTimeByStep = function(value, index) {
3766
- var newDate = new Date($timepicker.$date);
3767
- var hours = newDate.getHours(), hoursLength = formatDate(newDate, hoursFormat).length;
3768
- var minutes = newDate.getMinutes(), minutesLength = formatDate(newDate, minutesFormat).length;
3769
- if (index === 0) {
3770
- newDate.setHours(hours - (parseInt(options.hourStep, 10) * value));
3771
- }
3772
- else {
3773
- newDate.setMinutes(minutes - (parseInt(options.minuteStep, 10) * value));
3774
- }
3775
- $timepicker.select(newDate, index, true);
3776
- };
4329
+ // Protected methods
3777
4330
 
3778
- $timepicker.$moveIndex = function(value, index) {
3779
- var targetDate;
3780
- if(index === 0) {
3781
- targetDate = new Date(1970, 0, 1, viewDate.hour + (value * options.length), viewDate.minute);
3782
- angular.extend(viewDate, {hour: targetDate.getHours()});
3783
- } else if(index === 1) {
3784
- targetDate = new Date(1970, 0, 1, viewDate.hour, viewDate.minute + (value * options.length * options.minuteStep));
3785
- angular.extend(viewDate, {minute: targetDate.getMinutes()});
4331
+ $typeahead.$isVisible = function() {
4332
+ if(!options.minLength || !controller) {
4333
+ return !!scope.$matches.length;
3786
4334
  }
3787
- $timepicker.$build();
4335
+ // minLength support
4336
+ return scope.$matches.length && angular.isString(controller.$viewValue) && controller.$viewValue.length >= options.minLength;
3788
4337
  };
3789
4338
 
3790
- $timepicker.$onMouseDown = function(evt) {
3791
- // Prevent blur on mousedown on .dropdown-menu
3792
- if(evt.target.nodeName.toLowerCase() !== 'input') evt.preventDefault();
3793
- evt.stopPropagation();
3794
- // Emulate click for mobile devices
3795
- if(isTouch) {
3796
- var targetEl = angular.element(evt.target);
3797
- if(targetEl[0].nodeName.toLowerCase() !== 'button') {
3798
- targetEl = targetEl.parent();
3799
- }
3800
- targetEl.triggerHandler('click');
4339
+ $typeahead.$getIndex = function(value) {
4340
+ var l = scope.$matches.length, i = l;
4341
+ if(!l) return;
4342
+ for(i = l; i--;) {
4343
+ if(scope.$matches[i].value === value) break;
3801
4344
  }
4345
+ if(i < 0) return;
4346
+ return i;
3802
4347
  };
3803
4348
 
3804
- $timepicker.$onKeyDown = function(evt) {
3805
- if (!/(38|37|39|40|13)/.test(evt.keyCode) || evt.shiftKey || evt.altKey) return;
4349
+ $typeahead.$onMouseDown = function(evt) {
4350
+ // Prevent blur on mousedown
3806
4351
  evt.preventDefault();
3807
4352
  evt.stopPropagation();
4353
+ };
3808
4354
 
3809
- // Close on enter
3810
- if(evt.keyCode === 13) return $timepicker.hide(true);
3811
-
3812
- // Navigate with keyboard
3813
- var newDate = new Date($timepicker.$date);
3814
- var hours = newDate.getHours(), hoursLength = formatDate(newDate, hoursFormat).length;
3815
- var minutes = newDate.getMinutes(), minutesLength = formatDate(newDate, minutesFormat).length;
3816
- var lateralMove = /(37|39)/.test(evt.keyCode);
3817
- var count = 2 + showAM * 1;
3818
-
3819
- // Navigate indexes (left, right)
3820
- if (lateralMove) {
3821
- if(evt.keyCode === 37) selectedIndex = selectedIndex < 1 ? count - 1 : selectedIndex - 1;
3822
- else if(evt.keyCode === 39) selectedIndex = selectedIndex < count - 1 ? selectedIndex + 1 : 0;
3823
- }
3824
-
3825
- // Update values (up, down)
3826
- var selectRange = [0, hoursLength];
3827
- if(selectedIndex === 0) {
3828
- if(evt.keyCode === 38) newDate.setHours(hours - parseInt(options.hourStep, 10));
3829
- else if(evt.keyCode === 40) newDate.setHours(hours + parseInt(options.hourStep, 10));
3830
- // re-calculate hours length because we have changed hours value
3831
- hoursLength = formatDate(newDate, hoursFormat).length;
3832
- selectRange = [0, hoursLength];
3833
- } else if(selectedIndex === 1) {
3834
- if(evt.keyCode === 38) newDate.setMinutes(minutes - parseInt(options.minuteStep, 10));
3835
- else if(evt.keyCode === 40) newDate.setMinutes(minutes + parseInt(options.minuteStep, 10));
3836
- // re-calculate minutes length because we have changes minutes value
3837
- minutesLength = formatDate(newDate, minutesFormat).length;
3838
- selectRange = [hoursLength + 1, hoursLength + 1 + minutesLength];
3839
- } else if(selectedIndex === 2) {
3840
- if(!lateralMove) $timepicker.switchMeridian();
3841
- selectRange = [hoursLength + 1 + minutesLength + 1, hoursLength + 1 + minutesLength + 3];
3842
- }
3843
- $timepicker.select(newDate, selectedIndex, true);
3844
- createSelection(selectRange[0], selectRange[1]);
3845
- parentScope.$digest();
3846
- };
3847
-
3848
- // Private
3849
-
3850
- function createSelection(start, end) {
3851
- if(element[0].createTextRange) {
3852
- var selRange = element[0].createTextRange();
3853
- selRange.collapse(true);
3854
- selRange.moveStart('character', start);
3855
- selRange.moveEnd('character', end);
3856
- selRange.select();
3857
- } else if(element[0].setSelectionRange) {
3858
- element[0].setSelectionRange(start, end);
3859
- } else if(angular.isUndefined(element[0].selectionStart)) {
3860
- element[0].selectionStart = start;
3861
- element[0].selectionEnd = end;
3862
- }
3863
- }
3864
-
3865
- function focusElement() {
3866
- element[0].focus();
3867
- }
3868
-
3869
- // Overrides
4355
+ $typeahead.$onKeyDown = function(evt) {
4356
+ if(!/(38|40|13)/.test(evt.keyCode)) return;
3870
4357
 
3871
- var _init = $timepicker.init;
3872
- $timepicker.init = function() {
3873
- if(isNative && options.useNative) {
3874
- element.prop('type', 'time');
3875
- element.css('-webkit-appearance', 'textfield');
3876
- return;
3877
- } else if(isTouch) {
3878
- element.prop('type', 'text');
3879
- element.attr('readonly', 'true');
3880
- element.on('click', focusElement);
4358
+ // Let ngSubmit pass if the typeahead tip is hidden
4359
+ if($typeahead.$isVisible()) {
4360
+ evt.preventDefault();
4361
+ evt.stopPropagation();
3881
4362
  }
3882
- _init();
3883
- };
3884
4363
 
3885
- var _destroy = $timepicker.destroy;
3886
- $timepicker.destroy = function() {
3887
- if(isNative && options.useNative) {
3888
- element.off('click', focusElement);
4364
+ // Select with enter
4365
+ if(evt.keyCode === 13 && scope.$matches.length) {
4366
+ $typeahead.select(scope.$activeIndex);
3889
4367
  }
3890
- _destroy();
4368
+
4369
+ // Navigate with keyboard
4370
+ else if(evt.keyCode === 38 && scope.$activeIndex > 0) scope.$activeIndex--;
4371
+ else if(evt.keyCode === 40 && scope.$activeIndex < scope.$matches.length - 1) scope.$activeIndex++;
4372
+ else if(angular.isUndefined(scope.$activeIndex)) scope.$activeIndex = 0;
4373
+ scope.$digest();
3891
4374
  };
3892
4375
 
3893
- var _show = $timepicker.show;
3894
- $timepicker.show = function() {
3895
- _show();
4376
+ // Overrides
4377
+
4378
+ var show = $typeahead.show;
4379
+ $typeahead.show = function() {
4380
+ show();
3896
4381
  // use timeout to hookup the events to prevent
3897
4382
  // event bubbling from being processed imediately.
3898
4383
  $timeout(function() {
3899
- $timepicker.$element.on(isTouch ? 'touchstart' : 'mousedown', $timepicker.$onMouseDown);
4384
+ $typeahead.$element.on('mousedown', $typeahead.$onMouseDown);
3900
4385
  if(options.keyboard) {
3901
- element.on('keydown', $timepicker.$onKeyDown);
4386
+ element.on('keydown', $typeahead.$onKeyDown);
3902
4387
  }
3903
4388
  }, 0, false);
3904
4389
  };
3905
4390
 
3906
- var _hide = $timepicker.hide;
3907
- $timepicker.hide = function(blur) {
3908
- if(!$timepicker.$isShown) return;
3909
- $timepicker.$element.off(isTouch ? 'touchstart' : 'mousedown', $timepicker.$onMouseDown);
4391
+ var hide = $typeahead.hide;
4392
+ $typeahead.hide = function() {
4393
+ $typeahead.$element.off('mousedown', $typeahead.$onMouseDown);
3910
4394
  if(options.keyboard) {
3911
- element.off('keydown', $timepicker.$onKeyDown);
4395
+ element.off('keydown', $typeahead.$onKeyDown);
3912
4396
  }
3913
- _hide(blur);
4397
+ if(!options.autoSelect)
4398
+ $typeahead.activate(-1);
4399
+ hide();
3914
4400
  };
3915
4401
 
3916
- return $timepicker;
4402
+ return $typeahead;
3917
4403
 
3918
4404
  }
3919
4405
 
3920
- timepickerFactory.defaults = defaults;
3921
- return timepickerFactory;
4406
+ TypeaheadFactory.defaults = defaults;
4407
+ return TypeaheadFactory;
3922
4408
 
3923
4409
  }];
3924
4410
 
3925
4411
  })
3926
4412
 
4413
+ .directive('bsTypeahead', ["$window", "$parse", "$q", "$typeahead", "$parseOptions", function($window, $parse, $q, $typeahead, $parseOptions) {
3927
4414
 
3928
- .directive('bsTimepicker', ["$window", "$parse", "$q", "$dateFormatter", "$dateParser", "$timepicker", function($window, $parse, $q, $dateFormatter, $dateParser, $timepicker) {
3929
-
3930
- var defaults = $timepicker.defaults;
3931
- var isNative = /(ip(a|o)d|iphone|android)/ig.test($window.navigator.userAgent);
3932
- var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
4415
+ var defaults = $typeahead.defaults;
3933
4416
 
3934
4417
  return {
3935
4418
  restrict: 'EAC',
@@ -3937,131 +4420,86 @@ angular.module('mgcrea.ngStrap.timepicker', [
3937
4420
  link: function postLink(scope, element, attr, controller) {
3938
4421
 
3939
4422
  // Directive options
3940
- var options = {scope: scope, controller: controller};
3941
- angular.forEach(['placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'template', 'autoclose', 'timeType', 'timeFormat', 'modelTimeFormat', 'useNative', 'hourStep', 'minuteStep', 'length', 'arrowBehavior', 'iconUp', 'iconDown', 'id'], function(key) {
4423
+ var options = {scope: scope};
4424
+ angular.forEach(['placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'template', 'filter', 'limit', 'minLength', 'watchOptions', 'selectMode', 'autoSelect', 'comparator', 'id'], function(key) {
3942
4425
  if(angular.isDefined(attr[key])) options[key] = attr[key];
3943
4426
  });
3944
4427
 
3945
- // Visibility binding support
3946
- attr.bsShow && scope.$watch(attr.bsShow, function(newValue, oldValue) {
3947
- if(!timepicker || !angular.isDefined(newValue)) return;
3948
- if(angular.isString(newValue)) newValue = !!newValue.match(/true|,?(timepicker),?/i);
3949
- newValue === true ? timepicker.show() : timepicker.hide();
3950
- });
3951
-
3952
- // Initialize timepicker
3953
- if(isNative && (options.useNative || defaults.useNative)) options.timeFormat = 'HH:mm';
3954
- var timepicker = $timepicker(element, controller, options);
3955
- options = timepicker.$options;
4428
+ // Disable browser autocompletion
4429
+ element.attr('autocomplete' ,'off');
4430
+
4431
+ // Build proper bsOptions
4432
+ var filter = options.filter || defaults.filter;
4433
+ var limit = options.limit || defaults.limit;
4434
+ var comparator = options.comparator || defaults.comparator;
3956
4435
 
3957
- var lang = options.lang;
3958
- var formatDate = function(date, format) {
3959
- return $dateFormatter.formatDate(date, format, lang);
3960
- };
4436
+ var bsOptions = attr.bsOptions;
4437
+ if(filter) bsOptions += ' | ' + filter + ':$viewValue';
4438
+ if (comparator) bsOptions += ':' + comparator;
4439
+ if(limit) bsOptions += ' | limitTo:' + limit;
4440
+ var parsedOptions = $parseOptions(bsOptions);
3961
4441
 
3962
- // Initialize parser
3963
- var dateParser = $dateParser({format: options.timeFormat, lang: lang});
4442
+ // Initialize typeahead
4443
+ var typeahead = $typeahead(element, controller, options);
3964
4444
 
3965
- // Observe attributes for changes
3966
- angular.forEach(['minTime', 'maxTime'], function(key) {
3967
- // console.warn('attr.$observe(%s)', key, attr[key]);
3968
- angular.isDefined(attr[key]) && attr.$observe(key, function(newValue) {
3969
- timepicker.$options[key] = dateParser.getTimeForAttribute(key, newValue);
3970
- !isNaN(timepicker.$options[key]) && timepicker.$build();
3971
- validateAgainstMinMaxTime(controller.$dateValue);
3972
- });
3973
- });
4445
+ // Watch options on demand
4446
+ if(options.watchOptions) {
4447
+ // Watch bsOptions values before filtering for changes, drop function calls
4448
+ var watchedOptions = parsedOptions.$match[7].replace(/\|.+/, '').replace(/\(.*\)/g, '').trim();
4449
+ scope.$watch(watchedOptions, function (newValue, oldValue) {
4450
+ // console.warn('scope.$watch(%s)', watchedOptions, newValue, oldValue);
4451
+ parsedOptions.valuesFn(scope, controller).then(function (values) {
4452
+ typeahead.update(values);
4453
+ controller.$render();
4454
+ });
4455
+ }, true);
4456
+ }
3974
4457
 
3975
4458
  // Watch model for changes
3976
4459
  scope.$watch(attr.ngModel, function(newValue, oldValue) {
3977
- // console.warn('scope.$watch(%s)', attr.ngModel, newValue, oldValue, controller.$dateValue);
3978
- timepicker.update(controller.$dateValue);
3979
- }, true);
3980
-
3981
- function validateAgainstMinMaxTime(parsedTime) {
3982
- if (!angular.isDate(parsedTime)) return;
3983
- var isMinValid = isNaN(options.minTime) || new Date(parsedTime.getTime()).setFullYear(1970, 0, 1) >= options.minTime;
3984
- var isMaxValid = isNaN(options.maxTime) || new Date(parsedTime.getTime()).setFullYear(1970, 0, 1) <= options.maxTime;
3985
- var isValid = isMinValid && isMaxValid;
3986
- controller.$setValidity('date', isValid);
3987
- controller.$setValidity('min', isMinValid);
3988
- controller.$setValidity('max', isMaxValid);
3989
- // Only update the model when we have a valid date
3990
- if(!isValid) {
4460
+ // console.warn('$watch', element.attr('ng-model'), newValue);
4461
+ scope.$modelValue = newValue; // Publish modelValue on scope for custom templates
4462
+ parsedOptions.valuesFn(scope, controller)
4463
+ .then(function(values) {
4464
+ // Prevent input with no future prospect if selectMode is truthy
4465
+ // @TODO test selectMode
4466
+ if(options.selectMode && !values.length && newValue.length > 0) {
4467
+ controller.$setViewValue(controller.$viewValue.substring(0, controller.$viewValue.length - 1));
3991
4468
  return;
3992
- }
3993
- controller.$dateValue = parsedTime;
3994
- }
3995
-
3996
- // viewValue -> $parsers -> modelValue
3997
- controller.$parsers.unshift(function(viewValue) {
3998
- // console.warn('$parser("%s"): viewValue=%o', element.attr('ng-model'), viewValue);
3999
- // Null values should correctly reset the model value & validity
4000
- if(!viewValue) {
4001
- // BREAKING CHANGE:
4002
- // return null (not undefined) when input value is empty, so angularjs 1.3
4003
- // ngModelController can go ahead and run validators, like ngRequired
4004
- controller.$setValidity('date', true);
4005
- return null;
4006
- }
4007
- var parsedTime = angular.isDate(viewValue) ? viewValue : dateParser.parse(viewValue, controller.$dateValue);
4008
- if(!parsedTime || isNaN(parsedTime.getTime())) {
4009
- controller.$setValidity('date', false);
4010
- // return undefined, causes ngModelController to
4011
- // invalidate model value
4012
- return;
4013
- } else {
4014
- validateAgainstMinMaxTime(parsedTime);
4015
- }
4016
- if(options.timeType === 'string') {
4017
- return formatDate(parsedTime, options.modelTimeFormat || options.timeFormat);
4018
- } else if(options.timeType === 'number') {
4019
- return controller.$dateValue.getTime();
4020
- } else if(options.timeType === 'unix') {
4021
- return controller.$dateValue.getTime() / 1000;
4022
- } else if(options.timeType === 'iso') {
4023
- return controller.$dateValue.toISOString();
4024
- } else {
4025
- return new Date(controller.$dateValue);
4026
- }
4469
+ }
4470
+ if(values.length > limit) values = values.slice(0, limit);
4471
+ var isVisible = typeahead.$isVisible();
4472
+ isVisible && typeahead.update(values);
4473
+ // Do not re-queue an update if a correct value has been selected
4474
+ if(values.length === 1 && values[0].value === newValue) return;
4475
+ !isVisible && typeahead.update(values);
4476
+ // Queue a new rendering that will leverage collection loading
4477
+ controller.$render();
4478
+ });
4027
4479
  });
4028
4480
 
4029
4481
  // modelValue -> $formatters -> viewValue
4030
4482
  controller.$formatters.push(function(modelValue) {
4031
4483
  // console.warn('$formatter("%s"): modelValue=%o (%o)', element.attr('ng-model'), modelValue, typeof modelValue);
4032
- var date;
4033
- if(angular.isUndefined(modelValue) || modelValue === null) {
4034
- date = NaN;
4035
- } else if(angular.isDate(modelValue)) {
4036
- date = modelValue;
4037
- } else if(options.timeType === 'string') {
4038
- date = dateParser.parse(modelValue, null, options.modelTimeFormat);
4039
- } else if(options.timeType === 'unix') {
4040
- date = new Date(modelValue * 1000);
4041
- } else {
4042
- date = new Date(modelValue);
4043
- }
4044
- // Setup default value?
4045
- // if(isNaN(date.getTime())) date = new Date(new Date().setMinutes(0) + 36e5);
4046
- controller.$dateValue = date;
4047
- return getTimeFormattedString();
4484
+ var displayValue = parsedOptions.displayValue(modelValue);
4485
+ return displayValue === undefined ? '' : displayValue;
4048
4486
  });
4049
4487
 
4050
- // viewValue -> element
4051
- controller.$render = function() {
4052
- // console.warn('$render("%s"): viewValue=%o', element.attr('ng-model'), controller.$viewValue);
4053
- element.val(getTimeFormattedString());
4488
+ // Model rendering in view
4489
+ controller.$render = function () {
4490
+ // console.warn('$render', element.attr('ng-model'), 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue, 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue);
4491
+ if(controller.$isEmpty(controller.$viewValue)) return element.val('');
4492
+ var index = typeahead.$getIndex(controller.$modelValue);
4493
+ var selected = angular.isDefined(index) ? typeahead.$scope.$matches[index].label : controller.$viewValue;
4494
+ selected = angular.isObject(selected) ? parsedOptions.displayValue(selected) : selected;
4495
+ element.val(selected ? selected.toString().replace(/<(?:.|\n)*?>/gm, '').trim() : '');
4054
4496
  };
4055
4497
 
4056
- function getTimeFormattedString() {
4057
- return !controller.$dateValue || isNaN(controller.$dateValue.getTime()) ? '' : formatDate(controller.$dateValue, options.timeFormat);
4058
- }
4059
-
4060
4498
  // Garbage collection
4061
4499
  scope.$on('$destroy', function() {
4062
- if (timepicker) timepicker.destroy();
4500
+ if (typeahead) typeahead.destroy();
4063
4501
  options = null;
4064
- timepicker = null;
4502
+ typeahead = null;
4065
4503
  });
4066
4504
 
4067
4505
  }
@@ -4092,7 +4530,11 @@ angular.module('mgcrea.ngStrap.tooltip', ['mgcrea.ngStrap.helpers.dimensions'])
4092
4530
  type: '',
4093
4531
  delay: 0,
4094
4532
  autoClose: false,
4095
- bsEnabled: true
4533
+ bsEnabled: true,
4534
+ viewport: {
4535
+ selector: 'body',
4536
+ padding: 0
4537
+ }
4096
4538
  };
4097
4539
 
4098
4540
  this.$get = ["$window", "$rootScope", "$compile", "$q", "$templateCache", "$http", "$animate", "$sce", "dimensions", "$$rAF", "$timeout", function($window, $rootScope, $compile, $q, $templateCache, $http, $animate, $sce, dimensions, $$rAF, $timeout) {
@@ -4281,19 +4723,28 @@ angular.module('mgcrea.ngStrap.tooltip', ['mgcrea.ngStrap.helpers.dimensions'])
4281
4723
  // Options: custom classes
4282
4724
  if(options.customClass) tipElement.addClass(options.customClass);
4283
4725
 
4726
+ // Append the element, without any animations. If we append
4727
+ // using $animate.enter, some of the animations cause the placement
4728
+ // to be off due to the transforms.
4729
+ after ? after.after(tipElement) : parent.prepend(tipElement);
4730
+
4731
+ $tooltip.$isShown = scope.$isShown = true;
4732
+ safeDigest(scope);
4733
+
4734
+ // Now, apply placement
4735
+ $tooltip.$applyPlacement();
4736
+
4737
+ // Once placed, animate it.
4284
4738
  // Support v1.3+ $animate
4285
4739
  // https://github.com/angular/angular.js/commit/bf0f5502b1bbfddc5cdd2f138efd9188b8c652a9
4286
4740
  var promise = $animate.enter(tipElement, parent, after, enterAnimateCallback);
4287
4741
  if(promise && promise.then) promise.then(enterAnimateCallback);
4288
-
4289
- $tooltip.$isShown = scope.$isShown = true;
4290
4742
  safeDigest(scope);
4291
- $$rAF(function () {
4292
- $tooltip.$applyPlacement();
4293
4743
 
4294
- // Once placed, make the tooltip visible
4744
+ $$rAF(function () {
4745
+ // Once the tooltip is placed and the animation starts, make the tooltip visible
4295
4746
  if(tipElement) tipElement.css({visibility: 'visible'});
4296
- }); // var a = bodyEl.offsetWidth + 1; ?
4747
+ });
4297
4748
 
4298
4749
  // Bind events
4299
4750
  if(options.keyboard) {
@@ -4388,6 +4839,10 @@ angular.module('mgcrea.ngStrap.tooltip', ['mgcrea.ngStrap.helpers.dimensions'])
4388
4839
  options.bsEnabled = isEnabled;
4389
4840
  };
4390
4841
 
4842
+ $tooltip.setViewport = function(viewport) {
4843
+ options.viewport = viewport;
4844
+ };
4845
+
4391
4846
  // Protected methods
4392
4847
 
4393
4848
  $tooltip.$applyPlacement = function() {
@@ -4415,7 +4870,7 @@ angular.module('mgcrea.ngStrap.tooltip', ['mgcrea.ngStrap.helpers.dimensions'])
4415
4870
  // If we're auto placing, we need to check the positioning
4416
4871
  if (autoPlace) {
4417
4872
  var originalPlacement = placement;
4418
- var container = options.container ? angular.element(document.querySelector(options.container)) : element.parent();
4873
+ var container = options.container ? findElement(options.container) : element.parent();
4419
4874
  var containerPosition = getPosition(container);
4420
4875
 
4421
4876
  // Determine if the vertical placement
@@ -4443,7 +4898,7 @@ angular.module('mgcrea.ngStrap.tooltip', ['mgcrea.ngStrap.helpers.dimensions'])
4443
4898
 
4444
4899
  // Get the tooltip's top and left coordinates to center it with this directive.
4445
4900
  var tipPosition = getCalculatedOffset(placement, elementPosition, tipWidth, tipHeight);
4446
- applyPlacementCss(tipPosition.top, tipPosition.left);
4901
+ applyPlacement(tipPosition, placement);
4447
4902
  };
4448
4903
 
4449
4904
  $tooltip.$onKeyUp = function(evt) {
@@ -4543,22 +4998,28 @@ angular.module('mgcrea.ngStrap.tooltip', ['mgcrea.ngStrap.helpers.dimensions'])
4543
4998
  function getPosition($element) {
4544
4999
  $element = $element || (options.target || element);
4545
5000
 
4546
- var el = $element[0];
5001
+ var el = $element[0],
5002
+ isBody = el.tagName === 'BODY';
4547
5003
 
4548
5004
  var elRect = el.getBoundingClientRect();
4549
- if (elRect.width === null) {
4550
- // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
4551
- elRect = angular.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top });
5005
+ var rect = {};
5006
+
5007
+ // IE8 has issues with angular.extend and using elRect directly.
5008
+ // By coping the values of elRect into a new object, we can continue to use extend
5009
+ for (var p in elRect) {
5010
+ // DO NOT use hasOwnProperty when inspecting the return of getBoundingClientRect.
5011
+ rect[p] = elRect[p];
4552
5012
  }
4553
5013
 
4554
- var elPos;
4555
- if (options.container === 'body') {
4556
- elPos = dimensions.offset(el);
4557
- } else {
4558
- elPos = dimensions.position(el);
5014
+ if (rect.width === null) {
5015
+ // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
5016
+ rect = angular.extend({}, rect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top });
4559
5017
  }
5018
+ var elOffset = isBody ? { top: 0, left: 0 } : dimensions.offset(el),
5019
+ scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.prop('scrollTop') || 0 },
5020
+ outerDims = isBody ? { width: document.documentElement.clientWidth, height: $window.innerHeight } : null;
4560
5021
 
4561
- return angular.extend({}, elRect, elPos);
5022
+ return angular.extend({}, rect, scroll, outerDims, elOffset);
4562
5023
  }
4563
5024
 
4564
5025
  function getCalculatedOffset(placement, position, actualWidth, actualHeight) {
@@ -4618,8 +5079,101 @@ angular.module('mgcrea.ngStrap.tooltip', ['mgcrea.ngStrap.helpers.dimensions'])
4618
5079
  return offset;
4619
5080
  }
4620
5081
 
4621
- function applyPlacementCss(top, left) {
4622
- tipElement.css({ top: top + 'px', left: left + 'px' });
5082
+ function applyPlacement(offset, placement) {
5083
+ var tip = tipElement[0],
5084
+ width = tip.offsetWidth,
5085
+ height = tip.offsetHeight;
5086
+
5087
+ // manually read margins because getBoundingClientRect includes difference
5088
+ var marginTop = parseInt(dimensions.css(tip, 'margin-top'), 10),
5089
+ marginLeft = parseInt(dimensions.css(tip, 'margin-left'), 10);
5090
+
5091
+ // we must check for NaN for ie 8/9
5092
+ if (isNaN(marginTop)) marginTop = 0;
5093
+ if (isNaN(marginLeft)) marginLeft = 0;
5094
+
5095
+ offset.top = offset.top + marginTop;
5096
+ offset.left = offset.left + marginLeft;
5097
+
5098
+ // dimensions setOffset doesn't round pixel values
5099
+ // so we use setOffset directly with our own function
5100
+ dimensions.setOffset(tip, angular.extend({
5101
+ using: function (props) {
5102
+ tipElement.css({
5103
+ top: Math.round(props.top) + 'px',
5104
+ left: Math.round(props.left) + 'px'
5105
+ });
5106
+ }
5107
+ }, offset), 0);
5108
+
5109
+ // check to see if placing tip in new offset caused the tip to resize itself
5110
+ var actualWidth = tip.offsetWidth,
5111
+ actualHeight = tip.offsetHeight;
5112
+
5113
+ if (placement === 'top' && actualHeight !== height) {
5114
+ offset.top = offset.top + height - actualHeight;
5115
+ }
5116
+
5117
+ // If it's an exotic placement, exit now instead of
5118
+ // applying a delta and changing the arrow
5119
+ if (/top-left|top-right|bottom-left|bottom-right/.test(placement)) return;
5120
+
5121
+ var delta = getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight);
5122
+
5123
+ if (delta.left) {
5124
+ offset.left += delta.left;
5125
+ } else {
5126
+ offset.top += delta.top;
5127
+ }
5128
+
5129
+ dimensions.setOffset(tip, offset);
5130
+
5131
+ if (/top|right|bottom|left/.test(placement)) {
5132
+ var isVertical = /top|bottom/.test(placement),
5133
+ arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight,
5134
+ arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight';
5135
+
5136
+ replaceArrow(arrowDelta, tip[arrowOffsetPosition], isVertical);
5137
+ }
5138
+ }
5139
+
5140
+ function getViewportAdjustedDelta(placement, position, actualWidth, actualHeight) {
5141
+ var delta = { top: 0, left: 0 },
5142
+ $viewport = options.viewport && findElement(options.viewport.selector || options.viewport);
5143
+
5144
+ if (!$viewport) {
5145
+ return delta;
5146
+ }
5147
+
5148
+ var viewportPadding = options.viewport && options.viewport.padding || 0,
5149
+ viewportDimensions = getPosition($viewport);
5150
+
5151
+ if (/right|left/.test(placement)) {
5152
+ var topEdgeOffset = position.top - viewportPadding - viewportDimensions.scroll,
5153
+ bottomEdgeOffset = position.top + viewportPadding - viewportDimensions.scroll + actualHeight;
5154
+ if (topEdgeOffset < viewportDimensions.top) { // top overflow
5155
+ delta.top = viewportDimensions.top - topEdgeOffset;
5156
+ } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
5157
+ delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset;
5158
+ }
5159
+ } else {
5160
+ var leftEdgeOffset = position.left - viewportPadding,
5161
+ rightEdgeOffset = position.left + viewportPadding + actualWidth;
5162
+ if (leftEdgeOffset < viewportDimensions.left) { // left overflow
5163
+ delta.left = viewportDimensions.left - leftEdgeOffset;
5164
+ } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow
5165
+ delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset;
5166
+ }
5167
+ }
5168
+
5169
+ return delta;
5170
+ }
5171
+
5172
+ function replaceArrow(delta, dimension, isHorizontal) {
5173
+ var $arrow = findElement('.tooltip-arrow, .arrow', tipElement[0]);
5174
+
5175
+ $arrow.css(isHorizontal ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
5176
+ .css(isHorizontal ? 'top' : 'left', '');
4623
5177
  }
4624
5178
 
4625
5179
  function destroyTipElement() {
@@ -4664,13 +5218,8 @@ angular.module('mgcrea.ngStrap.tooltip', ['mgcrea.ngStrap.helpers.dimensions'])
4664
5218
  var fetchPromises = {};
4665
5219
  function fetchTemplate(template) {
4666
5220
  if(fetchPromises[template]) return fetchPromises[template];
4667
- return (fetchPromises[template] = $q.when($templateCache.get(template) || $http.get(template))
4668
- .then(function(res) {
4669
- if(angular.isObject(res)) {
4670
- $templateCache.put(template, res.data);
4671
- return res.data;
4672
- }
4673
- return res;
5221
+ return (fetchPromises[template] = $http.get(template, {cache: $templateCache}).then(function(res) {
5222
+ return res.data;
4674
5223
  }));
4675
5224
  }
4676
5225
 
@@ -4689,10 +5238,15 @@ angular.module('mgcrea.ngStrap.tooltip', ['mgcrea.ngStrap.helpers.dimensions'])
4689
5238
 
4690
5239
  // Directive options
4691
5240
  var options = {scope: scope};
4692
- angular.forEach(['template', 'contentTemplate', 'placement', 'container', 'target', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'backdropAnimation', 'type', 'customClass', 'id'], function(key) {
5241
+ angular.forEach(['template', 'contentTemplate', 'placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'backdropAnimation', 'type', 'customClass', 'id'], function(key) {
4693
5242
  if(angular.isDefined(attr[key])) options[key] = attr[key];
4694
5243
  });
4695
5244
 
5245
+ // should not parse target attribute, only data-target
5246
+ if(element.attr('data-target')) {
5247
+ options.target = element.attr('data-target');
5248
+ }
5249
+
4696
5250
  // overwrite inherited title value when no value specified
4697
5251
  // fix for angular 1.3.1 531a8de72c439d8ddd064874bf364c00cedabb11
4698
5252
  if (!scope.hasOwnProperty('title')){
@@ -4737,6 +5291,12 @@ angular.module('mgcrea.ngStrap.tooltip', ['mgcrea.ngStrap.helpers.dimensions'])
4737
5291
  newValue === false ? tooltip.setEnabled(false) : tooltip.setEnabled(true);
4738
5292
  });
4739
5293
 
5294
+ // Viewport support
5295
+ attr.viewport && scope.$watch(attr.viewport, function (newValue) {
5296
+ if(!tooltip || !angular.isDefined(newValue)) return;
5297
+ tooltip.setViewport(newValue);
5298
+ });
5299
+
4740
5300
  // Initialize popover
4741
5301
  var tooltip = $tooltip(element, options);
4742
5302
 
@@ -4752,263 +5312,4 @@ angular.module('mgcrea.ngStrap.tooltip', ['mgcrea.ngStrap.helpers.dimensions'])
4752
5312
 
4753
5313
  }]);
4754
5314
 
4755
- // Source: typeahead.js
4756
- angular.module('mgcrea.ngStrap.typeahead', ['mgcrea.ngStrap.tooltip', 'mgcrea.ngStrap.helpers.parseOptions'])
4757
-
4758
- .provider('$typeahead', function() {
4759
-
4760
- var defaults = this.defaults = {
4761
- animation: 'am-fade',
4762
- prefixClass: 'typeahead',
4763
- prefixEvent: '$typeahead',
4764
- placement: 'bottom-left',
4765
- template: 'typeahead/typeahead.tpl.html',
4766
- trigger: 'focus',
4767
- container: false,
4768
- keyboard: true,
4769
- html: false,
4770
- delay: 0,
4771
- minLength: 1,
4772
- filter: 'filter',
4773
- limit: 6,
4774
- comparator: ''
4775
- };
4776
-
4777
- this.$get = ["$window", "$rootScope", "$tooltip", "$timeout", function($window, $rootScope, $tooltip, $timeout) {
4778
-
4779
- var bodyEl = angular.element($window.document.body);
4780
-
4781
- function TypeaheadFactory(element, controller, config) {
4782
-
4783
- var $typeahead = {};
4784
-
4785
- // Common vars
4786
- var options = angular.extend({}, defaults, config);
4787
-
4788
- $typeahead = $tooltip(element, options);
4789
- var parentScope = config.scope;
4790
- var scope = $typeahead.$scope;
4791
-
4792
- scope.$resetMatches = function(){
4793
- scope.$matches = [];
4794
- scope.$activeIndex = 0;
4795
- };
4796
- scope.$resetMatches();
4797
-
4798
- scope.$activate = function(index) {
4799
- scope.$$postDigest(function() {
4800
- $typeahead.activate(index);
4801
- });
4802
- };
4803
-
4804
- scope.$select = function(index, evt) {
4805
- scope.$$postDigest(function() {
4806
- $typeahead.select(index);
4807
- });
4808
- };
4809
-
4810
- scope.$isVisible = function() {
4811
- return $typeahead.$isVisible();
4812
- };
4813
-
4814
- // Public methods
4815
-
4816
- $typeahead.update = function(matches) {
4817
- scope.$matches = matches;
4818
- if(scope.$activeIndex >= matches.length) {
4819
- scope.$activeIndex = 0;
4820
- }
4821
- };
4822
-
4823
- $typeahead.activate = function(index) {
4824
- scope.$activeIndex = index;
4825
- };
4826
-
4827
- $typeahead.select = function(index) {
4828
- var value = scope.$matches[index].value;
4829
- // console.log('$setViewValue', value);
4830
- controller.$setViewValue(value);
4831
- controller.$render();
4832
- scope.$resetMatches();
4833
- if(parentScope) parentScope.$digest();
4834
- // Emit event
4835
- scope.$emit(options.prefixEvent + '.select', value, index, $typeahead);
4836
- };
4837
-
4838
- // Protected methods
4839
-
4840
- $typeahead.$isVisible = function() {
4841
- if(!options.minLength || !controller) {
4842
- return !!scope.$matches.length;
4843
- }
4844
- // minLength support
4845
- return scope.$matches.length && angular.isString(controller.$viewValue) && controller.$viewValue.length >= options.minLength;
4846
- };
4847
-
4848
- $typeahead.$getIndex = function(value) {
4849
- var l = scope.$matches.length, i = l;
4850
- if(!l) return;
4851
- for(i = l; i--;) {
4852
- if(scope.$matches[i].value === value) break;
4853
- }
4854
- if(i < 0) return;
4855
- return i;
4856
- };
4857
-
4858
- $typeahead.$onMouseDown = function(evt) {
4859
- // Prevent blur on mousedown
4860
- evt.preventDefault();
4861
- evt.stopPropagation();
4862
- };
4863
-
4864
- $typeahead.$onKeyDown = function(evt) {
4865
- if(!/(38|40|13)/.test(evt.keyCode)) return;
4866
-
4867
- // Let ngSubmit pass if the typeahead tip is hidden
4868
- if($typeahead.$isVisible()) {
4869
- evt.preventDefault();
4870
- evt.stopPropagation();
4871
- }
4872
-
4873
- // Select with enter
4874
- if(evt.keyCode === 13 && scope.$matches.length) {
4875
- $typeahead.select(scope.$activeIndex);
4876
- }
4877
-
4878
- // Navigate with keyboard
4879
- else if(evt.keyCode === 38 && scope.$activeIndex > 0) scope.$activeIndex--;
4880
- else if(evt.keyCode === 40 && scope.$activeIndex < scope.$matches.length - 1) scope.$activeIndex++;
4881
- else if(angular.isUndefined(scope.$activeIndex)) scope.$activeIndex = 0;
4882
- scope.$digest();
4883
- };
4884
-
4885
- // Overrides
4886
-
4887
- var show = $typeahead.show;
4888
- $typeahead.show = function() {
4889
- show();
4890
- // use timeout to hookup the events to prevent
4891
- // event bubbling from being processed imediately.
4892
- $timeout(function() {
4893
- $typeahead.$element.on('mousedown', $typeahead.$onMouseDown);
4894
- if(options.keyboard) {
4895
- element.on('keydown', $typeahead.$onKeyDown);
4896
- }
4897
- }, 0, false);
4898
- };
4899
-
4900
- var hide = $typeahead.hide;
4901
- $typeahead.hide = function() {
4902
- $typeahead.$element.off('mousedown', $typeahead.$onMouseDown);
4903
- if(options.keyboard) {
4904
- element.off('keydown', $typeahead.$onKeyDown);
4905
- }
4906
- hide();
4907
- };
4908
-
4909
- return $typeahead;
4910
-
4911
- }
4912
-
4913
- TypeaheadFactory.defaults = defaults;
4914
- return TypeaheadFactory;
4915
-
4916
- }];
4917
-
4918
- })
4919
-
4920
- .directive('bsTypeahead', ["$window", "$parse", "$q", "$typeahead", "$parseOptions", function($window, $parse, $q, $typeahead, $parseOptions) {
4921
-
4922
- var defaults = $typeahead.defaults;
4923
-
4924
- return {
4925
- restrict: 'EAC',
4926
- require: 'ngModel',
4927
- link: function postLink(scope, element, attr, controller) {
4928
-
4929
- // Directive options
4930
- var options = {scope: scope};
4931
- angular.forEach(['placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'template', 'filter', 'limit', 'minLength', 'watchOptions', 'selectMode', 'comparator', 'id'], function(key) {
4932
- if(angular.isDefined(attr[key])) options[key] = attr[key];
4933
- });
4934
-
4935
- // Build proper ngOptions
4936
- var filter = options.filter || defaults.filter;
4937
- var limit = options.limit || defaults.limit;
4938
- var comparator = options.comparator || defaults.comparator;
4939
-
4940
- var ngOptions = attr.ngOptions;
4941
- if(filter) ngOptions += ' | ' + filter + ':$viewValue';
4942
- if (comparator) ngOptions += ':' + comparator;
4943
- if(limit) ngOptions += ' | limitTo:' + limit;
4944
- var parsedOptions = $parseOptions(ngOptions);
4945
-
4946
- // Initialize typeahead
4947
- var typeahead = $typeahead(element, controller, options);
4948
-
4949
- // Watch options on demand
4950
- if(options.watchOptions) {
4951
- // Watch ngOptions values before filtering for changes, drop function calls
4952
- var watchedOptions = parsedOptions.$match[7].replace(/\|.+/, '').replace(/\(.*\)/g, '').trim();
4953
- scope.$watch(watchedOptions, function (newValue, oldValue) {
4954
- // console.warn('scope.$watch(%s)', watchedOptions, newValue, oldValue);
4955
- parsedOptions.valuesFn(scope, controller).then(function (values) {
4956
- typeahead.update(values);
4957
- controller.$render();
4958
- });
4959
- }, true);
4960
- }
4961
-
4962
- // Watch model for changes
4963
- scope.$watch(attr.ngModel, function(newValue, oldValue) {
4964
- // console.warn('$watch', element.attr('ng-model'), newValue);
4965
- scope.$modelValue = newValue; // Publish modelValue on scope for custom templates
4966
- parsedOptions.valuesFn(scope, controller)
4967
- .then(function(values) {
4968
- // Prevent input with no future prospect if selectMode is truthy
4969
- // @TODO test selectMode
4970
- if(options.selectMode && !values.length && newValue.length > 0) {
4971
- controller.$setViewValue(controller.$viewValue.substring(0, controller.$viewValue.length - 1));
4972
- return;
4973
- }
4974
- if(values.length > limit) values = values.slice(0, limit);
4975
- var isVisible = typeahead.$isVisible();
4976
- isVisible && typeahead.update(values);
4977
- // Do not re-queue an update if a correct value has been selected
4978
- if(values.length === 1 && values[0].value === newValue) return;
4979
- !isVisible && typeahead.update(values);
4980
- // Queue a new rendering that will leverage collection loading
4981
- controller.$render();
4982
- });
4983
- });
4984
-
4985
- // modelValue -> $formatters -> viewValue
4986
- controller.$formatters.push(function(modelValue) {
4987
- // console.warn('$formatter("%s"): modelValue=%o (%o)', element.attr('ng-model'), modelValue, typeof modelValue);
4988
- var displayValue = parsedOptions.displayValue(modelValue);
4989
- return displayValue === undefined ? '' : displayValue;
4990
- });
4991
-
4992
- // Model rendering in view
4993
- controller.$render = function () {
4994
- // console.warn('$render', element.attr('ng-model'), 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue, 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue);
4995
- if(controller.$isEmpty(controller.$viewValue)) return element.val('');
4996
- var index = typeahead.$getIndex(controller.$modelValue);
4997
- var selected = angular.isDefined(index) ? typeahead.$scope.$matches[index].label : controller.$viewValue;
4998
- selected = angular.isObject(selected) ? parsedOptions.displayValue(selected) : selected;
4999
- element.val(selected ? selected.toString().replace(/<(?:.|\n)*?>/gm, '').trim() : '');
5000
- };
5001
-
5002
- // Garbage collection
5003
- scope.$on('$destroy', function() {
5004
- if (typeahead) typeahead.destroy();
5005
- options = null;
5006
- typeahead = null;
5007
- });
5008
-
5009
- }
5010
- };
5011
-
5012
- }]);
5013
-
5014
5315
  })(window, document);