|
1 |
| -angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position']) |
2 |
| - |
3 |
| -.constant('dropdownConfig', { |
4 |
| - openClass: 'open' |
5 |
| -}) |
6 |
| - |
7 |
| -.service('dropdownService', ['$document', '$rootScope', function($document, $rootScope) { |
8 |
| - var openScope = null; |
9 |
| - |
10 |
| - this.open = function( dropdownScope ) { |
11 |
| - if ( !openScope ) { |
12 |
| - $document.bind('click', closeDropdown); |
13 |
| - $document.bind('keydown', keybindFilter); |
14 |
| - } |
15 |
| - |
16 |
| - if ( openScope && openScope !== dropdownScope ) { |
17 |
| - openScope.isOpen = false; |
18 |
| - } |
19 |
| - |
20 |
| - openScope = dropdownScope; |
21 |
| - }; |
22 |
| - |
23 |
| - this.close = function( dropdownScope ) { |
24 |
| - if ( openScope === dropdownScope ) { |
25 |
| - openScope = null; |
26 |
| - $document.unbind('click', closeDropdown); |
27 |
| - $document.unbind('keydown', keybindFilter); |
28 |
| - } |
29 |
| - }; |
30 |
| - |
31 |
| - var closeDropdown = function( evt ) { |
32 |
| - // This method may still be called during the same mouse event that |
33 |
| - // unbound this event handler. So check openScope before proceeding. |
34 |
| - if (!openScope) { return; } |
35 |
| - |
36 |
| - if( evt && openScope.getAutoClose() === 'disabled' ) { return ; } |
37 |
| - |
38 |
| - var toggleElement = openScope.getToggleElement(); |
39 |
| - if ( evt && toggleElement && toggleElement[0].contains(evt.target) ) { |
40 |
| - return; |
41 |
| - } |
42 |
| - |
43 |
| - var $element = openScope.getElement(); |
44 |
| - if( evt && openScope.getAutoClose() === 'outsideClick' && $element && $element[0].contains(evt.target) ) { |
45 |
| - return; |
46 |
| - } |
47 |
| - |
48 |
| - openScope.isOpen = false; |
49 |
| - |
50 |
| - if (!$rootScope.$$phase) { |
51 |
| - openScope.$apply(); |
52 |
| - } |
53 |
| - }; |
54 |
| - |
55 |
| - var keybindFilter = function( evt ) { |
56 |
| - if ( evt.which === 27 ) { |
57 |
| - openScope.focusToggleElement(); |
58 |
| - closeDropdown(); |
59 |
| - } |
60 |
| - else if ( openScope.isKeynavEnabled() && /(38|40)/.test(evt.which) && openScope.isOpen ) { |
61 |
| - evt.preventDefault(); |
62 |
| - evt.stopPropagation(); |
63 |
| - openScope.focusDropdownEntry(evt.which); |
64 |
| - } |
65 |
| - }; |
66 |
| -}]) |
67 |
| - |
68 |
| -.controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', '$position', '$document', '$compile', '$templateRequest', function($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate, $position, $document, $compile, $templateRequest) { |
69 |
| - var self = this, |
70 |
| - scope = $scope.$new(), // create a child scope so we are not polluting original one |
71 |
| - templateScope, |
72 |
| - openClass = dropdownConfig.openClass, |
73 |
| - getIsOpen, |
74 |
| - setIsOpen = angular.noop, |
75 |
| - toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop, |
76 |
| - appendToBody = false, |
77 |
| - keynavEnabled =false, |
78 |
| - selectedOption = null; |
79 |
| - |
80 |
| - this.init = function( element ) { |
81 |
| - self.$element = element; |
82 |
| - |
83 |
| - if ( $attrs.isOpen ) { |
84 |
| - getIsOpen = $parse($attrs.isOpen); |
85 |
| - setIsOpen = getIsOpen.assign; |
86 |
| - |
87 |
| - $scope.$watch(getIsOpen, function(value) { |
88 |
| - scope.isOpen = !!value; |
89 |
| - }); |
90 |
| - } |
91 |
| - |
92 |
| - appendToBody = angular.isDefined($attrs.dropdownAppendToBody); |
93 |
| - keynavEnabled = angular.isDefined($attrs.keyboardNav); |
94 |
| - |
95 |
| - if ( appendToBody && self.dropdownMenu ) { |
96 |
| - $document.find('body').append( self.dropdownMenu ); |
97 |
| - element.on('$destroy', function handleDestroyEvent() { |
98 |
| - self.dropdownMenu.remove(); |
99 |
| - }); |
100 |
| - } |
101 |
| - }; |
102 |
| - |
103 |
| - this.toggle = function( open ) { |
104 |
| - return scope.isOpen = arguments.length ? !!open : !scope.isOpen; |
105 |
| - }; |
106 |
| - |
107 |
| - // Allow other directives to watch status |
108 |
| - this.isOpen = function() { |
109 |
| - return scope.isOpen; |
110 |
| - }; |
111 |
| - |
112 |
| - scope.getToggleElement = function() { |
113 |
| - return self.toggleElement; |
114 |
| - }; |
115 |
| - |
116 |
| - scope.getAutoClose = function() { |
117 |
| - return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled' |
118 |
| - }; |
119 |
| - |
120 |
| - scope.getElement = function() { |
121 |
| - return self.$element; |
122 |
| - }; |
123 |
| - |
124 |
| - scope.isKeynavEnabled = function() { |
125 |
| - return keynavEnabled; |
126 |
| - }; |
127 |
| - |
128 |
| - scope.focusDropdownEntry = function(keyCode) { |
129 |
| - var elems = self.dropdownMenu ? //If append to body is used. |
130 |
| - (angular.element(self.dropdownMenu).find('a')) : |
131 |
| - (angular.element(self.$element).find('ul').eq(0).find('a')); |
132 |
| - |
133 |
| - switch (keyCode) { |
134 |
| - case (40): { |
135 |
| - if ( !angular.isNumber(self.selectedOption)) { |
136 |
| - self.selectedOption = 0; |
137 |
| - } else { |
138 |
| - self.selectedOption = (self.selectedOption === elems.length -1 ? |
139 |
| - self.selectedOption : |
140 |
| - self.selectedOption + 1); |
141 |
| - } |
142 |
| - break; |
143 |
| - } |
144 |
| - case (38): { |
145 |
| - if ( !angular.isNumber(self.selectedOption)) { |
146 |
| - return; |
147 |
| - } else { |
148 |
| - self.selectedOption = (self.selectedOption === 0 ? |
149 |
| - 0 : |
150 |
| - self.selectedOption - 1); |
151 |
| - } |
152 |
| - break; |
153 |
| - } |
154 |
| - } |
155 |
| - elems[self.selectedOption].focus(); |
156 |
| - }; |
157 |
| - |
158 |
| - scope.focusToggleElement = function() { |
159 |
| - if ( self.toggleElement ) { |
160 |
| - self.toggleElement[0].focus(); |
161 |
| - } |
162 |
| - }; |
163 |
| - |
164 |
| - scope.$watch('isOpen', function( isOpen, wasOpen ) { |
165 |
| - if ( appendToBody && self.dropdownMenu ) { |
166 |
| - var pos = $position.positionElements(self.$element, self.dropdownMenu, 'bottom-left', true); |
167 |
| - self.dropdownMenu.css({ |
168 |
| - top: pos.top + 'px', |
169 |
| - left: pos.left + 'px', |
170 |
| - display: isOpen ? 'block' : 'none' |
171 |
| - }); |
172 |
| - } |
173 |
| - |
174 |
| - $animate[isOpen ? 'addClass' : 'removeClass'](self.$element, openClass); |
175 |
| - |
176 |
| - if ( isOpen ) { |
177 |
| - if (self.dropdownMenuTemplateUrl) { |
178 |
| - $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) { |
179 |
| - templateScope = scope.$new(); |
180 |
| - $compile(tplContent.trim())(templateScope, function(dropdownElement) { |
181 |
| - var newEl = dropdownElement; |
182 |
| - self.dropdownMenu.replaceWith(newEl); |
183 |
| - self.dropdownMenu = newEl; |
184 |
| - }); |
185 |
| - }); |
186 |
| - } |
187 |
| - |
188 |
| - scope.focusToggleElement(); |
189 |
| - dropdownService.open( scope ); |
190 |
| - } else { |
191 |
| - if (self.dropdownMenuTemplateUrl) { |
192 |
| - if (templateScope) { |
193 |
| - templateScope.$destroy(); |
194 |
| - } |
195 |
| - var newEl = angular.element('<ul class="dropdown-menu"></ul>'); |
196 |
| - self.dropdownMenu.replaceWith(newEl); |
197 |
| - self.dropdownMenu = newEl; |
198 |
| - } |
199 |
| - |
200 |
| - dropdownService.close( scope ); |
201 |
| - self.selectedOption = null; |
202 |
| - } |
203 |
| - |
204 |
| - setIsOpen($scope, isOpen); |
205 |
| - if (angular.isDefined(isOpen) && isOpen !== wasOpen) { |
206 |
| - toggleInvoker($scope, { open: !!isOpen }); |
207 |
| - } |
208 |
| - }); |
209 |
| - |
210 |
| - $scope.$on('$locationChangeSuccess', function() { |
211 |
| - if (scope.getAutoClose() !== 'disabled') { |
212 |
| - scope.isOpen = false; |
213 |
| - } |
214 |
| - }); |
215 |
| - |
216 |
| - $scope.$on('$destroy', function() { |
217 |
| - scope.$destroy(); |
218 |
| - }); |
219 |
| -}]) |
220 |
| - |
221 |
| -.directive('dropdown', function() { |
222 |
| - return { |
223 |
| - controller: 'DropdownController', |
224 |
| - link: function(scope, element, attrs, dropdownCtrl) { |
225 |
| - dropdownCtrl.init( element ); |
226 |
| - } |
227 |
| - }; |
228 |
| -}) |
229 |
| - |
230 |
| -.directive('dropdownMenu', function() { |
231 |
| - return { |
232 |
| - restrict: 'AC', |
233 |
| - require: '?^dropdown', |
234 |
| - link: function(scope, element, attrs, dropdownCtrl) { |
235 |
| - if (!dropdownCtrl) { |
236 |
| - return; |
237 |
| - } |
238 |
| - var tplUrl = attrs.templateUrl; |
239 |
| - if (tplUrl) { |
240 |
| - dropdownCtrl.dropdownMenuTemplateUrl = tplUrl; |
241 |
| - } |
242 |
| - if (!dropdownCtrl.dropdownMenu) { |
243 |
| - dropdownCtrl.dropdownMenu = element; |
244 |
| - } |
245 |
| - } |
246 |
| - }; |
247 |
| -}) |
248 |
| - |
249 |
| -.directive('keyboardNav', function() { |
250 |
| - return { |
251 |
| - restrict: 'A', |
252 |
| - require: '?^dropdown', |
253 |
| - link: function (scope, element, attrs, dropdownCtrl) { |
254 |
| - |
255 |
| - element.bind('keydown', function(e) { |
256 |
| - |
257 |
| - if ( /(38|40)/.test(e.which)) { |
258 |
| - |
259 |
| - e.preventDefault(); |
260 |
| - e.stopPropagation(); |
261 |
| - |
262 |
| - var elems = angular.element(element).find('a'); |
263 |
| - |
264 |
| - switch (e.keyCode) { |
265 |
| - case (40): { // Down |
266 |
| - if ( !angular.isNumber(dropdownCtrl.selectedOption)) { |
267 |
| - dropdownCtrl.selectedOption = 0; |
268 |
| - } else { |
269 |
| - dropdownCtrl.selectedOption = (dropdownCtrl.selectedOption === elems.length -1 ? dropdownCtrl.selectedOption : dropdownCtrl.selectedOption+1); |
270 |
| - } |
271 |
| - |
272 |
| - } |
273 |
| - break; |
274 |
| - case (38): { // Up |
275 |
| - dropdownCtrl.selectedOption = (dropdownCtrl.selectedOption === 0 ? 0 : dropdownCtrl.selectedOption-1); |
276 |
| - } |
277 |
| - break; |
278 |
| - } |
279 |
| - elems[dropdownCtrl.selectedOption].focus(); |
280 |
| - } |
281 |
| - }); |
282 |
| - } |
283 |
| - |
284 |
| - }; |
285 |
| -}) |
286 |
| - |
287 |
| -.directive('dropdownToggle', function() { |
288 |
| - return { |
289 |
| - require: '?^dropdown', |
290 |
| - link: function(scope, element, attrs, dropdownCtrl) { |
291 |
| - if ( !dropdownCtrl ) { |
292 |
| - return; |
293 |
| - } |
294 |
| - |
295 |
| - dropdownCtrl.toggleElement = element; |
296 |
| - |
297 |
| - var toggleDropdown = function(event) { |
298 |
| - event.preventDefault(); |
299 |
| - |
300 |
| - if ( !element.hasClass('disabled') && !attrs.disabled ) { |
301 |
| - scope.$apply(function() { |
302 |
| - dropdownCtrl.toggle(); |
303 |
| - }); |
304 |
| - } |
305 |
| - }; |
306 |
| - |
307 |
| - element.bind('click', toggleDropdown); |
308 |
| - |
309 |
| - // WAI-ARIA |
310 |
| - element.attr({ 'aria-haspopup': true, 'aria-expanded': false }); |
311 |
| - scope.$watch(dropdownCtrl.isOpen, function( isOpen ) { |
312 |
| - element.attr('aria-expanded', !!isOpen); |
313 |
| - }); |
314 |
| - |
315 |
| - scope.$on('$destroy', function() { |
316 |
| - element.unbind('click', toggleDropdown); |
317 |
| - }); |
318 |
| - } |
319 |
| - }; |
320 |
| -}); |
0 commit comments