@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
1,089 lines • 319 kB
JavaScript
import { CdkHeaderCell, CdkTable } from '@angular/cdk/table';
import { Component, ContentChild, ContentChildren, ElementRef, EventEmitter, Inject, Input, Optional, Output, QueryList, ViewChild, ViewChildren, ViewContainerRef, forwardRef } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { ActivatedRoute } from '@angular/router';
import { parseInt as _parseInt, castArray, flow, get, indexOf, isEmpty, isNil, union, uniqBy, without } from 'lodash-es';
import { BsModalService } from 'ngx-bootstrap/modal';
import { BehaviorSubject, Subject, combineLatest, forkJoin, isObservable, merge, of, pipe } from 'rxjs';
import { combineLatestWith, concatMap, debounceTime, delay, distinctUntilChanged, filter, first, map, mergeMap, startWith, switchMap, take, takeUntil, tap } from 'rxjs/operators';
import { AlertService } from '../alert/alert.service';
import { EmptyStateContextDirective } from '../common/empty-state/empty-state-context.directive';
import { toObservable } from '../common/extension-hooks';
import { LoadMoreComponent } from '../common/load-more.component';
import { gettext } from '../i18n/gettext';
import { GainsightService } from '../product-experience/gainsight.service';
import { PRODUCT_EXPERIENCE_EVENT_SOURCE } from '../product-experience/product-experience.model';
import { ActionControlsExtensionService } from './action-controls-extension.service';
import { ColumnDirective } from './column/column.directive';
import { CustomColumn } from './column/custom.column';
import { ExpandableRowColumn } from './column/expandable-row-column/expandable.data-grid-column';
import { ConfigureCustomColumnComponent } from './configure-custom-column';
import { DATA_GRID_CONFIGURATION_STRATEGY } from './data-grid-configuration.model';
import { BuiltInActionType, FilteringActionType, minColumnGridTrackSize, ratiosByColumnTypes } from './data-grid.model';
import { DataGridService } from './data-grid.service';
import { ExpandableRowDirective } from './expandable-row.directive';
import { GridDataSource } from './grid-data-source';
import { PX_ACTIONS, PX_EVENT_NAME } from './product-experience.constants';
import * as i0 from "@angular/core";
import * as i1 from "./data-grid.service";
import * as i2 from "@angular/platform-browser";
import * as i3 from "../product-experience/gainsight.service";
import * as i4 from "ngx-bootstrap/modal";
import * as i5 from "../alert/alert.service";
import * as i6 from "./action-controls-extension.service";
import * as i7 from "@angular/router";
import * as i8 from "@angular/common";
import * as i9 from "@angular/cdk/table";
import * as i10 from "@angular/cdk/drag-drop";
import * as i11 from "@angular/forms";
import * as i12 from "../common/icon.directive";
import * as i13 from "../i18n/c8y-translate.directive";
import * as i14 from "../common/loading.component";
import * as i15 from "../modal/popover-confirm.component";
import * as i16 from "ngx-bootstrap/dropdown";
import * as i17 from "ngx-bootstrap/popover";
import * as i18 from "ngx-bootstrap/tooltip";
import * as i19 from "ngx-bootstrap/pagination";
import * as i20 from "../product-experience/product-experience.directive";
import * as i21 from "@angular/cdk/a11y";
import * as i22 from "./column/cell-renderer.component";
import * as i23 from "./column/filtering-form-renderer.component";
import * as i24 from "../i18n/c8y-translate.pipe";
import * as i25 from "../common/map-function.pipe";
import * as i26 from "./filter-chip/filter-mapper.pipe";
import * as i27 from "./filter-chip/grouped-filter-chips.pipe";
import * as i28 from "./visible-controls.pipe";
var SortingOrder;
(function (SortingOrder) {
SortingOrder["ASC"] = "asc";
SortingOrder["DESC"] = "desc";
})(SortingOrder || (SortingOrder = {}));
export class DataGridComponent {
/** The list of rows to be displayed in the grid (used for client side data). */
set _rows(rows) {
this.rows = rows || [];
}
/** Pagination settings, e.g. allows for setting current page or page size. */
set _pagination(pagination) {
this.pagination = pagination;
}
/** Sets load more mode. */
set _infiniteScroll(infiniteScroll) {
this.infiniteScroll = infiniteScroll;
}
/**
* Sets a callback function which will be invoked whenever data needs to be loaded from server.
* The function should take [[DataSourceModifier]] and return [[ServerSideDataResult]].
*/
set _serverSideDataCallback(serverSideDataCallback) {
this.serverSideDataCallback = serverSideDataCallback;
}
/** Determines whether items can be selected by clicking a checkbox in the first column. */
set _selectable(selectable) {
this.selectable = selectable;
}
/** Restricts selection to a single row only. Selection column displays radio button instead of checkboxes */
set _singleSelection(singleSelection) {
this.singleSelection = singleSelection;
}
/** Determines which item's property will be used to distinguish selection status. */
set _selectionPrimaryKey(selectionPrimaryKey) {
this.selectionPrimaryKey = selectionPrimaryKey;
}
/** Sets display options. */
set _displayOptions(displayOptions) {
this.displayOptions = { ...this.displayOptions, ...displayOptions };
}
/** Sets action controls (actions available for individual items). */
set _actionControls(actionControls) {
this.actionControlsInput$.next(actionControls);
}
/** Sets bulk action controls (actions available for items selected by user). */
set _bulkActionControls(bulkActionControls) {
this.bulkActionControls = bulkActionControls || [];
}
/** Sets header action controls (actions available from data grid header). */
set _headerActionControls(headerActionControls) {
this.headerActionControls = headerActionControls || [];
}
constructor(configurationStrategy, dataGridService, sanitizer, gainsightService, bsModalService, alertService, actionControlsService, route) {
this.configurationStrategy = configurationStrategy;
this.dataGridService = dataGridService;
this.sanitizer = sanitizer;
this.gainsightService = gainsightService;
this.bsModalService = bsModalService;
this.alertService = alertService;
this.actionControlsService = actionControlsService;
this.route = route;
/** The title for the data grid, it's displayed in the grid's header. */
this.title = gettext('Items');
/** The label for load more button. */
this.loadMoreItemsLabel = gettext('Load more items');
/** The label for loading indicator. */
this.loadingItemsLabel = gettext('Loading items…');
/** Determines whether text search input is shown in the grid's header. */
this.showSearch = false;
this.columns = [];
this.dataSource = new GridDataSource();
this.filteringLabelsParams = {
filteredItemsCount: 0,
allItemsCount: 0
};
this.paginationLabelParams = {
pageFirstItemIdx: 0,
pageLastItemIdx: 0,
itemsTotal: 0
};
this.possiblePageSizes = [25, 50, 100];
this.minPossiblePageSize = Math.min(...this.possiblePageSizes);
this.selectable = false;
this.singleSelection = false;
this.selectionPrimaryKey = 'id';
this.displayOptions = {
striped: true,
bordered: false,
gridHeader: true,
filter: true,
hover: true
};
this.actionControls = [];
/** Sets initial search text. */
this.searchText = '';
/** Determines if custom columns button will be enabled. */
this.configureColumnsEnabled = true;
/** Shows the warning for the sub-assets counter */
this.showCounterWarning = false;
/**
* Sets the class name used for active rows (last clicked).
* Set empty string to disable appending active class to grid rows.
*/
this.activeClassName = 'active';
/** Determines if the rows of the data grid will be expandable.
* Possible values:
* - `NONE` - no expandable rows (default value)
* - `SYNC` - additional column with expand button is displayed and expandable rows are expanding synchronously when button is clicked
* - `ASYNC` - additional column with expand button is displayed and expandable rows are expanding asynchronously when button is clicked
*/
this.expandableRows = 'NONE';
/** Emits an event when mouse is over a row. */
this.rowMouseOver = new EventEmitter();
/** Emits an event when mouse leaves a row. */
this.rowMouseLeave = new EventEmitter();
/** Emits an event when a row is clicked. */
this.rowClick = new EventEmitter();
/** Emits an event when grid's configuration is changed. */
this.onConfigChange = new EventEmitter();
/** Emits an event before the filter is applied. */
this.onBeforeFilter = new EventEmitter();
/** Emits an event before the search is performed. */
this.onBeforeSearch = new EventEmitter();
/** Emits an event when a filter is applied in a column. */
this.onFilter = new EventEmitter();
/** Emits an event when items selection changes. The array contains keys of selected items (key property is defined by `selectionPrimaryKey`). */
this.itemsSelect = new EventEmitter();
/** Emits an event when reload button is clicked. */
this.onReload = new EventEmitter();
/** Emits an event when a custom column is added */
this.onAddCustomColumn = new EventEmitter();
/** Emits an event when a custom column is removed */
this.onRemoveCustomColumn = new EventEmitter();
/** Emits an event after the column filter has been reset */
this.onColumnFilterReset = new EventEmitter();
/** Emits an event when column sorting has been changed */
this.onSort = new EventEmitter();
/** Emits an event when page size has been changed */
this.onPageSizeChange = new EventEmitter();
/** Emits an event when column order has been changed */
this.onColumnReordered = new EventEmitter();
/** Emits an event when column order has been changed */
this.onColumnVisibilityChange = new EventEmitter();
this.columnNames = [];
this.styles = {
tableCursor: 'auto',
gridTemplateColumns: undefined,
gridInfiniteScrollColumn: undefined
};
this.searchText$ = new EventEmitter();
this.filteringApplied = false;
this.columnsWithFiltersApplied = [];
this.totalPagesCount$ = new BehaviorSubject(Infinity);
this.hidePagination$ = this.totalPagesCount$.pipe(map(totalPagesCount => totalPagesCount <= 1), delay(0) // prevents ExpressionChangedAfterItHasBeenCheckedError
);
this.selectedItemIds = [];
this.currentPageSelectionState = {
allSelected: false,
allDeselected: true
};
this.builtInActionType = {
Edit: BuiltInActionType.Edit,
Delete: BuiltInActionType.Delete,
Export: BuiltInActionType.Export
};
this.confirmRemoveColumnButtons = [
{
label: gettext('Cancel'),
action: () => Promise.resolve(false)
},
{
label: gettext('Remove`column,verb`'),
status: 'danger',
action: () => Promise.resolve(true)
}
];
this.isConfigContextKnown = false;
/**
* A map of rows which have been expanded.
*/
this.expandedRows = new Map();
/** Product experience constants declarations */
this.productExperienceEvent = { eventName: PX_EVENT_NAME };
this.PX_ACTIONS = PX_ACTIONS;
this.sortColumnTitle = gettext('Sort column "{{ name }}"');
this.resizeHandleMouseDown$ = new EventEmitter();
this.resizeHandleContainerMouseMove$ = new EventEmitter();
this.resizeHandleContainerMouseUp$ = new EventEmitter();
this.filtersHelpPopoverHtml = gettext('Click the column headers to apply filters.');
this.columnsInitialized = false;
this.defaultColumns = [];
this.reloadConfiguration$ = new Subject();
this.actionControlsInput$ = new BehaviorSubject([]);
this.unsubscribe$ = new Subject();
this.SEARCH_DEBOUNCE_TIME = 500;
/**
* Event emitter, taking boolean values used for loading data grid data with debounce.
* Default value is set to false. Set to true if data grid is using infinite scroll and page should be reloaded.
* This is used to avoid having multiple this.loadData() function calls.
*/
this.triggerLoadData = new EventEmitter();
this.isRowExpanded = (_, row) => {
return !!this.expandedRows.get(row);
};
this.triggerLoadData.pipe(debounceTime(1), takeUntil(this.unsubscribe$)).subscribe(reload => {
this.loadData(reload);
});
this.reloadConfiguration$
.pipe(switchMap(() => this.configurationStrategy?.getConfig$() ?? of(null)), tap(config => {
this.setColumns(config);
this.setPageSize(config);
this.triggerLoadData.emit(!!this.infiniteScroll);
}), switchMap(() => this.dataSource.stats$), tap(stats => {
this.createLoadMoreComponent(stats);
this.updateFilteringLabelsParams(stats);
this.updatePaginationLabelParams(stats);
this.updatePaginationWhenNoDevicesLastPage(stats);
}), takeUntil(this.unsubscribe$))
.subscribe();
}
ngOnInit() {
this.isConfigContextKnown = !!this.configurationStrategy?.isContextKnown();
this.searchText$
.pipe(takeUntil(this.unsubscribe$), debounceTime(this.SEARCH_DEBOUNCE_TIME), distinctUntilChanged(), tap(searchText => {
this.searchText = searchText;
this.onBeforeSearch.emit(this.searchText);
this.triggerEvent({
action: PX_ACTIONS.SEARCH,
searchInput: searchText
});
}))
.subscribe(() => {
this.reload();
});
if (this.selectable) {
combineLatest(this.dataSource.data$, this.itemsSelect.asObservable())
.pipe(takeUntil(this.unsubscribe$))
.subscribe(([data]) => {
const currentPageEmpty = data.length === 0;
this.currentPageSelectionState = {
allSelected: currentPageEmpty ? false : data.every(item => this.isItemSelected(item)),
allDeselected: currentPageEmpty ? true : data.every(item => !this.isItemSelected(item))
};
});
}
this.reloadConfiguration$.next();
this.actionControlsService.items$
.pipe(startWith([]), switchMap((hooks) => forkJoin(hooks.map(hook => toObservable(hook?.matchesGrid
? this.safelyInvokeMatcher(hook.matchesGrid, this.route, this.configurationStrategy?.getContext())
: false).pipe(map(matches => ({ hook, matches }))))).pipe(startWith([]))), map((hooks) => hooks.filter(hook => hook.matches).map(hook => hook.hook)), map((hooks) => hooks.reduce((actionControls, currentHook) => {
return [...actionControls, ...castArray(currentHook.actionControls)];
}, [])), combineLatestWith(this.actionControlsInput$), tap(([hookControls, inputControls]) => (this.actionControls = [...inputControls, ...hookControls])), takeUntil(this.unsubscribe$))
.subscribe();
if (this.refresh) {
this.refresh.pipe(takeUntil(this.unsubscribe$)).subscribe(() => {
this.cancel();
this.reload();
});
}
this.processAndPersistConfigChange();
this.updateColumns();
// Resetting the stats size to 0 when managed objects are deleted but sizing not yet updated
// TODO remove after MTM-60226 is resolved
this.emptyStateContext$ = combineLatest([this.dataSource.stats$, this.dataSource.data$]).pipe(map(([stats, data]) => {
if (stats.filteredSize === 1 && data.length === 0) {
return { ...stats, size: 0, filteredSize: 0 };
}
return stats;
}));
}
setExpandableRowVisible(row, success) {
if (success) {
this.expandedRows.get(row).visible$.next(true);
}
else {
this.expandedRows.get(row).visible$.next(false);
this.expandedRows.delete(row);
this.tableRef.renderRows();
}
}
ngOnChanges(event) {
if (((!event._actionControls && !event.searchText) || event._actionControls?.firstChange) &&
this.columnsInitialized) {
const reload = !!event._infiniteScroll?.currentValue && !event._infiniteScroll?.firstChange;
this.triggerLoadData.emit(reload);
}
if (!!event._columns && !event._columns.firstChange) {
this.reloadConfiguration$.next();
}
this.updateColumns();
}
ngAfterViewInit() {
this.updateGridColumnsSize();
this.updateThEls();
this.setupResizeHandle();
}
ngOnDestroy() {
this.unsubscribe$.next();
this.unsubscribe$.complete();
}
expand(row) {
const isSyncExpand = this.expandableRows === 'SYNC';
let visibleSubject;
if (isSyncExpand) {
visibleSubject = new BehaviorSubject(true);
}
else {
visibleSubject = new Subject();
}
this.expandedRows.set(row, { visible$: visibleSubject });
this.tableRef.renderRows();
return visibleSubject;
}
collapse(row) {
this.expandedRows.delete(row);
this.tableRef.renderRows();
}
setColumns(config) {
if (!!this.configurationStrategy && !isEmpty(this._columns)) {
this.columns = this.dataGridService.applyConfigToColumns(config, this._columns);
this.columnsInitialized = true;
}
else {
this.columns = this._columns || [];
this.columnsInitialized = this.columnsInitialized || !!this._columns;
}
this.defaultColumns = this.columns || [];
this.updateColumns();
}
setPageSize(config) {
if (!!config?.pagination) {
this.pagination = {
...this.pagination,
pageSize: config.pagination.pageSize
};
}
const pageSize = get(this.pagination, 'pageSize');
if (this.pagination &&
!this.possiblePageSizes.find(possiblePageSize => possiblePageSize === pageSize)) {
this.pagination = { ...this.pagination, pageSize: this.minPossiblePageSize };
}
}
openCustomColumnModal() {
const modalRef = this.bsModalService.show(ConfigureCustomColumnComponent, {
class: 'modal-sm',
ariaDescribedby: 'modal-body',
ariaLabelledBy: 'modal-title',
ignoreBackdropClick: true,
initialState: {
columns: this.columns
}
});
modalRef.content.onAddCustomColumn
.pipe(tap((customColumnConfig) => {
const firstFixedColumPosition = this.columns.indexOf(this.columns.find(column => column.positionFixed));
this.columns.splice(firstFixedColumPosition > -1 ? firstFixedColumPosition : this.columns.length, 0, new CustomColumn(customColumnConfig));
this.updateColumns();
this.triggerEvent({
action: PX_ACTIONS.ADD_CUSTOM_COLUMN,
column: customColumnConfig.header || customColumnConfig.name
});
}), takeUntil(modalRef.onHidden))
.subscribe(event => this.onAddCustomColumn.emit(event));
}
async removeCustomColumn(poConfirm, column, ddConfigureColumns) {
ddConfigureColumns.autoClose = false;
poConfirm.message = gettext('Do you want to remove this column?');
try {
const remove = await poConfirm.show(this.confirmRemoveColumnButtons);
if (remove) {
this.columns = this.columns.filter(col => col?.name !== column?.name);
this.updateColumns();
this.onRemoveCustomColumn.emit(column);
this.triggerEvent({
action: PX_ACTIONS.REMOVE_CUSTOM_COLUMN,
column: column.header || column.name
});
}
}
catch (e) {
this.alertService.addServerFailure(e);
}
setTimeout(() => (ddConfigureColumns.autoClose = true), 0);
}
async removeFilter(filter) {
const filteringModifier = filter.externalFilterQuery
? { externalFilterQuery: filter.externalFilterQuery }
: { filterPredicate: filter.filterPredicate };
this.onBeforeFilter.emit({
columnName: filter.columnName,
dropdown: undefined,
filteringModifier
});
if ((filter.externalFilterQuery && !this.checkIfAnyValuesExist(filter.externalFilterQuery)) ||
filter.filterPredicate) {
this.updateFiltering([filter.columnName], {
type: FilteringActionType.ResetFilter
});
this.onFilter.emit({ columnName: filter.columnName });
}
else {
this.updateFiltering([filter.columnName], {
type: FilteringActionType.ApplyFilter,
payload: { filteringModifier }
});
this.onFilter.emit({
columnName: filter.columnName,
dropdown: undefined,
filteringModifier
});
}
this.triggerEvent({
action: PX_ACTIONS.REMOVE_FILTER,
column: filter.columnName,
filteringModifier
});
}
trackByName(index, item) {
return item.name;
}
resolveCellValue(row, path) {
return flow([
x => this.dataSource.resolveValue(x, path),
this.dataSource.resolveFunction,
this.dataSource.normalizeNil
])(row);
}
changeSortOrder(columnName) {
const column = this.columns.find(({ name }) => name === columnName);
if (column) {
const { sortOrder } = column;
if (!sortOrder) {
this.updateSorting([columnName], SortingOrder.ASC);
}
else if (sortOrder === SortingOrder.ASC) {
this.updateSorting([columnName], SortingOrder.DESC);
}
else {
this.updateSorting([columnName], '');
}
}
}
updateSorting(columnNames, sortOrder) {
this.triggerEvent({
action: PX_ACTIONS.CHANGE_SORTING_ORDER,
columns: columnNames,
sortOrder: sortOrder === '' ? 'none' : sortOrder
});
this.columns = this.columns.map((column) => {
if (columnNames.includes(column.name)) {
return { ...column, sortOrder };
}
return column;
});
this.emitConfigChange('sort');
this.reload();
}
applyFilter(columnName, dropdown, filteringModifier) {
this.triggerEvent({
action: PX_ACTIONS.APPLY_FILTER,
column: columnName,
filteringModifier
});
this.onBeforeFilter.emit({ columnName, dropdown, filteringModifier });
this.updateFiltering([columnName], {
type: FilteringActionType.ApplyFilter,
payload: { filteringModifier }
});
dropdown.hide();
this.onFilter.emit({ columnName, dropdown, filteringModifier });
}
resetFilter(columnName, dropdown) {
this.triggerEvent({ action: PX_ACTIONS.RESET_FILTER, column: columnName });
this.updateFiltering([columnName], { type: FilteringActionType.ResetFilter });
dropdown.hide();
this.onFilter.emit({ columnName, dropdown });
}
clearFilters(reload = true) {
this.updateFiltering(this.columns.map(({ name }) => name), {
type: FilteringActionType.ResetFilter
}, reload);
this.onFilter.emit({});
this.triggerEvent({ action: PX_ACTIONS.CLEAR_FILTER });
}
updateFiltering(columnNames, action, reload = true) {
this.columns = this.columns.map(column => {
if (columnNames.includes(column.name)) {
return {
...column,
...(action.type === FilteringActionType.ApplyFilter
? action.payload.filteringModifier
: this.onResetFilterAction(column))
};
}
return column;
});
this.updateFilteringApplied();
if (reload) {
this.reload();
}
}
updateFilteringApplied() {
this.columnsWithFiltersApplied = this.columns.filter(this.isColumnFilteringApplied);
this.filteringApplied = this.columnsWithFiltersApplied.length > 0;
this.filtersHelpPopoverHtml = this.filteringApplied
? gettext('Click the column headers to apply filters. Click <b>Active filters</b> button to manage applied filters.')
: gettext('Click the column headers to apply filters.');
}
isColumnFilteringApplied(column) {
const { filterable, filterPredicate, externalFilterQuery } = column;
return !!(filterable && (filterPredicate || externalFilterQuery));
}
updatePagination({ itemsPerPage, page }) {
const configChanged = this.pagination?.pageSize !== itemsPerPage;
this.pagination = { ...this.pagination, pageSize: itemsPerPage, currentPage: page };
this.loadData();
if (configChanged) {
this.emitConfigChange('pagination');
}
this.triggerEvent({ action: PX_ACTIONS.CHANGE_PAGINATION, itemsPerPage, page });
}
clickReload() {
this.searchText = '';
this.reload();
this.onReload.next();
this.triggerEvent({ action: PX_ACTIONS.RELOAD });
}
reload(redirect = true) {
this.pagination = {
...this.pagination,
currentPage: redirect ? 1 : this.pagination.currentPage
};
this.recreateLoadMoreComponent = true;
this.loadData(true);
this.scrollToTop();
}
loadNextPage() {
this.pagination = { ...this.pagination, currentPage: this.pagination.nextPage };
this.loadData();
return this.dataSource.resultList$
.pipe(take(1)) // in order for `toPromise` to work, the observable needs to complete
.toPromise()
.then(result => {
return {
...result,
paging: {
...result.paging,
next: this.loadNextPage.bind(this)
}
};
});
}
getCellRendererSpec({ value, row, columnName }) {
return this._getCellRendererSpec({ type: 'CELL', value, row, columnName });
}
getHeaderCellRendererSpec({ value, columnName }) {
return this._getCellRendererSpec({ type: 'HEADER', value, row: undefined, columnName });
}
getFilteringFormRendererSpec({ column, dropdown }) {
return {
renderer: get(this.getColumnRenderer(column), 'filteringFormRendererDef.template') ||
column.filteringFormRendererComponent,
context: {
property: column,
applyFilter: this.applyFilter.bind(this, column.name, dropdown),
resetFilter: this.resetFilter.bind(this, column.name, dropdown)
}
};
}
setAllItemsSelected(selected) {
this.dataSource.selection$
.pipe(first())
.subscribe(({ filteredDataIds }) => this.setItemsSelected(filteredDataIds, selected));
}
setAllItemsInCurrentPageSelected(selected) {
this.dataSource.data$.pipe(first()).subscribe(data => this.setItemsSelected(data, selected));
}
setItemsSelected(items, selected) {
const itemIds = items.map((item) => typeof item === 'object' ? item[this.selectionPrimaryKey] : item);
this.selectedItemIds = selected
? union(this.selectedItemIds, itemIds)
: without(this.selectedItemIds, ...itemIds);
this.itemsSelect.emit(this.selectedItemIds);
}
changeSelectedItem(item) {
this.selectedItemIds = [item[this.selectionPrimaryKey]];
this.itemsSelect.emit(this.selectedItemIds);
}
cancel() {
this.selectedItemIds = [];
this.itemsSelect.emit(this.selectedItemIds);
}
isItemSelected(item) {
return this.selectedItemIds.includes(item[this.selectionPrimaryKey]);
}
onColumnDrop({ previousIndex, currentIndex }) {
const differentIndex = previousIndex !== currentIndex;
if (differentIndex) {
this.triggerEvent({
action: PX_ACTIONS.REORDER_COLUMNS,
column: this.columnNames[previousIndex]
});
const column = this.columns.splice(previousIndex, 1);
this.columns.splice(currentIndex, 0, column[0]);
this.emitConfigChange('reorderColumn');
}
this.updateColumnNames();
this.updateGridColumnsSize();
}
updateGridColumnsSize() {
this.styles = {
...this.styles,
gridTemplateColumns: this.sanitizer.bypassSecurityTrustStyle(this.columns
.filter(column => column.visible)
.map(({ gridTrackSize }) => gridTrackSize)
.join(' ')),
gridInfiniteScrollColumn: this.sanitizer.bypassSecurityTrustStyle(`1 / -1`)
};
}
updateThEls() {
setTimeout(() => {
this.thEls = this.thRefs
? this.thRefs.toArray().map(({ nativeElement }) => nativeElement)
: [];
}, 0);
}
// To be removed when columns are transformed to observables.
isDropDownPlacedRight(column) {
return (indexOf(this.columns.filter(c => c.visible), column) >
this.columns.filter(c => c.visible).length / 2);
}
emitConfigChange(eventType) {
if (this.columnsInitialized) {
const columns = this.columns.map(this.mapColumnToConfig.bind(this));
const config = { columns, pagination: this.pagination };
this.onConfigChange.emit(config);
switch (eventType) {
case 'sort':
this.onSort.emit(config);
break;
case 'pagination':
this.onPageSizeChange.emit(config);
break;
case 'reorderColumn':
this.onColumnReordered.emit(config);
break;
case 'changeColumnVisibility':
this.onColumnVisibilityChange.emit(config);
}
}
}
triggerEvent(eventData) {
this.gainsightService.triggerEvent(this.productExperienceEvent?.eventName || PX_EVENT_NAME, {
...this.productExperienceEvent?.data,
...eventData
});
}
handleClick(row) {
this.lastClickedRow = row;
this.rowClick.emit(row);
}
onResetFilterAction(column) {
this.onColumnFilterReset.emit(column);
return {
filterPredicate: undefined,
externalFilterQuery: undefined
};
}
mapColumnToConfig(column) {
let config;
if (column.custom) {
const { visible, sortOrder, name, externalFilterQuery, header, path } = column;
config = {
visible,
sortOrder,
name,
filter: { externalFilterQuery },
header,
path,
custom: true
};
}
else {
const { visible, sortOrder, name, externalFilterQuery } = column;
config = { visible, sortOrder, name, filter: { externalFilterQuery } };
}
if (isEmpty(config?.filter?.externalFilterQuery)) {
delete config.filter;
}
return config;
}
loadData(reload = false) {
const { rows, columns, pagination, searchText, serverSideDataCallback, selectable, selectionPrimaryKey, infiniteScroll } = this;
this.dataSource.loadData({
rows,
columns,
pagination,
searchText,
serverSideDataCallback,
selectable,
selectionPrimaryKey,
infiniteScroll,
reload
});
}
updateColumns() {
const specialColumn = {
sortable: false,
positionFixed: true
};
const selectionColumn = this.selectable
? {
...specialColumn,
name: this.singleSelection ? "radio-button" /* SpecialColumnName.RadioButton */ : "checkbox" /* SpecialColumnName.Checkbox */,
gridTrackSize: '32px'
}
: undefined;
const actionsColumn = this.actionControls?.length > 0
? {
...specialColumn,
name: "actions" /* SpecialColumnName.Actions */,
gridTrackSize: 'minmax(40px, auto)'
}
: undefined;
const expandableRowsColumn = this.expandableRows !== 'NONE' ? new ExpandableRowColumn() : null;
const columns = [expandableRowsColumn, selectionColumn, ...this.columns, actionsColumn]
.filter(Boolean)
.map(this.withColumnDefaults);
this.columns = uniqBy(columns, 'name');
this.updateColumnNames();
this.updateGridColumnsSize();
this.updateThEls();
this.updateFilteringApplied();
}
checkIfAnyValuesExist(obj, results = []) {
if (obj && Object.entries(obj)) {
Object.entries(obj).forEach(([key, value]) => {
if (typeof obj[key] === 'object') {
this.checkIfAnyValuesExist(obj[key], results);
}
else {
results.push(value);
}
});
}
return results.some(val => !!val);
}
withColumnDefaults(column) {
const dataType = column.dataType || "text-short" /* ColumnDataType.TextShort */;
const { headerCSSClassName, cellCSSClassName } = column;
return {
visible: true,
positionFixed: false,
resizable: true,
sortable: true,
sortOrder: '',
filterable: false,
...column,
dataType,
gridTrackSize: column.gridTrackSize ||
`minmax(${minColumnGridTrackSize}px, ${ratiosByColumnTypes[dataType]}fr)`,
headerCSSClassName: (typeof headerCSSClassName === 'string'
? headerCSSClassName.split(' ')
: headerCSSClassName) || [],
cellCSSClassName: (typeof cellCSSClassName === 'string' ? cellCSSClassName.split(' ') : cellCSSClassName) ||
[]
};
}
updateColumnNames() {
this.columnNames = this.columns.map(({ name }) => name);
}
setupResizeHandle() {
const resizeHandleDrag$ = this.resizeHandleMouseDown$.pipe(takeUntil(this.unsubscribe$), tap(() => this.clearMouseHighlights()), mergeMap(({ event, targetColumnName }) => {
this.columns = this.columns.map(column => {
if (column.name === targetColumnName) {
return {
...column,
headerCSSClassName: union(column.headerCSSClassName, ['header--being-resized'])
};
}
return column;
});
this.headerBeingResized = {
columnName: targetColumnName,
el: event.target?.parentNode
};
this.styles = {
...this.styles,
tableCursor: 'col-resize'
};
return this.resizeHandleContainerMouseMove$.pipe(tap(() => this.clearMouseHighlights()), takeUntil(this.resizeHandleContainerMouseUp$));
}));
resizeHandleDrag$.subscribe((event) => {
requestAnimationFrame(() => {
this.columns = this.columns.map((column, i) => {
if (this.headerBeingResized && column.name === this.headerBeingResized.columnName) {
const scrollContainerDiv = this.scrollContainer.nativeElement;
// Read scrollContainerEl's offset left relative to the document.
const horizontalOffset = scrollContainerDiv.getBoundingClientRect().left;
// Adjust with the scrollContainerEl horizontal scroll position.
const horizontalScrollOffset = scrollContainerDiv.scrollLeft - horizontalOffset;
// Read left offset of the resized header.
const headerOffsetLeft = this.headerBeingResized.el.offsetLeft || 0;
// Calculate the desired width.
const width = horizontalScrollOffset + event.clientX - headerOffsetLeft;
return {
...column,
// Update the column object with the new size value, enforce our minimum size.
gridTrackSize: `${Math.max(minColumnGridTrackSize, width)}px`
};
}
// For the other headers which don't have a set width, fix it to their computed width.
if (column.gridTrackSize.startsWith('minmax')) {
return {
...column,
// isn't fixed yet (it would be a px value)
gridTrackSize: `${_parseInt(this.thEls[i].clientWidth)}px`
};
}
return column;
});
/*
* Update the column sizes.
* Note: grid-template-columns sets the width for all columns in one value.
*/
this.updateGridColumnsSize();
});
});
this.resizeHandleContainerMouseUp$.pipe(takeUntil(this.unsubscribe$)).subscribe(() => {
if (this.headerBeingResized) {
this.columns = this.columns.map(column => {
if (column.name === this.headerBeingResized.columnName) {
return {
...column,
headerCSSClassName: without(column.headerCSSClassName, 'header--being-resized')
};
}
return column;
});
this.headerBeingResized = undefined;
this.styles = {
...this.styles,
tableCursor: 'auto'
};
}
});
}
clearMouseHighlights() {
if (window.getSelection) {
window.getSelection().removeAllRanges();
}
}
_getCellRendererSpec({ type, value, row, columnName }) {
const column = this.columns.find(({ name }) => name === columnName);
const columnRenderer = this.getColumnRenderer(column);
const rendererTemplate = get(columnRenderer, `${type === 'HEADER' ? 'headerCellRendererDef' : 'cellRendererDef'}.template`);
const { cellRendererComponent, headerCellRendererComponent } = column;
const rendererComponent = type === 'HEADER' ? headerCellRendererComponent : cellRendererComponent;
return {
renderer: rendererTemplate || rendererComponent,
context: {
value,
item: row,
property: column
}
};
}
getColumnRenderer(column) {
return this.columnRenderers.toArray().find(({ name }) => name === column.name);
}
updateFilteringLabelsParams(stats) {
this.filteringLabelsParams = {
filteredItemsCount: stats.filteredSize,
allItemsCount: stats.size
};
}
updatePaginationLabelParams(stats) {
if (stats.nextPage) {
this.pagination = { ...this.pagination, nextPage: stats.nextPage };
}
const pageFirstItemIdx = (stats.currentPage - 1) * stats.firstPageSize + 1;
this.paginationLabelParams = {
pageFirstItemIdx,
pageLastItemIdx: pageFirstItemIdx + (stats.currentPageSize - 1),
itemsTotal: stats.filteredSize
};
}
updatePaginationWhenNoDevicesLastPage(stats) {
if (!stats.nextPage && stats.currentPageSize === 0 && stats.size > 0) {
this.pagination = {
...this.pagination,
currentPage: this.pagination.currentPage - 1,
nextPage: null
};
}
}
createLoadMoreComponent(stats) {
if (this.infiniteScroll &&
stats &&
stats.nextPage &&
(!this.loadMoreComponent || this.recreateLoadMoreComponent)) {
this.recreateLoadMoreComponent = false;
this.infiniteScrollContainer.clear();
const componentRef = this.infiniteScrollContainer.createComponent(LoadMoreComponent);
const instance = componentRef.instance;
instance.useIntersection = this.infiniteScroll === 'auto' || this.infiniteScroll === 'hidden';
instance.hidden = this.infiniteScroll === 'hidden';
instance.paging = {
nextPage: stats.nextPage,
next: this.loadNextPage.bind(this)
};
instance.loadNextLabel = this.loadMoreItemsLabel;
instance.loadingLabel = this.loadingItemsLabel;
this.loadMoreComponent = instance;
}
else if (this.loadMoreComponent && !stats.nextPage) {
this.loadMoreComponent.paging = {
nextPage: null
};
}
}
scrollToTop() {
if (this.infiniteScroll) {
this.scrollContainer.nativeElement.scrollTop = 0;
}
}
processAndPersistConfigChange() {
merge(merge(this.onSort, this.onPageSizeChange, this.onColumnReordered, this.onColumnVisibilityChange).pipe(map(config => config.columns)), merge(this.onAddCustomColumn, this.onRemoveCustomColumn).pipe(map(() => (this.columns || []).map(this.mapColumnToConfig.bind(this)))), this.onFilter.pipe(map(({ columnName, filteringModifier }) => this.columns.map(this.mapColumnToConfig.bind(this)).map((column) => {
if (isNil(columnName)) {
delete column.filter;
}
else if (column.name === columnName) {
if (isEmpty(filteringModifier)) {
delete column.filter;
}
else {
column.filter = filteringModifier;
}
}
return column;
}))))
.pipe(map((columns) => ({
columns,
pagination: { pageSize: this.pagination.pageSize }
})), filter(() => !!this.configurationStrategy), this.trimFilterConfigPipe(), this.trimSortConfigPipe(), this.trimCustomColumnConfigPipe(), this.ignoreColumnOrderPipe(), this.ignoreColumnVisibilityPipe(), concatMap((config) => this.configurationStrategy.saveConfig$(config)), takeUntil(this.unsubscribe$))
.subscribe();
}
trimFilterConfigPipe() {
return pipe(this.checkEventPipe('filter', config => {
config.columns = (config.columns || []).map(col => {
delete col.filter;
return col;
});
return config;
}));
}
trimSortConfigPipe() {
return pipe(this.checkEventPipe('sort', config => {
config.columns = (config.columns || []).map(col => {
col.sortOrder = '';
return col;
});
return config;
}));
}
trimCustomColumnConfigPipe() {
return pipe(this.checkEventPipe('customColumns', config => {
config.columns = (config.columns || []).filter((col) => !col.custom);
return config;
}));
}
ignoreColumnOrderPipe() {
return pipe(this.checkEventPipe('order', config => {
return this.configurationStrategy.getConfig$().pipe(map(oldConfig => {
const oldColumns = oldConfig?.columns || this.defaultColumns;
// check if custom columns have been added
const columnsAdded = (config.columns || []).filter(col => !oldColumns.find(old => old.name === col.name));
config.columns = [
...oldColumns.map(oldCol => (config.columns || []).find(newCol => newCol.name === oldCol.name)),
...columnsAdded
];
return config;
}));
}));
}
ignoreColumnVisibilityPipe() {
return pipe(this.checkEventPipe('visibility', config => {
return this.configurationStrategy.getConfig$().pipe(map(oldConfig => {
config.columns = (config.columns || []).map(newCol => {
const columns = oldConfig?.columns || this.defaultColumns;
const oldCol = columns.find((col) => newCol.name === col.name);
newCol.visible = oldCol?.visible ?? true;
return newCol;
});
return config;
}));
}));
}
checkEventPipe(configPart, trimEventDataFn) {
return pipe(concatMap((config) => {
return this.resolveConfigFilter
.call(this, configPart)
.pipe(map(keepEventData => ({ config, keepEventData })));
}), map(({ config, keepEventData }) => keepEventData ? config : trimEventDataFn.call(this, config)), concatMap(config => (isObservable(config) ? config : of(config))));
}
resolveConfigFilter(configPart) {
let result;
const valueOrFn = this.configurationStrategy.getContext()?.configFilter?.[configPart];
if (typeof valueOrFn === 'function') {
result = valueOrFn();
}
else {
result = valueOrFn;
}
return toObservable(result ?? true);
}
safelyInvokeMatcher(matchesFn, route, context) {
if (matchesFn) {
try {
return matchesFn(route, context);
}
catch (e) {
return false;
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: DataGridComponent, deps: [{ token: DATA_GRID_CONFIGURATION_STRATEGY, optional: true }, { token: i1.DataGridService }, { token: i2.DomSanitizer }, { token: i3.GainsightService }, { token: i4.BsModalService }, { token: i5.AlertService }, { token: i6.ActionControlsExtensionService }, { token: i7.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: DataGridComponent, selector: "c8y-data-grid", inputs: { title: "title", loadMoreItemsLabel: "loadMoreItemsLabel", loadingItemsLabel: "loadingItemsLabel", showSearch: "showSearch", refresh: "refresh", _columns: ["columns", "_columns"], _rows: ["rows", "_rows"], _pagination: ["pagination", "_pagination"], _infiniteScroll: ["infiniteScroll", "_infiniteScroll"], _serverSideDataCallback: ["serverSideDataCallback", "_serverSideDataCallback"], _selectable: ["selectable", "_selectable"], _singleSelection: ["singleSelection", "_singleSelection"], _selectionPrimaryKey: ["selectionPrimaryKey", "_selectionPrimaryKey"], _displayOptions: ["displayOptions", "_displayOptions"], _actionControls: ["actionControls", "_actionControls"], _bulkActionControls: ["bulkActionControls", "_bulkActionControls"], _headerActionControls: ["headerActionControls", "_headerActionControls"], searchText: "searchText", configureColumnsEnabled: "configureColumnsEnabled", showCounterWarning: "showCounterWarning", activeClassName: "activeClassName", expandableRows: "expandableRows" }, outputs: { rowMouseOver: "rowMouseOver", rowMouseLeave: "rowMouseLeave", rowClick: "rowClick", onConfigChange: "onConfigChange", onBeforeFilter: "onBeforeFilter", onBeforeSearch: "onBeforeSearch", onFilter: "onFilter", itemsSelect: "itemsSelect", onReload: "onReload", onAddCustomColumn: "onAddCustomColumn", onRemoveCustomColumn: "onRemoveCustomColumn", onColumnFilterReset: "onColumnFilterReset", onSort: "onSort", onPageSizeChange: "onPageSizeChange", onColumnReordered: "onColumnReordered", onColumnVisibilityChange: "onColumnVisibilityChange" }, host: { classAttribute: "d-contents" }, providers: [
{
provide: PRODUCT_EXPERIENCE_EVENT_SOURCE,
useExisting: forwardRef(() => DataGridComponent)
}
], queries: [{ propertyName: "expandableRow", first: true, predicate: ExpandableRowDirective, descendants: true }, { propertyName: "emptyState", first: true, predicate: EmptyStateContextDirective, descendants: true }, { prop