rails-angular-strap 2.1.6 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
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);