我正在编写一个指令,将SlickGrid与我的角度应用程序集成。我希望能够使用角度模板(而不是格式化程序函数)配置SlickGrid列。为了实现这一点,我需要指令来动态创建将HTML作为字符串返回的格式化函数。
我的方法是创建一个临时范围,将模板链接到该范围,捕获html,然后销毁范围。这有效,但抱怨$digest already in progress
。有没有办法以这种方式渲染角度模板,与全局$ digest循环隔离?
BTW:我尝试使用$ interpolate,效果很好,但不支持ng-repeat
或其他指令。
var columnsConfig = [
{
id: "name",
name: "Name",
field: "name",
template: '<a href="{{context.url}}">{{value}}</a>'
},
{
id: "members",
name: "Members",
field: "members",
template: '<div ng-repeat="m in value">{{m}}</div>'
}
];
myModule.directive('SlickGrid', ['$compile', function($compile) {
return {
restrict: 'E',
scope: {
model: '='
},
link: function(scope, element, attrs) {
var columns = angular.copy(columnsConfig);
// Special Sauce: Allow columns to have an angular template
// in place of a regular slick grid formatter function
angular.forEach(columns, function(column){
var linker;
if (angular.isDefined(column.template)) {
linker = $compile(angular.element('<div>' + column.template + '</div>'));
delete column.template;
column.formatter = function(row, cell, value, columnDef, dataContext) {
var cellScope = scope.$new(true);
cellScope.value = value;
cellScope.context = dataContext;
var e = linker(cellScope);
cellScope.$apply();
cellScope.$destroy();
return e.html();
};
}
});
var options = {
enableColumnReorder: false,
enableTextSelectionOnCells: true,
autoHeight: true
};
var dataView = new Slick.Data.DataView();
var grid = new Slick.Grid(element, dataView, columns, options);
dataView.onRowCountChanged.subscribe(function (e, args) {
grid.updateRowCount();
grid.render();
});
dataView.onRowsChanged.subscribe(function (e, args) {
grid.invalidateRows(args.rows);
grid.render();
});
scope.$watch('model', function(data) {
if (angular.isArray(data)) {
dataView.setItems(data);
}
});
}
};
}]);
答案 0 :(得分:4)
好的,所以我需要做同样的事情,并想出一个可以被认为是一个黑客攻击的解决方案(但是没有其他方式AFAIK,因为SlickGrid只处理html字符串,而不是html / jquery对象)。
简而言之,它涉及在格式化程序中编译模板(正如您所做的那样),但除此之外,将生成的对象(不是HTML字符串)存储到字典中,并使用它使用 asyncPostRender (http://mleibman.github.io/SlickGrid/examples/example10-async-post-render.html)替换单元格内容。
以下是链接功能的一部分:
var cols = angular.copy(scope.columns);
var templates = new Array();
// Special Sauce: Allow columns to have an angular template
// in place of a regular slick grid formatter function
angular.forEach(cols, function (col) {
if (angular.isDefined(col.template)) {
col.formatter = function (row, cell, value, columnDef, dataContext) {
// Create a new scope, for each cell
var cellScope = scope.$parent.$new(false);
cellScope.value = value;
cellScope.context = dataContext;
// Interpolate (i.e. turns {{context.myProp}} into its value)
var interpolated = $interpolate(col.template)(cellScope);
// Compile the interpolated string into an angular object
var linker = $compile(interpolated);
var o = linker(cellScope);
// Create a guid to identify this object
var guid = guidGenerator.create();
// Set this guid to that object as an attribute
o.attr("guid", guid);
// Store that Angular object into a dictionary
templates[guid] = o;
// Returns the generated HTML: this is just so the grid displays the generated template right away, but if any event is bound to it, they won't work just yet
return o[0].outerHTML;
};
col.asyncPostRender = function(cellNode, row, dataContext, colDef) {
// From the cell, get the guid generated on the formatter above
var guid = $(cellNode.firstChild).attr("guid");
// Get the actual Angular object that matches that guid
var template = templates[guid];
// Remove it from the dictionary to free some memory, we only need it once
delete templates[guid];
if (template) {
// Empty the cell node...
$(cellNode).empty();
// ...and replace its content by the object (visually this won't make any difference, no flicker, but this one has event bound to it!)
$(cellNode).append(template);
} else {
console.log("Error: template not found");
}
};
}
});
列可以这样定义:
{ name: '', template: '<button ng-click="delete(context)" class="btn btn-danger btn-mini">Delete {{context.user}}</button>', width:80}
context.user将被正确插值(感谢$ interpolate)并且ng-click将起作用,这要归功于$ compile以及我们在asyncPostRender上使用真实对象而不是HTML的事实。
这是完整的指令,后跟HTML和控制器:
(function() {
'use strict';
var app = angular.module('xweb.common');
// Slick Grid Directive
app.directive('slickGrid', function ($compile, $interpolate, guidGenerator) {
return {
restrict: 'E',
replace: true,
template: '<div></div>',
scope: {
data:'=',
options: '=',
columns: '='
},
link: function (scope, element, attrs) {
var cols = angular.copy(scope.columns);
var templates = new Array();
// Special Sauce: Allow columns to have an angular template
// in place of a regular slick grid formatter function
angular.forEach(cols, function (col) {
if (angular.isDefined(col.template)) {
col.formatter = function (row, cell, value, columnDef, dataContext) {
// Create a new scope, for each cell
var cellScope = scope.$parent.$new(false);
cellScope.value = value;
cellScope.context = dataContext;
// Interpolate (i.e. turns {{context.myProp}} into its value)
var interpolated = $interpolate(col.template)(cellScope);
// Compile the interpolated string into an angular object
var linker = $compile(interpolated);
var o = linker(cellScope);
// Create a guid to identify this object
var guid = guidGenerator.create();
// Set this guid to that object as an attribute
o.attr("guid", guid);
// Store that Angular object into a dictionary
templates[guid] = o;
// Returns the generated HTML: this is just so the grid displays the generated template right away, but if any event is bound to it, they won't work just yet
return o[0].outerHTML;
};
col.asyncPostRender = function(cellNode, row, dataContext, colDef) {
// From the cell, get the guid generated on the formatter above
var guid = $(cellNode.firstChild).attr("guid");
// Get the actual Angular object that matches that guid
var template = templates[guid];
// Remove it from the dictionary to free some memory, we only need it once
delete templates[guid];
if (template) {
// Empty the cell node...
$(cellNode).empty();
// ...and replace its content by the object (visually this won't make any difference, no flicker, but this one has event bound to it!)
$(cellNode).append(template);
} else {
console.log("Error: template not found");
}
};
}
});
var container = element;
var slickGrid = null;
var dataView = new Slick.Data.DataView();
var bindDataView = function() {
templates = new Array();
var index = 0;
for (var j = 0; j < scope.data.length; j++) {
scope.data[j].data_view_id = index;
index++;
}
dataView.setItems(scope.data, 'data_view_id');
};
var rebind = function() {
bindDataView();
scope.options.enableAsyncPostRender = true;
slickGrid = new Slick.Grid(container, dataView, cols, scope.options);
slickGrid.onSort.subscribe(function(e, args) {
console.log('Sort clicked...');
var comparer = function(a, b) {
return a[args.sortCol.field] > b[args.sortCol.field];
};
dataView.sort(comparer, args.sortAsc);
scope.$apply();
});
slickGrid.onCellChange.subscribe(function(e, args) {
console.log('Cell changed');
console.log(e);
console.log(args);
args.item.isDirty = true;
scope.$apply();
});
};
rebind();
scope.$watch('data', function (val, prev) {
console.log('SlickGrid ngModel updated');
bindDataView();
slickGrid.invalidate();
}, true);
scope.$watch('columns', function (val, prev) {
console.log('SlickGrid columns updated');
rebind();
}, true);
scope.$watch('options', function (val, prev) {
console.log('SlickGrid options updated');
rebind();
}, true);
}
};
});
})();
<slick-grid id="slick" class="gridStyle" data="data" columns="columns" options="options" ></slick-grid>
$scope.data = [
{ spreadMultiplier: 1, supAmount: 2, from: "01/01/2013", to: "31/12/2013", user: "jaussan", id: 1000 },
{ spreadMultiplier: 2, supAmount: 3, from: "01/01/2014", to: "31/12/2014", user: "camerond", id: 1001 },
{ spreadMultiplier: 3, supAmount: 4, from: "01/01/2015", to: "31/12/2015", user: "sarkozyn", id: 1002 }
];
// SlickGrid Columns definitions
$scope.columns = [
{ name: "Spread Multiplier", field: "spreadMultiplier", id: "spreadMultiplier", sortable: true, width: 100, editor: Slick.Editors.Decimal },
{ name: "Sup Amount", field: "supAmount", id: "supAmount", sortable: true, width: 100, editor: Slick.Editors.Decimal },
{ name: "From", field: "from", id: "from", sortable: true, width: 130, editor: Slick.Editors.Date },
{ name: "To", field: "to", id: "to", sortable: true, width: 130, editor: Slick.Editors.Date },
{ name: "Added By", field: "user", id: "user", sortable: true, width: 200 },
{ name: '', template: '<button ng-click="delete(context)" class="btn btn-danger btn-mini">Delete</button>', width:80}
];
// SlickGrid Options
$scope.options = {
fullWidthRows: true,
editable: true,
selectable: true,
enableCellNavigation: true,
rowHeight:30
};
在rebind()方法上,注意
scope.options.enableAsyncPostRender = true;
这一点非常重要,否则永远不会调用asyncPostRender。
此外,为了完整起见,这里是GuidGenerator服务:
app.service('guidGenerator', function() {
this.create = function () {
function s4() {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}
function guid() {
return (s4() + s4() + "-" + s4() + "-" + s4() + "-" + s4() + "-" + s4() + s4() + s4());
}
return guid();
};
});
答案 1 :(得分:0)
我还没有尝试过使用模板,但是我使用的是格式化工具。
在列定义中,我使用了一个字符串作为格式化程序:
// Column definition:
{id: 'money', name: 'Money', field: 'money', sortable: true, formatter: 'money'}
在指令(或服务[它取决于您的slickgrid实现的架构])中,您可以使用例如:
var val = columns.formatter; // Get the string from the columns definition. Here: 'money'
columns.formatter = that.formatter[val]; // Set the method
// Method in directive or service
this.formatter = {
//function(row, cell, value, columnDef, dataContext)
money: function(row, cell, value){
// Using accounting.js
return accounting.formatNumber(value, 2, '.', ',');
}
}
我认为当您在指令中使用相同的方式来实现模板时,它运行正常 顺便说一下:你可以用同样的方式实现slick.grid.editors ......
来自简单的评论声明&#39;: 根据我的经验,当您使用带有css类的指令(列定义:cssClass)时,每次发生事件时都必须使用$ compile(onScroll,aso)......这个解决方案的性能很糟糕......
我在angular中实现格式化程序和编辑器的解决方案并不是很好,但没有很大的性能瓶颈。