Skip to content

GroupingList Controller

The GroupingList controller renders a data grid with all records grouped by a specified field, featuring collapsible group headers with expand/collapse toggles. Unlike the standard List controller, GroupingList loads all records at once (no paging) and organizes them into visual groups.

It inherits from the List controller, reusing its filter panel, toolbar, column headers, and standard actions (Add, Edit, View, Delete). The only difference is in how records are displayed: instead of a flat paginated table, records appear under collapsible group headings.

Basic usage

Set Controller: GroupingList on the view and configure the Grouping node under MainTable/Controller:

yaml
Type: Data

Controller: GroupingList

MainTable:
  Model: ACTIVITY
  Controller:
    Grouping:
      FieldName: TYPE
      StartCollapsed: True
      ShowCount: True
        ItemName: activity
        PluralItemName: activities

This produces a grid where activities are grouped by the TYPE field (resolved through its reference to display the type name), with each group initially collapsed. The group header shows the type name and the count of records, for example: Analysis (5 activities).

Grouping configuration

All grouping properties go under MainTable/Controller/Grouping:

PropertyTypeDefaultDescription
FieldNameString(required)Name of the field to group by. Can be any field including reference fields (which display the referenced value) or expression-based fields.
StartCollapsedBooleanFalseIf True, all groups appear initially collapsed (only the group header is visible). Users can click to expand.
ShowCountBooleanFalseIf True, shows the record count in each group header.
ShowCount/ItemNameString(empty)Singular noun used when count is 1 (e.g. activity).
ShowCount/PluralItemNameString(empty)Plural noun used when count > 1 (e.g. activities).
ShowNameBooleanFalseIf True, prefixes the group value with the field's display label (e.g. Type: Analysis instead of just Analysis).
SortFieldNamesString(grouping field)Space-separated list of field names for the ORDER BY clause. Defaults to the grouping field. Use this to add a secondary sort within each group.
AggregatesNode(none)Per-group totals appended to each header — see Aggregates in the group header.

Properties inherited from Kitto1 metadata that are not supported

ShowCount/Template and EnableMenu are accepted by the YAML parser but ignored. See Migrating a grouped view from Kitto1.

Aggregates in the group header

Each child of Aggregates names a field of the view table and describes what to compute over the records of the group:

yaml
Grouping:
  FieldName: Descrizione
  Aggregates:
    ImportoDare:
      Operation: Sum
      Label: Totale Dare
    ImportoAvere:
      Operation: Sum
      Label: Totale Avere
0601 - Cassa - Totale Dare: € 6.156,80 - Totale Avere: € 4.110,80
PropertyDefaultDescription
OperationSumSum, Avg, Min, Max or Count. Count counts the non-null values of the field, which is not the same as the record count of ShowCount.
Labelfield's DisplayLabelText shown before the value. Localized like every other label.

Aggregates are listed in the order they appear in the YAML, each separated by -, and they coexist with ShowName and ShowCount.

The value is formatted like the column it comes from: the user's format settings, and the currency symbol when the field is a Currency. Count is always a plain integer.

Only numeric fields can be aggregated. A field that does not exist in the view table, or that is not numeric, is skipped and the reason is written to the log — the header renders without it rather than failing. If a total you expect is missing, look there first.

A group with nothing to report shows nothing. When the column is null throughout the group, the aggregate is left out instead of being rendered as zero, which would read as a real total. Count is the exception: zero is a legitimate answer to "how many values are there".

Cost

Aggregates and ShowCount share a single pass over the records, and that pass is skipped entirely when neither is declared. Adding aggregates to a view that already shows the count costs nothing.

Group header format

The group header text is built as follows:

  • ShowName: False (default): Analysis
  • ShowName: True: Type: Analysis
  • ShowCount: True with count=1 and ItemName: Analysis (1 activity)
  • ShowCount: True with count>1 and PluralItemName: Analysis (5 activities)
  • ShowCount: True without ItemName/PluralItemName: Analysis (5)

Sorting

By default, records are sorted by the grouping field only. Use SortFieldNames to define a multi-field sort:

yaml
Grouping:
  FieldName: TYPE
  SortFieldNames: TYPE ACTIVITY_DATE

This sorts first by TYPE (to keep groups together), then by ACTIVITY_DATE within each group. Each field name in the list must correspond to a field defined in the view table.

Expand/Collapse behavior

  • Clicking a group header toggles the visibility of the data rows in that group.
  • The toggle icon changes between (collapsed) and (expanded).
  • The toggle is pure client-side JavaScript (no server round-trip).

Column layout

GroupingList selects and orders its columns exactly like the GridPanel: a layout named <ViewName>_Grid.yaml under Metadata\Views\Layouts is picked up by convention, or you can name one explicitly in Controller/Grid/Layout to share it between similar views. Per-field properties such as DisplayLabel, DisplayWidth and Align apply to grouped grids in the same way.

Without a layout, the columns are all the visible fields of the view table, in model order.

The grouping field gets no column of its own. Its value heads every group, so a column would repeat it identically on every row. This matches Kitto1, whose ExtJS grouping view set HideGroupedColumn, and it applies whether the columns come from a layout or from the view table.

You do not need to remove the field from the grid layout: leaving it there is harmless — it keeps the layout usable by an ungrouped view that shares it — and the grouped grid simply skips it. HelloKitto's Parties_Grid declares Party_Period for exactly that reason.

Filters

GroupingList supports the same filter system as the List controller. If a Filters node is defined under Controller, the filter panel is rendered above the grid and all records are reloaded (with the filter applied) when the user changes filter criteria.

Toolbar and actions

The toolbar is identical to the List controller: Add, Edit, View, Delete, Refresh, and custom ToolViews are all supported. Double-clicking a data row opens the form in Edit or View mode (depending on permissions).

Complete example

From the TasKitto sample application (ActivitiesByType.yaml):

yaml
Type: Data

Controller: GroupingList

MainTable:
  PluralDisplayLabel: Activities by Type
  Model: ACTIVITY
  EditController:
    CloneButton:
    Width: 1220
    Height: 460

  Controller:
    Form:
      Layout: ActivityInput_Form

    Grouping:
      FieldName: TYPE
      EnableMenu: False
      StartCollapsed: True
      ShowName: False
      ShowCount: True
        ItemName: activity
        PluralItemName: activities
      SortFieldNames: TYPE ACTIVITY_DATE

Differences from List controller

FeatureListGroupingList
PagingYes (configurable page size)No (all records loaded)
Record displayFlat rowsGrouped with collapsible headers
SortingUser-clickable column sort with arrow indicatorsFixed sort by SortFieldNames
FiltersSupportedSupported
ToolbarSupportedSupported
Grid layoutSupportedSupported
Grouping field as a columnShownHidden (its value heads the group)
Double-clickOpens formOpens form

Cell rendering is otherwise identical, HTMLMemo fields included: their markup is emitted as is in both controllers.

Because GroupingList loads every record, it is best paired with IsLarge: True on the model: that makes AutoOpen default to False, so the grid opens empty and the user populates it through a filter instead of pulling the whole table.

Migrating a grouped view from Kitto1

The Grouping node alone is not enough

In Kitto1 grouping was a property of the standard grid: any view could declare a Grouping node under Controller: List and the grid would group. In Kittox grouping is a separate controller, and a Grouping node under Controller: List is ignored without any error or warning — the view simply renders flat. When porting a grouped view, changing the controller type is the first and mandatory step.

yaml
# Kitto1
Controller: List
  ...
    Grouping:
      FieldName: Hair

# Kittox
Controller: GroupingList
  ...
    Grouping:
      FieldName: Hair

Translating ShowCount/Template

Kitto1 composed the header from an ExtJS template, GroupTextTpl. That syntax is not supported: it was evaluated client-side against an ExtJS store, which no longer exists. Express the same intent with the properties above:

Kitto1 TemplateKittox equivalentResult
{text}ShowName: False, ShowCount: FalseAnalysis
{text} ({[values.rs.length]})ShowCount: TrueAnalysis (5)
Label ({text})ShowName: True plus the field's DisplayLabelLabel: Analysis

Note the punctuation in the third case: the label is joined with a colon, not parentheses. If the field's general DisplayLabel is not the wording you want in the header, set it on the field and override the form caption with DisplayLabel_Form.

Templates that compute aggregates — anything calling a JavaScript helper over values.rs, such as a per-group total — are expressed declaratively by the Aggregates node. A Kitto1 template like

{text} - Totale €: {[sumValues(values.rs, "ImportoTot")]}

becomes

yaml
ShowCount: False
Aggregates:
  ImportoTot:
    Operation: Sum
    Label: Totale

ShowCount: True did not mean "show the number"

In Kitto1 it was the switch that enabled a custom header, and the count appeared only because it was part of the default template. A view that declared a Template of its own without a count showed no count. Port such a view with ShowCount: False, or it gains a (N) it never had.

EnableMenu

The ExtJS grouping menu (group by any column, turn grouping off) has no counterpart. The property is ignored; leaving EnableMenu: False in a ported view is harmless.

Limitations

  • Top-level views only. Grouping is activated by the controller type, so it cannot be applied to a detail grid inside a form: a Grouping node under DetailTables/Table/Controller has no effect.
  • No paging. Grouping and paging are mutually exclusive: a group would otherwise be split across pages.

Released under Apache License, Version 2.0.