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

Commit 7e3179a

Browse files
committed
feat(popover): add popover-template directive
1 parent bf4b5ec commit 7e3179a

File tree

7 files changed

+193
-8
lines changed

7 files changed

+193
-8
lines changed

src/popover/docs/demo.html

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,27 @@
22
<h4>Dynamic</h4>
33
<div class="form-group">
44
<label>Popup Text:</label>
5-
<input type="text" ng-model="dynamicPopover" class="form-control">
5+
<input type="text" ng-model="dynamicPopover.content" class="form-control">
66
</div>
77
<div class="form-group">
88
<label>Popup Title:</label>
9-
<input type="text" ng-model="dynamicPopoverTitle" class="form-control">
9+
<input type="text" ng-model="dynamicPopover.title" class="form-control">
1010
</div>
11-
<button popover="{{dynamicPopover}}" popover-title="{{dynamicPopoverTitle}}" class="btn btn-default">Dynamic Popover</button>
12-
11+
<div class="form-group">
12+
<label>Popup Template:</label>
13+
<input type="text" ng-model="dynamicPopover.templateUrl" class="form-control">
14+
</div>
15+
<button popover="{{dynamicPopover.content}}" popover-title="{{dynamicPopover.title}}" class="btn btn-default">Dynamic Popover</button>
16+
17+
<button popover-template="{{dynamicPopover.templateUrl}}" popover-template-title="{{dynamicPopover.title}}" class="btn btn-default">Popover With Template</button>
18+
19+
<script type="text/ng-template" id="myPopoverTemplate.html">
20+
<div>{{dynamicPopover.content}}</div>
21+
<div class="form-group">
22+
<label>Popup Title:</label>
23+
<input type="text" ng-model="dynamicPopover.title" class="form-control">
24+
</div>
25+
</script>
1326
<hr />
1427
<h4>Positional</h4>
1528
<button popover-placement="top" popover="On the Top!" class="btn btn-default">Top</button>

src/popover/docs/demo.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
angular.module('ui.bootstrap.demo').controller('PopoverDemoCtrl', function ($scope) {
2-
$scope.dynamicPopover = 'Hello, World!';
3-
$scope.dynamicPopoverTitle = 'Title';
2+
$scope.dynamicPopover = {
3+
content: 'Hello, World!',
4+
templateUrl: 'myTemplatePopover.html',
5+
title: 'Title'
6+
};
47
});

src/popover/docs/readme.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ directive supports multiple placements, optional transition animation, and more.
44
Like the Bootstrap jQuery plugin, the popover **requires** the tooltip
55
module.
66

7+
There are two versions of the popover: `popover` and `popover-template`:
8+
9+
- `popover` takes text only and will escape any HTML provided for the popover
10+
body.
11+
- `popover-template` takes text that specifies the location of a template to
12+
use for the popover body.
13+
714
The popover directives provides several optional attributes to control how it
815
will display:
916

src/popover/popover.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@
55
*/
66
angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] )
77

8+
.directive( 'popoverTemplatePopup', function () {
9+
return {
10+
restrict: 'EA',
11+
replace: true,
12+
scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&',
13+
originScope: '&' },
14+
templateUrl: 'template/popover/popover-template.html'
15+
};
16+
})
17+
18+
.directive( 'popoverTemplate', [ '$tooltip', function ( $tooltip ) {
19+
return $tooltip( 'popoverTemplate', 'popoverTemplate', 'click' );
20+
}])
21+
822
.directive( 'popoverPopup', function () {
923
return {
1024
restrict: 'EA',
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
describe('popover template', 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'));
13+
beforeEach(module('template/popover/popover-template.html'));
14+
15+
beforeEach(inject(function ($templateCache) {
16+
$templateCache.put('myUrl', [200, '<span>{{ myTemplateText }}</span>', {}]);
17+
}));
18+
19+
beforeEach(inject(function($rootScope, $compile) {
20+
elmBody = angular.element(
21+
'<div><span popover-template="{{ templateUrl }}">Selector Text</span></div>'
22+
);
23+
24+
scope = $rootScope;
25+
$compile(elmBody)(scope);
26+
scope.templateUrl = 'myUrl';
27+
28+
scope.$digest();
29+
elm = elmBody.find('span');
30+
elmScope = elm.scope();
31+
tooltipScope = elmScope.$$childTail;
32+
}));
33+
34+
it('should open on click', inject(function() {
35+
elm.trigger( 'click' );
36+
expect( tooltipScope.isOpen ).toBe( true );
37+
38+
expect( elmBody.children().length ).toBe( 2 );
39+
}));
40+
41+
it('should not open on click if templateUrl is empty', inject(function() {
42+
scope.templateUrl = null;
43+
scope.$digest();
44+
45+
elm.trigger( 'click' );
46+
expect( tooltipScope.isOpen ).toBe( false );
47+
48+
expect( elmBody.children().length ).toBe( 1 );
49+
}));
50+
51+
it('should show updated text', inject(function() {
52+
scope.myTemplateText = 'some text';
53+
scope.$digest();
54+
55+
elm.trigger( 'click' );
56+
expect( tooltipScope.isOpen ).toBe( true );
57+
58+
expect( elmBody.children().eq(1).text().trim() ).toBe( 'some text' );
59+
60+
scope.myTemplateText = 'new text';
61+
scope.$digest();
62+
63+
expect( elmBody.children().eq(1).text().trim() ).toBe( 'new text' );
64+
}));
65+
});
66+

src/tooltip/tooltip.js

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
103103
'class="'+startSym+'class'+endSym+'" '+
104104
'animation="animation" '+
105105
'is-open="isOpen"'+
106+
'origin-scope="origScope" '+
106107
'>'+
107108
'</div>';
108109

@@ -111,7 +112,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
111112
compile: function (tElem, tAttrs) {
112113
var tooltipLinker = $compile( template );
113114

114-
return function link ( scope, element, attrs ) {
115+
return function link ( scope, element, attrs, tooltipCtrl ) {
115116
var tooltip;
116117
var tooltipLinkedScope;
117118
var transitionTimeout;
@@ -132,6 +133,9 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
132133
tooltip.css( ttPosition );
133134
};
134135

136+
// Set up the correct scope to allow transclusion later
137+
ttScope.origScope = scope;
138+
135139
// By default, the tooltip is not open.
136140
// TODO add ability to start tooltip opened
137141
ttScope.isOpen = false;
@@ -197,7 +201,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
197201

198202
// And show the tooltip.
199203
ttScope.isOpen = true;
200-
ttScope.$digest(); // digest required as $apply is not called
204+
ttScope.$apply(); // digest required as $apply is not called
201205

202206
// Return positioning function as promise callback for correct
203207
// positioning after draw.
@@ -349,6 +353,74 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
349353
}];
350354
})
351355

356+
// This is mostly ngInclude code but with a custom scope
357+
.directive( 'tooltipTemplateTransclude', [
358+
'$animate', '$sce', '$compile', '$templateRequest',
359+
function ($animate , $sce , $compile , $templateRequest) {
360+
return {
361+
link: function ( scope, elem, attrs ) {
362+
var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);
363+
364+
var changeCounter = 0,
365+
currentScope,
366+
previousElement,
367+
currentElement;
368+
369+
var cleanupLastIncludeContent = function() {
370+
if (previousElement) {
371+
previousElement.remove();
372+
previousElement = null;
373+
}
374+
if (currentScope) {
375+
currentScope.$destroy();
376+
currentScope = null;
377+
}
378+
if (currentElement) {
379+
$animate.leave(currentElement).then(function() {
380+
previousElement = null;
381+
});
382+
previousElement = currentElement;
383+
currentElement = null;
384+
}
385+
};
386+
387+
scope.$watch($sce.parseAsResourceUrl(attrs.tooltipTemplateTransclude), function (src) {
388+
var thisChangeId = ++changeCounter;
389+
390+
if (src) {
391+
//set the 2nd param to true to ignore the template request error so that the inner
392+
//contents and scope can be cleaned up.
393+
$templateRequest(src, true).then(function(response) {
394+
if (thisChangeId !== changeCounter) { return; }
395+
var newScope = origScope.$new();
396+
var template = response;
397+
398+
var clone = $compile(template)(newScope, function(clone) {
399+
cleanupLastIncludeContent();
400+
$animate.enter(clone, elem);
401+
});
402+
403+
currentScope = newScope;
404+
currentElement = clone;
405+
406+
currentScope.$emit('$includeContentLoaded', src);
407+
}, function() {
408+
if (thisChangeId === changeCounter) {
409+
cleanupLastIncludeContent();
410+
scope.$emit('$includeContentError', src);
411+
}
412+
});
413+
scope.$emit('$includeContentRequested', src);
414+
} else {
415+
cleanupLastIncludeContent();
416+
}
417+
});
418+
419+
scope.$on('$destroy', cleanupLastIncludeContent);
420+
}
421+
};
422+
}])
423+
352424
.directive( 'tooltipPopup', function () {
353425
return {
354426
restrict: 'EA',
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<div class="popover {{placement}}" ng-class="{ in: isOpen(), fade: animation() }">
2+
<div class="arrow"></div>
3+
4+
<div class="popover-inner">
5+
<h3 class="popover-title" ng-bind="title" ng-show="title"></h3>
6+
<div class="popover-content"
7+
tooltip-template-transclude="content"
8+
tooltip-template-transclude-scope="originScope()"></div>
9+
</div>
10+
</div>

0 commit comments

Comments
 (0)