File

core/data-grid/data-grid.component.ts

Implements

DataGrid OnChanges AfterViewInit ProductExperienceEventSource

Metadata

Index

Properties
Methods
Inputs
Outputs
Accessors

Constructor

constructor(configurationStrategy: DataGridConfigurationStrategy, dataGridService: DataGridService, sanitizer: DomSanitizer, gainsightService: GainsightService, bsModalService: BsModalService, alertService: AlertService, actionControlsService: ActionControlsExtensionService, route: ActivatedRoute)
Parameters :
Name Type Optional
configurationStrategy DataGridConfigurationStrategy No
dataGridService DataGridService No
sanitizer DomSanitizer No
gainsightService GainsightService No
bsModalService BsModalService No
alertService AlertService No
actionControlsService ActionControlsExtensionService No
route ActivatedRoute No

Inputs

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.

expandableRows
Type : "NONE" | "SYNC" | "ASYNC"
Default value : 'NONE'

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
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.

Outputs

itemsSelect
Type : EventEmitter

Emits an event when items selection changes. The array contains keys of selected items (key property is defined by selectionPrimaryKey).

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.

Methods

applyFilter
applyFilter(columnName, dropdown, filteringModifier)
Parameters :
Name Optional
columnName No
dropdown No
filteringModifier No
Returns : void
cancel
cancel()
Returns : void
changeSelectedItem
changeSelectedItem(item: any)
Parameters :
Name Type Optional
item any No
Returns : void
changeSortOrder
changeSortOrder(columnName)
Parameters :
Name Optional
columnName No
Returns : void
clearFilters
clearFilters(reload)
Parameters :
Name Optional Default value
reload No true
Returns : void
clickReload
clickReload()
Returns : void
collapse
collapse(row: Row)
Parameters :
Name Type Optional
row Row No
Returns : void
emitConfigChange
emitConfigChange(eventType?: GridEventType)
Parameters :
Name Type Optional
eventType GridEventType Yes
Returns : void
expand
expand(row: Row)
Parameters :
Name Type Optional
row Row No
Returns : any
getCellRendererSpec
getCellRendererSpec(undefined)
Parameters :
Name Optional
No
Returns : CellRendererSpec
getFilteringFormRendererSpec
getFilteringFormRendererSpec(undefined)
Parameters :
Name Optional
No
getHeaderCellRendererSpec
getHeaderCellRendererSpec(undefined)
Parameters :
Name Optional
No
Returns : CellRendererSpec
handleClick
handleClick(row: Row)
Parameters :
Name Type Optional
row Row No
Returns : void
isColumnFilteringApplied
isColumnFilteringApplied(column: Column)
Parameters :
Name Type Optional
column Column No
Returns : boolean
isDropDownPlacedRight
isDropDownPlacedRight(column: Column)
Parameters :
Name Type Optional
column Column No
Returns : boolean
isItemSelected
isItemSelected(item)
Parameters :
Name Optional
item No
Returns : any
loadNextPage
loadNextPage()
Returns : Promise<IResultList<object>>
ngAfterViewInit
ngAfterViewInit()
Returns : void
ngOnChanges
ngOnChanges(event)
Parameters :
Name Optional
event No
Returns : void
ngOnDestroy
ngOnDestroy()
Returns : void
ngOnInit
ngOnInit()
Returns : void
onColumnDrop
onColumnDrop(undefined)
Parameters :
Name Optional
No
Returns : void
openCustomColumnModal
openCustomColumnModal()
Returns : void
reload
reload(redirect)
Parameters :
Name Optional Default value
redirect No true
Returns : void
Async removeCustomColumn
removeCustomColumn(poConfirm: PopoverConfirmComponent, column: Column, ddConfigureColumns: BsDropdownDirective)
Parameters :
Name Type Optional
poConfirm PopoverConfirmComponent No
column Column No
ddConfigureColumns BsDropdownDirective No
Returns : any
Async removeFilter
removeFilter(filter: Partial)
Parameters :
Name Type Optional
filter Partial<FilterChip> No
Returns : any
resetFilter
resetFilter(columnName, dropdown)
Parameters :
Name Optional
columnName No
dropdown No
Returns : void
resolveCellValue
resolveCellValue(row, path)
Parameters :
Name Optional
row No
path No
Returns : any
setAllItemsInCurrentPageSelected
setAllItemsInCurrentPageSelected(selected)
Parameters :
Name Optional
selected No
Returns : void
setAllItemsSelected
setAllItemsSelected(selected)
Parameters :
Name Optional
selected No
Returns : void
setColumns
setColumns(config: GridConfig)
Parameters :
Name Type Optional
config GridConfig No
Returns : void
setExpandableRowVisible
setExpandableRowVisible(row: Row, success: boolean)
Parameters :
Name Type Optional
row Row No
success boolean No
Returns : void
setItemsSelected
setItemsSelected(items: any, selected)
Parameters :
Name Type Optional
items any No
selected No
Returns : void
setPageSize
setPageSize(config: GridConfig)
Parameters :
Name Type Optional
config GridConfig No
Returns : void
trackByName
trackByName(index, item)
Parameters :
Name Optional
index No
item No
Returns : any
triggerEvent
triggerEvent(eventData)
Parameters :
Name Optional
eventData No
Returns : void
updateFiltering
updateFiltering(columnNames: string[], action: literal type, reload)
Parameters :
Name Type Optional Default value
columnNames string[] No
action literal type No
reload No true
Returns : void
updateFilteringApplied
updateFilteringApplied()
Returns : void
updateGridColumnsSize
updateGridColumnsSize()
Returns : void
updatePagination
updatePagination(undefined)
Parameters :
Name Optional
No
Returns : void
updateSorting
updateSorting(columnNames: string[], sortOrder: SortOrder)
Parameters :
Name Type Optional
columnNames string[] No
sortOrder SortOrder No
Returns : void
updateThEls
updateThEls()
Returns : void

Properties

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()
@Inject(DATA_GRID_CONFIGURATION_STRATEGY)
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)

Accessors

_rows
set_rows(rows: Row[])

The list of rows to be displayed in the grid (used for client side data).

Parameters :
Name Type Optional
rows Row[] No
Returns : void
_pagination
set_pagination(pagination: Pagination)

Pagination settings, e.g. allows for setting current page or page size.

Parameters :
Name Type Optional
pagination Pagination No
Returns : void
_infiniteScroll
set_infiniteScroll(infiniteScroll: LoadMoreMode)

Sets load more mode.

Parameters :
Name Type Optional
infiniteScroll LoadMoreMode No
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 :
Name Type Optional
serverSideDataCallback ServerSideDataCallback No
Returns : void
_selectable
set_selectable(selectable: boolean)

Determines whether items can be selected by clicking a checkbox in the first column.

Parameters :
Name Type Optional
selectable boolean No
Returns : void
_singleSelection
set_singleSelection(singleSelection: boolean)

Restricts selection to a single row only. Selection column displays radio button instead of checkboxes

Parameters :
Name Type Optional
singleSelection boolean No
Returns : void
_selectionPrimaryKey
set_selectionPrimaryKey(selectionPrimaryKey: string)

Determines which item's property will be used to distinguish selection status.

Parameters :
Name Type Optional
selectionPrimaryKey string No
Returns : void
_displayOptions
set_displayOptions(displayOptions: DisplayOptions)

Sets display options.

Parameters :
Name Type Optional
displayOptions DisplayOptions No
Returns : void
_actionControls
set_actionControls(actionControls: ActionControl[])

Sets action controls (actions available for individual items).

Parameters :
Name Type Optional
actionControls ActionControl[] No
Returns : void
_bulkActionControls
set_bulkActionControls(bulkActionControls: BulkActionControl[])

Sets bulk action controls (actions available for items selected by user).

Parameters :
Name Type Optional
bulkActionControls BulkActionControl[] No
Returns : void
_headerActionControls
set_headerActionControls(headerActionControls: HeaderActionControl[])

Sets header action controls (actions available from data grid header).

Parameters :
Name Type Optional
headerActionControls HeaderActionControl[] No
Returns : void

Data grid component

This component is used to present a data set in a grid of columns and rows.

Labels

You can set:

  • the title displayed at the top of the grid (title)
  • the labels for load more button (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>

Visual settings

The component allows to control some visual settings via displayOptions:

Example :
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).

Empty state

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.:

Example :
<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>

Columns

You can define columns by passing a list of objects compliant with Column interface. The most basic definition contains:

  • name - the name of the column
  • header - the header text for the column
  • path - the path in item's object from where the value should be taken

There 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:

Example :
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:

Example :
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:

Example :
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:

Example :
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

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 applied
  • filteredSize: the number of items that match current filters

Let'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:

Example :
/** 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:

Example :
/** 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:

Server-side data grid example

Row actions

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)
  }
];

Infinite scroll / "Load more" button

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 decide
  • none: doesn't perform any load more action
  • hidden: loads more data automatically but with no visible button for the user

Event emitters

The 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>

results matching ""

    No results matching ""