import { DataTable, ColumnExtents } from '@fusioncharts/datastore'; import GridDataTable from './grid-datatable'; import { GridConfig, RowFnParams, CellFnParams, ColumnType, styleObjectType, DerivedColumnOptions, cellCssClassField, cellCssStyleField, rowCssClassField, rowCssStyleField, templateFn, CardFnParams, InlineChartStyle, DerivedRowOptions } from '../public-api/interfaces'; import { ScaleLinear } from '../utils/scales/src'; import { GridColumnExtents } from '../globals/helpers/helpers'; import { ascComparer, descComparer } from '../utils'; interface CssClassStyle { class?: string[]; style?: styleObjectType; } interface GridBaseInfo extends CssClassStyle { hoverClass?: string[]; hoverStyle?: styleObjectType; params: RowFnParams | CellFnParams; } interface CellMetaInfo extends GridBaseInfo { content: string; inlineChartStyle?: InlineChartStyle; tooltipContent?: string | string[]; showTooltipInHelper?: boolean; } interface RowMetaInfo extends GridBaseInfo { selected: boolean; selectionClass?: string[]; selectionStyle?: styleObjectType; } interface CardContent { content: string; params: CardFnParams; } let UNDEF: undefined; class GridData { private _columnIndexMap!: number[]; private _gridDataTable: GridDataTable; private _gridConfig: GridConfig; private _stores: any; private _fnContext: any; private _currentRowData!: RowFnParams; private _rowSelectedState: boolean[]; private _sortState: any; private _latestSelectedRow?: number; private _globalSelectedState: boolean; private _dispatchEvent?: Function; get gridDataTable(): GridDataTable { return this._gridDataTable; } get gridConfig(): GridConfig { return this._gridConfig; } constructor(gridDataTable: GridDataTable, gridConfig: GridConfig, ctx: any, stores: any) { this._gridDataTable = gridDataTable; this._gridConfig = gridConfig; this._fnContext = ctx; this._globalSelectedState = false; this._buildColumnIndexMap(); this._buildInlineChartExtents(); this._stores = stores; this._rowSelectedState = new Array(this._gridDataTable.gridData.length).fill(false); this._stores.visualUtils.subscribe((tmpVisualUtils: any) => { this._dispatchEvent = tmpVisualUtils.dispatchEvent; }); } setDataTable(dataTable: DataTable) { this._gridDataTable.setDataTable(dataTable); this._buildInlineChartExtents(); this._rowSelectedState = new Array(this._gridDataTable.gridData.length).fill(false); } setColumns(columns: DerivedColumnOptions[]) { this._gridConfig.columns = columns; this._buildColumnIndexMap(); this._buildInlineChartExtents(); } setSortDetails(sortConfig: any) { let { order, parentDataIndex, cellIndex } = sortConfig; this._sortState = this._sortState || {}; if(order){ this._sortState.order = order; } this._sortState.cellIndex = cellIndex; this._sortState.parentDataIndex = parentDataIndex; } getGlobalSelectedState() { return this._globalSelectedState; } setGlobalSelectedState (isSelected: boolean) { this._globalSelectedState = isSelected; } getColumnIndexMap(){ return this._columnIndexMap; } private _buildColumnIndexMap() { this._columnIndexMap = this._gridConfig.columns.map(val => { if (typeof val.field !== 'undefined') { return this._gridDataTable.dataTable.indexOf(val.field); } return -1; }); } private _buildInlineChartExtents() { const columns = this._gridConfig.columns; let columnExtents: ColumnExtents, currentColumn: DerivedColumnOptions; for (let i = 0; i < columns.length; i++) { currentColumn = columns[i]; if (currentColumn.type === ColumnType.Chart) { // get the column min and max value from datatable columnExtents = this._gridDataTable.dataTable.extents(currentColumn.field || ''); currentColumn.minContent = columnExtents.min; currentColumn.maxContent = columnExtents.max; // user can also set min value and max value in chart config, but we need to check - user provided min value or max value must not exclude any real value present in column if (typeof currentColumn.chartconfig !== 'undefined') { if (typeof currentColumn.chartconfig.minvalue === 'number' && currentColumn.chartconfig.minvalue < columnExtents.min) { currentColumn.minContent = currentColumn.chartconfig.minvalue; } if (typeof currentColumn.chartconfig.maxvalue === 'number' && currentColumn.chartconfig.maxvalue > columnExtents.max) { currentColumn.maxContent = currentColumn.chartconfig.maxvalue; } } currentColumn.scale = new ScaleLinear(); } } } getCurrentRowData(rowIndex: number): RowFnParams { //removed if condition, since current row was not changing from the grid's first row to the filtered row. const data = this._gridDataTable.viewData[rowIndex], schema = this._gridDataTable.gridSchema, rowData: RowFnParams = { rowIndex, values: {} }; if(data && data.length){ for (let i = 0; i < schema.length; i++) { rowData.values[schema[i].name] = data[i]; } } this._currentRowData = rowData; return this._currentRowData; } /** * Toggle the selected status of a row * @param rowIndex Zero based index of the row */ toggleRowSelectedState(rowIndex: number, e?: Event) { if (rowIndex < this._rowSelectedState.length) { const rowIndexInData = this.gridDataTable.viewData[rowIndex].slice(-1)[0] - 1; this.setRowSelection(rowIndexInData, !this.getRowSelection(rowIndexInData), e); } } /** * @description toggle all the row state set */ syncAllRowSelectedStateWithGlobalSelection(e?: Event) { let updatedGlobalSelectedState: boolean = this.getGlobalSelectedState(), dispatchEvent = this._dispatchEvent, rowsSelectedArr: number[] = []; for (let index = 0; index < this._rowSelectedState.length; index++) { this.setRowSelection(index, updatedGlobalSelectedState); (this._rowSelectedState[index] === updatedGlobalSelectedState) && rowsSelectedArr.push(index); } if (dispatchEvent) { (updatedGlobalSelectedState)? dispatchEvent('allrowsselected', rowsSelectedArr, e) : dispatchEvent('allrowsdeselected', rowsSelectedArr, e); } this._stores.visualUtils.update( (tmpVisualUtils: any) =>{ if(tmpVisualUtils.gridData) tmpVisualUtils.gridData._rowSelectedState = this._rowSelectedState; return tmpVisualUtils; }); } private _getClasAndStyle(cellFnParams: CellFnParams, cssClass?: cellCssClassField, cssStyle?: cellCssStyleField): CssClassStyle { const fnContext = this._fnContext; let cssClassArr: string[] = [], cssStyleObj: styleObjectType; if (typeof cssClass === 'function') { cssClassArr = cssClassArr.concat(cssClass.call(fnContext, cellFnParams)); } else { cssClassArr = typeof cssClass === 'undefined' ? [] : cssClassArr.concat(cssClass); } if (typeof cssStyle === 'function') { cssStyleObj = { ...cssStyle.call(fnContext, cellFnParams) }; } else { cssStyleObj = typeof cssStyle === 'undefined' ? {} : cssStyle; } return { class: cssClassArr, style: cssStyleObj }; } // TO DO find a solution to the typescript problem and remove the duplicate function private _getRowClassAndStyle(rowFnParams: RowFnParams, cssClass?: rowCssClassField, cssStyle?: rowCssStyleField): CssClassStyle { const fnContext = this._fnContext; let cssClassArr: string[] = [], cssStyleObj: styleObjectType; if (typeof cssClass === 'function') { cssClassArr = cssClassArr.concat(cssClass.call(fnContext, rowFnParams)); } else { cssClassArr = typeof cssClass === 'undefined' ? [] : cssClassArr.concat(cssClass); } if (typeof cssStyle === 'function') { cssStyleObj = { ...cssStyle.call(fnContext, rowFnParams) }; } else { cssStyleObj = typeof cssStyle === 'undefined' ? {} : cssStyle; } return { class: cssClassArr, style: cssStyleObj }; } /** * Get cell content and style * @param rowIndex Index of the row * @param cellIndex Index of the cell inside row */ getCellMetaInfo(rowIndex: number, cellIndex: number): CellMetaInfo { let cellContent: string = '', inlineChartStyle: InlineChartStyle | undefined, hoverCssClassStyle: CssClassStyle, hoverClassArr: string[] = [], hoverStyleObj: styleObjectType = {}, tooltipContent: string | string[] = [], showTooltipInHelper: boolean = false; const fnContext = this._fnContext, gridColumn = this._gridConfig.columns[cellIndex], dtColIndex = this._columnIndexMap[cellIndex], dtColSchema = this._gridDataTable.gridSchema[dtColIndex], cellValue = this._gridDataTable.viewData.length && this._gridDataTable.viewData[rowIndex][dtColIndex], values = this.getCurrentRowData(rowIndex).values, cellFnParams: CellFnParams = { cellIndex, cellValue, rowIndex, values }, genericCssClassStyle = this._getClasAndStyle(cellFnParams, gridColumn["class"], gridColumn.style), genericClassArr = genericCssClassStyle["class"] || [], genericStyleObj = genericCssClassStyle.style || {}, cellCssClassStyle = this._getClasAndStyle(cellFnParams, gridColumn.cellclass, gridColumn.cellstyle), cellClassArr = cellCssClassStyle["class"] || [], cellStyleObj = cellCssClassStyle.style || {}, mergedClassArr = genericClassArr.concat(cellClassArr), mergedStyleObj = { ...genericStyleObj, ...cellStyleObj }, hoverOptions = gridColumn.hover, tooltipOptions = gridColumn.tooltip; if (typeof hoverOptions === 'object') { hoverCssClassStyle = this._getClasAndStyle(cellFnParams, hoverOptions["class"], hoverOptions.style), hoverClassArr = hoverCssClassStyle["class"] || [], hoverStyleObj = hoverCssClassStyle.style || {}; } if (gridColumn.type !== ColumnType.HTML) { if(typeof gridColumn.formatter === 'undefined') { if (typeof cellValue === 'undefined' || cellValue === null) { cellContent = ''; } else { if (dtColSchema.type === 'datetime') { // TO DO import format date from util package // Also check interval column cellContent = new Intl.DateTimeFormat('en-US').format(new Date(cellValue)); } else { cellContent = cellValue; } } } else { cellContent = gridColumn.formatter.call(fnContext, cellFnParams); } } if (gridColumn.type === ColumnType.HTML && typeof gridColumn.template !== 'undefined') { cellContent = gridColumn.template.call(fnContext, cellFnParams); } if (gridColumn.type === ColumnType.Chart && typeof gridColumn.chartconfig !== 'undefined') { if (typeof gridColumn.chartconfig.style === 'function') { inlineChartStyle = gridColumn.chartconfig.style.call(fnContext, cellFnParams); } else { inlineChartStyle = gridColumn.chartconfig.style; } } if (typeof tooltipOptions === 'object' && tooltipOptions.enablecelltooltip === true) { if (typeof tooltipOptions.celltooltip === 'function') { tooltipContent = tooltipOptions.celltooltip.call(fnContext, cellFnParams); } else { tooltipContent = cellContent; } if (typeof tooltipOptions.enablecellhelpericon === 'boolean' && gridColumn.type !== ColumnType.Chart ) { showTooltipInHelper = tooltipOptions.enablecellhelpericon; } } return { content: cellContent, class: mergedClassArr, style: mergedStyleObj, hoverClass: hoverClassArr, hoverStyle: hoverStyleObj, params: cellFnParams, tooltipContent, showTooltipInHelper, inlineChartStyle }; } /** * Get header cell style and content * @param headerIndex get index of the header cell */ getHeaderMetaInfo(headerIndex: number): CellMetaInfo { let hoverCssClassStyle: CssClassStyle, hoverClassArr: string[] = [], hoverStyleObj: styleObjectType = {}, tooltipContent: string | string[] = [], showTooltipInHelper: boolean = false; const gridColumn = this._gridConfig.columns[headerIndex] || {}, headerCellValue = typeof gridColumn.headertext === 'undefined' ? '' : gridColumn.headertext, headerFnParams: CellFnParams = { cellIndex: headerIndex, rowIndex: -1, cellValue: headerCellValue, values: {} }, genericCssClassStyle = this._getClasAndStyle(headerFnParams, gridColumn["class"], gridColumn.style), genericClassArr = genericCssClassStyle["class"] || [], genericStyleObj = genericCssClassStyle.style || {}, headerCssClassStyle = this._getClasAndStyle(headerFnParams, gridColumn.headerclass, gridColumn.headerstyle), headerClassArr = headerCssClassStyle["class"] || [], headerStyleObj = headerCssClassStyle.style || {}, mergedClassArr = genericClassArr.concat(headerClassArr), mergedStyleObj = { ...genericStyleObj, ...headerStyleObj }, hoverOptions = gridColumn.hover, tooltipOptions = gridColumn.tooltip; if (typeof hoverOptions === 'object') { hoverCssClassStyle = this._getClasAndStyle(headerFnParams, hoverOptions["class"], hoverOptions.style), hoverClassArr = hoverCssClassStyle["class"] || [], hoverStyleObj = hoverCssClassStyle.style || {}; } if (typeof tooltipOptions === 'object' && tooltipOptions.enableheadertooltip === true) { tooltipContent = tooltipOptions.headertooltip || headerCellValue; if (typeof tooltipOptions.enableheaderhelpericon === 'boolean') { showTooltipInHelper = tooltipOptions.enableheaderhelpericon; } } return { content: headerCellValue, class: mergedClassArr, style: mergedStyleObj, hoverClass: hoverClassArr, hoverStyle: hoverStyleObj, tooltipContent, showTooltipInHelper, params: headerFnParams }; } /** * Get row style * @param rowIndex Index of the row */ getRowMetaInfo(rowIndex: number): RowMetaInfo | undefined { const rowOptions: DerivedRowOptions = this._gridConfig.rowoptions; if (typeof rowOptions !== 'undefined') { const values = this.getCurrentRowData(rowIndex).values, rowFnParams: RowFnParams = { rowIndex, values }, cssClassStyle = this._getRowClassAndStyle(rowFnParams, rowOptions["class"], rowOptions.style), classArr = cssClassStyle["class"] || [], styleObj = cssClassStyle.style || {}, hoverOptions = rowOptions.hover, rowSelection = rowOptions.selection; let hoverCssClassStyle: CssClassStyle, hoverClassArr: string[] = [], hoverStyleObj: styleObjectType = {}, selectedClassStyle: CssClassStyle, selectedClassArr: string[] = [], selectedStyleObj: styleObjectType = {}, rowSelectedState = this._rowSelectedState; if (typeof hoverOptions === 'object') { hoverCssClassStyle = this._getRowClassAndStyle(rowFnParams, hoverOptions["class"], hoverOptions.style), hoverClassArr = hoverCssClassStyle["class"] || [], hoverStyleObj = hoverCssClassStyle.style || {}; } if (typeof rowSelection === 'object') { selectedClassStyle = this._getRowClassAndStyle(rowFnParams, rowSelection.selectionclass, rowSelection.selectionstyle), selectedClassArr = selectedClassStyle["class"] || [], selectedStyleObj = selectedClassStyle.style || {}; } if(this.gridDataTable.viewData.length !== this.gridDataTable.gridData.length || this._sortState?.order !== 'none') { rowSelectedState = []; this.gridDataTable.viewData.forEach((row) => { rowSelectedState.push(this._rowSelectedState[row[this._gridDataTable.gridSchema.length - 1] - 1]); }); } return { class: classArr, style: styleObj, hoverClass: hoverClassArr, hoverStyle: hoverStyleObj, selectionClass: selectedClassArr, selectionStyle: selectedStyleObj, selected: rowSelectedState[rowIndex], params: rowFnParams }; } return; } /** * Get card html based on a template function * @param recordIndex index of the record to build HTML * @param template template function to transform the record to HTML */ getCardHtml(recordIndex: number, template: templateFn): CardContent { const values = this.getCurrentRowData(recordIndex).values, cardFnParams: CardFnParams = { recordIndex, values }; return { content: template.call(this._fnContext, cardFnParams), params: cardFnParams }; } /** * @description get min and max content for a cell * @param fieldName contains field Name of the column * @returns min and max content of a column */ getMinMaxContent (fieldName: string): GridColumnExtents | undefined { return this.gridDataTable.getColumnExtents(fieldName); } /* * @description sets select property as passed * @rowIndex contains row number to mark selected or deselected * @isSelect contains boolean value to be set */ setRowSelection (rowIndex: number, isSelect: boolean, e?: Event) { let dispatchEvent = this._dispatchEvent; this._rowSelectedState[rowIndex] = isSelect; if (dispatchEvent) { (isSelect)? dispatchEvent('rowselected', this.gridDataTable.gridData[rowIndex], e) : dispatchEvent('rowdeselected', this.gridDataTable.gridData[rowIndex], e); } this._stores.visualUtils.update( (tmpVisualUtils: any) =>{ if(tmpVisualUtils.gridData) tmpVisualUtils.gridData._rowSelectedState[rowIndex] = this._rowSelectedState[rowIndex]; return tmpVisualUtils; }); } /** * @description returns selected status of a row * @param rowIndex contains row Index number for which selected state is asked for * @returns selected status of a row */ getRowSelection (rowIndex: number) { return this._rowSelectedState[rowIndex]; } /** * @description getter function of rowSelectedState that holds boolean values denoting whether the row * is selected or not. * @returns selected State of rows */ getRowSelectedState () { return this._rowSelectedState; } resetRowSelectedState() { for (let index = 0; index < this._rowSelectedState.length; index++) { this._rowSelectedState[index] = false; } } /** * @description getter function of _latestSelectedRow that holds latest selected row Index * @returns _latestSelectedRow that holds latest selected row Index */ getLatestSelectedRow () { return this._latestSelectedRow; } /** * @description setter function of _latestSelectedRow that holds latest selected row Index */ setLatestSelectedRow (rowIndex?:number) { let rowIndexInData; // eslint-disable-next-line no-undefined if(rowIndex !== undefined) { rowIndexInData = this.gridDataTable.viewData[rowIndex].slice(-1)[0] - 1; } this._latestSelectedRow = rowIndexInData; } /** * @description function to validate user given inputs and set them selected or deselected * @param rowIndexArr array containing rowIndexes to be deselected. * @param isSelected boolean value to decide set rows selected or deselected * @returns Object containing valid and invalid rows that are considered to select */ setRowsSelected (rowIndexArr: any[]| number, isSelected: boolean) { let validRowArr: number[] = [], inValidRowArr:any[] = [], gridDataLen: number = this._rowSelectedState.length; if (typeof rowIndexArr === 'number') { validRowArr.push(rowIndexArr); } else { for (let index = 0; index < rowIndexArr.length; index++) { const element: any = rowIndexArr[index]; if (element === parseInt(element, 10) && (element >=0 && element < gridDataLen)) { validRowArr.push(element); } else { inValidRowArr.push(element); } } } for (let index = 0; index < validRowArr.length; index++) { const validRowIndex:number = validRowArr[index]; this.setRowSelection(validRowIndex, isSelected, UNDEF); } if(validRowArr.length) this.reEvaluateGlobalSelectedState(); return { validRowArray: validRowArr, inValidRowArray: inValidRowArr }; } /** * @description function to verify whether update globalselected state when each row is clicked */ reEvaluateGlobalSelectedState () { let selectedState:boolean[] = this._rowSelectedState, selectGlobalState:boolean = true; for (let index = 0; index < selectedState.length; index++) { if (!selectedState[index]) { selectGlobalState = false; break; } } if (this.getGlobalSelectedState() !== selectGlobalState) { this.setGlobalSelectedState(selectGlobalState); this._stores.visualUtils.update( (tmpVisualUtils: any) =>{ if(tmpVisualUtils.gridData) tmpVisualUtils.gridData._globalSelectedState = this._globalSelectedState; return tmpVisualUtils; }); } } /** * @description resets the global selected state and updates the store */ resetGlobalSelectedState () { this._globalSelectedState = false; this._stores.visualUtils.update( (tmpVisualUtils: any) =>{ if(tmpVisualUtils.gridData) tmpVisualUtils.gridData._globalSelectedState = this._globalSelectedState; return tmpVisualUtils; }); } sortGridRows(customData: any) { const dataStore = this._gridDataTable.dataTable.getDataStore(), parentDataIndex = this._sortState ? this._sortState.parentDataIndex : '', viewData = customData || this._gridDataTable.dataTable._data; let newOrder, comparer: any, data: any, schema = this._gridDataTable.gridSchema; if(parentDataIndex > -1){ if(this._sortState) { if(this._sortState.order === "asc") { newOrder = "desc"; comparer = descComparer(parentDataIndex, schema); data = dataStore.sortRows(viewData, comparer); } else if(this._sortState.order === "desc") { newOrder = "none"; data = viewData; } else { newOrder = "asc"; comparer = ascComparer(parentDataIndex, schema); data = dataStore.sortRows(viewData, comparer); } } else { newOrder = "asc"; comparer = ascComparer(parentDataIndex, schema); data = dataStore.sortRows(viewData, comparer); } if(!customData){ data = this.getSortedViewData(data); } this._sortState.order = newOrder; this._fnContext.updateViewData(data); } } getSortedViewData(sortedData: any){ let viewData = this._gridDataTable.viewData; return sortedData.filter((row: any) => viewData.includes(row)); } setPreSortOrder(){ let currentOrder = this._sortState ? this._sortState.order : '', sortOrder; if(currentOrder === 'asc'){ sortOrder = 'none' ; } else if(currentOrder === 'desc'){ sortOrder = 'asc'; } else { sortOrder = 'desc'; } this._sortState ? this._sortState.order = sortOrder : { order: sortOrder }; } } export { GridData, CellMetaInfo, RowMetaInfo, CardContent };