@rangertechnologies/ngnxt
Version:
This library was used for creating dymanic UI based on the input JSON/data
883 lines • 437 kB
JavaScript
import { Component, ViewChild, Input, Output, EventEmitter, HostListener } from '@angular/core';
import { MatSort } from '@angular/material/sort';
import { CommonModule } from '@angular/common';
import { SelectionModel } from '@angular/cdk/collections';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NxtButtonComponent } from '../button/nxt-button.component';
import { NxtPagination } from '../pagination/pagination.component';
import { SearchFilterPipe } from '../../pipe/search-filter/search-filter.pipe';
import { DatePipe } from '../../pipe/date/date.pipe';
import { TimePipe } from '../../pipe/time/time.pipe';
import { EditColumnCheckPipe } from '../../pipe/editColumnCheck/edit-column-check.pipe';
import { EditColumnDropdownPipe } from '../../pipe/editColumnDropdown/edit-column-dropdown.pipe';
import { EditColumnTypePipe } from '../../pipe/editColumnType/edit-column-type.pipe';
import { MatTooltipModule } from '@angular/material/tooltip';
import { GetValuePipe } from '../../pipe/get-value.pipe';
import * as i0 from "@angular/core";
import * as i1 from "../../services/data.service";
import * as i2 from "../../services/change.service";
import * as i3 from "@angular/common";
import * as i4 from "@angular/forms";
import * as i5 from "@angular/material/tooltip";
import * as i6 from "@angular/cdk/bidi";
export class NxtDatatable {
renderer;
dataService;
changeService;
data = []; // get data that to be displayed in a array
columns = []; // Columns have a label, a column type, and filter details.
withCheckBox; // get if with or without checkbox in table
searchBar; // if it is true, the search bar will be displayed on table
tableSaveButton = false; // save button for editing LOP in payroll table, changed name from 'saveLOP'
stickyColumn = false; // if the last column should be sticky, it should be sent as boolean value
tableWidth = '100%'; // width of table can be adjusted from parent, by default it is 100%
actionColumHeader = 'Action'; // to change the header for action column
actionButton; //to show button colum as action
title; // the title for the table;
isButtons; // is button present in table;
buttonArray; // buttons to be displayed;
tableId; // id of table
isEditRow; // if it is true, the row can be edited
isDeleteRow; // if it is true, the row can be deleted
addInlineRecord; // if it is true, the row can be added inline
searchConfigs; // search configurations
direction = 'ltr'; // direction of table
pagination; // pagination configurations
actionButtonArray; // action button configurations
multipleFilter; // if pagination true then this will be used
isPagination; // pagination configurations if true then pagination will be enabled
isNosIndicator; // if true then no data indicator will be shown
isEditable; // if true then table will be editable
from; // from date paltform based initialize inputs ex:- 'formBuilder'
question; // question is used for nxt form builder table preview
rowTextSize; // size of row text
rowTextColor; // color of row text
apiMeta; // api meta is used for nxt form builder table preview
summaryRows; // Input for configurable summary rows
summaryColumns; // Input for summary columns config
isLoading; //SKS28MAR25 Input for loading indicator
tableConfig; // table config for nxt form builder table
tableRowClick = new EventEmitter; // datas to be passed when table row is clicked
onEditData = new EventEmitter; // if edit button is clicked, the data of that row will be passed
saveButtonData = new EventEmitter; // edited rows data will be passed
onDeleteData = new EventEmitter; // when delete button clicked pass the data
buttonEmit = new EventEmitter; // this will emit a table top button click event
hyperLinkEmit = new EventEmitter; // hyper link click event
sideNavEmit = new EventEmitter; // rightnav column emit
actionButtonEmit = new EventEmitter; // datas to be passed when table row active button is clicked
columnSelected = new EventEmitter();
removeColumn = new EventEmitter();
valueChange = new EventEmitter();
selectedValues = new EventEmitter(); // to pass the selected checkbox values
fileEmit = new EventEmitter(); // to pass the file
NxtTableEmit = new EventEmitter;
// {
// "pagination": { // 4
// "pageSize": 10,
// "pageIndex": 10
// },
// "tableSearch": { // 1
// "fields" : [
// "lightType",
// "system",
// "status"
// ] ,
// "value": 'r-001'
// },
// "searchFilterData": { // 2
// "lightType": [
// "AA",
// "BB"
// ]
// },
// "sort": { // 3
// "column": "assetCode",
// "direction": "asc"
// }
// }
sort;
tableContainer;
selection; // to get the selected row
stickyCondition; // to get the sticky column index
searchFilter = false; // to get the search filter
filterArray = []; // to get the filter array
selectedFilter; // to get the selected filter
renderColumns = 'displayedColumns'; // to get the rendered columns
pageSizeOptions; // for pagination get the page size options
dataSource; // for table data source
totalCount; // count of total records
searchBoxValue; // to get the search box value
prevSearch; // to get the previous search value
activeColumn = null; // Track the active column
isScrolled = false; // Boolean flag to track scroll state
isShadowHidden = false; // Boolean flag to track shadow state
currentSortColumn = ''; // to get the current sort column
currentSortDirection = 'none'; // to get the current sort direction
originalData; // Store original data for sorting
hoveredColumn = null; // Track the hovered column
searchText; // to get the search text
multipleFilterArray; // to get the multiple filter array
filterDataArray = {}; // to get the filter data array
isResized = false; // for table column resize
sFilterData; // to get the search filter data
headerLabels = []; // to get the header labels
displayedColumns = []; // to get the displayed columns
filterColumns = []; // to get the filter columns
sortColumns = []; // to get the filter columns
hyperLinkColumns = []; // to get the hyper link columns
editColumn = []; // to get the edit column
dateColumns = []; // to get the date columns
timeColumns = []; // to get the time columns
objectColumns = []; // to get the object columns
fileColumns = []; // to get the file columns
booleanColumns = []; // to get the boolean columns
inlineElement; // for add a new row on table
pageSize; // for pagination get the page size
pageIndex; // for pagination get the page index
totalRecords; // for pagination get the total records
configPagination = false; // flag for config pagination
deleteData; // to get the delete row
deleteModal = false; // for conformation popup
dropdownActionButton; // for action column dropdown button
currentOpenIndex = null; // track the current open action dropdown icon index
clickListener; // action column dropdown button click listener for close a dropdown button
selectedColumn = null; // to get the selected column
subscription; // for table data subscription
deleteIndex; // to get the delete index
showPopover = true;
summaryValues = {}; // SKS20MAR25 Object to store summary row values
isSummaryColumn = false; // SKS20MAR25 Flag to check if the column is in the displycolumns array
isSummaryStartColumn;
isSummaryEndColumn;
constructor(renderer, dataService, changeService) {
this.renderer = renderer;
this.dataService = dataService;
this.changeService = changeService;
}
ngOnChanges(changes) {
this.dataSource = { data: [] }; // or use a proper data structure
// console.log("ngOnChanges is running")
if (this.from === 'formBuilder') {
this.data = this.question?.input ? this.question?.input : [];
const parsedMeta = typeof this.question['fieldsMeta'] === 'object' ? this.question['fieldsMeta'] : JSON.parse(this.question['fieldsMeta']);
if (parsedMeta === null || parsedMeta === undefined || !Array.isArray(parsedMeta) || parsedMeta?.length === 0) {
console.warn('No valid metadata provided');
return;
}
if (this.tableConfig) {
const config = this.tableConfig;
this.isNosIndicator = config?.isNosIndicator !== false;
this.addInlineRecord = config.addInlineRecord !== false;
this.isPagination = config.isPagination !== false;
this.actionButton = config.actionButton !== false;
this.isDeleteRow = config.isDeleteRow !== false;
this.isEditRow = config.isEditRow !== false;
this.searchBar = config.searchBar !== false;
this.isButtons = config.isButtons !== false; //summaryColumn
this.pageSize = config.itemsPerPage || 5;
}
this.headerLabels = parsedMeta
.filter(column => column.summaryColumn !== true && column.summaryRow !== true && column.label) // Exclude only when explicitly true
.map(column => column.label);
this.displayedColumns = parsedMeta
.filter(column => column.summaryColumn !== true && column.summaryRow !== true && column.apiName) // Exclude only when explicitly true
.map(column => column.apiName);
this.filterColumns = parsedMeta.filter(column => column.filter === true).map(column => column.apiName);
this.sortColumns = parsedMeta.filter(column => column.sort === true).map(column => column.apiName);
this.hyperLinkColumns = parsedMeta.filter(column => column.hyperLink === true).map(column => column.apiName);
this.editColumn = parsedMeta.filter(column => column.readOnly === false).map(column => column.apiName);
this.dateColumns = parsedMeta.filter(column => column.type === 'Date').map(column => column.apiName);
this.timeColumns = parsedMeta.filter(column => column.type === 'Time').map(column => column.apiName);
this.objectColumns = parsedMeta.filter(column => column.type === 'Object').map(column => column.apiName);
this.fileColumns = parsedMeta.filter(column => column.type === 'File').map(column => column.apiName);
this.booleanColumns = parsedMeta.filter(column => column.type === 'Boolean').map(column => column.apiName);
this.summaryColumns = parsedMeta
.filter(column => column.summaryColumn === true)
.map(({ apiName, fldType, ...rest }) => ({
fieldName: apiName,
type: fldType,
...rest
}));
this.summaryRows = parsedMeta
.filter(column => column.summaryRow === true)
.map(({ apiName, fldType, ...rest }) => ({
fieldName: apiName,
type: fldType,
...rest
}));
}
// Ensure `this.data` is an array
if (!this.data || !Array.isArray(this.data)) {
console.warn('Data is not initialized or is not an array');
this.data = []; // Initialize as an empty array if it's not valid
}
// Ensure `this.dataSource.data` is initialized
//SKS15FEB25 Initialize this in your data fetch method
this.originalData = [...this.data];
this.sFilterData = [...this.data];
this.dataSource.data = this.originalData;
this.pageSize = this.pagination?.pageSize ? this.pagination['pageSize'] : 10;
this.pageIndex = this.pagination?.pageIndex ? this.pagination['pageIndex'] : 1;
this.totalRecords = this.pagination?.totalRecords ? this.pagination['totalRecords'] : undefined;
this.pageSizeOptions = this.pagination?.pageSizeOptions ? this.pagination['pageSizeOptions'] : ['5', '10', '25', '50', '100', '200'];
this.pagination === undefined ? this.configPagination = false : this.configPagination = true;
this.isPagination === undefined || this.isPagination === true ? this.isPagination = true : this.isPagination = false;
this.isNosIndicator === undefined || this.isNosIndicator === true ? this.isNosIndicator = true : this.isNosIndicator = false;
this.isEditable === undefined || this.isEditable === false ? this.isEditable = false : this.isEditable = true;
this.headerLabels = this.from !== 'formBuilder' ? this.columns.map(column => column.label) : this.headerLabels;
this.displayedColumns = this.from !== 'formBuilder' ? this.columns.map(column => column.fieldName) : this.displayedColumns;
// SKS20MAR25 Check if the displycolumn's have a summary column
if (this.summaryColumns && this.summaryColumns.length > 0) {
this.displayedColumns = [...this.displayedColumns, ...this.summaryColumns.map(column => column.fieldName)];
this.headerLabels = [...this.headerLabels, ...this.summaryColumns.map(column => column.label)];
}
if (this.summaryRows && this.summaryRows.length > 0 && this.displayedColumns.length > 0) {
this.isSummaryColumn = this.displayedColumns.some(element => element === this.summaryRows[0].column);
if (this.isSummaryColumn) {
const index = this.displayedColumns.indexOf(this.summaryRows[0].column);
if (index !== -1) {
this.isSummaryStartColumn = this.displayedColumns.slice(0, index - 1);
this.isSummaryEndColumn = this.displayedColumns.slice(index + 1);
}
}
}
this.filterColumns = this.from !== 'formBuilder' ? this.columns.filter(column => column.filter === true).map(column => column.fieldName) : this.filterColumns;
this.sortColumns = this.from !== 'formBuilder' ? this.columns.filter(column => column.sort === true).map(column => column.fieldName) : this.sortColumns;
this.hyperLinkColumns = this.from !== 'formBuilder' ? this.columns.filter(column => column.hyperLink === true).map(column => column.fieldName) : this.hyperLinkColumns;
this.editColumn = this.from !== 'formBuilder' ? this.columns.filter(column => column.edit === true).map(column => column.fieldName) : this.editColumn;
this.dateColumns = this.from !== 'formBuilder' ? this.columns.filter(column => column.fldType === 'date').map(column => column.fieldName) : this.dateColumns;
this.timeColumns = this.from !== 'formBuilder' ? this.columns.filter(column => column.fldType === 'time').map(column => column.fieldName) : this.timeColumns;
this.objectColumns = this.from !== 'formBuilder' ? this.columns.filter(column => column.fldType === 'object').map(column => column.fieldName) : this.objectColumns;
this.fileColumns = this.from !== 'formBuilder' ? this.columns.filter(column => column.fldType === 'file').map(column => column.fieldName) : this.fileColumns;
this.booleanColumns = this.from !== 'formBuilder' ? this.columns.filter(column => column.fldType === 'boolean').map(column => column.fieldName) : this.booleanColumns;
this.inlineElement = Object.fromEntries(this.displayedColumns.map(key => [key, ""]));
this.dropdownActionButton = (this.actionButtonArray?.buttonArray && this.actionButtonArray?.buttonArray.length > 0) ? this.actionButtonArray?.buttonArray.slice(this.actionButtonArray?.size) : [];
this.selection = new SelectionModel(true, []);
if (!this.totalRecords && this.data && this.isPagination) {
this.dataSource.data = this.data?.slice(0, 10);
}
else if (!this.isPagination) {
this.dataSource.data = this.data;
}
setTimeout(() => {
this.dataSource.sort = this.sort;
}, 100);
this.totalCount = this.totalRecords || this.data?.length;
if (this.stickyColumn === true && this.displayedColumns) {
this.stickyCondition = this.displayedColumns?.length;
}
else {
this.stickyCondition = this.displayedColumns?.length + 1;
}
if (this.withCheckBox && this.actionButton) {
this.renderColumns = 'both';
}
else if (this.withCheckBox) {
this.renderColumns = 'select';
}
else if (this.actionButton) {
this.renderColumns = 'action';
}
if (this.searchConfigs?.searchFilterVal) {
this.searchBoxValue = this.searchConfigs?.searchFilterVal; // Show value in the search box
this.prevSearch = this.searchConfigs?.searchFilterVal;
}
if (this.filterColumns?.length > 0 && !this.configPagination) {
this.multipleFilterArray = {};
const filtObjs = {};
this.filterColumns.forEach((element) => {
this.multipleFilterArray[element] = null;
filtObjs[element] = [];
});
this.data.forEach(element => {
this.filterColumns.forEach((key) => {
if (Object.keys(element).includes(key)) {
filtObjs[key].push(element[key]);
this.multipleFilterArray[key] = (new Set(filtObjs[key]));
}
});
});
this.multipleFilterArray = this.multipleFilterArray;
}
else {
this.multipleFilterArray = this.multipleFilter;
}
setTimeout(() => {
if (this.tableContainer?.nativeElement) {
const tableBody = this.tableContainer.nativeElement.querySelector('.table-body');
if (tableBody && this.isPagination) {
this.isShadowHidden = !(tableBody.scrollHeight < 450);
}
}
}, 1500); // Waits 100ms for rendering
// SKS20MAR25 Initialize summaryValues for input rows and compute values
if (changes['summaryRows'] || changes['summaryColumns'] || changes['data']) {
this.computeSummaryColumns(); // Compute per-row summary columns first
this.summaryRows?.forEach(row => {
if (row.type === 'input' && this.summaryValues[row.fieldName] === undefined) {
this.summaryValues[row.fieldName] = 0; // Default to 0 for inputs
}
});
this.computeSummaryValues();
}
}
ngOnInit() {
this.dataSource = { data: [] }; // or use a proper data structure
// SKS13MAR25 get data from question if from formBuilder
if (this.from === 'formBuilder') {
this.data = this.question?.input ? this.question?.input : [];
const parsedMeta = typeof this.question['fieldsMeta'] === 'object' ? this.question['fieldsMeta'] || [] : JSON.parse(this.question['fieldsMeta']);
if (parsedMeta === null || parsedMeta === undefined || !Array.isArray(parsedMeta) || parsedMeta?.length === 0) {
console.warn('No valid metadata provided');
return;
}
if (this.tableConfig) {
const config = this.tableConfig;
this.isNosIndicator = config?.isNosIndicator !== false;
this.addInlineRecord = config.addInlineRecord !== false;
this.isPagination = config.isPagination !== false;
this.actionButton = config.actionButton !== false;
this.isDeleteRow = config.isDeleteRow !== false;
this.isEditRow = config.isEditRow !== false;
this.searchBar = config.searchBar !== false;
this.isButtons = config.isButtons !== false;
this.pageSize = config.itemsPerPage || 5;
}
this.headerLabels = parsedMeta
.filter(column => column.summaryColumn !== true && column.summaryRow !== true && column.label) // Exclude only when explicitly true
.map(column => column.label);
this.displayedColumns = parsedMeta
.filter(column => column.summaryColumn !== true && column.summaryRow !== true && column.apiName) // Exclude only when explicitly true
.map(column => column.apiName);
this.filterColumns = parsedMeta.filter(column => column.filter === true).map(column => column.apiName);
this.sortColumns = parsedMeta.filter(column => column.sort === true).map(column => column.apiName);
this.hyperLinkColumns = parsedMeta.filter(column => column.hyperLink === true).map(column => column.apiName);
this.editColumn = parsedMeta.filter(column => column.readOnly === false).map(column => column.apiName);
this.dateColumns = parsedMeta.filter(column => column.fldType === 'Date').map(column => column.apiName);
this.timeColumns = parsedMeta.filter(column => column.fldType === 'Time').map(column => column.apiName);
this.objectColumns = parsedMeta.filter(column => column.fldType === 'Object').map(column => column.apiName);
this.fileColumns = parsedMeta.filter(column => column.fldType === 'File').map(column => column.apiName);
this.booleanColumns = parsedMeta.filter(column => column.fldType === 'Boolean').map(column => column.apiName);
this.summaryColumns = parsedMeta
.filter(column => column.summaryColumn === true)
.map(({ apiName, fldType, ...rest }) => ({
fieldName: apiName,
type: fldType,
...rest
}));
this.summaryRows = parsedMeta
.filter(column => column.summaryRow === true)
.map(({ apiName, fldType, ...rest }) => ({
fieldName: apiName,
type: fldType,
...rest
}));
if (this.apiMeta !== undefined) {
let options = [];
// AP-26MAR25 Ensure that this.apiMeta is always an object.
//AP-26MAR25 If it's already an object, keep it as is; otherwise, parse it from a JSON string.
this.apiMeta = typeof this.apiMeta === 'object' ? this.apiMeta : JSON.parse(this.apiMeta);
let apiObj = this.apiMeta;
if (apiObj && apiObj.endpoint) {
this.dataService.apiResponse(apiObj.endpoint)?.subscribe((apiResponse) => {
let responses;
if (apiObj.variable) {
// VD 22May24 - handling multiple child objects
responses = this.dataService.getValue(apiResponse, apiObj.variable);
let results = [];
// HA 19JAN24 To avoid undefined error in console
for (let i = 0; i < responses?.length; i++) {
var resp = responses[i];
results.push(resp);
}
options = results;
}
else { // VD 19JAN24 - if response has value(which is array) only
responses = apiResponse;
options = responses;
}
options = options.map((obj) => ({ ...obj, edit: false }));
this.data = options;
});
}
// VD NOV23 - handle the dependent update for dropdown
let sourceId = apiObj?.sourceQuestionId;
let field = apiObj?.field; // VD 13MAY24 - dynamic field changes
if (sourceId) {
// // VD 10May24 Subscribe for the changes
this.subscription = this.changeService.changeAnnounced$.subscribe((changeValue) => {
if (changeValue != undefined) {
if (changeValue.valueObj != undefined && changeValue.fromQuestionId == apiObj.sourceQuestionId) {
options = options.map((obj) => ({ ...obj, edit: false }));
let item = changeValue.valueObj;
let validItem = true;
// VD 13MAY24 - bind dynamic field
if (this.data.length > 0) {
this.data.forEach(element => {
// VD 26Jun24 - to handle multiple objects
const objElementValue = this.dataService.getValue(element, field);
const objItemValue = this.dataService.getValue(item, field);
if (objElementValue == objItemValue) {
validItem = false;
}
});
}
//RS 14FEB2025
//Update Pagination before emit
if (validItem) {
if (this.data.length > 0) {
this.data = [...this.data, item];
}
else {
this.data.push(item);
}
this.data = [...this.data];
this.emitTableDataValue(this.data);
// SKS20MAR25 Manually trigger ngOnChanges
this.ngOnChanges({});
}
}
this.changeService.confirmChange(apiObj?.sourceQuestionId);
}
});
}
}
}
this.searchBar === undefined || this.searchBar === true ? this.searchBar = true : this.searchBar = false;
this.withCheckBox === undefined || this.withCheckBox === false ? this.withCheckBox = false : this.withCheckBox = true;
this.isEditRow === undefined || this.isEditRow === false ? this.isEditRow = false : this.isEditRow = true;
this.isDeleteRow === undefined || this.isDeleteRow === false ? this.isDeleteRow = false : this.isDeleteRow = true;
this.actionButton === undefined || this.actionButton === false ? this.actionButton = false : this.actionButton = true;
this.isButtons === undefined || this.isButtons === false ? this.isButtons = false : this.isButtons = true;
this.addInlineRecord === undefined || this.addInlineRecord === false ? this.addInlineRecord = false : this.addInlineRecord = true;
this.isPagination === undefined || this.isPagination === true ? this.isPagination = true : this.isPagination = false;
this.isNosIndicator === undefined || this.isNosIndicator === true ? this.isNosIndicator = true : this.isNosIndicator = false;
this.isEditable === undefined || this.isEditable === false ? this.isEditable = false : this.isEditable = true;
this.headerLabels = this.from !== 'formBuilder' ? this.columns.map(column => column.label) : this.headerLabels;
this.displayedColumns = this.from !== 'formBuilder' ? this.columns.map(column => column.fieldName) : this.displayedColumns;
if (this.summaryColumns && this.summaryColumns.length > 0) {
this.displayedColumns = [...this.displayedColumns, ...this.summaryColumns.map(column => column.fieldName)];
this.headerLabels = [...this.headerLabels, ...this.summaryColumns.map(column => column.label)];
}
this.filterColumns = this.from !== 'formBuilder' ? this.columns.filter(column => column.filter === true).map(column => column.fieldName) : this.filterColumns;
this.sortColumns = this.from !== 'formBuilder' ? this.columns.filter(column => column.sort === true).map(column => column.fieldName) : this.sortColumns;
this.hyperLinkColumns = this.from !== 'formBuilder' ? this.columns.filter(column => column.hyperLink === true).map(column => column.fieldName) : this.hyperLinkColumns;
this.editColumn = this.from !== 'formBuilder' ? this.columns.filter(column => column.edit === true).map(column => column.fieldName) : this.editColumn;
this.dateColumns = this.from !== 'formBuilder' ? this.columns.filter(column => column.type === 'date').map(column => column.fieldName) : this.dateColumns;
this.timeColumns = this.from !== 'formBuilder' ? this.columns.filter(column => column.type === 'time').map(column => column.fieldName) : this.timeColumns;
this.objectColumns = this.from !== 'formBuilder' ? this.columns.filter(column => column.type === 'object').map(column => column.fieldName) : this.objectColumns;
this.fileColumns = this.from !== 'formBuilder' ? this.columns.filter(column => column.type === 'file').map(column => column.fieldName) : this.fileColumns;
this.booleanColumns = this.from !== 'formBuilder' ? this.columns.filter(column => column.type === 'boolean').map(column => column.fieldName) : this.booleanColumns;
this.inlineElement = Object.fromEntries(this.displayedColumns.map(key => [key, ""]));
this.pageSize = this.pagination?.pageSize ? this.pagination['pageSize'] : 10;
this.pageIndex = this.pagination?.pageIndex ? this.pagination['pageIndex'] : 1;
this.totalRecords = this.pagination?.totalRecords ? this.pagination['totalRecords'] : undefined;
this.pageSizeOptions = this.pagination?.pageSizeOptions ? this.pagination['pageSizeOptions'] : ['5', '10', '25', '50', '100', '200'];
this.pagination === undefined ? this.configPagination = false : this.configPagination = true;
this.dropdownActionButton = this.actionButtonArray?.buttonArray.slice(this.actionButtonArray?.size);
this.originalData = [...this.data];
this.sFilterData = [...this.data];
this.dataSource.data = this.originalData;
if (!this.configPagination && this.data && this.isPagination) {
this.dataSource.data = this.data?.slice(0, 10);
}
else if (!this.isPagination) {
this.dataSource.data = this.data;
}
if (this.filterColumns?.length > 0 && !this.configPagination) {
this.multipleFilterArray = {};
const filtObjs = {};
this.filterColumns.forEach((element) => {
this.multipleFilterArray[element] = null;
filtObjs[element] = [];
});
this.data.forEach(element => {
this.filterColumns.forEach((key) => {
if (Object.keys(element).includes(key)) {
filtObjs[key].push(element[key]);
this.multipleFilterArray[key] = (new Set(filtObjs[key]));
}
});
});
this.multipleFilterArray = this.multipleFilterArray;
}
else {
this.multipleFilterArray = this.multipleFilter;
}
this.selection = new SelectionModel(true, []);
setTimeout(() => {
this.dataSource.sort = this.sort;
}, 0);
this.totalCount = this.totalRecords || this.data?.length;
const NxtDataTable = JSON.parse(localStorage.getItem('NxtDataTable'));
this.currentSortColumn = NxtDataTable?.currentSortColumn;
this.currentSortDirection = NxtDataTable?.currentSortDirection;
if (NxtDataTable?.addRecord === true) {
this.pageSize = NxtDataTable?.pageSize;
this.pageIndex = NxtDataTable?.pageIndex;
var obj = {};
const keys = Object.keys(this.inlineElement);
keys.forEach((elementKey) => {
obj[elementKey] = '';
});
this.data.push(obj);
const data = this.data;
this.dataSource.data = data;
this.selection.select(this.data[this.data.length - 1]);
this.data[this.data.length - 1].editRow = true;
localStorage.setItem('NxtDataTable', JSON.stringify({ addRecord: false, pageSize: 10, pageIndex: this.pageIndex, currentSortColumn: this.currentSortColumn, currentSortDirection: this.currentSortDirection }));
}
if (this.stickyColumn === true && this.displayedColumns) {
this.stickyCondition = this.displayedColumns?.length;
}
else {
this.stickyCondition = this.displayedColumns?.length + 1;
}
if (this.withCheckBox && this.actionButton) {
this.renderColumns = 'both';
}
else if (this.withCheckBox) {
this.renderColumns = 'select';
}
else if (this.actionButton) {
this.renderColumns = 'action';
}
// SKS19MAR25 add first line for formbuilder table
if (this.isEditable) {
this.addTableRecord(this.inlineElement);
}
setTimeout(() => {
if (this.tableContainer?.nativeElement) {
const tableBody = this.tableContainer.nativeElement.querySelector('.table-body');
if (tableBody && this.isPagination) {
this.isShadowHidden = !(tableBody.scrollHeight < 450);
}
}
}, 1500); // Waits 100ms for rendering
}
// SKS20MAR25 summary columns calc
computeSummaryColumns() {
if (this.summaryColumns && this.summaryColumns.length > 0) {
this.data = this.data.map(row => {
const newRow = { ...row };
this.summaryColumns.forEach(summaryCol => {
if (summaryCol.type === 'calculation' && summaryCol.operation === 'sum' && summaryCol.operands) {
const sum = summaryCol.operands.reduce((acc, operand) => acc + (Number(newRow[operand]) || 0), 0);
newRow[summaryCol.fieldName] = sum;
}
else if (summaryCol.type === 'calculation' && summaryCol.operation === 'subtract' && summaryCol.operands) {
const [firstOperand, ...restOperands] = summaryCol.operands;
let sum = Number(newRow[firstOperand]) || 0; // Start with the first operand
sum = restOperands.reduce((acc, operand) => acc - (Number(newRow[operand]) || 0), sum);
newRow[summaryCol.fieldName] = sum;
}
else if (summaryCol.type === 'calculation' && summaryCol.operation === 'multiply' && summaryCol.operands) {
const product = summaryCol.operands.reduce((acc, operand) => acc * (Number(newRow[operand]) || 0), 1); // Start with 1
newRow[summaryCol.fieldName] = product;
}
});
return newRow;
});
this.dataSource.data = this.data; // Update dataSource
}
}
// SKS20MAR25 Compute values for summary rows
computeSummaryValues() {
this.summaryRows?.forEach(row => {
if (row.type === 'calculation') {
if (row.operation === 'sum' && row.column) {
// Correct sum operation
this.summaryValues[row.fieldName] = this.data.reduce((acc, dataRow) => acc + (Number(dataRow[row.column]) || 0), 0);
}
else if (row.operation === 'subtract' && row.column) {
// Subtract should start with the first value
this.summaryValues[row.fieldName] = this.data.reduce((acc, dataRow, index) => index === 0 ? Number(dataRow[row.column]) || 0 : acc - (Number(dataRow[row.column]) || 0), 0);
}
else if (row.operation === 'multiply' && row.column) {
// Multiply should start with 1
this.summaryValues[row.fieldName] = this.data.reduce((acc, dataRow) => acc * (Number(dataRow[row.column]) || 0), // Use 1 instead of 0
1);
}
else if (row.operation === 'subtract' && row.operands) {
// Subtract based on operands
const [op1, op2] = row.operands;
this.summaryValues[row.fieldName] = (this.summaryValues[op1] || 0) - (this.summaryValues[op2] || 0);
}
else if (row.operation === 'sum' && row.operands) {
// Sum based on operands
const [op1, op2] = row.operands;
this.summaryValues[row.fieldName] = (this.summaryValues[op1] || 0) + (this.summaryValues[op2] || 0);
}
else if (row.operation === 'multiply' && row.operands) {
// Multiply based on operands
const [op1, op2] = row.operands;
this.summaryValues[row.fieldName] = (this.summaryValues[op1] || 0) * (this.summaryValues[op2] || 0);
}
}
});
}
// SKS20MAR25 Handle manual input changes in summary rows
onSummaryInputChange() {
this.computeSummaryValues();
}
// SKS15FEB25 Custom sort function
sortData(column) {
// Define sorting states
const sortingStates = ['asc', 'desc', 'none'];
if (!this.configPagination || this.dataSource.data.length === this.totalRecords) {
// Determine the current sorting state
if (this.currentSortColumn === column) {
// Move to the next sorting state
const currentIndex = sortingStates.indexOf(this.currentSortDirection);
this.currentSortDirection = sortingStates[(currentIndex + 1) % sortingStates.length];
}
else {
// If a new column is selected, start with ascending sort
this.currentSortColumn = column;
this.currentSortDirection = 'asc';
}
const data = [...this.dataSource.data];
// Only sort if the direction is 'asc' or 'desc'
if (this.currentSortDirection === 'asc' || this.currentSortDirection === 'desc') {
data.sort((a, b) => {
let valueA = a[column];
let valueB = b[column];
// Handle undefined or null values
if (valueA == null && valueB == null)
return 0; // Both are null/undefined
if (valueA == null)
return this.currentSortDirection === 'asc' ? 1 : -1; // valueA is null
if (valueB == null)
return this.currentSortDirection === 'asc' ? -1 : 1; // valueB is null
// Handle date sorting
if (this.isDateColumn(column)) {
const dateA = new Date(valueA);
const dateB = new Date(valueB);
valueA = dateA.getTime();
valueB = dateB.getTime();
}
else if (typeof valueA === 'number' && typeof valueB === 'number') {
// Handle number sorting
valueA = Number(valueA);
valueB = Number(valueB);
}
else if (typeof valueA === 'string' && typeof valueB === 'string') {
// Handle string comparison
valueA = valueA.toLowerCase();
valueB = valueB.toLowerCase();
}
// Compare values
if (valueA < valueB) {
return this.currentSortDirection === 'asc' ? -1 : 1;
}
else if (valueA > valueB) {
return this.currentSortDirection === 'asc' ? 1 : -1;
}
else {
// If values are the same, return 0 to maintain original order
return 0;
}
});
this.dataSource.data = data;
}
else {
this.dataSource.data = this.data;
}
}
else {
// Determine the current sorting state
if (this.currentSortColumn === column) {
// Move to the next sorting state
const currentIndex = sortingStates.indexOf(this.currentSortDirection);
this.currentSortDirection = sortingStates[(currentIndex + 1) % sortingStates.length];
}
else {
// If a new column is selected, start with ascending sort
this.currentSortColumn = column;
this.currentSortDirection = 'asc';
}
localStorage.setItem('NxtDataTable', JSON.stringify({ addRecord: false, pageSize: this.pageSize, pageIndex: this.pageIndex, currentSortColumn: this.currentSortColumn, currentSortDirection: this.currentSortDirection }));
// Only sort if the direction is 'asc' or 'desc'
this.NxtTableEmit.emit({ pagination: { pageSize: this.pageSize, pageIndex: this.pageIndex }, tableSearch: { fields: this.displayedColumns, value: this.searchBoxValue }, searchFilterData: this.filterDataArray, sort: { column: this.currentSortColumn, direction: this.currentSortDirection } });
}
}
onScroll(tableContainer) {
this.isScrolled = tableContainer.scrollTop > 0;
const tableBodyHeight = tableContainer.scrollHeight;
const isAtBottom = tableBodyHeight - tableContainer.scrollTop <= tableContainer.clientHeight + 1;
//SKS15FEB25 Ensure shadow is hidden only if table body height is greater than 400px
if (this.isPagination) {
this.isShadowHidden = !(isAtBottom || tableBodyHeight < 450);
}
}
//SKS15FEB25 losing the filter component when the mouse is clicked outside table
onDocumentClick(event) {
this.searchFilter = false;
event.stopPropagation(); // prevents any other default action
}
//SKS15FEB25 filter data that to be displayed in header filter icon is passed in 'filterArray' array
filter(datas) {
this.filterArray = [];
this.selectedFilter = datas;
this.filterArray = this.multipleFilterArray[datas];
this.searchFilter = !this.searchFilter;
this.isResized = this.filterDataArray[this.selectedFilter]?.length > 0;
}
// SKS15FEB25 emitting pagr size and index to parent on paginating
pageParams(event) {
this.pageIndex = event.pageIndex;
this.pageSize = event.pageSize;
if (!this.configPagination || this.dataSource.data.length === this.totalRecords) {
const start = (Number(event.pageIndex) - 1) * Number(event.pageSize);
const end = start + Number(event.pageSize);
this.dataSource.data = this.sFilterData?.slice(start, end);
}
else {
this.NxtTableEmit.emit({ pagination: { pageSize: event.pageSize, pageIndex: event.pageIndex }, tableSearch: { fields: this.displayedColumns, value: this.searchBoxValue }, searchFilterData: this.filterDataArray, sort: { column: this.currentSortColumn, direction: this.currentSortDirection } });
}
}
// SKS15FEB25 apply search bar filter using mat
applyFilter(event) {
let filterValue = event.target.value;
filterValue = filterValue.trim().toLowerCase();
if (!filterValue) {
this.dataSource.data = [...this.originalData]; // Reset to original data
}
else {
this.dataSource.data = this.originalData.filter(item => this.displayedColumns.some(column => {
const value = item[column]?.toString().toLowerCase() || '';
return value.includes(filterValue);
}));
}
this.pageIndex = 1;
this.sFilterData = this.dataSource.data;
this.totalCount = this.dataSource.data.length;
const start = (Number(this.pageIndex) - 1) * Number(this.pageSize);
const end = start + Number(this.pageSize);
this.dataSource.data = this.dataSource.data?.slice(start, end);
this.filterRetain(this.filterDataArray);
}
/** SKS15FEB25 Whether the number of selected elements matches the total number of rows. */
isAllSelected() {
// check if all rows are selected
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected === numRows;
}
/** SKS15FEB25 Selects all rows if they are not all selected; otherwise clear selection. */
masterToggle() {
// if there is a selection then clear that selection
// emit the selected value only to parent
if (this.isSomeSelected()) {
// to remove the editRow, if the checkbox is unchecked
this.selection.selected.forEach((element) => {
if (element?.editRow)
element.editRow = false;
});
this.selection.clear();
this.selectedValues.emit(this.selection.selected);
}
else {
// If no items are selected, either select all items or clear the selection
// emit the selected values to parent
this.isAllSelected() ? this.selection.clear() : this.dataSource.data.forEach((row) => this.selection.select(row));
this.selectedValues.emit(this.selection.selected);
}
}
// SKS15FEB25 Checks whether any items are currently selected in the table.
isSomeSelected() {
return this.selection.selected.length > 0;
}
// SKS15FEB25 emit the check box values, that selected separately
separateRowSelect(data, element) {
// to remove the editRow id check box is unchecked on separate row
if (data && element?.editRow) {
element.editRow = false;
}
if (data) {
this.selectedValues.emit(this.selection.selected);
}
}
//SKS15FEB25 data that to be passed, when table row is clicked
tableClick(data) {
this.tableRowClick.emit(data);
}
//SKS15FEB25 Retain the filterdata
filterRetain(filterkey) {
if (Object.keys(filterkey).length == 0) {
this.data = this.sFilterData;
}
else {
this.data = [];
let isDataChecked = false;
for (let key of Object.keys(filterkey)) {
if (filterkey[key] && filterkey[key].length > 0) {
isDataChecked = true;
}
}
if (!isDataChecked) {
this.data = this.sFilterData;
}
else {
for (let key of Object.keys(filterkey)) {
if (filterkey[key]) {
const data = this.sFilterData.filter((name) => filterkey[key]?.includes(name[key]));
const data1 = [...this.data, ...data];
this.data = this.removeDuplicates(data1);
}
}
}
}
this.pageIndex = 1;
const start = (Number(this.pageIndex) - 1) * Number(this.pageSize);
const end = start + Number(this.pageSize);
this.dataSource.data = this.data?.slice(start, end);
this.totalCount = this.data.length;
}
// SKS15FEB25 to remove duplicate object form two arrays
removeDuplicates(objects) {
const seen = new Set();
const uniqueObjects = [];
objects.forEach((obj) => {
const key = JSON.stringify(obj);
if (!seen.has(key)) {
seen.add(key);
uniqueObjects.push(obj);
}
});
return uniqueObjects;
}
// SKS15FEB25 on delete click save the data in variables
deleteRecord(data, index) {
if (this.from === 'formBuilder') {
const updatedData = [...this.dataSource.data]; // Create a new reference
updatedData.splice(index, 1);
this.dataSource.data = updatedData; // Reassign to trigger change detection
this.data = updatedData; // Sync this.data
this.computeSummaryValues();
this.emitTableDataValue(this.dataSource.data);
}
else {
this.deleteData = data;
this.deleteIndex = index;
this.deleteModal = true;
}
}
// SKS15FEB25 after clicking yes in delete alert modal, emit to parent
deleteRecordData() {
this.onDeleteData.emit({ data: this.deleteData, allData: this.data });
this.deleteModal = false;
}
//SKS15FEB25 to save the edited data in table;
saveButton() {
// emitting all data with selected data
this.saveButtonData.emit({ selectedData: this.selection.selected, allData: this.data });
}
//SKS15FEB25 pass the rejected reason
onEdit(data) {
if (typeof data === 'object')
data.editRow = true; // made editing row as true // solve undefined error on string
this.selection.select(data);
this.onEditData.emit({ data: data, allData: this.data });
}
// SKS15FE