import { ColumnOptions, GridConfig, Viewport, Layout, ColumnType, RowOptions, DerivedRowOptions, LayoutKeyType, RowSelection, DerivedColumnOptions, DerivedInlineChartConfig, Pagination, ValueTextPositionType, ValueTextAlignmentType } from './interfaces'; import { GridData } from '../data-layer/grid-data'; import GridDataTable from '../data-layer/grid-datatable'; import { GridException, ROW_SELECTION_ERROR_MESSAGE, PaginationException } from '../../texts/exception'; import { GridWarningMessage } from '../../texts/warning'; import { DataTable } from '@fusioncharts/datastore'; import { deriveColumnTypeFromSchema } from '../utils/column-type'; import { DEFAULT_ROW_HEIGHT, DEFAULT_COLUMN_WIDTH, setMinColumnWidth, getMinColumnWidth, GridDimensions, ObjectTraverser, DEFAULT_CHECKBOX_COLUMN_WIDTH, VizRecordDomainConfig, columnIndexWithMinMaxContent, GridColumnExtents, DEFAULT_CREDIT_LABEL_HEIGHT, DEFAULT_VALUE_TEXT_ALIGNMENT, DEFAULT_VALUE_TEXT_POSITION, DEFAULT_PAGINATION_ROW_HEIGHT } from '../globals/helpers/helpers'; import ViewportManager from '../manager/viewport-manager'; import { ViewportObject } from '../manager/viewport-manager'; import LayoutManager from '../manager/layout-manager'; import InfiniteScrollManager from '../manager/infinite-scroll-manager'; import PaginationClass from '../components/pagination'; import createStores from '../components/store-generator'; import { lengthUnitGenerator } from '../utils/length-unit-generator'; import { isEqualObject } from '../utils/obj-eq-ckeck'; import { getAllValuesofObj } from '../utils/index'; import { Logger, convertKeysToLowerCase, mergeDeep, formatString, EventManager, LengthUnits, captalizeFirstLetter, parseLengthUnit, clone, isNaturalNumber, isWholeNumber } from '../utils/toolbox/src'; import xlsx from 'json-as-xlsx'; import { isIOS } from '../utils/toolbox/src/browsers/browser-type'; // @ts-ignore import Viz from '../viz/index.svelte'; import Redloum from '../decryption/redluom'; let UNDEF: undefined; const createExtViewportArray = (viewports: { [key: string]: Viewport }):Array => { let viewportArr: Array = []; for (let key in viewports) { viewportArr.push({ name: key, ...viewports[key] }); } return viewportArr; }, isRowStylingOption = (option : string): boolean => (option === 'style') || (option === 'hover') || (option === 'class'), // check for touch devices isTouchDevice: boolean = "ontouchstart" in window, calculatePaginationHeight = (screenWidth: number, config: Pagination): number=>{ if (!config.enable){ return 0; } // for screen widths less than 550px and with both showrowcount and options enabled // break the visuals into two rows if ((screenWidth <= 550) && (config.showrecordcount && config.pagesize!.options)){ return 2 * DEFAULT_PAGINATION_ROW_HEIGHT; } return DEFAULT_PAGINATION_ROW_HEIGHT; }, /** * Function to convert JSON to the following format for export, * data = [ { sheet: 'Adults', columns: [ { label: 'User', value: 'user' }, // Top level data { label: 'Age', value: row => (row.age + ' years') }, // Run functions { label: 'Phone', value: row => (row.more ? row.more.phone || '' : '') }, // Deep props ], content: [ { user: 'Andrea', age: 20, more: { phone: '11111111' } }, { user: 'Luis', age: 21, more: { phone: '12345678' } } ] } ] */ parseDataForExcelExport = (data: any, schema: any) => { let exportData : any = [{ sheet: 'chartdata', columns: schema.map((sch: any) => ({ label: sch.name, value: sch.name })), }], content: any = []; data.forEach((row:any) => { let rowData:any = {}; row.forEach((cell: any, index: any) => { rowData[schema[index].name] = cell; }); content.push(rowData); }); exportData[0].content = content; return exportData; }; enum OrientationEvents { Resize = "resize", OrientationChange = "orientationchange" } interface GridDefaults { columnOptions: ColumnOptions; rowOptions: RowOptions; lowerCaseIgnorableProperties: string[]; valsToConvert: string[]; } interface ContainerDimensions { width: number; height: number; left: number, top: number } export default class FusionGrid { static licenseKey: Function; static items: Array; static options: Object; static versionDetails: any; // Private properties private _container: HTMLElement; private _conatinerDimensions: ContainerDimensions; private _lazyRendering: boolean; private _data: DataTable; private _gridDataTable: GridDataTable; private _gridData: GridData; private _gridConfig: any; private _parsedGridConfig: GridConfig; private _logger: Logger; private _eventManager: EventManager; private _viewportManager: ViewportManager; private _pagination: PaginationClass; private _layoutManager: LayoutManager; private _viz: Viz; private _infiniteScrollManager: InfiniteScrollManager; private _lengthUnitsObject?: LengthUnits; private _systemDefaults: GridDefaults; private _defaultColumnOptions!: any; private _stores: any; /** * Create a FusionGrid instance * @param container DOM container to render the grid * @param data DataTable object to fetch grid data * @param gridConfig Grid configurations */ constructor(container?: HTMLElement, data?: DataTable, gridConfig?: any, licenseObj?: any) { let parsedGridConfig:GridConfig, systemDefaultColumnOptions: ColumnOptions, defaultRowOptions: RowOptions, defaultSelection: RowSelection, viewport: ViewportObject, eventToBeAttached: string = isIOS ? OrientationEvents.OrientationChange : OrientationEvents.Resize, gridSchema, selectedValues: any, key, creditLabel; this._stores = createStores(); // calculating length units if (document && container) { this._lengthUnitsObject = lengthUnitGenerator(document, container, { height: container.offsetHeight, width: container.offsetWidth }); setMinColumnWidth(container); } this._lazyRendering = true; // Initialize the event manager for grid this._eventManager = new EventManager({ context: this, events: gridConfig.events }); this._stores.visualUtils.set({ dispatchEvent: this._eventManager.dispatchEvent }); // Initialize the logger for grid this._logger = new Logger(this._eventManager.dispatchEvent); try { if (!container || !data) { throw new Error(GridException.parameterMissing); } if (!(container instanceof Element)) { throw new Error(GridException.containerNotElement); } // TO DO import DataTable and then uncomment the below check // if (!(data instanceof DataTable)) { // throw new Error(GridException.dataNotDataTable); // } this._container = container; this._data = data; this._gridConfig = gridConfig; // Set the defaults decided by system systemDefaultColumnOptions = { width: DEFAULT_COLUMN_WIDTH, // column width minwidth: 50, // column minimum width, useful to enforce a bound for sizeColumnsToFit & sizeColumnsToContent wraptext: false, // if text wrapping is enabled chartconfig: { type: 'bar', showvalue: true, showhundredpercentbar: false, valuetextposition: DEFAULT_VALUE_TEXT_POSITION, valuetextalignment: DEFAULT_VALUE_TEXT_ALIGNMENT } }; defaultRowOptions = { rowheight: DEFAULT_ROW_HEIGHT, headerrowheight: DEFAULT_ROW_HEIGHT, hover: { enable: true } }; this._systemDefaults = { columnOptions: systemDefaultColumnOptions, rowOptions: defaultRowOptions, lowerCaseIgnorableProperties: ['style', 'headerstyle', 'cellstyle', 'selectedrowstyle', 'positivebarstyle', 'negativebarstyle', 'hundredpercentbarstyle', 'valuetextstyle'], // these properties contains CSS styles, hence should not converted to lowercase valsToConvert: ['type', 'density', 'valuetextposition', 'valuetextalignment'] }; // create grid specific data table this._gridDataTable = new GridDataTable(this._data); // real-time data update in Grid this._data.getDataStore().on("itemsAdded", () => { if(typeof this._gridConfig.rowOptions !== "undefined" && this._gridConfig.rowOptions!.forceDataStoreRefresh) { this.refreshCells(); } }); this._data.getDataStore().on("itemUpdated", () => { this.setDataTable(this._data); }); // get the parsed config parsedGridConfig = this._buildGridConfig(); this._viewportManager = new ViewportManager({ externalViewports: createExtViewportArray(parsedGridConfig.viewports!), extLayout: parsedGridConfig.layout!, logger: this._logger, stores: { visualUtils : this._stores.visualUtils } }); viewport = this._viewportManager.getCurrentViewport(); this._parsedGridConfig = this._configurePrasedGridConfig(viewport, parsedGridConfig); this._gridData = this._configureGridData(parsedGridConfig); this._updateGridDataStore(); this._conatinerDimensions = { width: this._container.offsetWidth, height: this._container.offsetHeight - DEFAULT_CREDIT_LABEL_HEIGHT, top: this._container.offsetTop, left: this._container.offsetLeft, }; this._layoutManager = this._configureLayoutManager(viewport, parsedGridConfig, container); this._infiniteScrollManager = this._configureInfiniteScrollManager(parsedGridConfig, container); this._pagination = this._configurePagination(); // attach orientation handler for touch devices. // if the device is iOS then attach orientationchange handler to check for orientation angle, or attach resize event for other touch devices isTouchDevice && window.addEventListener(eventToBeAttached, ()=>{ this._viewportManager.orientationHandler(); let newViewPort: Viewport = this._viewportManager.getCurrentViewport(); if (this._viewportManager.config.viewportChanged){ let newLayoutObj: Layout = (newViewPort.config && newViewPort.config.layout) || {}; this._layoutManager.setLayout(newLayoutObj); } }); this._stores.gridDimensions.set({ width: this._conatinerDimensions.width, height: this._conatinerDimensions.height - 45.5, top: this._conatinerDimensions.top, left: this._conatinerDimensions.left, }); this._stores.colSearchValues.set(this._gridData.gridDataTable.gridSchema.map((sch, index) => { return { name: sch.name, index, text: '' }; })); gridSchema = this.getDataTable().getSchema(); this._stores.conditionalFilterLogicalOperators.set([...new Array(gridSchema.length)].map(() => 'and')); this._stores.conditionalFilterValues.set([...new Array(gridSchema.length)].map(() => [{ value: '' }])); selectedValues = gridSchema.map((sch, index) => { let tempData = this._gridDataTable.dataTable.getDataStore().getUniqueValues(this._gridDataTable.dataTable._data, index); return tempData; }); this._stores.valueFilterData.set([...new Array(gridSchema.length)].map((val, index) => ({ allSelected: true, selectedValues: selectedValues[index] }))); // trigger initialized public event this.trigger('initialized', { container: container, gridConfig: parsedGridConfig }); } catch (ex) { this._logger.error(ex.message); throw ex; } FusionGrid.items.push(this); // Instance level licensing check if(licenseObj){ key = licenseObj.key; creditLabel = licenseObj.creditLabel; } //@ts-ignore if(FusionGrid.options._chartSel){ Redloum.storeObj(FusionGrid); } else if(key || creditLabel || creditLabel === false){ Redloum.storeObj(FusionGrid, key, creditLabel); } // Check if predefined sort config has been passed, if yes then update view data this.checkPredefinedSortConfig(); } private checkPredefinedSortConfig() { let predefinedOrderIndex, gridConfig = this._gridConfig, columns = gridConfig.columns, sortOrder; columns && columns.length && columns.forEach((element: any, index: any) => { if(element.order || (element.sortable && element.sortable.order)){ predefinedOrderIndex = index; } }); // If pre-defined order present then sort view data accordingly if(predefinedOrderIndex && predefinedOrderIndex !== -1){ sortOrder = columns[predefinedOrderIndex].order || (columns[predefinedOrderIndex].sortable && columns[predefinedOrderIndex].sortable.order); if(sortOrder === 'desc'){ sortOrder = 'asc'; } else if(sortOrder === 'asc'){ sortOrder = 'none'; } else { sortOrder = 'desc'; } this._gridData.setSortDetails({ cellIndex: predefinedOrderIndex, parentDataIndex: this._gridData.getColumnIndexMap()[predefinedOrderIndex] , order: sortOrder }); // eslint-disable-next-line no-undefined this._gridData.sortGridRows(undefined); } } private _configurePrasedGridConfig(viewport: ViewportObject, parsedGridConfig: GridConfig): GridConfig { // current viewport may have many viewport specific configuration // we need to replace the parsed configuration with the current viewport configuration if (viewport && typeof viewport.config === 'object') { const viewportConfig = viewport.config, userDefaultColumnOptions = viewportConfig.defaultColumnOptions || {}, defaultColumnOptions = mergeDeep(this._systemDefaults.columnOptions, userDefaultColumnOptions), parsedColumnOptions = this._buildColumnOptions(viewportConfig.columns || [], defaultColumnOptions, false), viewportColumnOptions = parsedColumnOptions.parsedConfigColumns; if (typeof viewportColumnOptions !== 'undefined' && viewportColumnOptions.length > 0) { parsedGridConfig.columns = viewportColumnOptions; } if (typeof viewportConfig.layout !== 'undefined') { parsedGridConfig.layout = viewportConfig.layout; } if (typeof viewportConfig.rowoptions !== 'undefined') { parsedGridConfig.rowoptions = viewportConfig.rowoptions; } if (typeof viewportConfig.pagination !== 'undefined') { parsedGridConfig.pagination = this._buildPagination(viewportConfig.pagination); } } // if empty or invalid field name is provided then throw error if (!parsedGridConfig.columns.length) { throw new Error(GridException.noColumnFound); } return parsedGridConfig; } private _configurePagination(): PaginationClass{ let paginationConfig: Pagination = this._parsedGridConfig.pagination || {}, paginationHeight: number = calculatePaginationHeight(this._conatinerDimensions.width, paginationConfig); this._conatinerDimensions.height -= paginationHeight; return new PaginationClass({ pagination: paginationConfig, currentScreenWidth: this._conatinerDimensions.width, logger: this._logger, bodyHeight: this._conatinerDimensions.height - this._layoutManager.getDensedHeaderRowHeight(), rowHeight: this._layoutManager.getDensedBodyRowHeight(), dispatchEvent: this._eventManager.dispatchEvent, gridDataTable: this._gridDataTable, paginationHeight, stores: { vizRecordDomain: this._stores.vizRecordDomain, paginationState: this._stores.paginationState } }); } private _configureGridData(parsedGridConfig: GridConfig): GridData{ return new GridData(this._gridDataTable, parsedGridConfig, this, { visualUtils: this._stores.visualUtils }); } private _configureLayoutManager(viewport: Viewport, parsedGridConfig: GridConfig, container: HTMLElement): LayoutManager{ let logger = this._logger, layoutManager = new LayoutManager({ layoutConfig: clone((viewport.config && viewport.config.layout) || {}), rowOptionsConfig: parsedGridConfig.rowoptions, columnsConfig: parsedGridConfig.columns, selectionConfig: typeof parsedGridConfig.rowoptions === 'undefined' ? parsedGridConfig.rowoptions : parsedGridConfig.rowoptions.selection, defaultColumnOptions: parsedGridConfig.defaultcolumnoptions, domContainerDim: { height: container.offsetHeight, width: container.offsetWidth }, stores: { vizRecordDomain: this._stores.vizRecordDomain, autoHeight: this._stores.autoHeight, layoutObject: this._stores.layoutObject }, groupLevel: this._gridConfig.totalLevel, }); return layoutManager; } private _configureInfiniteScrollManager(parsedGridConfig: GridConfig, container: HTMLElement): InfiniteScrollManager{ let infiniteScrollManager: InfiniteScrollManager = new InfiniteScrollManager({ lazyRendering: this._lazyRendering, dataLength: this._gridData.gridDataTable.viewData.length, rowConfig: parsedGridConfig.rowoptions || {}, domContainer: this._conatinerDimensions, stores: { vizRecordDomain: this._stores.vizRecordDomain, layoutObject: this._stores.layoutObject, autoHeight: this._stores.autoHeight, paginationState: this._stores.paginationState } }); if (this._lazyRendering){ this._stores.vizRecordDomain.set({ start: 0, end: infiniteScrollManager.getEndRow() }); } else { this._stores.vizRecordDomain.set({ start: 0, end: this._gridData.gridDataTable.viewData.length - 1 }); } return infiniteScrollManager; } /** * Method that recalculates everything when viewport changes externally via API */ private _configure(){ // recalculate everything only if viewport has changed if (this._viewportManager.config.viewportChanged) { let viewport: Viewport = this._viewportManager.getCurrentViewport(), parsedGridConfig: GridConfig = this._buildGridConfig(), container: HTMLElement = this._container; this._parsedGridConfig = this._configurePrasedGridConfig(viewport, parsedGridConfig); this._gridData = this._configureGridData(parsedGridConfig); this._updateGridDataStore(); this._layoutManager = this._configureLayoutManager(viewport, parsedGridConfig, container); this._infiniteScrollManager = this._configureInfiniteScrollManager(parsedGridConfig, container); this._configurePagination(); // trigger updated public event this.trigger('updated', { gridConfig: this._parsedGridConfig }); } } render() { // trigger beforeRender public event this.trigger('beforerender', { container: this._container, gridConfig: this._parsedGridConfig }); let layoutManager: LayoutManager = this._layoutManager, pagination: PaginationClass = this._pagination; this._viz = new Viz({ target: this._container, props: { parent: this, container: this._container, parsedCardConfig: layoutManager.getCardConfig(), infiniteScrollManager: this._infiniteScrollManager, stores: this._stores, paginationHandlers: { 'jumpToNextPage': ()=>{ pagination.jumpToNextPage(); }, 'jumpToPreviousPage': ()=>{ pagination.jumpToPreviousPage(); }, 'jumpToFirstPage': ()=>{ pagination.jumpToFirstPage(); }, 'jumpToLastPage': ()=>{ pagination.jumpToLastPage(); }, 'jumpToPage': (pageNumber: number)=>{ pagination.jumpToPage(+pageNumber); }, 'setPageSize': (pageSize: number, recalculateOptions: boolean)=>{ pagination.setPageSize(+pageSize, recalculateOptions); }, }, addCreditLabel: this.addCreditLabel.bind(this), attachObserver: this.attachObserver.bind(this), handleCreditLabelClick: this.handleCreditLabelClick } }); } /** * Method that disposes of the grid by releasing all resources and disposing of all DOM. */ dispose() { const parent = this._container; parent.childNodes.forEach((child)=>{ parent.removeChild(child); }); this.removeCreditLabel(); } /** * Add a grid event handler * @param eventName Name of the event to listen * @param handler Function to handle the event */ on(eventName: string, handler: Function) { this._eventManager.addEventListener(eventName, handler); } /** * Remove a grid event handler * @param eventName Name of the event to remove handler from * @param handler Handler function remove */ off(eventName: string, handler: Function) { this._eventManager.removeEventListener(eventName, handler); } /** * Trigger a grid event * @param eventName Event to trigger * @param payload Event payload */ trigger(eventName: string, payload: object) { this._eventManager.dispatchEvent.call(this, eventName, payload); } /** * Get column configuration * @param columnIndex zero based index of the column */ getColumns(columnIndex?: number): ColumnOptions | ColumnOptions[] | undefined { if (this._gridConfig && this._gridConfig.columns && Array.isArray(this._gridConfig.columns)) { // user may get all column details without passing any parameter if (typeof columnIndex === 'undefined') { return this._gridConfig.columns; }else if (!isWholeNumber(columnIndex)) { this._logger.error(GridException.inValidColumnIndex,columnIndex); } else { return this._gridConfig.columns[columnIndex]; } } return UNDEF; } /** * Get the applied configurations of a column * @param columnIndex zero based index of the column */ getParsedColumnOptions(columnIndex?: number): DerivedColumnOptions | DerivedColumnOptions[] | undefined { if (typeof columnIndex === 'undefined') { return this._parsedGridConfig.columns; } else if (!isWholeNumber(columnIndex)) { this._logger.error(GridException.inValidColumnIndex,columnIndex); return UNDEF; } return this._parsedGridConfig.columns[columnIndex]; } setColumns(columns: any[]) { if (!Array.isArray(columns)) { this._logger.error(GridException.acceptsOnlyArrayAsInput); return this; } const lowerCasedColumns = convertKeysToLowerCase(columns, this._systemDefaults.lowerCaseIgnorableProperties, this._systemDefaults.valsToConvert) || {}, // convert all user provided JSON keys to lower case to avoid any errors parsedConfigColumns: DerivedColumnOptions[] = this._buildColumnOptions(lowerCasedColumns, this._defaultColumnOptions, true).parsedConfigColumns; this._gridData.setColumns(parsedConfigColumns); this._layoutManager.setColumnsConfig(parsedConfigColumns); this._updateGridDataStore(); this._layoutManager.calculatelayout(); // trigger updated public event this.trigger('updated', { gridConfig: this._parsedGridConfig }); return this; } setRowData() { const dataStore = this._data.getDataStore(); let parsedRows: Array; if(typeof arguments[0] === "number") { parsedRows = dataStore.parseRows([arguments[1]], this._data.getID()); this._gridData.gridDataTable.viewData[arguments[0]] = parsedRows[0]; this.updateViewData(this._gridData.gridDataTable.viewData); } else { parsedRows = dataStore.parseRows(arguments[0], this._data.getID()); const newData = [...this._gridData.gridDataTable.viewData, ...parsedRows]; this.updateViewData(newData); } } getRowData(index: number) { if(typeof index === "number") { return this._data._data[index]; } else if(index){ throw new Error(GridException.invalidRowIndex); } else { return this._data._data; } } syncCells() { this._data._data = this._gridData.gridDataTable.viewData; this._data.flushResults(); this._gridData.gridDataTable.syncDataTable(); } refreshCells() { this.setDataTable(this._data); } getDataTable(): DataTable { return this._data; } /** * Update grid data * @param dataTable DataTable to update the grid width */ setDataTable(dataTable: DataTable) { this._gridData.setDataTable(dataTable); if(this._parsedGridConfig.pagination?.enable) { this._pagination.setRowCount(this._gridData.gridDataTable.viewData.length); this._pagination.reconfigureState(); } else { this._infiniteScrollManager.setDatalength(this._gridData.gridDataTable.viewData.length); this._updateVizRecDomainStartEnd(0, this._infiniteScrollManager.getEndRow()); } this._updateGridDataStore(); // trigger updated public event this.trigger('updated', { gridConfig: this._parsedGridConfig }); return this; } updateViewData(rows: Array>){ this._gridDataTable.setViewData(rows); if(this._parsedGridConfig.pagination?.enable) { this._pagination.setRowCount(this._gridData.gridDataTable.viewData.length); this._pagination.reconfigureState(); } else { this._infiniteScrollManager.setDatalength(this._gridData.gridDataTable.viewData.length); this._updateVizRecDomainStartEnd(0, this._infiniteScrollManager.getEndRow()); } // trigger updated public event this.trigger('updated', { gridConfig: this._parsedGridConfig }); this._stores.visualUtils.update( (tmpVisualUtils: any) =>{ if(tmpVisualUtils.gridData) tmpVisualUtils.gridData._data = rows; return tmpVisualUtils; }); return this; } search(searchText: any){ let dataTable = this._gridData.gridDataTable.dataTable, text, filteredRows; if(typeof searchText === 'object' && !searchText.test){ text = searchText && searchText.text; //@ts-ignore filteredRows = dataTable.getDataStore().globalSearch(dataTable.getID(), text, searchText && searchText.columns); } else { //@ts-ignore filteredRows = dataTable.getDataStore().globalSearch(dataTable.getID(), searchText); } this.updateViewData(filteredRows); } getSearchText(name: any) { let colSearchValues:any = []; this._stores.colSearchValues.subscribe((colValues: any) => { colSearchValues = colValues; }); // eslint-disable-next-line no-undefined if(name !== undefined){ if(Number(name)){ return colSearchValues[Number(name)].text; } let field = colSearchValues.find( (col: any) => col.name === name); return field ? field.text : ''; } return colSearchValues.map((col: any) => { return col.text; }); } resetSearch(name: any) { this._viz.resetSearchValues(name); } // getRowOptions(property?: string) { // } // setRowOptions(keyOrObj: string | object, value: any) { // } getLayout(param: any) { let typeofParam = typeof param, layoutObject: Layout, returnObject:ObjectTraverser = {}, logger = this._logger; if (typeofParam !== 'string' && !Array.isArray(param) && typeofParam !== 'undefined') { logger.error(GridException.invalidLayoutConfiguration); return UNDEF; } layoutObject = this._layoutManager.getLayout(); if (typeofParam === 'string'){ return layoutObject[param.toLowerCase()]; } else if (Array.isArray(param)) { for (let index = 0; index < param.length; index++) { let attr = param[index]; if (typeof attr === 'string') { attr = attr.toLowerCase(); returnObject[attr] = layoutObject[attr]; } } return returnObject; } return layoutObject; } setLayout(param: any, value: any):FusionGrid { let prevLayout = clone(this._layoutManager.getLayout()), viewport: Viewport = this._viewportManager.getCurrentViewport(), logger = this._logger, newLayout: Layout, properties = param; // santizing to only object and string values passed as first argument if (typeof param !== "string" && typeof param !== "object" || Array.isArray(param)) { logger.error(GridException.invalidLayoutInput); return this; } if (typeof param === 'string') { prevLayout[param] = value; properties = prevLayout; } const lowerCasedProps:object = convertKeysToLowerCase(properties, this._systemDefaults.lowerCaseIgnorableProperties, this._systemDefaults.valsToConvert); this._layoutManager.setLayout(lowerCasedProps); newLayout = this._layoutManager.getLayout(); // if new and old layout are same then dont trigger event if (!isEqualObject(prevLayout, newLayout)){ // trigger layoutChanged public event this.trigger('layoutchanged', { layout: newLayout, viewport }); if (newLayout.type !== prevLayout.type){ this.trigger('layouttypechanged', { layout: newLayout, viewport, layoutType: newLayout.type, prevLayoutType: prevLayout.type }); } // trigger updated public event this.trigger('updated', { gridConfig: this._parsedGridConfig }); } return this; } getAllViewports() { return this._viewportManager.getAllViewports(); } getCurrentViewport(): Viewport { return this._viewportManager.getCurrentViewport(); } addViewport(viewportName: string, config: Viewport) { this._viewportManager.addViewport({ ...{ name: viewportName }, ...config }); this._configure(); } setRowOptions(param: any, value: any){ let parsedGridConfig = this._parsedGridConfig, gridDataConfig = this._gridData.gridConfig, logger = this._logger, fomattedValue: any, setStylingOptions = (option: any, val: any) => { if (option === 'style') { // eslint-disable-next-line dot-notation parsedGridConfig.rowoptions!.style = gridDataConfig.rowoptions!.style = val; } else if (option === 'class') { // eslint-disable-next-line dot-notation parsedGridConfig.rowoptions!.class = gridDataConfig.rowoptions!.class = val; } else if (option === 'hover') { // eslint-disable-next-line dot-notation parsedGridConfig.rowoptions!.hover = gridDataConfig.rowoptions!.hover = val; } this._updateGridDataStore(); }; if (typeof param === 'string') { let paramInLowerCase = param.toLowerCase(); fomattedValue = typeof value === 'object' ? convertKeysToLowerCase(value, this._systemDefaults.lowerCaseIgnorableProperties) : value; if (!isRowStylingOption(param)) { (paramInLowerCase === 'selection') && this._shouldResetRowSelectedState(fomattedValue); if (paramInLowerCase === 'rowheight') { let tmpRowHeight = parseLengthUnit(fomattedValue, this._lengthUnitsObject, 'height'); // if user provdies invalid row height ignoring the value and logging warning message (tmpRowHeight) ? this._layoutManager.setRowOptions('pxRowHeight', tmpRowHeight) : logger.error(GridException.invalidRowHeight, fomattedValue); } else if (paramInLowerCase === 'headerrowheight') { let tmpHeaderRowHeight = parseLengthUnit(fomattedValue, this._lengthUnitsObject, 'height'); // if user provdies invalid header row height ignoring the value and logging warning message (tmpHeaderRowHeight) ? this._layoutManager.setRowOptions('pxHeaderRowHeight', tmpHeaderRowHeight) : logger.error(GridException.invalidHeaderRowHeight, fomattedValue); } else { this._layoutManager.setRowOptions(paramInLowerCase, fomattedValue); } } else { setStylingOptions(param, fomattedValue); } } else if (typeof param === 'object') { const lowerCasedParam = convertKeysToLowerCase(param, this._systemDefaults.lowerCaseIgnorableProperties, this._systemDefaults.valsToConvert), parsedRowOptions: any = this._buildRowOptions(lowerCasedParam, this._systemDefaults.rowOptions); for (let key in parsedRowOptions) { if (!isRowStylingOption(param)) { (key === 'selection') && this._shouldResetRowSelectedState(parsedRowOptions[key]); this._layoutManager.setRowOptions(key, parsedRowOptions[key]); } else { setStylingOptions(key, parsedRowOptions[key]); } } } // trigger updated public event this.trigger('updated', { gridConfig: this._parsedGridConfig }); return this; } private _shouldResetRowSelectedState (newRowSelection: RowSelection) { let prevRowSelectionState: RowSelection = this._parsedGridConfig.rowoptions && this._parsedGridConfig.rowoptions.selection || {}, isEnableChanged = prevRowSelectionState.enable !== newRowSelection.enable, isShowCheckBoxChanged = prevRowSelectionState.enableselectioncheckbox !== newRowSelection.enableselectioncheckbox, shouldResetRowSelectedState = false; /** * reset rowselectedstate for the given scenarios * 1. selection enable changed, when enable set to true, and enableselectioncheckbox is changed or not changed but prev value was true * 2. selection enable changed, enable set to false and previously enableselectioncheckbox was true * 3. enable not changed, and still true, enableselection checkbox values set to true as well */ if (isEnableChanged) { if (newRowSelection.enable && newRowSelection.enableselectioncheckbox) { shouldResetRowSelectedState = true; } else if (!newRowSelection.enable && prevRowSelectionState.enableselectioncheckbox) { shouldResetRowSelectedState = true; } } else { if (newRowSelection.enable && isShowCheckBoxChanged) { shouldResetRowSelectedState = true; } } if (shouldResetRowSelectedState) { this._gridData.resetRowSelectedState(); this._gridData.resetGlobalSelectedState(); this._gridData.setLatestSelectedRow(UNDEF); } } getRowOptions(optionVal : string | string[] |undefined): any{ let option: string, rowOptions = this._gridData.gridConfig.rowoptions!, returnObj:ObjectTraverser = {}, getOptionValue = (opt: string): any=>{ if (!isRowStylingOption(opt)){ return this._layoutManager.getRowOption(opt); } if (opt === 'style'){ return rowOptions.style; } else if (opt === 'class'){ // eslint-disable-next-line dot-notation return rowOptions.class; } else if (opt === 'hover'){ return rowOptions.hover; } }; if (typeof optionVal === 'undefined'){ return rowOptions; } else if (typeof optionVal === 'string'){ option = optionVal.toLowerCase(); return getOptionValue(option); } else if (Array.isArray(optionVal) && optionVal.length){ for (let i = 0; i < optionVal.length; i++){ option = optionVal[i].toLowerCase(); returnObj[option] = getOptionValue(option); } return returnObj; } } enableAutoViewportSwitch() { this._viewportManager.setSwitch(true); } disableAutoViewportSwitch() { this._viewportManager.setSwitch(false); } private _updateGridDataStore() { this._stores.visualUtils.update((tmpVisualUtils: any) => { tmpVisualUtils.gridData = this._gridData; return tmpVisualUtils; }); } /** * Build parsed column options * @param columns Column options array * @param defaultColumnOptions Default column option to merge with each column option * @param isReplaceEmptyConfigWithDT If column options is not provided then should columns be generated from the DataTable */ private _buildColumnOptions(columnOptions: ColumnOptions[], defaultColumnOptions: ColumnOptions, isReplaceEmptyConfigWithDT: boolean) { let parsedConfigColumns: DerivedColumnOptions[] = [], columnConfigOptions; if (Array.isArray(columnOptions) && columnOptions.length) { columnConfigOptions = this._treeNodeTraversal(columnOptions, 1, defaultColumnOptions); parsedConfigColumns = this._getColumnConfig(columnConfigOptions.leafColumnOptions, defaultColumnOptions); } else if (isReplaceEmptyConfigWithDT) { this._logger.warn(GridWarningMessage.columnsBuiltFromSchema); parsedConfigColumns = this._gridDataTable.gridSchema.reduce((columns: DerivedColumnOptions[], val) => { if (val.name !== '_row_id') { columns.push(mergeDeep(defaultColumnOptions, { field: val.name, headertext: captalizeFirstLetter(val.name), type: deriveColumnTypeFromSchema(val.type || 'text'), sortable: val.sortable })); } return columns; }, []); } return { parsedConfigColumns, columnGroups: columnConfigOptions && columnConfigOptions.columnGroups, }; } /** * Function to start traversing the columns tree using DFS method and return the groups and leaf node list * @param columnOptions - Column options array containing groups * @param level - Initial level of the tree(1) * @param defaultColumnOptions - Default options of columns */ private _treeNodeTraversal(columnOptions: ColumnOptions[], level: Number, defaultColumnOptions: ColumnOptions){ let leafColumnOptions: any = [], columnGroups: any = []; columnOptions.forEach((col: any) => { this._nodeDFStraversal(col, level, leafColumnOptions, columnGroups , defaultColumnOptions, 1); }); return { leafColumnOptions, columnGroups, }; } /** * * @param col - Each node of tree * @param level - Current level of traversal * @param leafColumnOptions - Array to push leaf nodes * @param columnGroups - Array to push group nodes * @param defaultColumnOptions - Default column options * @param nodeHeight - Height of tree till the current node */ private _nodeDFStraversal(col: any, level: any, leafColumnOptions: any, columnGroups: any, defaultColumnOptions: any, nodeHeight: any){ if(col.children && col.children.length){ let groupConfig = { headerName: col.headername, level, childStartIndex: leafColumnOptions.length, headerClass: col.headerclass, headerStyle: col.headerstyle, childCount: 0, width: 0, totalHeight: 0 }, totalLevel = this._gridConfig.totalLevel, leafChildNodes = 0, totalHeight = nodeHeight; if(!totalLevel || level > totalLevel ){ this._gridConfig.totalLevel = level; } col.children.forEach((subNode: any) => { let childHeight = nodeHeight, childNodes = this._nodeDFStraversal(subNode, level + 1, leafColumnOptions, columnGroups, defaultColumnOptions, nodeHeight); leafChildNodes += childNodes.leafNodeCount; childHeight += childNodes.nodeHeight; if(childHeight > totalHeight){ totalHeight = childHeight; } }); groupConfig.childCount = leafChildNodes; groupConfig.width = defaultColumnOptions.width *leafChildNodes; groupConfig.totalHeight = totalHeight; columnGroups.push(groupConfig); return { leafNodeCount: leafChildNodes, nodeHeight: totalHeight } ; } // If leaf node, then add directly in result leafColumnOptions.push(col); // return count of leaf node to determine width of group cell // return node Height to determine height of group cell return { leafNodeCount: 1, nodeHeight: 1 }; } /** * Function to create config for columns array (excluding group cells) * @param columnOptions - Columns array without groups * @param defaultColumnOptions - Default options for columns */ private _getColumnConfig(columnOptions: ColumnOptions[], defaultColumnOptions: ColumnOptions) { const logger = this._logger; let parsedConfigColumns: DerivedColumnOptions[] = [], calculatedMinColumnWidth: number = getMinColumnWidth(); parsedConfigColumns = columnOptions.reduce((columns: DerivedColumnOptions[], val) => { const mergedObj: DerivedColumnOptions = mergeDeep(defaultColumnOptions, val); mergedObj.pxWidth = parseLengthUnit(mergedObj.width, this._lengthUnitsObject, 'width') || DEFAULT_COLUMN_WIDTH; mergedObj.pxMinWidth = Math.max(parseLengthUnit(mergedObj.minwidth, this._lengthUnitsObject, 'width')! , calculatedMinColumnWidth); mergedObj.pxMaxWidth = parseLengthUnit(mergedObj.maxwidth, this._lengthUnitsObject, 'width'); let inclusionFlag = true, minWidth = mergedObj.pxMinWidth || -Infinity, maxWidth = mergedObj.pxMaxWidth || +Infinity; if (mergedObj.type !== ColumnType.HTML) { // if type is not html then it must have a field attribute which points to a column in DataTable schema if (typeof mergedObj.field === 'undefined') { logger.error(GridException.fieldNotFound); inclusionFlag = false; } else { let valueTextAlignmentTypeArr = getAllValuesofObj(ValueTextAlignmentType), valueTextPositionTypeArr = getAllValuesofObj(ValueTextPositionType); // If type is not mentioned then type should be determined from the DataTable schema type if (typeof mergedObj.type === 'undefined' || mergedObj.type === ColumnType.Chart) { let derivedType = this._gridDataTable.getColumnType(mergedObj.field); // After the above step type can still be null under only one condition - if the field is not found in schema if (typeof derivedType === 'undefined') { logger.error(formatString(GridException.fieldError, mergedObj.field)); inclusionFlag = false; } // for chart type column - clone the chart config to form derived chart config and calculate the pxBarHeight if (mergedObj.type === ColumnType.Chart && typeof mergedObj.chartconfig !== 'undefined') { mergedObj.derivedChartConfig = clone(mergedObj.chartconfig); const valueTextPos = mergedObj.derivedChartConfig.valuetextposition, valueTextAlign = mergedObj.derivedChartConfig.valuetextalignment; if (valueTextPos && valueTextPositionTypeArr.indexOf(valueTextPos.toLowerCase()) === -1) { mergedObj.derivedChartConfig.valuetextposition = DEFAULT_VALUE_TEXT_POSITION; } if (valueTextAlign && valueTextAlignmentTypeArr.indexOf(valueTextAlign.toLowerCase()) === -1) { mergedObj.derivedChartConfig.valuetextalignment = DEFAULT_VALUE_TEXT_ALIGNMENT; } if (typeof mergedObj.derivedChartConfig.barheight !== 'undefined') { mergedObj.derivedChartConfig.pxBarHeight = parseLengthUnit(mergedObj.derivedChartConfig.barheight, this._lengthUnitsObject, 'height'); } else if (valueTextPos === ValueTextPositionType.top || valueTextPos === ValueTextPositionType.bottom) { // if bar height is not specified then top and bottom position is invalid // in those cases position will be revreted to default mergedObj.derivedChartConfig.valuetextposition = DEFAULT_VALUE_TEXT_POSITION; } } // The inline chart field must be of type number, otherwise log error and set the field type to it's base type if (mergedObj.type === ColumnType.Chart && derivedType !== ColumnType.Number) { logger.error(formatString(GridException.inlineChartTypeMismatch, mergedObj.field)); mergedObj.type = derivedType; } else if (typeof mergedObj.type === 'undefined') { mergedObj.type = derivedType; } } } } if (inclusionFlag) { // Check and set sortable default state if (typeof mergedObj.sortable === 'object') { // by default sortable is disabled but if someone has provided some config to sortable but has not specified the enable flag then it must be enabled if (typeof mergedObj.sortable.enable !== 'boolean') { mergedObj.sortable.enable = true; } } // Check and set hover default state if (typeof mergedObj.hover === 'object') { // by default hover is disabled but if someone has provided some config to hover but has not specified the enable flag then it must be enabled if (typeof mergedObj.hover.enable !== 'boolean') { mergedObj.hover.enable = true; } } // Check and set tooltip default state if (typeof mergedObj.tooltip === 'object') { // by default tooltip is disabled but if someone has provided some config for tooltip but has not specified the enable flag then it must be enabled if (typeof mergedObj.tooltip.enable !== 'boolean') { mergedObj.tooltip.enable = true; } // if tooltip is enabled but individual header or cell tooltip is not defined then make them true by default if (mergedObj.tooltip.enable === true) { if (typeof mergedObj.tooltip.enableheadertooltip !== 'boolean') { mergedObj.tooltip.enableheadertooltip = true; } if (typeof mergedObj.tooltip.enablecelltooltip !== 'boolean') { mergedObj.tooltip.enablecelltooltip = true; } } // by default helper icon is disabled so if user has not specified anything explicitly then set it false if (typeof mergedObj.tooltip.enablehelpericon !== 'boolean') { mergedObj.tooltip.enablehelpericon = false; } // if helper icon is enabled but individual header or cell helper icon is not defined then make them true by default if (mergedObj.tooltip.enablehelpericon === true) { if (typeof mergedObj.tooltip.enableheaderhelpericon !== 'boolean') { mergedObj.tooltip.enableheaderhelpericon = true; } if (typeof mergedObj.tooltip.enablecellhelpericon !== 'boolean') { mergedObj.tooltip.enablecellhelpericon = true; } } } // If header text is not defined then use the field name as header text by making the first letter of field caps if (typeof mergedObj.headertext === 'undefined') { mergedObj.headertext = captalizeFirstLetter(mergedObj.field) || ''; } if (minWidth > maxWidth) { logger.error(formatString(GridException.minGreaterMaxWidth, mergedObj.headertext || '')); } else { if (mergedObj.pxWidth < minWidth) { mergedObj.pxWidth = minWidth; } else if (mergedObj.pxWidth > maxWidth) { mergedObj.pxWidth = maxWidth; } } columns.push(mergedObj); } return columns; }, []); return parsedConfigColumns; } /** * Build row options * @param rowOptions Row options * @param baseOptions Base row options */ private _buildRowOptions(rowOptions: RowOptions, baseOptions: RowOptions): DerivedRowOptions { const logger = this._logger, mergedOptions: DerivedRowOptions = mergeDeep(baseOptions, rowOptions); if (typeof mergedOptions.hover === 'object') { if (typeof mergedOptions.hover.enable !== 'boolean') { mergedOptions.hover.enable = true; } } if(mergedOptions.selection) { if (typeof mergedOptions.selection === 'object') { if (typeof mergedOptions.selection.enable !== 'boolean') { mergedOptions.selection.enable = true; } if (typeof mergedOptions.selection.enableselectioncheckbox !== 'boolean') { mergedOptions.selection.enableselectioncheckbox = false; } } else { mergedOptions.selection = { enable: true }; } } mergedOptions.pxRowHeight = parseLengthUnit(mergedOptions.rowheight, this._lengthUnitsObject, 'height') || DEFAULT_ROW_HEIGHT; mergedOptions.pxHeaderRowHeight = parseLengthUnit(mergedOptions.headerrowheight, this._lengthUnitsObject, 'height') || DEFAULT_ROW_HEIGHT; return mergedOptions; } /** * Parse, sanitize and build the pagination object * @param pagination lowercased pagination object */ private _buildPagination(pagination: Pagination): Pagination { const logger = this._logger; if (typeof pagination !== 'undefined') { if (typeof pagination.enable !== 'boolean') { pagination.enable = true; } if (typeof pagination.showrecordcount !== 'boolean') { pagination.showrecordcount = false; } if (typeof pagination.showjumptoendbuttons !== 'boolean') { pagination.showjumptoendbuttons = false; } if (typeof pagination.showjumptofirstpagebutton !== 'boolean') { pagination.showjumptofirstpagebutton = pagination.showjumptoendbuttons; } if (typeof pagination.showjumptolastpagebutton !== 'boolean') { pagination.showjumptolastpagebutton = pagination.showjumptoendbuttons; } let showPages = pagination.showpages, pageSize = pagination.pagesize; if (typeof showPages === 'object') { if (typeof showPages.enable !== 'boolean') { showPages.enable = true; } if (typeof showPages.showtotal !== 'boolean') { showPages.showtotal = false; } if (typeof showPages.userinput !== 'boolean') { showPages.userinput = false; } } else { pagination.showpages = {}; } if (typeof pageSize === 'object') { // if options is an array then sanitize default value and options array if (Array.isArray(pageSize.options) && pageSize.options.length > 0) { // filter out the non numeric options pageSize.options = pageSize.options.filter(x => isNaturalNumber(x)); // if invalid default value is provided then first value of options is set as default if (pageSize.options.length === 0) { logger.error(GridException.invalidPageSizeOptionsArr); } else if (!isNaturalNumber(pageSize['default']) || (typeof pageSize['default'] !== 'undefined' && pageSize.options.indexOf(pageSize['default']) === -1)) { logger.error(GridException.invalidDefaultPageSize); pageSize['default'] = pageSize.options[0]; } } else if (typeof pageSize.options !== 'undefined' && typeof pageSize.options !== 'boolean') { // if options is neither undefined or a valid boolean or a number array then set options as false logger.error(GridException.invalidPageSizeOptions); pageSize.options = false; } // if default value is provided by the user then floor that value to avoid fractional numbers if (typeof pageSize['default'] !== 'undefined'){ pageSize['default'] = Math.floor(+pageSize['default']); } } else { pagination.pagesize = {}; } } return pagination; } /** * Parse, sanitize and merge config to build a sanitized grid config */ private _buildGridConfig(): GridConfig { const lowerCasedConfig = convertKeysToLowerCase(this._gridConfig, this._systemDefaults.lowerCaseIgnorableProperties, this._systemDefaults.valsToConvert) || {}, // convert all user provided JSON keys to lower case to avoid any errors userDefaultColumnOptions: ColumnOptions = lowerCasedConfig.defaultcolumnoptions || {}, // in grid config user can provide a generic default column options, if something is not defined in individual column then configuration will be picked from here defaultColumnOptions: ColumnOptions = this._defaultColumnOptions = mergeDeep(this._systemDefaults.columnOptions, userDefaultColumnOptions), gridConfigColumns: ColumnOptions[] = lowerCasedConfig.columns || [], rowOptions: DerivedRowOptions = this._buildRowOptions(lowerCasedConfig.rowoptions || {}, this._systemDefaults.rowOptions), pagination: Pagination = this._buildPagination(lowerCasedConfig.pagination), columnConfigOptions = this._buildColumnOptions(gridConfigColumns, defaultColumnOptions, true); let { parsedConfigColumns, columnGroups } = columnConfigOptions; return { columns: parsedConfigColumns, columnGroups, layout: lowerCasedConfig.layout, rowoptions: rowOptions, pagination, viewports: lowerCasedConfig.viewports || {}, events: lowerCasedConfig.events, defaultcolumnoptions: defaultColumnOptions, defaultColGroupOptions: this._gridConfig.defaultColGroupOptions }; } private _updateVizRecDomainStartEnd (start: number, end: number) { let logger = this._parsedGridConfig; this._stores.vizRecordDomain.update((tmpVizRecDomain: VizRecordDomainConfig)=>{ tmpVizRecDomain.start = start; tmpVizRecDomain.end = end; return tmpVizRecDomain; }); } sizeColumnsToContent(columnIndexes: number[]) { let inputColumnIndexes: number[] = (arguments.length > 0) ? columnIndexes : [], validColumns: columnIndexWithMinMaxContent[] = [], invalidColums: any[] = [], parsedGridLen: number = this._parsedGridConfig.columns.length; if (!inputColumnIndexes.length) { for (let index = 0; index < parsedGridLen; index++) { let field = this._parsedGridConfig.columns[index].field; if (field) { validColumns.push({ columnIndex: index, content: this._gridData.getMinMaxContent(field) || {} }); } } } else { for (let index = 0; index < inputColumnIndexes.length; index++) { const colIndex: any = inputColumnIndexes[index]; if(colIndex === parseInt(colIndex, 10)) { validColumns.push(colIndex); } else { invalidColums.push(colIndex); } } } if (document && this._container) this._layoutManager.sizeColumnsToContent(validColumns, document, this._container); // LOG-WARN if (invalidColums.length > 0) this._logger.error(GridException.sizeColumnsToContentFailedForColumn, invalidColums); } /** * @description Select specific rows by row index. * @param rowIndexArr array containing rowIndexes to be selected. * @returns FusionGrid instance */ selectRows (rowIndexArr: any[]| number): FusionGrid { let rowSlectionStateObj: {validRowArray: number[], inValidRowArray: number[]}, gridDataLen:number = this._gridData.getRowSelectedState().length, currentRowSelection = this._parsedGridConfig.rowoptions!.selection || {}, { enableselectioncheckbox: isCheckboxEnabled, enable: isSelectionEnabled } = currentRowSelection; if (!isSelectionEnabled) { this._logger.error(ROW_SELECTION_ERROR_MESSAGE.enableSelection); return this; } // when multiselection not enabled but array of rowindex more than one is passed if (!isCheckboxEnabled && Array.isArray(rowIndexArr) && rowIndexArr.length > 1) { this._logger.error(ROW_SELECTION_ERROR_MESSAGE.enableMultiRowSelectionForRowIndicesSelection, rowIndexArr); return this; } else if (!isCheckboxEnabled && (Array.isArray(rowIndexArr) && rowIndexArr.length === 1 || typeof rowIndexArr === 'number')) { let gridData = this._gridData, finalRowIndex = Array.isArray(rowIndexArr) ? rowIndexArr[0] : rowIndexArr, prevRowSelectionIndex = gridData.getLatestSelectedRow(); //only when selecting a different and valid row if (prevRowSelectionIndex !== finalRowIndex && (finalRowIndex === parseInt(finalRowIndex,10)) && (finalRowIndex >= 0 && finalRowIndex < gridDataLen)) { gridData.setLatestSelectedRow(finalRowIndex); (typeof prevRowSelectionIndex !== 'undefined') && gridData.setRowSelection(prevRowSelectionIndex, !gridData.getRowSelection(prevRowSelectionIndex)); } } rowSlectionStateObj = this._gridData.setRowsSelected(rowIndexArr, true); if(rowSlectionStateObj.validRowArray.length > 0) this._updateGridDataStore(); // LOG-WARN if(rowSlectionStateObj.inValidRowArray.length > 0) this._logger.error(ROW_SELECTION_ERROR_MESSAGE.invalidRowIndexs, rowSlectionStateObj.inValidRowArray); return this; } /** * @description deselects rows * @param rowIndexArr array containing rowIndexes to be deselected. * @returns FUsionGrid instance */ deselectRows (rowIndexArr: any[]): FusionGrid { let rowSlectionStateObj: {validRowArray: number[], inValidRowArray: number[]}, currentRowSelection = this._parsedGridConfig.rowoptions!.selection || {}, { enableselectioncheckbox: isCheckboxEnabled, enable: isSelectionEnabled } = currentRowSelection; if (!isSelectionEnabled) { this._logger.error(ROW_SELECTION_ERROR_MESSAGE.enableSelection); return this; } if (!isCheckboxEnabled && Array.isArray(rowIndexArr) && rowIndexArr.length > 1) { this._logger.error(ROW_SELECTION_ERROR_MESSAGE.enableMultiRowSelectionForRowIndicesDeselection, rowIndexArr); return this; } rowSlectionStateObj = this._gridData.setRowsSelected(rowIndexArr, false); if(rowSlectionStateObj.validRowArray.length > 0) this._updateGridDataStore(); // LOG-WARN if(rowSlectionStateObj.inValidRowArray.length > 0) this._logger.error(ROW_SELECTION_ERROR_MESSAGE.invalidRowIndexs, rowSlectionStateObj.inValidRowArray); return this; } /** * @description gets all rows marked selected * @returns gets all rows marked selected */ getSelection () { let selectedRows: any[], rowSelectedState, currentRowSelection = this._parsedGridConfig.rowoptions!.selection || {}, schema = this._gridData.gridDataTable.gridSchema.slice(0, -1), { enable: isSelectionEnabled } = currentRowSelection; if (!isSelectionEnabled) { this._logger.error(ROW_SELECTION_ERROR_MESSAGE.enableSelection); return this; } selectedRows = [], rowSelectedState = this._gridData.getRowSelectedState(); for (let index = 0; index < rowSelectedState.length; index++) { const state = rowSelectedState[index]; if(state)selectedRows.push(this._gridData.gridDataTable.gridData[index]); } return { selectedRows, headers: schema }; } /** * @description if any row(s) selected * @returns true if any row(s) are selected on the grid or false when no row(s) are selected on the grid. */ isSelected () { let selectedRowArr: number[], rowSelectedState, currentRowSelection = this._parsedGridConfig.rowoptions!.selection || {}, { enable: isSelectionEnabled } = currentRowSelection; if (!isSelectionEnabled) { this._logger.error(ROW_SELECTION_ERROR_MESSAGE.enableSelection); return false; } selectedRowArr = [], rowSelectedState = this._gridData.getRowSelectedState(); if(rowSelectedState.some((selectedState) => !!selectedState)) { return true; } return false; } /** * @description Select all rows. Even rows which are not visible due to pagination. * But any rows which are filtered out are not selected. * @returns FusionGrid instance */ selectAll (): FusionGrid { let currentRowSelection = this._parsedGridConfig.rowoptions!.selection || {}, { rowselection: selectionType, enable: isSelectionEnabled } = currentRowSelection; if (!isSelectionEnabled) { this._logger.error(ROW_SELECTION_ERROR_MESSAGE.enableSelection); return this; } if (selectionType !== "multiple") { this._logger.error(ROW_SELECTION_ERROR_MESSAGE.enableMultiRowSelectionForAllRowsSelection); return this; } this._gridData.setGlobalSelectedState(true); this._gridData.syncAllRowSelectedStateWithGlobalSelection(UNDEF); this._updateGridDataStore(); return this; } /** * @description Select all rows. Even rows which are not visible due to pagination. * But any rows which are filtered out are not selected. * @returns FusionGrid instance */ selectAllWithFilter (): FusionGrid { if(this._gridData.gridDataTable.viewData.length !== this._gridData.gridDataTable.gridData.length) { const indexesInView = this._gridData.gridDataTable.viewData.map((row) => row[this._gridData.gridConfig.columns.length] - 1); this._gridData.setRowsSelected(indexesInView, true); } else { this.selectAll(); } return this; } /** * @description deselect all rows. Even rows which are not visible due to pagination. * But any rows which are filtered out are not selected. * @returns FusionGrid instance */ clearSelection (): FusionGrid { let currentRowSelection = this._parsedGridConfig.rowoptions!.selection || {}, { rowselection: selectionType, enable: isSelectionEnabled } = currentRowSelection; if (!isSelectionEnabled) { this._logger.error(ROW_SELECTION_ERROR_MESSAGE.enableSelection); return this; } if (selectionType !== "multiple") { this._logger.error(ROW_SELECTION_ERROR_MESSAGE.enableMultiRowSelectionForAllRowsDeselection); return this; } this._gridData.setGlobalSelectedState(false); this._gridData.syncAllRowSelectedStateWithGlobalSelection(UNDEF); this._updateGridDataStore(); return this; } /* * @description When this method is called, grid columns are automatically resized to fit the container. * All columns will be assigned an equal width. If the cell content is more than the column width then ellipsis will appear. * This method will respect user provided minimum and maximum width of a column, but width value will be overridden. * @returns FusionGrid */ sizeColumnsToFit(): FusionGrid { let rowSelection = this._parsedGridConfig.rowoptions && this._parsedGridConfig.rowoptions.selection, isSizeToFitApplied, containerOffsetWidth = (rowSelection && rowSelection.enable && rowSelection.enableselectioncheckbox) ? this._container.offsetWidth - DEFAULT_CHECKBOX_COLUMN_WIDTH : this._container.offsetWidth; isSizeToFitApplied = this._layoutManager.sizeColumnsToFit(0, this._parsedGridConfig.columns.length, containerOffsetWidth); // LOG-WARN if(!isSizeToFitApplied) this._logger.error(GridException.sizeColumnsToFitFailed); return this; } /** * @description gets current page size * @returns Number current page size */ getPageSize(): number{ return this._pagination.getPageSize(); } /** * @description sets current page size * @param pageSize */ setPageSize(pageSize: number): FusionGrid{ this._pagination.setPageSize(pageSize); return this; } /** * @description gets current page number * @returns Number current page number */ getCurrentPage(): number{ return this._pagination.getCurrentPage(); } /** * @description gets total number of pages * @returns Number total number of pages */ getTotalPages(): number{ return this._pagination.getTotalPages(); } /** * @description gets total number of rows * @returns Number total number of rows */ getRowCount():number{ return this._pagination.getRowCount(); } /** * @description jumps to specified page number * @param pageNumber page number */ jumpToPage(pageNumber: number): FusionGrid{ if (!isWholeNumber(pageNumber)) { this._logger.error(PaginationException.invalidPageNumber); } this._pagination.jumpToPage(pageNumber); return this; } /** * @description method for jumping to the next page */ jumpToNextPage(): FusionGrid{ this._pagination.jumpToNextPage(); return this; } /** * @description method for jumping to the previous page */ jumpToPreviousPage(): FusionGrid{ this._pagination.jumpToPreviousPage(); return this; } /** * @description method for jumping to the first page */ jumpToFirstPage(): FusionGrid{ this._pagination.jumpToFirstPage(); return this; } /** * @description method for jumping to the last page */ jumpToLastPage(): FusionGrid{ this._pagination.jumpToLastPage(); return this; } /** * @description method setting pagination properties * @param param property name or object * @param value property value */ setPagination(param: any, value: any){ let pagination = this._pagination, paginationEnable: boolean, paginationHeight: number = pagination.paginationHeight, formattedValue: any; if (typeof param === "object"){ const lowerCasedParam = convertKeysToLowerCase(param, this._systemDefaults.lowerCaseIgnorableProperties, this._systemDefaults.valsToConvert); for (var key in lowerCasedParam){ pagination.setPagination(key, lowerCasedParam[key]); } } else if (typeof param === "string"){ formattedValue = typeof value === "object" ? convertKeysToLowerCase(value, this._systemDefaults.lowerCaseIgnorableProperties, this._systemDefaults.valsToConvert) : value; pagination.setPagination(param.toLowerCase(), value); } paginationEnable = pagination.config.enable; if (pagination.shouldMutateStore){ pagination.reconfigureState(); } this._stores.gridDimensions.update((dim: GridDimensions)=>{ dim.height = this._container.offsetHeight - DEFAULT_CREDIT_LABEL_HEIGHT - (paginationEnable ? paginationHeight : 0); return dim; }); } /** * @description method for getting pagination properties * @param param pagination properties * @return pagination config */ getPagination(param:any): Pagination{ return this._pagination.getPagination(param); } /** * Function to create credit label in case of invalid license key, post rendering */ addCreditLabel () { let parent: any, fusionText, trialText, shapeCont, hasCreditLabel, wrapperStyle = "margin-top: 13.5px;cursor: pointer;display: inline-block", containerStyle = "height: 14px;width: 14px;position: relative;display: inline-block;vertical-align: bottom;", textStyle = "width: 88px;height: 12px;flex-grow: 0;font-family: 'Source Sans Pro';font-size: 12px;font-weight: bold;font-stretch: normal;font-style: normal;line-height: 1;letter-spacing: 0.3px;text-align: left;color: #60728b;margin-left: 4px;", trialTextStyle = "font-weight: 500;", shapeStyle = "width: 2px;height: 2px;background-color: #60728b;position: absolute;"; this._stores.hasCreditLabel.subscribe((value: Boolean) => { hasCreditLabel = value; }); this._stores.creditLabelElement.subscribe((element: any) => { parent = element; }); // The following condition covers two cases // 1. creditLabel is provided and value is set to true // 2. creditLabel is not provided and license key is invalid // eslint-disable-next-line no-undefined if(hasCreditLabel){ if(parent && parent.parentNode){ parent.parentNode!.removeChild(parent); } this._stores.observer.disconnect(); parent = document.createElement('div'); parent.setAttribute('style', wrapperStyle); parent.addEventListener('click', this.handleCreditLabelClick); fusionText = document.createElement('span'); fusionText.setAttribute('style', textStyle); fusionText.textContent = 'FusionGrid '; trialText = document.createElement('span'); trialText.setAttribute('style', trialTextStyle); trialText.textContent = 'Trial'; shapeCont = document.createElement('div'); shapeCont.setAttribute('style', containerStyle); for(let i = 0;i < 10; i++){ let shape = document.createElement('div'), shapeInlineStyle = shapeStyle; switch(i){ case 2: shapeInlineStyle += 'margin-left: 4px;'; break; case 3: shapeInlineStyle += 'margin-left: 8px;'; break; case 4: shapeInlineStyle += 'margin-top: 4px;'; break; case 5: shapeInlineStyle += 'margin-left: 4px;margin-top: 4px;'; break; case 6: shapeInlineStyle += 'margin-left: 8px;margin-top: 4px;'; break; case 7: shapeInlineStyle += 'margin-top: 8px;'; break; case 8: shapeInlineStyle += 'margin-left: 4px;margin-top: 8px;'; break; case 9: shapeInlineStyle += 'margin-left: 8px;margin-top: 8px;'; break; default: break; } shape.setAttribute('style', shapeInlineStyle); shapeCont.append(shape); } fusionText.appendChild(trialText); parent.appendChild(fusionText); parent.insertBefore(shapeCont,fusionText ); this._container.appendChild(parent); this.attachObserver(this._container); this._stores.creditLabelElement.set(parent); } } /** * Function to attach mutation observer * @param crElement */ attachObserver(crElement: any){ this._stores.observer = new MutationObserver(this.addCreditLabel.bind(this)); this._stores.observer.observe(crElement, { childList: true, subtree: true, attributes: true, characterData: true }); } /** * Function to remove credit label in-case of valid license key, post rendering */ removeCreditLabel(){ let crElement : any; this._stores.creditLabelElement.subscribe((element: any) => { crElement = element; }); if(crElement){ this._stores.observer.disconnect(); crElement && crElement.parentNode && crElement.parentNode!.removeChild(crElement); } } /* Function to get data as per the current view * @param data * @param schema */ getViewData(data: any, schema: any){ let viewData: any = [], viewSchema: any = [], columnIndexMap = this._gridData.getColumnIndexMap(); data.map((row: any) => { let tempRow: any= []; columnIndexMap.map((index: any) => { tempRow.push(row[index]); return true; }); viewData.push(tempRow); return true; }); columnIndexMap.map((index: any) => { viewSchema.push(schema[index]); return true; }); return { data: viewData, schema: viewSchema }; } export(config: any) { let parsedConfig = convertKeysToLowerCase(config), { format, mode, skipcolumnheaders, skipcolumnheadergroups } = parsedConfig, isModeView = mode === 'view', data = isModeView ? this._gridData.gridDataTable.viewData : this._gridData.gridDataTable.gridData, formattedData: any = [], csvContent = '', schema = this._gridData.gridDataTable.gridSchema.slice(0, -1), blobType, filename, blob, link, viewDetails, exportData; this.trigger('exporttriggered', { gridConfig: this._parsedGridConfig, container: this._container, exportConfig: parsedConfig }); data.forEach(function(rowArray: any) { const rowArrayWithRemovedIndex = rowArray.slice(0, -1); let row = rowArrayWithRemovedIndex.map((item: any, itemIndex: number)=> { if(schema[itemIndex] && schema[itemIndex].type === "datetime") { return new Intl.DateTimeFormat('en-US').format(new Date(item)); } return item; }); formattedData.push(row); }); if(isModeView) { viewDetails = this.getViewData(formattedData, schema); } else { viewDetails = { data: formattedData, schema }; } // For IOS devices, file is not downloaded, instead data is displayed if(isIOS){ format = 'csv'; } switch(format) { case 'csv': blobType = 'text/csv'; filename = 'grid_data.csv'; csvContent = this.jsonToCsv(viewDetails.data, viewDetails.schema, isModeView ? this._parsedGridConfig.columnGroups : [] , skipcolumnheaders, skipcolumnheadergroups); // Using Blob to export as CSV blob = new Blob([csvContent], { type: blobType }); link = document.createElement("a"); link.setAttribute("href", window.URL.createObjectURL(blob)); link.setAttribute("download", filename); document.body.appendChild(link); link.click(); break; case 'xlsx': exportData = parseDataForExcelExport(viewDetails.data, viewDetails.schema); // Using json-to-xlsx npm package to export as XLSX xlsx(exportData, { fileName: 'grid_data', extraLength: 3, writeOptions: {} }); break; case 'json': this.trigger('exported', { gridConfig: this._parsedGridConfig, container: this._container, exportConfig: parsedConfig, data: viewDetails.data, scehma: viewDetails.schema, }); return viewDetails; default: return; } this.trigger('exported', { gridConfig: this._parsedGridConfig, container: this._container, exportConfig: parsedConfig, data: viewDetails.data, scehma: viewDetails.schema, }); return true; } sort(sortConfig: any){ let { columnIndex, column, order } = sortConfig, sortOrder; // Field name / index check if(!Number(columnIndex) && column){ let schema = this.getDataTable().getSchema(), fieldIndex = schema.findIndex((sch) => sch.name === column); // eslint-disable-next-line no-undefined if(fieldIndex !== undefined){ columnIndex = fieldIndex; } } // Set order if given if(order) { if(order === 'asc'){ sortOrder = 'none'; } else if(order === 'desc') { sortOrder = 'asc'; } else if(order === 'none') { sortOrder = 'desc'; } } this._gridData.setSortDetails({ order: sortOrder, cellIndex: columnIndex, parentDataIndex: this._gridData.getColumnIndexMap()[columnIndex] }); // eslint-disable-next-line no-undefined this._gridData.sortGridRows(undefined); } getCSVData(mode: String) { let data = this._data.getData().data, schema = this._data.getData().schema.slice(0, -1), viewDetails; const dataWithRemovedIndexes = data.map((item: any) => { return item.slice(0, -1); }); if(mode === 'view') { viewDetails = this.getViewData(dataWithRemovedIndexes, schema); } else { viewDetails = { data: dataWithRemovedIndexes, schema }; } return this.jsonToCsv(viewDetails.data, viewDetails.schema, null, false); } /** * Function to convert data, schema to csv format * @param data * @param schema * @param skipHeaders */ // eslint-disable-next-line class-methods-use-this jsonToCsv(data: any, schema: any, columnGroups: any = [], skipHeaders: Boolean, skipcolumnheadergroups: Boolean = true){ let csvContent = ''; if(!skipcolumnheadergroups && columnGroups.length) { const columnArray: Array = []; columnGroups.forEach((element: any) => { if(!columnArray[element.level - 1]) { columnArray[element.level - 1] = []; } columnArray[element.level - 1][element.childStartIndex] = element.headerName; }); csvContent = columnArray.join("\r\n"); csvContent += "\r\n"; } if(!skipHeaders){ csvContent += schema.map((sch: any) => { return sch.name; }); csvContent += "\r\n"; } data.forEach(function(rowArray: any) { const rowArrayWithQuotes = rowArray.map((item: any, itemIndex: number)=> { if(schema[itemIndex] && schema[itemIndex].type === "datetime") { return new Intl.DateTimeFormat('en-US').format(new Date(item)); } return (`"${item}"`); }); let row = rowArrayWithQuotes.join(","); csvContent += row + "\r\n"; }); return csvContent; } // eslint-disable-next-line class-methods-use-this handleCreditLabelClick(){ let link = 'https://www.fusioncharts.com/?BS=FGEvalMark&utm_source=FG_trial&pver='; link += FusionGrid.versionDetails[0].join('.'); window.open(link); } }