diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index 4e316a288..6e9da0e08 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -29,6 +29,9 @@ jobs: - name: Install run: npm ci + - name: Verify generated routes + run: npm run build:routes && git diff --exit-code + - name: Build and package library run: npm run package diff --git a/.prettierignore b/.prettierignore index 7601ace55..98b7295fc 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,6 +7,10 @@ tmp playwright/snapshots CHANGELOG.md +# Generated files +src/app/app.routes.ts +src/app/app.web-components.ts + # Skills managed by skills.sh .agents/skills/playwright-cli .agents/skills/gh-stack diff --git a/angular.json b/angular.json index d6bc284bd..28d175b04 100644 --- a/angular.json +++ b/angular.json @@ -19,6 +19,14 @@ "sourceRoot": "src", "prefix": "app", "architect": { + "build-routes": { + "builder": "@ngx-datatable/example-routes-builder:example-routes", + "options": { + "sourceRoot": "src/app", + "routesFile": "src/app/app.routes.ts", + "webComponentsFile": "src/app/app.web-components.ts" + } + }, "build": { "builder": "@angular/build:application", "options": { @@ -99,6 +107,20 @@ } } }, + "example-routes-builder": { + "projectType": "application", + "root": "tools/example-routes-builder", + "sourceRoot": "tools/example-routes-builder", + "architect": { + "lint": { + "builder": "@angular-eslint/builder:lint", + "options": { + "lintFilePatterns": ["tools/example-routes-builder/**/*.mts"], + "eslintConfig": "tools/example-routes-builder/eslint.config.mjs" + } + } + } + }, "ngx-datatable-lib": { "projectType": "library", "root": "projects/ngx-datatable", diff --git a/docs/introduction/column-modes.md b/docs/introduction/column-modes.md index 923e4f019..120fbe901 100644 --- a/docs/introduction/column-modes.md +++ b/docs/introduction/column-modes.md @@ -7,7 +7,7 @@ to the columns. The table comes with 3 modes; `standard`, `flex`, `force`. Columns are distributed given the width's defined in the column options. -{{ datatable_example('src/app/columns/fixed-column.component.ts', 'standard-column', '400px') }} +{{ datatable_example('src/app/columns/fixed-column.component.ts', 'fixed-column', '400px') }} ## Flex diff --git a/eslint.config.mjs b/eslint.config.mjs index 5ad299437..e8da3433f 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -11,7 +11,7 @@ const __dirname = path.dirname(__filename); export const tsConfig = typescriptEslint.config({ extends: [...angularTypescriptConfig], - files: ['**/*.ts'], + files: ['**/*.{ts,mts}'], languageOptions: { parserOptions: { project: ['tsconfig.app.json'], diff --git a/package-lock.json b/package-lock.json index 3ede9c736..8ee828e3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "ngx-datatable", "version": "27.0.2", + "workspaces": [ + "tools/example-routes-builder" + ], "dependencies": { "@angular/cdk": "22.0.6", "@angular/common": "22.0.8", @@ -3540,6 +3543,10 @@ "node": ">= 10" } }, + "node_modules/@ngx-datatable/example-routes-builder": { + "resolved": "tools/example-routes-builder", + "link": true + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -17904,6 +17911,9 @@ "peerDependencies": { "zod": "^3.25.28 || ^4" } + }, + "tools/example-routes-builder": { + "name": "@ngx-datatable/example-routes-builder" } } } diff --git a/package.json b/package.json index e6e69eeb4..c2c8261f7 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "start": "ng serve", "start:prod": "http-server dist/ngx-datatable-examples -s -p 4200 -a 127.0.0.1", "build": "ng build", + "build:routes": "tsc -p tools/example-routes-builder/tsconfig.json && ng run ngx-datatable:build-routes", "build:prod": "ng build --configuration production", "build:web-components": "ng build --configuration web-components", "format": "prettier --write .", @@ -37,6 +38,9 @@ "package": "npm run build:lib:prod && npm run copy-files && npm run build:css" }, "private": true, + "workspaces": [ + "tools/example-routes-builder" + ], "overrides": { "@microsoft/api-extractor": { "typescript": "6.0.3" diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts deleted file mode 100644 index 22d8fef7e..000000000 --- a/src/app/app-routing.module.ts +++ /dev/null @@ -1,378 +0,0 @@ -import { Routes } from '@angular/router'; - -export const routes: Routes = [ - { - path: '', - data: { title: 'Fluid Row Height', sourcePath: 'basic/fluid-row-height.component.ts' }, - loadComponent: () => - import('./basic/fluid-row-height.component').then(c => c.FluidRowHeightComponent) - }, - // Basic - { - path: 'large-data-set', - data: { title: 'Large Data Set', sourcePath: 'basic/large-data-set.component.ts' }, - loadComponent: () => - import('./basic/large-data-set.component').then(c => c.LargeDataSetComponent) - }, - { - path: 'full-screen', - data: { title: 'Full Screen', sourcePath: 'basic/full-screen.component.ts' }, - loadComponent: () => import('./basic/full-screen.component').then(c => c.FullScreenComponent) - }, - { - path: 'inline-editing', - data: { title: 'Inline Editing', sourcePath: 'basic/inline-editing.component.ts' }, - loadComponent: () => - import('./basic/inline-editing.component').then(c => c.InlineEditingComponent) - }, - { - path: 'horz-vert-scrolling', - data: { title: 'Horz/Vert Scrolling', sourcePath: 'basic/horz-vert-scrolling.component.ts' }, - loadComponent: () => - import('./basic/horz-vert-scrolling.component').then(c => c.HorzVertScrollingComponent) - }, - { - path: 'vert-dynamic-scrolling', - data: { - title: 'Vert Dynamic Scrolling', - sourcePath: 'basic/vert-dynamic-scrolling.component.ts' - }, - loadComponent: () => - import('./basic/vert-dynamic-scrolling.component').then(c => c.VertDynamicScrollingComponent) - }, - { - path: 'multiple-tables', - data: { title: 'Multiple Tables', sourcePath: 'basic/multiple-tables.component.ts' }, - loadComponent: () => - import('./basic/multiple-tables.component').then(c => c.MultipleTablesComponent) - }, - { - path: 'filtering', - data: { title: 'Filtering', sourcePath: 'basic/filtering.component.ts' }, - loadComponent: () => import('./basic/filtering.component').then(c => c.FilteringComponent) - }, - { - path: 'hidden-on-load', - data: { title: 'Hidden On Load', sourcePath: 'basic/hidden-on-load.component.ts' }, - loadComponent: () => - import('./basic/hidden-on-load.component').then(c => c.HiddenOnLoadComponent) - }, - { - path: 'live-data', - data: { title: 'Live Data', sourcePath: 'basic/live-data.component.ts' }, - loadComponent: () => import('./basic/live-data.component').then(c => c.LiveDataComponent) - }, - { - path: 'rxjs', - data: { title: 'RxJS', sourcePath: 'basic/rxjs.component.ts' }, - loadComponent: () => import('./basic/rxjs.component').then(c => c.RxjsComponent) - }, - { - path: 'context-menu', - data: { title: 'Context Menu', sourcePath: 'basic/context-menu.component.ts' }, - loadComponent: () => import('./basic/context-menu.component').then(c => c.ContextMenuComponent) - }, - { - path: 'css-classes', - data: { title: 'CSS Classes', sourcePath: 'basic/css-classes.component.ts' }, - loadComponent: () => import('./basic/css-classes.component').then(c => c.CssClassesComponent) - }, - { - path: 'footer-template', - data: { title: 'Footer Template', sourcePath: 'basic/footer-template.component.ts' }, - loadComponent: () => - import('./basic/footer-template.component').then(c => c.FooterTemplateComponent) - }, - { - path: 'empty-template', - data: { title: 'Empty Template', sourcePath: 'basic/empty-template.component.ts' }, - loadComponent: () => - import('./basic/empty-template.component').then(c => c.EmptyTemplateComponent) - }, - { - path: 'drag-drop', - data: { title: 'Drag Drop', sourcePath: 'drag-drop/drag-drop.component.ts' }, - loadComponent: () => import('./drag-drop/drag-drop.component').then(c => c.DragDropComponent) - }, - // Themes - { - path: 'dark-theme', - loadComponent: () => import('./basic/dark-theme.component').then(c => c.DarkThemeComponent), - data: { dark: true, title: 'Dark Theme', sourcePath: 'basic/dark-theme.component.ts' } - }, - { - path: 'bootstrap-theme', - data: { title: 'Bootstrap Theme', sourcePath: 'basic/bootstrap-theme.component.ts' }, - loadComponent: () => - import('./basic/bootstrap-theme.component').then(c => c.BootstrapThemeComponent) - }, - // Tree - { - path: 'full-screen-tree', - data: { title: 'Full Screen Tree', sourcePath: 'tree/full-screen-tree.component.ts' }, - loadComponent: () => - import('./tree/full-screen-tree.component').then(c => c.FullScreenTreeComponent) - }, - { - path: 'client-side-tree', - data: { title: 'Client Side Tree', sourcePath: 'tree/client-side-tree.component.ts' }, - loadComponent: () => - import('./tree/client-side-tree.component').then(c => c.ClientSideTreeComponent) - }, - // Rows - { - path: 'row-grouping', - data: { title: 'Row Grouping', sourcePath: 'basic/row-grouping.component.ts' }, - loadComponent: () => import('./basic/row-grouping.component').then(c => c.RowGroupingComponent) - }, - { - path: 'fixed-row-height', - data: { title: 'Fixed Row Height', sourcePath: 'basic/fixed-row-height.component.ts' }, - loadComponent: () => - import('./basic/fixed-row-height.component').then(c => c.FixedRowHeightComponent) - }, - { - path: 'dynamic-row-height', - data: { title: 'Dynamic Row Height', sourcePath: 'basic/dynamic-row-height.component.ts' }, - loadComponent: () => - import('./basic/dynamic-row-height.component').then(c => c.DynamicRowHeightComponent) - }, - { - path: 'row-detail', - data: { title: 'Row Detail', sourcePath: 'basic/row-detail.component.ts' }, - loadComponent: () => import('./basic/row-detail.component').then(c => c.RowDetailComponent) - }, - { - path: 'responsive', - data: { title: 'Responsive', sourcePath: 'basic/responsive.component.ts' }, - loadComponent: () => import('./basic/responsive.component').then(c => c.ResponsiveComponent) - }, - { - path: 'disabled', - data: { title: 'Disabled', sourcePath: 'basic/disabled.component.ts' }, - loadComponent: () => import('./basic/disabled.component').then(c => c.DisabledComponent) - }, - // Paging - { - path: 'client-side-paging', - data: { title: 'Client-side Paging', sourcePath: 'paging/client-side-paging.component.ts' }, - loadComponent: () => - import('./paging/client-side-paging.component').then(c => c.ClientSidePagingComponent) - }, - { - path: 'server-side-paging', - data: { title: 'Server-side Paging', sourcePath: 'paging/server-side-paging.component.ts' }, - loadComponent: () => - import('./paging/server-side-paging.component').then(c => c.ServerSidePagingComponent) - }, - { - path: 'scrolling-no-virtual', - data: { title: 'Scrolling no virtual', sourcePath: 'paging/scrolling-no-virtual.component.ts' }, - loadComponent: () => - import('./paging/scrolling-no-virtual.component').then(c => c.ScrollingNoVirtualComponent) - }, - { - path: 'scrolling-server-side', - data: { - title: 'Scrolling server-side', - sourcePath: 'paging/scrolling-server-side.component.ts' - }, - loadComponent: () => - import('./paging/scrolling-server-side.component').then(c => c.ScrollingServerSideComponent) - }, - { - path: 'virtual-server-side', - data: { title: 'Virtual server-side', sourcePath: 'paging/virtual-server-side.component.ts' }, - loadComponent: () => - import('./paging/virtual-server-side.component').then(c => c.VirtualServerSideComponent) - }, - // Sorting - { - path: 'client-side-sorting', - data: { title: 'Client-side Sorting', sourcePath: 'sorting/client-side-sorting.component.ts' }, - loadComponent: () => - import('./sorting/client-side-sorting.component').then(c => c.ClientSideSortingComponent) - }, - { - path: 'default-sort', - data: { title: 'Default Sort', sourcePath: 'sorting/default-sort.component.ts' }, - loadComponent: () => - import('./sorting/default-sort.component').then(c => c.DefaultSortComponent) - }, - { - path: 'server-side-sorting', - data: { title: 'Server-side Sorting', sourcePath: 'sorting/server-side-sorting.component.ts' }, - loadComponent: () => - import('./sorting/server-side-sorting.component').then(c => c.ServerSideSortingComponent) - }, - { - path: 'comparator', - data: { title: 'Comparator', sourcePath: 'sorting/comparator.component.ts' }, - loadComponent: () => import('./sorting/comparator.component').then(c => c.ComparatorComponent) - }, - // Selection - { - path: 'cell-selection', - data: { title: 'Cell Selection', sourcePath: 'selection/cell-selection.component.ts' }, - loadComponent: () => - import('./selection/cell-selection.component').then(c => c.CellSelectionComponent) - }, - { - path: 'single-row-selection', - data: { - title: 'Single Row Selection', - sourcePath: 'selection/single-row-selection.component.ts' - }, - loadComponent: () => - import('./selection/single-row-selection.component').then(c => c.SingleRowSelectionComponent) - }, - { - path: 'multi-row-selection', - data: { - title: 'Multi Row Selection', - sourcePath: 'selection/multi-row-selection.component.ts' - }, - loadComponent: () => - import('./selection/multi-row-selection.component').then(c => c.MultiRowSelectionComponent) - }, - { - path: 'multi-click-row-selection', - data: { - title: 'Multi Click Row Selection', - sourcePath: 'selection/multi-click-row-selection.component.ts' - }, - loadComponent: () => - import('./selection/multi-click-row-selection.component').then( - c => c.MultiClickRowSelectionComponent - ) - }, - { - path: 'disable-selection-callback', - data: { - title: 'Disable Selection Callback', - sourcePath: 'selection/disable-selection-callback.component.ts' - }, - loadComponent: () => - import('./selection/disable-selection-callback.component').then( - c => c.DisableSelectionCallbackComponent - ) - }, - { - path: 'checkbox-selection', - data: { title: 'Checkbox Selection', sourcePath: 'selection/checkbox-selection.component.ts' }, - loadComponent: () => - import('./selection/checkbox-selection.component').then(c => c.CheckboxSelectionComponent) - }, - { - path: 'custom-checkbox-selection', - data: { - title: 'Custom Checkbox Selection', - sourcePath: 'selection/custom-checkbox-selection.component.ts' - }, - loadComponent: () => - import('./selection/custom-checkbox-selection.component').then( - c => c.CustomCheckboxSelectionComponent - ) - }, - { - path: 'multi-click-and-checkbox-selection', - data: { - title: 'Multi Click and Checkbox Selection', - sourcePath: 'selection/multi-click-and-checkbox-selection.component.ts' - }, - loadComponent: () => - import('./selection/multi-click-and-checkbox-selection.component').then( - c => c.MultiClickAndCheckboxSelectionComponent - ) - }, - // Templates - { - path: 'inline-template', - data: { title: 'Inline Template', sourcePath: 'templates/inline-template.component.ts' }, - loadComponent: () => - import('./templates/inline-template.component').then(c => c.InlineTemplateComponent) - }, - { - path: 'template-ref', - data: { title: 'TemplateRef', sourcePath: 'templates/template-ref.component.ts' }, - loadComponent: () => - import('./templates/template-ref.component').then(c => c.TemplateRefComponent) - }, - // Column - { - path: 'flex-column', - data: { title: 'Flex Column', sourcePath: 'columns/flex-column.component.ts' }, - loadComponent: () => import('./columns/flex-column.component').then(c => c.FlexColumnComponent) - }, - { - path: 'column-toggling', - data: { title: 'Column Toggling', sourcePath: 'columns/column-toggling.component.ts' }, - loadComponent: () => - import('./columns/column-toggling.component').then(c => c.ColumnTogglingComponent) - }, - { - path: 'fixed-column', - data: { title: 'Fixed Column', sourcePath: 'columns/fixed-column.component.ts' }, - loadComponent: () => - import('./columns/fixed-column.component').then(c => c.FixedColumnComponent) - }, - { - path: 'force-column', - data: { title: 'Force Column', sourcePath: 'columns/force-column.component.ts' }, - loadComponent: () => - import('./columns/force-column.component').then(c => c.ForceColumnComponent) - }, - { - path: 'column-pinning', - data: { title: 'Column Pinning', sourcePath: 'columns/column-pinning.component.ts' }, - loadComponent: () => - import('./columns/column-pinning.component').then(c => c.ColumnPinningComponent) - }, - { - path: 'column-reorder', - data: { title: 'Column Reorder', sourcePath: 'columns/column-reorder.component.ts' }, - loadComponent: () => - import('./columns/column-reorder.component').then(c => c.ColumnReorderComponent) - }, - // Summary Row - { - path: 'simple-summary', - data: { title: 'Simple Summary', sourcePath: 'summary/simple-summary.component.ts' }, - loadComponent: () => - import('./summary/simple-summary.component').then(c => c.SimpleSummaryComponent) - }, - { - path: 'custom-template-summary', - data: { - title: 'Custom Template Summary', - sourcePath: 'summary/custom-template-summary.component.ts' - }, - loadComponent: () => - import('./summary/custom-template-summary.component').then( - c => c.CustomTemplateSummaryComponent - ) - }, - { - path: 'server-side-paging-summary', - data: { - title: 'Server-side Paging Summary', - sourcePath: 'summary/server-side-paging-summary.component.ts' - }, - loadComponent: () => - import('./summary/server-side-paging-summary.component').then( - c => c.ServerSidePagingSummaryComponent - ) - }, - { - path: 'inline-html-summary', - data: { title: 'Inline HTML Summary', sourcePath: 'summary/inline-html-summary.component.ts' }, - loadComponent: () => - import('./summary/inline-html-summary.component').then(c => c.InlineHtmlSummaryComponent) - }, - { - path: 'summary-row-actions', - data: { title: 'Summary Row Actions', sourcePath: 'summary/summary-row-actions.component.ts' }, - loadComponent: () => - import('./summary/summary-row-actions.component').then(c => c.SummaryRowActionsComponent) - } -]; diff --git a/src/app/app.component.html b/src/app/app.component.html index eba65a67a..ef2f996eb 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -244,6 +244,6 @@

Summary Row

@if (title()) { } - + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 938e1cfa4..a97b071f6 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -5,6 +5,11 @@ import { RouterLink, RouterOutlet } from '@angular/router'; import packageInfo from '../../projects/ngx-datatable/package.json'; import { ExampleTitleComponent } from './example-title.component'; +interface ExampleComponentConstructor { + dark?: boolean; + exampleTitle: string; +} + @Component({ selector: 'app-root', imports: [ExampleTitleComponent, RouterLink, RouterOutlet], @@ -24,11 +29,12 @@ export class AppComponent { readonly title = signal(''); readonly sourcePath = signal(''); - routeActivate(outlet: RouterOutlet): void { - const { dark = false, sourcePath = '', title = '' } = outlet.activatedRoute.snapshot.data; + routeActivate(component: object, outlet: RouterOutlet): void { + const { sourcePath = '' } = outlet.activatedRoute.snapshot.data; + const example = component.constructor as unknown as ExampleComponentConstructor; - this.dark.set(dark); + this.dark.set(example.dark ?? false); this.sourcePath.set(sourcePath); - this.title.set(title); + this.title.set(example.exampleTitle); } } diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts new file mode 100644 index 000000000..b1343227a --- /dev/null +++ b/src/app/app.routes.ts @@ -0,0 +1,289 @@ +// This file is generated by the example-routes builder. Do not edit manually. +import { Routes } from '@angular/router'; +export const routes: Routes = [ + { + path: '', + redirectTo: 'fluid-row-height', + pathMatch: 'full' + }, + { + path: 'bootstrap-theme', + data: { sourcePath: 'basic/bootstrap-theme.component.ts' }, + loadComponent: () => import('./basic/bootstrap-theme.component').then(component => component.BootstrapThemeComponent) + }, + { + path: 'cell-selection', + data: { sourcePath: 'selection/cell-selection.component.ts' }, + loadComponent: () => import('./selection/cell-selection.component').then(component => component.CellSelectionComponent) + }, + { + path: 'checkbox-selection', + data: { sourcePath: 'selection/checkbox-selection.component.ts' }, + loadComponent: () => import('./selection/checkbox-selection.component').then(component => component.CheckboxSelectionComponent) + }, + { + path: 'client-side-paging', + data: { sourcePath: 'paging/client-side-paging.component.ts' }, + loadComponent: () => import('./paging/client-side-paging.component').then(component => component.ClientSidePagingComponent) + }, + { + path: 'client-side-sorting', + data: { sourcePath: 'sorting/client-side-sorting.component.ts' }, + loadComponent: () => import('./sorting/client-side-sorting.component').then(component => component.ClientSideSortingComponent) + }, + { + path: 'client-side-tree', + data: { sourcePath: 'tree/client-side-tree.component.ts' }, + loadComponent: () => import('./tree/client-side-tree.component').then(component => component.ClientSideTreeComponent) + }, + { + path: 'column-pinning', + data: { sourcePath: 'columns/column-pinning.component.ts' }, + loadComponent: () => import('./columns/column-pinning.component').then(component => component.ColumnPinningComponent) + }, + { + path: 'column-reorder', + data: { sourcePath: 'columns/column-reorder.component.ts' }, + loadComponent: () => import('./columns/column-reorder.component').then(component => component.ColumnReorderComponent) + }, + { + path: 'column-toggling', + data: { sourcePath: 'columns/column-toggling.component.ts' }, + loadComponent: () => import('./columns/column-toggling.component').then(component => component.ColumnTogglingComponent) + }, + { + path: 'comparator', + data: { sourcePath: 'sorting/comparator.component.ts' }, + loadComponent: () => import('./sorting/comparator.component').then(component => component.ComparatorComponent) + }, + { + path: 'context-menu', + data: { sourcePath: 'basic/context-menu.component.ts' }, + loadComponent: () => import('./basic/context-menu.component').then(component => component.ContextMenuComponent) + }, + { + path: 'css-classes', + data: { sourcePath: 'basic/css-classes.component.ts' }, + loadComponent: () => import('./basic/css-classes.component').then(component => component.CssClassesComponent) + }, + { + path: 'custom-checkbox-selection', + data: { sourcePath: 'selection/custom-checkbox-selection.component.ts' }, + loadComponent: () => import('./selection/custom-checkbox-selection.component').then(component => component.CustomCheckboxSelectionComponent) + }, + { + path: 'custom-template-summary', + data: { sourcePath: 'summary/custom-template-summary.component.ts' }, + loadComponent: () => import('./summary/custom-template-summary.component').then(component => component.CustomTemplateSummaryComponent) + }, + { + path: 'dark-theme', + data: { sourcePath: 'basic/dark-theme.component.ts' }, + loadComponent: () => import('./basic/dark-theme.component').then(component => component.DarkThemeComponent) + }, + { + path: 'default-sort', + data: { sourcePath: 'sorting/default-sort.component.ts' }, + loadComponent: () => import('./sorting/default-sort.component').then(component => component.DefaultSortComponent) + }, + { + path: 'disable-selection-callback', + data: { sourcePath: 'selection/disable-selection-callback.component.ts' }, + loadComponent: () => import('./selection/disable-selection-callback.component').then(component => component.DisableSelectionCallbackComponent) + }, + { + path: 'disabled', + data: { sourcePath: 'basic/disabled.component.ts' }, + loadComponent: () => import('./basic/disabled.component').then(component => component.DisabledComponent) + }, + { + path: 'drag-drop', + data: { sourcePath: 'drag-drop/drag-drop.component.ts' }, + loadComponent: () => import('./drag-drop/drag-drop.component').then(component => component.DragDropComponent) + }, + { + path: 'dynamic-row-height', + data: { sourcePath: 'basic/dynamic-row-height.component.ts' }, + loadComponent: () => import('./basic/dynamic-row-height.component').then(component => component.DynamicRowHeightComponent) + }, + { + path: 'empty-template', + data: { sourcePath: 'basic/empty-template.component.ts' }, + loadComponent: () => import('./basic/empty-template.component').then(component => component.EmptyTemplateComponent) + }, + { + path: 'filtering', + data: { sourcePath: 'basic/filtering.component.ts' }, + loadComponent: () => import('./basic/filtering.component').then(component => component.FilteringComponent) + }, + { + path: 'fixed-column', + data: { sourcePath: 'columns/fixed-column.component.ts' }, + loadComponent: () => import('./columns/fixed-column.component').then(component => component.FixedColumnComponent) + }, + { + path: 'fixed-row-height', + data: { sourcePath: 'basic/fixed-row-height.component.ts' }, + loadComponent: () => import('./basic/fixed-row-height.component').then(component => component.FixedRowHeightComponent) + }, + { + path: 'flex-column', + data: { sourcePath: 'columns/flex-column.component.ts' }, + loadComponent: () => import('./columns/flex-column.component').then(component => component.FlexColumnComponent) + }, + { + path: 'fluid-row-height', + data: { sourcePath: 'basic/fluid-row-height.component.ts' }, + loadComponent: () => import('./basic/fluid-row-height.component').then(component => component.FluidRowHeightComponent) + }, + { + path: 'footer-template', + data: { sourcePath: 'basic/footer-template.component.ts' }, + loadComponent: () => import('./basic/footer-template.component').then(component => component.FooterTemplateComponent) + }, + { + path: 'force-column', + data: { sourcePath: 'columns/force-column.component.ts' }, + loadComponent: () => import('./columns/force-column.component').then(component => component.ForceColumnComponent) + }, + { + path: 'full-screen', + data: { sourcePath: 'basic/full-screen.component.ts' }, + loadComponent: () => import('./basic/full-screen.component').then(component => component.FullScreenComponent) + }, + { + path: 'full-screen-tree', + data: { sourcePath: 'tree/full-screen-tree.component.ts' }, + loadComponent: () => import('./tree/full-screen-tree.component').then(component => component.FullScreenTreeComponent) + }, + { + path: 'hidden-on-load', + data: { sourcePath: 'basic/hidden-on-load.component.ts' }, + loadComponent: () => import('./basic/hidden-on-load.component').then(component => component.HiddenOnLoadComponent) + }, + { + path: 'horz-vert-scrolling', + data: { sourcePath: 'basic/horz-vert-scrolling.component.ts' }, + loadComponent: () => import('./basic/horz-vert-scrolling.component').then(component => component.HorzVertScrollingComponent) + }, + { + path: 'inline-editing', + data: { sourcePath: 'basic/inline-editing.component.ts' }, + loadComponent: () => import('./basic/inline-editing.component').then(component => component.InlineEditingComponent) + }, + { + path: 'inline-html-summary', + data: { sourcePath: 'summary/inline-html-summary.component.ts' }, + loadComponent: () => import('./summary/inline-html-summary.component').then(component => component.InlineHtmlSummaryComponent) + }, + { + path: 'inline-template', + data: { sourcePath: 'templates/inline-template.component.ts' }, + loadComponent: () => import('./templates/inline-template.component').then(component => component.InlineTemplateComponent) + }, + { + path: 'large-data-set', + data: { sourcePath: 'basic/large-data-set.component.ts' }, + loadComponent: () => import('./basic/large-data-set.component').then(component => component.LargeDataSetComponent) + }, + { + path: 'live-data', + data: { sourcePath: 'basic/live-data.component.ts' }, + loadComponent: () => import('./basic/live-data.component').then(component => component.LiveDataComponent) + }, + { + path: 'multi-click-and-checkbox-selection', + data: { sourcePath: 'selection/multi-click-and-checkbox-selection.component.ts' }, + loadComponent: () => import('./selection/multi-click-and-checkbox-selection.component').then(component => component.MultiClickAndCheckboxSelectionComponent) + }, + { + path: 'multi-click-row-selection', + data: { sourcePath: 'selection/multi-click-row-selection.component.ts' }, + loadComponent: () => import('./selection/multi-click-row-selection.component').then(component => component.MultiClickRowSelectionComponent) + }, + { + path: 'multi-row-selection', + data: { sourcePath: 'selection/multi-row-selection.component.ts' }, + loadComponent: () => import('./selection/multi-row-selection.component').then(component => component.MultiRowSelectionComponent) + }, + { + path: 'multiple-tables', + data: { sourcePath: 'basic/multiple-tables.component.ts' }, + loadComponent: () => import('./basic/multiple-tables.component').then(component => component.MultipleTablesComponent) + }, + { + path: 'responsive', + data: { sourcePath: 'basic/responsive.component.ts' }, + loadComponent: () => import('./basic/responsive.component').then(component => component.ResponsiveComponent) + }, + { + path: 'row-detail', + data: { sourcePath: 'basic/row-detail.component.ts' }, + loadComponent: () => import('./basic/row-detail.component').then(component => component.RowDetailComponent) + }, + { + path: 'row-grouping', + data: { sourcePath: 'basic/row-grouping.component.ts' }, + loadComponent: () => import('./basic/row-grouping.component').then(component => component.RowGroupingComponent) + }, + { + path: 'rxjs', + data: { sourcePath: 'basic/rxjs.component.ts' }, + loadComponent: () => import('./basic/rxjs.component').then(component => component.RxjsComponent) + }, + { + path: 'scrolling-no-virtual', + data: { sourcePath: 'paging/scrolling-no-virtual.component.ts' }, + loadComponent: () => import('./paging/scrolling-no-virtual.component').then(component => component.ScrollingNoVirtualComponent) + }, + { + path: 'scrolling-server-side', + data: { sourcePath: 'paging/scrolling-server-side.component.ts' }, + loadComponent: () => import('./paging/scrolling-server-side.component').then(component => component.ScrollingServerSideComponent) + }, + { + path: 'server-side-paging', + data: { sourcePath: 'paging/server-side-paging.component.ts' }, + loadComponent: () => import('./paging/server-side-paging.component').then(component => component.ServerSidePagingComponent) + }, + { + path: 'server-side-paging-summary', + data: { sourcePath: 'summary/server-side-paging-summary.component.ts' }, + loadComponent: () => import('./summary/server-side-paging-summary.component').then(component => component.ServerSidePagingSummaryComponent) + }, + { + path: 'server-side-sorting', + data: { sourcePath: 'sorting/server-side-sorting.component.ts' }, + loadComponent: () => import('./sorting/server-side-sorting.component').then(component => component.ServerSideSortingComponent) + }, + { + path: 'simple-summary', + data: { sourcePath: 'summary/simple-summary.component.ts' }, + loadComponent: () => import('./summary/simple-summary.component').then(component => component.SimpleSummaryComponent) + }, + { + path: 'single-row-selection', + data: { sourcePath: 'selection/single-row-selection.component.ts' }, + loadComponent: () => import('./selection/single-row-selection.component').then(component => component.SingleRowSelectionComponent) + }, + { + path: 'summary-row-actions', + data: { sourcePath: 'summary/summary-row-actions.component.ts' }, + loadComponent: () => import('./summary/summary-row-actions.component').then(component => component.SummaryRowActionsComponent) + }, + { + path: 'template-ref', + data: { sourcePath: 'templates/template-ref.component.ts' }, + loadComponent: () => import('./templates/template-ref.component').then(component => component.TemplateRefComponent) + }, + { + path: 'vert-dynamic-scrolling', + data: { sourcePath: 'basic/vert-dynamic-scrolling.component.ts' }, + loadComponent: () => import('./basic/vert-dynamic-scrolling.component').then(component => component.VertDynamicScrollingComponent) + }, + { + path: 'virtual-server-side', + data: { sourcePath: 'paging/virtual-server-side.component.ts' }, + loadComponent: () => import('./paging/virtual-server-side.component').then(component => component.VirtualServerSideComponent) + } +]; diff --git a/src/app/app.web-components.ts b/src/app/app.web-components.ts new file mode 100644 index 000000000..922e990cc --- /dev/null +++ b/src/app/app.web-components.ts @@ -0,0 +1,60 @@ +// This file is generated by the example-routes builder. Do not edit manually. +import { Type } from '@angular/core'; +export const exampleLoaders: ReadonlyMap Promise>> = new Map Promise>>([ + ['bootstrap-theme', () => import('./basic/bootstrap-theme.component').then(component => component.BootstrapThemeComponent)], + ['cell-selection', () => import('./selection/cell-selection.component').then(component => component.CellSelectionComponent)], + ['checkbox-selection', () => import('./selection/checkbox-selection.component').then(component => component.CheckboxSelectionComponent)], + ['client-side-paging', () => import('./paging/client-side-paging.component').then(component => component.ClientSidePagingComponent)], + ['client-side-sorting', () => import('./sorting/client-side-sorting.component').then(component => component.ClientSideSortingComponent)], + ['client-side-tree', () => import('./tree/client-side-tree.component').then(component => component.ClientSideTreeComponent)], + ['column-pinning', () => import('./columns/column-pinning.component').then(component => component.ColumnPinningComponent)], + ['column-reorder', () => import('./columns/column-reorder.component').then(component => component.ColumnReorderComponent)], + ['column-toggling', () => import('./columns/column-toggling.component').then(component => component.ColumnTogglingComponent)], + ['comparator', () => import('./sorting/comparator.component').then(component => component.ComparatorComponent)], + ['context-menu', () => import('./basic/context-menu.component').then(component => component.ContextMenuComponent)], + ['css-classes', () => import('./basic/css-classes.component').then(component => component.CssClassesComponent)], + ['custom-checkbox-selection', () => import('./selection/custom-checkbox-selection.component').then(component => component.CustomCheckboxSelectionComponent)], + ['custom-template-summary', () => import('./summary/custom-template-summary.component').then(component => component.CustomTemplateSummaryComponent)], + ['dark-theme', () => import('./basic/dark-theme.component').then(component => component.DarkThemeComponent)], + ['default-sort', () => import('./sorting/default-sort.component').then(component => component.DefaultSortComponent)], + ['disable-selection-callback', () => import('./selection/disable-selection-callback.component').then(component => component.DisableSelectionCallbackComponent)], + ['disabled', () => import('./basic/disabled.component').then(component => component.DisabledComponent)], + ['drag-drop', () => import('./drag-drop/drag-drop.component').then(component => component.DragDropComponent)], + ['dynamic-row-height', () => import('./basic/dynamic-row-height.component').then(component => component.DynamicRowHeightComponent)], + ['empty-template', () => import('./basic/empty-template.component').then(component => component.EmptyTemplateComponent)], + ['filtering', () => import('./basic/filtering.component').then(component => component.FilteringComponent)], + ['fixed-column', () => import('./columns/fixed-column.component').then(component => component.FixedColumnComponent)], + ['fixed-row-height', () => import('./basic/fixed-row-height.component').then(component => component.FixedRowHeightComponent)], + ['flex-column', () => import('./columns/flex-column.component').then(component => component.FlexColumnComponent)], + ['fluid-row-height', () => import('./basic/fluid-row-height.component').then(component => component.FluidRowHeightComponent)], + ['footer-template', () => import('./basic/footer-template.component').then(component => component.FooterTemplateComponent)], + ['force-column', () => import('./columns/force-column.component').then(component => component.ForceColumnComponent)], + ['full-screen', () => import('./basic/full-screen.component').then(component => component.FullScreenComponent)], + ['full-screen-tree', () => import('./tree/full-screen-tree.component').then(component => component.FullScreenTreeComponent)], + ['hidden-on-load', () => import('./basic/hidden-on-load.component').then(component => component.HiddenOnLoadComponent)], + ['horz-vert-scrolling', () => import('./basic/horz-vert-scrolling.component').then(component => component.HorzVertScrollingComponent)], + ['inline-editing', () => import('./basic/inline-editing.component').then(component => component.InlineEditingComponent)], + ['inline-html-summary', () => import('./summary/inline-html-summary.component').then(component => component.InlineHtmlSummaryComponent)], + ['inline-template', () => import('./templates/inline-template.component').then(component => component.InlineTemplateComponent)], + ['large-data-set', () => import('./basic/large-data-set.component').then(component => component.LargeDataSetComponent)], + ['live-data', () => import('./basic/live-data.component').then(component => component.LiveDataComponent)], + ['multi-click-and-checkbox-selection', () => import('./selection/multi-click-and-checkbox-selection.component').then(component => component.MultiClickAndCheckboxSelectionComponent)], + ['multi-click-row-selection', () => import('./selection/multi-click-row-selection.component').then(component => component.MultiClickRowSelectionComponent)], + ['multi-row-selection', () => import('./selection/multi-row-selection.component').then(component => component.MultiRowSelectionComponent)], + ['multiple-tables', () => import('./basic/multiple-tables.component').then(component => component.MultipleTablesComponent)], + ['responsive', () => import('./basic/responsive.component').then(component => component.ResponsiveComponent)], + ['row-detail', () => import('./basic/row-detail.component').then(component => component.RowDetailComponent)], + ['row-grouping', () => import('./basic/row-grouping.component').then(component => component.RowGroupingComponent)], + ['rxjs', () => import('./basic/rxjs.component').then(component => component.RxjsComponent)], + ['scrolling-no-virtual', () => import('./paging/scrolling-no-virtual.component').then(component => component.ScrollingNoVirtualComponent)], + ['scrolling-server-side', () => import('./paging/scrolling-server-side.component').then(component => component.ScrollingServerSideComponent)], + ['server-side-paging', () => import('./paging/server-side-paging.component').then(component => component.ServerSidePagingComponent)], + ['server-side-paging-summary', () => import('./summary/server-side-paging-summary.component').then(component => component.ServerSidePagingSummaryComponent)], + ['server-side-sorting', () => import('./sorting/server-side-sorting.component').then(component => component.ServerSideSortingComponent)], + ['simple-summary', () => import('./summary/simple-summary.component').then(component => component.SimpleSummaryComponent)], + ['single-row-selection', () => import('./selection/single-row-selection.component').then(component => component.SingleRowSelectionComponent)], + ['summary-row-actions', () => import('./summary/summary-row-actions.component').then(component => component.SummaryRowActionsComponent)], + ['template-ref', () => import('./templates/template-ref.component').then(component => component.TemplateRefComponent)], + ['vert-dynamic-scrolling', () => import('./basic/vert-dynamic-scrolling.component').then(component => component.VertDynamicScrollingComponent)], + ['virtual-server-side', () => import('./paging/virtual-server-side.component').then(component => component.VirtualServerSideComponent)] +]); diff --git a/src/app/basic/bootstrap-theme.component.ts b/src/app/basic/bootstrap-theme.component.ts index 93ed29bb9..a812e5e38 100644 --- a/src/app/basic/bootstrap-theme.component.ts +++ b/src/app/basic/bootstrap-theme.component.ts @@ -27,6 +27,7 @@ import { DataService } from '../data.service'; ` }) export class BootstrapThemeComponent { + static readonly exampleTitle = 'Bootstrap Theme'; private dataService = inject(DataService); readonly rows = signal([]); readonly loadingIndicator = signal(true); diff --git a/src/app/basic/context-menu.component.ts b/src/app/basic/context-menu.component.ts index abc07ae5f..66aa8d15d 100644 --- a/src/app/basic/context-menu.component.ts +++ b/src/app/basic/context-menu.component.ts @@ -44,6 +44,7 @@ import { DataService } from '../data.service'; ` }) export class ContextMenuComponent { + static readonly exampleTitle = 'Context Menu'; protected readonly rows = inject(DataService).load('company.json'); columns: TableColumn[] = [{ prop: 'name' }, { name: 'Gender' }, { name: 'Company' }]; diff --git a/src/app/basic/css-classes.component.ts b/src/app/basic/css-classes.component.ts index 3cd2aa922..ca545fd7f 100644 --- a/src/app/basic/css-classes.component.ts +++ b/src/app/basic/css-classes.component.ts @@ -26,6 +26,7 @@ import { DataService } from '../data.service'; ` }) export class CssClassesComponent { + static readonly exampleTitle = 'CSS Classes'; protected readonly rows = inject(DataService) .load('100k.json') .pipe(map(data => data.slice(0, 50))); diff --git a/src/app/basic/dark-theme.component.ts b/src/app/basic/dark-theme.component.ts index b263e3ec5..e98f8e50b 100644 --- a/src/app/basic/dark-theme.component.ts +++ b/src/app/basic/dark-theme.component.ts @@ -26,6 +26,8 @@ import { DataService } from '../data.service'; ` }) export class DarkThemeComponent { + static readonly exampleTitle = 'Dark Theme'; + static readonly dark = true; private dataService = inject(DataService); readonly rows = signal([]); readonly loadingIndicator = signal(true); diff --git a/src/app/basic/disabled.component.ts b/src/app/basic/disabled.component.ts index 1f30ce9ec..a9551335f 100644 --- a/src/app/basic/disabled.component.ts +++ b/src/app/basic/disabled.component.ts @@ -83,6 +83,7 @@ import { DataService } from '../data.service'; ` }) export class DisabledComponent { + static readonly exampleTitle = 'Disabled'; private dataService = inject(DataService); readonly rows = signal<(FullEmployee & { isDisabled?: boolean })[]>([]); diff --git a/src/app/basic/dynamic-row-height.component.ts b/src/app/basic/dynamic-row-height.component.ts index 8765ef58f..5482c2ac2 100644 --- a/src/app/basic/dynamic-row-height.component.ts +++ b/src/app/basic/dynamic-row-height.component.ts @@ -26,6 +26,7 @@ import { DataService } from '../data.service'; ` }) export class DynamicRowHeightComponent { + static readonly exampleTitle = 'Dynamic Row Height'; protected readonly rows = inject(DataService) .load('100k.json') .pipe( diff --git a/src/app/basic/empty-template.component.ts b/src/app/basic/empty-template.component.ts index 42b7b2be5..2de4828ee 100644 --- a/src/app/basic/empty-template.component.ts +++ b/src/app/basic/empty-template.component.ts @@ -32,6 +32,7 @@ import { DatatableComponent, TableColumn } from '@siemens/ngx-datatable'; ` }) export class EmptyTemplateComponent { + static readonly exampleTitle = 'Empty Template'; columns: TableColumn[] = [ { prop: 'name' }, { name: 'Gender' }, diff --git a/src/app/basic/filtering.component.ts b/src/app/basic/filtering.component.ts index 9e34d8f24..2608f72ef 100644 --- a/src/app/basic/filtering.component.ts +++ b/src/app/basic/filtering.component.ts @@ -28,6 +28,7 @@ import { DataService } from '../data.service'; ` }) export class FilteringComponent { + static readonly exampleTitle = 'Filtering'; private dataService = inject(DataService); readonly rows = signal([]); diff --git a/src/app/basic/fixed-row-height.component.ts b/src/app/basic/fixed-row-height.component.ts index 62ef75bff..bc2e79c6f 100644 --- a/src/app/basic/fixed-row-height.component.ts +++ b/src/app/basic/fixed-row-height.component.ts @@ -20,6 +20,7 @@ import { DataService } from '../data.service'; ` }) export class FixedRowHeightComponent { + static readonly exampleTitle = 'Fixed Row Height'; protected readonly rows = inject(DataService).load('company.json'); columns: TableColumn[] = [{ prop: 'name' }, { name: 'Company' }, { name: 'Gender' }]; } diff --git a/src/app/basic/fluid-row-height.component.ts b/src/app/basic/fluid-row-height.component.ts index 84843076e..d49cdb068 100644 --- a/src/app/basic/fluid-row-height.component.ts +++ b/src/app/basic/fluid-row-height.component.ts @@ -26,6 +26,7 @@ import { DataService } from '../data.service'; host: { class: 'datatable-example' } }) export class FluidRowHeightComponent { + static readonly exampleTitle = 'Fluid Row Height'; private dataService = inject(DataService); readonly rows = signal([]); readonly loadingIndicator = signal(true); diff --git a/src/app/basic/footer-template.component.ts b/src/app/basic/footer-template.component.ts index 00f1ead25..012354352 100644 --- a/src/app/basic/footer-template.component.ts +++ b/src/app/basic/footer-template.component.ts @@ -54,6 +54,7 @@ import { DataService } from '../data.service'; ` }) export class FooterTemplateComponent { + static readonly exampleTitle = 'Footer Template'; protected readonly rows = inject(DataService) .load('company.json') .pipe(map(data => data.slice(0, 5))); diff --git a/src/app/basic/full-screen.component.ts b/src/app/basic/full-screen.component.ts index 3e20430d5..ffb52d984 100644 --- a/src/app/basic/full-screen.component.ts +++ b/src/app/basic/full-screen.component.ts @@ -29,5 +29,6 @@ import { DataService } from '../data.service'; ` }) export class FullScreenComponent { + static readonly exampleTitle = 'Full Screen'; protected readonly rows = inject(DataService).load('100k.json'); } diff --git a/src/app/basic/hidden-on-load.component.ts b/src/app/basic/hidden-on-load.component.ts index e7835e34e..2d97eecca 100644 --- a/src/app/basic/hidden-on-load.component.ts +++ b/src/app/basic/hidden-on-load.component.ts @@ -59,6 +59,7 @@ import { DataService } from '../data.service'; ` }) export class HiddenOnLoadComponent { + static readonly exampleTitle = 'Hidden On Load'; protected readonly rows = inject(DataService).load('100k.json'); tab1 = true; diff --git a/src/app/basic/horz-vert-scrolling.component.ts b/src/app/basic/horz-vert-scrolling.component.ts index 41cb4f87a..7ac591a45 100644 --- a/src/app/basic/horz-vert-scrolling.component.ts +++ b/src/app/basic/horz-vert-scrolling.component.ts @@ -27,5 +27,6 @@ import { DataService } from '../data.service'; ` }) export class HorzVertScrollingComponent { + static readonly exampleTitle = 'Horz/Vert Scrolling'; protected readonly rows = inject(DataService).load('100k.json'); } diff --git a/src/app/basic/inline-editing.component.ts b/src/app/basic/inline-editing.component.ts index 079e0496c..ab09b71f6 100644 --- a/src/app/basic/inline-editing.component.ts +++ b/src/app/basic/inline-editing.component.ts @@ -70,6 +70,7 @@ import { DataService } from '../data.service'; ` }) export class InlineEditingComponent { + static readonly exampleTitle = 'Inline Editing'; private dataService = inject(DataService); editing: Record = {}; rows: Employee[] = []; diff --git a/src/app/basic/large-data-set.component.ts b/src/app/basic/large-data-set.component.ts index 5434d2259..80cf86b60 100644 --- a/src/app/basic/large-data-set.component.ts +++ b/src/app/basic/large-data-set.component.ts @@ -63,6 +63,7 @@ import { DataService } from '../data.service'; ` }) export class LargeDataSetComponent { + static readonly exampleTitle = 'Large Data Set'; private dataService = inject(DataService); readonly rows = signal<(FullEmployee & { height: number })[]>([]); readonly scrollTarget = signal(0); diff --git a/src/app/basic/live-data.component.ts b/src/app/basic/live-data.component.ts index dea491e84..d5f36074b 100644 --- a/src/app/basic/live-data.component.ts +++ b/src/app/basic/live-data.component.ts @@ -33,6 +33,7 @@ import { DataService } from '../data.service'; ` }) export class LiveDataComponent { + static readonly exampleTitle = 'Live Data'; private dataService = inject(DataService); @ViewChild('mydatatable') mydatatable!: DatatableComponent; diff --git a/src/app/basic/multiple-tables.component.ts b/src/app/basic/multiple-tables.component.ts index 8c60ef629..fb9e7c9af 100644 --- a/src/app/basic/multiple-tables.component.ts +++ b/src/app/basic/multiple-tables.component.ts @@ -26,6 +26,7 @@ import { DatatableComponent, TableColumn } from '@siemens/ngx-datatable'; ` }) export class MultipleTablesComponent { + static readonly exampleTitle = 'Multiple Tables'; columns1: TableColumn[] = [{ prop: 'name' }, { name: 'Gender' }, { name: 'Company' }]; columns2: TableColumn[] = [{ prop: 'name', name: 'Name' }, { name: 'Gender' }]; diff --git a/src/app/basic/responsive.component.ts b/src/app/basic/responsive.component.ts index e16b831e3..817f016d3 100644 --- a/src/app/basic/responsive.component.ts +++ b/src/app/basic/responsive.component.ts @@ -121,6 +121,7 @@ import { DataService } from '../data.service'; encapsulation: ViewEncapsulation.None }) export class ResponsiveComponent { + static readonly exampleTitle = 'Responsive'; readonly rows = inject(DataService).load('100k.json'); @ViewChild('myTable') table!: DatatableComponent; diff --git a/src/app/basic/row-detail.component.ts b/src/app/basic/row-detail.component.ts index f15f3343e..47317317b 100644 --- a/src/app/basic/row-detail.component.ts +++ b/src/app/basic/row-detail.component.ts @@ -100,6 +100,7 @@ import { DataService } from '../data.service'; encapsulation: ViewEncapsulation.None }) export class RowDetailComponent { + static readonly exampleTitle = 'Row Detail'; readonly rows = inject(DataService).load('100k.json'); @ViewChild('myTable') table!: DatatableComponent; diff --git a/src/app/basic/row-grouping.component.ts b/src/app/basic/row-grouping.component.ts index a5e09b4e3..3971ee3c8 100644 --- a/src/app/basic/row-grouping.component.ts +++ b/src/app/basic/row-grouping.component.ts @@ -143,6 +143,7 @@ import { DataService } from '../data.service'; ` }) export class RowGroupingComponent { + static readonly exampleTitle = 'Row Grouping'; private dataService = inject(DataService); @ViewChild('myTable') table!: DatatableComponent; diff --git a/src/app/basic/rxjs.component.ts b/src/app/basic/rxjs.component.ts index 1fa692669..698ba044f 100644 --- a/src/app/basic/rxjs.component.ts +++ b/src/app/basic/rxjs.component.ts @@ -22,6 +22,7 @@ import { DataService } from '../data.service'; ` }) export class RxjsComponent { + static readonly exampleTitle = 'RxJS'; private dataService = inject(DataService); rows: Observable; diff --git a/src/app/basic/vert-dynamic-scrolling.component.ts b/src/app/basic/vert-dynamic-scrolling.component.ts index 1027ced5f..7c09b7413 100644 --- a/src/app/basic/vert-dynamic-scrolling.component.ts +++ b/src/app/basic/vert-dynamic-scrolling.component.ts @@ -73,6 +73,7 @@ import { DataService } from '../data.service'; ` }) export class VertDynamicScrollingComponent { + static readonly exampleTitle = 'Vert Dynamic Scrolling'; private dataService = inject(DataService); editing: Record = {}; rows: Employee[] = []; diff --git a/src/app/columns/column-pinning.component.ts b/src/app/columns/column-pinning.component.ts index d251010f0..90797b94a 100644 --- a/src/app/columns/column-pinning.component.ts +++ b/src/app/columns/column-pinning.component.ts @@ -27,5 +27,6 @@ import { DataService } from '../data.service'; ` }) export class ColumnPinningComponent { + static readonly exampleTitle = 'Column Pinning'; protected readonly rows = inject(DataService).load('100k.json'); } diff --git a/src/app/columns/column-reorder.component.ts b/src/app/columns/column-reorder.component.ts index 297904618..33ad06371 100644 --- a/src/app/columns/column-reorder.component.ts +++ b/src/app/columns/column-reorder.component.ts @@ -46,6 +46,7 @@ import { DataService } from '../data.service'; ` }) export class ColumnReorderComponent { + static readonly exampleTitle = 'Column Reorder'; private dataService = inject(DataService); readonly rows = signal([]); readonly loadingIndicator = signal(true); diff --git a/src/app/columns/column-toggling.component.ts b/src/app/columns/column-toggling.component.ts index 48f7f2b51..b2b69bc9e 100644 --- a/src/app/columns/column-toggling.component.ts +++ b/src/app/columns/column-toggling.component.ts @@ -40,6 +40,7 @@ import { Employee } from '../data.model'; ` }) export class ColumnTogglingComponent { + static readonly exampleTitle = 'Column Toggling'; rows: Employee[] = [ { name: 'Claudine Neal', diff --git a/src/app/columns/fixed-column.component.ts b/src/app/columns/fixed-column.component.ts index 06f729ab2..571ed1edd 100644 --- a/src/app/columns/fixed-column.component.ts +++ b/src/app/columns/fixed-column.component.ts @@ -41,6 +41,7 @@ import { DataService } from '../data.service'; host: { class: 'datatable-example' } }) export class FixedColumnComponent { + static readonly exampleTitle = 'Fixed Column'; protected readonly rows = inject(DataService) .load('company.json') .pipe(map(data => data.slice(0, 5))); diff --git a/src/app/columns/flex-column.component.ts b/src/app/columns/flex-column.component.ts index f666db702..934f60529 100644 --- a/src/app/columns/flex-column.component.ts +++ b/src/app/columns/flex-column.component.ts @@ -41,6 +41,7 @@ import { DataService } from '../data.service'; host: { class: 'datatable-example' } }) export class FlexColumnComponent { + static readonly exampleTitle = 'Flex Column'; protected readonly rows = inject(DataService) .load('company.json') .pipe(map(data => data.slice(0, 5))); diff --git a/src/app/columns/force-column.component.ts b/src/app/columns/force-column.component.ts index 706ca55c7..ed5bea161 100644 --- a/src/app/columns/force-column.component.ts +++ b/src/app/columns/force-column.component.ts @@ -41,6 +41,7 @@ import { DataService } from '../data.service'; host: { class: 'datatable-example' } }) export class ForceColumnComponent { + static readonly exampleTitle = 'Force Column'; protected readonly rows = inject(DataService) .load('company.json') .pipe(map(data => data.slice(0, 5))); diff --git a/src/app/drag-drop/drag-drop.component.ts b/src/app/drag-drop/drag-drop.component.ts index 790a3cf7a..fd1a2e47e 100644 --- a/src/app/drag-drop/drag-drop.component.ts +++ b/src/app/drag-drop/drag-drop.component.ts @@ -43,6 +43,7 @@ import { DataService } from '../data.service'; ` }) export class DragDropComponent { + static readonly exampleTitle = 'Drag Drop'; private dataService = inject(DataService); readonly rows = signal([]); readonly loadingIndicator = signal(true); diff --git a/src/app/paging/client-side-paging.component.ts b/src/app/paging/client-side-paging.component.ts index 3d76a07c8..b2930f78c 100644 --- a/src/app/paging/client-side-paging.component.ts +++ b/src/app/paging/client-side-paging.component.ts @@ -21,5 +21,6 @@ import { DataService } from '../data.service'; ` }) export class ClientSidePagingComponent { + static readonly exampleTitle = 'Client-side Paging'; protected readonly rows = inject(DataService).load('company.json'); } diff --git a/src/app/paging/scrolling-no-virtual.component.ts b/src/app/paging/scrolling-no-virtual.component.ts index d3d95bac7..367894d5a 100644 --- a/src/app/paging/scrolling-no-virtual.component.ts +++ b/src/app/paging/scrolling-no-virtual.component.ts @@ -33,6 +33,7 @@ import { Page } from './model/page'; providers: [MockServerResultsService] }) export class ScrollingNoVirtualComponent implements OnInit { + static readonly exampleTitle = 'Scrolling no virtual'; private serverResultsService = inject(MockServerResultsService); readonly page = signal({ pageNumber: 0, diff --git a/src/app/paging/scrolling-server-side.component.ts b/src/app/paging/scrolling-server-side.component.ts index 33d0f0b64..01cf04d35 100644 --- a/src/app/paging/scrolling-server-side.component.ts +++ b/src/app/paging/scrolling-server-side.component.ts @@ -48,6 +48,7 @@ export class MockServerResultsService { providers: [MockServerResultsService] }) export class ScrollingServerSideComponent implements OnInit { + static readonly exampleTitle = 'Scrolling server-side'; private serverResultsService = inject(MockServerResultsService); private el = inject(ElementRef); readonly headerHeight = 50; diff --git a/src/app/paging/server-side-paging.component.ts b/src/app/paging/server-side-paging.component.ts index e6f40d816..db59b74cc 100644 --- a/src/app/paging/server-side-paging.component.ts +++ b/src/app/paging/server-side-paging.component.ts @@ -29,6 +29,7 @@ import { Page } from './model/page'; providers: [MockServerResultsService] }) export class ServerSidePagingComponent implements OnInit { + static readonly exampleTitle = 'Server-side Paging'; private serverResultsService = inject(MockServerResultsService); readonly page = signal({ pageNumber: 0, diff --git a/src/app/paging/virtual-server-side.component.ts b/src/app/paging/virtual-server-side.component.ts index 9ef393b3f..721c76694 100644 --- a/src/app/paging/virtual-server-side.component.ts +++ b/src/app/paging/virtual-server-side.component.ts @@ -40,6 +40,7 @@ import { Page } from './model/page'; providers: [MockServerResultsService] }) export class VirtualServerSideComponent { + static readonly exampleTitle = 'Virtual server-side'; private serverResultsService = inject(MockServerResultsService); readonly totalElements = signal(0); pageNumber = 0; diff --git a/src/app/selection/cell-selection.component.ts b/src/app/selection/cell-selection.component.ts index 8276ae482..f53e2b207 100644 --- a/src/app/selection/cell-selection.component.ts +++ b/src/app/selection/cell-selection.component.ts @@ -25,6 +25,7 @@ import { DataService } from '../data.service'; ` }) export class CellSelectionComponent { + static readonly exampleTitle = 'Cell Selection'; readonly rows = inject(DataService).load('company.json'); selected: Employee[] = []; columns: TableColumn[] = [{ prop: 'name' }, { name: 'Company' }, { name: 'Gender' }]; diff --git a/src/app/selection/checkbox-selection.component.ts b/src/app/selection/checkbox-selection.component.ts index 3455c65bd..aa8b03570 100644 --- a/src/app/selection/checkbox-selection.component.ts +++ b/src/app/selection/checkbox-selection.component.ts @@ -67,6 +67,7 @@ import { DataService } from '../data.service'; ` }) export class CheckboxSelectionComponent { + static readonly exampleTitle = 'Checkbox Selection'; private dataService = inject(DataService); readonly rows = signal([]); selected: Employee[] = []; diff --git a/src/app/selection/custom-checkbox-selection.component.ts b/src/app/selection/custom-checkbox-selection.component.ts index cf85f5b86..116d9c8cd 100644 --- a/src/app/selection/custom-checkbox-selection.component.ts +++ b/src/app/selection/custom-checkbox-selection.component.ts @@ -86,6 +86,7 @@ import { DataService } from '../data.service'; ` }) export class CustomCheckboxSelectionComponent { + static readonly exampleTitle = 'Custom Checkbox Selection'; private dataService = inject(DataService); readonly rows = signal([]); selected: Employee[] = []; diff --git a/src/app/selection/disable-selection-callback.component.ts b/src/app/selection/disable-selection-callback.component.ts index 0e7297790..ded38be85 100644 --- a/src/app/selection/disable-selection-callback.component.ts +++ b/src/app/selection/disable-selection-callback.component.ts @@ -42,6 +42,7 @@ import { DataService } from '../data.service'; ` }) export class DisableSelectionCallbackComponent { + static readonly exampleTitle = 'Disable Selection Callback'; readonly rows = inject(DataService).load('company.json'); selected: Employee[] = []; diff --git a/src/app/selection/multi-click-and-checkbox-selection.component.ts b/src/app/selection/multi-click-and-checkbox-selection.component.ts index 417e17942..c1a301157 100644 --- a/src/app/selection/multi-click-and-checkbox-selection.component.ts +++ b/src/app/selection/multi-click-and-checkbox-selection.component.ts @@ -68,6 +68,7 @@ import { DataService } from '../data.service'; ` }) export class MultiClickAndCheckboxSelectionComponent { + static readonly exampleTitle = 'Multi Click and Checkbox Selection'; private dataService = inject(DataService); readonly rows = signal([]); selected: Employee[] = []; diff --git a/src/app/selection/multi-click-row-selection.component.ts b/src/app/selection/multi-click-row-selection.component.ts index b0615a440..78bbffe6d 100644 --- a/src/app/selection/multi-click-row-selection.component.ts +++ b/src/app/selection/multi-click-row-selection.component.ts @@ -45,6 +45,7 @@ import { DataService } from '../data.service'; ` }) export class MultiClickRowSelectionComponent { + static readonly exampleTitle = 'Multi Click Row Selection'; readonly rows = inject(DataService).load('company.json'); selected: Employee[] = []; diff --git a/src/app/selection/multi-row-selection.component.ts b/src/app/selection/multi-row-selection.component.ts index e2d2ccc3e..cccada7b5 100644 --- a/src/app/selection/multi-row-selection.component.ts +++ b/src/app/selection/multi-row-selection.component.ts @@ -47,6 +47,7 @@ import { DataService } from '../data.service'; ` }) export class MultiRowSelectionComponent { + static readonly exampleTitle = 'Multi Row Selection'; readonly rows = inject(DataService).load('company.json'); readonly selected = signal([]); diff --git a/src/app/selection/single-row-selection.component.ts b/src/app/selection/single-row-selection.component.ts index 2cfdd6d07..46924a208 100644 --- a/src/app/selection/single-row-selection.component.ts +++ b/src/app/selection/single-row-selection.component.ts @@ -48,6 +48,7 @@ import { DataService } from '../data.service'; ` }) export class SingleRowSelectionComponent { + static readonly exampleTitle = 'Single Row Selection'; private dataService = inject(DataService); readonly rows = signal([]); diff --git a/src/app/sorting/client-side-sorting.component.ts b/src/app/sorting/client-side-sorting.component.ts index c2c38e871..cd7297eec 100644 --- a/src/app/sorting/client-side-sorting.component.ts +++ b/src/app/sorting/client-side-sorting.component.ts @@ -22,6 +22,7 @@ import { DataService } from '../data.service'; ` }) export class ClientSideSortingComponent { + static readonly exampleTitle = 'Client-side Sorting'; protected readonly rows = inject(DataService).load('company.json'); columns: TableColumn[] = [{ name: 'Company' }, { name: 'Name' }, { name: 'Gender' }]; diff --git a/src/app/sorting/comparator.component.ts b/src/app/sorting/comparator.component.ts index 95b1fa701..915e7635d 100644 --- a/src/app/sorting/comparator.component.ts +++ b/src/app/sorting/comparator.component.ts @@ -21,6 +21,7 @@ import { DataService } from '../data.service'; ` }) export class ComparatorComponent { + static readonly exampleTitle = 'Comparator'; protected readonly rows = inject(DataService) .load('company.json') .pipe(map(data => data.slice(0, 20))); diff --git a/src/app/sorting/default-sort.component.ts b/src/app/sorting/default-sort.component.ts index 8c5ccd3cf..d6d666fa5 100644 --- a/src/app/sorting/default-sort.component.ts +++ b/src/app/sorting/default-sort.component.ts @@ -40,5 +40,6 @@ import { DataService } from '../data.service'; ` }) export class DefaultSortComponent { + static readonly exampleTitle = 'Default Sort'; protected readonly rows = inject(DataService).load('company.json'); } diff --git a/src/app/sorting/server-side-sorting.component.ts b/src/app/sorting/server-side-sorting.component.ts index f0ab2b56f..793f935bd 100644 --- a/src/app/sorting/server-side-sorting.component.ts +++ b/src/app/sorting/server-side-sorting.component.ts @@ -23,6 +23,7 @@ import { DataService } from '../data.service'; ` }) export class ServerSideSortingComponent { + static readonly exampleTitle = 'Server-side Sorting'; private dataService = inject(DataService); readonly loading = signal(false); diff --git a/src/app/summary/custom-template-summary.component.ts b/src/app/summary/custom-template-summary.component.ts index e65044e73..a51395a28 100644 --- a/src/app/summary/custom-template-summary.component.ts +++ b/src/app/summary/custom-template-summary.component.ts @@ -31,6 +31,7 @@ import { DataService } from '../data.service'; styleUrl: './custom-template-summary.component.scss' }) export class CustomTemplateSummaryComponent implements OnInit { + static readonly exampleTitle = 'Custom Template Summary'; private dataService = inject(DataService); readonly rows = signal([]); diff --git a/src/app/summary/inline-html-summary.component.ts b/src/app/summary/inline-html-summary.component.ts index 902fda1ae..4e1bcbe6d 100644 --- a/src/app/summary/inline-html-summary.component.ts +++ b/src/app/summary/inline-html-summary.component.ts @@ -28,6 +28,7 @@ import { DataService } from '../data.service'; ` }) export class InlineHtmlSummaryComponent { + static readonly exampleTitle = 'Inline HTML Summary'; private dataService = inject(DataService); readonly rows = signal([]); diff --git a/src/app/summary/server-side-paging-summary.component.ts b/src/app/summary/server-side-paging-summary.component.ts index ab0f77965..71d024e51 100644 --- a/src/app/summary/server-side-paging-summary.component.ts +++ b/src/app/summary/server-side-paging-summary.component.ts @@ -30,6 +30,7 @@ import { Page } from '../paging/model/page'; providers: [MockServerResultsService] }) export class ServerSidePagingSummaryComponent implements OnInit { + static readonly exampleTitle = 'Server-side Paging Summary'; private serverResultsService = inject(MockServerResultsService); readonly page = signal({ pageNumber: 0, diff --git a/src/app/summary/simple-summary.component.ts b/src/app/summary/simple-summary.component.ts index 8bef5d18a..1f193baa4 100644 --- a/src/app/summary/simple-summary.component.ts +++ b/src/app/summary/simple-summary.component.ts @@ -42,6 +42,7 @@ import { DataService } from '../data.service'; styleUrl: './simple-summary.component.scss' }) export class SimpleSummaryComponent { + static readonly exampleTitle = 'Simple Summary'; protected readonly rows = inject(DataService) .load('company.json') .pipe(map(data => data.slice(0, 5))); diff --git a/src/app/summary/summary-row-actions.component.ts b/src/app/summary/summary-row-actions.component.ts index 4f125477d..95334121c 100644 --- a/src/app/summary/summary-row-actions.component.ts +++ b/src/app/summary/summary-row-actions.component.ts @@ -65,6 +65,7 @@ import { DataService } from '../data.service'; ` }) export class SummaryRowActionsComponent { + static readonly exampleTitle = 'Summary Row Actions'; private dataService = inject(DataService); protected readonly rows = signal([]); protected readonly selected = signal([]); diff --git a/src/app/templates/inline-template.component.ts b/src/app/templates/inline-template.component.ts index 811d4b480..2c8a24824 100644 --- a/src/app/templates/inline-template.component.ts +++ b/src/app/templates/inline-template.component.ts @@ -56,6 +56,7 @@ import { DataService } from '../data.service'; ` }) export class InlineTemplateComponent { + static readonly exampleTitle = 'Inline Template'; protected readonly rows = inject(DataService) .load('company.json') .pipe(map(data => data.slice(0, 5))); diff --git a/src/app/templates/template-ref.component.ts b/src/app/templates/template-ref.component.ts index c1993cb89..320bf6700 100644 --- a/src/app/templates/template-ref.component.ts +++ b/src/app/templates/template-ref.component.ts @@ -34,6 +34,7 @@ import { DataService } from '../data.service'; ` }) export class TemplateRefComponent implements OnInit { + static readonly exampleTitle = 'TemplateRef'; readonly rows = inject(DataService) .load('company.json') .pipe(map(data => data.slice(0, 5))); diff --git a/src/app/tree/client-side-tree.component.ts b/src/app/tree/client-side-tree.component.ts index 0c158f180..d432f1358 100644 --- a/src/app/tree/client-side-tree.component.ts +++ b/src/app/tree/client-side-tree.component.ts @@ -43,6 +43,7 @@ import { DataService } from '../data.service'; styles: ['.icon {height: 10px; width: 10px; }', '.disabled {opacity: 0.5; }'] }) export class ClientSideTreeComponent { + static readonly exampleTitle = 'Client Side Tree'; protected readonly rows = inject(DataService).load('company_tree.json'); onTreeAction(event: any) { diff --git a/src/app/tree/full-screen-tree.component.ts b/src/app/tree/full-screen-tree.component.ts index d7664ecb8..39016d32f 100644 --- a/src/app/tree/full-screen-tree.component.ts +++ b/src/app/tree/full-screen-tree.component.ts @@ -60,6 +60,7 @@ import { DataService } from '../data.service'; styles: ['.icon {height: 10px; width: 10px; }', '.disabled {opacity: 0.5; }'] }) export class FullScreenTreeComponent { + static readonly exampleTitle = 'Full Screen Tree'; private dataService = inject(DataService); readonly rows = signal<(FullEmployee & { treeStatus: TreeStatus; parentId?: string })[]>([]); lastIndex = 15; diff --git a/src/main.ts b/src/main.ts index 34e7a2eb6..e4b94c549 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,8 +4,8 @@ import { bootstrapApplication } from '@angular/platform-browser'; import { provideRouter, withHashLocation } from '@angular/router'; import { providedNgxDatatableConfig } from '@siemens/ngx-datatable'; -import { routes } from './app/app-routing.module'; import { AppComponent } from './app/app.component'; +import { routes } from './app/app.routes'; bootstrapApplication(AppComponent, { providers: [ diff --git a/src/main.web-components.ts b/src/main.web-components.ts index 7c18d221f..cfce6bf84 100644 --- a/src/main.web-components.ts +++ b/src/main.web-components.ts @@ -9,6 +9,7 @@ import { createCustomElement } from '@angular/elements'; import { createApplication } from '@angular/platform-browser'; import { providedNgxDatatableConfig } from '@siemens/ngx-datatable'; +import { exampleLoaders } from './app/app.web-components'; import { DATA_ASSETS_URL } from './app/data.service'; declare global { @@ -27,44 +28,12 @@ createApplication({ }) .then(appRef => { window.loadDatatableExample = async example => { - switch (example) { - case 'fluid-row-height': { - registerExample( - 'ngx-datatable-fluid-row-height', - await import('./app/basic/fluid-row-height.component').then( - c => c.FluidRowHeightComponent - ), - appRef - ); - return; - } - case 'standard-column': { - registerExample( - 'ngx-datatable-standard-column', - await import('./app/columns/fixed-column.component').then(c => c.FixedColumnComponent), - appRef - ); - return; - } - case 'flex-column': { - registerExample( - 'ngx-datatable-flex-column', - await import('./app/columns/flex-column.component').then(c => c.FlexColumnComponent), - appRef - ); - return; - } - case 'force-column': { - registerExample( - 'ngx-datatable-force-column', - await import('./app/columns/force-column.component').then(c => c.ForceColumnComponent), - appRef - ); - return; - } - default: - throw new Error(`Unknown datatable example: ${example}`); + const loadComponent = exampleLoaders.get(example); + if (!loadComponent) { + throw new Error(`Unknown datatable example: ${example}`); } + + registerExample(`ngx-datatable-${example}`, await loadComponent(), appRef); }; window.dispatchEvent(new Event('ngx-datatable-examples-ready')); diff --git a/tools/example-routes-builder/builders.json b/tools/example-routes-builder/builders.json new file mode 100644 index 000000000..62089bbd2 --- /dev/null +++ b/tools/example-routes-builder/builders.json @@ -0,0 +1,8 @@ +{ + "builders": { + "example-routes": { + "implementation": "./dist/example-routes-builder.mjs", + "schema": "./example-routes-builder.schema.json" + } + } +} diff --git a/tools/example-routes-builder/eslint.config.mjs b/tools/example-routes-builder/eslint.config.mjs new file mode 100644 index 000000000..1c1e67f56 --- /dev/null +++ b/tools/example-routes-builder/eslint.config.mjs @@ -0,0 +1,13 @@ +import { defineConfig } from 'eslint/config'; + +import { tsConfig } from '../../eslint.config.mjs'; + +export default defineConfig({ + extends: [...tsConfig], + files: ['**/*.mts'], + languageOptions: { + parserOptions: { + project: ['tools/example-routes-builder/tsconfig.json'] + } + } +}); diff --git a/tools/example-routes-builder/example-routes-builder.mts b/tools/example-routes-builder/example-routes-builder.mts new file mode 100644 index 000000000..906185e4f --- /dev/null +++ b/tools/example-routes-builder/example-routes-builder.mts @@ -0,0 +1,325 @@ +import { createBuilder, type BuilderContext, type BuilderOutput } from '@angular-devkit/architect'; +import { readFile, readdir, writeFile } from 'node:fs/promises'; +import { join, relative, resolve, sep } from 'node:path'; +import ts from 'typescript'; + +interface Options { + sourceRoot: string; + routesFile: string; + webComponentsFile: string; +} + +interface Example { + componentName: string; + filePath: string; + importPath: string; + path: string; +} + +export const generateRoutes = async (options: Options, workspaceRoot: string): Promise => { + const sourceRoot = resolve(workspaceRoot, options.sourceRoot); + const examples = await getExamples(sourceRoot); + + if (!examples.length) { + throw new Error(`No example components found in ${options.sourceRoot}.`); + } + + const defaultExample = + examples.find(example => example.path === 'fluid-row-height') ?? examples[0]; + await Promise.all([ + writeGeneratedFile( + resolve(workspaceRoot, options.routesFile), + generateAngularRoutes(examples, defaultExample) + ), + writeGeneratedFile( + resolve(workspaceRoot, options.webComponentsFile), + generateWebComponentRoutes(examples) + ) + ]); + + return examples.length; +}; + +const execute = async (options: Options, context: BuilderContext): Promise => { + const count = await generateRoutes(options, context.workspaceRoot); + context.logger.info(`Generated routes for ${count} examples.`); + return { success: true }; +}; + +const getExamples = async (sourceRoot: string): Promise => { + const files = await getFiles(sourceRoot); + const examples = await Promise.all( + files.filter(file => file.endsWith('.component.ts')).map(file => getExample(sourceRoot, file)) + ); + + return examples + .filter((example): example is Example => example !== undefined) + .sort((a, b) => a.path.localeCompare(b.path)); +}; + +const getFiles = async (directory: string): Promise => { + const entries = await readdir(directory, { withFileTypes: true }); + const files: string[] = []; + + for (const entry of entries) { + const path = join(directory, entry.name); + if (entry.isDirectory()) { + files.push(...(await getFiles(path))); + } else { + files.push(path); + } + } + + return files; +}; + +const getExample = async (sourceRoot: string, filePath: string): Promise => { + const source = ts.createSourceFile( + filePath, + await readFile(filePath, 'utf8'), + ts.ScriptTarget.Latest, + true + ); + + for (const statement of source.statements) { + if (!ts.isClassDeclaration(statement) || !statement.name) { + continue; + } + + const selector = getComponentSelector(statement); + if (!selector) { + continue; + } + if (!getExampleTitle(statement)) { + throw new Error( + `Example component ${statement.name.text} in ${filePath} must declare a static string exampleTitle.` + ); + } + + const relativePath = relative(sourceRoot, filePath).split(sep).join('/'); + return { + componentName: statement.name.text, + filePath: relativePath, + importPath: `./${relativePath.replace(/\.ts$/, '')}`, + path: selector.replace(/-demo$/, '') + }; + } + + return undefined; +}; + +const getExampleTitle = (declaration: ts.ClassDeclaration): string | undefined => { + for (const member of declaration.members) { + if ( + ts.isPropertyDeclaration(member) && + member.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.StaticKeyword) && + ts.isIdentifier(member.name) && + member.name.text === 'exampleTitle' && + member.initializer && + ts.isStringLiteral(member.initializer) + ) { + return member.initializer.text; + } + } + + return undefined; +}; + +const getComponentSelector = (declaration: ts.ClassDeclaration): string | undefined => { + for (const decorator of ts.getDecorators(declaration) ?? []) { + if ( + !ts.isCallExpression(decorator.expression) || + !ts.isIdentifier(decorator.expression.expression) + ) { + continue; + } + if (decorator.expression.expression.text !== 'Component') { + continue; + } + + const metadata = decorator.expression.arguments[0]; + if (!metadata || !ts.isObjectLiteralExpression(metadata)) { + return undefined; + } + const selector = metadata.properties.find( + selectorProperty => + ts.isPropertyAssignment(selectorProperty) && + ts.isIdentifier(selectorProperty.name) && + selectorProperty.name.text === 'selector' && + ts.isStringLiteral(selectorProperty.initializer) + ); + if (selector && ts.isPropertyAssignment(selector) && ts.isStringLiteral(selector.initializer)) { + return selector.initializer.text.endsWith('-demo') ? selector.initializer.text : undefined; + } + } + + return undefined; +}; + +const generateAngularRoutes = (examples: Example[], defaultExample: Example): string => { + const factory = ts.factory; + const routes = [ + factory.createObjectLiteralExpression( + [ + property('path', ''), + property('redirectTo', defaultExample.path), + property('pathMatch', 'full') + ], + true + ), + ...examples.map(createRoute) + ]; + return printSource([ + createNamedImport('Routes', '@angular/router'), + factory.createVariableStatement( + [factory.createModifier(ts.SyntaxKind.ExportKeyword)], + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + 'routes', + undefined, + factory.createTypeReferenceNode('Routes'), + factory.createArrayLiteralExpression(routes, true) + ) + ], + ts.NodeFlags.Const + ) + ) + ]); +}; + +const generateWebComponentRoutes = (examples: Example[]): string => { + const factory = ts.factory; + const loaderType = createLoaderType(); + return printSource([ + createNamedImport('Type', '@angular/core'), + factory.createVariableStatement( + [factory.createModifier(ts.SyntaxKind.ExportKeyword)], + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + 'exampleLoaders', + undefined, + factory.createTypeReferenceNode('ReadonlyMap', [ + factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), + loaderType + ]), + factory.createNewExpression( + factory.createIdentifier('Map'), + [factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), loaderType], + [ + factory.createArrayLiteralExpression( + examples.map(example => + factory.createArrayLiteralExpression([ + stringLiteral(example.path), + createComponentLoader(example) + ]) + ), + true + ) + ] + ) + ) + ], + ts.NodeFlags.Const + ) + ) + ]); +}; + +const createLoaderType = (): ts.FunctionTypeNode => { + const factory = ts.factory; + return factory.createFunctionTypeNode( + undefined, + [], + factory.createTypeReferenceNode('Promise', [ + factory.createTypeReferenceNode('Type', [ + factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword) + ]) + ]) + ); +}; + +const createRoute = (example: Example): ts.ObjectLiteralExpression => { + const factory = ts.factory; + return factory.createObjectLiteralExpression( + [ + property('path', example.path), + factory.createPropertyAssignment( + 'data', + factory.createObjectLiteralExpression([property('sourcePath', example.filePath)], false) + ), + factory.createPropertyAssignment('loadComponent', createComponentLoader(example)) + ], + true + ); +}; + +const createComponentLoader = (example: Example): ts.ArrowFunction => { + const factory = ts.factory; + return factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createCallExpression(factory.createIdentifier('import'), undefined, [ + stringLiteral(example.importPath) + ]), + 'then' + ), + undefined, + [ + factory.createArrowFunction( + undefined, + undefined, + [factory.createParameterDeclaration(undefined, undefined, 'component')], + undefined, + factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + factory.createPropertyAccessExpression( + factory.createIdentifier('component'), + factory.createIdentifier(example.componentName) + ) + ) + ] + ) + ); +}; + +const createNamedImport = (name: string, moduleSpecifier: string): ts.ImportDeclaration => { + return ts.factory.createImportDeclaration( + undefined, + ts.factory.createImportClause( + undefined, + undefined, + ts.factory.createNamedImports([ + ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(name)) + ]) + ), + stringLiteral(moduleSpecifier) + ); +}; + +const property = (name: string, value: string): ts.PropertyAssignment => { + return ts.factory.createPropertyAssignment(name, stringLiteral(value)); +}; + +const stringLiteral = (value: string): ts.StringLiteral => { + return ts.factory.createStringLiteral(value, true); +}; + +const writeGeneratedFile = async (filePath: string, source: string): Promise => { + await writeFile(filePath, source); +}; + +const printSource = (statements: ts.Statement[]): string => { + const sourceFile = ts.factory.updateSourceFile( + ts.createSourceFile('generated.ts', '', ts.ScriptTarget.Latest, false), + statements + ); + return `// This file is generated by the example-routes builder. Do not edit manually.\n${ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }).printFile(sourceFile)}`; +}; + +export default createBuilder(execute); diff --git a/tools/example-routes-builder/example-routes-builder.schema.json b/tools/example-routes-builder/example-routes-builder.schema.json new file mode 100644 index 000000000..60242d69d --- /dev/null +++ b/tools/example-routes-builder/example-routes-builder.schema.json @@ -0,0 +1,11 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "object", + "additionalProperties": false, + "properties": { + "sourceRoot": { "type": "string" }, + "routesFile": { "type": "string" }, + "webComponentsFile": { "type": "string" } + }, + "required": ["sourceRoot", "routesFile", "webComponentsFile"] +} diff --git a/tools/example-routes-builder/package.json b/tools/example-routes-builder/package.json new file mode 100644 index 000000000..275a23652 --- /dev/null +++ b/tools/example-routes-builder/package.json @@ -0,0 +1,6 @@ +{ + "name": "@ngx-datatable/example-routes-builder", + "private": true, + "type": "module", + "builders": "./builders.json" +} diff --git a/tools/example-routes-builder/tsconfig.json b/tools/example-routes-builder/tsconfig.json new file mode 100644 index 000000000..101b1f45b --- /dev/null +++ b/tools/example-routes-builder/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "module": "NodeNext", + "moduleResolution": "NodeNext", + "outDir": "./dist", + "rootDir": ".", + "skipLibCheck": true, + "target": "ES2022", + "types": ["node"] + }, + "files": ["example-routes-builder.mts"] +} diff --git a/tsconfig.json b/tsconfig.json index 90cb97fa1..c7cc14286 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -34,6 +34,9 @@ }, { "path": "./projects/ngx-datatable/tsconfig.spec.json" + }, + { + "path": "./tools/example-routes-builder/tsconfig.json" } ] }