角模板字符串的一次性渲染 [英] One-off rendering of an angular template string

查看:245
本文介绍了角模板字符串的一次性渲染的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我写一个指令来整合 SlickGrid 我的角度应用程序。我希望能够与角模板(而不是一个格式化器功能)配置SlickGrid列。要做到这一点,我需要的指令来动态地创建格式化函数返回的HTML作为字符串。

我的方法是创建一个临时的范围,对链接的模板,捕捉到HTML,然后破坏范围。这工作,但抱怨说 $已经消化进度。有没有一种方法,我可以呈现以这种方式角模板,从全球$孤立的消化周期?

BTW:我试着用$插值,伟大的工程,但不支持 NG-重复或其他指令

  VAR columnsConfig = [
  {
    ID:姓名,
    名称:姓名,
    现场:姓名,
    模板:'< A HREF ={{context.url}}> {{}值}< / A>'
  },
  {
    ID:成员,
    名称:成员,
    现场:成员,
    模板:'< D​​IV NG重复=米价值> {{M-}}< / DIV>'
  }
];myModule.directive('SlickGrid',['$编译,函数($编译){
  返回{
    限制:'E',
    范围: {
      型号:'='
    },
    链接:功能(范围,元素,ATTRS){
      VAR列= angular.copy(columnsConfig);      //特制的酱汁:允许列有一个角模板
      //代替常规网格光滑的格式化功能
      angular.forEach(列,函数(列){
        VAR连接;        如果(angular.isDefined(column.template)){
          连接器= $编译(angular.element('< D​​IV>'+ 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',
            更换:真实,
            模板:'< D​​IV>< / 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆