Skip to content

Commit 9fb29cc

Browse files
committed
fix(uiGridCell): Use promises for tmpl processing
Issue #1712 demonstrates a race condition where a column's compiledElementFn is attempted to be called before the cellTemplate is present, e.g. in the case of a template fetched over the network. This causes the function to not be present and an exception to be thrown. This changes fixes this by added a getCompiledElementFn() method to GridColumn, which returns a promise that is created in the defaultColumnBuilder(). It gets resolved in Grid.preCompileCellTemplates(). uiGridCell now uses this promise if the function is not present at pre-link time. Added tests for getCompiledElementFn() and getCellTemplate() Fixes #1712
1 parent 3d4e857 commit 9fb29cc

File tree

7 files changed

+160
-10
lines changed

7 files changed

+160
-10
lines changed

misc/demo/cellTemplate.html

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div class="ui-grid-cell-contents">
2+
Testing $http template: {{COL_FIELD CUSTOM_FILTERS}}
3+
</div>

misc/demo/grid-directive.html

+11-3
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,20 @@ <h2>Grid</h2>
4646

4747
<script>
4848

49-
var app = angular.module('test', ['ui.grid', 'ui.grid.selection']);
49+
var app = angular.module('test', ['ui.grid', 'ui.grid.selection', 'ui.grid.expandable']);
5050

5151
app.controller('Main', function($scope, $http) {
5252
// $scope.gridOptions = { data: 'data' };
53-
$scope.gridOptions = {};
54-
// $scope.gridOptions.columnDefs = [ {name:"First Name", field:"firstName"} ];
53+
$scope.gridOptions = {
54+
enableFiltering: true
55+
};
56+
$scope.gridOptions.columnDefs = [
57+
{
58+
name : "Name",
59+
field: "name",
60+
cellTemplate: '/misc/demo/cellTemplate.html'
61+
}
62+
];
5563

5664
$http.get('/misc/site/data/100.json')
5765
.success(function (data) {

src/js/core/directives/ui-grid-cell.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,20 @@ angular.module('ui.grid').directive('uiGridCell', ['$compile', '$log', '$parse',
2121
// No controller, compile the element manually (for unit tests)
2222
else {
2323
if ( uiGridCtrl && !$scope.col.compiledElementFn ){
24-
$log.error('Render has been called before precompile. Please log a ui-grid issue');
25-
} else {
24+
// $log.error('Render has been called before precompile. Please log a ui-grid issue');
25+
26+
$scope.col.getCompiledElementFn()
27+
.then(function (compiledElementFn) {
28+
compiledElementFn($scope, function(clonedElement, scope) {
29+
$elm.append(clonedElement);
30+
});
31+
});
32+
}
33+
else {
2634
var html = $scope.col.cellTemplate
2735
.replace(uiGridConstants.MODEL_COL_FIELD, 'row.entity.' + gridUtil.preEval($scope.col.field))
2836
.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
37+
2938
var cellElement = $compile(html)($scope);
3039
$elm.append(cellElement);
3140
}

src/js/core/factories/Grid.js

+4
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,10 @@ angular.module('ui.grid')
429429

430430
var compiledElementFn = $compile(html);
431431
col.compiledElementFn = compiledElementFn;
432+
433+
if (col.compiledElementFnDefer) {
434+
col.compiledElementFnDefer.resolve(col.compiledElementFn);
435+
}
432436
});
433437
};
434438

src/js/core/factories/GridColumn.js

+12
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,18 @@ angular.module('ui.grid')
661661
}
662662
};
663663

664+
GridColumn.prototype.getCellTemplate = function () {
665+
var self = this;
666+
667+
return self.cellTemplatePromise;
668+
};
669+
670+
GridColumn.prototype.getCompiledElementFn = function () {
671+
var self = this;
672+
673+
return self.compiledElementFnDefer.promise;
674+
};
675+
664676
return GridColumn;
665677
}]);
666678

src/js/core/services/gridClassFactory.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,11 @@
124124
*/
125125
if (!colDef.cellTemplate) {
126126
colDef.cellTemplate = 'ui-grid/uiGridCell';
127+
col.cellTemplatePromise = gridUtil.getTemplate(colDef.cellTemplate);
127128
}
128129

129-
templateGetPromises.push(gridUtil.getTemplate(colDef.cellTemplate)
130+
col.cellTemplatePromise = gridUtil.getTemplate(colDef.cellTemplate);
131+
templateGetPromises.push(col.cellTemplatePromise
130132
.then(
131133
function (template) {
132134
col.cellTemplate = template.replace(uiGridConstants.CUSTOM_FILTERS, col.cellFilter ? "|" + col.cellFilter : "");
@@ -136,7 +138,6 @@
136138
})
137139
);
138140

139-
140141
templateGetPromises.push(gridUtil.getTemplate(colDef.headerCellTemplate)
141142
.then(
142143
function (template) {
@@ -147,6 +148,9 @@
147148
})
148149
);
149150

151+
// Create a promise for the compiled element function
152+
col.compiledElementFnDefer = $q.defer();
153+
150154
return $q.all(templateGetPromises);
151155
}
152156

test/unit/core/factories/GridColumn.spec.js

+113-3
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
describe('GridColumn factory', function () {
2-
var $q, $scope, cols, grid, gridCol, Grid, GridColumn, gridClassFactory, GridRenderContainer, uiGridConstants;
2+
var $q, $scope, cols, grid, gridCol, Grid, GridColumn, gridClassFactory, GridRenderContainer, uiGridConstants, $httpBackend;
33

44
beforeEach(module('ui.grid'));
55

66
function buildCols() {
7-
grid.buildColumns();
7+
return grid.buildColumns();
88
}
99

1010

11-
beforeEach(inject(function (_$q_, _$rootScope_, _Grid_, _GridColumn_, _gridClassFactory_, _GridRenderContainer_, _uiGridConstants_) {
11+
beforeEach(inject(function (_$q_, _$rootScope_, _Grid_, _GridColumn_, _gridClassFactory_, _GridRenderContainer_, _uiGridConstants_, _$httpBackend_) {
1212
$q = _$q_;
1313
$scope = _$rootScope_;
1414
Grid = _Grid_;
1515
GridColumn = _GridColumn_;
1616
gridClassFactory = _gridClassFactory_;
1717
GridRenderContainer = _GridRenderContainer_;
1818
uiGridConstants = _uiGridConstants_;
19+
$httpBackend = _$httpBackend_;
1920

2021
cols = [
2122
{ field: 'firstName' }
@@ -220,4 +221,113 @@ describe('GridColumn factory', function () {
220221
});
221222
});
222223

224+
describe('getCompiledElementFn()', function () {
225+
var col;
226+
227+
beforeEach(function () {
228+
col = grid.columns[0];
229+
});
230+
231+
it('should return a promise', function () {
232+
expect(col.getCompiledElementFn().hasOwnProperty('then')).toBe(true);
233+
});
234+
235+
it('should return a promise that is resolved when the cellTemplate is compiled', function () {
236+
var resolved = false;
237+
238+
runs(function () {
239+
buildCols().then(function () {
240+
grid.preCompileCellTemplates();
241+
});
242+
});
243+
244+
runs(function () {
245+
col.getCompiledElementFn().then(function () {
246+
resolved = true;
247+
});
248+
249+
$scope.$digest();
250+
});
251+
252+
// $scope.$digest();
253+
254+
runs(function () {
255+
expect(resolved).toBe(true);
256+
});
257+
});
258+
259+
it('should return a promise that is resolved when a URL-based cellTemplate is available', function () {
260+
var resolved = false;
261+
262+
var url = 'http://www.a-really-fake-url.com/template.html';
263+
cols[0].cellTemplate = url;
264+
265+
$httpBackend.when('GET', url).respond('<div>foo</div>');
266+
267+
runs(function () {
268+
buildCols().then(function () {
269+
grid.preCompileCellTemplates();
270+
});
271+
272+
col.getCompiledElementFn().then(function () {
273+
resolved = true;
274+
});
275+
276+
expect(resolved).toBe(false);
277+
278+
$httpBackend.flush();
279+
});
280+
281+
runs(function () {
282+
$scope.$digest();
283+
});
284+
285+
runs(function () {
286+
expect(resolved).toBe(true);
287+
});
288+
});
289+
});
290+
291+
describe('getCellTemplate()', function () {
292+
var col;
293+
294+
beforeEach(function () {
295+
col = grid.columns[0];
296+
});
297+
298+
it('should return a promise', function () {
299+
expect(col.getCellTemplate().hasOwnProperty('then')).toBe(true);
300+
});
301+
302+
it('should return a promise that is resolved when a URL-based cellTemplate is available', function () {
303+
var resolved = false;
304+
305+
var url = 'http://www.a-really-fake-url.com/template.html';
306+
cols[0].cellTemplate = url;
307+
308+
$httpBackend.when('GET', url).respond('<div>foo</div>');
309+
310+
runs(function () {
311+
buildCols().then(function () {
312+
grid.preCompileCellTemplates();
313+
});
314+
315+
col.getCellTemplate().then(function () {
316+
resolved = true;
317+
});
318+
319+
expect(resolved).toBe(false);
320+
321+
$httpBackend.flush();
322+
});
323+
324+
runs(function () {
325+
$scope.$digest();
326+
});
327+
328+
runs(function () {
329+
expect(resolved).toBe(true);
330+
});
331+
});
332+
});
223333
});

0 commit comments

Comments
 (0)