core/data-grid/data-grid.component.ts
DataGrid
OnChanges
AfterViewInit
ProductExperienceEventSource
host | { |
providers |
{
provide: PRODUCT_EXPERIENCE_EVENT_SOURCE, useExisting: forwardRef(() => DataGridComponent)
}
|
selector | c8y-data-grid |
templateUrl | ./data-grid.component.html |
constructor(configurationStrategy: DataGridConfigurationStrategy, dataGridService: DataGridService, sanitizer: DomSanitizer, gainsightService: GainsightService, bsModalService: BsModalService, alertService: AlertService, actionControlsService: ActionControlsExtensionService, route: ActivatedRoute)
|
|||||||||||||||||||||||||||
Parameters :
|
actionControls |
Type : ActionControl[]
|
Sets action controls (actions available for individual items). |
activeClassName |
Type : string
|
Default value : 'active'
|
Sets the class name used for active rows (last clicked). Set empty string to disable appending active class to grid rows. |
bulkActionControls |
Type : BulkActionControl[]
|
Sets bulk action controls (actions available for items selected by user). |
columns |
Type : Column[]
|
The list of columns to be displayed in the grid. |
configureColumnsEnabled |
Type : boolean
|
Default value : true
|
Determines if custom columns button will be enabled. |
displayOptions |
Type : DisplayOptions
|
Sets display options. |
headerActionControls |
Type : HeaderActionControl[]
|
Sets header action controls (actions available from data grid header). |
infiniteScroll |
Type : LoadMoreMode
|
Sets load more mode. |
loadingItemsLabel |
Type : string
|
Default value : gettext('Loading items…')
|
The label for loading indicator. |
loadMoreItemsLabel |
Type : string
|
Default value : gettext('Load more items')
|
The label for load more button. |
pagination |
Type : Pagination
|
Pagination settings, e.g. allows for setting current page or page size. |
refresh |
Type : EventEmitter<void>
|
Takes an event emitter. When an event is emitted, the grid will be reloaded. |
rows |
Type : Row[]
|
The list of rows to be displayed in the grid (used for client side data). |
searchText |
Type : string
|
Default value : ''
|
Sets initial search text. |
selectable |
Type : boolean
|
Determines whether items can be selected by clicking a checkbox in the first column. |
selectionPrimaryKey |
Type : string
|
Determines which item's property will be used to distinguish selection status. |
serverSideDataCallback |
Type : ServerSideDataCallback
|
Sets a callback function which will be invoked whenever data needs to be loaded from server. The function should take [[DataSourceModifier]] and return [[ServerSideDataResult]]. |
showCounterWarning |
Type : boolean
|
Default value : false
|
Shows the warning for the sub-assets counter |
showSearch |
Type : boolean
|
Default value : false
|
Determines whether text search input is shown in the grid's header. |
singleSelection |
Type : boolean
|
Restricts selection to a single row only. Selection column displays radio button instead of checkboxes |
title |
Type : string
|
Default value : gettext('Items')
|
The title for the data grid, it's displayed in the grid's header. |
itemsSelect |
Type : EventEmitter
|
Emits an event when items selection changes. The array contains keys of selected items (key property is defined by |
onAddCustomColumn |
Type : EventEmitter
|
Emits an event when a custom column is added |
onBeforeFilter |
Type : EventEmitter
|
Emits an event before the filter is applied. |
onBeforeSearch |
Type : EventEmitter
|
Emits an event before the search is performed. |
onColumnFilterReset |
Type : EventEmitter
|
Emits an event after the column filter has been reset |
onColumnReordered |
Type : EventEmitter
|
Emits an event when column order has been changed |
onColumnVisibilityChange |
Type : EventEmitter
|
Emits an event when column order has been changed |
onConfigChange |
Type : EventEmitter
|
Emits an event when grid's configuration is changed. |
onFilter |
Type : EventEmitter
|
Emits an event when a filter is applied in a column. |
onPageSizeChange |
Type : EventEmitter
|
Emits an event when page size has been changed |
onReload |
Type : EventEmitter
|
Emits an event when reload button is clicked. |
onRemoveCustomColumn |
Type : EventEmitter
|
Emits an event when a custom column is removed |
onSort |
Type : EventEmitter
|
Emits an event when column sorting has been changed |
rowClick |
Type : EventEmitter
|
Emits an event when a row is clicked. |
rowMouseLeave |
Type : EventEmitter
|
Emits an event when mouse leaves a row. |
rowMouseOver |
Type : EventEmitter
|
Emits an event when mouse is over a row. |
applyFilter | ||||||||
applyFilter(columnName, dropdown, filteringModifier)
|
||||||||
Parameters :
Returns :
void
|
cancel |
cancel()
|
Returns :
void
|
changeSelectedItem | ||||||
changeSelectedItem(item: any)
|
||||||
Parameters :
Returns :
void
|
changeSortOrder | ||||
changeSortOrder(columnName)
|
||||
Parameters :
Returns :
void
|
clearFilters | ||||||
clearFilters(reload)
|
||||||
Parameters :
Returns :
void
|
clickReload |
clickReload()
|
Returns :
void
|
collapse | ||||||
collapse(row: Row)
|
||||||
Parameters :
Returns :
void
|
emitConfigChange | ||||||
emitConfigChange(eventType?: GridEventType)
|
||||||
Parameters :
Returns :
void
|
expand | ||||||
expand(row: Row)
|
||||||
Parameters :
Returns :
any
|
getCellRendererSpec | |||
getCellRendererSpec(undefined)
|
|||
Parameters :
Returns :
CellRendererSpec
|
getFilteringFormRendererSpec | |||
getFilteringFormRendererSpec(undefined)
|
|||
Parameters :
Returns :
FilteringFormRendererSpec
|
getHeaderCellRendererSpec | |||
getHeaderCellRendererSpec(undefined)
|
|||
Parameters :
Returns :
CellRendererSpec
|
handleClick | ||||||
handleClick(row: Row)
|
||||||
Parameters :
Returns :
void
|
isColumnFilteringApplied | ||||||
isColumnFilteringApplied(column: Column)
|
||||||
Parameters :
Returns :
boolean
|
isDropDownPlacedRight | ||||||
isDropDownPlacedRight(column: Column)
|
||||||
Parameters :
Returns :
boolean
|
isItemSelected | ||||
isItemSelected(item)
|
||||
Parameters :
Returns :
any
|
loadNextPage |
loadNextPage()
|
Returns :
Promise<IResultList<object>>
|
ngAfterViewInit |
ngAfterViewInit()
|
Returns :
void
|
ngOnChanges | ||||
ngOnChanges(event)
|
||||
Parameters :
Returns :
void
|
ngOnDestroy |
ngOnDestroy()
|
Returns :
void
|
ngOnInit |
ngOnInit()
|
Returns :
void
|
onColumnDrop | |||
onColumnDrop(undefined)
|
|||
Parameters :
Returns :
void
|
openCustomColumnModal |
openCustomColumnModal()
|
Returns :
void
|
reload | ||||||
reload(redirect)
|
||||||
Parameters :
Returns :
void
|
Async removeCustomColumn | ||||||||||||
removeCustomColumn(poConfirm: PopoverConfirmComponent, column: Column, ddConfigureColumns: BsDropdownDirective)
|
||||||||||||
Parameters :
Returns :
any
|
Async removeFilter | ||||||
removeFilter(filter: Partial
|
||||||
Parameters :
Returns :
any
|
resetFilter | ||||||
resetFilter(columnName, dropdown)
|
||||||
Parameters :
Returns :
void
|
resolveCellValue | ||||||
resolveCellValue(row, path)
|
||||||
Parameters :
Returns :
any
|
setAllItemsInCurrentPageSelected | ||||
setAllItemsInCurrentPageSelected(selected)
|
||||
Parameters :
Returns :
void
|
setAllItemsSelected | ||||
setAllItemsSelected(selected)
|
||||
Parameters :
Returns :
void
|
setColumns | ||||||
setColumns(config: GridConfig)
|
||||||
Parameters :
Returns :
void
|
setExpandableRowVisible |
setExpandableRowVisible(row: Row, success: boolean)
|
Returns :
void
|
setItemsSelected | |||||||||
setItemsSelected(items: any, selected)
|
|||||||||
Parameters :
Returns :
void
|
setPageSize | ||||||
setPageSize(config: GridConfig)
|
||||||
Parameters :
Returns :
void
|
trackByName | ||||||
trackByName(index, item)
|
||||||
Parameters :
Returns :
any
|
triggerEvent | ||||
triggerEvent(eventData)
|
||||
Parameters :
Returns :
void
|
updateFiltering | ||||||||||||||||
updateFiltering(columnNames: string[], action: literal type, reload)
|
||||||||||||||||
Parameters :
Returns :
void
|
updateFilteringApplied |
updateFilteringApplied()
|
Returns :
void
|
updateGridColumnsSize |
updateGridColumnsSize()
|
Returns :
void
|
updatePagination | |||
updatePagination(undefined)
|
|||
Parameters :
Returns :
void
|
updateSorting | |||||||||
updateSorting(columnNames: string[], sortOrder: SortOrder)
|
|||||||||
Parameters :
Returns :
void
|
updateThEls |
updateThEls()
|
Returns :
void
|
actionControls |
Type : ActionControl[]
|
Default value : []
|
builtInActionType |
Type : object
|
Default value : {
Edit: BuiltInActionType.Edit,
Delete: BuiltInActionType.Delete,
Export: BuiltInActionType.Export
}
|
bulkActionControls |
Type : BulkActionControl[]
|
columnNames |
Type : []
|
Default value : []
|
columnRenderers |
Type : QueryList<ColumnDirective>
|
Decorators :
@ContentChildren(ColumnDirective)
|
columns |
Type : Column[]
|
Default value : []
|
columnsWithFiltersApplied |
Type : Column[]
|
Default value : []
|
Public configurationStrategy |
Type : DataGridConfigurationStrategy
|
Decorators :
@Optional()
|
confirmRemoveColumnButtons |
Type : PopoverConfirmButtons[]
|
Default value : [
{
label: gettext('Cancel'),
action: () => Promise.resolve(false)
},
{
label: gettext('Remove`column,verb`'),
status: 'danger',
action: () => Promise.resolve(true)
}
]
|
currentPageSelectionState |
Type : object
|
Default value : {
allSelected: false,
allDeselected: true
}
|
dataSource |
Default value : new GridDataSource()
|
displayOptions |
Type : DisplayOptions
|
Default value : {
striped: true,
bordered: false,
gridHeader: true,
filter: true,
hover: true
}
|
emptyState |
Type : EmptyStateContextDirective
|
Decorators :
@ContentChild(EmptyStateContextDirective)
|
emptyStateContext$ |
Type : Observable<DataSourceStats>
|
expandableRow |
Type : ExpandableRowDirective
|
Decorators :
@ContentChild(ExpandableRowDirective)
|
expandedRows |
Type : Map<Row | literal type>
|
Default value : new Map()
|
A map of rows which have been expanded. |
filteringApplied |
Default value : false
|
filteringLabelsParams |
Type : object
|
Default value : {
filteredItemsCount: 0,
allItemsCount: 0
}
|
filtersHelpPopoverHtml |
Type : string
|
Default value : gettext('Click the column headers to apply filters.')
|
headerActionControls |
Type : HeaderActionControl[]
|
hidePagination$ |
Default value : this.totalPagesCount$.pipe(
map(totalPagesCount => totalPagesCount <= 1),
delay(0) // prevents ExpressionChangedAfterItHasBeenCheckedError
)
|
infiniteScroll |
Type : LoadMoreMode
|
infiniteScrollContainer |
Type : ViewContainerRef
|
Decorators :
@ViewChild('infiniteScrollContainer', {static: false, read: ViewContainerRef})
|
isConfigContextKnown |
Default value : false
|
isRowExpanded |
Default value : () => {...}
|
lastClickedRow |
Type : Row
|
loadMoreComponent |
Type : LoadMoreComponent
|
Readonly minPossiblePageSize |
Default value : Math.min(...this.possiblePageSizes)
|
pagination |
Type : Pagination
|
paginationLabelParams |
Type : object
|
Default value : {
pageFirstItemIdx: 0,
pageLastItemIdx: 0,
itemsTotal: 0
}
|
Readonly possiblePageSizes |
Type : number[]
|
Default value : [25, 50, 100]
|
productExperienceEvent |
Type : ProductExperienceEvent
|
Default value : { eventName: PX_EVENT_NAME }
|
Product experience constants declarations |
PX_ACTIONS |
Default value : PX_ACTIONS
|
resizeHandleContainerMouseMove$ |
Default value : new EventEmitter<MouseEvent>()
|
resizeHandleContainerMouseUp$ |
Default value : new EventEmitter<MouseEvent>()
|
resizeHandleMouseDown$ |
Default value : new EventEmitter<{ event: MouseEvent; targetColumnName: string }>()
|
rows |
Type : Row[]
|
scrollContainer |
Type : ElementRef
|
Decorators :
@ViewChild('scroll', {static: true})
|
searchText$ |
Default value : new EventEmitter<string>()
|
selectable |
Default value : false
|
selectedItemIds |
Type : string[]
|
Default value : []
|
selectionPrimaryKey |
Type : string
|
Default value : 'id'
|
serverSideDataCallback |
Type : ServerSideDataCallback
|
singleSelection |
Default value : false
|
Readonly sortColumnTitle |
Default value : gettext('Sort column "{{ name }}"')
|
styles |
Type : object
|
Default value : {
tableCursor: 'auto',
gridTemplateColumns: undefined,
gridInfiniteScrollColumn: undefined
}
|
tableRef |
Type : CdkTable<any>
|
Decorators :
@ViewChild(CdkTable, {static: false})
|
totalPagesCount$ |
Default value : new BehaviorSubject<number>(Infinity)
|
_rows | ||||||
set_rows(rows: Row[])
|
||||||
The list of rows to be displayed in the grid (used for client side data).
Parameters :
Returns :
void
|
_pagination | ||||||
set_pagination(pagination: Pagination)
|
||||||
Pagination settings, e.g. allows for setting current page or page size.
Parameters :
Returns :
void
|
_infiniteScroll | ||||||
set_infiniteScroll(infiniteScroll: LoadMoreMode)
|
||||||
Sets load more mode.
Parameters :
Returns :
void
|
_serverSideDataCallback | ||||||
set_serverSideDataCallback(serverSideDataCallback: ServerSideDataCallback)
|
||||||
Sets a callback function which will be invoked whenever data needs to be loaded from server. The function should take [[DataSourceModifier]] and return [[ServerSideDataResult]].
Parameters :
Returns :
void
|
_selectable | ||||||
set_selectable(selectable: boolean)
|
||||||
Determines whether items can be selected by clicking a checkbox in the first column.
Parameters :
Returns :
void
|
_singleSelection | ||||||
set_singleSelection(singleSelection: boolean)
|
||||||
Restricts selection to a single row only. Selection column displays radio button instead of checkboxes
Parameters :
Returns :
void
|
_selectionPrimaryKey | ||||||
set_selectionPrimaryKey(selectionPrimaryKey: string)
|
||||||
Determines which item's property will be used to distinguish selection status.
Parameters :
Returns :
void
|
_displayOptions | ||||||
set_displayOptions(displayOptions: DisplayOptions)
|
||||||
Sets display options.
Parameters :
Returns :
void
|
_actionControls | ||||||
set_actionControls(actionControls: ActionControl[])
|
||||||
Sets action controls (actions available for individual items).
Parameters :
Returns :
void
|
_bulkActionControls | ||||||
set_bulkActionControls(bulkActionControls: BulkActionControl[])
|
||||||
Sets bulk action controls (actions available for items selected by user).
Parameters :
Returns :
void
|
_headerActionControls | ||||||
set_headerActionControls(headerActionControls: HeaderActionControl[])
|
||||||
Sets header action controls (actions available from data grid header).
Parameters :
Returns :
void
|
This component is used to present a data set in a grid of columns and rows.
You can set:
title
)loadMoreItemsLabel
, loadingItemsLabel
- when loading is in progress)Example
Example :<c8y-data-grid
[title]="'My objects'"
[loadMoreItemsLabel]="'Load more objects'"
[loadingItemsLabel]="'Loading objects…'"
></c8y-data-grid>
The component allows to control some visual settings via displayOptions
:
displayOptions: DisplayOptions = {
bordered: true,
striped: true,
filter: true,
gridHeader: true,
hover: true
};
Further visual settings can be set in individual columns with:
Header and cell rendering can be also customized with own components (see "Columns" section below for an example).
You can define what should be displayed when there are no items to be displayed in the grid by passing an element with c8y-empty-state
class, e.g.:
<c8y-data-grid (...)>
<c8y-ui-empty-state
[icon]="'search'"
[title]="'No results to display.' | translate"
[subtitle]="'Refine your search terms or check your spelling.' | translate"
[horizontal]="true"
></c8y-ui-empty-state>
</c8y-data-grid>
You can define columns by passing a list of objects compliant with Column
interface. The most basic definition contains:
name
- the name of the columnheader
- the header text for the columnpath
- the path in item's object from where the value should be takenThere are default renderers for header, cell and filtering form, but they can be overridden by custom components for fine-grained control over how these elements are presented.
Let's cosnider the following template:
Example :<c8y-data-grid [columns]="columns"></c8y-data-grid>
and the following columns list:
Example :columns: Column[] = [
{
name: 'id',
header: 'ID',
path: 'id',
filterable: true,
sortable: true
},
{
name: 'name',
header: 'Name',
path: 'name',
filterable: true,
sortable: true
},
new TypeDataGridColumn()
];
The first two columns will use default renderers but the third one will be customized. TypeDataGridColumn
needs to implement Column
interface and set the renderers:
import { Type } from '@angular/core';
import { Column, ColumnDataType, SortOrder, FilterPredicateFunction } from '@c8y/ngx-components';
import { TypeHeaderCellRendererComponent } from './type.header-cell-renderer.component';
import { TypeCellRendererComponent } from './type.cell-renderer.component';
import { TypeFilteringFormRendererComponent } from './type.filtering-form-renderer.component';
/**
* Defines a class for custom Type column.
* Implements `Column` interface and sets basic properties, as well as custom components.
*/
export class TypeDataGridColumn implements Column {
name: string;
path?: string;
header?: string;
dataType?: ColumnDataType;
visible?: boolean;
positionFixed?: boolean;
gridTrackSize?: string;
headerCSSClassName?: string | string[];
headerCellRendererComponent?: Type<any>;
cellCSSClassName?: string | string[];
cellRendererComponent?: Type<any>;
sortable?: boolean;
sortOrder?: SortOrder;
filterable?: boolean;
filteringFormRendererComponent?: Type<any>;
filterPredicate?: string | FilterPredicateFunction;
externalFilterQuery?: string | object;
constructor() {
this.name = 'type';
this.header = 'Type';
this.headerCellRendererComponent = TypeHeaderCellRendererComponent;
this.cellRendererComponent = TypeCellRendererComponent;
this.filterable = true;
this.filteringFormRendererComponent = TypeFilteringFormRendererComponent;
this.sortable = false;
}
}
Then each of the renderer components is responsible for rendering a particular part of the grid.
TypeHeaderCellRendererComponent
renders the header cell of the column:
import { Component } from '@angular/core';
import { CellRendererContext } from '@c8y/ngx-components';
/**
* The example component for custom header renderer.
* The header text is taken from `this.context.property` which contains current column object.
* Additionally the header has custom icon element and styled span element.
*/
@Component({
template: `
<i c8yIcon="rocket"></i>
<span style="text-transform: uppercase; font-variant: small-caps; text-decoration: underline;">
{{ context.property.header }}
</span>
`
})
export class TypeHeaderCellRendererComponent {
constructor(public context: CellRendererContext) {}
}
TypeCellRendererComponent
renders the data cell of the column:
import { Component, forwardRef, Inject } from '@angular/core';
import { CellRendererContext } from '@c8y/ngx-components';
import { ServerGridExampleService } from '../server-grid-example.service';
/**
* The example component for custom cell renderer.
* It gets `context` with the current row item and the column.
* Additionally, a service is injected to provide a helper method.
* The template displays the icon and the label with additional styling.
*/
@Component({
template: `
<span>
<i [c8yIcon]="value.icon" class="m-r-5"></i>
<code>{{ value.label }}</code>
</span>
`
})
export class TypeCellRendererComponent {
/** Returns the icon and label for the current item. */
get value() {
return this.service.getTypeIconAndLabel(this.context.item);
}
constructor(
public context: CellRendererContext,
@Inject(ServerGridExampleService) public service: ServerGridExampleService
) {}
}
The example above also shows how to use a custom service to process the current item before displaying it in the template.
TypeFilteringFormRendererComponent
renders the filtering form for the column where filtering options can be selected and applied or reset:
import { Component, Inject } from '@angular/core';
import { FilteringFormRendererContext } from '@c8y/ngx-components';
import { ServerGridExampleService, TypeFilteringModel } from '../server-grid-example.service';
/**
* This is the example component for custom filtering form.
* The form can contain any inputs you want.
* The important thing is to invoke one of the 2 methods:
*
* - `applyFilter` which sets `filterPredicate` or `externalFilterQuery` in the column,
* these values will later be used to generate the query
* - `resetFilter` which resets filter settings in the column
*
* Our example shows the list of checkboxes. Selecting them modifies the query being sent.
*/
@Component({
template: `
<form #filterForm="ngForm">
<strong>Show managed objects of type:</strong>
<c8y-form-group class="m-b-0">
<label class="c8y-checkbox">
<input type="checkbox" name="group" [(ngModel)]="model.group" />
<span></span>
<span>Group</span>
</label>
</c8y-form-group>
<c8y-form-group class="m-b-0">
<label class="c8y-checkbox">
<input type="checkbox" name="device" [(ngModel)]="model.device" />
<span></span>
<span>Device</span>
</label>
</c8y-form-group>
<c8y-form-group class="m-b-0">
<label class="c8y-checkbox">
<input type="checkbox" name="dashboard" [(ngModel)]="model.dashboard" />
<span></span>
<span>Dashboard</span>
</label>
</c8y-form-group>
<c8y-form-group class="m-b-0">
<label class="c8y-checkbox">
<input type="checkbox" name="smartRule" [(ngModel)]="model.smartRule" />
<span></span>
<span>Smart rule</span>
</label>
</c8y-form-group>
<c8y-form-group class="m-b-0">
<label class="c8y-checkbox">
<input type="checkbox" name="file" [(ngModel)]="model.file" />
<span></span>
<span>File</span>
</label>
</c8y-form-group>
<c8y-form-group class="m-b-0">
<label class="c8y-checkbox">
<input type="checkbox" name="application" [(ngModel)]="model.application" />
<span></span>
<span>Application</span>
</label>
</c8y-form-group>
</form>
<div class="data-grid__dropdown__footer d-flex separator-top">
<button class="btn btn-default btn-sm m-r-8 flex-grow" (click)="resetFilter()">Reset</button>
<button
class="btn btn-primary btn-sm flex-grow"
[disabled]="filterForm.invalid"
(click)="applyFilter()"
>
Apply
</button>
</div>
`
})
export class TypeFilteringFormRendererComponent {
model: TypeFilteringModel;
constructor(
public context: FilteringFormRendererContext,
@Inject(ServerGridExampleService) public service: ServerGridExampleService
) {
// restores the settings from current column setup
this.model = (this.context.property.externalFilterQuery || {}).model || {};
}
/**
* Applies the filter.
* Sets `externalFilterQuery.model` to restore the same settings the next time the form is displayed.
* Sets `externalFilterQuery.query` to pass the query object to be included in the final data grid query.
*/
applyFilter() {
this.context.applyFilter({
externalFilterQuery: {
model: this.model,
query: this.service.getTypeQuery(this.model)
}
});
}
/** Restes the filter, just call the method from context. */
resetFilter() {
this.context.resetFilter();
}
}
Later it will be presented how to use externalFilterQuery
to build a query to fetch data from the server.
Rows can be provided to the data grid via rows
input. However, this is usually only useful for predefined set of data which is then processed on the client side. If you want to know more about this way of providing data, see the example in our tutorial application.
Another way of providing data to the data grid is to use serverSideDataCallback
input. The function provided to this input will be invoked whenever the grid needs to load data, i.e. on initial load, on next page load, on column filtering or sorting settings change. This function should take a DataSourceModifier
object and return a ServerSideDataResult
object. The modifier represents the current state of the grid, i.e. filtering/sorting settings in columns, search text from the search input, selected items and pagination. Based on this state, you can perform any logic you need to determine what data should be displayed and then return it, along with additional statistics needed for accurate handling of the paging:
size
: the number of all items that can be displayed in a grid when no filters are appliedfilteredSize
: the number of items that match current filtersLet's consider the following example:
Example :/**
* This method loads data when data grid requests it (e.g. on initial load or on column settings change).
* It gets the object with current data grid setup and is supposed to return:
* full response, list of items, paging object, the number of items in the filtered subset, the number of all items.
*/
async onDataSourceModifier(
dataSourceModifier: DataSourceModifier
): Promise<ServerSideDataResult> {
let serverSideDataResult: ServerSideDataResult;
const { res, data, paging } = await this.service.getData(
dataSourceModifier.columns,
dataSourceModifier.pagination
);
const filteredSize: number = await this.service.getCount(
dataSourceModifier.columns,
dataSourceModifier.pagination
);
const size: number = await this.service.getTotal();
serverSideDataResult = { res, data, paging, filteredSize, size };
return serverSideDataResult;
}
As you can see, we're using the state from dataSourceModifier
to execute the queries and return the result. The actual implementation of these 3 queries might vary depending on your use case (what endpoint is used, what kind of columns you defined, etc.). In the following example we'll build and execute inventory queries based on columns with standard and custom filtering/sorting settings and pagination:
/** Returns data for current columns and pagination setup. */
async getData(columns: Column[], pagination: Pagination) {
// build filters based on columns and pagination
const filters = this.getFilters(columns, pagination);
// execute inventory query for the list of managed objects
return this.inventoryService.list(filters);
}
/** Returns the number of items matching current columns and pagination setup. */
async getCount(columns: Column[], pagination: Pagination) {
const filters = {
// build filters based on columns and pagination
...this.getFilters(columns, pagination),
// but we only need the number of items, not the items themselves
pageSize: 1,
currentPage: 1
};
return (await this.inventoryService.list(filters)).paging.totalPages;
}
/** Returns the total number of items (with no filters). */
async getTotal(): Promise<number> {
const filters = {
pageSize: 1,
withTotalPages: true
};
return (await this.inventoryService.list(filters)).paging.totalPages;
}
Two of the functions above use getFilters
method to build a query based on the columns and pagination setup:
/** Returns filters for given columns and pagination setup. */
private getFilters(columns: Column[], pagination: Pagination) {
return {
query: this.getQueryString(columns),
pageSize: pagination.pageSize,
currentPage: pagination.currentPage,
withChildren: false,
withTotalPages: true
};
}
/** Returns a query string based on columns setup. */
private getQueryString(columns: Column[]): string {
const fullQuery = this.getQueryObj(columns);
return this.queriesUtil.buildQuery(fullQuery);
}
/** Returns a query object based on columns setup. */
private getQueryObj(columns: Column[]): any {
return transform(columns, (query, column) => this.addColumnQuery(query, column), {
__filter: {},
__orderby: []
});
}
/** Extends given query with a part based on the setup of given column. */
private addColumnQuery(query: any, column: Column): void {
// when a column is marked as filterable
if (column.filterable) {
// in the case of default filtering form, `filterPredicate` will contain the string entered by a user
if (column.filterPredicate) {
// so we use it as the expected value, * allow to search for it anywhere in the property
query.__filter[column.path] = `*${column.filterPredicate}*`;
}
// in the case of custom filtering form, we're storing the query in `externalFilterQuery.query`
if (column.externalFilterQuery) {
query = this.queriesUtil.addAndFilter(query, column.externalFilterQuery.query);
}
}
// when a column is sortable and has a specified sorting order
if (column.sortable && column.sortOrder) {
// add sorting condition for the configured column `path`
query.__orderby.push({
[column.path]: column.sortOrder === 'asc' ? 1 : -1
});
}
return query;
}
You can see the above example working in our tutorial application:
The component supports actions to be invoked on individual rows or bulk actions to be invoked on multiple rows previously selected by user. You can either use predefined actions or define your own:
Example :actionControls: ActionControl[] = [
{ type: BuiltInActionType.Edit, callback: (item) => console.dir(item) },
{ type: BuiltInActionType.Export, callback: (item) => console.dir(item) },
{ type: BuiltInActionType.Delete, callback: (item) => console.dir(item) },
{
type: 'customAction',
icon: 'online',
text: 'Custom action',
callback: (item) => console.dir(item)
}
];
bulkActionControls: BulkActionControl[] = [
{
type: BuiltInActionType.Export,
callback: (selectedItemIds) => console.dir(selectedItemIds)
},
{
type: BuiltInActionType.Delete,
callback: (selectedItemIds) => console.dir(selectedItemIds)
},
{
type: 'customAction',
icon: 'online',
text: 'Custom action',
callback: (selectedItemIds) => console.dir(selectedItemIds)
}
];
The component takes infiniteScroll
input which can have one of the following values:
auto
: attempts to load more data automatically as user scrolls down (default)show
: shows a load more button for the user to decidenone
: doesn't perform any load more actionhidden
: loads more data automatically but with no visible button for the userThe component exposes several event emitter outputs:
itemsSelect
onChildDevices
onConfigChange
onFilter
rowClick
rowMouseLeave
rowMouseOver
to which you can bind via template, e.g.:
Example :<c8y-data-grid
(rowClick)="onRowClick($event)"
(itemsSelect)="onItemsSelect($event)"
(onConfigChange)="onConfigChange($event)"
></c8y-data-grid>
See more details about them in the outputs reference.
<div
class="table-data-grid-scroll"
#scroll
[ngClass]="{
'table-data-grid__overlay': (dataSource.loading$ | async) && !loadMoreComponent?.isLoading
}"
data-cy="c8y-data-grid--table-data-grid-scroll"
>
<div
class="table-data-grid__loading--wrapper"
*ngIf="(dataSource.loading$ | async) && !loadMoreComponent?.isLoading"
>
<div class="table-data-grid__loading--loader">
<c8y-loading
layout="application"
[message]="loadingItemsLabel"
></c8y-loading>
</div>
</div>
<div
class="table-data-grid-header separator large-padding"
*ngIf="displayOptions.gridHeader"
>
<div
class="h4"
[ngClass]="{ 'm-r-16': !!title }"
>
{{ title | translate }}
</div>
<ng-container *ngIf="displayOptions.filter">
<span *ngIf="!filteringApplied">
<small
class="m-r-4"
*ngIf="!!filteringLabelsParams.allItemsCount"
ngNonBindable
translate
[translateParams]="filteringLabelsParams"
>
{{ filteredItemsCount }} of {{ allItemsCount }} items
</small>
<span
class="label label-default m-r-4"
translate
>
No filters
</span>
</span>
<span
class="d-flex a-i-center"
*ngIf="filteringApplied"
>
<ng-container *ngIf="!!filteringLabelsParams.allItemsCount">
<div class="a-i-center">
<span class="badge badge-info m-r-4">
{{ (dataSource.stats$ | async).filteredSize }}
</span>
<small
ngNonBindable
translate
[translateParams]="filteringLabelsParams"
>
of {{ allItemsCount }} items
</small>
</div>
</ng-container>
<div
class="dropdown"
placement="bottom left"
dropdown
#ddFilters="bs-dropdown"
[cdkTrapFocus]="ddFilters.isOpen"
[insideClick]="true"
>
<button
class="btn btn-default btn-sm m-l-8"
title="{{ 'Active filters' | translate }}"
aria-haspopup="true"
dropdownToggle
data-cy="c8y-data-grid--filters"
>
<i
class="m-r-4"
c8yIcon="filter"
></i>
<span>{{ 'Active filters' | translate }}</span>
<span class="p-relative p-l-4 p-r-16">
<span class="badge badge-system p-absolute">
{{ columnsWithFiltersApplied.length }}
</span>
</span>
</button>
<div
class="dropdown-menu"
*dropdownMenu
(click)="$event.stopPropagation()"
>
<div class="data-grid__dropdown bg-level-0">
<ul class="list-unstyled m-0">
<li
*ngFor="let column of columnsWithFiltersApplied; let last = last"
[ngClass]="{ 'separator-bottom': !last }"
>
<ng-container>
<div
class="dropdown-header sticky-top text-truncate no-border-top p-b-0"
title="{{ (column.header | translate) || column.name }}"
>
<label>
{{ (column.header | translate) || column.name }}
</label>
</div>
<div
class="list-group-item borderless d-flex d-col"
*ngFor="
let groupedFilterChips of column
| mapToFilterChips
| async
| groupedFilterChips;
let first = first
"
[ngClass]="{ 'p-t-0': first }"
>
<p
class="small p-b-4"
*ngIf="groupedFilterChips.label"
>
{{ groupedFilterChips.label | translate }}
</p>
<div class="d-flex a-i-center gap-4 flex-wrap">
<span
class="tag tag--info chip"
*ngFor="let chip of groupedFilterChips.chips"
>
<button
class="btn btn-xs btn-clean text-10 m-r-4"
title="{{ 'Remove filter' | translate }}"
(click)="removeFilter(chip.remove())"
data-cy="c8y-data-grid--remove-chip"
>
<i c8yIcon="times"></i>
</button>
{{ chip.displayValue | translate }}
</span>
</div>
</div>
</ng-container>
</li>
</ul>
</div>
<div class="list-group-item separator-top sticky-bottom">
<button
class="btn btn-sm btn-default"
title="{{ 'Clear all filters' | translate }}"
type="button"
(click)="clearFilters()"
data-cy="c8y-data-grid--clear-filters"
>
{{ 'Clear all filters' | translate }}
</button>
</div>
</div>
</div>
</span>
<button
class="btn-help btn-help--sm hidden-xs hidden-sm"
[attr.aria-label]="'Help' | translate"
[popover]="filtersHelpPopover"
placement="right"
triggers="focus"
type="button"
*ngIf="displayOptions.filter"
data-cy="data-grid--help-filters"
>
<i c8yIcon="question-circle-o"></i>
</button>
<ng-template #filtersHelpPopover>
<div [innerHtml]="filtersHelpPopoverHtml | translate"></div>
</ng-template>
<button
class="btn-clean text-primary hidden-xs hidden-sm"
[attr.aria-label]="'Help' | translate"
popover="{{ 'The counter for the total number of items might be inaccurate.' | translate }}"
placement="right"
triggers="focus"
type="button"
*ngIf="showCounterWarning"
>
<i c8yIcon="warning"></i>
</button>
</ng-container>
<div class="m-l-auto">
<div class="btnbar d-flex a-i-center">
<ng-container
*ngFor="let headerActionControl of headerActionControls | visibleControls | async"
>
<ng-container *ngIf="!headerActionControl.template; else customTemplate">
<button
class="btnbar-btn btn-link"
title="{{ headerActionControl.text | translate }}"
type="button"
(click)="headerActionControl.callback()"
c8yProductExperience
inherit
[actionData]="{
action: PX_ACTIONS.CUSTOM_ACTION,
customActionName: headerActionControl.text,
type: headerActionControl.type
}"
>
<i
class="m-r-4"
[c8yIcon]="headerActionControl.icon"
></i>
<span>{{ headerActionControl.text | translate }}</span>
</button>
</ng-container>
<ng-template #customTemplate>
<ng-container
*ngTemplateOutlet="
headerActionControl.template;
context: { headerActionControl: headerActionControl }
"
></ng-container>
</ng-template>
</ng-container>
<div
class="dropdown"
placement="bottom left"
*ngIf="configureColumnsEnabled"
dropdown
#ddConfigureColumns="bs-dropdown"
[cdkTrapFocus]="ddConfigureColumns.isOpen"
[insideClick]="true"
>
<button
class="btnbar-btn"
title="{{ 'Configure columns' | translate }}"
type="button"
data-cy="data-grid--custom-column-btn"
dropdownToggle
>
<i
class="m-r-4"
c8yIcon="columns"
></i>
<span>{{ 'Configure columns' | translate }}</span>
</button>
<ul
class="dropdown-menu data-grid__dropdown"
*dropdownMenu
(click)="$event.stopPropagation()"
>
<li>
<div
class="list-group m-0"
cdkDropList
(cdkDropListDropped)="onColumnDrop($event)"
>
<div
*ngFor="let column of columns"
cdkDrag
cdkDragLockAxis="y"
>
<ng-container *ngIf="!column.positionFixed">
<div class="list-group-item draggable-after p-0 a-i-center">
<label
class="c8y-checkbox p-l-16"
title="{{ (column.header | translate) || column.name }}"
[attr.data-cy]="'data-grid--custom-column-header-' + column.header"
>
<input
type="checkbox"
[(ngModel)]="column.visible"
(change)="
updateGridColumnsSize(); emitConfigChange('changeColumnVisibility')
"
c8yProductExperience
inherit
[actionData]="{
action: PX_ACTIONS.CHANGE_VISIBILITY,
column: column.name,
visible: !column.visible
}"
/>
<span></span>
<span>{{ (column.header | translate) || column.name }}</span>
</label>
<button
class="btn btn-dot showOnHover max-width-fit a-i-center"
[attr.aria-label]="'Remove`column,verb`' | translate"
tooltip="{{ 'Remove`column,verb`' | translate }}"
placement="left"
container="body"
type="button"
(click)="removeCustomColumn(poConfirm, column, ddConfigureColumns)"
*ngIf="column.custom"
>
<c8y-popover-confirm
[title]="'Confirm removal' | translate"
triggers="focus"
[placement]="'left'"
#poConfirm
></c8y-popover-confirm>
<i
c8yIcon="minus-circle"
data-cy="data-grid--custom-column-remove-btn"
></i>
</button>
</div>
</ng-container>
</div>
</div>
</li>
<li
class="p-8 sticky-bottom separator-top"
*ngIf="isConfigContextKnown"
>
<button
class="btn btn-default btn-block"
title="{{ 'Add custom column' | translate }}"
type="button"
data-cy="data-grid--add-custom-column"
(click)="openCustomColumnModal(); ddConfigureColumns.hide()"
>
<i
class="m-r-4"
c8yIcon="plus-circle"
></i>
<span>{{ 'Add custom column' | translate }}</span>
</button>
</li>
</ul>
</div>
<button
class="btnbar-btn btn-link"
title="{{ 'Reload' | translate }}"
type="button"
data-cy="data-grid--reload-btn"
[disabled]="dataSource.loading$ | async"
(click)="clickReload()"
>
<i
class="m-r-4"
c8yIcon="refresh"
></i>
<span>{{ 'Reload' | translate }}</span>
</button>
<div
class="input-group input-group-search m-l-sm-16 data-grid__search-input"
*ngIf="!serverSideDataCallback || showSearch"
>
<input
class="form-control"
placeholder="{{ 'Search…' | translate }}"
type="search"
[ngModel]="searchText"
(input)="searchText$.emit($event.target.value)"
/>
<div class="input-group-addon">
<i
c8yIcon="search"
*ngIf="searchText.length === 0"
></i>
<i
class="pointer"
c8yIcon="times"
*ngIf="searchText.length > 0"
(click)="searchText = ''; searchText$.emit('')"
c8yProductExperience
inherit
[actionData]="{ action: PX_ACTIONS.CLEAR_SEARCH }"
></i>
</div>
</div>
</div>
</div>
<div
class="table-data-grid-header-bulk-actions animated slideInDown fast"
data-cy="table-data-grid-header-bulk-actions"
*ngIf="selectedItemIds.length !== 0"
>
<h4>
<ng-container [ngPlural]="selectedItemIds.length">
<ng-template ngPluralCase="=1">
<span translate>1 selected item.</span>
</ng-template>
<ng-template ngPluralCase="other">
<span
ngNonBindable
translate
[translateParams]="{ count: selectedItemIds.length }"
>
{{ count }} selected items.
</span>
</ng-template>
</ng-container>
<br class="visible-xs" />
<small *ngIf="!serverSideDataCallback && selectedItemIds.length >= pagination.pageSize">
<a
class="interact"
(click)="setAllItemsSelected(true)"
c8yProductExperience
inherit
[actionData]="{ action: PX_ACTIONS.SELECT_ALL_ITEMS }"
>
<span
ngNonBindable
translate
[translateParams]="{ count: (dataSource.stats$ | async).filteredSize }"
>
Select all {{ count }} items
</span>
</a>
</small>
</h4>
<div class="m-l-auto">
<div class="btnbar d-flex">
<ng-container
*ngFor="
let bulkActionControl of bulkActionControls | visibleControls: selectedItemIds | async
"
>
<ng-container [ngSwitch]="bulkActionControl.type">
<button
class="btnbar-btn"
title="{{ 'Export' | translate }}"
type="button"
(click)="bulkActionControl.callback(selectedItemIds, reload.bind(this))"
*ngSwitchCase="builtInActionType.Export"
[actionData]="{ action: PX_ACTIONS.BULK_EXPORT }"
c8yProductExperience
inherit
>
<i c8yIcon="sign-out"></i>
<span>{{ 'Export' | translate }}</span>
</button>
<button
class="btnbar-btn"
title="{{ 'Delete' | translate }}"
type="button"
(click)="bulkActionControl.callback(selectedItemIds, reload.bind(this))"
*ngSwitchCase="builtInActionType.Delete"
[actionData]="{ action: PX_ACTIONS.BULK_DELETE }"
c8yProductExperience
inherit
>
<i c8yIcon="delete"></i>
<span>{{ 'Delete' | translate }}</span>
</button>
<button
class="btnbar-btn"
title="{{ bulkActionControl.text | translate }}"
type="button"
(click)="bulkActionControl.callback(selectedItemIds, reload.bind(this))"
*ngSwitchDefault
[actionData]="{
action: PX_ACTIONS.BULK_CUSTOM_ACTION,
customActionName: bulkActionControl.text
}"
c8yProductExperience
inherit
>
<i
[class]="bulkActionControl.iconClasses"
c8yIcon="{{ bulkActionControl.icon }}"
></i>
<span>{{ bulkActionControl.text | translate }}</span>
</button>
</ng-container>
</ng-container>
<button
class="btnbar-btn"
title="{{ 'Cancel' | translate }}"
type="button"
(click)="cancel()"
[actionData]="{
action: PX_ACTIONS.BULK_CANCEL
}"
c8yProductExperience
inherit
>
<i c8yIcon="times"></i>
<span>{{ 'Cancel' | translate }}</span>
</button>
</div>
</div>
</div>
</div>
<table
class="table table-filtered-sorted table-data-grid large-padding"
[class.table-striped]="displayOptions.striped"
[class.table-bordered]="displayOptions.bordered"
[class.table-hover]="displayOptions.hover"
[class.table-data-grid-with-checkboxes]="selectable"
[class.table-data-grid-with-actions]="actionControls.length > 0"
[style.grid-template-columns]="styles.gridTemplateColumns"
cdk-table
[dataSource]="dataSource"
[multiTemplateDataRows]="true"
(mousemove)="resizeHandleContainerMouseMove$.emit($event)"
(mouseup)="resizeHandleContainerMouseUp$.emit($event)"
data-cy="c8y-data-grid--table"
>
<ng-container
*ngFor="let column of columns; let i = index; trackBy: trackByName"
[cdkColumnDef]="column.name"
>
<ng-container [ngSwitch]="column.name">
<ng-container *ngSwitchCase="'checkbox'">
<th
cdk-header-cell
*cdkHeaderCellDef
data-type="icon"
>
<div>
<label class="c8y-checkbox">
<input
[attr.aria-label]="'Selected' | translate"
type="checkbox"
[checked]="currentPageSelectionState.allSelected"
[indeterminate]="
!(
currentPageSelectionState.allSelected ||
currentPageSelectionState.allDeselected
)
"
(change)="setAllItemsInCurrentPageSelected($event.target.checked)"
c8yProductExperience
inherit
[actionData]="{ action: PX_ACTIONS.SELECT_ALL_ITEMS }"
/>
<span></span>
</label>
</div>
</th>
<td
cdk-cell
*cdkCellDef="let row"
data-type="icon"
>
<label class="c8y-checkbox">
<input
[attr.aria-label]="'Selected' | translate"
type="checkbox"
[checked]="isItemSelected(row)"
(change)="setItemsSelected([row], $event.target.checked)"
c8yProductExperience
inherit
[actionData]="{
action: PX_ACTIONS.SELECT_ITEM,
id: row.id
}"
data-cy="c8y-data-grid--checkbox"
/>
<span></span>
</label>
</td>
</ng-container>
<ng-container *ngSwitchCase="'radio-button'">
<th
cdk-header-cell
*cdkHeaderCellDef
data-type="icon"
></th>
<td
cdk-cell
*cdkCellDef="let row"
data-type="icon"
>
<label class="c8y-radio">
<input
[attr.aria-label]="'Selected' | translate"
name="select-row"
type="radio"
[checked]="isItemSelected(row)"
(change)="changeSelectedItem(row)"
c8yProductExperience
inherit
[actionData]="{
action: PX_ACTIONS.SELECT_ITEM,
id: row.id
}"
data-cy="c8y-data-grid--radio"
/>
<span></span>
</label>
</td>
</ng-container>
<ng-container *ngSwitchCase="'actions'">
<th
cdk-header-cell
*cdkHeaderCellDef
data-type="icon"
>
<p class="text-medium sr-only">{{ 'Actions' | translate }}</p>
</th>
<td
cdk-cell
*cdkCellDef="let row"
data-type="icon"
>
<ng-container
*ngFor="
let actionControl of actionControls
| visibleControls: row
| async
| slice: 0 : ((actionControls | visibleControls: row | async)?.length > 2 ? 1 : 2)
"
>
<ng-container [ngSwitch]="actionControl.type">
<button
class="btn btn-dot"
[attr.aria-label]="'Edit' | translate"
tooltip="{{ 'Edit' | translate }}"
container="body"
type="button"
*ngSwitchCase="builtInActionType.Edit"
[delay]="500"
(click)="actionControl.callback(row, reload.bind(this))"
c8yProductExperience
inherit
[actionData]="{
action: PX_ACTIONS.EDIT_ITEM,
id: row.id
}"
data-cy="c8y-data-grid--edit-button-in-row"
>
<i c8yIcon="pencil"></i>
</button>
<button
class="btn btn-dot btn-dot--danger showOnHover"
[attr.aria-label]="'Delete' | translate"
tooltip="{{ 'Delete' | translate }}"
container="body"
type="button"
[delay]="500"
(click)="actionControl.callback(row, reload.bind(this))"
*ngSwitchCase="builtInActionType.Delete"
[actionData]="{
action: PX_ACTIONS.DELETE_ITEM,
id: row.id
}"
c8yProductExperience
inherit
data-cy="c8y-data-grid--remove-button-in-row"
>
<i c8yIcon="delete"></i>
</button>
<button
class="btn btn-dot"
[attr.aria-label]="(actionControl.icon ? actionControl.text : '') | translate"
tooltip="{{ (actionControl.icon ? actionControl.text : '') | translate }}"
container="body"
type="button"
[ngClass]="{ showOnHover: actionControl.showOnHover }"
[delay]="500"
*ngSwitchDefault
(click)="actionControl.callback(row, reload.bind(this))"
[actionData]="{
action: PX_ACTIONS.CUSTOM_ACTION_ITEM,
customActionName: actionControl.text,
id: row.id
}"
c8yProductExperience
inherit
[attr.data-cy]="'c8y-data-grid--button-in-row--' + actionControl.text"
>
<i
c8yIcon="{{ actionControl.icon }}"
*ngIf="actionControl.icon"
></i>
<span *ngIf="!actionControl.icon">{{ actionControl.text | translate }}</span>
</button>
</ng-container>
</ng-container>
<div
[ngClass]="{
'm-l-auto overflow-visible':
(actionControls | visibleControls: row | async)?.length > 2
}"
>
<div
class="dropdown"
placement="bottom right"
container="body"
dropdown
*ngIf="(actionControls | visibleControls: row | async)?.length > 2"
>
<button
class="dropdown-toggle c8y-dropdown"
title="{{ 'Actions' | translate }}"
aria-haspopup="true"
type="button"
data-cy="c8y-data-grid--row-actions-dropdown"
dropdownToggle
>
<i c8yIcon="ellipsis-v"></i>
</button>
<ul
class="dropdown-menu dropdown-menu-right"
*dropdownMenu
>
<li
*ngFor="
let actionControl of actionControls
| visibleControls: row
| async
| slice
: ((actionControls | visibleControls: row | async)?.length > 2 ? 1 : 2)
"
>
<ng-container [ngSwitch]="actionControl.type">
<button
title="{{ 'Edit' | translate }}"
type="button"
*ngSwitchCase="builtInActionType.Edit"
(click)="actionControl.callback(row, reload.bind(this))"
[actionData]="{
action: PX_ACTIONS.EDIT_ITEM,
id: row.id
}"
c8yProductExperience
inherit
>
<i c8yIcon="pencil"></i>
{{ 'Edit' | translate }}
</button>
<button
title="{{ 'Delete' | translate }}"
type="button"
*ngSwitchCase="builtInActionType.Delete"
(click)="actionControl.callback(row, reload.bind(this))"
[actionData]="{
action: PX_ACTIONS.DELETE_ITEM,
id: row.id
}"
c8yProductExperience
inherit
>
<i c8yIcon="delete"></i>
{{ 'Delete' | translate }}
</button>
<button
title="{{ 'Export' | translate }}"
type="button"
*ngSwitchCase="builtInActionType.Export"
(click)="actionControl.callback(row, reload.bind(this))"
[actionData]="{
action: PX_ACTIONS.EXPORT_ITEM,
id: row.id
}"
c8yProductExperience
inherit
>
<i c8yIcon="data-export"></i>
{{ 'Export' | translate }}
</button>
<button
title="{{ actionControl.text | translate }}"
type="button"
*ngSwitchDefault
(click)="actionControl.callback(row, reload.bind(this))"
c8yProductExperience
inherit
[actionData]="{
action: PX_ACTIONS.CUSTOM_ACTION_ITEM,
customActionName: actionControl.text,
id: row.id
}"
>
<i c8yIcon="{{ actionControl.icon }}"></i>
{{ actionControl.text | translate }}
</button>
</ng-container>
</li>
</ul>
</div>
</div>
</td>
</ng-container>
<ng-container *ngSwitchDefault>
<th
[class.sorted]="column.sortOrder"
[class.filtered]="column | map: isColumnFilteringApplied"
[class.hidden]="!column.visible"
cdk-header-cell
*cdkHeaderCellDef
[ngClass]="column.headerCSSClassName"
[attr.data-type]="column.dataType"
>
<div
[title]="(column.header | translate) || column.name"
*ngIf="!column.filterable"
>
<ng-container
*ngIf="
[
{
columnName: column.name,
value: (column.header | translate) || column.name
}
] | map: getHeaderCellRendererSpec : this as cellRendererSpec
"
>
<c8y-cell-renderer [spec]="cellRendererSpec"></c8y-cell-renderer>
</ng-container>
</div>
<!-- isDropDownPlacedRight to be removed when columns are transformed to observables. -->
<div
class="dropdown"
placement="bottom {{ isDropDownPlacedRight(column) ? 'right' : 'left' }}"
*ngIf="column.filterable"
dropdown
#gridHeaderDropdown="bs-dropdown"
[cdkTrapFocus]="gridHeaderDropdown.isOpen"
[insideClick]="true"
>
<button
class="btn-header"
[title]="(column.header | translate) || column.name"
type="button"
[attr.data-cy]="'data-grid--header-btn--' + column.header"
dropdownToggle
>
<ng-container
*ngIf="
[
{
columnName: column.name,
value: (column.header | translate) || column.name
}
] | map: getHeaderCellRendererSpec : this as cellRendererSpec
"
>
<c8y-cell-renderer
data-cy="c8y-data-grid--c8y-cell-renderer"
[spec]="cellRendererSpec"
></c8y-cell-renderer>
</ng-container>
<i
c8yIcon="filter"
title="{{ 'Filter' | translate }}"
></i>
</button>
<!-- isDropDownPlacedRight to be removed when columns are transformed to observables. -->
<ul
class="dropdown-menu"
*dropdownMenu
[ngClass]="{ 'dropdown-menu-right-grid': isDropDownPlacedRight(column) }"
(click)="$event.stopPropagation()"
>
<li class="data-grid__dropdown">
<ng-container
*ngIf="
[
{
column: column,
dropdown: gridHeaderDropdown
}
] | map: getFilteringFormRendererSpec : this as filteringFormRendererSpec
"
>
<c8y-filtering-form-renderer
class="bg-component"
[spec]="filteringFormRendererSpec"
data-cy="c8y-data-grid--c8y-filtering-form-renderer"
></c8y-filtering-form-renderer>
</ng-container>
</li>
</ul>
</div>
<button
class="btn-sort"
[title]="sortColumnTitle | translate: { name: column.header | translate }"
type="button"
*ngIf="column.sortable"
(click)="changeSortOrder(column.name)"
data-cy="change-sort-order"
>
<ng-container [ngSwitch]="column.sortOrder">
<i
c8yIcon="long-arrow-up"
*ngSwitchCase="'asc'"
></i>
<i
c8yIcon="long-arrow-down"
*ngSwitchCase="'desc'"
></i>
<i
c8yIcon="exchange"
*ngSwitchDefault
></i>
</ng-container>
</button>
<span
class="resize-handle"
*ngIf="column.resizable"
(mousedown)="
resizeHandleMouseDown$.emit({ event: $event, targetColumnName: column.name })
"
></span>
</th>
<td
[class.hidden]="!column.visible"
[attr.data-cell-title]="column.header | translate"
cdk-cell
*cdkCellDef="let row"
[ngClass]="column.cellCSSClassName"
[attr.data-cy]="'data-grid--' + column.header"
[attr.data-type]="column.dataType"
>
<ng-container
*ngIf="
[
{
value: resolveCellValue(row, column.path),
row: row,
columnName: column.name
}
] | map: getCellRendererSpec : this as cellRendererSpec
"
>
<c8y-cell-renderer [spec]="cellRendererSpec"></c8y-cell-renderer>
</ng-container>
</td>
</ng-container>
</ng-container>
</ng-container>
<ng-container cdkColumnDef="infiniteScrollFooter">
<td
[style.grid-column]="styles.gridInfiniteScrollColumn"
cdk-footer-cell
*cdkFooterCellDef
>
<template #infiniteScrollContainer></template>
</td>
</ng-container>
<tr
cdk-header-row
*cdkHeaderRowDef="columnNames"
></tr>
<tr
data-cy="c8y-data-grid--row-in-data-grid"
cdk-row
*cdkRowDef="let row; columns: columnNames; let idx = dataIndex"
[ngClass]="[
activeClassName && row === lastClickedRow ? activeClassName : '',
idx % 2 === 0 ? 'even' : 'odd'
]"
(mouseover)="rowMouseOver.emit(row)"
(mouseleave)="rowMouseLeave.emit(row)"
(click)="handleClick(row)"
></tr>
<tr
class="expanded-row"
[ngClass]="{ hidden: !(expandedRows.get(row).visible$ | async) }"
data-cy="c8y-data-grid--expanded-row-in-data-grid"
cdk-row
*cdkRowDef="let row; columns: ['expanded-row']; when: isRowExpanded"
></tr>
<ng-container cdkColumnDef="expanded-row">
<td
[style.grid-column]="styles.gridInfiniteScrollColumn"
cdk-cell
*cdkCellDef="let row"
>
<ng-container
*ngTemplateOutlet="
expandableRow?.template;
context: {
$implicit: row,
asyncRenderSuccess: setExpandableRowVisible.bind(this, row, true),
asyncRenderFail: setExpandableRowVisible.bind(this, row, false)
}
"
></ng-container>
</td>
</ng-container>
<ng-container>
<tr
[ngClass]="{ hidden: !infiniteScroll }"
cdk-footer-row
*cdkFooterRowDef="['infiniteScrollFooter']"
></tr>
</ng-container>
</table>
<div
class="d-flex m-0 p-t-40 p-b-40"
*ngIf="
!(dataSource.loading$ | async) &&
((dataSource.stats$ | async).filteredSize === 0 || (dataSource.data$ | async).length === 0)
"
>
<div class="col-lg-3 col-sm-4 m-l-auto m-r-auto">
<ng-content select="c8y-ui-empty-state, .c8y-empty-state"></ng-content>
<ng-container
*ngTemplateOutlet="
emptyState?.templateRef;
context: { $implicit: emptyStateContext$ | async }
"
></ng-container>
</div>
</div>
<div
class="table-data-grid-footer separator large-padding"
*ngIf="pagination && !infiniteScroll"
>
<div class="col-sm-4 no-gutter">
<div
class="counter p-t-8 p-b-8"
*ngIf="(dataSource.stats$ | async).currentPageSize > 0"
data-cy="data-grid--counter"
>
<span
class="text-muted"
ngNonBindable
translate
[translateParams]="paginationLabelParams"
>
{{ pageFirstItemIdx }} - {{ pageLastItemIdx }} of {{ itemsTotal }}
</span>
</div>
</div>
<div class="col-sm-4 no-gutter text-center">
<div
class="form-group form-inline p-t-8 p-b-8"
*ngIf="(dataSource.stats$ | async).filteredSize > minPossiblePageSize"
>
<label
class="m-r-4"
for="filteredSize"
>
{{ 'Items per page' | translate }}
</label>
<div class="c8y-select-wrapper">
<select
class="form-control"
id="filteredSize"
data-cy="data-grid--pagesize-options"
[ngModel]="pagination.pageSize"
(ngModelChange)="
updatePagination({ itemsPerPage: $event, page: pagination.currentPage })
"
>
<option
*ngFor="let pageSize of possiblePageSizes"
[ngValue]="pageSize"
>
{{ pageSize }}
</option>
</select>
</div>
</div>
</div>
<div class="col-sm-4 no-gutter text-right">
<pagination
[class.hidden]="hidePagination$ | async"
class="p-t-8 p-b-8"
*ngIf="(dataSource.stats$ | async).filteredSize > 0"
[ngModel]="pagination.currentPage"
(pageChanged)="updatePagination($event)"
[totalItems]="(dataSource.stats$ | async).filteredSize"
[itemsPerPage]="pagination.pageSize"
(numPages)="totalPagesCount$.next($event)"
[maxSize]="5"
[boundaryLinks]="false"
previousText="Previous"
nextText="Next"
></pagination>
</div>
</div>
</div>