Skip to content

Commit

Permalink
fix(dropdown): restore support for dropups
Browse files Browse the repository at this point in the history
Fixes #1747
Part of #1171
Part of #1012

BREAKING CHANGE:

The `up` input is no longer supported by you can use more flexible
`placement` setting now.

Before:

```html
<div ngbDropdown [up]="true">
```

After:

```html
<div ngbDropdown placement="top-right">
```

Closes #1752
  • Loading branch information
pkozlowski-opensource committed Aug 22, 2017
1 parent 73ba757 commit c7a16a5
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
</div>

<div class="col text-right">
<div ngbDropdown [up]="true" class="d-inline-block">
<div ngbDropdown placement="top-right" class="d-inline-block">
<button class="btn btn-outline-primary" id="dropdownBasic2" ngbDropdownToggle>Toggle dropup</button>
<div ngbDropdownMenu class="dropdown-menu-right" aria-labelledby="dropdownBasic2">
<div ngbDropdownMenu aria-labelledby="dropdownBasic2">
<button class="dropdown-item">Action - 1</button>
<button class="dropdown-item">Another Action</button>
<button class="dropdown-item">Something else is here</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {NgbDropdownConfig} from '@ng-bootstrap/ng-bootstrap';
export class NgbdDropdownConfig {
constructor(config: NgbDropdownConfig) {
// customize default values of dropdowns used by this component tree
config.up = true;
config.placement = 'top-left';
config.autoClose = false;
}
}
2 changes: 1 addition & 1 deletion src/dropdown/dropdown-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ describe('ngb-dropdown-config', () => {
it('should have sensible default values', () => {
const config = new NgbDropdownConfig();

expect(config.up).toBe(false);
expect(config.placement).toBe('bottom-left');
expect(config.autoClose).toBe(true);
});

Expand Down
4 changes: 2 additions & 2 deletions src/dropdown/dropdown-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Injectable, TemplateRef} from '@angular/core';
import {Injectable} from '@angular/core';

/**
* Configuration service for the NgbDropdown directive.
Expand All @@ -7,6 +7,6 @@ import {Injectable, TemplateRef} from '@angular/core';
*/
@Injectable()
export class NgbDropdownConfig {
up = false;
autoClose: boolean | 'outside' | 'inside' = true;
placement = 'bottom-left';
}
15 changes: 4 additions & 11 deletions src/dropdown/dropdown.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,6 @@ describe('ngb-dropdown', () => {
TestBed.configureTestingModule({declarations: [TestComponent], imports: [NgbDropdownModule.forRoot()]});
});

it('should initialize inputs with provided config', () => {
const defaultConfig = new NgbDropdownConfig();
const dropdown = new NgbDropdown(defaultConfig);
expect(dropdown.up).toBe(defaultConfig.up);
expect(dropdown.autoClose).toBe(defaultConfig.autoClose);
});

it('should be closed and down by default', () => {
const html = `<div ngbDropdown><div ngbDropdownMenu></div></div>`;

Expand All @@ -70,8 +63,8 @@ describe('ngb-dropdown', () => {
expect(compiled).not.toBeShown();
});

it('should be up if up input is true', () => {
const html = `<div ngbDropdown [up]="true"></div>`;
it('should have dropup CSS class if placed on top', () => {
const html = `<div ngbDropdown placement="top"></div>`;

const fixture = createTestComponent(html);
const compiled = fixture.nativeElement;
Expand Down Expand Up @@ -491,7 +484,7 @@ describe('ngb-dropdown-toggle', () => {

beforeEach(inject([NgbDropdownConfig], (c: NgbDropdownConfig) => {
config = c;
config.up = true;
config.placement = 'top-right';
}));

it('should initialize inputs with provided config', () => {
Expand All @@ -506,7 +499,7 @@ describe('ngb-dropdown-toggle', () => {

describe('Custom config as provider', () => {
let config = new NgbDropdownConfig();
config.up = true;
config.placement = 'top-right';

beforeEach(() => {
TestBed.configureTestingModule(
Expand Down
46 changes: 34 additions & 12 deletions src/dropdown/dropdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import {
EventEmitter,
ElementRef,
ContentChild,
OnChanges,
SimpleChanges
NgZone
} from '@angular/core';
import {NgbDropdownConfig} from './dropdown-config';
import {positionElements} from '../util/positioning';

/**
*/
Expand All @@ -22,6 +22,8 @@ export class NgbDropdownMenu {
constructor(@Inject(forwardRef(() => NgbDropdown)) public dropdown, private _elementRef: ElementRef) {}

isEventFrom($event) { return this._elementRef.nativeElement.contains($event.target); }

position(triggerEl, placement) { positionElements(triggerEl, this._elementRef.nativeElement, placement); }
}

/**
Expand All @@ -37,7 +39,11 @@ export class NgbDropdownMenu {
}
})
export class NgbDropdownToggle {
constructor(@Inject(forwardRef(() => NgbDropdown)) public dropdown, private _elementRef: ElementRef) {}
anchorEl;

constructor(@Inject(forwardRef(() => NgbDropdown)) public dropdown, private _elementRef: ElementRef) {
this.anchorEl = _elementRef.nativeElement;
}

toggleOpen() { this.dropdown.toggle(); }

Expand All @@ -51,23 +57,20 @@ export class NgbDropdownToggle {
selector: '[ngbDropdown]',
exportAs: 'ngbDropdown',
host: {
'[class.dropdown]': '!up',
'[class.dropup]': 'up',
'[class.dropdown]': 'isDown()',
'[class.dropup]': 'isUp()',
'[class.show]': 'isOpen()',
'(keyup.esc)': 'closeFromOutsideEsc()',
'(document:click)': 'closeFromClick($event)'
}
})
export class NgbDropdown {
private _zoneSubscription: any;

@ContentChild(NgbDropdownMenu) private _menu: NgbDropdownMenu;

@ContentChild(NgbDropdownToggle) private _toggle: NgbDropdownToggle;

/**
* Indicates that the dropdown should open upwards
*/
@Input() up: boolean;

/**
* Indicates that dropdown should be closed when selecting one of dropdown items (click) or pressing ESC.
* When it is true (default) dropdowns are automatically closed on both outside and inside (menu) clicks.
Expand All @@ -82,17 +85,27 @@ export class NgbDropdown {
*/
@Input('open') _open = false;

/**
* Placement of a dropdown. Use "top-right" for dropups.
*/
@Input() placement = '';

/**
* An event fired when the dropdown is opened or closed.
* Event's payload equals whether dropdown is open.
*/
@Output() openChange = new EventEmitter();

constructor(config: NgbDropdownConfig) {
this.up = config.up;
constructor(config: NgbDropdownConfig, ngZone: NgZone) {
this.placement = config.placement;
this.autoClose = config.autoClose;
this._zoneSubscription = ngZone.onStable.subscribe(() => { this._positionMenu(); });
}

isUp() { return this.placement.indexOf('top') !== -1; }

isDown() { return this.placement.indexOf('bottom') !== -1; }

/**
* Checks if the dropdown menu is open or not.
*/
Expand All @@ -104,6 +117,7 @@ export class NgbDropdown {
open(): void {
if (!this._open) {
this._open = true;
this._positionMenu();
this.openChange.emit(true);
}
}
Expand Down Expand Up @@ -147,7 +161,15 @@ export class NgbDropdown {
}
}

ngOnDestroy() { this._zoneSubscription.unsubscribe(); }

private _isEventFromToggle($event) { return this._toggle ? this._toggle.isEventFrom($event) : false; }

private _isEventFromMenu($event) { return this._menu ? this._menu.isEventFrom($event) : false; }

private _positionMenu() {
if (this.isOpen() && this._menu && this._toggle) {
this._menu.position(this._toggle.anchorEl, this.placement);
}
}
}

0 comments on commit c7a16a5

Please sign in to comment.