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

Commit a9d3d25

Browse files
jczerwinskiwesleycho
authored andcommitted
feat(popover): add custom template support
- Adds custom template support for popovers Closes #4056 Closes #4057
1 parent 4f1e03f commit a9d3d25

File tree

4 files changed

+214
-1
lines changed

4 files changed

+214
-1
lines changed

Diff for: src/popover/docs/readme.md

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ There are two versions of the popover: `popover` and `popover-template`:
88

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

Diff for: src/popover/popover.js

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* The following features are still outstanding: popup delay, animation as a
33
* function, placement as a function, inside, support for more triggers than
4-
* just mouse enter/leave, html popovers, and selector delegatation.
4+
* just mouse enter/leave, and selector delegatation.
55
*/
66
angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] )
77

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

24+
.directive( 'popoverHtmlPopup', function () {
25+
return {
26+
restrict: 'EA',
27+
replace: true,
28+
scope: { contentExp: '&', title: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
29+
templateUrl: 'template/popover/popover-html.html'
30+
};
31+
})
32+
33+
.directive( 'popoverHtml', [ '$tooltip', function ( $tooltip ) {
34+
return $tooltip( 'popoverHtml', 'popover', 'click', {
35+
useContentExp: true
36+
});
37+
}])
38+
2439
.directive( 'popoverPopup', function () {
2540
return {
2641
restrict: 'EA',

Diff for: src/popover/test/popover-html.spec.js

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
describe('popover', function() {
2+
var elm,
3+
elmBody,
4+
scope,
5+
elmScope,
6+
tooltipScope;
7+
8+
// load the popover code
9+
beforeEach(module('ui.bootstrap.popover'));
10+
11+
// load the template
12+
beforeEach(module('template/popover/popover-html.html'));
13+
14+
beforeEach(inject(function($rootScope, $compile, $sce) {
15+
elmBody = angular.element(
16+
'<div><span popover-html="template">Selector Text</span></div>'
17+
);
18+
19+
scope = $rootScope;
20+
scope.template = $sce.trustAsHtml('<span>My template</span>');
21+
$compile(elmBody)(scope);
22+
scope.$digest();
23+
elm = elmBody.find('span');
24+
elmScope = elm.scope();
25+
tooltipScope = elmScope.$$childTail;
26+
}));
27+
28+
it('should not be open initially', inject(function() {
29+
expect( tooltipScope.isOpen ).toBe( false );
30+
31+
// We can only test *that* the popover-popup element wasn't created as the
32+
// implementation is templated and replaced.
33+
expect( elmBody.children().length ).toBe( 1 );
34+
}));
35+
36+
it('should open on click', inject(function() {
37+
elm.trigger( 'click' );
38+
expect( tooltipScope.isOpen ).toBe( true );
39+
40+
// We can only test *that* the popover-popup element was created as the
41+
// implementation is templated and replaced.
42+
expect( elmBody.children().length ).toBe( 2 );
43+
}));
44+
45+
it('should close on second click', inject(function() {
46+
elm.trigger( 'click' );
47+
elm.trigger( 'click' );
48+
expect( tooltipScope.isOpen ).toBe( false );
49+
}));
50+
51+
it('should not open on click if template is empty', inject(function() {
52+
scope.template = null;
53+
scope.$digest();
54+
55+
elm.trigger( 'click' );
56+
expect( tooltipScope.isOpen ).toBe( false );
57+
58+
expect( elmBody.children().length ).toBe( 1 );
59+
}));
60+
61+
it('should show updated text', inject(function($sce) {
62+
scope.template = $sce.trustAsHtml('<span>My template</span>');
63+
scope.$digest();
64+
65+
elm.trigger( 'click' );
66+
expect( tooltipScope.isOpen ).toBe( true );
67+
68+
expect( elmBody.children().eq(1).text().trim() ).toBe( 'My template' );
69+
70+
scope.template = $sce.trustAsHtml('<span>Another template</span>');
71+
scope.$digest();
72+
73+
expect( elmBody.children().eq(1).text().trim() ).toBe( 'Another template' );
74+
}));
75+
76+
it('should hide popover when template becomes empty', inject(function ($timeout) {
77+
elm.trigger( 'click' );
78+
expect( tooltipScope.isOpen ).toBe( true );
79+
80+
scope.template = '';
81+
scope.$digest();
82+
83+
expect( tooltipScope.isOpen ).toBe( false );
84+
85+
$timeout.flush();
86+
expect( elmBody.children().length ).toBe( 1 );
87+
}));
88+
89+
90+
it('should not unbind event handlers created by other directives - issue 456', inject( function( $compile ) {
91+
92+
scope.click = function() {
93+
scope.clicked = !scope.clicked;
94+
};
95+
96+
elmBody = angular.element(
97+
'<div><input popover-html="template" ng-click="click()" popover-trigger="mouseenter"/></div>'
98+
);
99+
$compile(elmBody)(scope);
100+
scope.$digest();
101+
102+
elm = elmBody.find('input');
103+
104+
elm.trigger( 'mouseenter' );
105+
elm.trigger( 'mouseleave' );
106+
expect(scope.clicked).toBeFalsy();
107+
108+
elm.click();
109+
expect(scope.clicked).toBeTruthy();
110+
}));
111+
112+
it('should popup with animate class by default', inject(function() {
113+
elm.trigger( 'click' );
114+
expect( tooltipScope.isOpen ).toBe( true );
115+
116+
expect(elmBody.children().eq(1)).toHaveClass('fade');
117+
}));
118+
119+
it('should popup without animate class when animation disabled', inject(function($compile) {
120+
elmBody = angular.element(
121+
'<div><span popover-html="template" popover-animation="false">Selector Text</span></div>'
122+
);
123+
124+
$compile(elmBody)(scope);
125+
scope.$digest();
126+
elm = elmBody.find('span');
127+
elmScope = elm.scope();
128+
tooltipScope = elmScope.$$childTail;
129+
130+
elm.trigger( 'click' );
131+
expect( tooltipScope.isOpen ).toBe( true );
132+
expect(elmBody.children().eq(1)).not.toHaveClass('fade');
133+
}));
134+
135+
describe('supports options', function () {
136+
137+
describe('placement', function () {
138+
139+
it('can specify an alternative, valid placement', inject(function ($compile) {
140+
elmBody = angular.element(
141+
'<div><span popover-html="template" popover-placement="left">Trigger here</span></div>'
142+
);
143+
$compile(elmBody)(scope);
144+
scope.$digest();
145+
elm = elmBody.find('span');
146+
elmScope = elm.scope();
147+
tooltipScope = elmScope.$$childTail;
148+
149+
elm.trigger( 'click' );
150+
expect( tooltipScope.isOpen ).toBe( true );
151+
152+
expect( elmBody.children().length ).toBe( 2 );
153+
var ttipElement = elmBody.find('div.popover');
154+
expect(ttipElement).toHaveClass('left');
155+
}));
156+
157+
});
158+
159+
describe('class', function () {
160+
161+
it('can specify a custom class', inject(function ($compile) {
162+
elmBody = angular.element(
163+
'<div><span popover-html="template" popover-class="custom">Trigger here</span></div>'
164+
);
165+
$compile(elmBody)(scope);
166+
scope.$digest();
167+
elm = elmBody.find('span');
168+
elmScope = elm.scope();
169+
tooltipScope = elmScope.$$childTail;
170+
171+
elm.trigger( 'click' );
172+
expect( tooltipScope.isOpen ).toBe( true );
173+
174+
expect( elmBody.children().length ).toBe( 2 );
175+
var ttipElement = elmBody.find('div.popover');
176+
expect(ttipElement).toHaveClass('custom');
177+
}));
178+
179+
});
180+
181+
});
182+
183+
});
184+
185+

Diff for: template/popover/popover-html.html

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<div class="popover"
2+
tooltip-animation-class="fade"
3+
tooltip-classes
4+
ng-class="{ in: isOpen() }">
5+
<div class="arrow"></div>
6+
7+
<div class="popover-inner">
8+
<h3 class="popover-title" ng-bind="title" ng-if="title"></h3>
9+
<div class="popover-content" ng-bind-html="contentExp()"></div>
10+
</div>
11+
</div>

0 commit comments

Comments
 (0)