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

Commit

Permalink
feat(popover): add custom template support
Browse files Browse the repository at this point in the history
- Adds custom template support for popovers

Closes #4056
Closes #4057
  • Loading branch information
jczerwinski authored and wesleycho committed Aug 1, 2015
1 parent 4f1e03f commit a9d3d25
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 1 deletion.
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>

0 comments on commit a9d3d25

Please sign in to comment.