diff --git a/src/dropdown/docs/readme.md b/src/dropdown/docs/readme.md index 09b646d072..3026673ce2 100644 --- a/src/dropdown/docs/readme.md +++ b/src/dropdown/docs/readme.md @@ -25,7 +25,7 @@ Each of these parts need to be used as attribute directives. * `dropdown-append-to-body` B _(Default: `false`)_ - - Appends the inner dropdown-menu to the body element. + Appends the inner dropdown-menu to the body element if the attribute is present without a value, or with a non `false` value. * `is-open` $ diff --git a/src/dropdown/dropdown.js b/src/dropdown/dropdown.js index 7c5f805419..312e880905 100644 --- a/src/dropdown/dropdown.js +++ b/src/dropdown/dropdown.js @@ -144,8 +144,6 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap. getIsOpen, setIsOpen = angular.noop, toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop, - appendToBody = false, - appendTo = null, keynavEnabled = false, selectedOption = null, body = $document.find('body'); @@ -162,26 +160,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap. }); } - if (angular.isDefined($attrs.dropdownAppendTo)) { - var appendToEl = $parse($attrs.dropdownAppendTo)(scope); - if (appendToEl) { - appendTo = angular.element(appendToEl); - } - } - - appendToBody = angular.isDefined($attrs.dropdownAppendToBody); keynavEnabled = angular.isDefined($attrs.keyboardNav); - - if (appendToBody && !appendTo) { - appendTo = body; - } - - if (appendTo && self.dropdownMenu) { - appendTo.append(self.dropdownMenu); - $element.on('$destroy', function handleDestroyEvent() { - self.dropdownMenu.remove(); - }); - } }; this.toggle = function(open) { @@ -253,7 +232,42 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap. } }; + function removeDropdownMenu() { + $element.append(self.dropdownMenu); + } + scope.$watch('isOpen', function(isOpen, wasOpen) { + var appendTo = null, + appendToBody = false; + + if (angular.isDefined($attrs.dropdownAppendTo)) { + var appendToEl = $parse($attrs.dropdownAppendTo)(scope); + if (appendToEl) { + appendTo = angular.element(appendToEl); + } + } + + if (angular.isDefined($attrs.dropdownAppendToBody)) { + var appendToBodyValue = $parse($attrs.dropdownAppendToBody)(scope); + if (appendToBodyValue !== false) { + appendToBody = true; + } + } + + if (appendToBody && !appendTo) { + appendTo = body; + } + + if (appendTo && self.dropdownMenu) { + if (isOpen) { + appendTo.append(self.dropdownMenu); + $element.on('$destroy', removeDropdownMenu); + } else { + $element.off('$destroy', removeDropdownMenu); + removeDropdownMenu(); + } + } + if (appendTo && self.dropdownMenu) { var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true), css, diff --git a/src/dropdown/test/dropdown.spec.js b/src/dropdown/test/dropdown.spec.js index dce7542ba8..17226b5c5e 100644 --- a/src/dropdown/test/dropdown.spec.js +++ b/src/dropdown/test/dropdown.spec.js @@ -215,43 +215,198 @@ describe('uib-dropdown', function() { }); describe('using dropdown-append-to-body', function() { - function dropdown() { - return $compile('
  • ')($rootScope); - } - - beforeEach(function() { - element = dropdown(); - $document.find('body').append(element); - }); - - afterEach(function() { - element.remove(); - }); + describe('with no value', function() { + function dropdown() { + return $compile('
  • ')($rootScope); + } - it('adds the menu to the body', function() { - expect($document.find('#dropdown-menu').parent()[0]).toBe($document.find('body')[0]); - }); + beforeEach(function() { + element = dropdown(); + $document.find('body').append(element); + }); - it('focuses the dropdown element on close', function() { - var toggle = element.find('[uib-dropdown-toggle]'); - var menu = $document.find('#dropdown-menu a'); - toggle.trigger('click'); - menu.focus(); + afterEach(function() { + element.remove(); + }); - menu.trigger('click'); + it('does not add the menu to the body', function() { + expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]); + }); - expect(document.activeElement).toBe(toggle[0]); + describe('when toggled open', function() { + var toggle; + beforeEach(function() { + toggle = element.find('[uib-dropdown-toggle]'); + toggle.trigger('click'); + }); + it('adds the menu to the body', function() { + expect($document.find('#dropdown-menu').parent()[0]).toBe($document.find('body')[0]); + }); + + describe('when toggled closed', function() { + beforeEach(function() { + toggle.trigger('click'); + }); + it('removes the menu from body', function() { + expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]); + }); + }); + + describe('when closed by clicking on menu', function() { + var menu; + beforeEach(function() { + menu = $document.find('#dropdown-menu a'); + menu.focus(); + menu.trigger('click'); + }); + it('focuses the dropdown element on close', function() { + expect(document.activeElement).toBe(toggle[0]); + }); + it('removes the menu from body', function() { + expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]); + }); + }); + describe('when the dropdown is removed', function() { + beforeEach(function() { + element.remove(); + $rootScope.$digest(); + }); + it('removes the menu from body', function() { + expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]); + }); + }); + }); }); - it('removes the menu when the dropdown is removed', function() { - element.remove(); - $rootScope.$digest(); - expect($document.find('#dropdown-menu').length).toEqual(0); + describe('with a value', function() { + function dropdown() { + return $compile('
  • ')($rootScope); + } + describe('that is not false', function() { + beforeEach(function() { + $rootScope.appendToBody = 'sure'; + + element = dropdown(); + $document.find('body').append(element); + }); + + afterEach(function() { + element.remove(); + }); + it('does not add the menu to the body', function() { + expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]); + }); + + describe('when toggled open', function() { + var toggle; + beforeEach(function() { + toggle = element.find('[uib-dropdown-toggle]'); + toggle.trigger('click'); + }); + it('adds the menu to the body', function() { + expect($document.find('#dropdown-menu').parent()[0]).toBe($document.find('body')[0]); + }); + + describe('when toggled closed', function() { + beforeEach(function() { + toggle.trigger('click'); + }); + it('removes the menu from body', function() { + expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]); + }); + }); + + describe('when closed by clicking on menu', function() { + var menu; + beforeEach(function() { + menu = $document.find('#dropdown-menu a'); + menu.focus(); + menu.trigger('click'); + }); + it('focuses the dropdown element on close', function() { + expect(document.activeElement).toBe(toggle[0]); + }); + it('removes the menu from body', function() { + expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]); + }); + }); + describe('when the dropdown is removed', function() { + beforeEach(function() { + element.remove(); + $rootScope.$digest(); + }); + it('removes the menu from body', function() { + expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]); + }); + }); + }); + }); + + describe('that is false', function() { + beforeEach(function() { + $rootScope.appendToBody = false; + + element = dropdown(); + $document.find('body').append(element); + }); + + afterEach(function() { + element.remove(); + }); + + it('does not add the menu to the body', function() { + expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]); + }); + + describe('when toggled open', function() { + var toggle; + beforeEach(function() { + toggle = element.find('[uib-dropdown-toggle]'); + toggle.trigger('click'); + }); + it('does not add the menu to the body', function() { + expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]); + }); + + describe('when toggled closed', function() { + beforeEach(function() { + toggle.trigger('click'); + }); + it('does not remove the menu', function() { + expect($document.find('#dropdown-menu').length).not.toEqual(0); + }); + }); + + describe('when closed by clicking on menu', function() { + var menu; + beforeEach(function() { + menu = $document.find('#dropdown-menu a'); + menu.focus(); + menu.trigger('click'); + }); + it('focuses the dropdown element on close', function() { + expect(document.activeElement).toBe(toggle[0]); + }); + it('does not removes the menu from body', function() { + expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]); + }); + }); + describe('when the dropdown is removed', function() { + beforeEach(function() { + element.remove(); + $rootScope.$digest(); + }); + it('removes the menu from body', function() { + expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]); + }); + }); + }); + }); }); }); describe('using dropdown-append-to', function() { - var initialPage; + var initialPage, container; function dropdown() { return $compile('
  • ')($rootScope); @@ -260,7 +415,7 @@ describe('uib-dropdown', function() { beforeEach(function() { $document.find('body').append(angular.element('')); - $rootScope.appendTo = $document.find('#dropdown-container'); + $rootScope.appendTo = container = $document.find('#dropdown-container'); element = dropdown(); $document.find('body').append(element); @@ -271,35 +426,64 @@ describe('uib-dropdown', function() { $document.find('#dropdown-container').remove(); }); - it('appends to container', function() { - expect($document.find('#dropdown-menu').parent()[0].id).toBe('dropdown-container'); + it('does not add the menu to the container', function() { + expect($document.find('#dropdown-menu').parent()[0]).not.toBe(container[0]); }); - - it('toggles open class on container', function() { - var container = $document.find('#dropdown-container'); - - expect(container).not.toHaveClass('uib-dropdown-open'); - element.find('[uib-dropdown-toggle]').click(); - expect(container).toHaveClass('uib-dropdown-open'); - element.find('[uib-dropdown-toggle]').click(); + it('does not add open class on container', function() { expect(container).not.toHaveClass('uib-dropdown-open'); }); - it('focuses the dropdown element on close', function() { - var toggle = element.find('[uib-dropdown-toggle]'); - var menu = $document.find('#dropdown-menu a'); - toggle.trigger('click'); - menu.focus(); - - menu.trigger('click'); + describe('when toggled open', function() { + var toggle; + beforeEach(function() { + toggle = element.find('[uib-dropdown-toggle]'); + toggle.trigger('click'); + }); + it('adds the menu to the container', function() { + expect($document.find('#dropdown-menu').parent()[0]).toBe(container[0]); + }); + it('adds open class on container', function() { + expect(container).toHaveClass('uib-dropdown-open'); + }); - expect(document.activeElement).toBe(toggle[0]); - }); + describe('when toggled closed', function() { + beforeEach(function() { + toggle.trigger('click'); + }); + it('removes the menu from the container', function() { + expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]); + }); + it('removes open class from container', function() { + expect(container).not.toHaveClass('uib-dropdown-open'); + }); + }); - it('removes the menu when the dropdown is removed', function() { - element.remove(); - $rootScope.$digest(); - expect($document.find('#dropdown-menu').length).toEqual(0); + describe('when closed by clicking on menu', function() { + var menu; + beforeEach(function() { + menu = $document.find('#dropdown-menu a'); + menu.focus(); + menu.trigger('click'); + }); + it('focuses the dropdown element on close', function() { + expect(document.activeElement).toBe(toggle[0]); + }); + it('removes the menu from the container', function() { + expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]); + }); + it('removes open class from container', function() { + expect(container).not.toHaveClass('uib-dropdown-open'); + }); + }); + describe('when the dropdown is removed', function() { + beforeEach(function() { + element.remove(); + $rootScope.$digest(); + }); + it('removes the menu from the container', function() { + expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]); + }); + }); }); });