Skip to content

Commit

Permalink
feat(dropdown): auto and array support in placement
Browse files Browse the repository at this point in the history
Closes #1171
Closes #1814
  • Loading branch information
mschoudry authored and pkozlowski-opensource committed Sep 5, 2017
1 parent b73023a commit 6123780
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 20 deletions.
3 changes: 2 additions & 1 deletion src/dropdown/dropdown-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {Injectable} from '@angular/core';
import {PlacementArray} from '../util/positioning';

/**
* Configuration service for the NgbDropdown directive.
Expand All @@ -8,5 +9,5 @@ import {Injectable} from '@angular/core';
@Injectable()
export class NgbDropdownConfig {
autoClose: boolean | 'outside' | 'inside' = true;
placement = 'bottom-left';
placement: PlacementArray = 'bottom-left';
}
46 changes: 39 additions & 7 deletions src/dropdown/dropdown.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,28 @@ describe('ngb-dropdown', () => {
});

it('should be closed and down by default', () => {
const html = `<div ngbDropdown><div ngbDropdownMenu></div></div>`;
const html = `
<div ngbDropdown>
<div ngbDropdownMenu>
<a class="dropdown-item">dropDown item</a>
<a class="dropdown-item">dropDown item</a>
</div>
</div>`;

const fixture = createTestComponent(html);
const compiled = fixture.nativeElement;

expect(getDropdownEl(compiled)).toHaveCssClass('dropdown');
expect(compiled).not.toBeShown();
});

it('should have dropup CSS class if placed on top', () => {
const html = `<div ngbDropdown placement="top"></div>`;
const html = `
<div ngbDropdown placement="top">
<div ngbDropdownMenu>
<a class="dropdown-item">dropDown item</a>
<a class="dropdown-item">dropDown item</a>
</div>
</div>`;

const fixture = createTestComponent(html);
const compiled = fixture.nativeElement;
Expand All @@ -73,12 +84,17 @@ describe('ngb-dropdown', () => {
});

it('should be open initially if open expression is true', () => {
const html = `<div ngbDropdown [open]="true"><div ngbDropdownMenu></div></div>`;
const html = `
<div ngbDropdown [open]="true">
<div ngbDropdownMenu>
<a class="dropdown-item">dropDown item</a>
<a class="dropdown-item">dropDown item</a>
</div>
</div>`;

const fixture = createTestComponent(html);
const compiled = fixture.nativeElement;

expect(getDropdownEl(compiled)).toHaveCssClass('dropdown');
expect(compiled).toBeShown();
});

Expand Down Expand Up @@ -479,7 +495,17 @@ describe('ngb-dropdown-toggle', () => {

beforeEach(() => {
TestBed.configureTestingModule({imports: [NgbDropdownModule.forRoot()]});
TestBed.overrideComponent(TestComponent, {set: {template: '<div ngbDropdown></div>'}});
TestBed.overrideComponent(TestComponent, {
set: {
template: `
<div ngbDropdown>
<div ngbDropdownMenu>
<a class="dropdown-item">dropDown item</a>
<a class="dropdown-item">dropDown item</a>
</div>
</div>`
}
});
});

beforeEach(inject([NgbDropdownConfig], (c: NgbDropdownConfig) => {
Expand Down Expand Up @@ -507,7 +533,13 @@ describe('ngb-dropdown-toggle', () => {
});

it('should initialize inputs with provided config as provider', () => {
const fixture = createTestComponent('<div ngbDropdown></div>');
const fixture = createTestComponent(`
<div ngbDropdown>
<div ngbDropdownMenu>
<a class="dropdown-item">dropup item</a>
<a class="dropdown-item">dropup item</a>
</div>
</div>`);
fixture.detectChanges();

const compiled = fixture.nativeElement;
Expand Down
48 changes: 36 additions & 12 deletions src/dropdown/dropdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,44 @@ import {
EventEmitter,
ElementRef,
ContentChild,
NgZone
NgZone,
Renderer2,
OnInit
} from '@angular/core';
import {NgbDropdownConfig} from './dropdown-config';
import {positionElements} from '../util/positioning';
import {positionElements, PlacementArray, Placement} from '../util/positioning';

/**
*/
@Directive(
{selector: '[ngbDropdownMenu]', host: {'[class.dropdown-menu]': 'true', '[class.show]': 'dropdown.isOpen()'}})
export class NgbDropdownMenu {
placement: Placement = 'bottom';
isOpen = false;

constructor(@Inject(forwardRef(() => NgbDropdown)) public dropdown, private _elementRef: ElementRef) {}
constructor(
@Inject(forwardRef(() => NgbDropdown)) public dropdown, private _elementRef: ElementRef,
private _renderer: Renderer2) {}

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

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

applyPlacement(_placement: Placement) {
// remove the current placement classes
this._renderer.removeClass(this._elementRef.nativeElement.parentElement, 'dropup');
this.placement = _placement;
/**
* apply the new placement
* change the class only in case of top to show up arrow
* or use defualt which is dropdown to show down arrow
*/
if (_placement.search('^top') !== -1) {
this._renderer.addClass(this._elementRef.nativeElement.parentElement, 'dropup');
}
}
}

/**
Expand Down Expand Up @@ -57,14 +78,12 @@ export class NgbDropdownToggle {
selector: '[ngbDropdown]',
exportAs: 'ngbDropdown',
host: {
'[class.dropdown]': 'isDown()',
'[class.dropup]': 'isUp()',
'[class.show]': 'isOpen()',
'(keyup.esc)': 'closeFromOutsideEsc()',
'(document:click)': 'closeFromClick($event)'
}
})
export class NgbDropdown {
export class NgbDropdown implements OnInit {
private _zoneSubscription: any;

@ContentChild(NgbDropdownMenu) private _menu: NgbDropdownMenu;
Expand All @@ -86,9 +105,12 @@ export class NgbDropdown {
@Input('open') _open = false;

/**
* Placement of a dropdown. Use "top-right" for dropups.
* Placement of a popover accepts:
* "top", "top-left", "top-right", "bottom", "bottom-left", "bottom-right",
* "left", "left-top", "left-bottom", "right", "right-top", "right-bottom"
* and array of above values.
*/
@Input() placement = '';
@Input() placement: PlacementArray;

/**
* An event fired when the dropdown is opened or closed.
Expand All @@ -102,9 +124,11 @@ export class NgbDropdown {
this._zoneSubscription = ngZone.onStable.subscribe(() => { this._positionMenu(); });
}

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

isDown() { return this.placement.indexOf('bottom') !== -1; }
ngOnInit() {
if (this._menu) {
this._menu.applyPlacement(Array.isArray(this.placement) ? (this.placement[0]) : this.placement as Placement);
}
}

/**
* Checks if the dropdown menu is open or not.
Expand Down

0 comments on commit 6123780

Please sign in to comment.