diff --git a/src/components/select/select.js b/src/components/select/select.js index 05bf3090ab2..ded804f2c5a 100755 --- a/src/components/select/select.js +++ b/src/components/select/select.js @@ -247,6 +247,7 @@ function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $compile, $par inputCheckValue(); }; + attr.$observe('placeholder', ngModelCtrl.$render); @@ -386,6 +387,10 @@ function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $compile, $par if (!element[0].hasAttribute('id')) { ariaAttrs.id = 'select_' + $mdUtil.nextUid(); } + + var containerId = 'select_container_' + $mdUtil.nextUid(); + selectContainer.attr('id', containerId); + ariaAttrs['aria-owns'] = containerId; element.attr(ariaAttrs); scope.$on('$destroy', function() { @@ -419,6 +424,9 @@ function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $compile, $par } selectMenuCtrl = selectContainer.find('md-select-menu').controller('mdSelectMenu'); selectMenuCtrl.init(ngModelCtrl, attr.ngModel); + element.on('$destroy', function() { + selectContainer.remove(); + }); } function handleKeypress(e) { @@ -442,7 +450,7 @@ function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $compile, $par } } - function openSelect(e) { + function openSelect() { selectScope.isOpen = true; element.attr('aria-expanded', 'true'); @@ -452,8 +460,8 @@ function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $compile, $par skipCompile: true, element: selectContainer, target: element[0], + selectCtrl: mdSelectCtrl, preserveElement: true, - parent: element, hasBackdrop: true, loadingAsync: attr.mdOnOpen ? scope.$eval(attr.mdOnOpen) || true : false }).finally(function() { @@ -1247,7 +1255,7 @@ function SelectProvider($$interimElementProvider) { * trigger the [optional] user-defined expression */ function announceClosed(opts) { - var mdSelect = opts.selectEl.controller('mdSelect'); + var mdSelect = opts.selectCtrl; if (mdSelect) { var menuController = opts.selectEl.controller('mdSelectMenu'); mdSelect.setLabelText(menuController.selectedLabels()); @@ -1262,7 +1270,7 @@ function SelectProvider($$interimElementProvider) { function calculateMenuPositions(scope, element, opts) { var containerNode = element[0], - targetNode = opts.target[0].children[1], // target the label + targetNode = opts.target[0].children[0], // target the label parentNode = $document[0].body, selectNode = opts.selectEl[0], contentNode = opts.contentEl[0], @@ -1370,7 +1378,7 @@ function SelectProvider($$interimElementProvider) { } else { left = (targetRect.left + centeredRect.left - centeredRect.paddingLeft) + 2; top = Math.floor(targetRect.top + targetRect.height / 2 - centeredRect.height / 2 - - centeredRect.top + contentNode.scrollTop) + 5; + centeredRect.top + contentNode.scrollTop) + 4; transformOrigin = (centeredRect.left + targetRect.width / 2) + 'px ' + (centeredRect.top + centeredRect.height / 2 - contentNode.scrollTop) + 'px 0px'; diff --git a/src/components/select/select.spec.js b/src/components/select/select.spec.js index e3f4e223a1b..9669fa6bc89 100755 --- a/src/components/select/select.spec.js +++ b/src/components/select/select.spec.js @@ -52,11 +52,19 @@ describe('', function() { var select = setupSelect('ng-model="val", md-container-class="test"').find('md-select'); openSelect(select); - var container = select[0].querySelector('.md-select-menu-container'); + var container = $document[0].querySelector('.md-select-menu-container'); expect(container).toBeTruthy(); expect(container.classList.contains('test')).toBe(true); })); + it('sets aria-owns between the select and the container', function() { + var select = setupSelect('ng-model="val"').find('md-select'); + var ownsId = select.attr('aria-owns'); + expect(ownsId).toBeTruthy(); + var containerId = select[0].querySelector('.md-select-menu-container').getAttribute('id'); + expect(ownsId).toBe(containerId); + }); + it('calls md-on-close when the select menu closes', inject(function($document, $rootScope) { var called = false; $rootScope.onClose = function() { @@ -89,6 +97,15 @@ describe('', function() { expect(backdrop.length).toBe(0); })); + it('removes the menu container when the select is removed', inject(function($document) { + var select = setupSelect('ng-model="val"', [1]).find('md-select'); + openSelect(select); + + select.remove(); + + expect($document.find('md-select-menu').length).toBe(0); + })); + it('should not trigger ng-change without a change when using trackBy', inject(function($rootScope) { var changed = false; $rootScope.onChange = function() { changed = true; }; @@ -402,7 +419,6 @@ describe('', function() { clickOption(el, 1); expect(selectedOptions(el).length).toBe(1); - expect(el.find('md-option').eq(1).attr('selected')).toBe('selected'); expect($rootScope.model).toBe(2); })); @@ -758,33 +774,33 @@ describe('', function() { })); describe('md-select', function() { - it('can be opened with a space key', inject(function($document) { + it('can be opened with a space key', function() { var el = setupSelect('ng-model="someModel"', [1, 2, 3]).find('md-select'); pressKey(el, 32); waitForSelectOpen(); expectSelectOpen(el); - })); + }); - it('can be opened with an enter key', inject(function($document) { + it('can be opened with an enter key', function() { var el = setupSelect('ng-model="someModel"', [1, 2, 3]).find('md-select'); pressKey(el, 13); waitForSelectOpen(); expectSelectOpen(el); - })); + }); - it('can be opened with the up key', inject(function($document) { + it('can be opened with the up key', function() { var el = setupSelect('ng-model="someModel"', [1, 2, 3]).find('md-select'); pressKey(el, 38); waitForSelectOpen(); expectSelectOpen(el); - })); + }); - it('can be opened with the down key', inject(function($document) { + it('can be opened with the down key', function() { var el = setupSelect('ng-model="someModel"', [1, 2, 3]).find('md-select'); pressKey(el, 40); waitForSelectOpen(); expectSelectOpen(el); - })); + }); it('supports typing an option name', inject(function($document, $rootScope) { var el = setupSelect('ng-model="someModel"', [1, 2, 3]).find('md-select'); @@ -798,7 +814,7 @@ describe('', function() { var el = setupSelect('ng-model="someVal"', [1, 2, 3]).find('md-select'); openSelect(el); expectSelectOpen(el); - var selectMenu = el.find('md-select-menu'); + var selectMenu = $document.find('md-select-menu'); expect(selectMenu.length).toBe(1); pressKey(selectMenu, 27); waitForSelectClose(); @@ -845,7 +861,15 @@ describe('', function() { } function selectedOptions(el) { - return angular.element(el[0].querySelectorAll('md-option[selected]')); + var res; + var querySelector = 'md-option[selected]'; + inject(function($document) { + res = angular.element($document[0].querySelectorAll(querySelector)); + if (!res.length) { + res = angular.element(el[0].querySelectorAll(querySelector)); + } + }); + return res; } function openSelect(el) { @@ -891,7 +915,7 @@ describe('', function() { function clickOption(select, index) { inject(function($rootScope, $document) { expectSelectOpen(select); - var openMenu = select.find('md-select-menu'); + var openMenu = $document.find('md-select-menu'); var opt = angular.element(openMenu.find('md-option')[index]).find('div')[0]; if (!opt) throw Error('Could not find option at index: ' + index); @@ -905,21 +929,23 @@ describe('', function() { } function expectSelectClosed(element) { - element = angular.element(element); - var menu = angular.element(element[0].querySelector('.md-select-menu-container')); - if (menu.length) { - if (menu.hasClass('md-active') || menu.attr('aria-hidden') == 'false') { - throw Error('Expected select to be closed'); + inject(function($document) { + var menu = angular.element($document[0].querySelector('.md-select-menu-container')); + if (menu.length) { + if (menu.hasClass('md-active') || menu.attr('aria-hidden') == 'false') { + throw Error('Expected select to be closed'); + } } - } + }); } function expectSelectOpen(element) { - element = angular.element(element); - var menu = angular.element(element[0].querySelector('.md-select-menu-container')); - if (!(menu.hasClass('md-active') && menu.attr('aria-hidden') == 'false')) { - throw Error('Expected select to be open'); - } + inject(function($document) { + var menu = angular.element($document[0].querySelector('.md-select-menu-container')); + if (!(menu.hasClass('md-active') && menu.attr('aria-hidden') == 'false')) { + throw Error('Expected select to be open'); + } + }); } });