From 701458f542e3d1df3f9e993ac759034c006bc93f Mon Sep 17 00:00:00 2001 From: Ryan Schmukler Date: Mon, 7 Dec 2015 15:37:29 -0800 Subject: [PATCH] amend(select): keep select in DOM but move to body on open references #6071, references #6030 --- src/components/select/select.js | 15 ++++--- src/components/select/select.spec.js | 67 +++++++++++++++++----------- 2 files changed, 52 insertions(+), 30 deletions(-) diff --git a/src/components/select/select.js b/src/components/select/select.js index 05bf3090ab2..ad74ab62f1e 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() { @@ -442,7 +447,7 @@ function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $compile, $par } } - function openSelect(e) { + function openSelect() { selectScope.isOpen = true; element.attr('aria-expanded', 'true'); @@ -452,8 +457,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 +1252,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 +1267,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 +1375,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..e17a94cf8ca 100755 --- a/src/components/select/select.spec.js +++ b/src/components/select/select.spec.js @@ -1,4 +1,4 @@ -describe('', function() { +ddescribe('', function() { var attachedElements = [], body; @@ -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() { @@ -402,7 +410,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 +765,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 +805,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 +852,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 +906,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 +920,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'); + } + }); } });