Skip to content

Commit

Permalink
feat(popover): allow templates as popover title
Browse files Browse the repository at this point in the history
Closes #1221
Closes #2621
  • Loading branch information
divdavem authored and pkozlowski-opensource committed Aug 17, 2018
1 parent 850b3ff commit 340c9b3
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<p>
Popovers can contain any arbitrary HTML, Angular bindings and even directives!
Simply enclose desired content in a <code>&lt;ng-template&gt;</code> element.
Simply enclose desired content or title in a <code>&lt;ng-template&gt;</code> element.
</p>

<ng-template #popContent>Hello, <b>{{name}}</b>!</ng-template>
<button type="button" class="btn btn-outline-secondary" [ngbPopover]="popContent" popoverTitle="Fancy content">
<ng-template #popTitle>Fancy <b>content!!</b></ng-template>
<button type="button" class="btn btn-outline-secondary" [ngbPopover]="popContent" [popoverTitle]="popTitle">
I've got markup and bindings in my popover!
</button>
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,25 @@
</p>

<ng-template #popContent let-greeting="greeting">{{greeting}}, <b>{{name}}</b>!</ng-template>
<ng-template #popTitle let-language="language">Greeting in {{language}}</ng-template>
<button
type="button" class="btn btn-outline-secondary mr-2"
[ngbPopover]="popContent" popoverTitle="Greeting"
triggers="manual" #p1="ngbPopover" (click)="toggleWithGreeting(p1, 'Bonjour')"
[ngbPopover]="popContent" [popoverTitle]="popTitle"
triggers="manual" #p1="ngbPopover" (click)="toggleWithGreeting(p1, 'Bonjour', 'French')"
>
French
</button>
<button
type="button" class="btn btn-outline-secondary mr-2"
[ngbPopover]="popContent" popoverTitle="Greeting"
triggers="manual" #p2="ngbPopover" (click)="toggleWithGreeting(p2, 'Gutentag')"
[ngbPopover]="popContent" [popoverTitle]="popTitle"
triggers="manual" #p2="ngbPopover" (click)="toggleWithGreeting(p2, 'Gutentag', 'German')"
>
German
</button>
<button
type="button" class="btn btn-outline-secondary mr-2"
[ngbPopover]="popContent" popoverTitle="Greeting"
triggers="manual" #p3="ngbPopover" (click)="toggleWithGreeting(p3, 'Hello')"
[ngbPopover]="popContent" [popoverTitle]="popTitle"
triggers="manual" #p3="ngbPopover" (click)="toggleWithGreeting(p3, 'Hello', 'English')"
>
English
</button>
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import {Component} from '@angular/core';
export class NgbdPopoverTplwithcontext {
name = 'World';

toggleWithGreeting(popover, greeting: string) {
toggleWithGreeting(popover, greeting: string, language: string) {
if (popover.isOpen()) {
popover.close();
} else {
popover.open({greeting});
popover.open({greeting, language});
}
}
}
8 changes: 7 additions & 1 deletion src/popover/popover.module.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import {NgModule, ModuleWithProviders} from '@angular/core';

import {NgbPopover, NgbPopoverWindow} from './popover';
import {CommonModule} from '@angular/common';

export {NgbPopover} from './popover';
export {NgbPopoverConfig} from './popover-config';
export {Placement} from '../util/positioning';

@NgModule({declarations: [NgbPopover, NgbPopoverWindow], exports: [NgbPopover], entryComponents: [NgbPopoverWindow]})
@NgModule({
declarations: [NgbPopover, NgbPopoverWindow],
exports: [NgbPopover],
imports: [CommonModule],
entryComponents: [NgbPopoverWindow]
})
export class NgbPopoverModule {
/**
* Importing with '.forRoot()' is no longer necessary, you can simply import the module.
Expand Down
38 changes: 37 additions & 1 deletion src/popover/popover.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {TestBed, ComponentFixture, inject, fakeAsync, tick} from '@angular/core/
import {createGenericTestComponent, createKeyEvent} from '../test/common';

import {By} from '@angular/platform-browser';
import {Component, ViewChild, ChangeDetectionStrategy, Injectable, OnDestroy} from '@angular/core';
import {Component, ViewChild, ChangeDetectionStrategy, Injectable, OnDestroy, TemplateRef} from '@angular/core';

import {Key} from '../util/key';

Expand Down Expand Up @@ -169,6 +169,42 @@ describe('ngb-popover', () => {
expect(directive.nativeElement.getAttribute('aria-describedby')).toBeNull();
});

it('should accept a template for the title and properly destroy it when closing', () => {
const fixture = createTestComponent(`
<ng-template #t>Hello, {{name}}! <destroyable-cmpt></destroyable-cmpt></ng-template>
<div ngbPopover="Body" [popoverTitle]="t"></div>`);
const directive = fixture.debugElement.query(By.directive(NgbPopover));
const spyService = fixture.debugElement.injector.get(SpyService);

directive.triggerEventHandler('click', {});
fixture.detectChanges();
const windowEl = getWindow(fixture.nativeElement);
expect(windowEl.textContent.trim()).toBe('Hello, World! Some contentBody');
expect(spyService.called).toBeFalsy();

directive.triggerEventHandler('click', {});
fixture.detectChanges();
expect(getWindow(fixture.nativeElement)).toBeNull();
expect(spyService.called).toBeTruthy();
});

it('should pass the context to the template for the title', () => {
const fixture = createTestComponent(`
<ng-template #t let-greeting="greeting">{{greeting}}, {{name}}!</ng-template>
<div ngbPopover="!!" [popoverTitle]="t"></div>`);
const directive = fixture.debugElement.query(By.directive(NgbPopover));

fixture.componentInstance.name = 'tout le monde';
fixture.componentInstance.popover.open({greeting: 'Bonjour'});
fixture.detectChanges();
const windowEl = getWindow(fixture.nativeElement);
expect(windowEl.textContent.trim()).toBe('Bonjour, tout le monde!!!');

directive.triggerEventHandler('click', {});
fixture.detectChanges();
expect(getWindow(fixture.nativeElement)).toBeNull();
});

it('should properly destroy TemplateRef content', () => {
const fixture = createTestComponent(`
<ng-template #t><destroyable-cmpt></destroyable-cmpt></ng-template>
Expand Down
14 changes: 11 additions & 3 deletions src/popover/popover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ let nextId = 0;
},
template: `
<div class="arrow"></div>
<h3 class="popover-header">{{title}}</h3><div class="popover-body"><ng-content></ng-content></div>`,
<h3 class="popover-header">
<ng-template #simpleTitle>{{title}}</ng-template>
<ng-template [ngTemplateOutlet]="isTitleTemplate() ? title : simpleTitle" [ngTemplateOutletContext]="context"></ng-template>
</h3>
<div class="popover-body"><ng-content></ng-content></div>`,
styles: [`
:host.bs-popover-top .arrow, :host.bs-popover-bottom .arrow {
left: 50%;
Expand Down Expand Up @@ -76,12 +80,15 @@ let nextId = 0;
})
export class NgbPopoverWindow {
@Input() placement: Placement = 'top';
@Input() title: string;
@Input() title: string | TemplateRef<any>;
@Input() id: string;
@Input() popoverClass: string;
@Input() context: any;

constructor(private _element: ElementRef<HTMLElement>, private _renderer: Renderer2) {}

isTitleTemplate() { return this.title instanceof TemplateRef; }

applyPlacement(_placement: Placement) {
// remove the current placement classes
this._renderer.removeClass(this._element.nativeElement, 'bs-popover-' + this.placement.toString().split('-')[0]);
Expand Down Expand Up @@ -129,7 +136,7 @@ export class NgbPopover implements OnInit, OnDestroy, OnChanges {
/**
* Title of a popover. If title and content are empty, the popover won't open.
*/
@Input() popoverTitle: string;
@Input() popoverTitle: string | TemplateRef<any>;
/**
* Placement of a popover accepts:
* "top", "top-left", "top-right", "bottom", "bottom-left", "bottom-right",
Expand Down Expand Up @@ -213,6 +220,7 @@ export class NgbPopover implements OnInit, OnDestroy, OnChanges {
if (!this._windowRef && !this._isDisabled()) {
this._windowRef = this._popupService.open(this.ngbPopover, context);
this._windowRef.instance.title = this.popoverTitle;
this._windowRef.instance.context = context;
this._windowRef.instance.popoverClass = this.popoverClass;
this._windowRef.instance.id = this._ngbPopoverWindowId;

Expand Down

0 comments on commit 340c9b3

Please sign in to comment.