Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: lazy render list and table templates #585

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libs/angular-accelerator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@onecx/integration-interface": "^5",
"@onecx/angular-integration-interface": "^5",
"@onecx/angular-remote-components": "^5",
"@onecx/angular-testing": "^5",
"chart.js": "^4.4.3",
"d3-scale-chromatic": "^3.1.0",
"rxjs": "~7.8.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,17 @@
<ng-template #listItem let-rows pTemplate="listItem">
<div class="p-grid p-nogutter grid grid-nogutter">
<ng-template ngFor let-item [ngForOf]="rows">
@defer (on viewport; on idle){
<ng-container
[ngTemplateOutlet]="_listItem ? _listItem : defaultListItem"
[ngTemplateOutletContext]="{
item:item,
columnTemplates: columnTemplates
}"
></ng-container>
} @placeholder {
<div style="width: 100%; height: 80px"></div>
}
</ng-template>
</div>
</ng-template>
Expand Down Expand Up @@ -87,7 +91,7 @@
</div>
</ng-template>
<ng-template #defaultListItem let-item="item" let-columnTemplates="columnTemplates">
<div class="col-12">
<div class="col-12" *ngIf="columnTemplates">
<div class="data-list-items p-3">
<div class="item-name-row flex flex-row justify-content-between">
<div class="item-name mr-3 text-2xl font-medium align-content-center">
Expand Down Expand Up @@ -190,16 +194,19 @@
[ocxTooltipOnOverflow]="col.columnType === columnType.TRANSLATION_KEY ? (resolveFieldData(item,col.id) | translate) : resolveFieldData(item, col.id)"
tooltipPosition="top"
>
@defer(on viewport;){
<ng-container
[ngTemplateOutlet]="
_listValue ? _listValue: defaultListValue
"
[ngTemplateOutlet]="_listValue ? _listValue: defaultListValue"
[ngTemplateOutletContext]="{
rowObject: item,
column: col,
columnTemplates: columnTemplates
}"
></ng-container>
}"
>
</ng-container>
} @placeholder {
<p-skeleton width="5rem" />
}
</div>
</div>
</div>
Expand All @@ -214,6 +221,7 @@

<ng-template #defaultListValue let-rowObject="rowObject" let-column="column" let-columnTemplates="columnTemplates">
<ng-container
*ngIf="columnTemplates[column.id]"
[ngTemplateOutlet]="columnTemplates[column.id]"
[ngTemplateOutletContext]="{
rowObject: rowObject,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ export class DataListGridComponent extends DataSortBase implements OnInit, DoChe
this._columns$.next(value)
const obs = value.map((c) => this.getTemplate(c))
this.columnTemplates$ = combineLatest(obs).pipe(
map((values) => Object.fromEntries(value.map((c, i) => [c.id, values[i]])))
map((values) => Object.fromEntries(value.map((c, i) => [c.id, values[i]]))),
debounceTime(50)
)
}
@Input() name = ''
Expand Down Expand Up @@ -648,8 +649,7 @@ export class DataListGridComponent extends DataSortBase implements OnInit, DoChe
null
)
}
}),
debounceTime(50)
})
)
}
return this.templatesObservables[column.id]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@
</tr>
</ng-template>
<ng-template pTemplate="body" let-rowObject>
<tr>
<tr *ngIf="columnTemplates">
<td *ngIf="selectionChangedObserved">
<p-checkbox
*ngIf="isRowSelectionDisabled(rowObject) && isSelected(rowObject); else defaultCheckbox"
Expand All @@ -254,15 +254,21 @@
<ng-container *ngTemplateOutlet="actionColumn; context: {localRowObject: rowObject}"></ng-container>
</ng-container>
<td *ngFor="let column of columns">
@defer(on viewport){
<ng-container
*ngIf="columnTemplates[column.id]"
[ngTemplateOutlet]="
_cell ? _cell: columnTemplates[column.id]
_cell ? _cell: columnTemplates[column.id]
"
[ngTemplateOutletContext]="{
rowObject: rowObject,
column: column,
}"
></ng-container>
>
</ng-container>
} @placeholder {
<p-skeleton width="3rem" />
}
</td>
<ng-container *ngIf="actionColumnPosition === 'right';">
<ng-container *ngTemplateOutlet="actionColumn; context: {localRowObject: rowObject}"></ng-container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ export class DataTableComponent extends DataSortBase implements OnInit, AfterCon
const obs = value.map((c) => this.getTemplate(c, TemplateType.CELL))
const filterObs = value.map((c) => this.getTemplate(c, TemplateType.FILTERCELL))
this.columnTemplates$ = combineLatest(obs).pipe(
map((values) => Object.fromEntries(value.map((c, i) => [c.id, values[i]])))
map((values) => Object.fromEntries(value.map((c, i) => [c.id, values[i]]))),
debounceTime(50)
)
this.columnFilterTemplates$ = combineLatest(filterObs).pipe(
map((values) => Object.fromEntries(value.map((c, i) => [c.id, values[i]])))
Expand Down Expand Up @@ -908,8 +909,7 @@ export class DataTableComponent extends DataSortBase implements OnInit, AfterCon
return columnTemplate
}
return this.getColumnTypeTemplate(templates, column.columnType, templateType)
}),
debounceTime(50)
})
)
}
return templatesData.templatesObservables[column.id]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,8 @@ describe('InteractiveDataViewComponent', () => {
viewItemEvent = undefined
editItemEvent = undefined
deleteItemEvent = undefined

console.log("Global IntersectionObserver", global.IntersectionObserver)
})

it('should create', () => {
Expand Down Expand Up @@ -433,7 +435,6 @@ describe('InteractiveDataViewComponent', () => {
]
const sortButton = await tableHeaders[0].getSortButton()
await sortButton.click()

tableRows = (await dataTable?.getRows()) ?? []
const rows = await parallel(() => tableRows.map((row) => row.getData()))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import { of } from 'rxjs'
import { Filter, FilterType } from '../../model/filter.model'
import { FilterViewComponent } from '../filter-view/filter-view.component'
import { FocusTrapModule } from 'primeng/focustrap'
import { TooltipOnOverflowDirective } from '../../directives/tooltipOnOverflow.directive'
import { SkeletonModule } from 'primeng/skeleton'

type InteractiveDataViewInputTypes = Pick<
InteractiveDataViewComponent,
Expand Down Expand Up @@ -85,6 +87,7 @@ const InteractiveDataViewComponentSBConfig: Meta<InteractiveDataViewComponent> =
DataListGridComponent,
DataListGridSortingComponent,
FilterViewComponent,
TooltipOnOverflowDirective
],
imports: [
TableModule,
Expand All @@ -105,6 +108,7 @@ const InteractiveDataViewComponentSBConfig: Meta<InteractiveDataViewComponent> =
OverlayPanelModule,
FocusTrapModule,
ChipModule,
SkeletonModule
],
}),
],
Expand Down Expand Up @@ -192,6 +196,71 @@ export const WithMockData = {
},
}

function generateColumns(count: number) {
const data = []
for (let i = 0; i < count; i++) {
const row = {
id: `${i + 1}`,
columnType: ColumnType.STRING,
nameKey: `Product${i + 1}`,
sortable: false,
filterable: true,
predefinedGroupKeys: ['test'],
};
data.push(row)
}
return data
}

function generateRows(rowCount: number, columnCount: number) {
const data = []
for (let i = 0; i < rowCount; i++) {
const row = {} as any
for(let j = 0; j < columnCount; j++) {
row[j+1] = `Test value for ${j+1}`
}
data.push(row)
}
return data
}

function generateColumnTemplates(columnCount: number) {
let templates = ''
for(let i = 0; i < columnCount; i++) {
templates += `
<ng-template pTemplate="${i+1}IdListValue" let-rowObject="rowObject" let-column="column">
<ng-container>${i+1} {{ rowObject[${i+1}] }} </ng-container>
</ng-template>`
}
return templates
}

const columnCount = 30;
const rowCount = 500

const HugeMockDataTemplate: StoryFn<InteractiveDataViewComponent> = (args) => ({
props: args,
template: `
<ocx-interactive-data-view [emptyResultsMessage]="emptyResultsMessage" [columns]="columns" [data]="data">
${generateColumnTemplates(Math.ceil(columnCount/3))}
</ocx-interactive-data-view>`,
})

export const WithHugeMockData = {
argTypes: {
componentStateChanged: { action: 'componentStateChanged' },
selectionChanged: { action: 'selectionChanged' },
},
render: HugeMockDataTemplate,
args: {
columns: generateColumns(columnCount),
data: generateRows(rowCount, columnCount),
emptyResultsMessage: 'No results',
selectedRows: [],
pageSize: 50,
},
}

export const WithPageSizes = {
argTypes: {
componentStateChanged: { action: 'componentStateChanged' },
Expand Down
7 changes: 6 additions & 1 deletion libs/angular-accelerator/testing/data-list-grid.harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@ import { ContentContainerComponentHarness, TestElement, parallel } from '@angula
import { PPaginatorHarness } from '@onecx/angular-testing'
import { DefaultGridItemHarness } from './default-grid-item.harness'
import { DefaultListItemHarness } from './default-list-item.harness'
import { waitForDeferredViewsToBeRendered } from '@onecx/angular-testing'

export class DataListGridHarness extends ContentContainerComponentHarness {
static hostSelector = 'ocx-data-list-grid'

getDefaultGridItems = this.locatorForAll(DefaultGridItemHarness)
getDefaultListItems = this.locatorForAll(DefaultListItemHarness)
getPaginator = this.locatorFor(PPaginatorHarness)
getMenuButton = this.locatorFor(`[name="data-grid-item-menu-button"]`)

async getDefaultListItems() {
await waitForDeferredViewsToBeRendered(this)
return await this.locatorForAll(DefaultListItemHarness)()
}

async getActionButtons(actionButtonType: 'list' | 'grid' | 'grid-hidden') {
if (actionButtonType === 'list') {
return await this.locatorForAll(`[name="data-list-action-button"]`)()
Expand Down
2 changes: 2 additions & 0 deletions libs/angular-accelerator/testing/default-list-item.harness.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ComponentHarness } from '@angular/cdk/testing'
import { ButtonHarness, DivHarness } from '@onecx/angular-testing'
import { waitForDeferredViewsToBeRendered } from '@onecx/angular-testing'

export class DefaultListItemHarness extends ComponentHarness {
static hostSelector = '.data-list-items'
Expand All @@ -12,6 +13,7 @@ export class DefaultListItemHarness extends ComponentHarness {
private getAllDivs = this.locatorForAll(DivHarness)

async getData() {
await waitForDeferredViewsToBeRendered(this)
const isDataListItemsDiv = await Promise.all(
(await this.getAllDivs()).map((innerDivHarness) => this.checkDivsHasClasses(innerDivHarness))
)
Expand Down
6 changes: 6 additions & 0 deletions libs/angular-accelerator/testing/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ensureIntersectionObserverMockExists } from '@onecx/angular-testing'

export * from './column-group-selection.harness'
export * from './custom-group-column-selector.harness'
export * from './data-layout-selection.harness'
Expand All @@ -18,3 +20,7 @@ export * from './search-header.harness'
export * from '@angular/cdk/testing'
export * from '@angular/cdk/testing/testbed'
export * from '@onecx/angular-testing'

ensureIntersectionObserverMockExists()
declare let global: any
global.origin = ''
3 changes: 3 additions & 0 deletions libs/angular-testing/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ export * from './lib/harnesses/table-row.harness'

export * from '@angular/cdk/testing'
export * from '@angular/cdk/testing/testbed'

export * from './lib/mocks/IntersectionObserverMock'
export * from './lib/utils/waitForDeferredViewsToBeRendered'
1 change: 1 addition & 0 deletions libs/angular-testing/src/lib/harnesses/button.harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class ButtonHarness extends ComponentHarness {
} else {
console.warn('Button cannot be clicked, because it is disabled!')
}
await this.waitForTasksOutsideAngular()
}

async isDisabled(): Promise<boolean> {
Expand Down
2 changes: 2 additions & 0 deletions libs/angular-testing/src/lib/harnesses/table-row.harness.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ContentContainerComponentHarness } from '@angular/cdk/testing'
import { ButtonHarness } from './button.harness'
import { waitForDeferredViewsToBeRendered } from '../utils/waitForDeferredViewsToBeRendered'

export class TableRowHarness extends ContentContainerComponentHarness {
static hostSelector = 'tbody > tr'
Expand All @@ -10,6 +11,7 @@ export class TableRowHarness extends ContentContainerComponentHarness {
getDeleteButton = this.locatorForOptional(ButtonHarness.with({ class: 'deleteTableRowButton' }))

async getData(): Promise<string[]> {
await waitForDeferredViewsToBeRendered(this)
const tds = await this.locatorForAll('td')()
const isActionsTd = await Promise.all(tds.map((t) => t.hasClass('actions')))
const textTds = tds.filter((_v, index) => !isActionsTd[index])
Expand Down
42 changes: 42 additions & 0 deletions libs/angular-testing/src/lib/mocks/IntersectionObserverMock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export class IntersectionObserverMock {
private callback: any
private entries: any[]
root: any
rootMargin: any
thresholds: any
constructor(callback: any, _options: any) {
this.callback = callback
this.entries = []
}

observe(target: Element) {
const entry = {
boundingClientRect: target.getBoundingClientRect(),
intersectionRatio: 1,
isIntersecting: true,
target: target,
}
this.entries.push(entry)
setTimeout(() => {
this.callback(this.entries, this)
})
}

takeRecords() {
return this.entries
}

unobserve(target: any) {
this.entries = this.entries.filter((entry) => entry.target !== target)
}

disconnect() {
this.entries = []
}
}

export function ensureIntersectionObserverMockExists() {
if (!global.IntersectionObserver || global.IntersectionObserver !== IntersectionObserverMock) {
global.IntersectionObserver = IntersectionObserverMock
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ComponentHarness, ContentContainerComponentHarness } from '@angular/cdk/testing'

export async function waitForDeferredViewsToBeRendered(harness: ComponentHarness | ContentContainerComponentHarness) {
return await new Promise<void>((resolve) => {
setTimeout(() => {
console.warn(
'waitForTasksOutsideAngular has not finished within 500ms. We are not waiting any longer to not cause timeouts.'
);
(harness as any).forceStabilize().then(() => resolve())
}, 500)
// waitForTasksOutsideAngular makes sure that the observe method of the IntersectionObserver is called for each defer block.
// setTimeout makes sure that we are only continuing after the IntersectionObserverMock has called ther callback for each
// defer block, because js scheduling is making sure that all methods which are scheduled via setTimeout are executed in the
// respective order. This guarentees that the resolve method is called after the defer block was rendered.
;(harness as any).waitForTasksOutsideAngular().then(() => setTimeout(() => resolve()))
})
}
Loading
Loading