@adaptabletools/adaptable
Version:
Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements
454 lines (453 loc) • 21.5 kB
JavaScript
import { AdaptableModuleBase } from './AdaptableModuleBase';
import * as ModuleConstants from '../Utilities/Constants/ModuleConstants';
import * as LayoutRedux from '../Redux/ActionsReducers/LayoutRedux';
import ArrayExtensions from '../Utilities/Extensions/ArrayExtensions';
import { LayoutRadioSelector } from '../View/Layout/LayoutRadioSelector';
import { LayoutStatusbar } from '../View/Layout/LayoutStatusBarSubPanelPopover';
import { EditCurrentLayoutButton } from '../View/Layout/EditCurrentLayoutButton';
import { LayoutCloneButton } from '../View/Layout/LayoutCloneButton';
import { LayoutWizard } from '../View/Layout/Wizard/LayoutWizard';
import { getLayoutFilterViewItems } from './Utilities/Layout/getLayoutFilterViewItems';
import { getLayoutSortViewItems } from './Utilities/Layout/getLayoutSortViewItems';
import { WEIGHTED_AVERAGE_AGG_FN_NAME } from '../AdaptableState/Common/AggregationColumns';
import { SHOW_PIVOT_COLUMN_DETAILS } from '../View/Components/Popups/WindowPopups/windowFactory';
import flattenDeep from 'lodash/flattenDeep';
import StringExtensions from '../Utilities/Extensions/StringExtensions';
import { getGridFilterViewItems } from '../View/Layout/Wizard/getGridFilterPreview';
import { RowSummaryService } from '../Utilities/Services/RowSummaryService';
import { isPivotLayout } from '../Api/Implementation/LayoutHelpers';
export class LayoutModule extends AdaptableModuleBase {
constructor(api) {
super(ModuleConstants.LayoutModuleId, ModuleConstants.LayoutFriendlyName, 'grid', 'LayoutPopup', 'Named sets of column visibility, order, groupings, aggregation, pivots etc.', api);
this.rowSummaryService = new RowSummaryService(this.api);
}
onAdaptableReady() {
this.rowSummaryService.onAdaptableReady();
this.api.eventApi.on('LayoutChanged', (layoutChangedInfo) => {
if (layoutChangedInfo.actionName === 'LAYOUT_SELECT' &&
layoutChangedInfo.newLayoutState.CurrentLayout !==
layoutChangedInfo.oldLayoutState?.CurrentLayout) {
this.handleLayoutSelection();
}
});
requestAnimationFrame(() => {
if (this.api.isDestroyed()) {
return;
}
this.api.eventApi.internalApi.fireLayoutChangedEvent('ADAPTABLE_READY', null, this.api.layoutApi.getLayoutState());
});
}
getModuleAdaptableObjects() {
return this.api.layoutApi.getLayouts();
}
getExplicitlyReferencedColumnIds(layout) {
const columnIds = [];
if (layout.TableColumns) {
const visibility = layout.ColumnVisibility || {};
columnIds.push(...layout.TableColumns.filter((colId) => {
return visibility[colId] !== false;
}));
}
if (layout.TableAggregationColumns) {
columnIds.push(...Object.keys(layout.TableAggregationColumns));
}
if (isPivotLayout(layout)) {
if (layout.PivotColumns) {
columnIds.push(...layout.PivotColumns);
}
if (layout.PivotAggregationColumns) {
columnIds.push(...layout.PivotAggregationColumns.map((x) => x.ColumnId));
}
if (layout.PivotGroupedColumns) {
columnIds.push(...layout.PivotGroupedColumns);
}
}
if (layout.RowGroupedColumns) {
columnIds.push(...layout.RowGroupedColumns);
}
return Array.from(new Set(columnIds));
}
getTeamSharingReferences(adaptableObject) {
const teamSharingReferences = super.getTeamSharingReferences(adaptableObject);
const layoutName = adaptableObject.Name;
if (this.api.layoutApi.internalApi.hasLayoutSpecificObjects() && !!layoutName) {
const layoutAssociatedObjectReferences = [];
const loadConfig = {
associatedWithLayout: layoutName,
};
// we ensured that there are layout specific objects, so all the "getAll*()" api methods will return only the objects available in the current layout
this.api.alertApi.getAlertDefinitions(loadConfig).forEach((alertDefinition) => layoutAssociatedObjectReferences.push({
Reference: alertDefinition,
Module: 'Alert',
}));
this.api.customSortApi.getCustomSorts(loadConfig).forEach((customSort) => layoutAssociatedObjectReferences.push({
Reference: customSort,
Module: 'CustomSort',
}));
this.api.flashingCellApi.getFlashingCellDefinitions(loadConfig).forEach((flashingCell) => layoutAssociatedObjectReferences.push({
Reference: flashingCell,
Module: 'FlashingCell',
}));
this.api.formatColumnApi.getFormatColumns(loadConfig).forEach((formatColumn) => layoutAssociatedObjectReferences.push({
Reference: formatColumn,
Module: 'FormatColumn',
}));
this.api.styledColumnApi.getStyledColumns(loadConfig).forEach((styledcolumn) => layoutAssociatedObjectReferences.push({
Reference: styledcolumn,
Module: 'StyledColumn',
}));
this.api.plusMinusApi.getAllPlusMinus(loadConfig).forEach((plusMinusNudge) => layoutAssociatedObjectReferences.push({
Reference: plusMinusNudge,
Module: 'PlusMinus',
}));
this.api.shortcutApi.getShortcuts(loadConfig).forEach((shortcut) => layoutAssociatedObjectReferences.push({
Reference: shortcut,
Module: 'Shortcut',
}));
[
...this.api.scheduleApi.getReportSchedules(loadConfig),
...this.api.scheduleApi.getReminderSchedules(loadConfig),
...this.api.scheduleApi.getIPushPullSchedules(loadConfig),
...this.api.scheduleApi.getOpenFinSchedules(loadConfig),
].forEach((schedule) => layoutAssociatedObjectReferences.push({
Reference: schedule,
Module: 'Schedule',
}));
teamSharingReferences.push(...layoutAssociatedObjectReferences);
}
return teamSharingReferences;
}
hasNamedQueryReferences() {
return true;
}
createColumnMenuItems(column) {
if (!this.isModuleEditable()) {
return;
}
let returnColumnMenuItems = [];
const isReadOnlyLayout = this.api.layoutApi.isCurrentLayoutReadOnly();
if (!isReadOnlyLayout) {
returnColumnMenuItems.push(this.createMenuItemShowPopup('layout-edit', 'Edit Layout', this.moduleInfo.Popup, 'edit-table', {
action: 'Edit',
source: 'ColumnMenu',
value: this.api.layoutApi.getCurrentLayout(),
}));
if (column && !column.isTreeColumn) {
returnColumnMenuItems.push(this.createMenuItemClickFunction('layout-column-caption-change', 'Change Caption', 'edit', () => this.api.layoutApi.showChangeColumnCaption(column)));
}
}
if (column) {
if (column.hideable) {
returnColumnMenuItems.push(this.createMenuItemClickFunction('layout-column-hide', 'Hide Column', 'visibility-off-bold', () => {
this.api.columnApi.hideColumn(column.columnId);
}));
}
const hasExistingSelection = this.api.gridApi.getSelectedCellInfo()?.gridCells?.length;
if (hasExistingSelection) {
returnColumnMenuItems.push(this.createMenuItemClickFunction('layout-column-select-preserve', 'Select Column (Preserve Selection)', 'select-fwd', () => {
this.api.columnApi.addColumnToSelection(column.columnId);
}));
returnColumnMenuItems.push(this.createMenuItemClickFunction('layout-column-select-reset', 'Select Column (Reset Selection)', 'tab-unselected', () => {
this.api.columnApi.selectColumn(column.columnId);
}));
}
else {
returnColumnMenuItems.push(this.createMenuItemClickFunction('layout-column-select', 'Select Column', 'tab-unselected', () => {
this.api.columnApi.selectColumn(column.columnId);
}));
}
}
returnColumnMenuItems.push(this.createMenuItemClickFunction('layout-grid-select', 'Select Grid', 'select-all', () => {
this.api.gridApi.selectAll();
}));
return returnColumnMenuItems;
}
createContextMenuItems(menuContext) {
let returnColumnMenuItems = [];
if (this.isModuleEditable() && !this.api.layoutApi.isCurrentLayoutReadOnly()) {
returnColumnMenuItems.push(this.createMenuItemShowPopup('layout-edit', 'Edit Layout', this.moduleInfo.Popup, 'edit-table', {
action: 'Edit',
source: 'ColumnMenu',
}));
}
if ((menuContext.selectedCellInfo &&
ArrayExtensions.IsNotNullOrEmpty(menuContext.selectedCellInfo.columns)) ||
(menuContext.selectedRowInfo &&
ArrayExtensions.IsNotNullOrEmpty(menuContext.selectedRowInfo.gridRows))) {
returnColumnMenuItems.push(this.createMenuItemClickFunction('layout-clear-selection', 'Clear Selected Cells', 'select-off', () => {
this.api.gridApi.deselectAll();
}));
}
returnColumnMenuItems.push(this.createMenuItemClickFunction('layout-select-all', 'Select Grid', 'select-all', () => {
this.api.gridApi.selectAll();
}));
returnColumnMenuItems.push(this.createMenuItemClickFunction('layout-auto-size', 'Auto Size', 'arrow-expand', () => {
this.api.columnApi.autosizeAllColumns();
}));
const viewPivotItemsMenuItem = this.createViewPivotItemsMenuItem(menuContext);
if (viewPivotItemsMenuItem) {
returnColumnMenuItems.push(viewPivotItemsMenuItem);
}
return returnColumnMenuItems;
}
// TODO next time this method is touched, it should be extracted in an internal Api
createViewPivotItemsMenuItem(menuContext) {
// current group => menuContext.rowNode.field;
const selectedCellInfo = menuContext.selectedCellInfo ?? this.api.gridApi.getSelectedCellInfo();
if (selectedCellInfo?.gridCells?.length !== 1) {
return;
}
const gridCell = menuContext.gridCell ?? selectedCellInfo.gridCells[0];
const currentLayout = this.api.layoutApi.getCurrentLayout();
if (!isPivotLayout(currentLayout)) {
return;
}
const { agGridColumn } = menuContext;
const columnId = agGridColumn.getColId();
const isPivotColumn = this.api.columnApi.isPivotResultColumn(columnId);
if (!isPivotColumn && this.api.columnApi.getColumnDataTypeForColumnId(columnId) !== 'number') {
return;
}
/**
* Cannot base the values on the pivot column id, because the value or the field may have underscrores
*
*
* e.g. pivot_license_MIT License_stargazers_count
*/
// pivot_status_Booked_price
// price
const aggColumn = agGridColumn?.getColDef?.().pivotValueColumn?.getColDef?.()?.field;
// Booked
const pivotValue = agGridColumn?.getColDef?.()?.pivotKeys?.[0];
// status - the preview works only with one column
const pivotColumnId = currentLayout?.PivotColumns?.[0];
function getData(rows) {
return [
...rows.map((row) => {
if (row.childrenAfterFilter) {
return getData(row.childrenAfterFilter);
}
return row.data;
}),
];
}
const data = flattenDeep(getData(menuContext.rowNode.childrenAfterFilter));
const rowGroups = currentLayout.RowGroupedColumns;
const cellValue = isPivotColumn
? menuContext.rowNode?.aggData?.[columnId]
: gridCell.displayValue;
const popupProps = {
hasPivotValue: isPivotColumn,
columnId,
rowGroups,
rows: data,
layout: currentLayout,
aggColumn,
pivotValue,
pivotColumnId,
};
return {
name: 'layout-aggregated-view',
category: this.moduleInfo.ModuleName,
isVisible: true,
label: 'Expand Aggregated Value',
onClick: () => {
this.api.internalApi.showPopupWindow({
// force only one window instance opened
id: SHOW_PIVOT_COLUMN_DETAILS,
factoryId: SHOW_PIVOT_COLUMN_DETAILS,
title: `Rows for Aggregated Value: ${cellValue}`,
icon: undefined,
popupProps,
});
},
icon: {
name: 'arrow-expand',
},
};
}
getTeamSharingAction() {
return {
ModuleEntities: this.api.layoutApi.getLayouts(),
AddAction: LayoutRedux.LayoutAdd,
EditAction: LayoutRedux.LayoutSave,
};
}
toViewAll() {
return this.getModuleAdaptableObjects().map((layout) => this.toView(layout));
}
toView(layout) {
const maxColumnsToDisplay = this.api.optionsApi.getLayoutOptions().layoutViewOptions?.maxColumnsToDisplay ?? 10;
const columnIdToFriendlyName = (columnId) => {
return this.api.columnApi.getFriendlyNameForColumnId(columnId);
};
let columns = (layout.TableColumns || [])
.filter((c) => !this.api.columnApi.isAutoRowGroupColumn(c))
.filter((c) => !this.api.columnApi.isPivotResultColumn(c));
if (columns.length >
maxColumnsToDisplay + 1 /* +1 is to show tag only beginning with 2, 'other 2' */) {
const extraColumns = columns.length - maxColumnsToDisplay;
const firstNColumns = columns
.slice(0, maxColumnsToDisplay)
.map((column) => columnIdToFriendlyName(column));
columns = [...firstNColumns, `and ${extraColumns} more`];
}
else {
columns = columns.map((column) => columnIdToFriendlyName(column));
}
return {
items: [
{
name: 'Grid Type',
values: [isPivotLayout(layout) ? 'Pivot' : 'Table'],
},
{
name: 'Name',
values: [layout.Name],
},
/**
* Hide pivot columns.
*
* The generated pivot columns cannot be prevented from beeing on the layout object.
* When they are removed/not added, they no longer appear in the grid.
*/
!isPivotLayout(layout) && {
name: 'Columns',
values: columns,
},
layout.ColumnSorts?.length && getLayoutSortViewItems(layout, this.api),
layout?.ColumnFilters?.length && getLayoutFilterViewItems(layout, this.api),
layout?.RowGroupedColumns?.length && {
name: 'Row Groups',
values: layout.RowGroupedColumns.map((colId) => columnIdToFriendlyName(colId)),
},
layout?.PivotColumns?.length && {
name: 'Pivot Columns',
values: layout.PivotColumns.map((colId) => columnIdToFriendlyName(colId)),
},
layout?.PivotGroupedColumns?.length && {
name: 'Pivot Row Groups',
values: layout.PivotGroupedColumns.map((colId) => columnIdToFriendlyName(colId)),
},
layout?.TableAggregationColumns &&
layout.TableAggregationColumns.length && {
name: 'Aggregations',
values: layout.TableAggregationColumns.map(({ ColumnId, AggFunc: aggFn }) => {
if (ColumnId === 'Source' || ColumnId === 'Uuid' || ColumnId === 'AdaptableVersion') {
return '';
}
let aggFnName = '';
if (typeof aggFn === 'string') {
aggFnName = aggFn;
}
else if (typeof aggFn === 'object' && aggFn.type === 'weightedAverage') {
aggFnName = WEIGHTED_AVERAGE_AGG_FN_NAME;
}
return `${aggFnName}(${columnIdToFriendlyName(ColumnId)})`;
}).filter(Boolean),
},
isPivotLayout(layout) &&
layout?.PivotAggregationColumns &&
layout.PivotAggregationColumns.length && {
name: 'Pivot Aggregations',
values: layout.PivotAggregationColumns.map(({ ColumnId, AggFunc: aggFn }) => {
if (ColumnId === 'Source' || ColumnId === 'Uuid' || ColumnId === 'AdaptableVersion') {
return '';
}
let aggFnName = '';
if (typeof aggFn === 'string') {
aggFnName = aggFn;
}
else if (typeof aggFn === 'object' && aggFn.type === 'weightedAverage') {
aggFnName = WEIGHTED_AVERAGE_AGG_FN_NAME;
}
return `${aggFnName}(${columnIdToFriendlyName(ColumnId)})`;
}).filter(Boolean),
},
layout?.GridFilter &&
StringExtensions.IsNotNullOrEmpty(layout.GridFilter.Expression) &&
getGridFilterViewItems(layout),
layout &&
!isPivotLayout(layout) &&
layout.RowSummaries?.length && {
name: 'Row Summaries',
values: layout.RowSummaries.map((rowSummary) => {
const columns = Object.entries(rowSummary.ColumnsMap ?? {})
.map(([columnId, expression]) => {
if (columnId === 'Source' ||
columnId === 'Uuid' ||
columnId === 'AdaptableVersion') {
return '';
}
return `${expression}(${columnIdToFriendlyName(columnId)})`;
})
.filter(Boolean)
.join(', ');
return `${rowSummary.Position}: ${columns}`;
}),
},
].filter(Boolean),
abObject: layout,
};
}
getViewProperties() {
return {
abObjectTypes: [
{
name: 'Table Layout',
accessLevel: this.AccessLevel,
},
!this.api.gridApi.isTreeDataGrid() && {
name: 'Pivot Layout',
accessLevel: this.AccessLevel,
},
].filter(Boolean),
actions: [LayoutRadioSelector, LayoutCloneButton],
getDeleteAction: (layout) => {
// make sure we do not delete the last layout
if (this.getModuleAdaptableObjects().length === 1) {
return null;
}
return LayoutRedux.LayoutDelete(layout);
},
getEditWizard: () => LayoutWizard,
getStatusBarPanelProps: () => {
return {
triggerActionOnWrapperClick: false,
content: LayoutStatusbar,
extraActions: [EditCurrentLayoutButton],
};
},
};
}
handleLayoutSelection() {
if (!this.api.layoutApi.internalApi.hasLayoutSpecificObjects()) {
// no need if no callback implementation is provided or no auto checking
return;
}
// PlusMinus
this.api.internalApi
.getModuleService()
.getModuleById('PlusMinus')
.checkListenToKeyDown();
// Shortcut
this.api.internalApi
.getModuleService()
.getModuleById('Shortcut')
.checkListenToKeyDown();
// Schedule
this.api.internalApi
.getModuleService()
.getModuleById('Schedule')
.setUpScheduleJobs();
// CustomSort, FlashingCell, FormatColumn, StyledColumn
// we need to re-setup the column defs, as some colDefs properties may be changed
this.api.internalApi.getAdaptableInstance().updateColumnModelAndRefreshGrid();
}
getReferencedNamedQueryNames(layout) {
if (!layout.GridFilter?.Expression) {
return [];
}
return this.api.namedQueryApi.internalApi.getReferencedNamedQueryNames(layout.GridFilter.Expression);
}
}