Reputation: 3025
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-grid
s 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
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