diff --git a/src/OSFramework/DataGrid/Enum/ErrorCodes.ts b/src/OSFramework/DataGrid/Enum/ErrorCodes.ts index bfc6e3f4..4318bdb2 100644 --- a/src/OSFramework/DataGrid/Enum/ErrorCodes.ts +++ b/src/OSFramework/DataGrid/Enum/ErrorCodes.ts @@ -82,9 +82,10 @@ namespace OSFramework.DataGrid.Enum { API_FailedAddColumnToGroupPanel = 'GRID-API-10007', API_FailedSetColumnWordWrap = 'GRID-API-10008', API_FailedSetColumnHeader = 'GRID-API-10009', + API_FailedSetNumberAggregateConditionalFormatting = 'GRID-API-10010', //EXPORT API_FailedCustomizeExportingMessage = 'GRID-API-11001', //COLUMNPICKER - API_FailedSetColumnVisibility = 'GRID-API-10001' + API_FailedSetColumnVisibility = 'GRID-API-12001' } } diff --git a/src/OSFramework/DataGrid/Feature/IColumnAggregate.ts b/src/OSFramework/DataGrid/Feature/IColumnAggregate.ts index fb7456d4..7391541a 100644 --- a/src/OSFramework/DataGrid/Feature/IColumnAggregate.ts +++ b/src/OSFramework/DataGrid/Feature/IColumnAggregate.ts @@ -1,5 +1,29 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars namespace OSFramework.DataGrid.Feature { export interface IColumnAggregate - extends Interface.IProviderConfig {} + extends Interface.IProviderConfig { + /** + * Function to add the aggregate cell class + * + * @param columnBinding {string} => The column binding of the aggregate to add the class + * @param className {string} => Classname to be added + */ + addClass(columnBinding: string, className: string): void; + + /** + * Function to remove the aggregate cell class + * + * @param columnBinding {string} => The column binding of the aggregate to remove the class + * @param className {string} => Classname to be removed + */ + removeClass(columnBinding: string, className: string): void; + + /** + * Function that will set the conditional format to the aggregate rows + * + * @param columnID {string} => The columnID of the aggregate to add the new conditional format rules + * @param conditionalFormat {string} => String containing the conditional format rules + */ + setConditionalFormat(columnID: string, conditionalFormat: string); + } } diff --git a/src/OSFramework/DataGrid/Feature/IConditionalFormat.ts b/src/OSFramework/DataGrid/Feature/IConditionalFormat.ts index 9366c120..08dfc18a 100644 --- a/src/OSFramework/DataGrid/Feature/IConditionalFormat.ts +++ b/src/OSFramework/DataGrid/Feature/IConditionalFormat.ts @@ -1,8 +1,23 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars namespace OSFramework.DataGrid.Feature { export interface IConditionalFormat { + /** + * Adds new conditional format rules to the desired binding from the aggregate rows + * + * @param binding {string} => The column binding to add the new conditional format rules + * @param rules {Array} => Array containing the conditional format rules + */ + addAggregateRules( + binding: string, + rules: Array + ): void; + /** * Adds new conditional format rules to the desired binding. + * + * @param binding {string} => The column binding to add the new conditional format rules + * @param rules {Array} => Array containing the conditional format rules + * @param refresh (optional) => True if it should clear the classes previously added */ addRules( binding: string, @@ -12,6 +27,8 @@ namespace OSFramework.DataGrid.Feature { /** * Removes rules of desired binding. + * + * @param binding {string} => The column binding to remove the conditional format rules */ removeRules(binding: string); } diff --git a/src/OutSystems/GridAPI/ConditionalFormat.ts b/src/OutSystems/GridAPI/ConditionalFormat.ts index 3e575142..7e9daab5 100644 --- a/src/OutSystems/GridAPI/ConditionalFormat.ts +++ b/src/OutSystems/GridAPI/ConditionalFormat.ts @@ -31,6 +31,49 @@ namespace OutSystems.GridAPI.ConditionalFormat { ); } + /** + * Adds new conditional format rules to the desired aggregate column. + * + * @export + * @param {string} gridID Grid ID + * @param {string} columnID Column ID + * @param {string} conditionalFormat Rules for conditional formatting. + */ + export function SetNumberAggregateConditionalFormatting( + gridID: string, + columnID: string, + conditionalFormat: string + ): string { + Performance.SetMark( + 'ColumnManager.SetNumberAggregateConditionalFormatting' + ); + const result = Auxiliary.CreateApiResponse({ + gridID, + errorCode: + OSFramework.DataGrid.Enum.ErrorCodes + .API_FailedSetNumberAggregateConditionalFormatting, + callback: () => { + const grid = GridManager.GetGridById(gridID); + + grid.features.columnAggregate.setConditionalFormat( + columnID, + conditionalFormat + ); + } + }); + + Performance.SetMark( + 'ColumnManager.SetNumberAggregateConditionalFormatting-end' + ); + Performance.GetMeasure( + '@datagrid-ColumnManager.SetNumberAggregateConditionalFormatting', + 'ColumnManager.SetNumberAggregateConditionalFormatting', + 'ColumnManager.SetNumberAggregateConditionalFormatting-end' + ); + + return result; + } + /** * Removes rules of desired binding. * diff --git a/src/Providers/DataGrid/Wijmo/Features/ColumnAggregate.ts b/src/Providers/DataGrid/Wijmo/Features/ColumnAggregate.ts index 0a0df3e8..85c99125 100644 --- a/src/Providers/DataGrid/Wijmo/Features/ColumnAggregate.ts +++ b/src/Providers/DataGrid/Wijmo/Features/ColumnAggregate.ts @@ -4,18 +4,145 @@ namespace Providers.DataGrid.Wijmo.Feature { export class ColumnAggregate implements OSFramework.DataGrid.Feature.IColumnAggregate, - OSFramework.DataGrid.Interface.IBuilder + OSFramework.DataGrid.Interface.IBuilder, + OSFramework.DataGrid.Interface.IDisposable { private _aggregateRow: wijmo.grid.GroupRow; + private _cellClasses: Map>; private _grid: Grid.IGridWijmo; private _showAggregateValue: boolean; constructor(grid: Grid.IGridWijmo, showAggregateValue: boolean) { this._grid = grid; this._showAggregateValue = showAggregateValue; + this._cellClasses = new Map>(); } + + private _formatItem( + s: wijmo.grid.FlexGrid, + e: wijmo.grid.FormatItemEventArgs + ) { + if (e.panel.cellType === wijmo.grid.CellType.ColumnFooter) { + const binding = this._grid.provider.columns[e.col].binding; + const classesToAdd = this._cellClasses.get(binding); + classesToAdd?.forEach((className) => + wijmo.addClass(e.cell, className) + ); + } + } + + /** + * Function to add the aggregate cell class + * + * @param columnBinding {string} => The column binding of the aggregate to add the class + * @param className {string} => Classname to be added + */ + public addClass(columnBinding: string, className: string): void { + let cellClassArray = []; + + // Get the array associated with the column binding, if it exists. + if (this._cellClasses.has(columnBinding)) + cellClassArray = this._cellClasses.get(columnBinding); + + // If the className does not exists in the cellClassArray yet, we should add it + const shouldAdd = + cellClassArray.findIndex( + (cellClassName) => cellClassName === className + ) === -1; + + // If shouldAdd is true and the className does not exist in the array yet, let's add it. + if (shouldAdd) { + cellClassArray = [...cellClassArray, className]; // append new className to the cellClassArray array + this._cellClasses.set(columnBinding, cellClassArray); // update the cellClassArray associated with the biding + } + } + public build(): void { this.setState(this._showAggregateValue); + this._grid.provider.formatItem.addHandler( + this._formatItem.bind(this) + ); + } + + public dispose(): void { + this._grid.provider.formatItem.removeHandler( + this._formatItem.bind(this) + ); + } + + /** + * Function to remove the aggregate cell class + * + * @param columnBinding {string} => The column binding of the aggregate to add the class + * @param className {string} => Classname to be added + */ + public removeClass(columnBinding: string, className: string): void { + // If the columnBinding does not in the _cellClasses, no action is required + if (!this._cellClasses.has(columnBinding)) return; + + // Get the array associated with the column binding + const cellClassArray = this._cellClasses.get(columnBinding); + + // Get the className index in the array. + const classIndex = cellClassArray.findIndex( + (cellClassName) => cellClassName === className + ); + + // If the className exists in the array, let's remove it. + if (classIndex > -1) { + // Remove the desired className + cellClassArray.splice(classIndex, 1); + + // If the array is not empty, update it. + // Otherwise, delete the binding element from the Map + if (cellClassArray.length > 0) + this._cellClasses.set(columnBinding, cellClassArray); + else this._cellClasses.delete(columnBinding); + } + } + + /** + * Function that will set the conditional format to the aggregate rows + * + * @param columnID {string} => The columnID of the aggregate to add the new conditional format rules + * @param conditionalFormat {string} => String containing the conditional format rules + */ + public setConditionalFormat( + columnID: string, + conditionalFormat: string + ): void { + const column = this._grid.getColumn(columnID); + + if (!column) { + throw new Error( + OSFramework.DataGrid.Enum.ErrorMessages.InvalidColumnIdentifier + ); + } + + if (column.provider.aggregate === wijmo.Aggregate.None) { + throw new Error( + OSFramework.DataGrid.Enum.ErrorMessages.Aggregate_NotFound + ); + } + + if ( + !( + column.columnType === + OSFramework.DataGrid.Enum.ColumnType.Number || + column.columnType === + OSFramework.DataGrid.Enum.ColumnType.Currency + ) + ) + throw new Error( + `It seems you are trying to add the conditional format to a ${column.columnType}Column's aggregate. This is not allowed, try to use Number and Currency column instead.` + ); + + this._grid.features.conditionalFormat.addAggregateRules( + column.config.binding, + JSON.parse(conditionalFormat) + ); + + this._grid.provider.collectionView.refresh(); } /** diff --git a/src/Providers/DataGrid/Wijmo/Features/ConditionalFormat.ts b/src/Providers/DataGrid/Wijmo/Features/ConditionalFormat.ts index 82fb6e97..c9dda73d 100644 --- a/src/Providers/DataGrid/Wijmo/Features/ConditionalFormat.ts +++ b/src/Providers/DataGrid/Wijmo/Features/ConditionalFormat.ts @@ -107,7 +107,7 @@ namespace Providers.DataGrid.Wijmo.Feature { } } - class Condition { + class Condition implements OSFramework.DataGrid.OSStructure.Format { public condition: Rules; // eslint-disable-next-line @typescript-eslint/no-explicit-any public value: any; @@ -158,11 +158,44 @@ namespace Providers.DataGrid.Wijmo.Feature { class ConditionExecuter { private _grid: Grid.IGridWijmo; + private _isAggregate: boolean; public conditions: Array; - constructor(conditions: Array, grid: Grid.IGridWijmo) { + constructor( + conditions: Array, + grid: Grid.IGridWijmo, + isAggregate = false + ) { this.conditions = conditions; this._grid = grid; + this._isAggregate = isAggregate; + } + + /** + * Adds or removes a ColumnAggregate cell class based on the condition. If the condition is true, we'll add the class, if it's false we'll remove it. + * @param columnBinding + * @param rowClass + * @param rowNumber + * @param shouldAdd + */ + private _addOrRemoveAggregateClass( + columnBinding: string, + className: string, + shouldAdd: boolean + ) { + if (className) { + if (shouldAdd) { + this._grid.features.columnAggregate.addClass( + columnBinding, + className + ); + } else { + this._grid.features.columnAggregate.removeClass( + columnBinding, + className + ); + } + } } /** @@ -247,19 +280,27 @@ namespace Providers.DataGrid.Wijmo.Feature { e.col ).binding; - this._addOrRemoveRowClass( - binding, - condition.rowClass, - e.row, - condition.isTrue - ); - - this._addOrRemoveCellClass( - binding, - condition.cellClass, - e.row, - condition.isTrue - ); + if (this._isAggregate) { + this._addOrRemoveAggregateClass( + binding, + condition.cellClass, + condition.isTrue + ); + } else { + this._addOrRemoveRowClass( + binding, + condition.rowClass, + e.row, + condition.isTrue + ); + + this._addOrRemoveCellClass( + binding, + condition.cellClass, + e.row, + condition.isTrue + ); + } }); } } @@ -271,14 +312,17 @@ namespace Providers.DataGrid.Wijmo.Feature { { private _grid: Grid.IGridWijmo; private _mappedRules: Map; + private _mappedRulesAggregate: Map; constructor(grid: Grid.IGridWijmo) { this._grid = grid; this._mappedRules = new Map(); + this._mappedRulesAggregate = new Map(); } private _parseRule( - rules: Array + rules: Array, + isAggregate = false ): ConditionExecuter { const conditionExecuters = []; rules.forEach((element) => { @@ -292,25 +336,70 @@ namespace Providers.DataGrid.Wijmo.Feature { ); conditionExecuters.push(conditionAnds); }); - return new ConditionExecuter(conditionExecuters, this._grid); + return new ConditionExecuter( + conditionExecuters, + this._grid, + isAggregate + ); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars - private _updatingViewHandler(s: any, e: any) { + private _updateAggregateRows() { + // Get the columns that contain a rule associatede + const columnsAggregate = this._grid + .getColumns() + .filter((x) => + this._mappedRulesAggregate.get(x.config.binding) + ); + + // Iterate columns in order to get aggregate cell values + columnsAggregate.forEach((column) => { + const colIndex = this._grid.provider.columns.find( + (x) => x.binding === column.provider.binding + ).index; + + if ( + this._grid.provider.columns[colIndex].aggregate === + wijmo.Aggregate.None + ) { + throw new Error( + OSFramework.DataGrid.Enum.ErrorMessages.Aggregate_NotFound + ); + } + + // We need to use the getAggregate function to get the current column aggregate value + const aggregateValue = + this._grid.provider.itemsSource.getAggregate( + this._grid.provider.columns[colIndex].aggregate, + this._grid.provider.columns[colIndex].binding + ); + + // Execute the rule + this._mappedRulesAggregate.get(column.config.binding).execute( + aggregateValue, + { + col: colIndex + }, + column.columnType + ); + }); + } + + private _updateRows() { const columns = this._grid .getColumns() .filter((x) => this._mappedRules.get(x.config.binding)); // iterate all rows and columns in order to get cell values - s.rows.forEach((row, index) => { - columns.forEach((column) => { - const isDropdown = - column.columnType === - OSFramework.DataGrid.Enum.ColumnType.Dropdown; - - const colIndex = this._grid.provider.columns.find( - (x) => x.binding === column.provider.binding - ).index; + columns.forEach((column) => { + const isDropdown = + column.columnType === + OSFramework.DataGrid.Enum.ColumnType.Dropdown; + + const colIndex = this._grid.provider.columns.find( + (x) => x.binding === column.provider.binding + ).index; + + this._grid.provider.rows.forEach((row, index) => { const value = this._grid.provider.getCellData( index, colIndex, @@ -329,6 +418,35 @@ namespace Providers.DataGrid.Wijmo.Feature { }); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars + private _updatingViewHandler() { + this._updateRows(); + this._updateAggregateRows(); + } + + /** + * Adds new conditional format rules to the desired binding from the aggregate rows. + * + * @param binding {string} => The column binding to add the new conditional format rules + * @param rules {Array} => Array containing the conditional format rules + */ + public addAggregateRules( + binding: string, + rules: Array + ): void { + this._mappedRulesAggregate.set( + binding, + this._parseRule(rules, true) + ); + } + + /** + * Adds new conditional format rules to the desired binding. + * + * @param binding {string} => The column binding to add the new conditional format rules + * @param rules {Array} => Array containing the conditional format rules + * @param refresh (optional) => True if it should clear the classes previously added + */ public addRules( binding: string, rules: Array, @@ -349,6 +467,11 @@ namespace Providers.DataGrid.Wijmo.Feature { ); } + /** + * Removes rules of desired binding. + * + * @param binding {string} => The column binding to remove the conditional format rules + */ public removeRules(binding: string): void { this._mappedRules.delete(binding); } diff --git a/src/Providers/DataGrid/Wijmo/Features/Selection.ts b/src/Providers/DataGrid/Wijmo/Features/Selection.ts index 9572cbd6..09a3282d 100644 --- a/src/Providers/DataGrid/Wijmo/Features/Selection.ts +++ b/src/Providers/DataGrid/Wijmo/Features/Selection.ts @@ -591,7 +591,9 @@ namespace Providers.DataGrid.Wijmo.Feature { isSelected = true ): number[] { if (this._grid.features.rowHeader.hasCheckbox) { - return undefined; + throw new Error( + OSFramework.DataGrid.Enum.ErrorMessages.SetRowAsSelected + ); } rowsIndex.forEach((index) => {