You can build a cell-oriented pivot table in a ViewModel Aided View when you need different content and actions in individual intersections of a row and column, rather than one fixed set of columns for every row.
A pivot table displays values along two dynamic axes. For example, you can show months as columns, products as rows, and an editable budget, status, or action in each product/month cell.
MDriven's HTML/Bootstrap pivot approach keeps searching, filtering, ordering, and pagination in the model and ViewModel rather than making them responsibilities of a browser grid component. This means the same ViewModel logic can be used by other client technologies. You can still supply data from a ViewModel to a third-party grid when that is appropriate.
When to use a pivot
Use this pattern when the columns are data-driven and each cell may need its own rendering or behavior.
| Need | Recommended approach |
|---|---|
| A fixed set of columns and one record per row | Use a normal table or grid. |
| Dynamic row and column axes, such as products by month | Use this pivot pattern. |
| Search, filter, ordering, or paging over a large result set | Put the logic in the ViewModel and server-side query process. Read Documentation:Tables, search and ordering. |
| A custom WPF control that renders a pivot from named ViewModel nestings | Follow HowTos:Custom Controls in ViewModel Aided Views. |
| A Blazor CSS-grid layout composed from placing containers | Use the Blazor Pivot structural-component pattern described in the Blazor Pivot walkthrough. It is a separate approach from the row/cell data structure described on this page. |
The required ViewModel shape
Create a root ViewModel object, such as CurrentMonthView. Its data must provide:
- ordered column headers;
- ordered rows; and
- all cells for each displayed row.
The renderer lays out cells in the order in which it receives them. It does not use an arbitrary paint order to return later and fill a missing position. Therefore, provide rows in display order and provide every required cell for each row.
A typical structure uses the following ViewModel classes or equivalent modeled classes:
| Part | Purpose | Example |
|---|---|---|
| Root object | Holds the pivot data for the current screen. | CurrentMonthView
|
ColumnHeader
|
Provides the displayed columns, in left-to-right order. | January, February, March |
Row
|
Provides the displayed rows, in top-to-bottom order. | Consulting, Training, Support |
AllCellsList
|
Provides the cells that belong to rows. Each cell carries a row identifier and is supplied in column order. | Consulting/January, Consulting/February, Consulting/March |
There is no Y-position on Row and no X-position on ColumnHeader. The sequence of the header list determines column order, and the sequence of the row list determines row order.
Both Row and AllCellsList must expose a row identifier. The renderer uses this value to group cells with their row, then lays out those cells in their supplied column order.
Example: budget by product and month
Suppose the screen displays a budget plan for three products and three months.
| Column headers, in order | Rows, in order | Cells for each row, in order |
|---|---|---|
| Jan, Feb, Mar | Consulting | Consulting/Jan, Consulting/Feb, Consulting/Mar |
| Jan, Feb, Mar | Training | Training/Jan, Training/Feb, Training/Mar |
| Jan, Feb, Mar | Support | Support/Jan, Support/Feb, Support/Mar |
Give the Consulting row and its three cells the same RowId, for example Consulting. Give Training a different RowId, for example Training. The identifier must be unique within the pivot shown on the screen; it does not need to be globally unique.
Do not omit February for Consulting and expect the next cell to be placed correctly. Supply a cell for every displayed column. If a cell has no business value, provide a cell object that renders the intended empty content.
Build the pivot data
You can create the structure in either of these ways:
- Use a ViewModel and OCL expressions to add objects to the lists. The list can contain objects of different types when their ViewModel attributes evaluate to the fields required by the pivot pattern.
- Model transient or persistent classes that organize the data into the required root, headers, rows, and cells. Transient classes are useful when the pivot is a calculated screen structure rather than stored business data.
For maintainability, use subclasses for cells with different responsibilities. For example, a MonthViewCell base class can have subclasses for an editable budget cell, a calculated total cell, and an action cell. Put the derived-attribute OCL for each cell type on the relevant subclass. This keeps the logic for each kind of cell close to that cell type and lets you attach the appropriate action to the cell.
Configure the pivot
Configure the pivot against the ViewModel fields that implement the structure.
- Create the root object and the ViewModel classes or modeled classes for headers, rows, and cells.
- Populate the column-header list in the exact left-to-right display order.
- Populate the row list in the exact top-to-bottom display order.
- Populate
AllCellsListso that every displayed row has all of its cells, in the same column order as the header list. - Add the row identifier attribute to both the
RowViewModel class and theAllCellsListViewModel class. - Set the pivot tagged values to the names of the ViewModel fields that provide the header, row, cell, and row-identifier values. If a tagged value is not set, the pivot uses its default field name without the
Attrsuffix. - Run the screen and verify that each row has the expected number of cells before adding cell-specific presentation or actions.
Tagged values
The tagged values map the pivot's expected fields to the names you selected in the ViewModel. The RowIdAttr mapping requires particular attention: it must identify the attribute in both the row data and the cell data whose equal values bind cells to the correct row.
The following presentation mappings are optional:
| Tagged-value mapping | Use |
|---|---|
FormatAttr
|
Supplies a format value for a cell. |
CssClassAttr
|
Supplies a CSS class for a cell. |
StyleAttr
|
Supplies an inline style value for a cell. |
ValueFormatFilterName
|
Supplies the value-format filter name. |
A CurrentMonthViewColumnHeaderRow configuration does not require tagged values.
Put behavior in cells
A pivot is intended to render different content at different intersections. Put an action on the cell type that owns the behavior instead of treating the entire row as the unit of interaction.
For example, an editable budget cell can expose an edit action while a calculated total cell shows a formatted value without an edit action. For the behavior and limitations of actions rendered inside tables, see Documentation:Web client actions in tables.
If you need normal table editing behavior, see Documentation:Nesting.Editable.
Ordering, filtering, and performance
Keep the pivot data set deliberate. When users search, filter, page, or change ordering for a partial result set, rerun the appropriate server-side query and rebuild the ordered headers, rows, and cells. Client-side ordering of only the loaded page does not represent the full result set. Tables, search and ordering explains this distinction.
The pivot's ordering rules are strict:
- The header sequence is the column sequence.
- The row sequence is the row sequence.
- The cell sequence within a row is the column sequence.
- Every displayed row must include all displayed cells.
- Row identifiers bind cells to rows; they do not determine visual column placement.
Related custom-control pattern
The WPF custom-control example uses named nestings XAxis, YAxis, and Values. Its value objects provide X, Y, and Value; optional HeaderKey values on the axes can provide a more reliable match than the displayed header text. That control is delivered with the Modlr installation as WPFViewModelExternalControls.PivotDataGrid. For setup details and the custom control contract, see HowTos:Custom Controls in ViewModel Aided Views.
