angular-strap-rails 2.0.1

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