Skip to content
This repository was archived by the owner on Jun 1, 2025. It is now read-only.

Commit e56d435

Browse files
committed
fix: Row Detail should also work with fixed grid height or no autoHeight
1 parent 6729d98 commit e56d435

File tree

6 files changed

+54
-77
lines changed

6 files changed

+54
-77
lines changed

src/app/examples/grid45-detail.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<h4>{{ model.companyName }} - Order Details (id: {{ model.id }})</h4>
33
<div class="container-fluid">
44
<angular-slickgrid
5+
class="innergrid"
56
[gridId]="innerGridId"
67
[columnDefinitions]="innerColDefs"
78
[gridOptions]="innerGridOptions"

src/app/examples/grid45-detail.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export interface OrderData {
2121
}
2222

2323
@Component({
24-
styles: ['.detail-label { display: inline-flex; align-items: center; gap: 4px; padding: 4px; }', 'label { font-weight: 600; }'],
24+
styles: ['.innergrid { --slick-header-menu-display: inline-block; }'],
2525
templateUrl: './grid45-detail.component.html',
2626
encapsulation: ViewEncapsulation.None,
2727
})
@@ -47,7 +47,7 @@ export class Grid45DetailComponent implements OnDestroy, OnInit {
4747
}
4848

4949
ngOnDestroy(): void {
50-
console.log('destroying row detail');
50+
console.log('destroying row detail', this.model.id);
5151
}
5252

5353
angularGridReady(angularGrid: AngularGridInstance) {

src/app/examples/grid45.component.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export class Grid45Component implements OnDestroy, OnInit {
1919
gridOptions!: GridOption;
2020
angularGrid!: AngularGridInstance;
2121
dataset: Distributor[] = [];
22-
detailViewRowCount = 8;
22+
detailViewRowCount = 9;
2323
showSubTitle = true;
2424
isUsingInnerGridStatePresets = false;
2525
serverWaitDelay = FAKE_SERVER_DELAY;
@@ -100,6 +100,7 @@ export class Grid45Component implements OnDestroy, OnInit {
100100
this.gridOptions = {
101101
autoResize: {
102102
container: '#demo-container',
103+
autoHeight: false, // works with/without autoHeight
103104
bottomPadding: 20,
104105
},
105106
autoHeight: false,
@@ -230,8 +231,6 @@ export class Grid45Component implements OnDestroy, OnInit {
230231

231232
toggleSubTitle() {
232233
this.showSubTitle = !this.showSubTitle;
233-
const action = this.showSubTitle ? 'remove' : 'add';
234-
document.querySelector('.subtitle')?.classList[action]('hidden');
235234
this.angularGrid.resizerService?.resizeGrid(1);
236235
}
237236
}

src/app/modules/angular-slickgrid/extensions/__tests__/slickRowDetailView.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,24 +175,24 @@ describe('SlickRowDetailView', () => {
175175
jest.spyOn(gridStub, 'getOptions').mockReturnValue(gridOptionsMock);
176176

177177
plugin.init(gridStub);
178-
const output = await (gridOptionsMock.rowDetailView as RowDetailView).preTemplate!();
178+
const output = (await (gridOptionsMock.rowDetailView as RowDetailView).preTemplate!()) as HTMLElement;
179179

180-
expect(output).toEqual(`<div class="${PRELOAD_CONTAINER_PREFIX}"></div>`);
180+
expect(output.outerHTML).toEqual(`<div class="${PRELOAD_CONTAINER_PREFIX}"></div>`);
181181
});
182182

183183
it('should provide a sanitized "postTemplate" when only a "viewComponent" is provided (meaning no "postTemplate" is originally provided)', async () => {
184184
(gridOptionsMock.rowDetailView as RowDetailView).viewComponent = TestComponent;
185185
jest.spyOn(gridStub, 'getOptions').mockReturnValue(gridOptionsMock);
186186

187-
const output = await gridOptionsMock.rowDetailView!.postTemplate!({ id: 'field1', field: 'field1' });
188-
expect(output).toEqual(`<div class="${ROW_DETAIL_CONTAINER_PREFIX}field1"></div>`);
187+
const output = (await gridOptionsMock.rowDetailView!.postTemplate!({ id: 'field1', field: 'field1' })) as HTMLElement;
188+
expect(output.outerHTML).toEqual(`<div class="${ROW_DETAIL_CONTAINER_PREFIX}field1"></div>`);
189189
});
190190

191191
it('should define "datasetIdPropertyName" with different "id" and provide a sanitized "postTemplate" when only a "viewComponent" is provided (meaning no "postTemplate" is originally provided)', async () => {
192192
(gridOptionsMock.rowDetailView as RowDetailView).viewComponent = TestComponent;
193193
gridOptionsMock.datasetIdPropertyName = 'rowId';
194-
const output = await gridOptionsMock.rowDetailView!.postTemplate!({ rowId: 'field1', field: 'field1' });
195-
expect(output).toEqual(`<div class="${ROW_DETAIL_CONTAINER_PREFIX}field1"></div>`);
194+
const output = (await gridOptionsMock.rowDetailView!.postTemplate!({ rowId: 'field1', field: 'field1' })) as HTMLElement;
195+
expect(output.outerHTML).toEqual(`<div class="${ROW_DETAIL_CONTAINER_PREFIX}field1"></div>`);
196196
});
197197

198198
describe('registered addon', () => {

src/app/modules/angular-slickgrid/extensions/slickRowDetailView.ts

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
import {
1010
addToArrayWhenNotExists,
1111
castObservableToPromise,
12+
createDomElement,
1213
SlickEventData,
1314
SlickRowSelectionModel,
1415
unsubscribeAll,
@@ -119,15 +120,12 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
119120
// when those are Angular View/ViewModel, we need to create View Component & provide the html containers to the Plugin (preTemplate/postTemplate methods)
120121
if (!this.gridOptions.rowDetailView.preTemplate) {
121122
this._preloadComponent = this.gridOptions?.rowDetailView?.preloadComponent;
122-
this.addonOptions.preTemplate = () =>
123-
this._grid.sanitizeHtmlString(`<div class="${PRELOAD_CONTAINER_PREFIX}"></div>`) as string;
123+
this.addonOptions.preTemplate = () => createDomElement('div', { className: `${PRELOAD_CONTAINER_PREFIX}` });
124124
}
125125
if (!this.gridOptions.rowDetailView.postTemplate) {
126126
this._viewComponent = this.gridOptions?.rowDetailView?.viewComponent;
127127
this.addonOptions.postTemplate = (itemDetail: any) =>
128-
this._grid.sanitizeHtmlString(
129-
`<div class="${ROW_DETAIL_CONTAINER_PREFIX}${itemDetail[this.datasetIdPropName]}"></div>`
130-
) as string;
128+
createDomElement('div', { className: `${ROW_DETAIL_CONTAINER_PREFIX}${itemDetail[this.datasetIdPropName]}` });
131129
}
132130

133131
// this also requires the Row Selection Model to be registered as well
@@ -247,21 +245,19 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
247245

248246
/** Redraw the necessary View Component */
249247
redrawViewComponent(createdView: CreatedView) {
250-
const containerElements = this.gridContainerElement.getElementsByClassName(`${ROW_DETAIL_CONTAINER_PREFIX}${createdView.id}`);
251-
if (containerElements?.length >= 0) {
248+
const containerElement = this.gridContainerElement.querySelector(`.${ROW_DETAIL_CONTAINER_PREFIX}${createdView.id}`);
249+
if (containerElement) {
252250
this.renderViewModel(createdView.dataContext);
253251
}
254252
}
255253

256254
/** Render (or re-render) the View Component (Row Detail) */
257255
renderPreloadView() {
258-
const containerElements = this.gridContainerElement.getElementsByClassName(
259-
`${PRELOAD_CONTAINER_PREFIX}`
260-
) as HTMLCollectionOf<HTMLElement>;
261-
if (this._preloadComponent && containerElements?.length >= 0) {
256+
const containerElement = this.gridContainerElement.querySelector(`.${PRELOAD_CONTAINER_PREFIX}`);
257+
if (this._preloadComponent && containerElement) {
262258
const preloadComp = this.angularUtilService.createAngularComponentAppendToDom(
263259
this._preloadComponent,
264-
containerElements[containerElements.length - 1],
260+
containerElement,
265261
{},
266262
{ sanitizer: this._grid.sanitizeHtmlString }
267263
);
@@ -271,15 +267,14 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
271267

272268
/** Render (or re-render) the View Component (Row Detail) */
273269
renderViewModel(item: any): CreatedView | undefined {
274-
const containerElements = this.gridContainerElement.getElementsByClassName(
275-
`${ROW_DETAIL_CONTAINER_PREFIX}${item[this.datasetIdPropName]}`
276-
) as HTMLCollectionOf<HTMLElement>;
277-
278-
if (this._viewComponent && containerElements?.length > 0) {
270+
const containerElement = this.gridContainerElement.querySelector(
271+
`.${ROW_DETAIL_CONTAINER_PREFIX}${item[this.datasetIdPropName]}`
272+
);
273+
if (this._viewComponent && containerElement) {
279274
// render row detail
280275
const componentOutput = this.angularUtilService.createAngularComponentAppendToDom(
281276
this._viewComponent,
282-
containerElements[containerElements.length - 1],
277+
containerElement,
283278
{
284279
model: item,
285280
addon: this,
@@ -291,6 +286,7 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
291286
sanitizer: this._grid.sanitizeHtmlString,
292287
}
293288
);
289+
294290
if (componentOutput?.componentRef) {
295291
const viewObj = this._views.find((obj) => obj.id === item[this.datasetIdPropName]);
296292
if (viewObj) {
@@ -309,22 +305,21 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
309305

310306
protected disposeViewByItem(item: any, removeFromArray = false): void {
311307
const foundViewIndex = this._views.findIndex((view: CreatedView) => view.id === item[this.datasetIdPropName]);
312-
if (foundViewIndex >= 0 && foundViewIndex in this._views) {
313-
const expandedView = this._views[foundViewIndex];
314-
this.disposeView(expandedView);
308+
if (foundViewIndex >= 0) {
309+
this.disposeView(this._views[foundViewIndex]);
315310
if (removeFromArray) {
316311
this._views.splice(foundViewIndex, 1);
317312
}
318313
}
319314
}
320315

321316
protected disposeView(expandedView: CreatedView): CreatedView | void {
317+
expandedView.rendered = false;
322318
const compRef = expandedView?.componentRef;
323319
if (compRef) {
324320
this.appRef.detachView(compRef.hostView);
325321
if (typeof compRef?.destroy === 'function') {
326322
compRef.destroy();
327-
expandedView.rendered = false;
328323
}
329324
return expandedView;
330325
}
@@ -358,8 +353,10 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
358353
}
359354

360355
if (!awaitedItemDetail || !(this.datasetIdPropName in awaitedItemDetail)) {
361-
throw new Error(`[Angular-Slickgrid] could not process the Row Detail, you must make sure that your "process" callback
362-
(a Promise or an HttpClient call returning an Observable) returns an item object that has an "${this.datasetIdPropName}" property`);
356+
throw new Error(
357+
'[Angular-Slickgrid] could not process the Row Detail, you must make sure that your "process" callback ' +
358+
`returns an item object that has an "${this.datasetIdPropName}" property`
359+
);
363360
}
364361

365362
// notify the plugin with the new item details
@@ -382,8 +379,7 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
382379
dataContext: args.item,
383380
rendered: false,
384381
};
385-
const idPropName = this.gridOptions.datasetIdPropertyName || 'id';
386-
addToArrayWhenNotExists(this._views, viewInfo, idPropName);
382+
addToArrayWhenNotExists(this._views, viewInfo, this.datasetIdPropName);
387383
} else {
388384
// collapsing, so dispose of the View/Component
389385
this.disposeViewByItem(args.item, true);
@@ -392,7 +388,7 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
392388

393389
/** When Row comes back to Viewport Range, we need to redraw the View */
394390
protected handleOnRowBackToViewportRange(_e: SlickEventData<OnRowBackToViewportRangeArgs>, args: OnRowBackToViewportRangeArgs) {
395-
const viewModel = Array.from(this._views).find((x) => x.id === args.rowId);
391+
const viewModel = this._views.find((x) => x.id === args.rowId);
396392
if (viewModel && !viewModel.rendered) {
397393
this.redrawViewComponent(viewModel);
398394
}

test/cypress/e2e/example45.cy.ts

Lines changed: 20 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe('Example 45 - Row Detail with inner Grid', () => {
1616
.each(($child, index) => expect($child.text()).to.eq(rootGridTitles[index]));
1717
});
1818

19-
it('should row detail height to 8 rows and change server delay to 40ms for faster testing', () => {
19+
it('should set row detail height to 8 rows and change server delay to 40ms for faster testing', () => {
2020
cy.get('[data-test="detail-view-row-count"]').clear().type('8');
2121
cy.get('[data-test="set-count-btn"]').click();
2222
cy.get('[data-test="server-delay"]').type('{backspace}');
@@ -25,7 +25,7 @@ describe('Example 45 - Row Detail with inner Grid', () => {
2525
it('should open the Row Detail of the 2nd row and expect to find an inner grid with all inner column titles', () => {
2626
cy.get('.slick-cell.detail-view-toggle:nth(1)').click().wait(40);
2727

28-
cy.get('.slick-cell + .dynamic-cell-detail').find('h4').should('contain', `- Order Details (id: ${1})`);
28+
cy.get('.slick-cell + .dynamic-cell-detail').find('h4').should('contain', '- Order Details (id: 1)');
2929

3030
cy.get('#innergrid-1')
3131
.find('.slick-header-columns')
@@ -35,12 +35,7 @@ describe('Example 45 - Row Detail with inner Grid', () => {
3535

3636
it('should sort 2nd Row Detail inner grid "Freight" column in ascending order and filter "Ship City" with "m" and expect 2 sorted rows', () => {
3737
cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 0);
38-
cy.get('#innergrid-1')
39-
.find('.slick-header-column:nth(2)')
40-
.trigger('mouseover')
41-
.children('.slick-header-menu-button')
42-
.invoke('show')
43-
.click();
38+
cy.get('#innergrid-1').find('.slick-header-column:nth(2)').children('.slick-header-menu-button').click();
4439

4540
cy.get('#innergrid-1 .slick-header-menu .slick-menu-command-list')
4641
.should('be.visible')
@@ -132,12 +127,7 @@ describe('Example 45 - Row Detail with inner Grid', () => {
132127

133128
cy.get('.slick-cell + .dynamic-cell-detail').find('h4').should('contain', `- Order Details (id: ${1})`);
134129

135-
cy.get('#innergrid-1')
136-
.find('.slick-header-column:nth(2)')
137-
.trigger('mouseover')
138-
.children('.slick-header-menu-button')
139-
.invoke('show')
140-
.click();
130+
cy.get('#innergrid-1').find('.slick-header-column:nth(2)').children('.slick-header-menu-button').click();
141131

142132
cy.get('#innergrid-1 .slick-header-menu .slick-menu-command-list')
143133
.should('be.visible')
@@ -181,12 +171,7 @@ describe('Example 45 - Row Detail with inner Grid', () => {
181171

182172
cy.get('.slick-cell + .dynamic-cell-detail').find('h4').should('contain', `- Order Details (id: ${1})`);
183173

184-
cy.get('#innergrid-1')
185-
.find('.slick-header-column:nth(2)')
186-
.trigger('mouseover')
187-
.children('.slick-header-menu-button')
188-
.invoke('show')
189-
.click();
174+
cy.get('#innergrid-1').find('.slick-header-column:nth(2)').children('.slick-header-menu-button').click();
190175

191176
cy.get('#innergrid-1 .slick-header-menu .slick-menu-command-list')
192177
.should('be.visible')
@@ -235,12 +220,7 @@ describe('Example 45 - Row Detail with inner Grid', () => {
235220

236221
cy.get('.slick-cell + .dynamic-cell-detail').find('h4').should('contain', `- Order Details (id: ${1})`);
237222

238-
cy.get('#innergrid-1')
239-
.find('.slick-header-column:nth(2)')
240-
.trigger('mouseover')
241-
.children('.slick-header-menu-button')
242-
.invoke('show')
243-
.click();
223+
cy.get('#innergrid-1').find('.slick-header-column:nth(2)').children('.slick-header-menu-button').click();
244224

245225
cy.get('#innergrid-1 .slick-header-menu .slick-menu-command-list')
246226
.should('be.visible')
@@ -264,11 +244,7 @@ describe('Example 45 - Row Detail with inner Grid', () => {
264244

265245
cy.get('.slick-cell + .dynamic-cell-detail').find('h4').should('contain', `- Order Details (id: ${2})`);
266246

267-
cy.get('#innergrid-2 .slick-header-column:nth(2)')
268-
.trigger('mouseover')
269-
.children('.slick-header-menu-button')
270-
.invoke('show')
271-
.click();
247+
cy.get('#innergrid-2 .slick-header-column:nth(2)').children('.slick-header-menu-button').click();
272248

273249
cy.get('#innergrid-2 .slick-header-menu .slick-menu-command-list')
274250
.should('be.visible')
@@ -289,10 +265,11 @@ describe('Example 45 - Row Detail with inner Grid', () => {
289265

290266
it('should go to the bottom end of the grid and open row 987', () => {
291267
cy.get('#grid45').type('{ctrl}{end}', { release: false });
292-
cy.wait(50);
293268
cy.get('.slick-row[data-row=1001] .detail-view-toggle').first().click();
294269

295-
cy.get('#innergrid-987 .search-filter.filter-orderId').clear().type('>987');
270+
cy.get('#innergrid-987 .search-filter.filter-orderId').as('orderIdSearch');
271+
cy.get('@orderIdSearch').clear();
272+
cy.get('@orderIdSearch').type('>987');
296273
cy.get('.slick-empty-data-warning').should('be.visible');
297274
});
298275

@@ -315,7 +292,9 @@ describe('Example 45 - Row Detail with inner Grid', () => {
315292
it('should go back to the bottom of the grid and still expect row detail 987 to be opened with same filter and no rows inside it', () => {
316293
cy.get('#grid45').type('{ctrl}{end}', { release: false });
317294

318-
cy.get('#innergrid-987 .search-filter.filter-orderId').clear().type('>987');
295+
cy.get('#innergrid-987 .search-filter.filter-orderId').as('orderIdSearch');
296+
cy.get('@orderIdSearch').clear();
297+
cy.get('@orderIdSearch').type('>987');
319298
cy.get('.slick-empty-data-warning').should('be.visible');
320299
});
321300

@@ -351,7 +330,9 @@ describe('Example 45 - Row Detail with inner Grid', () => {
351330
it('should change Row Detail panel height to 15, open 2nd and 3rd then execute PageDown twice', () => {
352331
ROW_DETAIL_PANEL_COUNT = 15;
353332
cy.get('[data-test="collapse-all-btn"]').click();
354-
cy.get('[data-test="detail-view-row-count"]').clear().type('15');
333+
cy.get('[data-test="detail-view-row-count"]').as('rowCount');
334+
cy.get('@rowCount').clear();
335+
cy.get('@rowCount').type('15');
355336
cy.get('[data-test="set-count-btn"]').click();
356337

357338
cy.get('.slick-cell.detail-view-toggle:nth(1)').click().wait(40);
@@ -363,9 +344,7 @@ describe('Example 45 - Row Detail with inner Grid', () => {
363344
cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München');
364345

365346
// open 3rd row detail
366-
cy.get(`.slick-row[data-row="${ROW_DETAIL_PANEL_COUNT - 1}"] .slick-cell:nth(0)`)
367-
.click()
368-
.wait(40);
347+
cy.get(`.slick-row[data-row="14"] .slick-cell:nth(0)`).click().wait(40);
369348

370349
// 3rd row detail
371350
cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281');
@@ -393,7 +372,9 @@ describe('Example 45 - Row Detail with inner Grid', () => {
393372
it('should change Row Detail panel height back to 8, open 2nd and 3rd and filter Company ID with "1..2" and expect only these 2 rows to be rendered in the grid', () => {
394373
ROW_DETAIL_PANEL_COUNT = 8;
395374
cy.get('[data-test="collapse-all-btn"]').click();
396-
cy.get('[data-test="detail-view-row-count"]').clear().type('8');
375+
cy.get('[data-test="detail-view-row-count"]').as('rowCount');
376+
cy.get('@rowCount').clear();
377+
cy.get('@rowCount').type('8');
397378
cy.get('[data-test="set-count-btn"]').click();
398379

399380
cy.get('.slick-cell.detail-view-toggle:nth(1)').click().wait(40);

0 commit comments

Comments
 (0)