angular-strap-rails 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.gitignore +17 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +45 -0
  7. data/Rakefile +1 -0
  8. data/angular-strap-rails.gemspec +15 -0
  9. data/lib/angular-strap-rails.rb +8 -0
  10. data/lib/angular-strap-rails/version.rb +5 -0
  11. data/vendor/.DS_Store +0 -0
  12. data/vendor/assets/.DS_Store +0 -0
  13. data/vendor/assets/javascripts/.DS_Store +0 -0
  14. data/vendor/assets/javascripts/angular-strap.coffee +0 -0
  15. data/vendor/assets/javascripts/angular-strap/.DS_Store +0 -0
  16. data/vendor/assets/javascripts/angular-strap/datepicker.coffee +11 -0
  17. data/vendor/assets/javascripts/angular-strap/modal.coffee +9 -0
  18. data/vendor/assets/javascripts/dist/angular-strap.js +3682 -0
  19. data/vendor/assets/javascripts/dist/angular-strap.tpl.js +100 -0
  20. data/vendor/assets/javascripts/dist/modules/affix.js +191 -0
  21. data/vendor/assets/javascripts/dist/modules/alert.js +114 -0
  22. data/vendor/assets/javascripts/dist/modules/alert.tpl.js +14 -0
  23. data/vendor/assets/javascripts/dist/modules/aside.js +96 -0
  24. data/vendor/assets/javascripts/dist/modules/aside.tpl.js +14 -0
  25. data/vendor/assets/javascripts/dist/modules/button.js +141 -0
  26. data/vendor/assets/javascripts/dist/modules/date-parser.js +150 -0
  27. data/vendor/assets/javascripts/dist/modules/datepicker.js +583 -0
  28. data/vendor/assets/javascripts/dist/modules/datepicker.tpl.js +14 -0
  29. data/vendor/assets/javascripts/dist/modules/debounce.js +60 -0
  30. data/vendor/assets/javascripts/dist/modules/dimensions.js +142 -0
  31. data/vendor/assets/javascripts/dist/modules/dropdown.js +124 -0
  32. data/vendor/assets/javascripts/dist/modules/dropdown.tpl.js +14 -0
  33. data/vendor/assets/javascripts/dist/modules/modal.js +282 -0
  34. data/vendor/assets/javascripts/dist/modules/modal.tpl.js +14 -0
  35. data/vendor/assets/javascripts/dist/modules/navbar.js +55 -0
  36. data/vendor/assets/javascripts/dist/modules/parse-options.js +51 -0
  37. data/vendor/assets/javascripts/dist/modules/popover.js +100 -0
  38. data/vendor/assets/javascripts/dist/modules/popover.tpl.js +14 -0
  39. data/vendor/assets/javascripts/dist/modules/raf.js +45 -0
  40. data/vendor/assets/javascripts/dist/modules/scrollspy.js +229 -0
  41. data/vendor/assets/javascripts/dist/modules/select.js +281 -0
  42. data/vendor/assets/javascripts/dist/modules/select.tpl.js +14 -0
  43. data/vendor/assets/javascripts/dist/modules/tab.js +69 -0
  44. data/vendor/assets/javascripts/dist/modules/tab.tpl.js +14 -0
  45. data/vendor/assets/javascripts/dist/modules/timepicker.js +430 -0
  46. data/vendor/assets/javascripts/dist/modules/timepicker.tpl.js +14 -0
  47. data/vendor/assets/javascripts/dist/modules/tooltip.js +405 -0
  48. data/vendor/assets/javascripts/dist/modules/tooltip.tpl.js +14 -0
  49. data/vendor/assets/javascripts/dist/modules/typeahead.js +225 -0
  50. data/vendor/assets/javascripts/dist/modules/typeahead.tpl.js +14 -0
  51. data/vendor/assets/stylesheets/angular-strap.css +564 -0
  52. metadata +94 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f48c1d5e6939a86e897fd4976bff5f1c3e3c2710
4
+ data.tar.gz: afa4a5447262e4d7eb0662b7dd43301ea7494a78
5
+ SHA512:
6
+ metadata.gz: e5bc176a9818048aee7c3947a9e1ade2390595f26724df8303c95f23c199c15a7b93dd225470fd72dde77d5f48d1a4e339c3d484af9f439a925c91e11131eae1
7
+ data.tar.gz: 6f94c9436fdb28da7c7887906a9aec9a0084bfba35624095d38c3bda77293c13c65ae326b874742b8d309b9bff3f2d1400402e984f906fec63d19216e6c31099
Binary file
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in angular-strap.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Vulpi Shu
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,45 @@
1
+ # AngularStrap-Rails
2
+
3
+ angular-strap-rails adds AngularStrap
4
+ set of angular directives to your rails asset pipeline.
5
+
6
+ ## Usage
7
+
8
+ To install put this in your Gemfile
9
+
10
+ ```Gemfile
11
+ gem 'angular-strap-rails'
12
+ ```
13
+
14
+ or
15
+
16
+ ```Gemfile
17
+ gem 'angular-strap-rails', github: 'vulpi-shu/angular-strap-rails'
18
+ ```
19
+
20
+ Then add the following to your application.js
21
+ ```Javascript
22
+ //= require angular-strap
23
+ ```
24
+
25
+ You may also need to include the following in application.js
26
+ ```Javascript
27
+ //= require angular-strap/datepicker
28
+ //= require angular-strap/select
29
+ //= require angular-strap/datepicker
30
+ ```
31
+
32
+ And this in your aplication.css.sass
33
+ ```Sass
34
+ # require angular-strap
35
+ ```
36
+
37
+ For further information on how to use it refer to AngularStrap
38
+
39
+ ## Contributing
40
+
41
+ 1. Fork it
42
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
43
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
44
+ 4. Push to the branch (`git push origin my-new-feature`)
45
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,15 @@
1
+ # coding: utf-8
2
+ require File.expand_path('../lib/angular-strap-rails/version', __FILE__)
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = 'angular-strap-rails'
6
+ spec.version = AngularStrap::Rails::VERSION
7
+ spec.authors = ['Vulpi Shu']
8
+ spec.email = ['vulpi.shu@gmail.com']
9
+ spec.description = %q{AngularStrap for rails.}
10
+ spec.summary = %q{This gem adds AngularStrap to your rails asset pipeline.}
11
+ spec.homepage = ''
12
+ spec.license = 'MIT'
13
+
14
+ spec.files = `git ls-files`.split($/)
15
+ end
@@ -0,0 +1,8 @@
1
+ require "angular-strap-rails/version"
2
+
3
+ module AngularStrap
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module AngularStrap
2
+ module Rails
3
+ VERSION = "2.0.1"
4
+ end
5
+ end
Binary file
Binary file
@@ -0,0 +1,11 @@
1
+ #= require ../dist/modules/raf.js
2
+
3
+ #= require ../dist/modules/dimensions.js
4
+
5
+ #= require ../dist/modules/tooltip.js
6
+ #= require ../dist/modules/tooltip.tpl.js
7
+
8
+ #= require ../dist/modules/date-parser.js
9
+
10
+ #= require ../dist/modules/datepicker.js
11
+ #= require ../dist/modules/datepicker.tpl.js
@@ -0,0 +1,9 @@
1
+ #= require ../dist/modules/raf.js
2
+
3
+ #= require ../dist/modules/dimensions.js
4
+
5
+ #= require ../dist/modules/tooltip.js
6
+ #= require ../dist/modules/tooltip.tpl.js
7
+
8
+ #= require ../dist/modules/modal.js
9
+ #= require ../dist/modules/modal.tpl.js
@@ -0,0 +1,3682 @@
1
+ /**
2
+ * angular-strap
3
+ * @version v2.0.1 - 2014-04-10
4
+ * @link http://mgcrea.github.io/angular-strap
5
+ * @author Olivier Louvignes (olivier@mg-crea.com)
6
+ * @license MIT License, http://www.opensource.org/licenses/MIT
7
+ */
8
+ (function(window, document, undefined) {
9
+ 'use strict';
10
+ // Source: module.js
11
+ angular.module('mgcrea.ngStrap', [
12
+ 'mgcrea.ngStrap.modal',
13
+ 'mgcrea.ngStrap.aside',
14
+ 'mgcrea.ngStrap.alert',
15
+ 'mgcrea.ngStrap.button',
16
+ 'mgcrea.ngStrap.select',
17
+ 'mgcrea.ngStrap.datepicker',
18
+ 'mgcrea.ngStrap.timepicker',
19
+ 'mgcrea.ngStrap.navbar',
20
+ 'mgcrea.ngStrap.tooltip',
21
+ 'mgcrea.ngStrap.popover',
22
+ 'mgcrea.ngStrap.dropdown',
23
+ 'mgcrea.ngStrap.typeahead',
24
+ 'mgcrea.ngStrap.scrollspy',
25
+ 'mgcrea.ngStrap.affix',
26
+ 'mgcrea.ngStrap.tab'
27
+ ]);
28
+
29
+ // Source: affix.js
30
+ angular.module('mgcrea.ngStrap.affix', [
31
+ 'mgcrea.ngStrap.helpers.dimensions',
32
+ 'mgcrea.ngStrap.helpers.debounce'
33
+ ]).provider('$affix', function () {
34
+ var defaults = this.defaults = { offsetTop: 'auto' };
35
+ this.$get = [
36
+ '$window',
37
+ 'debounce',
38
+ 'dimensions',
39
+ function ($window, debounce, dimensions) {
40
+ var bodyEl = angular.element($window.document.body);
41
+ var windowEl = angular.element($window);
42
+ function AffixFactory(element, config) {
43
+ var $affix = {};
44
+ // Common vars
45
+ var options = angular.extend({}, defaults, config);
46
+ var targetEl = options.target;
47
+ // Initial private vars
48
+ var reset = 'affix affix-top affix-bottom', initialAffixTop = 0, initialOffsetTop = 0, offsetTop = 0, offsetBottom = 0, affixed = null, unpin = null;
49
+ var parent = element.parent();
50
+ // Options: custom parent
51
+ if (options.offsetParent) {
52
+ if (options.offsetParent.match(/^\d+$/)) {
53
+ for (var i = 0; i < options.offsetParent * 1 - 1; i++) {
54
+ parent = parent.parent();
55
+ }
56
+ } else {
57
+ parent = angular.element(options.offsetParent);
58
+ }
59
+ }
60
+ $affix.init = function () {
61
+ $affix.$parseOffsets();
62
+ initialOffsetTop = dimensions.offset(element[0]).top + initialAffixTop;
63
+ // Bind events
64
+ targetEl.on('scroll', $affix.checkPosition);
65
+ targetEl.on('click', $affix.checkPositionWithEventLoop);
66
+ windowEl.on('resize', $affix.$debouncedOnResize);
67
+ // Both of these checkPosition() calls are necessary for the case where
68
+ // the user hits refresh after scrolling to the bottom of the page.
69
+ $affix.checkPosition();
70
+ $affix.checkPositionWithEventLoop();
71
+ };
72
+ $affix.destroy = function () {
73
+ // Unbind events
74
+ targetEl.off('scroll', $affix.checkPosition);
75
+ targetEl.off('click', $affix.checkPositionWithEventLoop);
76
+ windowEl.off('resize', $affix.$debouncedOnResize);
77
+ };
78
+ $affix.checkPositionWithEventLoop = function () {
79
+ setTimeout($affix.checkPosition, 1);
80
+ };
81
+ $affix.checkPosition = function () {
82
+ // if (!this.$element.is(':visible')) return
83
+ var scrollTop = getScrollTop();
84
+ var position = dimensions.offset(element[0]);
85
+ var elementHeight = dimensions.height(element[0]);
86
+ // Get required affix class according to position
87
+ var affix = getRequiredAffixClass(unpin, position, elementHeight);
88
+ // Did affix status changed this last check?
89
+ if (affixed === affix)
90
+ return;
91
+ affixed = affix;
92
+ // Add proper affix class
93
+ element.removeClass(reset).addClass('affix' + (affix !== 'middle' ? '-' + affix : ''));
94
+ if (affix === 'top') {
95
+ unpin = null;
96
+ element.css('position', options.offsetParent ? '' : 'relative');
97
+ element.css('top', '');
98
+ } else if (affix === 'bottom') {
99
+ if (options.offsetUnpin) {
100
+ unpin = -(options.offsetUnpin * 1);
101
+ } else {
102
+ // Calculate unpin threshold when affixed to bottom.
103
+ // Hopefully the browser scrolls pixel by pixel.
104
+ unpin = position.top - scrollTop;
105
+ }
106
+ element.css('position', options.offsetParent ? '' : 'relative');
107
+ element.css('top', options.offsetParent ? '' : bodyEl[0].offsetHeight - offsetBottom - elementHeight - initialOffsetTop + 'px');
108
+ } else {
109
+ // affix === 'middle'
110
+ unpin = null;
111
+ element.css('position', 'fixed');
112
+ element.css('top', initialAffixTop + 'px');
113
+ }
114
+ };
115
+ $affix.$onResize = function () {
116
+ $affix.$parseOffsets();
117
+ $affix.checkPosition();
118
+ };
119
+ $affix.$debouncedOnResize = debounce($affix.$onResize, 50);
120
+ $affix.$parseOffsets = function () {
121
+ // Reset position to calculate correct offsetTop
122
+ element.css('position', options.offsetParent ? '' : 'relative');
123
+ if (options.offsetTop) {
124
+ if (options.offsetTop === 'auto') {
125
+ options.offsetTop = '+0';
126
+ }
127
+ if (options.offsetTop.match(/^[-+]\d+$/)) {
128
+ initialAffixTop = -options.offsetTop * 1;
129
+ if (options.offsetParent) {
130
+ offsetTop = dimensions.offset(parent[0]).top + options.offsetTop * 1;
131
+ } else {
132
+ offsetTop = dimensions.offset(element[0]).top - dimensions.css(element[0], 'marginTop', true) + options.offsetTop * 1;
133
+ }
134
+ } else {
135
+ offsetTop = options.offsetTop * 1;
136
+ }
137
+ }
138
+ if (options.offsetBottom) {
139
+ if (options.offsetParent && options.offsetBottom.match(/^[-+]\d+$/)) {
140
+ // add 1 pixel due to rounding problems...
141
+ offsetBottom = getScrollHeight() - (dimensions.offset(parent[0]).top + dimensions.height(parent[0])) + options.offsetBottom * 1 + 1;
142
+ } else {
143
+ offsetBottom = options.offsetBottom * 1;
144
+ }
145
+ }
146
+ };
147
+ // Private methods
148
+ function getRequiredAffixClass(unpin, position, elementHeight) {
149
+ var scrollTop = getScrollTop();
150
+ var scrollHeight = getScrollHeight();
151
+ if (scrollTop <= offsetTop) {
152
+ return 'top';
153
+ } else if (unpin !== null && scrollTop + unpin <= position.top) {
154
+ return 'middle';
155
+ } else if (offsetBottom !== null && position.top + elementHeight + initialAffixTop >= scrollHeight - offsetBottom) {
156
+ return 'bottom';
157
+ } else {
158
+ return 'middle';
159
+ }
160
+ }
161
+ function getScrollTop() {
162
+ return targetEl[0] === $window ? $window.pageYOffset : targetEl[0] === $window;
163
+ }
164
+ function getScrollHeight() {
165
+ return targetEl[0] === $window ? $window.document.body.scrollHeight : targetEl[0].scrollHeight;
166
+ }
167
+ $affix.init();
168
+ return $affix;
169
+ }
170
+ return AffixFactory;
171
+ }
172
+ ];
173
+ }).directive('bsAffix', [
174
+ '$affix',
175
+ '$window',
176
+ function ($affix, $window) {
177
+ return {
178
+ restrict: 'EAC',
179
+ require: '^?bsAffixTarget',
180
+ link: function postLink(scope, element, attr, affixTarget) {
181
+ var options = {
182
+ scope: scope,
183
+ offsetTop: 'auto',
184
+ target: affixTarget ? affixTarget.$element : angular.element($window)
185
+ };
186
+ angular.forEach([
187
+ 'offsetTop',
188
+ 'offsetBottom',
189
+ 'offsetParent',
190
+ 'offsetUnpin'
191
+ ], function (key) {
192
+ if (angular.isDefined(attr[key]))
193
+ options[key] = attr[key];
194
+ });
195
+ var affix = $affix(element, options);
196
+ scope.$on('$destroy', function () {
197
+ options = null;
198
+ affix = null;
199
+ });
200
+ }
201
+ };
202
+ }
203
+ ]).directive('bsAffixTarget', function () {
204
+ return {
205
+ controller: [
206
+ '$element',
207
+ function ($element) {
208
+ this.$element = $element;
209
+ }
210
+ ]
211
+ };
212
+ });
213
+
214
+ // Source: alert.js
215
+ // @BUG: following snippet won't compile correctly
216
+ // @TODO: submit issue to core
217
+ // '<span ng-if="title"><strong ng-bind="title"></strong>&nbsp;</span><span ng-bind-html="content"></span>' +
218
+ angular.module('mgcrea.ngStrap.alert', ['mgcrea.ngStrap.modal']).provider('$alert', function () {
219
+ var defaults = this.defaults = {
220
+ animation: 'am-fade',
221
+ prefixClass: 'alert',
222
+ placement: null,
223
+ template: 'alert/alert.tpl.html',
224
+ container: false,
225
+ element: null,
226
+ backdrop: false,
227
+ keyboard: true,
228
+ show: true,
229
+ duration: false,
230
+ type: false
231
+ };
232
+ this.$get = [
233
+ '$modal',
234
+ '$timeout',
235
+ function ($modal, $timeout) {
236
+ function AlertFactory(config) {
237
+ var $alert = {};
238
+ // Common vars
239
+ var options = angular.extend({}, defaults, config);
240
+ $alert = $modal(options);
241
+ // Support scope as string options [/*title, content, */type]
242
+ if (options.type) {
243
+ $alert.$scope.type = options.type;
244
+ }
245
+ // Support auto-close duration
246
+ var show = $alert.show;
247
+ if (options.duration) {
248
+ $alert.show = function () {
249
+ show();
250
+ $timeout(function () {
251
+ $alert.hide();
252
+ }, options.duration * 1000);
253
+ };
254
+ }
255
+ return $alert;
256
+ }
257
+ return AlertFactory;
258
+ }
259
+ ];
260
+ }).directive('bsAlert', [
261
+ '$window',
262
+ '$location',
263
+ '$sce',
264
+ '$alert',
265
+ function ($window, $location, $sce, $alert) {
266
+ var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
267
+ return {
268
+ restrict: 'EAC',
269
+ scope: true,
270
+ link: function postLink(scope, element, attr, transclusion) {
271
+ // Directive options
272
+ var options = {
273
+ scope: scope,
274
+ element: element,
275
+ show: false
276
+ };
277
+ angular.forEach([
278
+ 'template',
279
+ 'placement',
280
+ 'keyboard',
281
+ 'html',
282
+ 'container',
283
+ 'animation',
284
+ 'duration'
285
+ ], function (key) {
286
+ if (angular.isDefined(attr[key]))
287
+ options[key] = attr[key];
288
+ });
289
+ // Support scope as data-attrs
290
+ angular.forEach([
291
+ 'title',
292
+ 'content',
293
+ 'type'
294
+ ], function (key) {
295
+ attr[key] && attr.$observe(key, function (newValue, oldValue) {
296
+ scope[key] = $sce.trustAsHtml(newValue);
297
+ });
298
+ });
299
+ // Support scope as an object
300
+ attr.bsAlert && scope.$watch(attr.bsAlert, function (newValue, oldValue) {
301
+ if (angular.isObject(newValue)) {
302
+ angular.extend(scope, newValue);
303
+ } else {
304
+ scope.content = newValue;
305
+ }
306
+ }, true);
307
+ // Initialize alert
308
+ var alert = $alert(options);
309
+ // Trigger
310
+ element.on(attr.trigger || 'click', alert.toggle);
311
+ // Garbage collection
312
+ scope.$on('$destroy', function () {
313
+ alert.destroy();
314
+ options = null;
315
+ alert = null;
316
+ });
317
+ }
318
+ };
319
+ }
320
+ ]);
321
+
322
+ // Source: aside.js
323
+ angular.module('mgcrea.ngStrap.aside', ['mgcrea.ngStrap.modal']).provider('$aside', function () {
324
+ var defaults = this.defaults = {
325
+ animation: 'am-fade-and-slide-right',
326
+ prefixClass: 'aside',
327
+ placement: 'right',
328
+ template: 'aside/aside.tpl.html',
329
+ contentTemplate: false,
330
+ container: false,
331
+ element: null,
332
+ backdrop: true,
333
+ keyboard: true,
334
+ html: false,
335
+ show: true
336
+ };
337
+ this.$get = [
338
+ '$modal',
339
+ function ($modal) {
340
+ function AsideFactory(config) {
341
+ var $aside = {};
342
+ // Common vars
343
+ var options = angular.extend({}, defaults, config);
344
+ $aside = $modal(options);
345
+ return $aside;
346
+ }
347
+ return AsideFactory;
348
+ }
349
+ ];
350
+ }).directive('bsAside', [
351
+ '$window',
352
+ '$location',
353
+ '$sce',
354
+ '$aside',
355
+ function ($window, $location, $sce, $aside) {
356
+ var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
357
+ return {
358
+ restrict: 'EAC',
359
+ scope: true,
360
+ link: function postLink(scope, element, attr, transclusion) {
361
+ // Directive options
362
+ var options = {
363
+ scope: scope,
364
+ element: element,
365
+ show: false
366
+ };
367
+ angular.forEach([
368
+ 'template',
369
+ 'contentTemplate',
370
+ 'placement',
371
+ 'backdrop',
372
+ 'keyboard',
373
+ 'html',
374
+ 'container',
375
+ 'animation'
376
+ ], function (key) {
377
+ if (angular.isDefined(attr[key]))
378
+ options[key] = attr[key];
379
+ });
380
+ // Support scope as data-attrs
381
+ angular.forEach([
382
+ 'title',
383
+ 'content'
384
+ ], function (key) {
385
+ attr[key] && attr.$observe(key, function (newValue, oldValue) {
386
+ scope[key] = $sce.trustAsHtml(newValue);
387
+ });
388
+ });
389
+ // Support scope as an object
390
+ attr.bsAside && scope.$watch(attr.bsAside, function (newValue, oldValue) {
391
+ if (angular.isObject(newValue)) {
392
+ angular.extend(scope, newValue);
393
+ } else {
394
+ scope.content = newValue;
395
+ }
396
+ }, true);
397
+ // Initialize aside
398
+ var aside = $aside(options);
399
+ // Trigger
400
+ element.on(attr.trigger || 'click', aside.toggle);
401
+ // Garbage collection
402
+ scope.$on('$destroy', function () {
403
+ aside.destroy();
404
+ options = null;
405
+ aside = null;
406
+ });
407
+ }
408
+ };
409
+ }
410
+ ]);
411
+
412
+ // Source: button.js
413
+ angular.module('mgcrea.ngStrap.button', []).provider('$button', function () {
414
+ var defaults = this.defaults = {
415
+ activeClass: 'active',
416
+ toggleEvent: 'click'
417
+ };
418
+ this.$get = function () {
419
+ return { defaults: defaults };
420
+ };
421
+ }).directive('bsCheckboxGroup', function () {
422
+ return {
423
+ restrict: 'A',
424
+ require: 'ngModel',
425
+ compile: function postLink(element, attr) {
426
+ element.attr('data-toggle', 'buttons');
427
+ element.removeAttr('ng-model');
428
+ var children = element[0].querySelectorAll('input[type="checkbox"]');
429
+ angular.forEach(children, function (child) {
430
+ var childEl = angular.element(child);
431
+ childEl.attr('bs-checkbox', '');
432
+ childEl.attr('ng-model', attr.ngModel + '.' + childEl.attr('value'));
433
+ });
434
+ }
435
+ };
436
+ }).directive('bsCheckbox', [
437
+ '$button',
438
+ '$$rAF',
439
+ function ($button, $$rAF) {
440
+ var defaults = $button.defaults;
441
+ var constantValueRegExp = /^(true|false|\d+)$/;
442
+ return {
443
+ restrict: 'A',
444
+ require: 'ngModel',
445
+ link: function postLink(scope, element, attr, controller) {
446
+ var options = defaults;
447
+ // Support label > input[type="checkbox"]
448
+ var isInput = element[0].nodeName === 'INPUT';
449
+ var activeElement = isInput ? element.parent() : element;
450
+ var trueValue = angular.isDefined(attr.trueValue) ? attr.trueValue : true;
451
+ if (constantValueRegExp.test(attr.trueValue)) {
452
+ trueValue = scope.$eval(attr.trueValue);
453
+ }
454
+ var falseValue = angular.isDefined(attr.falseValue) ? attr.falseValue : false;
455
+ if (constantValueRegExp.test(attr.falseValue)) {
456
+ falseValue = scope.$eval(attr.falseValue);
457
+ }
458
+ // Parse exotic values
459
+ var hasExoticValues = typeof trueValue !== 'boolean' || typeof falseValue !== 'boolean';
460
+ if (hasExoticValues) {
461
+ controller.$parsers.push(function (viewValue) {
462
+ // console.warn('$parser', element.attr('ng-model'), 'viewValue', viewValue);
463
+ return viewValue ? trueValue : falseValue;
464
+ });
465
+ // Fix rendering for exotic values
466
+ scope.$watch(attr.ngModel, function (newValue, oldValue) {
467
+ controller.$render();
468
+ });
469
+ }
470
+ // model -> view
471
+ controller.$render = function () {
472
+ // console.warn('$render', element.attr('ng-model'), 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue, 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue);
473
+ var isActive = angular.equals(controller.$modelValue, trueValue);
474
+ $$rAF(function () {
475
+ if (isInput)
476
+ element[0].checked = isActive;
477
+ activeElement.toggleClass(options.activeClass, isActive);
478
+ });
479
+ };
480
+ // view -> model
481
+ element.bind(options.toggleEvent, function () {
482
+ scope.$apply(function () {
483
+ // console.warn('!click', element.attr('ng-model'), 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue, 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue);
484
+ if (!isInput) {
485
+ controller.$setViewValue(!activeElement.hasClass('active'));
486
+ }
487
+ if (!hasExoticValues) {
488
+ controller.$render();
489
+ }
490
+ });
491
+ });
492
+ }
493
+ };
494
+ }
495
+ ]).directive('bsRadioGroup', function () {
496
+ return {
497
+ restrict: 'A',
498
+ require: 'ngModel',
499
+ compile: function postLink(element, attr) {
500
+ element.attr('data-toggle', 'buttons');
501
+ element.removeAttr('ng-model');
502
+ var children = element[0].querySelectorAll('input[type="radio"]');
503
+ angular.forEach(children, function (child) {
504
+ angular.element(child).attr('bs-radio', '');
505
+ angular.element(child).attr('ng-model', attr.ngModel);
506
+ });
507
+ }
508
+ };
509
+ }).directive('bsRadio', [
510
+ '$button',
511
+ '$$rAF',
512
+ function ($button, $$rAF) {
513
+ var defaults = $button.defaults;
514
+ var constantValueRegExp = /^(true|false|\d+)$/;
515
+ return {
516
+ restrict: 'A',
517
+ require: 'ngModel',
518
+ link: function postLink(scope, element, attr, controller) {
519
+ var options = defaults;
520
+ // Support `label > input[type="radio"]` markup
521
+ var isInput = element[0].nodeName === 'INPUT';
522
+ var activeElement = isInput ? element.parent() : element;
523
+ var value = constantValueRegExp.test(attr.value) ? scope.$eval(attr.value) : attr.value;
524
+ // model -> view
525
+ controller.$render = function () {
526
+ // console.warn('$render', element.attr('value'), 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue, 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue);
527
+ var isActive = angular.equals(controller.$modelValue, value);
528
+ $$rAF(function () {
529
+ if (isInput)
530
+ element[0].checked = isActive;
531
+ activeElement.toggleClass(options.activeClass, isActive);
532
+ });
533
+ };
534
+ // view -> model
535
+ element.bind(options.toggleEvent, function () {
536
+ scope.$apply(function () {
537
+ // console.warn('!click', element.attr('value'), 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue, 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue);
538
+ controller.$setViewValue(value);
539
+ controller.$render();
540
+ });
541
+ });
542
+ }
543
+ };
544
+ }
545
+ ]);
546
+
547
+ // Source: datepicker.js
548
+ angular.module('mgcrea.ngStrap.datepicker', [
549
+ 'mgcrea.ngStrap.helpers.dateParser',
550
+ 'mgcrea.ngStrap.tooltip'
551
+ ]).provider('$datepicker', function () {
552
+ var defaults = this.defaults = {
553
+ animation: 'am-fade',
554
+ prefixClass: 'datepicker',
555
+ placement: 'bottom-left',
556
+ template: 'datepicker/datepicker.tpl.html',
557
+ trigger: 'focus',
558
+ container: false,
559
+ keyboard: true,
560
+ html: false,
561
+ delay: 0,
562
+ useNative: false,
563
+ dateType: 'date',
564
+ dateFormat: 'shortDate',
565
+ strictFormat: false,
566
+ autoclose: false,
567
+ minDate: -Infinity,
568
+ maxDate: +Infinity,
569
+ startView: 0,
570
+ minView: 0,
571
+ startWeek: 0
572
+ };
573
+ this.$get = [
574
+ '$window',
575
+ '$document',
576
+ '$rootScope',
577
+ '$sce',
578
+ '$locale',
579
+ 'dateFilter',
580
+ 'datepickerViews',
581
+ '$tooltip',
582
+ function ($window, $document, $rootScope, $sce, $locale, dateFilter, datepickerViews, $tooltip) {
583
+ var bodyEl = angular.element($window.document.body);
584
+ var isTouch = 'createTouch' in $window.document;
585
+ var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent);
586
+ if (!defaults.lang)
587
+ defaults.lang = $locale.id;
588
+ function DatepickerFactory(element, controller, config) {
589
+ var $datepicker = $tooltip(element, angular.extend({}, defaults, config));
590
+ var parentScope = config.scope;
591
+ var options = $datepicker.$options;
592
+ var scope = $datepicker.$scope;
593
+ if (options.startView)
594
+ options.startView -= options.minView;
595
+ // View vars
596
+ var pickerViews = datepickerViews($datepicker);
597
+ $datepicker.$views = pickerViews.views;
598
+ var viewDate = pickerViews.viewDate;
599
+ scope.$mode = options.startView;
600
+ var $picker = $datepicker.$views[scope.$mode];
601
+ // Scope methods
602
+ scope.$select = function (date) {
603
+ $datepicker.select(date);
604
+ };
605
+ scope.$selectPane = function (value) {
606
+ $datepicker.$selectPane(value);
607
+ };
608
+ scope.$toggleMode = function () {
609
+ $datepicker.setMode((scope.$mode + 1) % $datepicker.$views.length);
610
+ };
611
+ // Public methods
612
+ $datepicker.update = function (date) {
613
+ // console.warn('$datepicker.update() newValue=%o', date);
614
+ if (angular.isDate(date) && !isNaN(date.getTime())) {
615
+ $datepicker.$date = date;
616
+ $picker.update.call($picker, date);
617
+ }
618
+ // Build only if pristine
619
+ $datepicker.$build(true);
620
+ };
621
+ $datepicker.select = function (date, keep) {
622
+ // console.warn('$datepicker.select', date, scope.$mode);
623
+ if (!angular.isDate(controller.$dateValue))
624
+ controller.$dateValue = new Date(date);
625
+ controller.$dateValue.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
626
+ if (!scope.$mode || keep) {
627
+ controller.$setViewValue(controller.$dateValue);
628
+ controller.$render();
629
+ if (options.autoclose && !keep) {
630
+ $datepicker.hide(true);
631
+ }
632
+ } else {
633
+ angular.extend(viewDate, {
634
+ year: date.getFullYear(),
635
+ month: date.getMonth(),
636
+ date: date.getDate()
637
+ });
638
+ $datepicker.setMode(scope.$mode - 1);
639
+ $datepicker.$build();
640
+ }
641
+ };
642
+ $datepicker.setMode = function (mode) {
643
+ // console.warn('$datepicker.setMode', mode);
644
+ scope.$mode = mode;
645
+ $picker = $datepicker.$views[scope.$mode];
646
+ $datepicker.$build();
647
+ };
648
+ // Protected methods
649
+ $datepicker.$build = function (pristine) {
650
+ // console.warn('$datepicker.$build() viewDate=%o', viewDate);
651
+ if (pristine === true && $picker.built)
652
+ return;
653
+ if (pristine === false && !$picker.built)
654
+ return;
655
+ $picker.build.call($picker);
656
+ };
657
+ $datepicker.$updateSelected = function () {
658
+ for (var i = 0, l = scope.rows.length; i < l; i++) {
659
+ angular.forEach(scope.rows[i], updateSelected);
660
+ }
661
+ };
662
+ $datepicker.$isSelected = function (date) {
663
+ return $picker.isSelected(date);
664
+ };
665
+ $datepicker.$selectPane = function (value) {
666
+ var steps = $picker.steps;
667
+ var targetDate = new Date(Date.UTC(viewDate.year + (steps.year || 0) * value, viewDate.month + (steps.month || 0) * value, viewDate.date + (steps.day || 0) * value));
668
+ angular.extend(viewDate, {
669
+ year: targetDate.getUTCFullYear(),
670
+ month: targetDate.getUTCMonth(),
671
+ date: targetDate.getUTCDate()
672
+ });
673
+ $datepicker.$build();
674
+ };
675
+ $datepicker.$onMouseDown = function (evt) {
676
+ // Prevent blur on mousedown on .dropdown-menu
677
+ evt.preventDefault();
678
+ evt.stopPropagation();
679
+ // Emulate click for mobile devices
680
+ if (isTouch) {
681
+ var targetEl = angular.element(evt.target);
682
+ if (targetEl[0].nodeName.toLowerCase() !== 'button') {
683
+ targetEl = targetEl.parent();
684
+ }
685
+ targetEl.triggerHandler('click');
686
+ }
687
+ };
688
+ $datepicker.$onKeyDown = function (evt) {
689
+ if (!/(38|37|39|40|13)/.test(evt.keyCode) || evt.shiftKey || evt.altKey)
690
+ return;
691
+ evt.preventDefault();
692
+ evt.stopPropagation();
693
+ if (evt.keyCode === 13) {
694
+ if (!scope.$mode) {
695
+ return $datepicker.hide(true);
696
+ } else {
697
+ return scope.$apply(function () {
698
+ $datepicker.setMode(scope.$mode - 1);
699
+ });
700
+ }
701
+ }
702
+ // Navigate with keyboard
703
+ $picker.onKeyDown(evt);
704
+ parentScope.$digest();
705
+ };
706
+ // Private
707
+ function updateSelected(el) {
708
+ el.selected = $datepicker.$isSelected(el.date);
709
+ }
710
+ function focusElement() {
711
+ element[0].focus();
712
+ }
713
+ // Overrides
714
+ var _init = $datepicker.init;
715
+ $datepicker.init = function () {
716
+ if (isNative && options.useNative) {
717
+ element.prop('type', 'date');
718
+ element.css('-webkit-appearance', 'textfield');
719
+ return;
720
+ } else if (isTouch) {
721
+ element.prop('type', 'text');
722
+ element.attr('readonly', 'true');
723
+ element.on('click', focusElement);
724
+ }
725
+ _init();
726
+ };
727
+ var _destroy = $datepicker.destroy;
728
+ $datepicker.destroy = function () {
729
+ if (isNative && options.useNative) {
730
+ element.off('click', focusElement);
731
+ }
732
+ _destroy();
733
+ };
734
+ var _show = $datepicker.show;
735
+ $datepicker.show = function () {
736
+ _show();
737
+ setTimeout(function () {
738
+ $datepicker.$element.on(isTouch ? 'touchstart' : 'mousedown', $datepicker.$onMouseDown);
739
+ if (options.keyboard) {
740
+ element.on('keydown', $datepicker.$onKeyDown);
741
+ }
742
+ });
743
+ };
744
+ var _hide = $datepicker.hide;
745
+ $datepicker.hide = function (blur) {
746
+ $datepicker.$element.off(isTouch ? 'touchstart' : 'mousedown', $datepicker.$onMouseDown);
747
+ if (options.keyboard) {
748
+ element.off('keydown', $datepicker.$onKeyDown);
749
+ }
750
+ _hide(blur);
751
+ };
752
+ return $datepicker;
753
+ }
754
+ DatepickerFactory.defaults = defaults;
755
+ return DatepickerFactory;
756
+ }
757
+ ];
758
+ }).directive('bsDatepicker', [
759
+ '$window',
760
+ '$parse',
761
+ '$q',
762
+ '$locale',
763
+ 'dateFilter',
764
+ '$datepicker',
765
+ '$dateParser',
766
+ '$timeout',
767
+ function ($window, $parse, $q, $locale, dateFilter, $datepicker, $dateParser, $timeout) {
768
+ var defaults = $datepicker.defaults;
769
+ var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent);
770
+ var isNumeric = function (n) {
771
+ return !isNaN(parseFloat(n)) && isFinite(n);
772
+ };
773
+ return {
774
+ restrict: 'EAC',
775
+ require: 'ngModel',
776
+ link: function postLink(scope, element, attr, controller) {
777
+ // Directive options
778
+ var options = {
779
+ scope: scope,
780
+ controller: controller
781
+ };
782
+ angular.forEach([
783
+ 'placement',
784
+ 'container',
785
+ 'delay',
786
+ 'trigger',
787
+ 'keyboard',
788
+ 'html',
789
+ 'animation',
790
+ 'template',
791
+ 'autoclose',
792
+ 'dateType',
793
+ 'dateFormat',
794
+ 'strictFormat',
795
+ 'startWeek',
796
+ 'useNative',
797
+ 'lang',
798
+ 'startView',
799
+ 'minView'
800
+ ], function (key) {
801
+ if (angular.isDefined(attr[key]))
802
+ options[key] = attr[key];
803
+ });
804
+ // Initialize datepicker
805
+ if (isNative && options.useNative)
806
+ options.dateFormat = 'yyyy-MM-dd';
807
+ var datepicker = $datepicker(element, controller, options);
808
+ options = datepicker.$options;
809
+ // Observe attributes for changes
810
+ angular.forEach([
811
+ 'minDate',
812
+ 'maxDate'
813
+ ], function (key) {
814
+ // console.warn('attr.$observe(%s)', key, attr[key]);
815
+ angular.isDefined(attr[key]) && attr.$observe(key, function (newValue) {
816
+ // console.warn('attr.$observe(%s)=%o', key, newValue);
817
+ if (newValue === 'today') {
818
+ var today = new Date();
819
+ datepicker.$options[key] = +new Date(today.getFullYear(), today.getMonth(), today.getDate() + (key === 'maxDate' ? 1 : 0), 0, 0, 0, key === 'minDate' ? 0 : -1);
820
+ } else if (angular.isString(newValue) && newValue.match(/^".+"$/)) {
821
+ // Support {{ dateObj }}
822
+ datepicker.$options[key] = +new Date(newValue.substr(1, newValue.length - 2));
823
+ } else if (isNumeric(newValue)) {
824
+ datepicker.$options[key] = +new Date(parseInt(newValue, 10));
825
+ } else {
826
+ datepicker.$options[key] = +new Date(newValue);
827
+ }
828
+ // Build only if dirty
829
+ !isNaN(datepicker.$options[key]) && datepicker.$build(false);
830
+ });
831
+ });
832
+ // Watch model for changes
833
+ scope.$watch(attr.ngModel, function (newValue, oldValue) {
834
+ datepicker.update(controller.$dateValue);
835
+ }, true);
836
+ var dateParser = $dateParser({
837
+ format: options.dateFormat,
838
+ lang: options.lang,
839
+ strict: options.strictFormat
840
+ });
841
+ // viewValue -> $parsers -> modelValue
842
+ controller.$parsers.unshift(function (viewValue) {
843
+ // console.warn('$parser("%s"): viewValue=%o', element.attr('ng-model'), viewValue);
844
+ // Null values should correctly reset the model value & validity
845
+ if (!viewValue) {
846
+ controller.$setValidity('date', true);
847
+ return;
848
+ }
849
+ var parsedDate = dateParser.parse(viewValue, controller.$dateValue);
850
+ if (!parsedDate || isNaN(parsedDate.getTime())) {
851
+ controller.$setValidity('date', false);
852
+ return;
853
+ } else {
854
+ var isValid = (isNaN(datepicker.$options.minDate) || parsedDate.getTime() >= datepicker.$options.minDate) && (isNaN(datepicker.$options.maxDate) || parsedDate.getTime() <= datepicker.$options.maxDate);
855
+ controller.$setValidity('date', isValid);
856
+ // Only update the model when we have a valid date
857
+ if (isValid)
858
+ controller.$dateValue = parsedDate;
859
+ }
860
+ if (options.dateType === 'string') {
861
+ return dateFilter(viewValue, options.dateFormat);
862
+ } else if (options.dateType === 'number') {
863
+ return controller.$dateValue.getTime();
864
+ } else if (options.dateType === 'iso') {
865
+ return controller.$dateValue.toISOString();
866
+ } else {
867
+ return new Date(controller.$dateValue);
868
+ }
869
+ });
870
+ // modelValue -> $formatters -> viewValue
871
+ controller.$formatters.push(function (modelValue) {
872
+ // console.warn('$formatter("%s"): modelValue=%o (%o)', element.attr('ng-model'), modelValue, typeof modelValue);
873
+ var date;
874
+ if (angular.isUndefined(modelValue) || modelValue === null) {
875
+ date = NaN;
876
+ } else if (angular.isDate(modelValue)) {
877
+ date = modelValue;
878
+ } else if (options.dateType === 'string') {
879
+ date = dateParser.parse(modelValue);
880
+ } else {
881
+ date = new Date(modelValue);
882
+ }
883
+ // Setup default value?
884
+ // if(isNaN(date.getTime())) {
885
+ // var today = new Date();
886
+ // date = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0, 0);
887
+ // }
888
+ controller.$dateValue = date;
889
+ return controller.$dateValue;
890
+ });
891
+ // viewValue -> element
892
+ controller.$render = function () {
893
+ // console.warn('$render("%s"): viewValue=%o', element.attr('ng-model'), controller.$viewValue);
894
+ element.val(!controller.$dateValue || isNaN(controller.$dateValue.getTime()) ? '' : dateFilter(controller.$dateValue, options.dateFormat));
895
+ };
896
+ // Garbage collection
897
+ scope.$on('$destroy', function () {
898
+ datepicker.destroy();
899
+ options = null;
900
+ datepicker = null;
901
+ });
902
+ }
903
+ };
904
+ }
905
+ ]).provider('datepickerViews', function () {
906
+ var defaults = this.defaults = {
907
+ dayFormat: 'dd',
908
+ daySplit: 7
909
+ };
910
+ // Split array into smaller arrays
911
+ function split(arr, size) {
912
+ var arrays = [];
913
+ while (arr.length > 0) {
914
+ arrays.push(arr.splice(0, size));
915
+ }
916
+ return arrays;
917
+ }
918
+ // Modulus operator
919
+ function mod(n, m) {
920
+ return (n % m + m) % m;
921
+ }
922
+ this.$get = [
923
+ '$locale',
924
+ '$sce',
925
+ 'dateFilter',
926
+ function ($locale, $sce, dateFilter) {
927
+ return function (picker) {
928
+ var scope = picker.$scope;
929
+ var options = picker.$options;
930
+ var weekDaysMin = $locale.DATETIME_FORMATS.SHORTDAY;
931
+ var weekDaysLabels = weekDaysMin.slice(options.startWeek).concat(weekDaysMin.slice(0, options.startWeek));
932
+ var weekDaysLabelsHtml = $sce.trustAsHtml('<th class="dow text-center">' + weekDaysLabels.join('</th><th class="dow text-center">') + '</th>');
933
+ var startDate = picker.$date || new Date();
934
+ var viewDate = {
935
+ year: startDate.getFullYear(),
936
+ month: startDate.getMonth(),
937
+ date: startDate.getDate()
938
+ };
939
+ var timezoneOffset = startDate.getTimezoneOffset() * 60000;
940
+ var views = [
941
+ {
942
+ format: 'dd',
943
+ split: 7,
944
+ steps: { month: 1 },
945
+ update: function (date, force) {
946
+ if (!this.built || force || date.getFullYear() !== viewDate.year || date.getMonth() !== viewDate.month) {
947
+ angular.extend(viewDate, {
948
+ year: picker.$date.getFullYear(),
949
+ month: picker.$date.getMonth(),
950
+ date: picker.$date.getDate()
951
+ });
952
+ picker.$build();
953
+ } else if (date.getDate() !== viewDate.date) {
954
+ viewDate.date = picker.$date.getDate();
955
+ picker.$updateSelected();
956
+ }
957
+ },
958
+ build: function () {
959
+ var firstDayOfMonth = new Date(viewDate.year, viewDate.month, 1), firstDayOfMonthOffset = firstDayOfMonth.getTimezoneOffset();
960
+ var firstDate = new Date(+firstDayOfMonth - mod(firstDayOfMonth.getDay() - options.startWeek, 6) * 86400000), firstDateOffset = firstDate.getTimezoneOffset();
961
+ // Handle daylight time switch
962
+ if (firstDateOffset !== firstDayOfMonthOffset)
963
+ firstDate = new Date(+firstDate + (firstDateOffset - firstDayOfMonthOffset) * 60000);
964
+ var days = [], day;
965
+ for (var i = 0; i < 42; i++) {
966
+ // < 7 * 6
967
+ day = new Date(firstDate.getFullYear(), firstDate.getMonth(), firstDate.getDate() + i);
968
+ days.push({
969
+ date: day,
970
+ label: dateFilter(day, this.format),
971
+ selected: picker.$date && this.isSelected(day),
972
+ muted: day.getMonth() !== viewDate.month,
973
+ disabled: this.isDisabled(day)
974
+ });
975
+ }
976
+ scope.title = dateFilter(firstDayOfMonth, 'MMMM yyyy');
977
+ scope.labels = weekDaysLabelsHtml;
978
+ scope.rows = split(days, this.split);
979
+ this.built = true;
980
+ },
981
+ isSelected: function (date) {
982
+ return picker.$date && date.getFullYear() === picker.$date.getFullYear() && date.getMonth() === picker.$date.getMonth() && date.getDate() === picker.$date.getDate();
983
+ },
984
+ isDisabled: function (date) {
985
+ return date.getTime() < options.minDate || date.getTime() > options.maxDate;
986
+ },
987
+ onKeyDown: function (evt) {
988
+ var actualTime = picker.$date.getTime();
989
+ if (evt.keyCode === 37)
990
+ picker.select(new Date(actualTime - 1 * 86400000), true);
991
+ else if (evt.keyCode === 38)
992
+ picker.select(new Date(actualTime - 7 * 86400000), true);
993
+ else if (evt.keyCode === 39)
994
+ picker.select(new Date(actualTime + 1 * 86400000), true);
995
+ else if (evt.keyCode === 40)
996
+ picker.select(new Date(actualTime + 7 * 86400000), true);
997
+ }
998
+ },
999
+ {
1000
+ name: 'month',
1001
+ format: 'MMM',
1002
+ split: 4,
1003
+ steps: { year: 1 },
1004
+ update: function (date, force) {
1005
+ if (!this.built || date.getFullYear() !== viewDate.year) {
1006
+ angular.extend(viewDate, {
1007
+ year: picker.$date.getFullYear(),
1008
+ month: picker.$date.getMonth(),
1009
+ date: picker.$date.getDate()
1010
+ });
1011
+ picker.$build();
1012
+ } else if (date.getMonth() !== viewDate.month) {
1013
+ angular.extend(viewDate, {
1014
+ month: picker.$date.getMonth(),
1015
+ date: picker.$date.getDate()
1016
+ });
1017
+ picker.$updateSelected();
1018
+ }
1019
+ },
1020
+ build: function () {
1021
+ var firstMonth = new Date(viewDate.year, 0, 1);
1022
+ var months = [], month;
1023
+ for (var i = 0; i < 12; i++) {
1024
+ month = new Date(viewDate.year, i, 1);
1025
+ months.push({
1026
+ date: month,
1027
+ label: dateFilter(month, this.format),
1028
+ selected: picker.$isSelected(month),
1029
+ disabled: this.isDisabled(month)
1030
+ });
1031
+ }
1032
+ scope.title = dateFilter(month, 'yyyy');
1033
+ scope.labels = false;
1034
+ scope.rows = split(months, this.split);
1035
+ this.built = true;
1036
+ },
1037
+ isSelected: function (date) {
1038
+ return picker.$date && date.getFullYear() === picker.$date.getFullYear() && date.getMonth() === picker.$date.getMonth();
1039
+ },
1040
+ isDisabled: function (date) {
1041
+ var lastDate = +new Date(date.getFullYear(), date.getMonth() + 1, 0);
1042
+ return lastDate < options.minDate || date.getTime() > options.maxDate;
1043
+ },
1044
+ onKeyDown: function (evt) {
1045
+ var actualMonth = picker.$date.getMonth();
1046
+ if (evt.keyCode === 37)
1047
+ picker.select(new Date(picker.$date.setMonth(actualMonth - 1)), true);
1048
+ else if (evt.keyCode === 38)
1049
+ picker.select(new Date(picker.$date.setMonth(actualMonth - 4)), true);
1050
+ else if (evt.keyCode === 39)
1051
+ picker.select(new Date(picker.$date.setMonth(actualMonth + 1)), true);
1052
+ else if (evt.keyCode === 40)
1053
+ picker.select(new Date(picker.$date.setMonth(actualMonth + 4)), true);
1054
+ }
1055
+ },
1056
+ {
1057
+ name: 'year',
1058
+ format: 'yyyy',
1059
+ split: 4,
1060
+ steps: { year: 12 },
1061
+ update: function (date, force) {
1062
+ if (!this.built || force || parseInt(date.getFullYear() / 20, 10) !== parseInt(viewDate.year / 20, 10)) {
1063
+ angular.extend(viewDate, {
1064
+ year: picker.$date.getFullYear(),
1065
+ month: picker.$date.getMonth(),
1066
+ date: picker.$date.getDate()
1067
+ });
1068
+ picker.$build();
1069
+ } else if (date.getFullYear() !== viewDate.year) {
1070
+ angular.extend(viewDate, {
1071
+ year: picker.$date.getFullYear(),
1072
+ month: picker.$date.getMonth(),
1073
+ date: picker.$date.getDate()
1074
+ });
1075
+ picker.$updateSelected();
1076
+ }
1077
+ },
1078
+ build: function () {
1079
+ var firstYear = viewDate.year - viewDate.year % (this.split * 3);
1080
+ var years = [], year;
1081
+ for (var i = 0; i < 12; i++) {
1082
+ year = new Date(firstYear + i, 0, 1);
1083
+ years.push({
1084
+ date: year,
1085
+ label: dateFilter(year, this.format),
1086
+ selected: picker.$isSelected(year),
1087
+ disabled: this.isDisabled(year)
1088
+ });
1089
+ }
1090
+ scope.title = years[0].label + '-' + years[years.length - 1].label;
1091
+ scope.labels = false;
1092
+ scope.rows = split(years, this.split);
1093
+ this.built = true;
1094
+ },
1095
+ isSelected: function (date) {
1096
+ return picker.$date && date.getFullYear() === picker.$date.getFullYear();
1097
+ },
1098
+ isDisabled: function (date) {
1099
+ var lastDate = +new Date(date.getFullYear() + 1, 0, 0);
1100
+ return lastDate < options.minDate || date.getTime() > options.maxDate;
1101
+ },
1102
+ onKeyDown: function (evt) {
1103
+ var actualYear = picker.$date.getFullYear();
1104
+ if (evt.keyCode === 37)
1105
+ picker.select(new Date(picker.$date.setYear(actualYear - 1)), true);
1106
+ else if (evt.keyCode === 38)
1107
+ picker.select(new Date(picker.$date.setYear(actualYear - 4)), true);
1108
+ else if (evt.keyCode === 39)
1109
+ picker.select(new Date(picker.$date.setYear(actualYear + 1)), true);
1110
+ else if (evt.keyCode === 40)
1111
+ picker.select(new Date(picker.$date.setYear(actualYear + 4)), true);
1112
+ }
1113
+ }
1114
+ ];
1115
+ return {
1116
+ views: options.minView ? Array.prototype.slice.call(views, options.minView) : views,
1117
+ viewDate: viewDate
1118
+ };
1119
+ };
1120
+ }
1121
+ ];
1122
+ });
1123
+
1124
+ // Source: dropdown.js
1125
+ angular.module('mgcrea.ngStrap.dropdown', ['mgcrea.ngStrap.tooltip']).provider('$dropdown', function () {
1126
+ var defaults = this.defaults = {
1127
+ animation: 'am-fade',
1128
+ prefixClass: 'dropdown',
1129
+ placement: 'bottom-left',
1130
+ template: 'dropdown/dropdown.tpl.html',
1131
+ trigger: 'click',
1132
+ container: false,
1133
+ keyboard: true,
1134
+ html: false,
1135
+ delay: 0
1136
+ };
1137
+ this.$get = [
1138
+ '$window',
1139
+ '$rootScope',
1140
+ '$tooltip',
1141
+ function ($window, $rootScope, $tooltip) {
1142
+ var bodyEl = angular.element($window.document.body);
1143
+ var matchesSelector = Element.prototype.matchesSelector || Element.prototype.webkitMatchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector;
1144
+ function DropdownFactory(element, config) {
1145
+ var $dropdown = {};
1146
+ // Common vars
1147
+ var options = angular.extend({}, defaults, config);
1148
+ var scope = $dropdown.$scope = options.scope && options.scope.$new() || $rootScope.$new();
1149
+ $dropdown = $tooltip(element, options);
1150
+ // Protected methods
1151
+ $dropdown.$onKeyDown = function (evt) {
1152
+ if (!/(38|40)/.test(evt.keyCode))
1153
+ return;
1154
+ evt.preventDefault();
1155
+ evt.stopPropagation();
1156
+ // Retrieve focused index
1157
+ var items = angular.element($dropdown.$element[0].querySelectorAll('li:not(.divider) a'));
1158
+ if (!items.length)
1159
+ return;
1160
+ var index;
1161
+ angular.forEach(items, function (el, i) {
1162
+ if (matchesSelector && matchesSelector.call(el, ':focus'))
1163
+ index = i;
1164
+ });
1165
+ // Navigate with keyboard
1166
+ if (evt.keyCode === 38 && index > 0)
1167
+ index--;
1168
+ else if (evt.keyCode === 40 && index < items.length - 1)
1169
+ index++;
1170
+ else if (angular.isUndefined(index))
1171
+ index = 0;
1172
+ items.eq(index)[0].focus();
1173
+ };
1174
+ // Overrides
1175
+ var show = $dropdown.show;
1176
+ $dropdown.show = function () {
1177
+ show();
1178
+ setTimeout(function () {
1179
+ options.keyboard && $dropdown.$element.on('keydown', $dropdown.$onKeyDown);
1180
+ bodyEl.on('click', onBodyClick);
1181
+ });
1182
+ };
1183
+ var hide = $dropdown.hide;
1184
+ $dropdown.hide = function () {
1185
+ options.keyboard && $dropdown.$element.off('keydown', $dropdown.$onKeyDown);
1186
+ bodyEl.off('click', onBodyClick);
1187
+ hide();
1188
+ };
1189
+ // Private functions
1190
+ function onBodyClick(evt) {
1191
+ if (evt.target === element[0])
1192
+ return;
1193
+ return evt.target !== element[0] && $dropdown.hide();
1194
+ }
1195
+ return $dropdown;
1196
+ }
1197
+ return DropdownFactory;
1198
+ }
1199
+ ];
1200
+ }).directive('bsDropdown', [
1201
+ '$window',
1202
+ '$location',
1203
+ '$sce',
1204
+ '$dropdown',
1205
+ function ($window, $location, $sce, $dropdown) {
1206
+ return {
1207
+ restrict: 'EAC',
1208
+ scope: true,
1209
+ link: function postLink(scope, element, attr, transclusion) {
1210
+ // Directive options
1211
+ var options = { scope: scope };
1212
+ angular.forEach([
1213
+ 'placement',
1214
+ 'container',
1215
+ 'delay',
1216
+ 'trigger',
1217
+ 'keyboard',
1218
+ 'html',
1219
+ 'animation',
1220
+ 'template'
1221
+ ], function (key) {
1222
+ if (angular.isDefined(attr[key]))
1223
+ options[key] = attr[key];
1224
+ });
1225
+ // Support scope as an object
1226
+ attr.bsDropdown && scope.$watch(attr.bsDropdown, function (newValue, oldValue) {
1227
+ scope.content = newValue;
1228
+ }, true);
1229
+ // Initialize dropdown
1230
+ var dropdown = $dropdown(element, options);
1231
+ // Garbage collection
1232
+ scope.$on('$destroy', function () {
1233
+ dropdown.destroy();
1234
+ options = null;
1235
+ dropdown = null;
1236
+ });
1237
+ }
1238
+ };
1239
+ }
1240
+ ]);
1241
+
1242
+ // Source: date-parser.js
1243
+ angular.module('mgcrea.ngStrap.helpers.dateParser', []).provider('$dateParser', [
1244
+ '$localeProvider',
1245
+ function ($localeProvider) {
1246
+ var proto = Date.prototype;
1247
+ function isNumeric(n) {
1248
+ return !isNaN(parseFloat(n)) && isFinite(n);
1249
+ }
1250
+ var defaults = this.defaults = {
1251
+ format: 'shortDate',
1252
+ strict: false
1253
+ };
1254
+ this.$get = [
1255
+ '$locale',
1256
+ function ($locale) {
1257
+ var DateParserFactory = function (config) {
1258
+ var options = angular.extend({}, defaults, config);
1259
+ var $dateParser = {};
1260
+ var regExpMap = {
1261
+ 'sss': '[0-9]{3}',
1262
+ 'ss': '[0-5][0-9]',
1263
+ 's': options.strict ? '[1-5]?[0-9]' : '[0-9]|[0-5][0-9]',
1264
+ 'mm': '[0-5][0-9]',
1265
+ 'm': options.strict ? '[1-5]?[0-9]' : '[0-9]|[0-5][0-9]',
1266
+ 'HH': '[01][0-9]|2[0-3]',
1267
+ 'H': options.strict ? '1?[0-9]|2[0-3]' : '[01]?[0-9]|2[0-3]',
1268
+ 'hh': '[0][1-9]|[1][012]',
1269
+ 'h': options.strict ? '[1-9]|1[012]' : '0?[1-9]|1[012]',
1270
+ 'a': 'AM|PM',
1271
+ 'EEEE': $locale.DATETIME_FORMATS.DAY.join('|'),
1272
+ 'EEE': $locale.DATETIME_FORMATS.SHORTDAY.join('|'),
1273
+ 'dd': '0[1-9]|[12][0-9]|3[01]',
1274
+ 'd': options.strict ? '[1-9]|[1-2][0-9]|3[01]' : '0?[1-9]|[1-2][0-9]|3[01]',
1275
+ 'MMMM': $locale.DATETIME_FORMATS.MONTH.join('|'),
1276
+ 'MMM': $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
1277
+ 'MM': '0[1-9]|1[012]',
1278
+ 'M': options.strict ? '[1-9]|1[012]' : '0?[1-9]|1[012]',
1279
+ 'yyyy': '[1]{1}[0-9]{3}|[2]{1}[0-9]{3}',
1280
+ 'yy': '[0-9]{2}',
1281
+ 'y': options.strict ? '-?(0|[1-9][0-9]{0,3})' : '-?0*[0-9]{1,4}'
1282
+ };
1283
+ var setFnMap = {
1284
+ 'sss': proto.setMilliseconds,
1285
+ 'ss': proto.setSeconds,
1286
+ 's': proto.setSeconds,
1287
+ 'mm': proto.setMinutes,
1288
+ 'm': proto.setMinutes,
1289
+ 'HH': proto.setHours,
1290
+ 'H': proto.setHours,
1291
+ 'hh': proto.setHours,
1292
+ 'h': proto.setHours,
1293
+ 'dd': proto.setDate,
1294
+ 'd': proto.setDate,
1295
+ 'a': function (value) {
1296
+ var hours = this.getHours();
1297
+ return this.setHours(value.match(/pm/i) ? hours + 12 : hours);
1298
+ },
1299
+ 'MMMM': function (value) {
1300
+ return this.setMonth($locale.DATETIME_FORMATS.MONTH.indexOf(value));
1301
+ },
1302
+ 'MMM': function (value) {
1303
+ return this.setMonth($locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value));
1304
+ },
1305
+ 'MM': function (value) {
1306
+ return this.setMonth(1 * value - 1);
1307
+ },
1308
+ 'M': function (value) {
1309
+ return this.setMonth(1 * value - 1);
1310
+ },
1311
+ 'yyyy': proto.setFullYear,
1312
+ 'yy': function (value) {
1313
+ return this.setFullYear(2000 + 1 * value);
1314
+ },
1315
+ 'y': proto.setFullYear
1316
+ };
1317
+ var regex, setMap;
1318
+ $dateParser.init = function () {
1319
+ $dateParser.$format = $locale.DATETIME_FORMATS[options.format] || options.format;
1320
+ regex = regExpForFormat($dateParser.$format);
1321
+ setMap = setMapForFormat($dateParser.$format);
1322
+ };
1323
+ $dateParser.isValid = function (date) {
1324
+ if (angular.isDate(date))
1325
+ return !isNaN(date.getTime());
1326
+ return regex.test(date);
1327
+ };
1328
+ $dateParser.parse = function (value, baseDate) {
1329
+ if (angular.isDate(value))
1330
+ return value;
1331
+ var matches = regex.exec(value);
1332
+ if (!matches)
1333
+ return false;
1334
+ var date = baseDate || new Date(0);
1335
+ for (var i = 0; i < matches.length - 1; i++) {
1336
+ setMap[i] && setMap[i].call(date, matches[i + 1]);
1337
+ }
1338
+ return date;
1339
+ };
1340
+ // Private functions
1341
+ function setMapForFormat(format) {
1342
+ var keys = Object.keys(setFnMap), i;
1343
+ var map = [], sortedMap = [];
1344
+ // Map to setFn
1345
+ var clonedFormat = format;
1346
+ for (i = 0; i < keys.length; i++) {
1347
+ if (format.split(keys[i]).length > 1) {
1348
+ var index = clonedFormat.search(keys[i]);
1349
+ format = format.split(keys[i]).join('');
1350
+ if (setFnMap[keys[i]])
1351
+ map[index] = setFnMap[keys[i]];
1352
+ }
1353
+ }
1354
+ // Sort result map
1355
+ angular.forEach(map, function (v) {
1356
+ sortedMap.push(v);
1357
+ });
1358
+ return sortedMap;
1359
+ }
1360
+ function escapeReservedSymbols(text) {
1361
+ return text.replace(/\//g, '[\\/]').replace('/-/g', '[-]').replace(/\./g, '[.]').replace(/\\s/g, '[\\s]');
1362
+ }
1363
+ function regExpForFormat(format) {
1364
+ var keys = Object.keys(regExpMap), i;
1365
+ var re = format;
1366
+ // Abstract replaces to avoid collisions
1367
+ for (i = 0; i < keys.length; i++) {
1368
+ re = re.split(keys[i]).join('${' + i + '}');
1369
+ }
1370
+ // Replace abstracted values
1371
+ for (i = 0; i < keys.length; i++) {
1372
+ re = re.split('${' + i + '}').join('(' + regExpMap[keys[i]] + ')');
1373
+ }
1374
+ format = escapeReservedSymbols(format);
1375
+ return new RegExp('^' + re + '$', ['i']);
1376
+ }
1377
+ $dateParser.init();
1378
+ return $dateParser;
1379
+ };
1380
+ return DateParserFactory;
1381
+ }
1382
+ ];
1383
+ }
1384
+ ]);
1385
+
1386
+ // Source: debounce.js
1387
+ angular.module('mgcrea.ngStrap.helpers.debounce', []).constant('debounce', function (func, wait, immediate) {
1388
+ var timeout, args, context, timestamp, result;
1389
+ return function () {
1390
+ context = this;
1391
+ args = arguments;
1392
+ timestamp = new Date();
1393
+ var later = function () {
1394
+ var last = new Date() - timestamp;
1395
+ if (last < wait) {
1396
+ timeout = setTimeout(later, wait - last);
1397
+ } else {
1398
+ timeout = null;
1399
+ if (!immediate)
1400
+ result = func.apply(context, args);
1401
+ }
1402
+ };
1403
+ var callNow = immediate && !timeout;
1404
+ if (!timeout) {
1405
+ timeout = setTimeout(later, wait);
1406
+ }
1407
+ if (callNow)
1408
+ result = func.apply(context, args);
1409
+ return result;
1410
+ };
1411
+ }).constant('throttle', function (func, wait, options) {
1412
+ var context, args, result;
1413
+ var timeout = null;
1414
+ var previous = 0;
1415
+ options || (options = {});
1416
+ var later = function () {
1417
+ previous = options.leading === false ? 0 : new Date();
1418
+ timeout = null;
1419
+ result = func.apply(context, args);
1420
+ };
1421
+ return function () {
1422
+ var now = new Date();
1423
+ if (!previous && options.leading === false)
1424
+ previous = now;
1425
+ var remaining = wait - (now - previous);
1426
+ context = this;
1427
+ args = arguments;
1428
+ if (remaining <= 0) {
1429
+ clearTimeout(timeout);
1430
+ timeout = null;
1431
+ previous = now;
1432
+ result = func.apply(context, args);
1433
+ } else if (!timeout && options.trailing !== false) {
1434
+ timeout = setTimeout(later, remaining);
1435
+ }
1436
+ return result;
1437
+ };
1438
+ });
1439
+
1440
+ // Source: dimensions.js
1441
+ angular.module('mgcrea.ngStrap.helpers.dimensions', []).factory('dimensions', [
1442
+ '$document',
1443
+ '$window',
1444
+ function ($document, $window) {
1445
+ var jqLite = angular.element;
1446
+ var fn = {};
1447
+ /**
1448
+ * Test the element nodeName
1449
+ * @param element
1450
+ * @param name
1451
+ */
1452
+ var nodeName = fn.nodeName = function (element, name) {
1453
+ return element.nodeName && element.nodeName.toLowerCase() === name.toLowerCase();
1454
+ };
1455
+ /**
1456
+ * Returns the element computed style
1457
+ * @param element
1458
+ * @param prop
1459
+ * @param extra
1460
+ */
1461
+ fn.css = function (element, prop, extra) {
1462
+ var value;
1463
+ if (element.currentStyle) {
1464
+ //IE
1465
+ value = element.currentStyle[prop];
1466
+ } else if (window.getComputedStyle) {
1467
+ value = window.getComputedStyle(element)[prop];
1468
+ } else {
1469
+ value = element.style[prop];
1470
+ }
1471
+ return extra === true ? parseFloat(value) || 0 : value;
1472
+ };
1473
+ /**
1474
+ * Provides read-only equivalent of jQuery's offset function:
1475
+ * @required-by bootstrap-tooltip, bootstrap-affix
1476
+ * @url http://api.jquery.com/offset/
1477
+ * @param element
1478
+ */
1479
+ fn.offset = function (element) {
1480
+ var boxRect = element.getBoundingClientRect();
1481
+ var docElement = element.ownerDocument;
1482
+ return {
1483
+ width: element.offsetWidth,
1484
+ height: element.offsetHeight,
1485
+ top: boxRect.top + (window.pageYOffset || docElement.documentElement.scrollTop) - (docElement.documentElement.clientTop || 0),
1486
+ left: boxRect.left + (window.pageXOffset || docElement.documentElement.scrollLeft) - (docElement.documentElement.clientLeft || 0)
1487
+ };
1488
+ };
1489
+ /**
1490
+ * Provides read-only equivalent of jQuery's position function
1491
+ * @required-by bootstrap-tooltip, bootstrap-affix
1492
+ * @url http://api.jquery.com/offset/
1493
+ * @param element
1494
+ */
1495
+ fn.position = function (element) {
1496
+ var offsetParentRect = {
1497
+ top: 0,
1498
+ left: 0
1499
+ }, offsetParentElement, offset;
1500
+ // Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent
1501
+ if (fn.css(element, 'position') === 'fixed') {
1502
+ // We assume that getBoundingClientRect is available when computed position is fixed
1503
+ offset = element.getBoundingClientRect();
1504
+ } else {
1505
+ // Get *real* offsetParentElement
1506
+ offsetParentElement = offsetParent(element);
1507
+ offset = fn.offset(element);
1508
+ // Get correct offsets
1509
+ offset = fn.offset(element);
1510
+ if (!nodeName(offsetParentElement, 'html')) {
1511
+ offsetParentRect = fn.offset(offsetParentElement);
1512
+ }
1513
+ // Add offsetParent borders
1514
+ offsetParentRect.top += fn.css(offsetParentElement, 'borderTopWidth', true);
1515
+ offsetParentRect.left += fn.css(offsetParentElement, 'borderLeftWidth', true);
1516
+ }
1517
+ // Subtract parent offsets and element margins
1518
+ return {
1519
+ width: element.offsetWidth,
1520
+ height: element.offsetHeight,
1521
+ top: offset.top - offsetParentRect.top - fn.css(element, 'marginTop', true),
1522
+ left: offset.left - offsetParentRect.left - fn.css(element, 'marginLeft', true)
1523
+ };
1524
+ };
1525
+ /**
1526
+ * Returns the closest, non-statically positioned offsetParent of a given element
1527
+ * @required-by fn.position
1528
+ * @param element
1529
+ */
1530
+ var offsetParent = function offsetParentElement(element) {
1531
+ var docElement = element.ownerDocument;
1532
+ var offsetParent = element.offsetParent || docElement;
1533
+ if (nodeName(offsetParent, '#document'))
1534
+ return docElement.documentElement;
1535
+ while (offsetParent && !nodeName(offsetParent, 'html') && fn.css(offsetParent, 'position') === 'static') {
1536
+ offsetParent = offsetParent.offsetParent;
1537
+ }
1538
+ return offsetParent || docElement.documentElement;
1539
+ };
1540
+ /**
1541
+ * Provides equivalent of jQuery's height function
1542
+ * @required-by bootstrap-affix
1543
+ * @url http://api.jquery.com/height/
1544
+ * @param element
1545
+ * @param outer
1546
+ */
1547
+ fn.height = function (element, outer) {
1548
+ var value = element.offsetHeight;
1549
+ if (outer) {
1550
+ value += fn.css(element, 'marginTop', true) + fn.css(element, 'marginBottom', true);
1551
+ } else {
1552
+ value -= fn.css(element, 'paddingTop', true) + fn.css(element, 'paddingBottom', true) + fn.css(element, 'borderTopWidth', true) + fn.css(element, 'borderBottomWidth', true);
1553
+ }
1554
+ return value;
1555
+ };
1556
+ /**
1557
+ * Provides equivalent of jQuery's height function
1558
+ * @required-by bootstrap-affix
1559
+ * @url http://api.jquery.com/width/
1560
+ * @param element
1561
+ * @param outer
1562
+ */
1563
+ fn.width = function (element, outer) {
1564
+ var value = element.offsetWidth;
1565
+ if (outer) {
1566
+ value += fn.css(element, 'marginLeft', true) + fn.css(element, 'marginRight', true);
1567
+ } else {
1568
+ value -= fn.css(element, 'paddingLeft', true) + fn.css(element, 'paddingRight', true) + fn.css(element, 'borderLeftWidth', true) + fn.css(element, 'borderRightWidth', true);
1569
+ }
1570
+ return value;
1571
+ };
1572
+ return fn;
1573
+ }
1574
+ ]);
1575
+
1576
+ // Source: parse-options.js
1577
+ angular.module('mgcrea.ngStrap.helpers.parseOptions', []).provider('$parseOptions', function () {
1578
+ var defaults = this.defaults = { regexp: /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/ };
1579
+ this.$get = [
1580
+ '$parse',
1581
+ '$q',
1582
+ function ($parse, $q) {
1583
+ function ParseOptionsFactory(attr, config) {
1584
+ var $parseOptions = {};
1585
+ // Common vars
1586
+ var options = angular.extend({}, defaults, config);
1587
+ $parseOptions.$values = [];
1588
+ // Private vars
1589
+ var match, displayFn, valueName, keyName, groupByFn, valueFn, valuesFn;
1590
+ $parseOptions.init = function () {
1591
+ $parseOptions.$match = match = attr.match(options.regexp);
1592
+ displayFn = $parse(match[2] || match[1]), valueName = match[4] || match[6], keyName = match[5], groupByFn = $parse(match[3] || ''), valueFn = $parse(match[2] ? match[1] : valueName), valuesFn = $parse(match[7]);
1593
+ };
1594
+ $parseOptions.valuesFn = function (scope, controller) {
1595
+ return $q.when(valuesFn(scope, controller)).then(function (values) {
1596
+ $parseOptions.$values = values ? parseValues(values) : {};
1597
+ return $parseOptions.$values;
1598
+ });
1599
+ };
1600
+ // Private functions
1601
+ function parseValues(values) {
1602
+ return values.map(function (match, index) {
1603
+ var locals = {}, label, value;
1604
+ locals[valueName] = match;
1605
+ label = displayFn(locals);
1606
+ value = valueFn(locals) || index;
1607
+ return {
1608
+ label: label,
1609
+ value: value
1610
+ };
1611
+ });
1612
+ }
1613
+ $parseOptions.init();
1614
+ return $parseOptions;
1615
+ }
1616
+ return ParseOptionsFactory;
1617
+ }
1618
+ ];
1619
+ });
1620
+
1621
+ // Source: raf.js
1622
+ angular.version.minor < 3 && angular.version.dot < 14 && angular.module('ng').factory('$$rAF', [
1623
+ '$window',
1624
+ '$timeout',
1625
+ function ($window, $timeout) {
1626
+ var requestAnimationFrame = $window.requestAnimationFrame || $window.webkitRequestAnimationFrame || $window.mozRequestAnimationFrame;
1627
+ var cancelAnimationFrame = $window.cancelAnimationFrame || $window.webkitCancelAnimationFrame || $window.mozCancelAnimationFrame || $window.webkitCancelRequestAnimationFrame;
1628
+ var rafSupported = !!requestAnimationFrame;
1629
+ var raf = rafSupported ? function (fn) {
1630
+ var id = requestAnimationFrame(fn);
1631
+ return function () {
1632
+ cancelAnimationFrame(id);
1633
+ };
1634
+ } : function (fn) {
1635
+ var timer = $timeout(fn, 16.66, false);
1636
+ // 1000 / 60 = 16.666
1637
+ return function () {
1638
+ $timeout.cancel(timer);
1639
+ };
1640
+ };
1641
+ raf.supported = rafSupported;
1642
+ return raf;
1643
+ }
1644
+ ]); // .factory('$$animateReflow', function($$rAF, $document) {
1645
+ // var bodyEl = $document[0].body;
1646
+ // return function(fn) {
1647
+ // //the returned function acts as the cancellation function
1648
+ // return $$rAF(function() {
1649
+ // //the line below will force the browser to perform a repaint
1650
+ // //so that all the animated elements within the animation frame
1651
+ // //will be properly updated and drawn on screen. This is
1652
+ // //required to perform multi-class CSS based animations with
1653
+ // //Firefox. DO NOT REMOVE THIS LINE.
1654
+ // var a = bodyEl.offsetWidth + 1;
1655
+ // fn();
1656
+ // });
1657
+ // };
1658
+ // });
1659
+
1660
+ // Source: modal.js
1661
+ angular.module('mgcrea.ngStrap.modal', ['mgcrea.ngStrap.helpers.dimensions']).provider('$modal', function () {
1662
+ var defaults = this.defaults = {
1663
+ animation: 'am-fade',
1664
+ backdropAnimation: 'am-fade',
1665
+ prefixClass: 'modal',
1666
+ prefixEvent: 'modal',
1667
+ placement: 'top',
1668
+ template: 'modal/modal.tpl.html',
1669
+ contentTemplate: false,
1670
+ container: false,
1671
+ element: null,
1672
+ backdrop: true,
1673
+ keyboard: true,
1674
+ html: false,
1675
+ show: true
1676
+ };
1677
+ this.$get = [
1678
+ '$window',
1679
+ '$rootScope',
1680
+ '$compile',
1681
+ '$q',
1682
+ '$templateCache',
1683
+ '$http',
1684
+ '$animate',
1685
+ '$timeout',
1686
+ '$sce',
1687
+ 'dimensions',
1688
+ function ($window, $rootScope, $compile, $q, $templateCache, $http, $animate, $timeout, $sce, dimensions) {
1689
+ var forEach = angular.forEach;
1690
+ var trim = String.prototype.trim;
1691
+ var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
1692
+ var bodyElement = angular.element($window.document.body);
1693
+ var htmlReplaceRegExp = /ng-bind="/gi;
1694
+ function ModalFactory(config) {
1695
+ var $modal = {};
1696
+ // Common vars
1697
+ var options = $modal.$options = angular.extend({}, defaults, config);
1698
+ $modal.$promise = fetchTemplate(options.template);
1699
+ var scope = $modal.$scope = options.scope && options.scope.$new() || $rootScope.$new();
1700
+ if (!options.element && !options.container) {
1701
+ options.container = 'body';
1702
+ }
1703
+ // Support scope as string options
1704
+ forEach([
1705
+ 'title',
1706
+ 'content'
1707
+ ], function (key) {
1708
+ if (options[key])
1709
+ scope[key] = $sce.trustAsHtml(options[key]);
1710
+ });
1711
+ // Provide scope helpers
1712
+ scope.$hide = function () {
1713
+ scope.$$postDigest(function () {
1714
+ $modal.hide();
1715
+ });
1716
+ };
1717
+ scope.$show = function () {
1718
+ scope.$$postDigest(function () {
1719
+ $modal.show();
1720
+ });
1721
+ };
1722
+ scope.$toggle = function () {
1723
+ scope.$$postDigest(function () {
1724
+ $modal.toggle();
1725
+ });
1726
+ };
1727
+ // Support contentTemplate option
1728
+ if (options.contentTemplate) {
1729
+ $modal.$promise = $modal.$promise.then(function (template) {
1730
+ var templateEl = angular.element(template);
1731
+ return fetchTemplate(options.contentTemplate).then(function (contentTemplate) {
1732
+ var contentEl = findElement('[ng-bind="content"]', templateEl[0]).removeAttr('ng-bind').html(contentTemplate);
1733
+ // Drop the default footer as you probably don't want it if you use a custom contentTemplate
1734
+ if (!config.template)
1735
+ contentEl.next().remove();
1736
+ return templateEl[0].outerHTML;
1737
+ });
1738
+ });
1739
+ }
1740
+ // Fetch, compile then initialize modal
1741
+ var modalLinker, modalElement;
1742
+ var backdropElement = angular.element('<div class="' + options.prefixClass + '-backdrop"/>');
1743
+ $modal.$promise.then(function (template) {
1744
+ if (angular.isObject(template))
1745
+ template = template.data;
1746
+ if (options.html)
1747
+ template = template.replace(htmlReplaceRegExp, 'ng-bind-html="');
1748
+ template = trim.apply(template);
1749
+ modalLinker = $compile(template);
1750
+ $modal.init();
1751
+ });
1752
+ $modal.init = function () {
1753
+ // Options: show
1754
+ if (options.show) {
1755
+ scope.$$postDigest(function () {
1756
+ $modal.show();
1757
+ });
1758
+ }
1759
+ };
1760
+ $modal.destroy = function () {
1761
+ // Remove element
1762
+ if (modalElement) {
1763
+ modalElement.remove();
1764
+ modalElement = null;
1765
+ }
1766
+ if (backdropElement) {
1767
+ backdropElement.remove();
1768
+ backdropElement = null;
1769
+ }
1770
+ // Destroy scope
1771
+ scope.$destroy();
1772
+ };
1773
+ $modal.show = function () {
1774
+ scope.$emit(options.prefixEvent + '.show.before', $modal);
1775
+ var parent = options.container ? findElement(options.container) : null;
1776
+ var after = options.container ? null : options.element;
1777
+ // Fetch a cloned element linked from template
1778
+ modalElement = $modal.$element = modalLinker(scope, function (clonedElement, scope) {
1779
+ });
1780
+ // Set the initial positioning.
1781
+ modalElement.css({ display: 'block' }).addClass(options.placement);
1782
+ // Options: animation
1783
+ if (options.animation) {
1784
+ if (options.backdrop) {
1785
+ backdropElement.addClass(options.backdropAnimation);
1786
+ }
1787
+ modalElement.addClass(options.animation);
1788
+ }
1789
+ if (options.backdrop) {
1790
+ $animate.enter(backdropElement, bodyElement, null, function () {
1791
+ });
1792
+ }
1793
+ $animate.enter(modalElement, parent, after, function () {
1794
+ scope.$emit(options.prefixEvent + '.show', $modal);
1795
+ });
1796
+ scope.$isShown = true;
1797
+ scope.$$phase || scope.$root.$$phase || scope.$digest();
1798
+ // Focus once the enter-animation has started
1799
+ // Weird PhantomJS bug hack
1800
+ var el = modalElement[0];
1801
+ requestAnimationFrame(function () {
1802
+ el.focus();
1803
+ });
1804
+ bodyElement.addClass(options.prefixClass + '-open');
1805
+ if (options.animation) {
1806
+ bodyElement.addClass(options.prefixClass + '-with-' + options.animation);
1807
+ }
1808
+ // Bind events
1809
+ if (options.backdrop) {
1810
+ modalElement.on('click', hideOnBackdropClick);
1811
+ backdropElement.on('click', hideOnBackdropClick);
1812
+ }
1813
+ if (options.keyboard) {
1814
+ modalElement.on('keyup', $modal.$onKeyUp);
1815
+ }
1816
+ };
1817
+ $modal.hide = function () {
1818
+ scope.$emit(options.prefixEvent + '.hide.before', $modal);
1819
+ $animate.leave(modalElement, function () {
1820
+ scope.$emit(options.prefixEvent + '.hide', $modal);
1821
+ bodyElement.removeClass(options.prefixClass + '-open');
1822
+ if (options.animation) {
1823
+ bodyElement.addClass(options.prefixClass + '-with-' + options.animation);
1824
+ }
1825
+ });
1826
+ if (options.backdrop) {
1827
+ $animate.leave(backdropElement, function () {
1828
+ });
1829
+ }
1830
+ scope.$isShown = false;
1831
+ scope.$$phase || scope.$root.$$phase || scope.$digest();
1832
+ // Unbind events
1833
+ if (options.backdrop) {
1834
+ modalElement.off('click', hideOnBackdropClick);
1835
+ backdropElement.off('click', hideOnBackdropClick);
1836
+ }
1837
+ if (options.keyboard) {
1838
+ modalElement.off('keyup', $modal.$onKeyUp);
1839
+ }
1840
+ };
1841
+ $modal.toggle = function () {
1842
+ scope.$isShown ? $modal.hide() : $modal.show();
1843
+ };
1844
+ $modal.focus = function () {
1845
+ modalElement[0].focus();
1846
+ };
1847
+ // Protected methods
1848
+ $modal.$onKeyUp = function (evt) {
1849
+ evt.which === 27 && $modal.hide();
1850
+ };
1851
+ // Private methods
1852
+ function hideOnBackdropClick(evt) {
1853
+ if (evt.target !== evt.currentTarget)
1854
+ return;
1855
+ options.backdrop === 'static' ? $modal.focus() : $modal.hide();
1856
+ }
1857
+ return $modal;
1858
+ }
1859
+ // Helper functions
1860
+ function findElement(query, element) {
1861
+ return angular.element((element || document).querySelectorAll(query));
1862
+ }
1863
+ function fetchTemplate(template) {
1864
+ return $q.when($templateCache.get(template) || $http.get(template)).then(function (res) {
1865
+ if (angular.isObject(res)) {
1866
+ $templateCache.put(template, res.data);
1867
+ return res.data;
1868
+ }
1869
+ return res;
1870
+ });
1871
+ }
1872
+ return ModalFactory;
1873
+ }
1874
+ ];
1875
+ }).directive('bsModal', [
1876
+ '$window',
1877
+ '$location',
1878
+ '$sce',
1879
+ '$modal',
1880
+ function ($window, $location, $sce, $modal) {
1881
+ return {
1882
+ restrict: 'EAC',
1883
+ scope: true,
1884
+ link: function postLink(scope, element, attr, transclusion) {
1885
+ // Directive options
1886
+ var options = {
1887
+ scope: scope,
1888
+ element: element,
1889
+ show: false
1890
+ };
1891
+ angular.forEach([
1892
+ 'template',
1893
+ 'contentTemplate',
1894
+ 'placement',
1895
+ 'backdrop',
1896
+ 'keyboard',
1897
+ 'html',
1898
+ 'container',
1899
+ 'animation'
1900
+ ], function (key) {
1901
+ if (angular.isDefined(attr[key]))
1902
+ options[key] = attr[key];
1903
+ });
1904
+ // Support scope as data-attrs
1905
+ angular.forEach([
1906
+ 'title',
1907
+ 'content'
1908
+ ], function (key) {
1909
+ attr[key] && attr.$observe(key, function (newValue, oldValue) {
1910
+ scope[key] = $sce.trustAsHtml(newValue);
1911
+ });
1912
+ });
1913
+ // Support scope as an object
1914
+ attr.bsModal && scope.$watch(attr.bsModal, function (newValue, oldValue) {
1915
+ if (angular.isObject(newValue)) {
1916
+ angular.extend(scope, newValue);
1917
+ } else {
1918
+ scope.content = newValue;
1919
+ }
1920
+ }, true);
1921
+ // Initialize modal
1922
+ var modal = $modal(options);
1923
+ // Trigger
1924
+ element.on(attr.trigger || 'click', modal.toggle);
1925
+ // Garbage collection
1926
+ scope.$on('$destroy', function () {
1927
+ modal.destroy();
1928
+ options = null;
1929
+ modal = null;
1930
+ });
1931
+ }
1932
+ };
1933
+ }
1934
+ ]);
1935
+
1936
+ // Source: navbar.js
1937
+ angular.module('mgcrea.ngStrap.navbar', []).provider('$navbar', function () {
1938
+ var defaults = this.defaults = {
1939
+ activeClass: 'active',
1940
+ routeAttr: 'data-match-route',
1941
+ strict: false
1942
+ };
1943
+ this.$get = function () {
1944
+ return { defaults: defaults };
1945
+ };
1946
+ }).directive('bsNavbar', [
1947
+ '$window',
1948
+ '$location',
1949
+ '$navbar',
1950
+ function ($window, $location, $navbar) {
1951
+ var defaults = $navbar.defaults;
1952
+ return {
1953
+ restrict: 'A',
1954
+ link: function postLink(scope, element, attr, controller) {
1955
+ // Directive options
1956
+ var options = angular.copy(defaults);
1957
+ angular.forEach(Object.keys(defaults), function (key) {
1958
+ if (angular.isDefined(attr[key]))
1959
+ options[key] = attr[key];
1960
+ });
1961
+ // Watch for the $location
1962
+ scope.$watch(function () {
1963
+ return $location.path();
1964
+ }, function (newValue, oldValue) {
1965
+ var liElements = element[0].querySelectorAll('li[' + options.routeAttr + ']');
1966
+ angular.forEach(liElements, function (li) {
1967
+ var liElement = angular.element(li);
1968
+ var pattern = liElement.attr(options.routeAttr).replace('/', '\\/');
1969
+ if (options.strict) {
1970
+ pattern = '^' + pattern + '$';
1971
+ }
1972
+ var regexp = new RegExp(pattern, ['i']);
1973
+ if (regexp.test(newValue)) {
1974
+ liElement.addClass(options.activeClass);
1975
+ } else {
1976
+ liElement.removeClass(options.activeClass);
1977
+ }
1978
+ });
1979
+ });
1980
+ }
1981
+ };
1982
+ }
1983
+ ]);
1984
+
1985
+ // Source: popover.js
1986
+ angular.module('mgcrea.ngStrap.popover', ['mgcrea.ngStrap.tooltip']).provider('$popover', function () {
1987
+ var defaults = this.defaults = {
1988
+ animation: 'am-fade',
1989
+ placement: 'right',
1990
+ template: 'popover/popover.tpl.html',
1991
+ contentTemplate: false,
1992
+ trigger: 'click',
1993
+ keyboard: true,
1994
+ html: false,
1995
+ title: '',
1996
+ content: '',
1997
+ delay: 0,
1998
+ container: false
1999
+ };
2000
+ this.$get = [
2001
+ '$tooltip',
2002
+ function ($tooltip) {
2003
+ function PopoverFactory(element, config) {
2004
+ // Common vars
2005
+ var options = angular.extend({}, defaults, config);
2006
+ var $popover = $tooltip(element, options);
2007
+ // Support scope as string options [/*title, */content]
2008
+ if (options.content) {
2009
+ $popover.$scope.content = options.content;
2010
+ }
2011
+ return $popover;
2012
+ }
2013
+ return PopoverFactory;
2014
+ }
2015
+ ];
2016
+ }).directive('bsPopover', [
2017
+ '$window',
2018
+ '$location',
2019
+ '$sce',
2020
+ '$popover',
2021
+ function ($window, $location, $sce, $popover) {
2022
+ var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
2023
+ return {
2024
+ restrict: 'EAC',
2025
+ scope: true,
2026
+ link: function postLink(scope, element, attr) {
2027
+ // Directive options
2028
+ var options = { scope: scope };
2029
+ angular.forEach([
2030
+ 'template',
2031
+ 'contentTemplate',
2032
+ 'placement',
2033
+ 'container',
2034
+ 'delay',
2035
+ 'trigger',
2036
+ 'keyboard',
2037
+ 'html',
2038
+ 'animation'
2039
+ ], function (key) {
2040
+ if (angular.isDefined(attr[key]))
2041
+ options[key] = attr[key];
2042
+ });
2043
+ // Support scope as data-attrs
2044
+ angular.forEach([
2045
+ 'title',
2046
+ 'content'
2047
+ ], function (key) {
2048
+ attr[key] && attr.$observe(key, function (newValue, oldValue) {
2049
+ scope[key] = $sce.trustAsHtml(newValue);
2050
+ angular.isDefined(oldValue) && requestAnimationFrame(function () {
2051
+ popover && popover.$applyPlacement();
2052
+ });
2053
+ });
2054
+ });
2055
+ // Support scope as an object
2056
+ attr.bsPopover && scope.$watch(attr.bsPopover, function (newValue, oldValue) {
2057
+ if (angular.isObject(newValue)) {
2058
+ angular.extend(scope, newValue);
2059
+ } else {
2060
+ scope.content = newValue;
2061
+ }
2062
+ angular.isDefined(oldValue) && requestAnimationFrame(function () {
2063
+ popover && popover.$applyPlacement();
2064
+ });
2065
+ }, true);
2066
+ // Initialize popover
2067
+ var popover = $popover(element, options);
2068
+ // Garbage collection
2069
+ scope.$on('$destroy', function () {
2070
+ popover.destroy();
2071
+ options = null;
2072
+ popover = null;
2073
+ });
2074
+ }
2075
+ };
2076
+ }
2077
+ ]);
2078
+
2079
+ // Source: scrollspy.js
2080
+ angular.module('mgcrea.ngStrap.scrollspy', [
2081
+ 'mgcrea.ngStrap.helpers.debounce',
2082
+ 'mgcrea.ngStrap.helpers.dimensions'
2083
+ ]).provider('$scrollspy', function () {
2084
+ // Pool of registered spies
2085
+ var spies = this.$$spies = {};
2086
+ var defaults = this.defaults = {
2087
+ debounce: 150,
2088
+ throttle: 100,
2089
+ offset: 100
2090
+ };
2091
+ this.$get = [
2092
+ '$window',
2093
+ '$document',
2094
+ '$rootScope',
2095
+ 'dimensions',
2096
+ 'debounce',
2097
+ 'throttle',
2098
+ function ($window, $document, $rootScope, dimensions, debounce, throttle) {
2099
+ var windowEl = angular.element($window);
2100
+ var docEl = angular.element($document.prop('documentElement'));
2101
+ var bodyEl = angular.element($window.document.body);
2102
+ // Helper functions
2103
+ function nodeName(element, name) {
2104
+ return element[0].nodeName && element[0].nodeName.toLowerCase() === name.toLowerCase();
2105
+ }
2106
+ function ScrollSpyFactory(config) {
2107
+ // Common vars
2108
+ var options = angular.extend({}, defaults, config);
2109
+ if (!options.element)
2110
+ options.element = bodyEl;
2111
+ var isWindowSpy = nodeName(options.element, 'body');
2112
+ var scrollEl = isWindowSpy ? windowEl : options.element;
2113
+ var scrollId = isWindowSpy ? 'window' : options.id;
2114
+ // Use existing spy
2115
+ if (spies[scrollId]) {
2116
+ spies[scrollId].$$count++;
2117
+ return spies[scrollId];
2118
+ }
2119
+ var $scrollspy = {};
2120
+ // Private vars
2121
+ var unbindViewContentLoaded, unbindIncludeContentLoaded;
2122
+ var trackedElements = $scrollspy.$trackedElements = [];
2123
+ var sortedElements = [];
2124
+ var activeTarget;
2125
+ var debouncedCheckPosition;
2126
+ var throttledCheckPosition;
2127
+ var debouncedCheckOffsets;
2128
+ var viewportHeight;
2129
+ var scrollTop;
2130
+ $scrollspy.init = function () {
2131
+ // Setup internal ref counter
2132
+ this.$$count = 1;
2133
+ // Bind events
2134
+ debouncedCheckPosition = debounce(this.checkPosition, options.debounce);
2135
+ throttledCheckPosition = throttle(this.checkPosition, options.throttle);
2136
+ scrollEl.on('click', this.checkPositionWithEventLoop);
2137
+ windowEl.on('resize', debouncedCheckPosition);
2138
+ scrollEl.on('scroll', throttledCheckPosition);
2139
+ debouncedCheckOffsets = debounce(this.checkOffsets, options.debounce);
2140
+ unbindViewContentLoaded = $rootScope.$on('$viewContentLoaded', debouncedCheckOffsets);
2141
+ unbindIncludeContentLoaded = $rootScope.$on('$includeContentLoaded', debouncedCheckOffsets);
2142
+ debouncedCheckOffsets();
2143
+ // Register spy for reuse
2144
+ if (scrollId) {
2145
+ spies[scrollId] = $scrollspy;
2146
+ }
2147
+ };
2148
+ $scrollspy.destroy = function () {
2149
+ // Check internal ref counter
2150
+ this.$$count--;
2151
+ if (this.$$count > 0) {
2152
+ return;
2153
+ }
2154
+ // Unbind events
2155
+ scrollEl.off('click', this.checkPositionWithEventLoop);
2156
+ windowEl.off('resize', debouncedCheckPosition);
2157
+ scrollEl.off('scroll', debouncedCheckPosition);
2158
+ unbindViewContentLoaded();
2159
+ unbindIncludeContentLoaded();
2160
+ if (scrollId) {
2161
+ delete spies[scrollId];
2162
+ }
2163
+ };
2164
+ $scrollspy.checkPosition = function () {
2165
+ // Not ready yet
2166
+ if (!sortedElements.length)
2167
+ return;
2168
+ // Calculate the scroll position
2169
+ scrollTop = (isWindowSpy ? $window.pageYOffset : scrollEl.prop('scrollTop')) || 0;
2170
+ // Calculate the viewport height for use by the components
2171
+ viewportHeight = Math.max($window.innerHeight, docEl.prop('clientHeight'));
2172
+ // Activate first element if scroll is smaller
2173
+ if (scrollTop < sortedElements[0].offsetTop && activeTarget !== sortedElements[0].target) {
2174
+ return $scrollspy.$activateElement(sortedElements[0]);
2175
+ }
2176
+ // Activate proper element
2177
+ for (var i = sortedElements.length; i--;) {
2178
+ if (angular.isUndefined(sortedElements[i].offsetTop) || sortedElements[i].offsetTop === null)
2179
+ continue;
2180
+ if (activeTarget === sortedElements[i].target)
2181
+ continue;
2182
+ if (scrollTop < sortedElements[i].offsetTop)
2183
+ continue;
2184
+ if (sortedElements[i + 1] && scrollTop > sortedElements[i + 1].offsetTop)
2185
+ continue;
2186
+ return $scrollspy.$activateElement(sortedElements[i]);
2187
+ }
2188
+ };
2189
+ $scrollspy.checkPositionWithEventLoop = function () {
2190
+ setTimeout(this.checkPosition, 1);
2191
+ };
2192
+ // Protected methods
2193
+ $scrollspy.$activateElement = function (element) {
2194
+ if (activeTarget) {
2195
+ var activeElement = $scrollspy.$getTrackedElement(activeTarget);
2196
+ if (activeElement) {
2197
+ activeElement.source.removeClass('active');
2198
+ if (nodeName(activeElement.source, 'li') && nodeName(activeElement.source.parent().parent(), 'li')) {
2199
+ activeElement.source.parent().parent().removeClass('active');
2200
+ }
2201
+ }
2202
+ }
2203
+ activeTarget = element.target;
2204
+ element.source.addClass('active');
2205
+ if (nodeName(element.source, 'li') && nodeName(element.source.parent().parent(), 'li')) {
2206
+ element.source.parent().parent().addClass('active');
2207
+ }
2208
+ };
2209
+ $scrollspy.$getTrackedElement = function (target) {
2210
+ return trackedElements.filter(function (obj) {
2211
+ return obj.target === target;
2212
+ })[0];
2213
+ };
2214
+ // Track offsets behavior
2215
+ $scrollspy.checkOffsets = function () {
2216
+ angular.forEach(trackedElements, function (trackedElement) {
2217
+ var targetElement = document.querySelector(trackedElement.target);
2218
+ trackedElement.offsetTop = targetElement ? dimensions.offset(targetElement).top : null;
2219
+ if (options.offset && trackedElement.offsetTop !== null)
2220
+ trackedElement.offsetTop -= options.offset * 1;
2221
+ });
2222
+ sortedElements = trackedElements.filter(function (el) {
2223
+ return el.offsetTop !== null;
2224
+ }).sort(function (a, b) {
2225
+ return a.offsetTop - b.offsetTop;
2226
+ });
2227
+ debouncedCheckPosition();
2228
+ };
2229
+ $scrollspy.trackElement = function (target, source) {
2230
+ trackedElements.push({
2231
+ target: target,
2232
+ source: source
2233
+ });
2234
+ };
2235
+ $scrollspy.untrackElement = function (target, source) {
2236
+ var toDelete;
2237
+ for (var i = trackedElements.length; i--;) {
2238
+ if (trackedElements[i].target === target && trackedElements[i].source === source) {
2239
+ toDelete = i;
2240
+ break;
2241
+ }
2242
+ }
2243
+ trackedElements = trackedElements.splice(toDelete, 1);
2244
+ };
2245
+ $scrollspy.activate = function (i) {
2246
+ trackedElements[i].addClass('active');
2247
+ };
2248
+ // Initialize plugin
2249
+ $scrollspy.init();
2250
+ return $scrollspy;
2251
+ }
2252
+ return ScrollSpyFactory;
2253
+ }
2254
+ ];
2255
+ }).directive('bsScrollspy', [
2256
+ '$rootScope',
2257
+ 'debounce',
2258
+ 'dimensions',
2259
+ '$scrollspy',
2260
+ function ($rootScope, debounce, dimensions, $scrollspy) {
2261
+ return {
2262
+ restrict: 'EAC',
2263
+ link: function postLink(scope, element, attr) {
2264
+ var options = { scope: scope };
2265
+ angular.forEach([
2266
+ 'offset',
2267
+ 'target'
2268
+ ], function (key) {
2269
+ if (angular.isDefined(attr[key]))
2270
+ options[key] = attr[key];
2271
+ });
2272
+ var scrollspy = $scrollspy(options);
2273
+ scrollspy.trackElement(options.target, element);
2274
+ scope.$on('$destroy', function () {
2275
+ scrollspy.untrackElement(options.target, element);
2276
+ scrollspy.destroy();
2277
+ options = null;
2278
+ scrollspy = null;
2279
+ });
2280
+ }
2281
+ };
2282
+ }
2283
+ ]).directive('bsScrollspyList', [
2284
+ '$rootScope',
2285
+ 'debounce',
2286
+ 'dimensions',
2287
+ '$scrollspy',
2288
+ function ($rootScope, debounce, dimensions, $scrollspy) {
2289
+ return {
2290
+ restrict: 'A',
2291
+ compile: function postLink(element, attr) {
2292
+ var children = element[0].querySelectorAll('li > a[href]');
2293
+ angular.forEach(children, function (child) {
2294
+ var childEl = angular.element(child);
2295
+ childEl.parent().attr('bs-scrollspy', '').attr('data-target', childEl.attr('href'));
2296
+ });
2297
+ }
2298
+ };
2299
+ }
2300
+ ]);
2301
+
2302
+ // Source: select.js
2303
+ angular.module('mgcrea.ngStrap.select', [
2304
+ 'mgcrea.ngStrap.tooltip',
2305
+ 'mgcrea.ngStrap.helpers.parseOptions'
2306
+ ]).provider('$select', function () {
2307
+ var defaults = this.defaults = {
2308
+ animation: 'am-fade',
2309
+ prefixClass: 'select',
2310
+ placement: 'bottom-left',
2311
+ template: 'select/select.tpl.html',
2312
+ trigger: 'focus',
2313
+ container: false,
2314
+ keyboard: true,
2315
+ html: false,
2316
+ delay: 0,
2317
+ multiple: false,
2318
+ sort: true,
2319
+ caretHtml: '&nbsp;<span class="caret"></span>',
2320
+ placeholder: 'Choose among the following...',
2321
+ maxLength: 3,
2322
+ maxLengthHtml: 'selected'
2323
+ };
2324
+ this.$get = [
2325
+ '$window',
2326
+ '$document',
2327
+ '$rootScope',
2328
+ '$tooltip',
2329
+ function ($window, $document, $rootScope, $tooltip) {
2330
+ var bodyEl = angular.element($window.document.body);
2331
+ var isTouch = 'createTouch' in $window.document;
2332
+ function SelectFactory(element, controller, config) {
2333
+ var $select = {};
2334
+ // Common vars
2335
+ var options = angular.extend({}, defaults, config);
2336
+ $select = $tooltip(element, options);
2337
+ var parentScope = config.scope;
2338
+ var scope = $select.$scope;
2339
+ scope.$matches = [];
2340
+ scope.$activeIndex = 0;
2341
+ scope.$isMultiple = options.multiple;
2342
+ scope.$activate = function (index) {
2343
+ scope.$$postDigest(function () {
2344
+ $select.activate(index);
2345
+ });
2346
+ };
2347
+ scope.$select = function (index, evt) {
2348
+ scope.$$postDigest(function () {
2349
+ $select.select(index);
2350
+ });
2351
+ };
2352
+ scope.$isVisible = function () {
2353
+ return $select.$isVisible();
2354
+ };
2355
+ scope.$isActive = function (index) {
2356
+ return $select.$isActive(index);
2357
+ };
2358
+ // Public methods
2359
+ $select.update = function (matches) {
2360
+ scope.$matches = matches;
2361
+ $select.$updateActiveIndex();
2362
+ };
2363
+ $select.activate = function (index) {
2364
+ if (options.multiple) {
2365
+ scope.$activeIndex.sort();
2366
+ $select.$isActive(index) ? scope.$activeIndex.splice(scope.$activeIndex.indexOf(index), 1) : scope.$activeIndex.push(index);
2367
+ if (options.sort)
2368
+ scope.$activeIndex.sort();
2369
+ } else {
2370
+ scope.$activeIndex = index;
2371
+ }
2372
+ return scope.$activeIndex;
2373
+ };
2374
+ $select.select = function (index) {
2375
+ var value = scope.$matches[index].value;
2376
+ $select.activate(index);
2377
+ if (options.multiple) {
2378
+ controller.$setViewValue(scope.$activeIndex.map(function (index) {
2379
+ return scope.$matches[index].value;
2380
+ }));
2381
+ } else {
2382
+ controller.$setViewValue(value);
2383
+ }
2384
+ controller.$render();
2385
+ if (parentScope)
2386
+ parentScope.$digest();
2387
+ // Hide if single select
2388
+ if (!options.multiple) {
2389
+ $select.hide();
2390
+ }
2391
+ // Emit event
2392
+ scope.$emit('$select.select', value, index);
2393
+ };
2394
+ // Protected methods
2395
+ $select.$updateActiveIndex = function () {
2396
+ if (controller.$modelValue && scope.$matches.length) {
2397
+ if (options.multiple && angular.isArray(controller.$modelValue)) {
2398
+ scope.$activeIndex = controller.$modelValue.map(function (value) {
2399
+ return $select.$getIndex(value);
2400
+ });
2401
+ } else {
2402
+ scope.$activeIndex = $select.$getIndex(controller.$modelValue);
2403
+ }
2404
+ } else if (scope.$activeIndex >= scope.$matches.length) {
2405
+ scope.$activeIndex = options.multiple ? [] : 0;
2406
+ }
2407
+ };
2408
+ $select.$isVisible = function () {
2409
+ if (!options.minLength || !controller) {
2410
+ return scope.$matches.length;
2411
+ }
2412
+ // minLength support
2413
+ return scope.$matches.length && controller.$viewValue.length >= options.minLength;
2414
+ };
2415
+ $select.$isActive = function (index) {
2416
+ if (options.multiple) {
2417
+ return scope.$activeIndex.indexOf(index) !== -1;
2418
+ } else {
2419
+ return scope.$activeIndex === index;
2420
+ }
2421
+ };
2422
+ $select.$getIndex = function (value) {
2423
+ var l = scope.$matches.length, i = l;
2424
+ if (!l)
2425
+ return;
2426
+ for (i = l; i--;) {
2427
+ if (scope.$matches[i].value === value)
2428
+ break;
2429
+ }
2430
+ if (i < 0)
2431
+ return;
2432
+ return i;
2433
+ };
2434
+ $select.$onMouseDown = function (evt) {
2435
+ // Prevent blur on mousedown on .dropdown-menu
2436
+ evt.preventDefault();
2437
+ evt.stopPropagation();
2438
+ // Emulate click for mobile devices
2439
+ if (isTouch) {
2440
+ var targetEl = angular.element(evt.target);
2441
+ targetEl.triggerHandler('click');
2442
+ }
2443
+ };
2444
+ $select.$onKeyDown = function (evt) {
2445
+ if (!/(9|13|38|40)/.test(evt.keyCode))
2446
+ return;
2447
+ evt.preventDefault();
2448
+ evt.stopPropagation();
2449
+ // Select with enter
2450
+ if (evt.keyCode === 13 || evt.keyCode === 9) {
2451
+ return $select.select(scope.$activeIndex);
2452
+ }
2453
+ // Navigate with keyboard
2454
+ if (evt.keyCode === 38 && scope.$activeIndex > 0)
2455
+ scope.$activeIndex--;
2456
+ else if (evt.keyCode === 40 && scope.$activeIndex < scope.$matches.length - 1)
2457
+ scope.$activeIndex++;
2458
+ else if (angular.isUndefined(scope.$activeIndex))
2459
+ scope.$activeIndex = 0;
2460
+ scope.$digest();
2461
+ };
2462
+ // Overrides
2463
+ var _show = $select.show;
2464
+ $select.show = function () {
2465
+ _show();
2466
+ if (options.multiple) {
2467
+ $select.$element.addClass('select-multiple');
2468
+ }
2469
+ setTimeout(function () {
2470
+ $select.$element.on(isTouch ? 'touchstart' : 'mousedown', $select.$onMouseDown);
2471
+ if (options.keyboard) {
2472
+ element.on('keydown', $select.$onKeyDown);
2473
+ }
2474
+ });
2475
+ };
2476
+ var _hide = $select.hide;
2477
+ $select.hide = function () {
2478
+ $select.$element.off(isTouch ? 'touchstart' : 'mousedown', $select.$onMouseDown);
2479
+ if (options.keyboard) {
2480
+ element.off('keydown', $select.$onKeyDown);
2481
+ }
2482
+ _hide();
2483
+ };
2484
+ return $select;
2485
+ }
2486
+ SelectFactory.defaults = defaults;
2487
+ return SelectFactory;
2488
+ }
2489
+ ];
2490
+ }).directive('bsSelect', [
2491
+ '$window',
2492
+ '$parse',
2493
+ '$q',
2494
+ '$select',
2495
+ '$parseOptions',
2496
+ function ($window, $parse, $q, $select, $parseOptions) {
2497
+ var defaults = $select.defaults;
2498
+ return {
2499
+ restrict: 'EAC',
2500
+ require: 'ngModel',
2501
+ link: function postLink(scope, element, attr, controller) {
2502
+ // Directive options
2503
+ var options = { scope: scope };
2504
+ angular.forEach([
2505
+ 'placement',
2506
+ 'container',
2507
+ 'delay',
2508
+ 'trigger',
2509
+ 'keyboard',
2510
+ 'html',
2511
+ 'animation',
2512
+ 'template',
2513
+ 'placeholder',
2514
+ 'multiple',
2515
+ 'maxLength',
2516
+ 'maxLengthHtml'
2517
+ ], function (key) {
2518
+ if (angular.isDefined(attr[key]))
2519
+ options[key] = attr[key];
2520
+ });
2521
+ // Add support for select markup
2522
+ if (element[0].nodeName.toLowerCase() === 'select') {
2523
+ var inputEl = element;
2524
+ inputEl.css('display', 'none');
2525
+ element = angular.element('<button type="button" class="btn btn-default"></button>');
2526
+ inputEl.after(element);
2527
+ }
2528
+ // Build proper ngOptions
2529
+ var parsedOptions = $parseOptions(attr.ngOptions);
2530
+ // Initialize select
2531
+ var select = $select(element, controller, options);
2532
+ // Watch ngOptions values before filtering for changes
2533
+ var watchedOptions = parsedOptions.$match[7].replace(/\|.+/, '').trim();
2534
+ scope.$watch(watchedOptions, function (newValue, oldValue) {
2535
+ // console.warn('scope.$watch(%s)', watchedOptions, newValue, oldValue);
2536
+ parsedOptions.valuesFn(scope, controller).then(function (values) {
2537
+ select.update(values);
2538
+ controller.$render();
2539
+ });
2540
+ }, true);
2541
+ // Watch model for changes
2542
+ scope.$watch(attr.ngModel, function (newValue, oldValue) {
2543
+ // console.warn('scope.$watch(%s)', attr.ngModel, newValue, oldValue);
2544
+ select.$updateActiveIndex();
2545
+ }, true);
2546
+ // Model rendering in view
2547
+ controller.$render = function () {
2548
+ // console.warn('$render', element.attr('ng-model'), 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue, 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue);
2549
+ var selected, index;
2550
+ if (options.multiple && angular.isArray(controller.$modelValue)) {
2551
+ selected = controller.$modelValue.map(function (value) {
2552
+ index = select.$getIndex(value);
2553
+ return angular.isDefined(index) ? select.$scope.$matches[index].label : false;
2554
+ }).filter(angular.isDefined);
2555
+ if (selected.length > (options.maxLength || defaults.maxLength)) {
2556
+ selected = selected.length + ' ' + (options.maxLengthHtml || defaults.maxLengthHtml);
2557
+ } else {
2558
+ selected = selected.join(', ');
2559
+ }
2560
+ } else {
2561
+ index = select.$getIndex(controller.$modelValue);
2562
+ selected = angular.isDefined(index) ? select.$scope.$matches[index].label : false;
2563
+ }
2564
+ element.html((selected ? selected : attr.placeholder || defaults.placeholder) + defaults.caretHtml);
2565
+ };
2566
+ // Garbage collection
2567
+ scope.$on('$destroy', function () {
2568
+ select.destroy();
2569
+ options = null;
2570
+ select = null;
2571
+ });
2572
+ }
2573
+ };
2574
+ }
2575
+ ]);
2576
+
2577
+ // Source: tab.js
2578
+ angular.module('mgcrea.ngStrap.tab', []).run([
2579
+ '$templateCache',
2580
+ function ($templateCache) {
2581
+ $templateCache.put('$pane', '{{pane.content}}');
2582
+ }
2583
+ ]).provider('$tab', function () {
2584
+ var defaults = this.defaults = {
2585
+ animation: 'am-fade',
2586
+ template: 'tab/tab.tpl.html'
2587
+ };
2588
+ this.$get = function () {
2589
+ return { defaults: defaults };
2590
+ };
2591
+ }).directive('bsTabs', [
2592
+ '$window',
2593
+ '$animate',
2594
+ '$tab',
2595
+ function ($window, $animate, $tab) {
2596
+ var defaults = $tab.defaults;
2597
+ return {
2598
+ restrict: 'EAC',
2599
+ scope: true,
2600
+ require: '?ngModel',
2601
+ templateUrl: function (element, attr) {
2602
+ return attr.template || defaults.template;
2603
+ },
2604
+ link: function postLink(scope, element, attr, controller) {
2605
+ // Directive options
2606
+ var options = defaults;
2607
+ angular.forEach(['animation'], function (key) {
2608
+ if (angular.isDefined(attr[key]))
2609
+ options[key] = attr[key];
2610
+ });
2611
+ // Require scope as an object
2612
+ attr.bsTabs && scope.$watch(attr.bsTabs, function (newValue, oldValue) {
2613
+ scope.panes = newValue;
2614
+ }, true);
2615
+ // Add base class
2616
+ element.addClass('tabs');
2617
+ // Support animations
2618
+ if (options.animation) {
2619
+ element.addClass(options.animation);
2620
+ }
2621
+ scope.active = scope.activePane = 0;
2622
+ // view -> model
2623
+ scope.setActive = function (index, ev) {
2624
+ scope.active = index;
2625
+ if (controller) {
2626
+ controller.$setViewValue(index);
2627
+ }
2628
+ };
2629
+ // model -> view
2630
+ if (controller) {
2631
+ controller.$render = function () {
2632
+ scope.active = controller.$modelValue * 1;
2633
+ };
2634
+ }
2635
+ }
2636
+ };
2637
+ }
2638
+ ]);
2639
+
2640
+ // Source: timepicker.js
2641
+ angular.module('mgcrea.ngStrap.timepicker', [
2642
+ 'mgcrea.ngStrap.helpers.dateParser',
2643
+ 'mgcrea.ngStrap.tooltip'
2644
+ ]).provider('$timepicker', function () {
2645
+ var defaults = this.defaults = {
2646
+ animation: 'am-fade',
2647
+ prefixClass: 'timepicker',
2648
+ placement: 'bottom-left',
2649
+ template: 'timepicker/timepicker.tpl.html',
2650
+ trigger: 'focus',
2651
+ container: false,
2652
+ keyboard: true,
2653
+ html: false,
2654
+ delay: 0,
2655
+ useNative: true,
2656
+ timeType: 'date',
2657
+ timeFormat: 'shortTime',
2658
+ autoclose: false,
2659
+ minTime: -Infinity,
2660
+ maxTime: +Infinity,
2661
+ length: 5,
2662
+ hourStep: 1,
2663
+ minuteStep: 5
2664
+ };
2665
+ this.$get = [
2666
+ '$window',
2667
+ '$document',
2668
+ '$rootScope',
2669
+ '$sce',
2670
+ '$locale',
2671
+ 'dateFilter',
2672
+ '$tooltip',
2673
+ function ($window, $document, $rootScope, $sce, $locale, dateFilter, $tooltip) {
2674
+ var bodyEl = angular.element($window.document.body);
2675
+ var isTouch = 'createTouch' in $window.document;
2676
+ var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent);
2677
+ if (!defaults.lang)
2678
+ defaults.lang = $locale.id;
2679
+ function timepickerFactory(element, controller, config) {
2680
+ var $timepicker = $tooltip(element, angular.extend({}, defaults, config));
2681
+ var parentScope = config.scope;
2682
+ var options = $timepicker.$options;
2683
+ var scope = $timepicker.$scope;
2684
+ // View vars
2685
+ var selectedIndex = 0;
2686
+ var startDate = controller.$dateValue || new Date();
2687
+ var viewDate = {
2688
+ hour: startDate.getHours(),
2689
+ meridian: startDate.getHours() < 12,
2690
+ minute: startDate.getMinutes(),
2691
+ second: startDate.getSeconds(),
2692
+ millisecond: startDate.getMilliseconds()
2693
+ };
2694
+ var format = $locale.DATETIME_FORMATS[options.timeFormat] || options.timeFormat;
2695
+ var formats = /(h+)[:]?(m+)[ ]?(a?)/i.exec(format).slice(1);
2696
+ // Scope methods
2697
+ scope.$select = function (date, index) {
2698
+ $timepicker.select(date, index);
2699
+ };
2700
+ scope.$moveIndex = function (value, index) {
2701
+ $timepicker.$moveIndex(value, index);
2702
+ };
2703
+ scope.$switchMeridian = function (date) {
2704
+ $timepicker.switchMeridian(date);
2705
+ };
2706
+ // Public methods
2707
+ $timepicker.update = function (date) {
2708
+ // console.warn('$timepicker.update() newValue=%o', date);
2709
+ if (angular.isDate(date) && !isNaN(date.getTime())) {
2710
+ $timepicker.$date = date;
2711
+ angular.extend(viewDate, {
2712
+ hour: date.getHours(),
2713
+ minute: date.getMinutes(),
2714
+ second: date.getSeconds(),
2715
+ millisecond: date.getMilliseconds()
2716
+ });
2717
+ $timepicker.$build();
2718
+ } else if (!$timepicker.$isBuilt) {
2719
+ $timepicker.$build();
2720
+ }
2721
+ };
2722
+ $timepicker.select = function (date, index, keep) {
2723
+ // console.warn('$timepicker.select', date, scope.$mode);
2724
+ if (!controller.$dateValue || isNaN(controller.$dateValue.getTime()))
2725
+ controller.$dateValue = new Date(1970, 0, 1);
2726
+ if (!angular.isDate(date))
2727
+ date = new Date(date);
2728
+ if (index === 0)
2729
+ controller.$dateValue.setHours(date.getHours());
2730
+ else if (index === 1)
2731
+ controller.$dateValue.setMinutes(date.getMinutes());
2732
+ controller.$setViewValue(controller.$dateValue);
2733
+ controller.$render();
2734
+ if (options.autoclose && !keep) {
2735
+ $timepicker.hide(true);
2736
+ }
2737
+ };
2738
+ $timepicker.switchMeridian = function (date) {
2739
+ var hours = (date || controller.$dateValue).getHours();
2740
+ controller.$dateValue.setHours(hours < 12 ? hours + 12 : hours - 12);
2741
+ controller.$render();
2742
+ };
2743
+ // Protected methods
2744
+ $timepicker.$build = function () {
2745
+ // console.warn('$timepicker.$build() viewDate=%o', viewDate);
2746
+ var i, midIndex = scope.midIndex = parseInt(options.length / 2, 10);
2747
+ var hours = [], hour;
2748
+ for (i = 0; i < options.length; i++) {
2749
+ hour = new Date(1970, 0, 1, viewDate.hour - (midIndex - i) * options.hourStep);
2750
+ hours.push({
2751
+ date: hour,
2752
+ label: dateFilter(hour, formats[0]),
2753
+ selected: $timepicker.$date && $timepicker.$isSelected(hour, 0),
2754
+ disabled: $timepicker.$isDisabled(hour, 0)
2755
+ });
2756
+ }
2757
+ var minutes = [], minute;
2758
+ for (i = 0; i < options.length; i++) {
2759
+ minute = new Date(1970, 0, 1, 0, viewDate.minute - (midIndex - i) * options.minuteStep);
2760
+ minutes.push({
2761
+ date: minute,
2762
+ label: dateFilter(minute, formats[1]),
2763
+ selected: $timepicker.$date && $timepicker.$isSelected(minute, 1),
2764
+ disabled: $timepicker.$isDisabled(minute, 1)
2765
+ });
2766
+ }
2767
+ var rows = [];
2768
+ for (i = 0; i < options.length; i++) {
2769
+ rows.push([
2770
+ hours[i],
2771
+ minutes[i]
2772
+ ]);
2773
+ }
2774
+ scope.rows = rows;
2775
+ scope.showAM = !!formats[2];
2776
+ scope.isAM = ($timepicker.$date || hours[midIndex].date).getHours() < 12;
2777
+ $timepicker.$isBuilt = true;
2778
+ };
2779
+ $timepicker.$isSelected = function (date, index) {
2780
+ if (!$timepicker.$date)
2781
+ return false;
2782
+ else if (index === 0) {
2783
+ return date.getHours() === $timepicker.$date.getHours();
2784
+ } else if (index === 1) {
2785
+ return date.getMinutes() === $timepicker.$date.getMinutes();
2786
+ }
2787
+ };
2788
+ $timepicker.$isDisabled = function (date, index) {
2789
+ var selectedTime;
2790
+ if (index === 0) {
2791
+ selectedTime = date.getTime() + viewDate.minute * 60000;
2792
+ } else if (index === 1) {
2793
+ selectedTime = date.getTime() + viewDate.hour * 3600000;
2794
+ }
2795
+ return selectedTime < options.minTime || selectedTime > options.maxTime;
2796
+ };
2797
+ $timepicker.$moveIndex = function (value, index) {
2798
+ var targetDate;
2799
+ if (index === 0) {
2800
+ targetDate = new Date(1970, 0, 1, viewDate.hour + value * options.length, viewDate.minute);
2801
+ angular.extend(viewDate, { hour: targetDate.getHours() });
2802
+ } else if (index === 1) {
2803
+ targetDate = new Date(1970, 0, 1, viewDate.hour, viewDate.minute + value * options.length * options.minuteStep);
2804
+ angular.extend(viewDate, { minute: targetDate.getMinutes() });
2805
+ }
2806
+ $timepicker.$build();
2807
+ };
2808
+ $timepicker.$onMouseDown = function (evt) {
2809
+ // Prevent blur on mousedown on .dropdown-menu
2810
+ if (evt.target.nodeName.toLowerCase() !== 'input')
2811
+ evt.preventDefault();
2812
+ evt.stopPropagation();
2813
+ // Emulate click for mobile devices
2814
+ if (isTouch) {
2815
+ var targetEl = angular.element(evt.target);
2816
+ if (targetEl[0].nodeName.toLowerCase() !== 'button') {
2817
+ targetEl = targetEl.parent();
2818
+ }
2819
+ targetEl.triggerHandler('click');
2820
+ }
2821
+ };
2822
+ $timepicker.$onKeyDown = function (evt) {
2823
+ if (!/(38|37|39|40|13)/.test(evt.keyCode) || evt.shiftKey || evt.altKey)
2824
+ return;
2825
+ evt.preventDefault();
2826
+ evt.stopPropagation();
2827
+ // Close on enter
2828
+ if (evt.keyCode === 13)
2829
+ return $timepicker.hide(true);
2830
+ // Navigate with keyboard
2831
+ var newDate = new Date($timepicker.$date);
2832
+ var hours = newDate.getHours(), hoursLength = dateFilter(newDate, 'h').length;
2833
+ var minutes = newDate.getMinutes(), minutesLength = dateFilter(newDate, 'mm').length;
2834
+ var lateralMove = /(37|39)/.test(evt.keyCode);
2835
+ var count = 2 + !!formats[2] * 1;
2836
+ // Navigate indexes (left, right)
2837
+ if (lateralMove) {
2838
+ if (evt.keyCode === 37)
2839
+ selectedIndex = selectedIndex < 1 ? count - 1 : selectedIndex - 1;
2840
+ else if (evt.keyCode === 39)
2841
+ selectedIndex = selectedIndex < count - 1 ? selectedIndex + 1 : 0;
2842
+ }
2843
+ // Update values (up, down)
2844
+ if (selectedIndex === 0) {
2845
+ if (lateralMove)
2846
+ return createSelection(0, hoursLength);
2847
+ if (evt.keyCode === 38)
2848
+ newDate.setHours(hours - parseInt(options.hourStep, 10));
2849
+ else if (evt.keyCode === 40)
2850
+ newDate.setHours(hours + parseInt(options.hourStep, 10));
2851
+ } else if (selectedIndex === 1) {
2852
+ if (lateralMove)
2853
+ return createSelection(hoursLength + 1, hoursLength + 1 + minutesLength);
2854
+ if (evt.keyCode === 38)
2855
+ newDate.setMinutes(minutes - parseInt(options.minuteStep, 10));
2856
+ else if (evt.keyCode === 40)
2857
+ newDate.setMinutes(minutes + parseInt(options.minuteStep, 10));
2858
+ } else if (selectedIndex === 2) {
2859
+ if (lateralMove)
2860
+ return createSelection(hoursLength + 1 + minutesLength + 1, hoursLength + 1 + minutesLength + 3);
2861
+ $timepicker.switchMeridian();
2862
+ }
2863
+ $timepicker.select(newDate, selectedIndex, true);
2864
+ parentScope.$digest();
2865
+ };
2866
+ // Private
2867
+ function createSelection(start, end) {
2868
+ if (element[0].createTextRange) {
2869
+ var selRange = element[0].createTextRange();
2870
+ selRange.collapse(true);
2871
+ selRange.moveStart('character', start);
2872
+ selRange.moveEnd('character', end);
2873
+ selRange.select();
2874
+ } else if (element[0].setSelectionRange) {
2875
+ element[0].setSelectionRange(start, end);
2876
+ } else if (angular.isUndefined(element[0].selectionStart)) {
2877
+ element[0].selectionStart = start;
2878
+ element[0].selectionEnd = end;
2879
+ }
2880
+ }
2881
+ function focusElement() {
2882
+ element[0].focus();
2883
+ }
2884
+ // Overrides
2885
+ var _init = $timepicker.init;
2886
+ $timepicker.init = function () {
2887
+ if (isNative && options.useNative) {
2888
+ element.prop('type', 'time');
2889
+ element.css('-webkit-appearance', 'textfield');
2890
+ return;
2891
+ } else if (isTouch) {
2892
+ element.prop('type', 'text');
2893
+ element.attr('readonly', 'true');
2894
+ element.on('click', focusElement);
2895
+ }
2896
+ _init();
2897
+ };
2898
+ var _destroy = $timepicker.destroy;
2899
+ $timepicker.destroy = function () {
2900
+ if (isNative && options.useNative) {
2901
+ element.off('click', focusElement);
2902
+ }
2903
+ _destroy();
2904
+ };
2905
+ var _show = $timepicker.show;
2906
+ $timepicker.show = function () {
2907
+ _show();
2908
+ setTimeout(function () {
2909
+ $timepicker.$element.on(isTouch ? 'touchstart' : 'mousedown', $timepicker.$onMouseDown);
2910
+ if (options.keyboard) {
2911
+ element.on('keydown', $timepicker.$onKeyDown);
2912
+ }
2913
+ });
2914
+ };
2915
+ var _hide = $timepicker.hide;
2916
+ $timepicker.hide = function (blur) {
2917
+ $timepicker.$element.off(isTouch ? 'touchstart' : 'mousedown', $timepicker.$onMouseDown);
2918
+ if (options.keyboard) {
2919
+ element.off('keydown', $timepicker.$onKeyDown);
2920
+ }
2921
+ _hide(blur);
2922
+ };
2923
+ return $timepicker;
2924
+ }
2925
+ timepickerFactory.defaults = defaults;
2926
+ return timepickerFactory;
2927
+ }
2928
+ ];
2929
+ }).directive('bsTimepicker', [
2930
+ '$window',
2931
+ '$parse',
2932
+ '$q',
2933
+ '$locale',
2934
+ 'dateFilter',
2935
+ '$timepicker',
2936
+ '$dateParser',
2937
+ '$timeout',
2938
+ function ($window, $parse, $q, $locale, dateFilter, $timepicker, $dateParser, $timeout) {
2939
+ var defaults = $timepicker.defaults;
2940
+ var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent);
2941
+ var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
2942
+ return {
2943
+ restrict: 'EAC',
2944
+ require: 'ngModel',
2945
+ link: function postLink(scope, element, attr, controller) {
2946
+ // Directive options
2947
+ var options = {
2948
+ scope: scope,
2949
+ controller: controller
2950
+ };
2951
+ angular.forEach([
2952
+ 'placement',
2953
+ 'container',
2954
+ 'delay',
2955
+ 'trigger',
2956
+ 'keyboard',
2957
+ 'html',
2958
+ 'animation',
2959
+ 'template',
2960
+ 'autoclose',
2961
+ 'timeType',
2962
+ 'timeFormat',
2963
+ 'useNative',
2964
+ 'hourStep',
2965
+ 'minuteStep'
2966
+ ], function (key) {
2967
+ if (angular.isDefined(attr[key]))
2968
+ options[key] = attr[key];
2969
+ });
2970
+ // Initialize timepicker
2971
+ if (isNative && (options.useNative || defaults.useNative))
2972
+ options.timeFormat = 'HH:mm';
2973
+ var timepicker = $timepicker(element, controller, options);
2974
+ options = timepicker.$options;
2975
+ // Initialize parser
2976
+ var dateParser = $dateParser({
2977
+ format: options.timeFormat,
2978
+ lang: options.lang
2979
+ });
2980
+ // Observe attributes for changes
2981
+ angular.forEach([
2982
+ 'minTime',
2983
+ 'maxTime'
2984
+ ], function (key) {
2985
+ // console.warn('attr.$observe(%s)', key, attr[key]);
2986
+ angular.isDefined(attr[key]) && attr.$observe(key, function (newValue) {
2987
+ if (newValue === 'now') {
2988
+ timepicker.$options[key] = new Date().setFullYear(1970, 0, 1);
2989
+ } else if (angular.isString(newValue) && newValue.match(/^".+"$/)) {
2990
+ timepicker.$options[key] = +new Date(newValue.substr(1, newValue.length - 2));
2991
+ } else {
2992
+ timepicker.$options[key] = dateParser.parse(newValue);
2993
+ }
2994
+ !isNaN(timepicker.$options[key]) && timepicker.$build();
2995
+ });
2996
+ });
2997
+ // Watch model for changes
2998
+ scope.$watch(attr.ngModel, function (newValue, oldValue) {
2999
+ // console.warn('scope.$watch(%s)', attr.ngModel, newValue, oldValue, controller.$dateValue);
3000
+ timepicker.update(controller.$dateValue);
3001
+ }, true);
3002
+ // viewValue -> $parsers -> modelValue
3003
+ controller.$parsers.unshift(function (viewValue) {
3004
+ // console.warn('$parser("%s"): viewValue=%o', element.attr('ng-model'), viewValue);
3005
+ // Null values should correctly reset the model value & validity
3006
+ if (!viewValue) {
3007
+ controller.$setValidity('date', true);
3008
+ return;
3009
+ }
3010
+ var parsedTime = dateParser.parse(viewValue, controller.$dateValue);
3011
+ if (!parsedTime || isNaN(parsedTime.getTime())) {
3012
+ controller.$setValidity('date', false);
3013
+ } else {
3014
+ var isValid = parsedTime.getTime() >= options.minTime && parsedTime.getTime() <= options.maxTime;
3015
+ controller.$setValidity('date', isValid);
3016
+ // Only update the model when we have a valid date
3017
+ if (isValid)
3018
+ controller.$dateValue = parsedTime;
3019
+ }
3020
+ if (options.timeType === 'string') {
3021
+ return dateFilter(viewValue, options.timeFormat);
3022
+ } else if (options.timeType === 'number') {
3023
+ return controller.$dateValue.getTime();
3024
+ } else if (options.timeType === 'iso') {
3025
+ return controller.$dateValue.toISOString();
3026
+ } else {
3027
+ return new Date(controller.$dateValue);
3028
+ }
3029
+ });
3030
+ // modelValue -> $formatters -> viewValue
3031
+ controller.$formatters.push(function (modelValue) {
3032
+ // console.warn('$formatter("%s"): modelValue=%o (%o)', element.attr('ng-model'), modelValue, typeof modelValue);
3033
+ var date;
3034
+ if (angular.isUndefined(modelValue) || modelValue === null) {
3035
+ date = NaN;
3036
+ } else if (angular.isDate(modelValue)) {
3037
+ date = modelValue;
3038
+ } else if (options.timeType === 'string') {
3039
+ date = dateParser.parse(modelValue);
3040
+ } else {
3041
+ date = new Date(modelValue);
3042
+ }
3043
+ // Setup default value?
3044
+ // if(isNaN(date.getTime())) date = new Date(new Date().setMinutes(0) + 36e5);
3045
+ controller.$dateValue = date;
3046
+ return controller.$dateValue;
3047
+ });
3048
+ // viewValue -> element
3049
+ controller.$render = function () {
3050
+ // console.warn('$render("%s"): viewValue=%o', element.attr('ng-model'), controller.$viewValue);
3051
+ element.val(!controller.$dateValue || isNaN(controller.$dateValue.getTime()) ? '' : dateFilter(controller.$dateValue, options.timeFormat));
3052
+ };
3053
+ // Garbage collection
3054
+ scope.$on('$destroy', function () {
3055
+ timepicker.destroy();
3056
+ options = null;
3057
+ timepicker = null;
3058
+ });
3059
+ }
3060
+ };
3061
+ }
3062
+ ]);
3063
+
3064
+ // Source: tooltip.js
3065
+ angular.module('mgcrea.ngStrap.tooltip', ['mgcrea.ngStrap.helpers.dimensions']).provider('$tooltip', function () {
3066
+ var defaults = this.defaults = {
3067
+ animation: 'am-fade',
3068
+ prefixClass: 'tooltip',
3069
+ prefixEvent: 'tooltip',
3070
+ container: false,
3071
+ placement: 'top',
3072
+ template: 'tooltip/tooltip.tpl.html',
3073
+ contentTemplate: false,
3074
+ trigger: 'hover focus',
3075
+ keyboard: false,
3076
+ html: false,
3077
+ show: false,
3078
+ title: '',
3079
+ type: '',
3080
+ delay: 0
3081
+ };
3082
+ this.$get = [
3083
+ '$window',
3084
+ '$rootScope',
3085
+ '$compile',
3086
+ '$q',
3087
+ '$templateCache',
3088
+ '$http',
3089
+ '$animate',
3090
+ '$timeout',
3091
+ 'dimensions',
3092
+ '$$rAF',
3093
+ function ($window, $rootScope, $compile, $q, $templateCache, $http, $animate, $timeout, dimensions, $$rAF) {
3094
+ var trim = String.prototype.trim;
3095
+ var isTouch = 'createTouch' in $window.document;
3096
+ var htmlReplaceRegExp = /ng-bind="/gi;
3097
+ function TooltipFactory(element, config) {
3098
+ var $tooltip = {};
3099
+ // Common vars
3100
+ var options = $tooltip.$options = angular.extend({}, defaults, config);
3101
+ $tooltip.$promise = fetchTemplate(options.template);
3102
+ var scope = $tooltip.$scope = options.scope && options.scope.$new() || $rootScope.$new();
3103
+ if (options.delay && angular.isString(options.delay)) {
3104
+ options.delay = parseFloat(options.delay);
3105
+ }
3106
+ // Support scope as string options
3107
+ if (options.title) {
3108
+ $tooltip.$scope.title = options.title;
3109
+ }
3110
+ // Provide scope helpers
3111
+ scope.$hide = function () {
3112
+ scope.$$postDigest(function () {
3113
+ $tooltip.hide();
3114
+ });
3115
+ };
3116
+ scope.$show = function () {
3117
+ scope.$$postDigest(function () {
3118
+ $tooltip.show();
3119
+ });
3120
+ };
3121
+ scope.$toggle = function () {
3122
+ scope.$$postDigest(function () {
3123
+ $tooltip.toggle();
3124
+ });
3125
+ };
3126
+ $tooltip.$isShown = scope.$isShown = false;
3127
+ // Private vars
3128
+ var timeout, hoverState;
3129
+ // Support contentTemplate option
3130
+ if (options.contentTemplate) {
3131
+ $tooltip.$promise = $tooltip.$promise.then(function (template) {
3132
+ var templateEl = angular.element(template);
3133
+ return fetchTemplate(options.contentTemplate).then(function (contentTemplate) {
3134
+ var contentEl = findElement('[ng-bind="content"]', templateEl[0]);
3135
+ if (!contentEl.length)
3136
+ contentEl = findElement('[ng-bind="title"]', templateEl[0]);
3137
+ contentEl.removeAttr('ng-bind').html(contentTemplate);
3138
+ return templateEl[0].outerHTML;
3139
+ });
3140
+ });
3141
+ }
3142
+ // Fetch, compile then initialize tooltip
3143
+ var tipLinker, tipElement, tipTemplate, tipContainer;
3144
+ $tooltip.$promise.then(function (template) {
3145
+ if (angular.isObject(template))
3146
+ template = template.data;
3147
+ if (options.html)
3148
+ template = template.replace(htmlReplaceRegExp, 'ng-bind-html="');
3149
+ template = trim.apply(template);
3150
+ tipTemplate = template;
3151
+ tipLinker = $compile(template);
3152
+ $tooltip.init();
3153
+ });
3154
+ $tooltip.init = function () {
3155
+ // Options: delay
3156
+ if (options.delay && angular.isNumber(options.delay)) {
3157
+ options.delay = {
3158
+ show: options.delay,
3159
+ hide: options.delay
3160
+ };
3161
+ }
3162
+ // Replace trigger on touch devices ?
3163
+ // if(isTouch && options.trigger === defaults.trigger) {
3164
+ // options.trigger.replace(/hover/g, 'click');
3165
+ // }
3166
+ // Options : container
3167
+ if (options.container === 'self') {
3168
+ tipContainer = element;
3169
+ } else if (options.container) {
3170
+ tipContainer = findElement(options.container);
3171
+ }
3172
+ // Options: trigger
3173
+ var triggers = options.trigger.split(' ');
3174
+ angular.forEach(triggers, function (trigger) {
3175
+ if (trigger === 'click') {
3176
+ element.on('click', $tooltip.toggle);
3177
+ } else if (trigger !== 'manual') {
3178
+ element.on(trigger === 'hover' ? 'mouseenter' : 'focus', $tooltip.enter);
3179
+ element.on(trigger === 'hover' ? 'mouseleave' : 'blur', $tooltip.leave);
3180
+ trigger !== 'hover' && element.on(isTouch ? 'touchstart' : 'mousedown', $tooltip.$onFocusElementMouseDown);
3181
+ }
3182
+ });
3183
+ // Options: show
3184
+ if (options.show) {
3185
+ scope.$$postDigest(function () {
3186
+ options.trigger === 'focus' ? element[0].focus() : $tooltip.show();
3187
+ });
3188
+ }
3189
+ };
3190
+ $tooltip.destroy = function () {
3191
+ // Unbind events
3192
+ var triggers = options.trigger.split(' ');
3193
+ for (var i = triggers.length; i--;) {
3194
+ var trigger = triggers[i];
3195
+ if (trigger === 'click') {
3196
+ element.off('click', $tooltip.toggle);
3197
+ } else if (trigger !== 'manual') {
3198
+ element.off(trigger === 'hover' ? 'mouseenter' : 'focus', $tooltip.enter);
3199
+ element.off(trigger === 'hover' ? 'mouseleave' : 'blur', $tooltip.leave);
3200
+ trigger !== 'hover' && element.off(isTouch ? 'touchstart' : 'mousedown', $tooltip.$onFocusElementMouseDown);
3201
+ }
3202
+ }
3203
+ // Remove element
3204
+ if (tipElement) {
3205
+ tipElement.remove();
3206
+ tipElement = null;
3207
+ }
3208
+ // Destroy scope
3209
+ scope.$destroy();
3210
+ };
3211
+ $tooltip.enter = function () {
3212
+ clearTimeout(timeout);
3213
+ hoverState = 'in';
3214
+ if (!options.delay || !options.delay.show) {
3215
+ return $tooltip.show();
3216
+ }
3217
+ timeout = setTimeout(function () {
3218
+ if (hoverState === 'in')
3219
+ $tooltip.show();
3220
+ }, options.delay.show);
3221
+ };
3222
+ $tooltip.show = function () {
3223
+ scope.$emit(options.prefixEvent + '.show.before', $tooltip);
3224
+ var parent = options.container ? tipContainer : null;
3225
+ var after = options.container ? null : element;
3226
+ // Hide any existing tipElement
3227
+ if (tipElement)
3228
+ tipElement.remove();
3229
+ // Fetch a cloned element linked from template
3230
+ tipElement = $tooltip.$element = tipLinker(scope, function (clonedElement, scope) {
3231
+ });
3232
+ // Set the initial positioning.
3233
+ tipElement.css({
3234
+ top: '0px',
3235
+ left: '0px',
3236
+ display: 'block'
3237
+ }).addClass(options.placement);
3238
+ // Options: animation
3239
+ if (options.animation)
3240
+ tipElement.addClass(options.animation);
3241
+ // Options: type
3242
+ if (options.type)
3243
+ tipElement.addClass(options.prefixClass + '-' + options.type);
3244
+ $animate.enter(tipElement, parent, after, function () {
3245
+ scope.$emit(options.prefixEvent + '.show', $tooltip);
3246
+ });
3247
+ $tooltip.$isShown = scope.$isShown = true;
3248
+ scope.$$phase || scope.$root.$$phase || scope.$digest();
3249
+ $$rAF($tooltip.$applyPlacement);
3250
+ // var a = bodyEl.offsetWidth + 1; ?
3251
+ // Bind events
3252
+ if (options.keyboard) {
3253
+ if (options.trigger !== 'focus') {
3254
+ $tooltip.focus();
3255
+ tipElement.on('keyup', $tooltip.$onKeyUp);
3256
+ } else {
3257
+ element.on('keyup', $tooltip.$onFocusKeyUp);
3258
+ }
3259
+ }
3260
+ };
3261
+ $tooltip.leave = function () {
3262
+ clearTimeout(timeout);
3263
+ hoverState = 'out';
3264
+ if (!options.delay || !options.delay.hide) {
3265
+ return $tooltip.hide();
3266
+ }
3267
+ timeout = setTimeout(function () {
3268
+ if (hoverState === 'out') {
3269
+ $tooltip.hide();
3270
+ }
3271
+ }, options.delay.hide);
3272
+ };
3273
+ $tooltip.hide = function (blur) {
3274
+ if (!$tooltip.$isShown)
3275
+ return;
3276
+ scope.$emit(options.prefixEvent + '.hide.before', $tooltip);
3277
+ $animate.leave(tipElement, function () {
3278
+ scope.$emit(options.prefixEvent + '.hide', $tooltip);
3279
+ });
3280
+ $tooltip.$isShown = scope.$isShown = false;
3281
+ scope.$$phase || scope.$root.$$phase || scope.$digest();
3282
+ // Unbind events
3283
+ if (options.keyboard && tipElement !== null) {
3284
+ tipElement.off('keyup', $tooltip.$onKeyUp);
3285
+ }
3286
+ // Allow to blur the input when hidden, like when pressing enter key
3287
+ if (blur && options.trigger === 'focus') {
3288
+ return element[0].blur();
3289
+ }
3290
+ };
3291
+ $tooltip.toggle = function () {
3292
+ $tooltip.$isShown ? $tooltip.leave() : $tooltip.enter();
3293
+ };
3294
+ $tooltip.focus = function () {
3295
+ tipElement[0].focus();
3296
+ };
3297
+ // Protected methods
3298
+ $tooltip.$applyPlacement = function () {
3299
+ if (!tipElement)
3300
+ return;
3301
+ // Get the position of the tooltip element.
3302
+ var elementPosition = getPosition();
3303
+ // Get the height and width of the tooltip so we can center it.
3304
+ var tipWidth = tipElement.prop('offsetWidth'), tipHeight = tipElement.prop('offsetHeight');
3305
+ // Get the tooltip's top and left coordinates to center it with this directive.
3306
+ var tipPosition = getCalculatedOffset(options.placement, elementPosition, tipWidth, tipHeight);
3307
+ // Now set the calculated positioning.
3308
+ tipPosition.top += 'px';
3309
+ tipPosition.left += 'px';
3310
+ tipElement.css(tipPosition);
3311
+ };
3312
+ $tooltip.$onKeyUp = function (evt) {
3313
+ evt.which === 27 && $tooltip.hide();
3314
+ };
3315
+ $tooltip.$onFocusKeyUp = function (evt) {
3316
+ evt.which === 27 && element[0].blur();
3317
+ };
3318
+ $tooltip.$onFocusElementMouseDown = function (evt) {
3319
+ evt.preventDefault();
3320
+ evt.stopPropagation();
3321
+ // Some browsers do not auto-focus buttons (eg. Safari)
3322
+ $tooltip.$isShown ? element[0].blur() : element[0].focus();
3323
+ };
3324
+ // Private methods
3325
+ function getPosition() {
3326
+ if (options.container === 'body') {
3327
+ return dimensions.offset(element[0]);
3328
+ } else {
3329
+ return dimensions.position(element[0]);
3330
+ }
3331
+ }
3332
+ function getCalculatedOffset(placement, position, actualWidth, actualHeight) {
3333
+ var offset;
3334
+ var split = placement.split('-');
3335
+ switch (split[0]) {
3336
+ case 'right':
3337
+ offset = {
3338
+ top: position.top + position.height / 2 - actualHeight / 2,
3339
+ left: position.left + position.width
3340
+ };
3341
+ break;
3342
+ case 'bottom':
3343
+ offset = {
3344
+ top: position.top + position.height,
3345
+ left: position.left + position.width / 2 - actualWidth / 2
3346
+ };
3347
+ break;
3348
+ case 'left':
3349
+ offset = {
3350
+ top: position.top + position.height / 2 - actualHeight / 2,
3351
+ left: position.left - actualWidth
3352
+ };
3353
+ break;
3354
+ default:
3355
+ offset = {
3356
+ top: position.top - actualHeight,
3357
+ left: position.left + position.width / 2 - actualWidth / 2
3358
+ };
3359
+ break;
3360
+ }
3361
+ if (!split[1]) {
3362
+ return offset;
3363
+ }
3364
+ // Add support for corners @todo css
3365
+ if (split[0] === 'top' || split[0] === 'bottom') {
3366
+ switch (split[1]) {
3367
+ case 'left':
3368
+ offset.left = position.left;
3369
+ break;
3370
+ case 'right':
3371
+ offset.left = position.left + position.width - actualWidth;
3372
+ }
3373
+ } else if (split[0] === 'left' || split[0] === 'right') {
3374
+ switch (split[1]) {
3375
+ case 'top':
3376
+ offset.top = position.top - actualHeight;
3377
+ break;
3378
+ case 'bottom':
3379
+ offset.top = position.top + position.height;
3380
+ }
3381
+ }
3382
+ return offset;
3383
+ }
3384
+ return $tooltip;
3385
+ }
3386
+ // Helper functions
3387
+ function findElement(query, element) {
3388
+ return angular.element((element || document).querySelectorAll(query));
3389
+ }
3390
+ function fetchTemplate(template) {
3391
+ return $q.when($templateCache.get(template) || $http.get(template)).then(function (res) {
3392
+ if (angular.isObject(res)) {
3393
+ $templateCache.put(template, res.data);
3394
+ return res.data;
3395
+ }
3396
+ return res;
3397
+ });
3398
+ }
3399
+ return TooltipFactory;
3400
+ }
3401
+ ];
3402
+ }).directive('bsTooltip', [
3403
+ '$window',
3404
+ '$location',
3405
+ '$sce',
3406
+ '$tooltip',
3407
+ '$$rAF',
3408
+ function ($window, $location, $sce, $tooltip, $$rAF) {
3409
+ return {
3410
+ restrict: 'EAC',
3411
+ scope: true,
3412
+ link: function postLink(scope, element, attr, transclusion) {
3413
+ // Directive options
3414
+ var options = { scope: scope };
3415
+ angular.forEach([
3416
+ 'template',
3417
+ 'contentTemplate',
3418
+ 'placement',
3419
+ 'container',
3420
+ 'delay',
3421
+ 'trigger',
3422
+ 'keyboard',
3423
+ 'html',
3424
+ 'animation',
3425
+ 'type'
3426
+ ], function (key) {
3427
+ if (angular.isDefined(attr[key]))
3428
+ options[key] = attr[key];
3429
+ });
3430
+ // Observe scope attributes for change
3431
+ angular.forEach(['title'], function (key) {
3432
+ attr[key] && attr.$observe(key, function (newValue, oldValue) {
3433
+ scope[key] = $sce.trustAsHtml(newValue);
3434
+ angular.isDefined(oldValue) && $$rAF(function () {
3435
+ tooltip && tooltip.$applyPlacement();
3436
+ });
3437
+ });
3438
+ });
3439
+ // Support scope as an object
3440
+ attr.bsTooltip && scope.$watch(attr.bsTooltip, function (newValue, oldValue) {
3441
+ if (angular.isObject(newValue)) {
3442
+ angular.extend(scope, newValue);
3443
+ } else {
3444
+ scope.title = newValue;
3445
+ }
3446
+ angular.isDefined(oldValue) && $$rAF(function () {
3447
+ tooltip && tooltip.$applyPlacement();
3448
+ });
3449
+ }, true);
3450
+ // Initialize popover
3451
+ var tooltip = $tooltip(element, options);
3452
+ // Garbage collection
3453
+ scope.$on('$destroy', function () {
3454
+ tooltip.destroy();
3455
+ options = null;
3456
+ tooltip = null;
3457
+ });
3458
+ }
3459
+ };
3460
+ }
3461
+ ]);
3462
+
3463
+ // Source: typeahead.js
3464
+ angular.module('mgcrea.ngStrap.typeahead', [
3465
+ 'mgcrea.ngStrap.tooltip',
3466
+ 'mgcrea.ngStrap.helpers.parseOptions'
3467
+ ]).provider('$typeahead', function () {
3468
+ var defaults = this.defaults = {
3469
+ animation: 'am-fade',
3470
+ prefixClass: 'typeahead',
3471
+ placement: 'bottom-left',
3472
+ template: 'typeahead/typeahead.tpl.html',
3473
+ trigger: 'focus',
3474
+ container: false,
3475
+ keyboard: true,
3476
+ html: false,
3477
+ delay: 0,
3478
+ minLength: 1,
3479
+ filter: 'filter',
3480
+ limit: 6
3481
+ };
3482
+ this.$get = [
3483
+ '$window',
3484
+ '$rootScope',
3485
+ '$tooltip',
3486
+ function ($window, $rootScope, $tooltip) {
3487
+ var bodyEl = angular.element($window.document.body);
3488
+ function TypeaheadFactory(element, config) {
3489
+ var $typeahead = {};
3490
+ // Common vars
3491
+ var options = angular.extend({}, defaults, config);
3492
+ var controller = options.controller;
3493
+ $typeahead = $tooltip(element, options);
3494
+ var parentScope = config.scope;
3495
+ var scope = $typeahead.$scope;
3496
+ scope.$resetMatches = function () {
3497
+ scope.$matches = [];
3498
+ scope.$activeIndex = 0;
3499
+ };
3500
+ scope.$resetMatches();
3501
+ scope.$activate = function (index) {
3502
+ scope.$$postDigest(function () {
3503
+ $typeahead.activate(index);
3504
+ });
3505
+ };
3506
+ scope.$select = function (index, evt) {
3507
+ scope.$$postDigest(function () {
3508
+ $typeahead.select(index);
3509
+ });
3510
+ };
3511
+ scope.$isVisible = function () {
3512
+ return $typeahead.$isVisible();
3513
+ };
3514
+ // Public methods
3515
+ $typeahead.update = function (matches) {
3516
+ scope.$matches = matches;
3517
+ if (scope.$activeIndex >= matches.length) {
3518
+ scope.$activeIndex = 0;
3519
+ }
3520
+ };
3521
+ $typeahead.activate = function (index) {
3522
+ scope.$activeIndex = index;
3523
+ };
3524
+ $typeahead.select = function (index) {
3525
+ var value = scope.$matches[index].value;
3526
+ if (controller) {
3527
+ controller.$setViewValue(value);
3528
+ controller.$render();
3529
+ if (parentScope)
3530
+ parentScope.$digest();
3531
+ }
3532
+ scope.$resetMatches();
3533
+ // Emit event
3534
+ scope.$emit('$typeahead.select', value, index);
3535
+ };
3536
+ // Protected methods
3537
+ $typeahead.$isVisible = function () {
3538
+ if (!options.minLength || !controller) {
3539
+ return !!scope.$matches.length;
3540
+ }
3541
+ // minLength support
3542
+ return scope.$matches.length && angular.isString(controller.$viewValue) && controller.$viewValue.length >= options.minLength;
3543
+ };
3544
+ $typeahead.$getIndex = function (value) {
3545
+ var l = scope.$matches.length, i = l;
3546
+ if (!l)
3547
+ return;
3548
+ for (i = l; i--;) {
3549
+ if (scope.$matches[i].value === value)
3550
+ break;
3551
+ }
3552
+ if (i < 0)
3553
+ return;
3554
+ return i;
3555
+ };
3556
+ $typeahead.$onMouseDown = function (evt) {
3557
+ // Prevent blur on mousedown
3558
+ evt.preventDefault();
3559
+ evt.stopPropagation();
3560
+ };
3561
+ $typeahead.$onKeyDown = function (evt) {
3562
+ if (!/(38|40|13)/.test(evt.keyCode))
3563
+ return;
3564
+ evt.preventDefault();
3565
+ evt.stopPropagation();
3566
+ // Select with enter
3567
+ if (evt.keyCode === 13 && scope.$matches.length) {
3568
+ return $typeahead.select(scope.$activeIndex);
3569
+ }
3570
+ // Navigate with keyboard
3571
+ if (evt.keyCode === 38 && scope.$activeIndex > 0)
3572
+ scope.$activeIndex--;
3573
+ else if (evt.keyCode === 40 && scope.$activeIndex < scope.$matches.length - 1)
3574
+ scope.$activeIndex++;
3575
+ else if (angular.isUndefined(scope.$activeIndex))
3576
+ scope.$activeIndex = 0;
3577
+ scope.$digest();
3578
+ };
3579
+ // Overrides
3580
+ var show = $typeahead.show;
3581
+ $typeahead.show = function () {
3582
+ show();
3583
+ setTimeout(function () {
3584
+ $typeahead.$element.on('mousedown', $typeahead.$onMouseDown);
3585
+ if (options.keyboard) {
3586
+ element.on('keydown', $typeahead.$onKeyDown);
3587
+ }
3588
+ });
3589
+ };
3590
+ var hide = $typeahead.hide;
3591
+ $typeahead.hide = function () {
3592
+ $typeahead.$element.off('mousedown', $typeahead.$onMouseDown);
3593
+ if (options.keyboard) {
3594
+ element.off('keydown', $typeahead.$onKeyDown);
3595
+ }
3596
+ hide();
3597
+ };
3598
+ return $typeahead;
3599
+ }
3600
+ TypeaheadFactory.defaults = defaults;
3601
+ return TypeaheadFactory;
3602
+ }
3603
+ ];
3604
+ }).directive('bsTypeahead', [
3605
+ '$window',
3606
+ '$parse',
3607
+ '$q',
3608
+ '$typeahead',
3609
+ '$parseOptions',
3610
+ function ($window, $parse, $q, $typeahead, $parseOptions) {
3611
+ var defaults = $typeahead.defaults;
3612
+ return {
3613
+ restrict: 'EAC',
3614
+ require: 'ngModel',
3615
+ link: function postLink(scope, element, attr, controller) {
3616
+ // Directive options
3617
+ var options = {
3618
+ scope: scope,
3619
+ controller: controller
3620
+ };
3621
+ angular.forEach([
3622
+ 'placement',
3623
+ 'container',
3624
+ 'delay',
3625
+ 'trigger',
3626
+ 'keyboard',
3627
+ 'html',
3628
+ 'animation',
3629
+ 'template',
3630
+ 'filter',
3631
+ 'limit',
3632
+ 'minLength'
3633
+ ], function (key) {
3634
+ if (angular.isDefined(attr[key]))
3635
+ options[key] = attr[key];
3636
+ });
3637
+ // Build proper ngOptions
3638
+ var filter = options.filter || defaults.filter;
3639
+ var limit = options.limit || defaults.limit;
3640
+ var ngOptions = attr.ngOptions;
3641
+ if (filter)
3642
+ ngOptions += ' | ' + filter + ':$viewValue';
3643
+ if (limit)
3644
+ ngOptions += ' | limitTo:' + limit;
3645
+ var parsedOptions = $parseOptions(ngOptions);
3646
+ // Initialize typeahead
3647
+ var typeahead = $typeahead(element, options);
3648
+ // if(!dump) var dump = console.error.bind(console);
3649
+ // Watch model for changes
3650
+ scope.$watch(attr.ngModel, function (newValue, oldValue) {
3651
+ scope.$modelValue = newValue;
3652
+ //Set model value on the scope to custom templates can use it.
3653
+ parsedOptions.valuesFn(scope, controller).then(function (values) {
3654
+ if (values.length > limit)
3655
+ values = values.slice(0, limit);
3656
+ // if(matches.length === 1 && matches[0].value === newValue) return;
3657
+ typeahead.update(values);
3658
+ // Queue a new rendering that will leverage collection loading
3659
+ controller.$render();
3660
+ });
3661
+ });
3662
+ // Model rendering in view
3663
+ controller.$render = function () {
3664
+ // console.warn('$render', element.attr('ng-model'), 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue, 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue);
3665
+ if (controller.$isEmpty(controller.$viewValue))
3666
+ return element.val('');
3667
+ var index = typeahead.$getIndex(controller.$modelValue);
3668
+ var selected = angular.isDefined(index) ? typeahead.$scope.$matches[index].label : controller.$viewValue;
3669
+ element.val(selected.replace(/<(?:.|\n)*?>/gm, '').trim());
3670
+ };
3671
+ // Garbage collection
3672
+ scope.$on('$destroy', function () {
3673
+ typeahead.destroy();
3674
+ options = null;
3675
+ typeahead = null;
3676
+ });
3677
+ }
3678
+ };
3679
+ }
3680
+ ]);
3681
+
3682
+ })(window, document);