dashstrap 0.2.3 → 1.0.0

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