# Server-Side Row Model - Styled Columns

Canonical page: https://www.adaptabletools.com/docs/dev-guide-row-models-server-styled-columns

- Styled Columns operate normally with the Server-Side Row Model
- They render as each cell comes into view — Gradient, Percent Bar, Badge and the rest

[Styled Columns](https://www.adaptabletools.com/docs/handbook-styled-column-overview/index.md) work fully when using the Server-Side Row Model.

They are evaluated in real-time as a cell comes into view — no server work is required.

**Example: SSRM - Styled Columns**

Server-Side Row Model: Styled Columns

- This demo shows Styled Columns applied when using Server-Side Row Model; it contains:
  - a [Gradient Style](https://www.adaptabletools.com/docs/handbook-styled-column-gradient/index.md) on `Gold`
  - a [Percent Bar](https://www.adaptabletools.com/docs/handbook-styled-column-percent-bar/index.md) on `Silver`
  - a [Badge Style](https://www.adaptabletools.com/docs/handbook-styled-column-badge/index.md) on `Country`

- Scroll vertically and note that as new data is loaded into the grid, it is automatically styled

Demo project (Vanilla); the files without a marker are shown below:

```text
src/
  adaptableOptions.ts
  agGridModules.ts     [template]
  columnDefs.ts
  gridOptions.ts
  index.ts             [template]
  onAdaptableReady.ts
  rowData.ts           [template]
  styles.css           [template]
index.html             [template]
package.json           [template]
tsconfig.json          [template]
```

Files marked `[template]` are the same in every demo; see the [demo template](https://www.adaptabletools.com/llms/demo-template.md#vanilla).

**`src/adaptableOptions.ts`**

```ts
import {AdaptableOptions} from '@adaptabletools/adaptable';

export const adaptableOptions: AdaptableOptions = {
  primaryKey: 'id',
  adaptableId: 'SSRM - Styled Columns',

  initialState: {
    Dashboard: {
      ModuleButtons: ['StyledColumn'],
    },
    Theme: {
      CurrentTheme: 'dark',
    },
    Layout: {
      CurrentLayout: 'SSRM Layout',
      Layouts: [
        {
          Name: 'SSRM Layout',
          TableColumns: [
            'athlete',
            'gold',
            'silver',
            'bronze',
            'country',
            'sport',
            'year',
          ],
          ColumnSizing: {
            athlete: {Width: 175},
            bronze: {Width: 100},
            country: {Width: 160},
            gold: {Width: 110},
            silver: {Width: 130},
            sport: {Width: 175},
            year: {Width: 115},
          },
        },
      ],
    },

    StyledColumn: {
      StyledColumns: [
        {
          Name: 'gold Gradient',
          ColumnId: 'gold',
          GradientStyle: {
            CellRanges: [{Min: 0, Max: 10, Color: '#ffee2e'}],
          },
        },
        {
          Name: 'silver Percent Bar',
          ColumnId: 'silver',
          PercentBarStyle: {
            RangeValueType: 'Number',
            CellRanges: [
              {
                Min: 0,
                Max: 10,
                Color: '#c0c0c0',
              },
            ],
            BackColor: '#4a4a4a',
          },
        },
        {
          Name: 'country Badge',
          ColumnId: 'country',
          BadgeStyle: {
            Badges: [
              {
                Shape: 'Rounded',
                Rule: '[country] = "United States"',
                PillStyle: {
                  BackColor: '#3c3b6e',
                  ForeColor: 'var(--ab-color-palette-14)',
                },
              },
              {
                Shape: 'Rounded',
                Rule: '[country] = "China"',
                PillStyle: {
                  BackColor: '#de2910',
                  ForeColor: 'var(--ab-color-palette-14)',
                },
              },
              {
                Shape: 'Rounded',
                Rule: '[country] = "Russia"',
                PillStyle: {
                  BackColor: '#0039a6',
                  ForeColor: 'var(--ab-color-palette-14)',
                },
              },
              {
                Shape: 'Rounded',
                Rule: '[country] = "Australia"',
                PillStyle: {
                  BackColor: '#012169',
                  ForeColor: 'var(--ab-color-palette-14)',
                },
              },
              {
                Shape: 'Rounded',
                Rule: '[country] = "Great Britain"',
                PillStyle: {
                  BackColor: '#012169',
                  ForeColor: 'var(--ab-color-palette-14)',
                },
              },
              {
                Shape: 'Rounded',
                PillStyle: {
                  BackColor: '#555555',
                  ForeColor: 'var(--ab-color-palette-14)',
                },
              },
            ],
          },
        },
      ],
    },
  },
};
```

**`src/onAdaptableReady.ts`**

```ts
import {AdaptableReadyInfo, AdaptableApi} from '@adaptabletools/adaptable';
import {
  ColDef,
  GridApi,
  IServerSideDatasource,
  IServerSideGetRowsParams,
  IServerSideGetRowsRequest,
} from 'ag-grid-enterprise';

export const onAdaptableReady = ({
  adaptableApi,
  agGridApi,
}: AdaptableReadyInfo) => {
  agGridApi.setGridOption(
    'serverSideDatasource',
    createDataSource(adaptableApi)
  );
};

const API_BASE: string = process.env.NEXT_PUBLIC_ATHLETES_API_URL!;

// v24 Column Filters are Expressions. The athletes mock server evaluates
// both Grid Filter and Column Filters from `gridFilterAST`.
function rewriteAutoGroupColumn(
  node: unknown,
  groupedColumnId?: string
): unknown {
  if (!groupedColumnId || node == null || typeof node !== 'object') {
    return node;
  }
  if (Array.isArray(node)) {
    return node.map(item => rewriteAutoGroupColumn(item, groupedColumnId));
  }
  const astNode = node as {type?: string; args?: unknown[]};
  if (astNode.type === 'COL' && astNode.args?.[0] === 'ag-Grid-AutoColumn') {
    return {
      ...astNode,
      args: [groupedColumnId, ...astNode.args.slice(1)],
    };
  }
  if (!astNode.args) {
    return node;
  }
  return {
    ...astNode,
    args: astNode.args.map(arg =>
      rewriteAutoGroupColumn(arg, groupedColumnId)
    ),
  };
}

function appendExpressionAst(
  astNodes: unknown[],
  ast: unknown,
  groupedColumnId?: string
) {
  if (ast == null) {
    return;
  }
  const nodes = Array.isArray(ast) ? ast : [ast];
  for (const node of nodes) {
    astNodes.push(rewriteAutoGroupColumn(node, groupedColumnId));
  }
}

function toServerFilterFields(
  adaptableApi: AdaptableApi,
  groupedColumnId?: string
) {
  const filterState = adaptableApi.stateApi.getAdaptableFilterState();
  const astNodes: unknown[] = [];
  appendExpressionAst(astNodes, filterState.gridFilterAST, groupedColumnId);
  for (const columnFilter of filterState.columnFilters ?? []) {
    if (!columnFilter.Expression) {
      continue;
    }
    appendExpressionAst(
      astNodes,
      adaptableApi.expressionApi.getASTForExpression(columnFilter.Expression),
      groupedColumnId
    );
  }
  return {
    adaptableFilters: [],
    gridFilterAST: astNodes.length ? astNodes : undefined,
  };
}

function createDataSource(adaptableApi: AdaptableApi): IServerSideDatasource {
  return {
    getRows(params: IServerSideGetRowsParams) {
      const getRowsRequest: IServerSideGetRowsRequest = params.request;
      const adaptableSortState = adaptableApi.stateApi.getAdaptableSortState();
      const groupedColumn = getRowsRequest.rowGroupCols?.[0];
      const serverFilters = toServerFilterFields(
        adaptableApi,
        groupedColumn?.field ?? groupedColumn?.id
      );

      const sortModel = getRowsRequest.sortModel.map(sort => {
        const customSort = adaptableSortState.customSorts.find(
          customSort => customSort.ColumnId === sort.colId
        );
        if (customSort) {
          return {
            ...sort,
            sortedValues: customSort.SortedValues,
          };
        }
        return sort;
      });

      const request = {
        ...getRowsRequest,
        sortModel,
        ...serverFilters,
        includeSQL: true,
      };
      console.log('request to server', request);

      fetch(API_BASE, {
        method: 'post',
        body: JSON.stringify(request),
        headers: {'Content-Type': 'application/json; charset=utf-8'},
      })
        .then(httpResponse => httpResponse.json())
        .then(response => {
          params.success({
            rowData: response.rows,
            rowCount: response.lastRow ?? 0,
          });

          if (response.pivotFields?.length) {
            addPivotColumnDefs(response, params.api);
          } else {
            if (!adaptableApi.layoutApi.isCurrentLayoutPivot()) {
              if (params.api.getPivotResultColumns() != null) {
                params.api.setPivotResultColumns(null);
              }
            }
          }

          adaptableApi.systemStatusApi.setInfoSystemStatus(
            `SQL: ${response.sql.slice(0, 40)}`,
            response.sql
          );
        })
        .catch(error => {
          params.fail();
        });
    },
  };
}

function addPivotColumnDefs(response: any, agGridApi: GridApi) {
  const existingPivotColDefs = agGridApi.getPivotResultColumns();
  if (existingPivotColDefs && existingPivotColDefs.length > 0) {
    return;
  }

  const pivotColDefs: ColDef[] = response.pivotFields.map(function (
    field: any,
    index: number
  ) {
    const [_key, value] = Object.entries(field)[0];
    const valueStr = `${value}`;
    return {
      headerName: valueStr,
      field: valueStr,
      filter: false,
      colId: `${Date.now()}_${index}`,
      cellDataType: 'number',
    };
  });

  agGridApi.setPivotResultColumns(pivotColDefs);
}
```

**`src/gridOptions.ts`**

```ts
import {GridOptions, themeBalham} from 'ag-grid-enterprise';

import {columnDefs} from './columnDefs';

export const gridOptions: GridOptions = {
  theme: themeBalham,
  defaultColDef: {
    sortable: true,
    filter: true,
    floatingFilter: true,
    enableRowGroup: true,
  },
  rowModelType: 'serverSide',
  columnDefs: columnDefs,
  sideBar: ['adaptable', 'columns', 'filters'],
};
```

**`src/columnDefs.ts`**

```ts
import {ColDef} from 'ag-grid-enterprise';

export const columnDefs: ColDef[] = [
  {field: 'id', hide: true, cellDataType: 'text'},
  {
    field: 'athlete',
    cellDataType: 'text',
    enableRowGroup: true,
    resizable: true,
  },
  {
    field: 'country',
    cellDataType: 'text',
    enableRowGroup: true,
    resizable: true,
  },
  {
    field: 'sport',
    cellDataType: 'text',
    enableRowGroup: true,
    resizable: true,
  },
  {
    field: 'year',
    cellDataType: 'number',
    enableRowGroup: true,
    resizable: true,
  },
  {
    field: 'gold',
    aggFunc: 'sum',
    cellDataType: 'number',
    enableValue: true,
    resizable: true,
  },
  {
    field: 'silver',
    aggFunc: 'sum',
    cellDataType: 'number',
    enableValue: true,
    resizable: true,
  },
  {
    field: 'bronze',
    aggFunc: 'sum',
    cellDataType: 'number',
    enableValue: true,
    resizable: true,
  },
];
```
