Skip to content

Commit dc580ed

Browse files
rysilvafernando-sendMail
authored andcommitted
feat(dropdown): Dropdown append-to-body
Closes angular-ui#3411 Fixes angular-ui#1030
1 parent 0347584 commit dc580ed

File tree

4 files changed

+78
-7
lines changed

4 files changed

+78
-7
lines changed

Diff for: src/dropdown/docs/demo.html

+14
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,20 @@
7070
</ul>
7171
</div>
7272

73+
<!-- Single button using append-to-body -->
74+
<div class="btn-group" dropdown dropdown-append-to-body>
75+
<button type="button" class="btn btn-primary dropdown-toggle" dropdown-toggle>
76+
Dropdown on Body <span class="caret"></span>
77+
</button>
78+
<ul class="dropdown-menu" role="menu">
79+
<li><a href="#">Action</a></li>
80+
<li><a href="#">Another action</a></li>
81+
<li><a href="#">Something else here</a></li>
82+
<li class="divider"></li>
83+
<li><a href="#">Separated link</a></li>
84+
</ul>
85+
</div>
86+
7387
<hr />
7488
<p>
7589
<button type="button" class="btn btn-default btn-sm" ng-click="toggleDropdown($event)">Toggle button dropdown</button>

Diff for: src/dropdown/docs/readme.md

+5
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@
22
Dropdown is a simple directive which will toggle a dropdown menu on click or programmatically.
33
You can either use `is-open` to toggle or add inside a `<a dropdown-toggle>` element to toggle it when is clicked.
44
There is also the `on-toggle(open)` optional expression fired when dropdown changes state.
5+
6+
Add `dropdown-append-to-body` to the `dropdown` element to append to the inner `dropdown-menu` to the body.
7+
This is useful when the dropdown button is inside a div with `overflow: hidden`, and the menu would otherwise be hidden.
8+
59
By default the dropdown will automatically close if any of its elements is clicked, you can change this behavior by setting the `auto-close` option as follows:
610

711
* `always` - (Default) automatically closes the dropdown when any of its elements is clicked.
812
* `outsideClick` - closes the dropdown automatically only when the user clicks any element outside the dropdown.
913
* `disabled` - disables the auto close. You can then control the open/close status of the dropdown manually, by using `is-open`. Please notice that the dropdown will still close if the toggle is clicked, the `esc` key is pressed or another dropdown is open.
14+

Diff for: src/dropdown/dropdown.js

+35-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
angular.module('ui.bootstrap.dropdown', [])
1+
angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
22

33
.constant('dropdownConfig', {
44
openClass: 'open'
@@ -60,13 +60,14 @@ angular.module('ui.bootstrap.dropdown', [])
6060
};
6161
}])
6262

63-
.controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', function($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate) {
63+
.controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', '$position', '$document', function($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate, $position, $document) {
6464
var self = this,
6565
scope = $scope.$new(), // create a child scope so we are not polluting original one
6666
openClass = dropdownConfig.openClass,
6767
getIsOpen,
6868
setIsOpen = angular.noop,
69-
toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop;
69+
toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
70+
appendToBody = false;
7071

7172
this.init = function( element ) {
7273
self.$element = element;
@@ -79,6 +80,15 @@ angular.module('ui.bootstrap.dropdown', [])
7980
scope.isOpen = !!value;
8081
});
8182
}
83+
84+
appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
85+
86+
if ( appendToBody && self.dropdownMenu ) {
87+
$document.find('body').append( self.dropdownMenu );
88+
element.on('$destroy', function handleDestroyEvent() {
89+
self.dropdownMenu.remove();
90+
});
91+
}
8292
};
8393

8494
this.toggle = function( open ) {
@@ -109,6 +119,15 @@ angular.module('ui.bootstrap.dropdown', [])
109119
};
110120

111121
scope.$watch('isOpen', function( isOpen, wasOpen ) {
122+
if ( appendToBody && self.dropdownMenu ) {
123+
var pos = $position.positionElements(self.$element, self.dropdownMenu, 'bottom-left', true);
124+
self.dropdownMenu.css({
125+
top: pos.top + 'px',
126+
left: pos.left + 'px',
127+
display: isOpen ? 'block' : 'none'
128+
});
129+
}
130+
112131
$animate[isOpen ? 'addClass' : 'removeClass'](self.$element, openClass);
113132

114133
if ( isOpen ) {
@@ -142,6 +161,19 @@ angular.module('ui.bootstrap.dropdown', [])
142161
};
143162
})
144163

164+
.directive('dropdownMenu', function() {
165+
return {
166+
restrict: 'AC',
167+
require: '?^dropdown',
168+
link: function(scope, element, attrs, dropdownCtrl) {
169+
if ( !dropdownCtrl ) {
170+
return;
171+
}
172+
dropdownCtrl.dropdownMenu = element;
173+
}
174+
};
175+
})
176+
145177
.directive('dropdownToggle', function() {
146178
return {
147179
require: '?^dropdown',

Diff for: src/dropdown/test/dropdown.spec.js

+24-4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ describe('dropdownToggle', function() {
99
$document = _$document_;
1010
}));
1111

12+
afterEach(function() {
13+
element.remove();
14+
});
15+
1216
var clickDropdownToggle = function(elm) {
1317
elm = elm || element;
1418
elm.find('a[dropdown-toggle]').click();
@@ -50,7 +54,6 @@ describe('dropdownToggle', function() {
5054
var optionEl = element.find('ul > li').eq(0).find('a').eq(0);
5155
optionEl.click();
5256
expect(element.hasClass('open')).toBe(false);
53-
element.remove();
5457
});
5558

5659
it('should close on document click', function() {
@@ -66,7 +69,6 @@ describe('dropdownToggle', function() {
6669
triggerKeyDown($document, 27);
6770
expect(element.hasClass('open')).toBe(false);
6871
expect(isFocused(element.find('a'))).toBe(true);
69-
element.remove();
7072
});
7173

7274
it('should not close on backspace key', function() {
@@ -180,6 +182,26 @@ describe('dropdownToggle', function() {
180182
});
181183
});
182184

185+
describe('using dropdown-append-to-body', function() {
186+
function dropdown() {
187+
return $compile('<li dropdown dropdown-append-to-body><a href dropdown-toggle></a><ul class="dropdown-menu" id="dropdown-menu"><li><a href>Hello On Body</a></li></ul></li>')($rootScope);
188+
}
189+
190+
beforeEach(function() {
191+
element = dropdown();
192+
});
193+
194+
it('adds the menu to the body', function() {
195+
expect($document.find('#dropdown-menu').parent()[0]).toBe($document.find('body')[0]);
196+
});
197+
198+
it('removes the menu when the dropdown is removed', function() {
199+
element.remove();
200+
$rootScope.$digest();
201+
expect($document.find('#dropdown-menu').length).toEqual(0);
202+
});
203+
});
204+
183205
describe('integration with $location URL rewriting', function() {
184206
function dropdown() {
185207

@@ -256,7 +278,6 @@ describe('dropdownToggle', function() {
256278
$rootScope.isopen = true;
257279
$rootScope.$digest();
258280
expect(isFocused(element.find('a'))).toBe(true);
259-
element.remove();
260281
});
261282
});
262283

@@ -396,7 +417,6 @@ describe('dropdownToggle', function() {
396417
triggerKeyDown($document, 27);
397418
expect(element.hasClass('open')).toBe(false);
398419
expect(isFocused(element.find('a'))).toBe(true);
399-
element.remove();
400420
});
401421

402422
it('should close anyway if another dropdown is opened', function() {

0 commit comments

Comments
 (0)