projects/angular/components/ui-grid/src/components/ui-grid-toggle-columns/ui-grid-toggle-columns.component.ts
changeDetection | ChangeDetectionStrategy.OnPush |
encapsulation | ViewEncapsulation.None |
selector | COMPONENT_SELECTOR |
styleUrls | ./ui-grid-toggle-columns.component.scss |
templateUrl | ./ui-grid-toggle-columns.component.html |
Properties |
|
Methods |
Inputs |
Outputs |
HostBindings |
Accessors |
constructor(_elementRef: ElementRef
|
|||||||||
Parameters :
|
dirty | |
Type : boolean
|
|
Default value : false
|
|
options | |
resetToDefaults | |
Type : string
|
|
showDivider | |
Type : boolean
|
|
Default value : false
|
|
togglePlaceholderTitle | |
Type : string
|
|
toggleTitle | |
Type : string
|
|
toggleTooltip | |
Type : string
|
|
useLegacyDesign | |
Type : boolean
|
|
Default value : false
|
|
resetColumns | |
Type : EventEmitter
|
|
visibleColumns | |
Type : EventEmitter
|
|
visibleColumnsToggled | |
Type : EventEmitter
|
|
class |
Type : string
|
Default value : COMPONENT_SELECTOR
|
ngAfterViewInit |
ngAfterViewInit()
|
Returns :
void
|
ngOnDestroy |
ngOnDestroy()
|
Returns :
void
|
reset |
reset()
|
Returns :
void
|
resetKeyDown | ||||||
resetKeyDown(e: KeyboardEvent)
|
||||||
Parameters :
Returns :
void
|
selectionChange | |||||
selectionChange(undefined: MatSelectChange)
|
|||||
Parameters :
Returns :
void
|
hostClass |
Default value : COMPONENT_SELECTOR
|
Decorators :
@HostBinding('class')
|
Optional resetBtn |
Type : MatAnchor
|
Decorators :
@ViewChild('resetBtn', {static: false})
|
Optional selectColumns |
Type : MatSelect
|
Decorators :
@ViewChild(MatSelect, {static: false})
|
options | ||||||
getoptions()
|
||||||
setoptions(options: IVisibleModel<T>[] | null)
|
||||||
Parameters :
Returns :
void
|
selected |
getselected()
|
import isEqual from 'lodash-es/isEqual';
import {
fromEvent,
Subject,
} from 'rxjs';
import {
filter,
takeUntil,
} from 'rxjs/operators';
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
HostBinding,
Input,
OnDestroy,
Output,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import type { MatAnchor } from '@angular/material/button';
import {
MatSelect,
MatSelectChange,
} from '@angular/material/select';
import {
IGridDataEntry,
IVisibleModel,
} from '../../models';
const COMPONENT_SELECTOR = 'ui-grid-toggle-columns';
@Component({
selector: COMPONENT_SELECTOR,
templateUrl: './ui-grid-toggle-columns.component.html',
styleUrls: ['./ui-grid-toggle-columns.component.scss'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UiGridToggleColumnsComponent<T extends IGridDataEntry> implements AfterViewInit, OnDestroy {
@HostBinding('class')
hostClass = COMPONENT_SELECTOR;
@HostBinding(`class.${COMPONENT_SELECTOR}-dirty`)
@Input()
dirty = false;
@Input()
showDivider = false;
@Input()
set options(options: IVisibleModel<T>[] | null) {
if (!options || isEqual(this._options, options)) { return; }
this._options = options;
this._selected = options
.filter(({ checked }) => checked)
.map(o => o.property);
}
get options() {
return this._options;
}
@Input()
useLegacyDesign = false;
@Input()
toggleTooltip?: string;
@Input()
toggleTitle?: string;
@Input()
resetToDefaults?: string;
@Input()
togglePlaceholderTitle?: string;
@Output()
visibleColumns = new EventEmitter<(string | keyof T)[]>();
@Output()
resetColumns = new EventEmitter<void>();
@Output()
visibleColumnsToggled = new EventEmitter<boolean>();
@ViewChild(MatSelect, { static: false })
selectColumns?: MatSelect;
@ViewChild('resetBtn', { static: false })
resetBtn?: MatAnchor;
get selected() {
return this._selected;
}
private get _currentIndex() {
if (!this.selectColumns) { return null; }
// eslint-disable-next-line no-underscore-dangle
return this.selectColumns._keyManager.activeItemIndex;
}
private set _currentIndex(i: number | null) {
if (i == null || !this.selectColumns) { return; }
// eslint-disable-next-line no-underscore-dangle
this.selectColumns._keyManager.setActiveItem(i);
}
private get _isFirstValidIndex() {
return !this._isResetIndex &&
this._currentIndex === this._options.findIndex(o => !o.disabled);
}
private get _isResetIndex() {
return this._currentIndex === -1;
}
private _selected: (string | keyof T)[] = [];
private _options: IVisibleModel<T>[] = [];
private _destroyed$ = new Subject<void>();
constructor(
private _elementRef: ElementRef<HTMLElement>,
private _cd: ChangeDetectorRef,
) { }
ngAfterViewInit() {
fromEvent<KeyboardEvent>(
this._elementRef.nativeElement,
'keydown',
{ capture: true },
).pipe(
filter(_ => this.dirty),
takeUntil(this._destroyed$),
).subscribe(this._onKeyDown);
this.selectColumns!.openedChange
.pipe(
takeUntil(this._destroyed$),
)
.subscribe((open) => {
this.visibleColumnsToggled.emit(open);
});
}
ngOnDestroy() {
this._destroyed$.next();
this._destroyed$.complete();
}
selectionChange({ value }: MatSelectChange) {
this._selected = value;
this._options
.forEach(c => c.checked = value.includes(c.property));
this.visibleColumns.emit(value);
}
reset() {
this.resetColumns.emit();
this.selectColumns!.close();
this.selectColumns!.focus();
}
resetKeyDown(e: KeyboardEvent) {
if (this._isArrowUp(e)) {
e.stopImmediatePropagation();
return;
}
if (this._isArrowDown(e)) {
this.selectColumns?.focus();
}
}
private _onKeyDown = (e: KeyboardEvent) => {
if (
this._isResetIndex &&
this._isArrowDown(e)
) {
e.preventDefault();
e.stopImmediatePropagation();
this.selectColumns?.focus();
// eslint-disable-next-line no-underscore-dangle
this.selectColumns?._keyManager.setFirstItemActive();
this._cd.detectChanges();
}
if (
this._isArrowUp(e) &&
this._isFirstValidIndex
) {
e.stopPropagation();
this._focusOnReset();
}
};
private _isArrowUp(e: KeyboardEvent) {
return ['Up', 'ArrowUp'].includes(e.key);
}
private _isArrowDown(e: KeyboardEvent) {
return ['Down', 'ArrowDown'].includes(e.key);
}
private _focusOnReset() {
this._currentIndex = -1;
this.resetBtn?.focus('keyboard');
this._cd.detectChanges();
}
}
<ng-container *ngIf="options!.length">
<button *ngIf="!useLegacyDesign; else classicColumnToggleTmpl"
[color]="dirty ? 'primary' : undefined"
(click)="selectColumns!.open(); selectColumns!.focus()"
type="button"
mat-button>
<mat-icon class="material-icons-outlined">table_chart</mat-icon>
<span>{{togglePlaceholderTitle}}</span>
<mat-icon class="material-icons-outlined">keyboard_arrow_down</mat-icon>
</button>
<mat-select [value]="selected"
[aria-label]="togglePlaceholderTitle ?? ''"
[disableOptionCentering]="true"
[class.use-alternate]="!useLegacyDesign"
[panelClass]="['ui-grid-toggle-panel', useLegacyDesign ? '' : 'ui-grid-toggle-panel-alternate']"
(selectionChange)="selectionChange($event)"
tabIndex="-1"
multiple>
<mat-optgroup>
<span>
<mat-icon class="material-icons-outlined">view_column</mat-icon> {{ toggleTitle }}
</span>
<a #resetBtn
*ngIf="dirty"
[matTooltip]="resetToDefaults ?? ''"
(click)="reset()"
(keydown.enter)="reset()"
(keydown.space)="reset()"
(keydown)="resetKeyDown($event)"
class="ui-grid-toggle-reset"
role="button"
color="primary"
tabindex="-1"
mat-button>
{{resetToDefaults}}
</a>
<mat-option *ngFor="let o of options"
[matTooltip]="o.label"
[disabled]="o.disabled"
[value]="o.property">{{ o.label }}</mat-option>
</mat-optgroup>
</mat-select>
<mat-divider *ngIf="showDivider"
[vertical]="true"></mat-divider>
</ng-container>
<ng-template #classicColumnToggleTmpl>
<button [matTooltip]="togglePlaceholderTitle ?? ''"
[color]="dirty ? 'primary' : undefined"
(click)="selectColumns!.open(); selectColumns!.focus()"
matTooltipPosition="right"
type="button"
mat-icon-button>
<mat-icon class="material-icons-outlined">table_chart</mat-icon>
</button>
</ng-template>
./ui-grid-toggle-columns.component.scss
$toggle-col-width: 10em;
.ui-grid-toggle-columns {
margin-right: 6px;
padding-left: 0;
display: flex;
mat-divider {
margin-left: 6px;
height: 32px;
align-self: center;
}
mat-select {
overflow: hidden;
width: 0;
// adjustments in order to match trigger icon with optgroup-label icon
position: relative;
top: -10px;
left: -4px;
&.use-alternate {
top: -10px;
left: -67px;
}
}
}
// overlay panel
.ui-grid-toggle-panel {
// IE fix - min-width with calc does not render properly so we add a fallback in case min-width is evaluated even lower
width: 210px !important;
.mat-optgroup-label {
padding-left: 12px;
display: flex;
padding-right: 6px;
justify-content: space-between;
align-items: center;
.mat-icon {
margin-right: 0;
}
}
&-alternate {
.mat-optgroup-label {
padding-right: 3px;
}
}
}