角模板字符串的一次性渲染 [英] One-off rendering of an angular template string
问题描述
我写一个指令来整合 SlickGrid 我的角度应用程序。我希望能够与角模板(而不是一个格式化器功能)配置SlickGrid列。要做到这一点,我需要的指令来动态地创建格式化函数返回的HTML作为字符串。
我的方法是创建一个临时的范围,对链接的模板,捕捉到HTML,然后破坏范围。这工作,但抱怨说 $已经消化进度
。有没有一种方法,我可以呈现以这种方式角模板,从全球$孤立的消化周期?
BTW:我试着用$插值,伟大的工程,但不支持 NG-重复
或其他指令
VAR columnsConfig = [
{
ID:姓名,
名称:姓名,
现场:姓名,
模板:'< A HREF ={{context.url}}> {{}值}< / A>'
},
{
ID:成员,
名称:成员,
现场:成员,
模板:'< DIV NG重复=米价值> {{M-}}< / DIV>'
}
];myModule.directive('SlickGrid',['$编译,函数($编译){
返回{
限制:'E',
范围: {
型号:'='
},
链接:功能(范围,元素,ATTRS){
VAR列= angular.copy(columnsConfig); //特制的酱汁:允许列有一个角模板
//代替常规网格光滑的格式化功能
angular.forEach(列,函数(列){
VAR连接; 如果(angular.isDefined(column.template)){
连接器= $编译(angular.element('< DIV>'+ column.template +'< / DIV>'));
删除column.template; column.formatter =功能(行,单元格,价值,columnDef,DataContext的){
。VAR cellScope =范围美元的新(真);
cellScope.value =价值;
cellScope.context = DataContext的; 变种E =连接器(cellScope);
。cellScope $适用();
。cellScope $ destroy()方法; 返回e.html();
};
}
}); VAR的选择= {
enableColumnReorder:假的,
enableTextSelectionOnCells:真实,
autoHeight:真
}; VAR数据视图=新Slick.Data.DataView();
VAR电网=新Slick.Grid(元素,数据视图,列选项); dataView.onRowCountChanged.subscribe(功能(即参数){
grid.updateRowCount();
grid.render();
}); dataView.onRowsChanged.subscribe(功能(即参数){
grid.invalidateRows(args.rows);
grid.render();
}); 范围。手表$('模型',功能(数据){
IF(angular.isArray(数据)){
dataView.setItems(数据);
}
});
}
};
}]);
好了,所以我需要做的pretty类似的事情,并与的解决方案提出了可能的被认为是一个黑客位(但有AFAIK没有别的办法,因为SlickGrid只能用HTML字符串,而不是HTML / jQuery的对象待遇)。
在简单地说,它涉及编译模板的格式(像你一样),但除此之外,存储产生的目标(而不是HTML字符串)成dictionnary和使用它通过替换单元格内容的 asyncPostRender (适用的http://mleibman.github.io/SlickGrid/examples/example10-async-post-render.html).
下面是链接的功能就是在这里讨论的一部分:
VAR COLS = angular.copy(scope.columns);
VAR模板=新的Array();//特制的酱汁:允许列有一个角模板
//代替常规网格光滑的格式化功能
angular.forEach(COLS,函数(COL){ 如果(angular.isDefined(col.template)){ col.formatter =功能(行,单元格,价值,columnDef,DataContext的){ //创建一个新的范围,针对每个小区
。VAR cellScope = $范围母公司$新的(假)。
cellScope.value =价值;
cellScope.context = DataContext的; //插值(即转{{context.myProp}}到它的价值)
VAR插值= $插值(col.template)(cellScope); //插值字符串编译成一个角度对象
VAR连接= $编译(插值);
变种O =连接器(cellScope); //创建一个GUID来确定这个对象
变种GUID = guidGenerator.create(); //设置此GUID到对象的属性
o.attr(GUID,GUID) //存储的角度对象转换成字典
模板[GUID] = O; //返回生成的HTML:这只是使网格显示生成的模板向右走,但如果任何事件被绑定到它,他们将无法正常工作,只是还没有
返回的问题o [0] .outerHTML;
}; col.asyncPostRender =功能(cellNode,行,DataContext的,colDef){ //从细胞,坐上格式生成的GUID以上
VAR GUID = $(cellNode.firstChild).attr(GUID); //获取匹配的GUID的实际角度对象
VAR模板=模板[GUID] //从字典中删除以释放一些内存,我们只需要一次
删除模板[GUID] 如果(模板){
//清空小区节点...
$(cellNode).empty();
// ...与物体替换其内容(视觉,这将没有任何区别,无闪烁,但这一事件已绑定到它!)
$(cellNode).append(模板); }其他{
的console.log(错误:模板没有找到);
}
};
}
});
列可以定义为这样的:
{名称:'',模板:'<按钮NG点击=删除(上下文)级=BTN BTN-危险BTN-迷你>删除{{ context.user}}< /按钮>',宽度:80}
该context.user将被正确插值(感谢$插值)和NG-点击将是工作的感谢$编译和我们使用的真正的对象,而不是HTML的asyncPostRender的事实。
这是完整的指令,接着由HTML和控制器:
指令:
(函数(){
使用严格的; VAR应用= angular.module('xweb.common'); //油滑电网指令
app.directive('slickGrid',函数($编译,$插值,guidGenerator){
返回{
限制:'E',
更换:真实,
模板:'< DIV>< / DIV>,
范围: {
数据:=,
选项:'=',
列:'='
},
链接:功能(范围,元素,ATTRS){ VAR COLS = angular.copy(scope.columns);
VAR模板=新的Array(); //特制的酱汁:允许列有一个角模板
//代替常规网格光滑的格式化功能
angular.forEach(COLS,函数(COL){ 如果(angular.isDefined(col.template)){ col.formatter =功能(行,单元格,价值,columnDef,DataContext的){ //创建一个新的范围,针对每个小区
。VAR cellScope = $范围母公司$新的(假)。
cellScope.value =价值;
cellScope.context = DataContext的; //插值(即转{{context.myProp}}到它的价值)
VAR插值= $插值(col.template)(cellScope); //插值字符串编译成一个角度对象
VAR连接= $编译(插值);
变种O =连接器(cellScope); //创建一个GUID来确定这个对象
变种GUID = guidGenerator.create(); //设置此GUID到对象的属性
o.attr(GUID,GUID) //存储的角度对象转换成字典
模板[GUID] = O; //返回生成的HTML:这只是使网格显示生成的模板向右走,但如果任何事件被绑定到它,他们将无法正常工作,只是还没有
返回的问题o [0] .outerHTML;
}; col.asyncPostRender =功能(cellNode,行,DataContext的,colDef){ //从细胞,坐上格式生成的GUID以上
VAR GUID = $(cellNode.firstChild).attr(GUID); //获取匹配的GUID的实际角度对象
VAR模板=模板[GUID] //从字典中删除以释放一些内存,我们只需要一次
删除模板[GUID] 如果(模板){
//清空小区节点...
$(cellNode).empty();
// ...与物体替换其内容(视觉,这将没有任何区别,无闪烁,但这一事件已绑定到它!)
$(cellNode).append(模板); }其他{
的console.log(错误:模板没有找到);
}
};
}
}); VAR容器=元素;
VAR slickGrid = NULL;
VAR数据视图=新Slick.Data.DataView(); VAR bindDataView =功能(){
模板=新的Array(); 变种索引= 0;
对于(VAR J = 0; J< scope.data.length; J ++){
scope.data [J] .data_view_id =指数;
指数++;
} dataView.setItems(scope.data,'data_view_id');
}; VAR重新绑定=功能(){ bindDataView(); scope.options.enableAsyncPostRender = TRUE; slickGrid =新Slick.Grid(集装箱,数据视图,COLS,scope.options);
slickGrid.onSort.subscribe(功能(即参数){
的console.log('排序点击...'); 变种比较器=函数(A,B){
返回[args.sortCol.field> B〔args.sortCol.field];
}; dataView.sort(比较器,args.sortAsc);
。范围$适用();
}); slickGrid.onCellChange.subscribe(功能(即参数){
的console.log('细胞改变');
的console.log(E);
的console.log(参数);
args.item.isDirty = TRUE;
。范围$适用();
});
}; 重新绑定(); 范围。$表(数据,功能(VAL,preV){
的console.log('SlickGrid ngModel更新');
bindDataView();
slickGrid.invalidate();
},真正的); 范围。$腕表('列',函数(VAL,preV){
的console.log('SlickGrid列更新');
重新绑定();
},真正的); 范围。$腕表('选项',函数(VAL,preV){
的console.log('SlickGrid选择更新');
重新绑定();
},真正的);
}
};
});})();
的HTML:
<油滑网ID =华而不实级=gridStyle数据=数据栏=栏选项=选项>< /光滑网>
控制器:
$ scope.data = [
{S preadMultiplier:1,supAmount:2,从2013年1月1日,到31/12/2013,用户jaussan,编号:1000},
{S preadMultiplier:2,supAmount:3,从2014年1月1日,到31/12/2014,用户camerond,ID 1001},
{S preadMultiplier:3,supAmount:4,从2015年1月1日,到31/12/2015,用户sarkozyn,编号:1002}
];// SlickGrid列定义
$ scope.columns = [
{名:S $ P $垫器,现场:S preadMultiplier,ID:S preadMultiplier,排序:真,宽度:100,编辑:Slick.Editors.Decimal},
{名:燮金额,现场supAmount,ID:supAmount,排序:真,宽度:100,编辑:Slick.Editors.Decimal},
{名:从,现场:从,ID:从,排序:真,宽度:130,编辑:Slick.Editors.Date},
{名:收件人,现场为,ID:到,排序:真,宽度:130,编辑:Slick.Editors.Date},
{名:添加者,现场用户,ID:用户,排序:真,宽度:200},
{名称:'',模板:'<按钮NG点击=删除(上下文)级=BTN BTN-危险BTN-迷你>删除< /按钮>',宽度:80}
];// SlickGrid选项
$ scope.options = {
fullWidthRows:真实,
编辑:真的,
可选:真实,
enableCellNavigation:真实,
的rowHeight:30
};
重要提示
在重新绑定()方法,注意
scope.options.enableAsyncPostRender = TRUE;
这是非常重要的有,否则asyncPostRender永远不会被调用。
另外,为了完整起见,这里是GuidGenerator服务
app.service('guidGenerator',函数(){
this.create =功能(){ 功能S4(){
返回(((1 +的Math.random())*为0x10000)| 0)的ToString(16).substring(1);
} 功能GUID(){
返回(S4()+ S4()+ - + S4()+ - + S4()+ - + S4()+ - + S4()+ S4()+ S4()) ;
} 返回GUID();
};
});
I am writing a directive to integrate SlickGrid with my angular app. I want to be able to configure SlickGrid columns with an angular template (instead of a formatter function). To achieve this, I need the directive to dynamically create formatter functions that return HTML as a string.
My approach has been to create a temporary scope, link the template against that, capture the html, and then destroy the scope. This works, but complains that $digest already in progress
. Is there a way I can render an angular template in this fashion, isolated from the global $digest cycle?
BTW: I tried using $interpolate, which works great, but doesn't support ng-repeat
or other directives.
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);
}
});
}
};
}]);
Ok so I needed to do pretty much the same thing, and came up with a solution that could be considered a bit of a hack (but there's no other way AFAIK, since SlickGrid only deals with html string, not html/jquery objects).
In a nutshell, it involves compiling the template in the formatter (as you did), but in addition to that, stores the generated object (not the HTML string) into a dictionnary, and use it to replace the cell content by using asyncPostRender (http://mleibman.github.io/SlickGrid/examples/example10-async-post-render.html).
Here is the part of the link function that is of interest here:
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");
}
};
}
});
The column can be defined as such:
{ name: '', template: '<button ng-click="delete(context)" class="btn btn-danger btn-mini">Delete {{context.user}}</button>', width:80}
The context.user will be properly interpolated (thanks to $interpolate) and the ng-click will be working thanks to $compile and the fact that we use the real object and not the HTML on the asyncPostRender.
This is the full directive, followed by the HTML and the controller:
Directive:
(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);
}
};
});
})();
The HTML:
<slick-grid id="slick" class="gridStyle" data="data" columns="columns" options="options" ></slick-grid>
The controller:
$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
};
Important:
on the rebind() method, notice the
scope.options.enableAsyncPostRender = true;
This is very important to have that, otherwise the asyncPostRender is never called.
Also, for the sake of completeness, here is the GuidGenerator service:
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();
};
});
这篇关于角模板字符串的一次性渲染的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!