Grandizer
Grandizer

Reputation: 3025

How to dynamically create multiple ui-grids with Angular

We have a situation where there are 2 constant Bootstrap tabs and any number of dynamic tabs. These dynamic tabs will need to have two ui-grids on them. At this point, I am only trying to get one ui-grid to work. I have an array of a custom set model that holds unique ID and data for each tab called TabGroups. On a click event in a different part of the screen is what triggers any new tab to be created. The tabs show up and I can get to them. I started looking into how to add the ui-grid and have been stuck for a while now. This article seems to be what I am looking for but I am not sure if, since both angular and ui-grid are updated to newer versions, things no longer work for me.

Here is what happens when a new tab is created:

$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

Here is the setup of the Directive:

app.directive("cdaTab", cdaTab);

Here is the cdaTab code:

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

Here is the HTML for the dynamic tabs:

<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>

The first one works great, tab is created and data is loaded. When the second tab is created, the tab shows but it fails to load data because it gets a null-reference exception on data. Specifically line 3,150 of the ui-grid.js file which is here (ui-grid - v3.1.0 - 2016-01-19):

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

$scope exists but uiGrid does not.

I have also noticed that it does not go into the cdaTab() method again after the first dynamic grid is created. Not sure if it should, but I would assume so.

It seems close to working but there is just something missing.

If there is a better approach to have, I am all ears. I did a fair amount of research that all seemed to point to using a Directive, so I did.

EDIT RE: a request, here is the ProfitAndLossController code:

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

Upvotes: 2

Views: 1770

Answers (1)

Pratik Bhat
Pratik Bhat

Reputation: 7614

You are close to the solution, but there is a caveat when you use UI grid wrapped in a directive. Basically you want to reuse the directive multiple times but with your current setup it can be used just once.

In your link function, you should 'watch' the grid Data that is passed on, and then create the grid using grid configurations.
This way your grid will always render when there is some data

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

Hope this helps!

Upvotes: 1

Related Questions