import {
GridApi,
GridOptions,
ICellRendererParams,
createGrid,
ColDef,
} from "ag-grid-community";
export class DetailCellRenderer {
private eGui!: HTMLDivElement;
private detailGridApi!: GridApi;
init(params: ICellRendererParams) {
// Create the main container - no padding or margin for full width
this.eGui = document.createElement('div');
this.eGui.classList.add('detail-cell-renderer');
this.eGui.style.cssText = `
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background-color: #F9F9FB;
box-sizing: border-box;
`;
// Create grid container - full width, full height, no header
const gridContainer = document.createElement('div');
gridContainer.classList.add('detail-grid-container');
gridContainer.style.cssText = `
height: 100%;
width: 100%;
margin: 0;
padding: 0;
background-color: #F9F9FB;
box-sizing: border-box;
`;
this.eGui.appendChild(gridContainer);
// Define columns for the detail grid
const detailColumnDefs: ColDef[] = [
{
field: "callId",
headerName: "Call ID",
width: 100,
pinned: 'left'
},
{
field: "direction",
headerName: "Direction",
width: 120,
cellStyle: (params) => {
return params.value === 'Outbound'
? { color: '#28a745', fontWeight: 'bold' }
: { color: '#dc3545', fontWeight: 'bold' };
}
},
{
field: "duration",
headerName: "Duration",
width: 100,
valueFormatter: (params) => `${params.value}m`
},
{
field: "subject",
headerName: "Subject",
flex: 1,
minWidth: 200
},
{
field: "date",
headerName: "Date",
width: 120,
valueFormatter: (params) => new Date(params.value).toLocaleDateString()
},
{
field: "status",
headerName: "Status",
width: 100,
cellStyle: (params) => {
const statusColors: { [key: string]: string } = {
'Completed': '#28a745',
'Missed': '#dc3545',
'In Progress': '#ffc107'
};
return {
color: statusColors[params.value] || '#6c757d',
fontWeight: 'bold'
};
}
}
];
// Grid options for the detail grid
const detailGridOptions: GridOptions = {
columnDefs: detailColumnDefs,
rowData: this.generateDetailData(params.data),
defaultColDef: {
sortable: true,
filter: true,
resizable: true,
},
suppressHorizontalScroll: false,
suppressColumnVirtualisation: true,
headerHeight: 40,
rowHeight: 35,
animateRows: true,
enableRangeSelection: true,
pagination: true,
paginationPageSize: 10,
paginationPageSizeSelector: [5, 10, 20],
onGridReady: () => {
// Auto-size columns to fit content
this.detailGridApi.sizeColumnsToFit();
}
};
// Create the detail grid
this.detailGridApi = createGrid(gridContainer, detailGridOptions);
}
getGui() {
return this.eGui;
}
destroy() {
if (this.detailGridApi) {
this.detailGridApi.destroy();
}
}
refresh(): boolean {
return false;
}
// Generate sample detail data based on the parent row
private generateDetailData(parentData: any) {
const detailData = [];
const callCount = parentData.calls || 5;
for (let i = 1; i <= Math.min(callCount, 20); i++) {
const directions = ['Inbound', 'Outbound'];
const subjects = [
'Sales inquiry',
'Technical support',
'Billing question',
'Product demo',
'Follow-up call',
'Customer onboarding',
'Renewal discussion',
'Feature request'
];
const statuses = ['Completed', 'Missed', 'In Progress'];
detailData.push({
callId: `${parentData.account}-${i.toString().padStart(3, '0')}`,
direction: directions[Math.floor(Math.random() * directions.length)],
duration: Math.floor(Math.random() * 60) + 1,
subject: subjects[Math.floor(Math.random() * subjects.length)],
date: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000).toISOString(),
status: statuses[Math.floor(Math.random() * statuses.length)]
});
}
return detailData.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
}
}
import {
ClientSideRowModelModule,
FirstDataRenderedEvent,
GridApi,
GridOptions,
ModuleRegistry,
RowApiModule,
ValidationModule,
createGrid,
} from "ag-grid-community";
import {
ColumnMenuModule,
ColumnsToolPanelModule,
ContextMenuModule,
MasterDetailModule,
} from "ag-grid-enterprise";
import { DetailCellRenderer } from "./detailCellRenderer";
import { IAccount } from "./interfaces";
ModuleRegistry.registerModules([
RowApiModule,
ClientSideRowModelModule,
ColumnsToolPanelModule,
MasterDetailModule,
ColumnMenuModule,
ContextMenuModule,
...(process.env.NODE_ENV !== "production" ? [ValidationModule] : []),
]);
let gridApi: GridApi<IAccount>;
const gridOptions: GridOptions<IAccount> = {
masterDetail: true,
detailCellRenderer: DetailCellRenderer,
detailRowHeight: 300, // Reduced since no header
detailRowAutoHeight: false, // Disable auto height for consistent layout
// CRITICAL: Remove default detail cell padding
detailCellRendererParams: {
suppressCount: true,
template: '<div class="ag-details-row ag-details-grid"></div>'
},
columnDefs: [
// group cell renderer needed for expand / collapse icons
{
field: "name",
cellRenderer: "agGroupCellRenderer",
minWidth: 200,
flex: 1
},
{
field: "account",
minWidth: 150,
flex: 1
},
{
field: "calls",
minWidth: 100,
width: 120,
type: 'numericColumn'
},
{
field: "minutes",
valueFormatter: "x.toLocaleString() + 'm'",
minWidth: 120,
width: 150,
type: 'numericColumn'
},
],
defaultColDef: {
sortable: true,
filter: true,
resizable: true,
},
// Ensure detail grids span full width
embedFullWidthRows: true,
// Performance optimizations
suppressColumnVirtualisation: true,
animateRows: true,
// Row selection
rowSelection: 'single',
onFirstDataRendered: onFirstDataRendered,
// Handle detail grid events
onRowClicked: (event) => {
if (event.node.master) {
// Toggle expand/collapse on row click
event.node.setExpanded(!event.node.expanded);
}
},
// Styling
getRowStyle: (params) => {
return {
backgroundColor: '#F9F9FB'
};
},
// Header styling
getRowClass: () => 'custom-row-background',
};
function onFirstDataRendered(params: FirstDataRenderedEvent) {
// Auto-size all columns
params.api.sizeColumnsToFit();
// Expand first row for demo
params.api.forEachNode(function (node) {
node.setExpanded(node.id === "1");
});
}
const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);
fetch("https://www.ag-grid.com/example-assets/master-detail-data.json")
.then((response) => response.json())
.then((data: IAccount[]) => {
gridApi!.setGridOption("rowData", data);
})
.catch((error) => {
console.error("Error loading data:", error);
});
<!doctype html>
<html lang="en">
<head>
<title>
Typescript Example - Master Detail Custom Detail - Simple Custom Detail
</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="robots" content="noindex" />
<link
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;700&display=swap"
rel="stylesheet"
/>
<style>
/* Master Grid Background Styling - High Specificity */
.ag-header {
background-color: #F9F9FB !important;
color: #495057 !important;
font-weight: 600 !important;
border-bottom: 1px solid #e0e4e7 !important;
}
.ag-header-cell {
background-color: #F9F9FB !important;
border-right: 1px solid #e0e4e7 !important;
}
.ag-theme-quartz .ag-header-cell-label,
.ag-theme-alpine .ag-header-cell-label,
.ag-header-cell-label {
color: #495057 !important;
}
/* Master Grid Row Styling */
.ag-theme-quartz .ag-row,
.ag-theme-alpine .ag-row,
.ag-row {
background-color: #F9F9FB !important;
}
.ag-theme-quartz .ag-row-even,
.ag-theme-alpine .ag-row-even,
.ag-row-even {
background-color: #F9F9FB !important;
}
.ag-theme-quartz .ag-row-odd,
.ag-theme-alpine .ag-row-odd,
.ag-row-odd {
background-color: #F9F9FB !important;
}
/* Master Grid Hover Effects */
.ag-theme-quartz .ag-row:hover,
.ag-theme-alpine .ag-row:hover,
.ag-row:hover {
background-color: #f0f0f5 !important;
}
/* Master Grid Cell Styling */
.ag-theme-quartz .ag-cell,
.ag-theme-alpine .ag-cell,
.ag-cell {
background-color: #Ffffff !important;
}
/* Detail Cell Renderer Styles - Full Width Override */
.detail-cell-renderer {
width: 100% !important;
box-sizing: border-box !important;
margin: 0 !important;
padding: 0 !important;
background-color: #F9F9FB !important;
}
.detail-grid-container {
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
background-color: #F9F9FB !important;
}
/* Override ag-Grid's default detail row padding/margins */
.ag-details-row {
padding: 0 !important;
margin: 0 !important;
background-color: #F9F9FB !important;
}
.ag-details-grid {
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
background-color: #F9F9FB !important;
}
/* Remove any default spacing from the detail cell */
.ag-cell-wrapper.ag-row-group-indent-0 {
padding-left: 0 !important;
}
/* Ensure the detail grid takes full width */
.detail-grid-container .ag-root-wrapper {
width: 100% !important;
background-color: #F9F9FB !important;
}
.detail-grid-container .ag-root {
width: 100% !important;
background-color: #F9F9FB !important;
}
/* Detail Grid Header Styling */
.detail-grid-container .ag-header {
background-color: #F9F9FB !important;
color: #495057 !important;
font-weight: 600 !important;
border-bottom: 1px solid #e0e4e7 !important;
}
.detail-grid-container .ag-header-cell {
background-color: #F9F9FB !important;
border-right: 1px solid #e0e4e7 !important;
}
.detail-grid-container .ag-header-cell-label {
color: #495057 !important;
}
/* Detail Grid Row Backgrounds */
.detail-grid-container .ag-row-even {
background-color: #F9F9FB !important;
}
.detail-grid-container .ag-row-odd {
background-color: #F9F9FB !important;
}
.detail-grid-container .ag-row {
background-color: #F9F9FB !important;
}
.detail-grid-container .ag-cell {
background-color: #F9F9FB !important;
}
/* Detail Grid Hover Effects */
.detail-grid-container .ag-row:hover {
background-color: #f0f0f5 !important;
transition: background-color 0.2s ease;
}
/* Detail Grid Pagination */
.detail-grid-container .ag-paging-panel {
border-top: 1px solid #e0e4e7 !important;
background-color: #F9F9FB !important;
padding: 8px 12px !important;
}
/* Grid Root Container Background */
.detail-grid-container .ag-root-wrapper-body {
background-color: #F9F9FB !important;
}
.detail-grid-container .ag-body-viewport {
background-color: #F9F9FB !important;
}
/* Custom scrollbar for the detail grid */
.detail-grid-container .ag-body-viewport::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.detail-grid-container .ag-body-viewport::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.detail-grid-container .ag-body-viewport::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
.detail-grid-container .ag-body-viewport::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.detail-cell-renderer {
padding: 10px;
}
.detail-grid-container {
height: 250px;
}
}
/* Master detail specific styling */
.ag-details-row {
padding: 0 !important;
}
.ag-details-row .ag-details-grid {
width: 100% !important;
}
</style>
<style media="only screen">
:root,
body {
height: 100%;
width: 100%;
margin: 0;
box-sizing: border-box;
-webkit-overflow-scrolling: touch;
}
html {
position: absolute;
top: 0;
left: 0;
padding: 0;
overflow: auto;
font-family: -apple-system, "system-ui", "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
"Noto Color Emoji";
}
body {
padding: 16px;
overflow: auto;
background-color: transparent;
}
</style>
</head>
<body>
<div id="myGrid" style="height: 100%"></div>
<script>
(function () {
const appLocation = "";
window.__basePath = appLocation;
})();
</script>
<script>
var appLocation = "";
var boilerplatePath = "";
var systemJsMap = {
"@ag-grid-community/styles":
"https://cdn.jsdelivr.net/npm/@ag-grid-community/styles@34.0.2",
"ag-grid-community":
"https://cdn.jsdelivr.net/npm/ag-grid-community@34.0.2",
"ag-grid-enterprise":
"https://cdn.jsdelivr.net/npm/ag-grid-enterprise@34.0.2/",
};
var systemJsPaths = {
"@ag-grid-community/locale":
"https://cdn.jsdelivr.net/npm/@ag-grid-community/locale@34.0.2/dist/package/main.cjs.js",
"ag-charts-community":
"https://cdn.jsdelivr.net/npm/ag-charts-community@12.0.2/dist/package/main.cjs.js",
"ag-charts-core":
"https://cdn.jsdelivr.net/npm/ag-charts-core@12.0.2/dist/package/main.cjs.js",
"ag-charts-enterprise":
"https://cdn.jsdelivr.net/npm/ag-charts-enterprise@12.0.2/dist/package/main.cjs.js",
"ag-charts-types":
"https://cdn.jsdelivr.net/npm/ag-charts-types@12.0.2/",
};
</script>
<script src="https://cdn.jsdelivr.net/npm/systemjs@0.19.47/dist/system.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import("main.ts").catch(function (err) {
document.body.innerHTML =
'<div class="example-error" style="background:#fdb022;padding:1rem;">' +
"Example Error: " +
err +
"</div>";
console.error(err);
});
</script>
</body>
</html>
export interface ICallRecord {
name: string;
callId: number;
duration: number;
switchCode: string;
direction: string;
number: string;
}
export interface IAccount {
name: string;
account: number;
calls: number;
minutes: number;
callRecords: ICallRecord[];
}
/* Target only the main grid container */
#myGrid .ag-header,
#myGrid .ag-header-cell {
background-color: #F9F9FB !important;
color: #495057 !important;
border-right: 1px solid #e0e4e7 !important;
}
#myGrid .ag-header {
border-bottom: 1px solid #e0e4e7 !important;
}
#myGrid .ag-header-cell-label {
color: #495057 !important;
}
/* Master Grid Row Styling - only for main grid */
#myGrid .ag-row,
#myGrid .ag-row-even,
#myGrid .ag-row-odd {
background-color: #F9F9FB !important;
}
#myGrid .ag-row:hover {
background-color: #f0f0f5 !important;
}
#myGrid .ag-cell {
background-color: #F9F9FB !important;
}
/* Detail Cell Renderer Styles - Full Width Override */
.detail-cell-renderer {
width: 100% !important;
box-sizing: border-box !important;
margin: 0 !important;
padding: 0 !important;
background-color: #F9F9FB !important;
}
.detail-grid-container {
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
background-color: #F9F9FB !important;
}
/* Override ag-Grid's default detail row padding/margins */
#myGrid .ag-details-row {
padding: 0 !important;
margin: 0 !important;
background-color: #F9F9FB !important;
}
#myGrid .ag-details-grid {
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
background-color: #F9F9FB !important;
}
/* Remove any default spacing from the detail cell */
#myGrid .ag-cell-wrapper.ag-row-group-indent-0 {
padding-left: 0 !important;
}
/* Ensure the detail grid takes full width */
.detail-grid-container .ag-root-wrapper {
width: 100% !important;
background-color: #F9F9FB !important;
}
.detail-grid-container .ag-root {
width: 100% !important;
background-color: #F9F9FB !important;
}
/* Detail Grid Header Styling */
.detail-grid-container .ag-header {
background-color: #F9F9FB !important;
color: #495057 !important;
font-weight: 600 !important;
border-bottom: 1px solid #e0e4e7 !important;
}
.detail-grid-container .ag-header-cell {
background-color: #F9F9FB !important;
border-right: 1px solid #e0e4e7 !important;
}
.detail-grid-container .ag-header-cell-label {
color: #495057 !important;
}
/* Detail Grid Row Backgrounds */
.detail-grid-container .ag-row-even,
.detail-grid-container .ag-row-odd,
.detail-grid-container .ag-row {
background-color: #F9F9FB !important;
}
.detail-grid-container .ag-cell {
background-color: #F9F9FB !important;
}
/* Detail Grid Hover Effects */
.detail-grid-container .ag-row:hover {
background-color: #f0f0f5 !important;
transition: background-color 0.2s ease;
}
/* Detail Grid Pagination */
.detail-grid-container .ag-paging-panel {
border-top: 1px solid #e0e4e7 !important;
background-color: #F9F9FB !important;
padding: 8px 12px !important;
}
/* Grid Root Container Background */
.detail-grid-container .ag-root-wrapper-body,
.detail-grid-container .ag-body-viewport {
background-color: #F9F9FB !important;
}
/* Custom scrollbar for the detail grid */
.detail-grid-container .ag-body-viewport::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.detail-grid-container .ag-body-viewport::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.detail-grid-container .ag-body-viewport::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
.detail-grid-container .ag-body-viewport::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.detail-cell-renderer {
padding: 10px;
}
.detail-grid-container {
height: 250px;
}
}
(function (global) {
process = { env: { NODE_ENV: 'development' } };
System.config({
// DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
transpiler: 'ts',
typescriptOptions: {},
meta: {
typescript: {
exports: 'ts',
},
'*.css': { loader: 'css' },
},
paths: {
// paths serve as alias
'npm:': 'https://cdn.jsdelivr.net/npm/',
...systemJsPaths,
},
// map tells the System loader where to look for things
map: {
css: (boilerplatePath.length === 0 ? `./` : `${boilerplatePath}/`) + 'css.js',
ts: 'npm:plugin-typescript@8.0.0/lib/plugin.js',
tslib: 'npm:tslib@2.3.1/tslib.js',
typescript: 'npm:typescript@5.4.5/lib/typescript.min.js',
// appLocation comes from index.html
app: appLocation,
...systemJsMap,
},
// packages tells the System loader how to load when no filename and/or no extension
packages: {
css: {},
app: {
main: './main.ts',
defaultExtension: 'ts',
},
'ag-grid-community': {
main: './dist/package/main.cjs.js',
defaultExtension: 'js',
format: 'cjs',
},
'ag-grid-enterprise': {
main: './dist/package/main.cjs.js',
defaultExtension: 'js',
format: 'cjs',
},
'ag-charts-types': {
defaultExtension: 'js',
format: 'cjs',
},
'ag-charts-core': {
defaultExtension: 'js',
format: 'cjs',
},
'ag-charts-community': {
defaultExtension: 'js',
format: 'cjs',
},
'ag-charts-enterprise': {
defaultExtension: 'js',
format: 'cjs',
},
'@ag-grid-community/locale': {
format: 'cjs',
},
},
});
})(this);
window.addEventListener('error', (e) => {
console.error('ERROR', e.message, e.filename);
});
{
"name": "ag-grid-example",
"dependencies": {
"ag-grid-community": "34.0.2",
"ag-grid-enterprise": "34.0.2"
},
"devDependencies": {
"@types/node": "^22"
}
}