add table header and sync width directive
This commit is contained in:
parent
f0c4408764
commit
7e9b28a10c
@ -18,3 +18,37 @@
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.header-item {
|
||||
background-color: $btn-bg;
|
||||
height: 50px;
|
||||
padding: 0 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 1;
|
||||
border-bottom: 1px solid $separator;
|
||||
box-sizing: border-box;
|
||||
|
||||
> *:not(:last-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
> *:not(:last-child) {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&.selection-enabled {
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.scrollbar-placeholder {
|
||||
width: 11px;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ export * from './lib/utils/types/utility-types';
|
||||
export * from './lib/utils/types/tooltip-positions.type';
|
||||
export * from './lib/utils/decorators/bind.decorator';
|
||||
export * from './lib/utils/decorators/required.decorator';
|
||||
export * from './lib/utils/decorators/debounce.decorator';
|
||||
export * from './lib/buttons/circle-button/circle-button.type';
|
||||
export * from './lib/buttons/circle-button/circle-button.component';
|
||||
export * from './lib/filtering/filter-utils';
|
||||
@ -25,3 +26,4 @@ export * from './lib/tables/entities.service';
|
||||
export * from './lib/tables/listing-component.directive';
|
||||
export * from './lib/tables/models/table-column-config.model';
|
||||
export * from './lib/tables/table-column-name/table-column-name.component';
|
||||
export * from './lib/tables/table-header/table-header.component';
|
||||
|
||||
@ -12,6 +12,9 @@ import { SortByPipe } from './sorting/sort-by.pipe';
|
||||
import { HumanizePipe } from './utils/pipes/humanize.pipe';
|
||||
import { TableColumnNameComponent } from './tables/table-column-name/table-column-name.component';
|
||||
import { QuickFiltersComponent } from './filtering/quick-filters/quick-filters.component';
|
||||
import { TableHeaderComponent } from './tables/table-header/table-header.component';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SyncWidthDirective } from './tables/sync-width.directive';
|
||||
|
||||
const buttons = [IconButtonComponent, ChevronButtonComponent, CircleButtonComponent];
|
||||
|
||||
@ -19,14 +22,16 @@ const inputs = [RoundCheckboxComponent];
|
||||
|
||||
const matModules = [MatIconModule, MatButtonModule, MatTooltipModule];
|
||||
|
||||
const components = [...buttons, ...inputs, TableColumnNameComponent, QuickFiltersComponent];
|
||||
const modules = [...matModules, TranslateModule];
|
||||
|
||||
const pipes = [SortByPipe, HumanizePipe];
|
||||
const components = [...buttons, ...inputs, TableColumnNameComponent, QuickFiltersComponent, TableHeaderComponent];
|
||||
|
||||
const utils = [SortByPipe, HumanizePipe, SyncWidthDirective];
|
||||
|
||||
@NgModule({
|
||||
declarations: [...components, ...pipes],
|
||||
imports: [CommonModule, ...matModules],
|
||||
exports: [...components, ...pipes, ...matModules]
|
||||
declarations: [...components, ...utils],
|
||||
imports: [CommonModule, ...modules],
|
||||
exports: [...components, ...utils, ...modules]
|
||||
})
|
||||
export class CommonUiModule {
|
||||
constructor(private readonly _iconRegistry: MatIconRegistry, private readonly _sanitizer: DomSanitizer) {
|
||||
|
||||
@ -10,7 +10,7 @@ import { FilterService } from '../filter.service';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class QuickFiltersComponent {
|
||||
@Input() @Required() quickFilters!: Set<NestedFilter>;
|
||||
@Input() @Required() quickFilters!: readonly NestedFilter[];
|
||||
|
||||
constructor(readonly filterService: FilterService) {}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Directive, Injector, OnDestroy, Provider } from '@angular/core';
|
||||
import { Directive, Injector, OnDestroy } from '@angular/core';
|
||||
import { combineLatest, Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, map, switchMap } from 'rxjs/operators';
|
||||
import { FilterService } from '../filtering/filter.service';
|
||||
@ -11,7 +11,7 @@ import { KeysOf } from '../utils/types/utility-types';
|
||||
import { TableColumnConfig } from './models/table-column-config.model';
|
||||
import { EntitiesService } from './entities.service';
|
||||
|
||||
export const DefaultListingServices = new Set<Provider>().add(FilterService).add(SearchService).add(EntitiesService).add(SortingService);
|
||||
export const DefaultListingServices = [FilterService, SearchService, EntitiesService, SortingService] as const;
|
||||
|
||||
@Directive()
|
||||
export abstract class ListingComponent<T extends object> extends AutoUnsubscribe implements OnDestroy {
|
||||
@ -23,7 +23,7 @@ export abstract class ListingComponent<T extends object> extends AutoUnsubscribe
|
||||
readonly noMatch$ = this._noMatch$;
|
||||
readonly sortedDisplayedEntities$ = this._sortedDisplayedEntities$;
|
||||
|
||||
abstract readonly tableColumnConfigs: TableColumnConfig<T>[];
|
||||
abstract readonly tableColumnConfigs: readonly TableColumnConfig<T>[];
|
||||
/**
|
||||
* Key used in the *trackBy* function with **ngFor* or **cdkVirtualFor*
|
||||
* and in the default sorting and as the search field
|
||||
|
||||
71
src/lib/tables/sync-width.directive.ts
Normal file
71
src/lib/tables/sync-width.directive.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { Directive, ElementRef, HostListener, Input, OnDestroy } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[iqserSyncWidth]'
|
||||
})
|
||||
export class SyncWidthDirective implements OnDestroy {
|
||||
@Input() iqserSyncWidth!: string;
|
||||
private readonly _interval: number;
|
||||
|
||||
constructor(private readonly _elementRef: ElementRef) {
|
||||
this._interval = setInterval(() => {
|
||||
this._matchWidth();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
clearInterval(this._interval);
|
||||
}
|
||||
|
||||
private _matchWidth() {
|
||||
const headerItems = this._elementRef.nativeElement.children;
|
||||
const tableRows = this._elementRef.nativeElement.parentElement.parentElement.getElementsByClassName(this.iqserSyncWidth);
|
||||
|
||||
if (!tableRows || !tableRows.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._elementRef.nativeElement.setAttribute('synced', true);
|
||||
|
||||
const { tableRow, length } = this._sampleRow(tableRows);
|
||||
if (!tableRow) return;
|
||||
|
||||
const hasExtraColumns = headerItems.length !== length ? 1 : 0;
|
||||
|
||||
for (let idx = 0; idx < length - hasExtraColumns - 1; ++idx) {
|
||||
if (headerItems[idx]) {
|
||||
headerItems[idx].style.width = `${tableRow.children[idx].getBoundingClientRect().width}px`;
|
||||
headerItems[idx].style.minWidth = `${tableRow.children[idx].getBoundingClientRect().width}px`;
|
||||
}
|
||||
}
|
||||
|
||||
for (let idx = length - hasExtraColumns - 1; idx < headerItems.length; ++idx) {
|
||||
if (headerItems[idx]) {
|
||||
headerItems[idx].style.minWidth = `0`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('window:resize')
|
||||
onResize() {
|
||||
this._matchWidth();
|
||||
}
|
||||
|
||||
private _sampleRow(tableRows: HTMLCollectionOf<Element>): {
|
||||
tableRow?: Element;
|
||||
length: number;
|
||||
} {
|
||||
let length = 0;
|
||||
let tableRow: Element | undefined;
|
||||
|
||||
for (let idx = 0; idx < tableRows.length; ++idx) {
|
||||
const row = tableRows.item(idx);
|
||||
if (row && row.children.length > length) {
|
||||
length = row.children.length;
|
||||
tableRow = row;
|
||||
}
|
||||
}
|
||||
|
||||
return { tableRow, length };
|
||||
}
|
||||
}
|
||||
42
src/lib/tables/table-header/table-header.component.html
Normal file
42
src/lib/tables/table-header/table-header.component.html
Normal file
@ -0,0 +1,42 @@
|
||||
<div [class.selection-enabled]="selectionEnabled" class="header-item">
|
||||
<iqser-round-checkbox
|
||||
(click)="entitiesService.selectAll()"
|
||||
*ngIf="selectionEnabled"
|
||||
[active]="entitiesService.areAllSelected$ | async"
|
||||
[indeterminate]="entitiesService.notAllSelected$ | async"
|
||||
></iqser-round-checkbox>
|
||||
|
||||
<span class="all-caps-label">
|
||||
{{ tableHeaderLabel | translate: { length: (entitiesService.displayedLength$ | async) } }}
|
||||
</span>
|
||||
|
||||
<ng-container [ngTemplateOutlet]="bulkActions"></ng-container>
|
||||
|
||||
<iqser-quick-filters *ngIf="quickFilters$ | async as quickFilters" [quickFilters]="quickFilters"></iqser-quick-filters>
|
||||
|
||||
<!-- Custom content-->
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
|
||||
<div
|
||||
[class.no-data]="entitiesService.noData$ | async"
|
||||
[class.selection-enabled]="selectionEnabled"
|
||||
class="table-header"
|
||||
iqserSyncWidth="table-item"
|
||||
>
|
||||
<div *ngIf="selectionEnabled" class="select-oval-placeholder"></div>
|
||||
|
||||
<iqser-table-column-name
|
||||
*ngFor="let config of tableColumnConfigs"
|
||||
[class]="config.class"
|
||||
[sortByKey]="config.sortByKey"
|
||||
[label]="config.label | translate"
|
||||
[leftIcon]="config.leftIcon"
|
||||
[rightIconTooltip]="config.rightIconTooltip"
|
||||
[rightIcon]="config.rightIcon"
|
||||
></iqser-table-column-name>
|
||||
|
||||
<div *ngIf="hasEmptyColumn"></div>
|
||||
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
23
src/lib/tables/table-header/table-header.component.ts
Normal file
23
src/lib/tables/table-header/table-header.component.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, TemplateRef } from '@angular/core';
|
||||
import { Required } from '../../utils/decorators/required.decorator';
|
||||
import { FilterService } from '../../filtering/filter.service';
|
||||
import { TableColumnConfig } from '../models/table-column-config.model';
|
||||
import { EntitiesService } from '../entities.service';
|
||||
|
||||
@Component({
|
||||
selector: 'iqser-table-header',
|
||||
templateUrl: './table-header.component.html',
|
||||
styleUrls: ['./table-header.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class TableHeaderComponent<T extends object> {
|
||||
@Input() @Required() tableHeaderLabel!: string;
|
||||
@Input() @Required() tableColumnConfigs!: readonly TableColumnConfig<T>[];
|
||||
@Input() hasEmptyColumn = false;
|
||||
@Input() selectionEnabled = false;
|
||||
@Input() bulkActions?: TemplateRef<any>;
|
||||
|
||||
readonly quickFilters$ = this.filterService.getFilterModels$('quickFilters');
|
||||
|
||||
constructor(readonly entitiesService: EntitiesService<T>, readonly filterService: FilterService) {}
|
||||
}
|
||||
14
src/lib/utils/decorators/debounce.decorator.ts
Normal file
14
src/lib/utils/decorators/debounce.decorator.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export function Debounce(delay = 300): MethodDecorator {
|
||||
return function (target: Object, propertyKey: PropertyKey, descriptor: PropertyDescriptor) {
|
||||
let timeout: number | undefined;
|
||||
|
||||
const original = descriptor.value;
|
||||
|
||||
descriptor.value = function (...args: unknown[]) {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => original.apply(this, args), delay);
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user