Skip to content

Commit df785dc

Browse files
committed
feat(tooltip,modal): reuse fetchTemplate promises (fixes mgcrea#1301)
1 parent ef323a1 commit df785dc

File tree

4 files changed

+67
-26
lines changed

4 files changed

+67
-26
lines changed

src/modal/modal.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@ angular.module('mgcrea.ngStrap.modal', ['mgcrea.ngStrap.helpers.dimensions'])
157157
var promise = $animate.enter(modalElement, parent, after, enterAnimateCallback);
158158
if(promise && promise.then) promise.then(enterAnimateCallback);
159159

160-
scope.$isShown = true;
161-
scope.$$phase || (scope.$root && scope.$root.$$phase) || scope.$digest();
160+
$modal.$isShown = scope.$isShown = true;
161+
safeDigest(scope);
162162
// Focus once the enter-animation has started
163163
// Weird PhantomJS bug hack
164164
var el = modalElement[0];
@@ -199,8 +199,8 @@ angular.module('mgcrea.ngStrap.modal', ['mgcrea.ngStrap.helpers.dimensions'])
199199
if(options.backdrop) {
200200
$animate.leave(backdropElement);
201201
}
202-
scope.$isShown = false;
203-
scope.$$phase || (scope.$root && scope.$root.$$phase) || scope.$digest();
202+
$modal.$isShown = scope.$isShown = false;
203+
safeDigest(scope);
204204

205205
// Unbind events
206206
if(options.backdrop) {
@@ -254,19 +254,26 @@ angular.module('mgcrea.ngStrap.modal', ['mgcrea.ngStrap.helpers.dimensions'])
254254

255255
// Helper functions
256256

257+
function safeDigest(scope) {
258+
scope.$$phase || (scope.$root && scope.$root.$$phase) || scope.$digest();
259+
}
260+
257261
function findElement(query, element) {
258262
return angular.element((element || document).querySelectorAll(query));
259263
}
260264

265+
var fetchPromises = {};
261266
function fetchTemplate(template) {
262-
return $q.when($templateCache.get(template) || $http.get(template))
267+
if(fetchPromises[template]) return fetchPromises[template];
268+
fetchPromises[template] = $q.when($templateCache.get(template) || $http.get(template))
263269
.then(function(res) {
264270
if(angular.isObject(res)) {
265271
$templateCache.put(template, res.data);
266272
return res.data;
267273
}
268274
return res;
269275
});
276+
return fetchPromises[template];
270277
}
271278

272279
return ModalFactory;

src/modal/test/modal.spec.js

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,24 @@
33
describe('modal', function() {
44

55
var bodyEl = $('body'), sandboxEl;
6-
var $compile, $templateCache, $modal, $animate, $rootScope, scope;
6+
var $rootScope, $compile, $templateCache, $$rAF, $animate, $httpBackend, $modal, scope;
77

8-
beforeEach(module('ngSanitize'));
8+
beforeEach(module('ngAnimate'));
99
beforeEach(module('ngAnimateMock'));
1010
beforeEach(module('mgcrea.ngStrap.modal'));
1111

12-
beforeEach(inject(function (_$rootScope_, _$compile_, _$templateCache_, _$modal_, _$animate_) {
13-
$rootScope = _$rootScope_;
14-
scope = _$rootScope_.$new();
12+
beforeEach(inject(function($injector) {
13+
$rootScope = $injector.get('$rootScope');
14+
$compile = $injector.get('$compile');
15+
$templateCache = $injector.get('$templateCache');
16+
$$rAF = $injector.get('$$rAF');
17+
$animate = $injector.get('$animate');
18+
$httpBackend = $injector.get('$httpBackend');
19+
$modal = $injector.get('$modal');
20+
1521
bodyEl.html('');
1622
sandboxEl = $('<div>').attr('id', 'sandbox').appendTo(bodyEl);
17-
$compile = _$compile_;
18-
$templateCache = _$templateCache_;
19-
$modal = _$modal_;
20-
$animate = _$animate_;
23+
scope = $rootScope.$new();
2124
}));
2225

2326
afterEach(function() {
@@ -254,7 +257,7 @@ describe('modal', function() {
254257
expect(emit).toHaveBeenCalledWith('alert.hide', myModal);
255258
});
256259

257-
it("should can cancel show on show.before event", function() {
260+
it('should can cancel show on show.before event', function() {
258261
$rootScope.$on('modal.show.before', function(e) {
259262
e.preventDefault();
260263
});
@@ -266,7 +269,7 @@ describe('modal', function() {
266269
$animate.triggerCallbacks();
267270
});
268271

269-
it("should can cancel hide on hide.before event", function() {
272+
it('should can cancel hide on hide.before event', function() {
270273
$rootScope.$on('modal.hide.before', function(e) {
271274
e.preventDefault();
272275
});
@@ -359,6 +362,23 @@ describe('modal', function() {
359362
expect(sandboxEl.find('.modal-inner').text()).toBe('foo: ' + scope.modal.title);
360363
});
361364

365+
it('should request custom template via $http', function() {
366+
$httpBackend.expectGET('custom').respond(200, '<div class="modal"><div class="modal-inner">foo: {{title}}</div></div>');
367+
var elm = compileDirective('options-template');
368+
$httpBackend.flush();
369+
angular.element(elm[0]).triggerHandler('click');
370+
expect(sandboxEl.find('.modal-inner').text()).toBe('foo: ' + scope.modal.title);
371+
});
372+
373+
it('should request custom template via $http only once', function() {
374+
$httpBackend.expectGET('custom').respond(200, '<div class="modal"><div class="modal-inner">foo: {{title}}</div></div>');
375+
var elm = compileDirective('options-template');
376+
var elmBis = compileDirective('options-template');
377+
$httpBackend.flush();
378+
angular.element(elm[0]).triggerHandler('click');
379+
expect(sandboxEl.find('.modal-inner').text()).toBe('foo: ' + scope.modal.title);
380+
});
381+
362382
it('should support template with ngRepeat', function() {
363383
$templateCache.put('custom', '<div class="modal"><div class="modal-inner"><ul><li ng-repeat="item in items">{{item}}</li></ul></div></div>');
364384
var elm = compileDirective('options-template');

src/tooltip/test/tooltip.spec.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ describe('tooltip', function() {
88

99
beforeEach(module('ngAnimate'));
1010
beforeEach(module('ngAnimateMock'));
11-
beforeEach(module('ngSanitize'));
1211
beforeEach(module('mgcrea.ngStrap.tooltip'));
1312

1413
beforeEach(inject(function($injector) {
@@ -20,9 +19,9 @@ describe('tooltip', function() {
2019
$httpBackend = $injector.get('$httpBackend');
2120
$tooltip = $injector.get('$tooltip');
2221

23-
scope = $rootScope.$new();
2422
bodyEl.html('');
2523
sandboxEl = $('<div>').attr('id', 'sandbox').appendTo(bodyEl);
24+
scope = $rootScope.$new();
2625
}));
2726

2827
afterEach(function() {
@@ -73,8 +72,7 @@ describe('tooltip', function() {
7372
element: '<a data-trigger="click" bs-tooltip="tooltip">click me</a>'
7473
},
7574
'options-html': {
76-
scope: {tooltip: {title: 'Hello Tooltip<br>This is a multiline message!'}},
77-
element: '<a data-html="1" bs-tooltip="tooltip">hover me</a>'
75+
element: '<a data-html="1" title="Hello Tooltip<br>This is a multiline message!" bs-tooltip>hover me</a>'
7876
},
7977
'options-template': {
8078
scope: {tooltip: {title: 'Hello Tooltip!', counter: 0}, items: ['foo', 'bar', 'baz']},
@@ -677,7 +675,7 @@ describe('tooltip', function() {
677675
it('should correctly compile inner content', function() {
678676
var elm = compileDirective('options-html');
679677
angular.element(elm[0]).triggerHandler('mouseenter');
680-
expect(sandboxEl.find('.tooltip-inner').html()).toBe(scope.tooltip.title);
678+
expect(sandboxEl.find('.tooltip-inner').html()).toBe('Hello Tooltip<br>This is a multiline message!');
681679
});
682680

683681
});
@@ -699,6 +697,15 @@ describe('tooltip', function() {
699697
expect(sandboxEl.find('.tooltip-inner').text()).toBe('foo: ' + scope.tooltip.title);
700698
});
701699

700+
it('should request custom template via $http only once', function() {
701+
$httpBackend.expectGET('custom').respond(200, '<div class="tooltip"><div class="tooltip-inner">foo: {{title}}</div></div>');
702+
var elm = compileDirective('options-template');
703+
var elmBis = compileDirective('options-template');
704+
$httpBackend.flush();
705+
angular.element(elm[0]).triggerHandler('mouseenter');
706+
expect(sandboxEl.find('.tooltip-inner').text()).toBe('foo: ' + scope.tooltip.title);
707+
});
708+
702709
it('should support template with ngRepeat', function() {
703710
$templateCache.put('custom', '<div class="tooltip"><div class="tooltip-inner"><ul><li ng-repeat="item in items">{{item}}</li></ul></div></div>');
704711
var elm = compileDirective('options-template');

src/tooltip/tooltip.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ angular.module('mgcrea.ngStrap.tooltip', ['mgcrea.ngStrap.helpers.dimensions'])
2525
bsEnabled: true
2626
};
2727

28-
this.$get = function($window, $rootScope, $compile, $q, $templateCache, $http, $animate, dimensions, $$rAF, $timeout) {
28+
this.$get = function($window, $rootScope, $compile, $q, $templateCache, $http, $animate, $sce, dimensions, $$rAF, $timeout) {
2929

3030
var trim = String.prototype.trim;
3131
var isTouch = 'createTouch' in $window.document;
@@ -48,7 +48,7 @@ angular.module('mgcrea.ngStrap.tooltip', ['mgcrea.ngStrap.helpers.dimensions'])
4848

4949
// Support scope as string options
5050
if(options.title) {
51-
$tooltip.$scope.title = options.title;
51+
scope.title = $sce.trustAsHtml(options.title);
5252
}
5353

5454
// Provide scope helpers
@@ -211,7 +211,7 @@ angular.module('mgcrea.ngStrap.tooltip', ['mgcrea.ngStrap.helpers.dimensions'])
211211
if(promise && promise.then) promise.then(enterAnimateCallback);
212212

213213
$tooltip.$isShown = scope.$isShown = true;
214-
scope.$$phase || (scope.$root && scope.$root.$$phase) || scope.$digest();
214+
safeDigest(scope);
215215
$$rAF(function () {
216216
$tooltip.$applyPlacement();
217217

@@ -267,7 +267,7 @@ angular.module('mgcrea.ngStrap.tooltip', ['mgcrea.ngStrap.helpers.dimensions'])
267267
if(promise && promise.then) promise.then(leaveAnimateCallback);
268268

269269
$tooltip.$isShown = scope.$isShown = false;
270-
scope.$$phase || (scope.$root && scope.$root.$$phase) || scope.$digest();
270+
safeDigest(scope);
271271

272272
// Unbind events
273273
if(options.keyboard && tipElement !== null) {
@@ -514,19 +514,26 @@ angular.module('mgcrea.ngStrap.tooltip', ['mgcrea.ngStrap.helpers.dimensions'])
514514

515515
// Helper functions
516516

517+
function safeDigest(scope) {
518+
scope.$$phase || (scope.$root && scope.$root.$$phase) || scope.$digest();
519+
}
520+
517521
function findElement(query, element) {
518522
return angular.element((element || document).querySelectorAll(query));
519523
}
520524

525+
var fetchPromises = {};
521526
function fetchTemplate(template) {
522-
return $q.when($templateCache.get(template) || $http.get(template))
527+
if(fetchPromises[template]) return fetchPromises[template];
528+
fetchPromises[template] = $q.when($templateCache.get(template) || $http.get(template))
523529
.then(function(res) {
524530
if(angular.isObject(res)) {
525531
$templateCache.put(template, res.data);
526532
return res.data;
527533
}
528534
return res;
529535
});
536+
return fetchPromises[template];
530537
}
531538

532539
return TooltipFactory;

0 commit comments

Comments
 (0)