2

SlickGridを角度のあるアプリに統合するためのディレクティブを作成しています。(フォーマッター関数ではなく) angular テンプレートを使用して SlickGrid 列を構成できるようにしたいと考えています。これを実現するには、HTML を文字列として返すフォーマッタ関数を動的に作成するディレクティブが必要です。

私のアプローチは、一時的なスコープを作成し、それに対してテンプレートをリンクし、html をキャプチャしてから、スコープを破棄することでした。これは機能しますが、$digest already in progress. グローバル $digest サイクルから分離して、この方法で角度テンプレートをレンダリングできる方法はありますか?

ng-repeatところで: $interpolate を使用してみました。これはうまく機能しますが、他のディレクティブはサポートしていません。

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);
        }
      });
    }
  };
}]);
4

2 に答える 2

4

わかりましたので、ほとんど同じことをする必要があり、少しハックと見なすことができる解決策を思いつきました(しかし、SlickGrid は html/jquery オブジェクトではなく html 文字列のみを処理するため、他に方法はありません)。

簡単に言えば、フォーマッターでテンプレートをコンパイルする必要がありますが (あなたが行ったように)、それに加えて、生成されたオブジェクト(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);
            }
        };
    });

})();

HTML:

<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();
        };
    });
于 2013-09-04T09:30:13.940 に答える
0

テンプレートを使用しようとしたことはありませんが、angular でフォーマッターを使用しています。

列の定義では、フォーマッタに文字列を使用しました。

// 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 を同じ方法で実装できます...

「Simple As Could Be」からのコメントへの声明: 私の経験では、css クラス (列定義: cssClass) でディレクティブを使用する場合、イベントが発生するたびに $compile を使用する必要があります (onScroll、aso) ... パフォーマンスこのソリューションではひどいです...

angular でフォーマッターとエディターを実装するという私のソリューションは素晴らしいものではありませんが、大きなパフォーマンスのボトルネックはありません。

于 2014-10-04T17:30:02.523 に答える