MaDataGrid
@amn31/ma-data-grid is an Angular component for presenting large and complex data. The table was designed to be flexible and light package. The features regarding "filtering", "sorting" or "paging" are already available without creating additional code.
@amn31/ma-data-grid is running in one of 2 modes: "pagination enabled", reserved for large databases (more than 10,000 records). In that latest mode, the sorting, paging and offset features are assigned to the database server (backend). Otherwise, in the "pagination disabled", the sorting, offset, and paging features are controlled by @amn31/ma-data-grid
It was built for modern browsers using TypeScript, CSS3 and HTML5 and Angular 10.0.0.
See demo for more information!
Demo
https://amn31.github.io/ma-data-grid/
You can download demo project ma-data-grid-demo.zip then run command
npm install
ng serve
Installation
Install package
npm install @amn31/ma-data-grid
Add MaDataGridModule module in file app.module.ts
imports: [
MaDataGridModule,
...
],
Add google fontes in file "index.html"
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
Add materialize.css in file "angular.json"
architect": {
"build": {
"options": {
"styles": [
...
"node_modules/materialize-css/dist/css/materialize.css",
"src/styles.css"
],
"scripts": [
...
"node_modules/jquery/dist/jquery.min.js",
"node_modules/materialize-css/dist/js/materialize.min.js"
]
}
How to use ?
Example of our data for following examples
[
{
"emplacement": "Exterieur",
"commune": "CHARLEVILLE-MEZIERES",
"localisation": "FONTAINE CHARLES DE GONZAGUE",
"liaison": "Mesh",
"code_insee": "08105",
"internet": "VDSL",
"densite": "Normale",
"modele": "T300",
"address": "Cours Briand",
"usage": 24,
"timestamp": "2019-12-16",
"lat": 4.719361,
"lng": 49.770574,
"id": 25,
"isNew": null
},
{
"emplacement": "Exterieur",
"commune": "Damouzy",
"localisation": "RUE BOURBON / RUE DU THEATRE",
"liaison": "Fibre",
"code_insee": "08105",
"internet": "VDSL",
"densite": "Normale",
"modele": "T300",
"address": "Rue Bourbon",
"usage": 22,
"timestamp": "2018-11-27",
"lat": 4.7179,
"lng": 49.772291,
"id": 49,
"isNew": true
}
]
Use "pagination disabled ([pagination]="false")"
In the "pagination disabled" data are stored in memory and @amn31/ma-data-grid will manage the most of things.
In component.html, add HTML code
<!--
[pagination]="false" pagination mode is set to FALSE
[columns]="columns" define columns to display
[headFilter]="true" the header filter will be shown
(filterChange)="" Method which will receive filtering event
[limit]="10" Max number of rows to display
[canSelect]="{row|cell}" Define if row or cell can be selected
(select)="" Method which will receive selecting event
[rows]="rows" complete data to display
-->
<ma-data-grid #datagrid
[pagination]="false"
[columns]="columns"
[rows]="rows"
[limit]="10"
canSelect="cell" (select)="SelectRowOrCell($event)"
[headFilter]="true" (filterChange)="updateFilter($event)" >
</ma-data-grid>
In component.ts
Define all columns display
columns: MaDataGridColumnOptions[] = [
{ prop: 'id', // Propertie to display
title: 'Id', // Column title
sorted: true, // Column can be sorted
dataType: 'number' // Data type regarding the propertie
},
{ prop: 'commune',
title: 'City', sorted: true, dataType: 'string',
cssClass: 'td_small' // cssClass to use to display each cell
canEdit: false // If you want to change the data use:
// canEdit: true
},
{
prop: 'usage', title: 'Usage', sorted: true, dataType: 'number',
useTemplate: CellUsageComponent // It's possible to use another component to display cell
// that component must implement MaDataGridCell
},
{ prop: 'isNew', title: 'New', sorted: true, dataType: 'boolean' },
{ prop: 'lng', title: 'Lng', sorted: true, dataType: 'number' },
{ prop: 'timestamp', title: 'Recorded', sorted: true, dataType: 'date' },
{ prop: 'liaison', title: 'Link', sorted: true,
isHTML: true // the value can be display using DOM HTML
},
{ prop: 'internet', title: 'Type', sorted: true,
pipe: this.formatData // A method can be used to change the data value
},
{ prop: 'internet', title: 'Internet', sorted: true,
/* In order to filter some data,
We can define specific filter with "headFilter" based on this model
{ value: ...
operator: ...
label: ...
}
*/
headFilter: [
{ value: 'VDSL', operator: '=', label: 'vdsl' },
{ value: 'ADSL', operator: '=', label: 'adsl' }
]
},
//
{ prop: 'modele', title: 'Model', sorted: true, extFilter: true, extFilterSelected: false }
];
Load data
ngOnInit() {
// From a service, we load data in variables this.rows
this.wifiPointsService.getAllPoints().then((data: []) => {
this.rows = data;
// Create header filters for 'Model' and 'Link'
// according to datas found
this.autoHeaderFilter('modele');
this.autoHeaderFilter('liaison');
})
}
Create header filter according to datas found
According to the distinct values found in datas the headFilter can be create automaticaly
autoHeaderFilter(field: string) {
this.wifiPointsService.getDistinctValues(field).then((values: any) => {
console.log('MaDataGridHeadFilter ' + field, values);
let d: MaDataGridHeadFilter[] = []
values.forEach(element => {
d.push({ value: element, operator: '=', label: element });
});
this.columns.find(elem => elem.prop === field).headFilter = d;
})
}
How change data displayed with 'pipe'
Above we see pipe was setted to this.formatData
formatData(value, row:any, col): string {
//console.log("formatData "+ row[col.prop], col)
/*
if (col.prop == 'internet' && 'ADSL' == value) {
return '('+row['internet']+')';
}*/
if (value)
return value.split('')[0];
}
How detect selecting
SelectRowOrCell(event: MaDataGridSelectEvent) {
console.log('SelectRowOrCell', event);
// Reset selection after few seconds
setTimeout(() => {
this.datagrid.resetSelection();
}, 5000)
}
How detect change on header filter
When filter is changed, the new data are provided by event
updateFilter(event: MaDataGridHeadFilterEvent) {
console.log('updateFilter', event.where);
console.log("DATA HEADER FILTER", event.data)
}
In component.css
In column 'City', we have used cssClass so, the associated value have to be declared
/deep/ .td_small {
font-size: xx-small;
background-color: yellow;
}
Template (CellUsageComponent)
In column 'Usage', we have used useTemplate so, the defined component must exist
import { Component, Input, Type } from '@angular/core';
import { MaDataGridCell } from '@amn31/ma-data-grid'
@Component({
selector: 'app-cell-usage',
template: `<div>
<small> {{data.usage}}% </small>
<div class="cell_usage" style="{{style}}"> </div>
</div>`,
styleUrls: ['./cell-usage.component.css']
})
export class CellUsageComponent implements MaDataGridCell {
style: string = '';
@Input() data: any;
@Input() prop: string;
@Input() col: MaDataGridColumnOptions;
constructor(private datagrid:MaDataGridComponent) {
}
ngOnInit(): void {
// this.data represents the value of the current row
this.style = "width: "+this.data.usage+'px;';
}
}
Use "pagination enabled ([pagination]="true")"
In the "pagination enabled" data have to be manages by the backend server and @amn31/ma-data-grid have to send parameters of filtering, ordering, paging and offset.
In component.html, add HTML code
<!--
[pagination]="true" pagination mode is set to TRUE
[columns]="columns" define columns to display
[headFilter]="true" the header filter will be shown
[limit]="10" Max number of rows to display
[canSelect]="{row|cell}" Define if row or cell can be selected
(select)="" Method which will receive selecting event
# The component code must be manage paging, sorting, filtering, offset and counter
[rows]="rows" represents the data to display of the current page
(filterChange)="updateFilter($event)" To detect change of header filter
[count]="counter" represents the total records of filtered datas
[page]="page" represents the current page
(changePage)="changePage($event)" Method which will called when page is setted
(sort)="sortBy($event)" Method which will called when user will sort a column
If you want Edit :
- Remove [canSelect]=
Then you can add:
(rowsChange)="rowsChange($event)" Method which will called when data are changed
(rowsSelect)="rowsSelect($event)" Method which will called when selector is changed (Cf: 'selector')
-->
<ma-data-grid #datagrid
[pagination]="true"
[count]="counter"
[page]="page"
(changePage)="changePage($event)"
(sort)="sortBy($event)"
[columns]="columns"
[rows]="rows"
[limit]="10"
canSelect="row" (select)="SelectRowOrCell($event)"
[headFilter]="true" (filterChange)="updateFilter($event)" >
</ma-data-grid>
In component.ts
Define all columns display
See below how set columns
columns: MaDataGridColumnOptions[] = [
...
]
Load data
The most complicate part is here because you have to manage data according to parameters. This step must be executed when component is initialized but also when user edit the header filter, or change the sort of columns.
ngOnInit() {
console.log("ngOnInit");
/* Initialize datas on start */
this.loadPage(0);
// Create header filters for 'Model' and 'Link'
// according to datas found (like below)
this.autoHeaderFilter('modele');
this.autoHeaderFilter('liaison');
}
/**
* Load data the current page according to parameters:
*
* page: The current page to display
* limit: Max number of rows to display in the datagrid
* where: It's the where clause used according to the header Filter of the datagrid
* here the method updateFilter() will be used
* sort: sort is used to sort the data according to that example of query {field: 'id', reverse: true}
* here the method sortBy() will be used
*
* @param {number} page
*/
loadPage(page:number) {
console.log("this._loadPage()");
this.wifiPointsService.getPoints({
where: this.where,
offset: page * this.limit,
limit: this.limit,
sort: this.sort // Example: {field: 'id', reverse: true}
}).then((data: any) => {
// We see the content of datagrid is completed here
// with
// - this.rows all rows of current page
// - this.page current page [0-n]
// - this.counter number of records
this.rows = data.rows;
this.page = page;
this.counter = data.count;
console.log('loadPage DATA ============================= ', data);
})
}
/**
* When user uses header filter this method is called
*
* @param {MaDataGridHeadFilterEvent} event contains where a element of type FilterConditions
* See https://www.npmjs.com/package/@amn31/filter-multiple-conditions
* https://www.npmjs.com/package/@amn31/convert2sequelize
*
*/
updateFilter(event: MaDataGridHeadFilterEvent) {
console.log('updateFilter', event.where);
this.where = event.where;
this.loadPage(0);
}
/**
* When user sort column this method is called
*
* @param {MaDataGridFilterEvent} event contains for example: {field: 'id', reverse: true}
* @memberof Test2Component
*/
sortBy(event: MaDataGridFilterEvent) {
console.log('sortBy event', event);
this.sort = event;
this.loadPage(this.page);
}
/**
* Call when data are changed
* @param event event
*/
rowsChange(event) {
console.log('EFFECTIVE rowsChange', event);
}
/**
* Call when column(s) are select with the dataType 'selector'
* @param event Event
*/
rowsSelect(event) {
if (!event.length) {
console.log('LINE SELECTOR ',event)
} else {
console.log('LINE(S) SELECTOR ',event)
}
}
Use the External filter (Extra)
A specific component for filtering can be used, to see it go to https://amn31.github.io/ma-data-grid/. That component can be used for specific columns in order to filter the current page or more...
In component.html, add directives "extFilter"
<!--
[extFilter]="true" if "true" external filter is shown
(extFilterChange)="extUpdateFilter($event)" On change on the external filter that method receive the events
-->
<ma-data-grid #datagrid
...
[extFilter]="true"
(extFilterChange)="extUpdateFilter($event)"
>
</ma-data-grid>
In component.ts
Define columns which will use external filter by adding "extFilterSelected"
columns: MaDataGridColumnOptions[] = [
/* EDIT DATA:
For use selectiong line, use dataType: 'selector'
{ prop: '_selected', dataType: 'selector' },
// Edit Lat
{ prop: 'lat', title: 'Lat', dataType: 'number', canEdit: true };
*/
{ prop: 'commune',
title: 'City', sorted: true, ...
extFilterSelected: true // allow externe filtering for that property 'City'
},
{ prop: 'isNew', title: 'New', sorted: true, dataType: 'boolean' },
{ prop: 'lng', title: 'Lng', sorted: true, dataType: 'number', extFilterSelected: true },
{ prop: 'timestamp', title: 'Recorded', sorted: true, dataType: 'date' },
...
How detect change on external filter
When external filter is changed, the value to search and fields are provided. The below example makes a basic search in the current page
/**
* Basic filter used for external filter
*
* @param {MaDataGridFilterEvent} event
*/
extUpdateFilter(event: MaDataGridFilterEvent) {
console.log('extUpdateFilter', event);
// Value du search
const val = event.text.toLowerCase();
// filter on data and for each provided fields (Ex:[ 'id', 'City' ])
const temp = this.temp.filter(function (d) {
if (!val) {
return true;
}
for (var f of event.fields) {
if (d[f] != null && d[f].toString().toLowerCase().indexOf(val) !== -1)
return true;
}
return false;
});
// update the rows
this.rows = temp;
console.log("DATA EXTERNAL FILTER ", this.rows)
}