Skip to content

Commit

Permalink
Fixed issue when dropdown does not include menu (blackbaud#1470)
Browse files Browse the repository at this point in the history
  • Loading branch information
Blackbaud-SteveBrush authored Feb 15, 2018
1 parent e81840a commit 1625b1a
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 58 deletions.
55 changes: 53 additions & 2 deletions src/modules/dropdown/dropdown-menu.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,20 @@ import {
HostListener,
Input,
OnDestroy,
Optional,
Output,
QueryList
} from '@angular/core';

import { Subject } from 'rxjs/Subject';

import { SkyDropdownComponent } from './dropdown.component';
import { SkyDropdownItemComponent } from './dropdown-item.component';

import {
SkyDropdownMenuChange
SkyDropdownMenuChange,
SkyDropdownMessage,
SkyDropdownMessageType
} from './types';

@Component({
Expand Down Expand Up @@ -61,10 +65,57 @@ export class SkyDropdownMenuComponent implements AfterContentInit, OnDestroy {
private _menuIndex = 0;

constructor(
private changeDetector: ChangeDetectorRef
private changeDetector: ChangeDetectorRef,
@Optional() private dropdownComponent: SkyDropdownComponent
) { }

public ngAfterContentInit() {
/* istanbul ignore else */
if (this.dropdownComponent) {
this.dropdownComponent.messageStream
.takeUntil(this.destroy)
.subscribe((message: SkyDropdownMessage) => {
/* tslint:disable-next-line:switch-default */
switch (message.type) {
case SkyDropdownMessageType.Open:
case SkyDropdownMessageType.Close:
this.reset();
break;

case SkyDropdownMessageType.FocusFirstItem:
this.focusFirstItem();
break;

case SkyDropdownMessageType.FocusNextItem:
this.focusNextItem();
break;

case SkyDropdownMessageType.FocusPreviousItem:
this.focusPreviousItem();
break;
}
});

this.menuChanges
.takeUntil(this.destroy)
.subscribe((change: SkyDropdownMenuChange) => {
// Close the dropdown when a menu item is selected.
if (change.selectedItem) {
this.dropdownComponent.messageStream.next({
type: SkyDropdownMessageType.Close
});
}

if (change.items) {
// Update the popover style and position whenever the number of
// items changes.
this.dropdownComponent.messageStream.next({
type: SkyDropdownMessageType.Reposition
});
}
});
}

// Reset dropdown whenever the menu items change.
this.menuItems.changes
.takeUntil(this.destroy)
Expand Down
16 changes: 8 additions & 8 deletions src/modules/dropdown/dropdown.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('Dropdown component', () => {
}

function verifyActiveMenuItemByIndex(index: number) {
const menuItems = component.dropdown['menuComponent'].menuItems.toArray();
const menuItems = component.dropdownMenu.menuItems.toArray();

menuItems.forEach((item: any, i: number) => {
if (i === index) {
Expand Down Expand Up @@ -310,7 +310,7 @@ describe('Dropdown component', () => {
fixture.detectChanges();

const buttonElem = getDropdownButtonElement();
const spy = spyOn(component.dropdown['menuComponent'], 'focusFirstItem').and.callThrough();
const spy = spyOn(component.dropdownMenu, 'focusFirstItem').and.callThrough();

verifyMenuVisibility(false);

Expand Down Expand Up @@ -338,8 +338,8 @@ describe('Dropdown component', () => {

verifyMenuVisibility(false);

const firstSpy = spyOn(component.dropdown['menuComponent']['menuItems'].first, 'focusElement').and.callThrough();
const lastSpy = spyOn(component.dropdown['menuComponent']['menuItems'].last, 'focusElement').and.callThrough();
const firstSpy = spyOn(component.dropdownMenu.menuItems.first, 'focusElement').and.callThrough();
const lastSpy = spyOn(component.dropdownMenu.menuItems.last, 'focusElement').and.callThrough();

dispatchKeyboardButtonClickEvent(buttonElem);
tick();
Expand All @@ -361,7 +361,7 @@ describe('Dropdown component', () => {

verifyMenuVisibility(false);

const firstSpy = spyOn(component.dropdown['menuComponent']['menuItems'].first, 'focusElement').and.callThrough();
const firstSpy = spyOn(component.dropdownMenu.menuItems.first, 'focusElement').and.callThrough();

dispatchKeyboardButtonClickEvent(buttonElem);
tick();
Expand All @@ -377,7 +377,7 @@ describe('Dropdown component', () => {
{ name: 'Foo', disabled: false }
]);

const menuItemComponent = component.dropdown['menuComponent']['menuItems'].first;
const menuItemComponent = component.dropdownMenu.menuItems.first;
spyOn(menuItemComponent.elementRef.nativeElement, 'querySelector').and.returnValue(undefined);

fixture.detectChanges();
Expand Down Expand Up @@ -435,7 +435,7 @@ describe('Dropdown component', () => {
fixture.detectChanges();

const buttonElem = getDropdownButtonElement();
component.dropdown['menuComponent'].useNativeFocus = false;
component.dropdownMenu.useNativeFocus = false;
fixture.detectChanges();

verifyMenuVisibility(false);
Expand All @@ -447,7 +447,7 @@ describe('Dropdown component', () => {

verifyMenuVisibility();

const itemComponent = component.dropdown['menuComponent']['menuItems'].first;
const itemComponent = component.dropdownMenu.menuItems.first;
const focusSpy = spyOn(itemComponent['buttonElement'], 'focus');

TestUtility.fireKeyboardEvent(buttonElem, 'keydown', { key: 'arrowdown' });
Expand Down
50 changes: 6 additions & 44 deletions src/modules/dropdown/dropdown.component.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import {
AfterContentInit,
ChangeDetectionStrategy,
Component,
ContentChild,
ElementRef,
HostListener,
Input,
Expand All @@ -27,10 +25,7 @@ import {
SkyWindowRefService
} from '../window';

import { SkyDropdownMenuComponent } from './dropdown-menu.component';

import {
SkyDropdownMenuChange,
SkyDropdownMessage,
SkyDropdownMessageType,
SkyDropdownTriggerType
Expand All @@ -42,7 +37,7 @@ import {
styleUrls: ['./dropdown.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SkyDropdownComponent implements OnInit, AfterContentInit, OnDestroy {
export class SkyDropdownComponent implements OnInit, OnDestroy {
@Input()
public alignment: SkyPopoverAlignment = 'left';

Expand Down Expand Up @@ -97,9 +92,6 @@ export class SkyDropdownComponent implements OnInit, AfterContentInit, OnDestroy
@ViewChild(SkyPopoverComponent)
private popover: SkyPopoverComponent;

@ContentChild(SkyDropdownMenuComponent)
private menuComponent: SkyDropdownMenuComponent;

private destroy = new Subject<boolean>();
private isKeyboardActive = false;
private isOpen = false;
Expand All @@ -121,23 +113,6 @@ export class SkyDropdownComponent implements OnInit, AfterContentInit, OnDestroy
});
}

public ngAfterContentInit() {
this.menuComponent.menuChanges
.takeUntil(this.destroy)
.subscribe((change: SkyDropdownMenuChange) => {
// Close the dropdown when a menu item is selected.
if (change.selectedItem) {
this.sendMessage(SkyDropdownMessageType.Close);
}

if (change.items) {
// Update the popover style and position whenever the number of
// items changes.
this.sendMessage(SkyDropdownMessageType.Reposition);
}
});
}

public ngOnDestroy() {
this.destroy.next(true);
this.destroy.unsubscribe();
Expand All @@ -163,7 +138,7 @@ export class SkyDropdownComponent implements OnInit, AfterContentInit, OnDestroy
case 'arrowdown':
if (!this.isKeyboardActive) {
this.isKeyboardActive = true;
this.menuComponent.focusFirstItem();
this.sendMessage(SkyDropdownMessageType.FocusFirstItem);
event.preventDefault();
}
break;
Expand Down Expand Up @@ -208,36 +183,23 @@ export class SkyDropdownComponent implements OnInit, AfterContentInit, OnDestroy
/* tslint:disable-next-line:switch-default */
switch (message.type) {
case SkyDropdownMessageType.Open:
this.menuComponent.reset();
this.positionPopover();
break;

case SkyDropdownMessageType.Close:
this.popover.close();
break;

case SkyDropdownMessageType.FocusTriggerButton:
this.triggerButton.nativeElement.focus();
break;

case SkyDropdownMessageType.FocusFirstItem:
this.menuComponent.focusFirstItem();
break;

case SkyDropdownMessageType.FocusNextItem:
this.menuComponent.focusNextItem();
break;

case SkyDropdownMessageType.FocusPreviousItem:
this.menuComponent.focusPreviousItem();
break;

case SkyDropdownMessageType.Reposition:
// Only reposition the dropdown if it is already open.
if (this.isOpen) {
this.popover.reposition();
}
break;

case SkyDropdownMessageType.FocusTriggerButton:
this.triggerButton.nativeElement.focus();
break;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<sky-dropdown-button>
Show dropdown
</sky-dropdown-button>
<sky-dropdown-menu>
<sky-dropdown-menu #dropdownMenu>
<sky-dropdown-item *ngFor="let item of items">
<button type="button" [attr.disabled]="item.disabled ? '' : null">
{{ item.name }}
Expand Down
8 changes: 5 additions & 3 deletions src/modules/dropdown/fixtures/dropdown.component.fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import {
SkyDropdownMessage
} from '../types';

import {
SkyDropdownComponent
} from '../dropdown.component';
import { SkyDropdownComponent } from '../dropdown.component';
import { SkyDropdownMenuComponent } from '../dropdown-menu.component';

@Component({
selector: 'sky-test-cmp',
Expand All @@ -39,6 +38,9 @@ export class DropdownTestComponent {
@ViewChild('dropdown')
public dropdown: SkyDropdownComponent;

@ViewChild('dropdownMenu')
public dropdownMenu: SkyDropdownMenuComponent;

@ViewChild('outsideButton')
public outsideButton: ElementRef;

Expand Down

0 comments on commit 1625b1a

Please sign in to comment.