Skip to content
This repository was archived by the owner on May 29, 2019. It is now read-only.

feat(popover): add template expression support #4057

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/popover/docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ There are two versions of the popover: `popover` and `popover-template`:

- `popover` takes text only and will escape any HTML provided for the popover
body.
- `popover-html` takes an expression that evaluates to an html string. *The user is responsible for ensuring the
content is safe to put into the DOM!*
- `popover-template` takes text that specifies the location of a template to
use for the popover body.

Expand Down
17 changes: 16 additions & 1 deletion src/popover/popover.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* The following features are still outstanding: popup delay, animation as a
* function, placement as a function, inside, support for more triggers than
* just mouse enter/leave, html popovers, and selector delegatation.
* just mouse enter/leave, and selector delegatation.
*/
angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] )

Expand All @@ -21,6 +21,21 @@ angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] )
} );
}])

.directive( 'popoverHtmlPopup', function () {
return {
restrict: 'EA',
replace: true,
scope: { contentExp: '&', title: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
templateUrl: 'template/popover/popover-html.html'
};
})

.directive( 'popoverHtml', [ '$tooltip', function ( $tooltip ) {
return $tooltip( 'popoverHtml', 'popover', 'click', {
useContentExp: true
});
}])

.directive( 'popoverPopup', function () {
return {
restrict: 'EA',
Expand Down
185 changes: 185 additions & 0 deletions src/popover/test/popover-html.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
describe('popover', function() {
var elm,
elmBody,
scope,
elmScope,
tooltipScope;

// load the popover code
beforeEach(module('ui.bootstrap.popover'));

// load the template
beforeEach(module('template/popover/popover-html.html'));

beforeEach(inject(function($rootScope, $compile, $sce) {
elmBody = angular.element(
'<div><span popover-html="template">Selector Text</span></div>'
);

scope = $rootScope;
scope.template = $sce.trustAsHtml('<span>My template</span>');
$compile(elmBody)(scope);
scope.$digest();
elm = elmBody.find('span');
elmScope = elm.scope();
tooltipScope = elmScope.$$childTail;
}));

it('should not be open initially', inject(function() {
expect( tooltipScope.isOpen ).toBe( false );

// We can only test *that* the popover-popup element wasn't created as the
// implementation is templated and replaced.
expect( elmBody.children().length ).toBe( 1 );
}));

it('should open on click', inject(function() {
elm.trigger( 'click' );
expect( tooltipScope.isOpen ).toBe( true );

// We can only test *that* the popover-popup element was created as the
// implementation is templated and replaced.
expect( elmBody.children().length ).toBe( 2 );
}));

it('should close on second click', inject(function() {
elm.trigger( 'click' );
elm.trigger( 'click' );
expect( tooltipScope.isOpen ).toBe( false );
}));

it('should not open on click if template is empty', inject(function() {
scope.template = null;
scope.$digest();

elm.trigger( 'click' );
expect( tooltipScope.isOpen ).toBe( false );

expect( elmBody.children().length ).toBe( 1 );
}));

it('should show updated text', inject(function($sce) {
scope.template = $sce.trustAsHtml('<span>My template</span>');
scope.$digest();

elm.trigger( 'click' );
expect( tooltipScope.isOpen ).toBe( true );

expect( elmBody.children().eq(1).text().trim() ).toBe( 'My template' );

scope.template = $sce.trustAsHtml('<span>Another template</span>');
scope.$digest();

expect( elmBody.children().eq(1).text().trim() ).toBe( 'Another template' );
}));

it('should hide popover when template becomes empty', inject(function ($timeout) {
elm.trigger( 'click' );
expect( tooltipScope.isOpen ).toBe( true );

scope.template = '';
scope.$digest();

expect( tooltipScope.isOpen ).toBe( false );

$timeout.flush();
expect( elmBody.children().length ).toBe( 1 );
}));


it('should not unbind event handlers created by other directives - issue 456', inject( function( $compile ) {

scope.click = function() {
scope.clicked = !scope.clicked;
};

elmBody = angular.element(
'<div><input popover-html="template" ng-click="click()" popover-trigger="mouseenter"/></div>'
);
$compile(elmBody)(scope);
scope.$digest();

elm = elmBody.find('input');

elm.trigger( 'mouseenter' );
elm.trigger( 'mouseleave' );
expect(scope.clicked).toBeFalsy();

elm.click();
expect(scope.clicked).toBeTruthy();
}));

it('should popup with animate class by default', inject(function() {
elm.trigger( 'click' );
expect( tooltipScope.isOpen ).toBe( true );

expect(elmBody.children().eq(1)).toHaveClass('fade');
}));

it('should popup without animate class when animation disabled', inject(function($compile) {
elmBody = angular.element(
'<div><span popover-html="template" popover-animation="false">Selector Text</span></div>'
);

$compile(elmBody)(scope);
scope.$digest();
elm = elmBody.find('span');
elmScope = elm.scope();
tooltipScope = elmScope.$$childTail;

elm.trigger( 'click' );
expect( tooltipScope.isOpen ).toBe( true );
expect(elmBody.children().eq(1)).not.toHaveClass('fade');
}));

describe('supports options', function () {

describe('placement', function () {

it('can specify an alternative, valid placement', inject(function ($compile) {
elmBody = angular.element(
'<div><span popover-html="template" popover-placement="left">Trigger here</span></div>'
);
$compile(elmBody)(scope);
scope.$digest();
elm = elmBody.find('span');
elmScope = elm.scope();
tooltipScope = elmScope.$$childTail;

elm.trigger( 'click' );
expect( tooltipScope.isOpen ).toBe( true );

expect( elmBody.children().length ).toBe( 2 );
var ttipElement = elmBody.find('div.popover');
expect(ttipElement).toHaveClass('left');
}));

});

describe('class', function () {

it('can specify a custom class', inject(function ($compile) {
elmBody = angular.element(
'<div><span popover-html="template" popover-class="custom">Trigger here</span></div>'
);
$compile(elmBody)(scope);
scope.$digest();
elm = elmBody.find('span');
elmScope = elm.scope();
tooltipScope = elmScope.$$childTail;

elm.trigger( 'click' );
expect( tooltipScope.isOpen ).toBe( true );

expect( elmBody.children().length ).toBe( 2 );
var ttipElement = elmBody.find('div.popover');
expect(ttipElement).toHaveClass('custom');
}));

});

});

});


11 changes: 11 additions & 0 deletions template/popover/popover-html.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div class="popover"
tooltip-animation-class="fade"
tooltip-classes
ng-class="{ in: isOpen() }">
<div class="arrow"></div>

<div class="popover-inner">
<h3 class="popover-title" ng-bind="title" ng-if="title"></h3>
<div class="popover-content" ng-bind-html="contentExp()"></div>
</div>
</div>