如何使用 Angular 动态创建多个 ui-grid

How to dynamically create multiple ui-grids with Angular

我们的情况是有 2 个常量 Bootstrap 选项卡和任意数量的动态选项卡。这些动态选项卡上需要有两个 ui-grid。在这一点上,我只是想让一个 ui-grid 工作。我有一个自定义集模型数组,其中包含每个名为 TabGroups 的选项卡的唯一 ID 和数据。在屏幕不同部分的点击事件是触发创建任何新选项卡的原因。选项卡出现,我可以找到它们。我开始研究如何添加 ui-grid 并且现在已经停滞了一段时间。这个 似乎是我要找的东西,但我不确定,因为 angular 和 ui-grid 都更新到较新的版本,事情不再适合我。

创建新选项卡时会发生以下情况:

$scope.TabGroups = []; // Contains the Name and useful data
// Temp-faked data
$scope.Testing.tabCDA_ManagementWages = [{ "firstName": "Kevin", "lastName": "G" }, { "firstName": "John", "lastName": "M" }];
$scope.Testing.tabCDA_NonManagementWages = [{ "firstName": "Doug", "lastName": "D" }, { "firstName": "Jeff", "lastName": "C" }];

$scope.createTab = function (rowNumber) {
    var SelectedRow = $scope.Groupings.filter(function (item) { return item.RowNumber == rowNumber; });
    var newTab = {
        Name: SelectedRow[0].Description,
        IsLoading: false,
        UniqueID: "tabCDA_" + SelectedRow[0].Description.replace(/[^a-zA-Z0-9]/g, "")
    };

    newTab.Data = $scope.Testing[newTab.UniqueID];
    $scope.TabGroups.push(newTab);
}; // createTab

这是 Directive 的设置:

app.directive("cdaTab", cdaTab);

这里是 cdaTab 代码:

function cdaTab() {
    return {
        template: "<div class='grid' ui-grid='gridOptions'></div>",
        restrict: "E",
        scope: {
            options: "="
        },
        controller: "ProfitAndLossController",
        controllerAs: "vm",
        bindToController: true,
        link: function (scope, element, attrs) {
            scope.gridOptions = {
                data: scope.vm.options.Data,
                columnDefs: [
                    { name: "firstName", field: "firstName", displayName: "First Name", type: "string", width: 150 },
                    { name: "lastName",  field: "lastName",  displayName: "Last Name",  type: "string", width: 150 },
                ]
            };
        } // link
    }; // return
} // cdaTab

这里是动态标签的HTML:

<div role="tabpanel" class="tab-pane" ng-repeat="tab in TabGroups" id="{{ tab.UniqueID }}">
    <div class="row">
        <div class="col-md-12 small">
            <cda-tab id="{{ tab.UniqueID + '_A' }}" options="tab" class="cdaSummaryInfo gca-highlight"></cda-tab>
        </div>
    </div>
</div>

第一个效果很好,创建了选项卡并加载了数据。创建第二个选项卡时,该选项卡显示但加载数据失败,因为它在 data 上获得 null-reference exception特别是 ui-grid.js 文件的第 3,150 行(ui-grid - v3.1.0 - 2016-01-19):

if (angular.isString($scope.uiGrid.data)) {

$scope 存在,但 uiGrid 不存在。

我还注意到,在创建第一个动态网格后,它不会再次进入 cdaTab() 方法。不确定是否应该,但我会这样认为。

似乎快要工作了,但只是缺少一些东西。

如果有更好的方法,我会洗耳恭听。我做了相当多的研究,似乎都指向使用指令,所以我做了。

编辑 回复:请求,这里是 ProfitAndLossController 代码:

app.controller("ProfitAndLossController", ["$scope", "$rootScope", "uiGridConstants", "$http", "$window", "$filter", "CommonCode",
    function ($scope, $rootScope, uiGridConstants, $http, $window, $filter, CommonCode) {
        var windowUrl = $window.location.href.replace("#", "");
        var nextTabInteger = 0;

        $scope.isLoadingData = true;
        $scope.isExportingData = false;

        $scope.Groupings = [];
        $scope.DataRows = [];
        $scope.ConsolidatedMonths = [];
        $scope.MostRecentDate = FormatDateMDYYYY(new Date(), "/");
        $scope.TabGroups = []; // Contains the Name and useful data to fill in the TabPrimaryData values
        $scope.TabPrimaryData = [];
        $scope.TabDetailsData = [];
        $scope.Testing = []; // Array of ui-grids
        $scope.Testing2 = []; // Array of ui-grids for the details
        $scope.cdaitems = [];

        $scope.Testing.tabCDA_ManagementWages = [{ "firstName": "Kevin", "lastName": "G" }, { "firstName": "John", "lastName": "M" }];
        $scope.Testing.tabCDA_NonManagementWages = [{ "firstName": "Doug", "lastName": "D" }, { "firstName": "Jeff", "lastName": "C" }];

        $scope.URLs = {
            Data: windowUrl + "/ProfitAndLossData",
            Details: windowUrl + "/IncidentReportingDetails",
            New: windowUrl + "/NewIncidentReport",
            Groupings: windowUrl + "/GetGroupings",
            GroupingsFlat: windowUrl + "/GetGroupingsFlat",
            TabPrimaryData: windowUrl + "/GetPrimaryCDA",
            TabDetailsData: windowUrl + "/GetPrimaryCDADetails"
        }; // URLs

        $scope.fillGroupings = function () {
            $scope.isLoadingData = true;

            $http({
                method: "GET",
                url: $scope.URLs.GroupingsFlat,
                headers: { "Content-Type": "application/json" }
            }).success(function (data) {
                $scope.Groupings = data;
                $scope.isLoadingData = false;
            }).error(function (data, status, headers, config) {
                $scope.isLoadingData = false;
                alert("Error retrieving Grouping data. The error has been logged. Please retry and contact the system administrator if it happens again. Status is " + status + ".");
                return status;
            }); // http
        }; // fillGroupings

        $scope.fillRowData = function () {
            $scope.isLoadingData = true;
            var UsersFilters = $scope.GatherFilters();

            // Calculate the Consolidated P&L Dates
            $scope.ConsolidatedMonths = [];
            UsersFilters.EndDate = $scope.MostRecentDate;

            var DateParts = UsersFilters.EndDate.split('/');
            var MRD = new Date(DateParts[2], DateParts[0] - 1, DateParts[1]);

            for (var i = 11; i >= 0; i--)
                $scope.ConsolidatedMonths[i] = incrementDate(MRD, -i);

            $http({
                method: "POST",
                url: $scope.URLs.Data,
                headers: { "Content-Type": "application/json" },
                data: { "filters": UsersFilters }
            }).success(function (data) {
                if (data.WasSuccessful == true) {
                    for (var i = 0; i < data.Result.length; i++)
                        $scope.DataRows[data.Result[i].RowNumber] = data.Result[i];
                } else {
                    alert("Whoops");
                } // if it got the row of data successfully

                //$scope.updateProgress(1);
                $scope.isLoadingData = false;
            }).error(function (data, status, headers, config) {
                $scope.isLoadingData = false;
                alert("Error retrieving Row data. The error has been logged. Please retry and contact the system administrator if it happens again. Status is " + status + ".");
                return status;
            }); // http
        }; // fillRowData

        $scope.createTab = function (rowNumber) {
            var Found = $scope.TabGroups.filter(function (item) { return item.RowNumber == rowNumber; });

            if (Found.length > 0) {
                alert("This item already exists.  Taking you there now.");
            } else {
                // New tab created
                var SelectedRow = $scope.Groupings.filter(function (item) { return item.RowNumber == rowNumber; });
                var newTab = {
                    RowNumber: rowNumber,
                    Name: SelectedRow[0].Description,
                    Date: "", // May need the column they selected
                    IsLoading: false,
                    TabInteger: nextTabInteger,
                    UniqueID: "tabCDA_" + SelectedRow[0].Description.replace(/[^a-zA-Z0-9]/g, "") // Remove all non-alphanumeric characters
                };

                newTab.Data = $scope.Testing[newTab.UniqueID];
                $scope.TabGroups.push(newTab);

                $("#" + newTab.UniqueID).tab("show");
                nextTabInteger++;
            } // if that Tab is already in the list
        }; // createTab

        $scope.deleteTab = function (uniqueID) {
            var Found = $scope.TabGroups.filter(function (item) { return item.UniqueID == uniqueID; });

            if (Found.length > 0) {
                var index = $scope.TabGroups.indexOf(Found[0]);
                $scope.TabGroups.splice(index, 1);
                $scope.TabPrimaryData.splice(index, 1);
                $scope.TabDetailsData.splice(index, 1);
            } // if we found the tab
        }; // deleteTab

        $scope.fillTabPrimaryData = function (tabGroup) {

            var UsersFilters = $scope.GatherFilters();
            var parameters = [];
            ////$scope.CDADetailsSummary.data = [];
            ////$scope.CDADetails.data = [];
            //UsersFilters.PageNumber = $scope.paginationOptions.pageNumber;
            //UsersFilters.RecordSize = $scope.paginationOptions.pageSize;
            //UsersFilters.SortField = $scope.paginationOptions.sort;
            //UsersFilters.SortDirection = $scope.paginationOptions.sortOrder;

            tabGroup.IsLoading = true;
            var InputDate = new Date($scope.MostRecentDate);
            var Month = InputDate.getMonth();
            var Year = InputDate.getFullYear().toString().substr(2, 2)


            parameters.push({ Name: "RowNumber", Value: tabGroup.RowNumber });
            parameters.push({ Name: "FromFiscalMonth", Value: Month });
            parameters.push({ Name: "BusinessUnit", Value: "1000" });
            parameters.push({ Name: "Year", Value: Year });

            $http({
                method: "POST",
                url: $scope.URLs.TabPrimaryData,
                // headers: { "Content-Type": "application/json" },
                headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8;" },
                data: $.param({ filters: UsersFilters, parameters: parameters })
            }).success(function (data, status, headers, config) {
                if (data.WasSuccessful == true) {
                    //$scope.CDADetailsSummary.data = data.Result;
                    //  $scope.CDADetailsSummary[$scope.TabGroups.indexOf(tabGroup)] = data.Result;
                } else {
                    DisplayToastMessage("Failure attempting to gather Primary data", data);
                } // if the response was successful

                tabGroup.IsLoading = false;
            }).error(function (data, status, headers, config) {
                tabGroup.IsLoading = false;
                $scope.status = status;
                alert("Error retrieving data. The error has been logged. Please retry and contact the system administrator if it happens again. Status is " + status + ".");
            }); // http
        }; // fillTabPrimaryData

        $scope.fillTabPrimaryDataSAVE = function (tabGroup) {
            tabGroup.IsLoading = true;
            var UsersFilters = $scope.GatherFilters();

            $http({
                method: "POST",
                url: $scope.URLs.TabPrimaryData,
                headers: { "Content-Type": "application/json" },
                data: { "rowNumber": tabGroup.RowNumber, "filters": UsersFilters }
            }).success(function (data) {
                if (data.WasSuccessful == true) {
                    $scope.TabPrimaryData[$scope.TabGroups.indexOf(tabGroup)] = data.Result;
                } else {
                    alert("Whoops");
                } // if it got the row of data successfully

                tabGroup.IsLoading = false;
            }).error(function (data, status, headers, config) {
                tabGroup.IsLoading = false;
                alert("Error retrieving CDA data. The error has been logged. Please retry and contact the system administrator if it happens again. Status is " + status + ".");
                return status;
            }); // http
        }; // fillTabPrimaryData

        $scope.fillTabCDADetails = function (businessUnit) {
            //  tabGroup.IsLoading = true;
            var UsersFilters = $scope.GatherFilters();
            var parameters = [];

            $http({
                method: "POST",
                url: $scope.URLs.TabDetailsData,
                headers: { "Content-Type": "application/json" },
                data: { "filters": UsersFilters, "parameters": parameters }
            }).success(function (data) {
                if (data.WasSuccessful == true) {
                    ////$scope.CDADetails.data = data.Result;
                } else {
                    alert("Whoops");
                } // if it got the row of data successfully

                //   tabGroup.IsLoading = false;
            }).error(function (data, status, headers, config) {
                //   tabGroup.IsLoading = false;
                alert("Error retrieving CDA data. The error has been logged. Please retry and contact the system administrator if it happens again. Status is " + status + ".");
                return status;
            }); // http
        }; // fillPrimaryData

        $scope.export = function (export_format, gridName) {
            var myElement = null;
            var exporter = null;
            var spinner = null;

            switch (gridName) {
                case "data":
                    exporter = $scope.gridData.exporter;
                    spinner = "isExportingData";
                    break;
                case "details":
                    exporter = $scope.gridDetail.exporter;
                    spinner = "isExportingDetails";
                    break;
            } // switch

            $scope[spinner] = true;

            switch (export_format) {
                case "visible_csv":
                    myElement = angular.element(document.querySelectorAll(".custom-csv-link-location"));
                    exporter.csvExport("visible", "visible", myElement);
                    break;
                case "all_csv":
                    myElement = angular.element(document.querySelectorAll(".custom-csv-link-location"));
                    exporter.csvExport("all", "all", myElement);
                    break;
            } // switch

            $scope[spinner] = false;
        }; // Export

        $scope.FormatExportDates = function (grid, row, col, value) {
            return ((col.name.substr(col.name.length - 4).toLowerCase() == "date") && (value !== undefined) && (value !== null)) ? new Date(parseInt(value.substr(6))).toISOString().split("T")[0] : value;
        }; // FormatExportDates

        $scope.clearSelections = function () {
            $scope.refreshData();
            ////$scope.CDADetails.data = [];
        }; // clearSelections

        $scope.refreshData = function () {
            $scope.fillRowData();
            //$scope.CDADetails.data = [];
        }; // refreshData

        $scope.lessThan = function (property, value) {
            return function (item) {
                return item[property] < value;
            }
        }; // lessThan - Filter

        angular.element(document).ready(function () {
            $scope.GatherFilters = $rootScope.GatherFilters;
            $scope.fillGroupings();
            $scope.refreshData();

            $(document).on("click", "a[class^='base-']", function () { // targets anything with base- class in it, starts with it
                var link = $(this);
                var rowID = link.data("row-id");
                var rowLvl = link.data("row-level");
                var start = 0;

                $("ul[id=" + rowID + "-E]").toggle(); // Expand or Collapse the clicked item
                var symbol = $(this).closest("li").children("span:first");
                symbol.hasClass("glyphicon glyphicon-menu-right") ? symbol.removeClass("glyphicon glyphicon-menu-right").addClass("glyphicon glyphicon-menu-down") : symbol.removeClass("glyphicon glyphicon-menu-down").addClass("glyphicon glyphicon-menu-right");

                // Show or Hide the corresponding row data items
                $.each($("div[data-parent='" + rowID + "']"), function (key, value) {
                    var rid = $(this).data("row-id");

                    // Only Collapse if it is already Expanded and we are Collapsing the Level 3 Parent
                    if ((rowLvl == 3) && ($(this).is(":visible"))) {
                        $.each($("div[data-parent='" + rid + "']:visible"), function (key, value) {
                            $(this).hide();
                        }); // foreach of the Level 1 children with the current Level 2 Parent ID

                        var smbl = $("a[data-row-id='" + rid + "']").closest("li").children("span:first");
                        $("ul[id=" + rid + "-E]").hide(); // Collapse the sub menu item
                        smbl.removeClass("glyphicon glyphicon-menu-down").addClass("glyphicon glyphicon-menu-right");
                    } // if we are on a Level 3 item and are Collapsing it

                    $(this).toggle(); // Show/Hide the Level
                }); // foreach of the Level 2 items within the clicked Level 3 Parent
            }); // Click event for tree view

            // Setup the date control
            $("[ng-model='MostRecentDate']").closest(".input-group.date").datetimepicker({
                format: "L", // "MM/DD/YYYY"
                minDate: moment("01/01/2012", "MM/DD/YYYY"),
                maxDate: FormatDateMDYYYY(new Date(), "/"),
                defaultDate: FormatDateMDYYYY(new Date(), "/")
            }).on("dp.change", function (e) {
                $scope.MostRecentDate = $(e.target).find("input").val();
            });

            // Setup the hover on rows
            $(document).on("mouseenter", "li a[data-row-id], div[data-row-id]", function () {
                var rid = $(this).data("row-id");
                $("div[data-row-id='" + rid + "'] div div").addClass("highlight");
                $("a[data-row-id='" + rid + "']").addClass("highlight");
            })
            .on("mouseleave", "li a[data-row-id], div[data-row-id]", function () {
                var rid = $(this).data("row-id");
                $("div[data-row-id='" + rid + "'] div div").removeClass("highlight");
                $("a[data-row-id='" + rid + "']").removeClass("highlight");
            });

        }); // Document Ready

        $scope.CDADetailsSummary = {
            fastWatch: true,
            data: [],
            enableColumnResizing: true,
            flatEntityAccess: true,
            enableSorting: true,
            enableFiltering: true,
            enableRowHeaderSelection: false,
            enableRowSelection: true,
            showColumnFooter: true,
            multiSelect: false,
            exporterCsvFilename: "CDADetailsSummary_" + Today + ".csv",
            exporterFieldCallback: $scope.FormatExportDates,
            exporterMenuCsv: false,
            exporterMenuPdf: false,
            enableGridMenu: true,
            paginationPageSizes: [15, 25, 50, 75, 100, 250],
            paginationPageSize: 15,
            columnDefs: [
                { name: "AccountNumber", field: "AccountNumber", displayName: "Account Number", type: "string", width: 150 },
                { name: "AccountName", field: "AccountName", displayName: "Account Name", type: "string", width: 150 },
                { name: "FiscalYear", field: "FiscalYear", displayName: "Fiscal Year", type: "number", width: 150 },
                { name: "FiscalMonth", field: "FiscalMonth", displayName: "Fiscal Month", type: "number", width: 150 },
                { name: "ActualAmount", field: "ActualAmount", displayName: "Actual Amount", type: "number", cellFilter: "currency:$:0", cellClass: "text-right", width: 120 },
                { name: "BudgetAmount", field: "BudgetAmount", displayName: "Budget Amount", type: "number", cellFilter: "currency:$:0", cellClass: "text-right", width: 120 },
                { name: "PandLGroup", field: "PandLGroup", displayName: "PandLGroup", type: "string", width: 150 },
                { name: "CategoryCode", field: "CategoryCode", displayName: "Category Code", type: "string", width: 120 },
                { name: "Company", field: "Company", displayName: "Company", type: "string", width: 120 },
                { name: "Division", field: "Division", displayName: "Division", type: "string", width: 120 },
                { name: "RegionalVicePresident", field: "RegionalVicePresident", displayName: "RVP", type: "string", width: 150 },
                { name: "SeniorRegionalManager", field: "SeniorRegionalManager", displayName: "SRM", type: "string", width: 150 },
                { name: "RegionalManager", field: "RegionalManager", displayName: "RM", type: "string", width: 150 },
                { name: "AccountManager", field: "AccountManager", displayName: "Account Manager", type: "string", width: 150 },
                { name: "BusinessUnit", field: "BusinessUnit", displayName: "Business Unit", type: "string", width: 120 }
            ],
            onRegisterApi: function (gridApi) {
                $scope.gridData = gridApi;
                gridApi.cellNav.on.navigate($scope, function (newRowcol, oldRowCol) {
                    $scope.fillTabCDADetails(newRowcol.row.entity.AccountNumber);
                });
            } // onRegisterApi

            //gridApi.cellNav.on.navigate($scope, function (newRowcol, oldRowCol) {
            //    console.log($scope.BusinessUnit);

            //    if ($scope.BusinessUnit !== null) {
            //        $scope.fillTabCDADetails($scope.BusinessUnit);
            //     } // if this is a non-period field
            //});

            //gridApi.core.on.rowsRendered($scope, function (grid) {
            //    $('#cdaSummaryGridDataMessage').hide();
            //    $('#cdaSummaryGridLoadingMessage').show();
            //});
            //   }
        }; // CDADetailsSummary

        $scope.CDADetails = {
            fastWatch: true,
            data: [],
            enableColumnResizing: true,
            flatEntityAccess: true,
            enableSorting: true,
            enableFiltering: true,
            enableRowHeaderSelection: false,
            enableRowSelection: true,
            showColumnFooter: true,
            multiSelect: false,
            exporterCsvFilename: "EmployeeMaster_" + Today + ".csv",
            exporterFieldCallback: $scope.FormatExportDates,
            exporterMenuCsv: false,
            exporterMenuPdf: false,
            enableGridMenu: true,
            paginationPageSizes: [15, 25, 50, 75, 100, 250],
            paginationPageSize: 15,
            useExternalPagination: true,
            columnDefs: [

            { name: "AccountNumber", field: "AccountNumber", displayName: "Account Number", type: "string", width: 150 },
            { name: "AccountName", field: "AccountName", displayName: "Account Name", type: "string", width: 150 },
                            { name: "GLDate", field: "GLDate", displayName: "GL Date", type: "number", width: 150 },
                            { name: "ActualAmount", field: "ActualAmount", displayName: "Actual Amount", type: "number", cellFilter: "currency:$:0", cellClass: "text-right", width: 120 },
                            { name: "BudgetAmount", field: "BudgetAmount", displayName: "Budget Amount", type: "number", cellFilter: "currency:$:0", cellClass: "text-right", width: 120 },
                            { name: "Description", field: "Description", displayName: "Description", type: "string", width: 75 },
                            { name: "Remark", field: "Remark", displayName: "Remark", type: "string", width: 90 },
                            { name: "USerName", field: "UserName", displayName: "User Name", type: "string", width: 90 },
                            { name: "BatchNo", field: "BatchNo", displayName: "Batch No", type: "string", width: 90 },
                            { name: "BatchDate", field: "BatchDate", displayName: "Batch Date", type: "string", width: 90 },
                            { name: "Source", field: "Source", displayName: "Source", type: "string", width: 90 },
                            { name: "InvoiceNo", field: "InvoiceNo", displayName: "Invoice No", type: "string", width: 90 }
            ],
            onRegisterApi: function (gridApi) {
                $scope.gridDetail = gridApi;

            } // onRegisterApi

            //gridApi.cellNav.on.navigate($scope, function (newRowcol, oldRowCol) {
            //    console.log($scope.BusinessUnit);

            //    if ($scope.BusinessUnit !== null) {
            //        $scope.fillTabCDADetails($scope.BusinessUnit);
            //     } // if this is a non-period field
            //});

            //gridApi.core.on.rowsRendered($scope, function (grid) {
            //    $('#cdaSummaryGridDataMessage').hide();
            //    $('#cdaSummaryGridLoadingMessage').show();
            //});
            //   }
        }; // CDADetailsSummary

        var deregister = $scope.$on("clearAll", function (event, data) {
            if ($scope.IncidentRepprtingGrid.data !== null)
                $scope.IncidentRepprtingGrid.data.length = 0;

            $scope.isLoadingPrimaryData = null;
            $scope.isLoadingDetailsData = null;
            $scope.isExportingData = null;
            $scope.isPrinting = null;
        }); // deregister

        $scope.$on("$destroy", deregister);
    }
]);
app.directive("cdaTab", cdaTab);

var incrementDate = function (date, addMonth) {
    var Result = new Date(date);
    Result.setMonth(Result.getMonth() + addMonth)
    return Result;
};

function cdaTab() {
    return {
        template: "<div class='grid' ui-grid='gridOptions'></div>",
        restrict: "E",
        scope: {
            options: "="
        },
        controller: "ProfitAndLossController",
        controllerAs: "vm",
        bindToController: true,
        link: function (scope, element, attrs) {
            scope.gridOptions = {
                data: scope.vm.options.Datax,
                columnDefs: [
                    { name: "firstName", field: "firstName", displayName: "First Name", type: "string", width: 150 },
                    { name: "lastName",  field: "lastName",  displayName: "Last Name",  type: "string", width: 150 },
                ]
            };
        } // link
    }; // return
} // cdaTab

您已接近解决方案,但是当您使用包含在指令中的 UI 网格时需要注意。基本上您想多次重复使用该指令,但对于您当前的设置,它只能使用一次。

在您的 link 函数中,您应该 'watch' 传递的网格数据,然后使用网格配置创建网格。
这样,当有一些数据时,您的网格将始终呈现

scope.$watch('scope.vm.options.Data', function(scope.vm.options.Data) {

if(scope.vm.options.Data) {
scope.gridOptions = {
                data: scope.vm.options.Data,
                columnDefs: [
                    { name: "firstName", field: "firstName", displayName: "First Name", type: "string", width: 150 },
                    { name: "lastName",  field: "lastName",  displayName: "Last Name",  type: "string", width: 150 },
                ]
            };
}
});

希望对您有所帮助!