Listing UI page template

Listing pages facilitate object type (*Info class) listing.

Listing page example from the Event log application

All listing pages must inherit from the ListingPage base class. Override the ObjectType property to specify which objects are listed.

Example

using CMS.Membership;

using Kentico.Xperience.Admin.Base;

public class UserObjectListing : ListingPage
{
    // Set to the object type of the object you wish to have listed
    protected override string ObjectType => UserInfo.OBJECT_TYPE;
}

Format displayed data

The listing page displays items of the selected object type in a grid-like structure, one item per line, and one field (database column) per column.

Specify the displayed item properties within an override of the ConfigurePage method. Call the AddColumn method on PageConfiguration.ColumnConfigurations and provide the name of the database column to be displayed.

Example - adding object properties (database columns) to the grid

using Kentico.Xperience.Admin.Base;

...

public override Task ConfigurePage()
{
    // Adds the specified columns to the grid and sets their caption
    PageConfiguration.ColumnConfigurations
                // Adds data from the 'UserName', 'FirstName', and 'Email' columns of the 'CMS_User' table
                .AddColumn(nameof(UserInfo.UserName), "User name")
                .AddColumn(nameof(UserInfo.FirstName), "First name")
                .AddColumn(nameof(UserInfo.Email), "Email");
}

You can further configure each column by supplying the following optional parameters to the AddColumn method:

  • Caption – sets a custom caption for the column heading. Defaults to the column name if not set.

  • Sortable – indicates whether the column can be used to sort the listing.

  • DefaultSortDirection – a SortTypeEnum value that determines the default order for properties in the column.

  • Formatter – sets the formatter to use for the column values.

    Example
    
      .AddColumn(nameof(UserInfo.UserCreated), formatter: (value, _) => String.Format("{0:MMMM dd, yyyy}", Convert.ToDateTime(value)))
      
  • Visible – default visibility state of the column. For hidden columns, the data is obtained from the server but hidden.

  • Searchable – indicates whether column values are searchable via a search box above the listing. If no columns are set as searchable, the search dialog is hidden.

  • Localizable – persisted values have localization macros resolved.

Add row actions

The listing template allows you to assign a page command that gets invoked when users select a row in the listing.

A typical use case for this is opening an item’s view or edit page (e.g., for users, contacts). For this purpose, the system provides the PageConfiguration.AddEditRowAction<TInfoEditPage> extension method. The method generates a link to an edit page for each item (by ensuring the corresponding object’s ID in the URL). Specify the System.Type of the corresponding edit layout page as the method’s generic parameter.

See the Page URLs and routing section on UI pages for more information about generating URL structures for admin pages.

Add header and item actions

The listing page allows you to add actions (page commands) to the page header (displayed to the left of the search box) and to each item in the grid (displayed in the Actions column).

Add actions to the listing by calling the following methods on either PageConfiguration.HeaderActions or PageConfiguration.TableActions:

  • AddDeleteAction – adds a delete action to individual items in the list. The base class provides a  Delete page command that deletes items based on an object’s ID.

    Delete page command provided by the ListingPage base class
    
      [PageCommand]
      public override Task<ICommandResponse<RowActionResult>> Delete(int id) => base.Delete(id);
      
  • AddLink<TPage> – adds a link to a specified page in the admin UI. Supply the System.Type of the target page as the method’s generic parameter.

  • AddCommand – adds a page command to the page header or an item’s Actions column.

Raise a confirmation prompt

You can optionally display a confirmation dialog when users select an action.

Raise a confirmation prompt on command invocation

public override Task ConfigurePage()
{
    PageConfiguration
        .TableActions
            .AddCommandWithConfirmation
                (
                    label: "Title",
                    command: nameof(DoSomething),
                    icon: Icons.ArrowsCrooked,
                    // Providing these properties raises a confirmation dialog
                    // when a user invokes the corresponding action
                    confirmation: "Are you sure you want to do this?",
                    confirmationButton: "Yes, please"
                );
}

When users invoke the action, they are prompted with the following confirmation.

Listing action confirmation dialog

Additionally, the dialog can also display a form that allows users to submit data to the corresponding command handler. Typically this can be of a multiple-choice selector that determines how the back end processes the action.

Listing action confirmation dialog with a form

The form must be defined within a dedicated model class and annotated with Editing components. Validation rules are also supported. The class must be annotated with CommandConfirmationModel.

Model defining the confirmation form

[CommandConfirmationModel]
public class ConfirmationDialogFormModel
{
    [RequiredValidationRule]
    [RadioGroupComponent(Inline = false, Order = 1,
                         Options = $"option1;Option 1\r\noption2;Option 2\r\n")]
    public string ChooseOne { get; set; } = "option1";
}

Specify the model class as a parameter within AddCommandWithConfirmation. The other parameters of the method are identical to AddCommand.

Raise a confirmation prompt on command invocation

public override Task ConfigurePage()
{
    PageConfiguration
        .TableActions
            .AddCommandWithConfirmation
                (
                    label: "Title",
                    command: nameof(DoSomething),
                    icon: Icons.ArrowsCrooked,
                    // Providing these properties raises a confirmation dialog
                    // when a user invokes the corresponding action
                    confirmation: "What do you want to do?",
                    confirmationButton: "This, please",
                    // Setting the confirmation model displays a dialog with form
                    confirmationModel: typeof(ConfirmationDialogFormModel)
                );
}

The corresponding command handler method must expect the form model in its signature. The system automatically binds the form received from the client to the corresponding parameter.

Action command handler

[PageCommand]
public async Task<ICommandResponse<RowActionResult>> ActionWithFormConfirmation(ConfirmationDialogFormModel model)
{
    // Decide what to do based on the received form data...

    return ResponseFrom(new RowActionResult(reload: true))
                 .AddSuccessMessage($"You have selected {model.ChooseOne}");
}

actionStateEvaluator

Some methods optionally take an Action delegate that allows you to programmatically disable or modify the commands they add for items meeting your specified criteria.

Example - disable the delete action for specific users

.AddDeleteAction(nameof(Delete), actionStateEvaluator: DisableDeleteForSpecificUsers);

// ...

// Disables the delete action for the default global administrator and public users
private void DisableDeleteForSpecificUsers(ActionConfiguration actionConfiguration, IDataRecord rowData)
{
    if (!actionConfiguration.Disabled)
    {
        var userName = Convert.ToString(rowData[nameof(UserInfo.UserName)]);

        if (String.Equals(userName, UserInfoProvider.AdministratorUserName, StringComparison.OrdinalIgnoreCase) ||
            String.Equals(userName, UserInfoProvider.PublicUserName, StringComparison.OrdinalIgnoreCase))
        {
            actionConfiguration.Disabled = true;
        }
    }
}

Add mass actions

The listing page template allows you to add actions executed upon a collection of items selected from the listing.

Mass actions example

Mass actions appear at the top of the listing – immediately below the search bar – when users select at least one listed item.

Page commands that implement mass actions must use the following signature:

Mass action method signature

public async Task<ICommandResponse<MassActionResult>> MassActionCommand(IEnumerable<int> identifiers, CancellationToken cancellationToken)

where IEnumerable<int> identifiers is a collection of object type IDs identifying the items selected by the user. Use conventional provider APIs to work with these objects.

Add mass actions using the following methods on PageConfiguration.MassActions:

  • AddCommand – adds a command to execute.
  • AddCommandWithConfirmation – adds a command with execution approved via a confirmation prompt.

Note that the actionStateEvaluator parameter offered by these methods is not supported by mass actions.

Register mass actions

public override async Task ConfigurePage()
{
    PageConfiguration
        .MassActions
            .AddCommandWithConfirmation
            (
                label: "Title",
                command: nameof(MyMassActionPageCommand),
                icon: Icons.ArrowsCrooked,
                // Providing these properties raises a confirmation
                // dialog when users invoke the added action
                confirmation: "Confirmation title",
                confirmationButton: "Confirm"
            );
}

Add React components to cells

The listing template supports loading React components inside dedicated columns within the grid via the AddComponentColumn extension method of the PageConfiguration.ColumnConfigurations object.

The method takes identical parameters as AddColumn, with the following additions:

  • string componentKey – the name of the React component to be displayed. Provide a full name in the {orgName}/{projectName}/component format, as specified when you set up your custom admin JS module. Make sure the component is exported (visible to other modules). For example: @acme/web-admin-custom/MyComponent
    When loading the component, the client application suffixes the provided value with TableCellComponent. For the provided example, the client React component must be named MyComponentTableCellComponent.

  • bool loadedExternally – indicates whether data present in the column is loaded externally (not a property of the *Info object being listed). For example, via a separate ObjectQuery call from a different object.

  • Func<object, IDataContainer, object> modelRetriever – a function that provides the props for the front-end component to be displayed. The component props data must be wrapped in a dedicated class (for serialization and transfer to the client admin application).

    • object – the localized (if localizable) and formatted (if a formatter was provided) value of the column to be listed. Empty if the loadedExternally flag is true.
    • IDataContainer – contains the entire database record of the object being listed (as IDataContainer). Column values can be accessed using indexer notation.

The system instantiates the selected component for each row, with the passed props.

Example

protected override string ObjectType => UserInfo.OBJECT_TYPE;

// ...

PageConfiguration.ColumnConfigurations
    // Adds a column displaying a React component with data loaded from UserInfo
    .AddComponentColumn(
        "assignedRoles",

        // Sets the name of the component to render
        // When loading the component, the client application automatically suffixes
        // the component name with 'TableCellComponent'. For this example, the React
        // component must be named 'MyComponentTableCellComponent'.
        "@orgName/projectName/MyComponent",

        // Sets column caption
        "Roles",

        modelRetriever: (formattedColumnValue, rowData) =>
        {
            // 'formattedColumnValue' contains the localized (if localizable) and
            // formatted (if a formatter was provided) value of the column
            // specified in 'columnName.'
            // Empty if the loadedExternally flag is set to 'true'

            // 'rowData' contains the entire user record
            // from the database (as 'IDataContainer').
            // Column values can be accessed using indexer notation.
            var userID = (int)rowData[nameof(UserInfo.UserID)];

            // Fetches roles assigned to individual users using external logic
            // (omitted for brevity).
            var userRoles = GetRoles(userID);

            // Instantiates a DTO consisting of props for the displayed component
            return new ReactComponentToDisplayProps
            {
                prop1 = userRoles.ToString(),
                prop2 = ...
            };
        },

        // Column data will not be fetched as part of the
        // ObjectQuery retrieving other UserInfo object data
        loadedExternally: true,
        sortable: false);

Add callouts

Callouts are information messages that can be displayed above the listing grid.

Callout example

You can add a callout via PageConfiguration.Callouts.

Example - adding a callout

public override Task ConfigurePage()
{
    PageConfiguration.Callouts = new List<CalloutConfiguration>()
    {
        new CalloutConfiguration
        {
            Headline = "Callout headline",
            Content = "Callout text",
            Type = CalloutType.QuickTip,
            Placement = CalloutPlacement.OnDesk
        }
    };
}

Where CalloutType determines the style of the callout:

  • QuickTip – a light blue unobtrusive design intended for general information messages
  • FriendlyWarning – tinted yellow, designed to attract user attention

And CalloutPlacement determines the callout element width:

  • OnDesk – half-page width
  • OnPaper – full-page width

Modify loaded data

You can modify the query that retrieves objects for display in the grid via PageConfiguration.QueryModifiers and the AddModifier method. The query uses the system’s ObjectQuery API to retrieve the table data. See ObjectQuery API to learn more about the feature and available parameterization options.

Modifiers can be useful if you wish to, for example, filter listed objects based on other parameters present in the URL. See the Page URLs and routing section on UI pages for more information about routing, the page URL hierarchy, and passing parameters via page URLs.


// Binds a site ID parameter provided by a parent page in the hierarchy
[PageParameter(typeof(IntPageModelBinder))]
public int SiteId { get; set; }

...

public override Task ConfigurePage()
{
    PageConfiguration.QueryModifiers
        .AddModifier((query, _) =>
        {
            // Filters displayed objects by the 'SiteID' parameter
            return query.WhereEquals(nameof(SiteDomainAliasInfo.SiteID), SiteId);
        });
}

Add a listing filter

Listing pages often contain a large number of objects. Filters allow users to limit which objects are displayed according to specified criteria.

You can implement filters for both your own custom listing pages and the default listing pages in the Xperience administration UI.

Create the filter model

  1. Add a model class to your custom admin project with properties representing individual inputs in the filter.

  2. Define the filter UI by assigning Editing components to the properties (like when preparing model-based edit pages).

  3. Decorate the filter properties with the FilterCondition attribute (available in the Kentico.Xperience.Admin.Base.Filters namespace). The attribute has the following parameters:

    • BuilderType – a type implementing IWhereConditionBuilder, which generates the where condition applied to the listing for the given property. See Create filter condition builders below for more information.

    • ColumnName – the column name passed to the IWhereConditionBuilder implementation assigned to the property. Specify a column of the object type displayed in the listing, e.g., using the nameof expression.

      Both attribute parameters are optional.

      If you omit BuilderType, a basic equality condition builder is used. The condition is fulfilled if the specified column is equal to the filter property’s value.

      If ColumnName is omitted, the name of the property in the model class is used as the column name.

      Properties without the FilterCondition attribute automatically generate a basic equality condition (same behavior as when using the attribute with both parameters omitted).

When the filter is applied, the conditions are generated for all properties in the model class and combined using the SQL AND operator. The resulting listing displays only items that fulfill all of the conditions.

Filter model example

using System.Collections.Generic;

using CMS.Membership;

using Kentico.Xperience.Admin.Base.Filters;
using Kentico.Xperience.Admin.Base.FormAnnotations;
using Kentico.Xperience.Admin.Base.Forms;

public class UserListFilterModel
{
    // Text input that filters listed users based on their email address
    [TextInputComponent(Label = "User email", Order = 0)]
    [FilterCondition(BuilderType = typeof(ContainsTextConditionBuilder), ColumnName = nameof(UserInfo.Email))]
    public string UserEmail { get; set; }

    // Drop-down selector that filters the listing to show only active or inactive user accounts
    [DropDownComponent(
        Label = "User status",
        Placeholder = "Any",
        Options = "true;Active\r\nfalse;Inactive",
        Order = 1)]
    [FilterCondition(BuilderType = typeof(IsTrueConditionBuilder), ColumnName = nameof(UserInfo.UserEnabled))]
    public string UserEnabled { get; set; }
}

Create filter condition builders

To define filtering conditions, prepare classes that implement IWhereConditionBuilder (available in the Kentico.Xperience.Admin.Base.Filters namespace). Define the Build method and return an IWhereCondition object (available in the CMS.DataEngine namespace).

Recommendations for implementing the Build method:

  • Always return an instance of the WhereCondition class (except when throwing exceptions for invalid error states). If you do not wish to affect the filtering condition, return a new (empty) WhereCondition object rather than null.
  • Use the predefined where condition methods available in the ObjectQuery API to build your conditions.
  • The value parameter is passed as an object, so perform appropriate type-testing and casting operations.
  • Consider performance when creating and assigning where condition builders to listings with a large number of items. For example, a WhereStartsWith condition provides better performance than WhereContains.
  • To avoid security vulnerabilities, never use the value parameter to directly pass SQL query text to the where condition.
Contains text IWhereConditionBuilder example

using System;
using System.Threading.Tasks;

using CMS.DataEngine;

using Kentico.Xperience.Admin.Base.Filters;

public class ContainsTextConditionBuilder : IWhereConditionBuilder
{
    public Task<IWhereCondition> Build(string columnName, object value)
    {
        if (string.IsNullOrEmpty(columnName))
        {
            throw new ArgumentException($"{nameof(columnName)} cannot be a null or an empty string.");
        }

        // Creates a new where condition
        var whereCondition = new WhereCondition();

        if (value is null || value is not string )
        {
            // Returns an empty condition if a valid value is not specified
            return Task.FromResult<IWhereCondition>(whereCondition);
        }

        // Adds a where condition returning only items that contain the value in the specified column
        whereCondition.WhereContains(columnName, (string)value);

        return Task.FromResult<IWhereCondition>(whereCondition);
    }
}
Is true IWhereConditionBuilder example

using System;
using System.Threading.Tasks;

using CMS.DataEngine;

using Kentico.Xperience.Admin.Base.Filters;

public class IsTrueConditionBuilder : IWhereConditionBuilder
{
    public Task<IWhereCondition> Build(string columnName, object value)
    {
        if (string.IsNullOrEmpty(columnName))
        {
            throw new ArgumentException($"{nameof(columnName)} cannot be a null or an empty string.");
        }

        // Creates a new where condition
        var whereCondition = new WhereCondition();

        if (value is null || value is not string userEnabled)
        {
            // Returns an empty condition if a valid filtering option is not selected
            return Task.FromResult<IWhereCondition>(whereCondition);
        }

        if (userEnabled.Equals("true", StringComparison.InvariantCultureIgnoreCase))
        {
            // Adds a where condition returning only enabled user accounts
            whereCondition.WhereTrue(columnName);
        }

        if (userEnabled.Equals("false", StringComparison.InvariantCultureIgnoreCase))
        {
            // Adds a where condition returning only users that are not enabled
            whereCondition.WhereFalse(columnName);
        }

        return Task.FromResult<IWhereCondition>(whereCondition);
    }
}

Assign filters to listing pages

To add a filter to your own custom listing pages, assign an instance of your filter model class to the UI page’s PageConfiguration.FilterFormModel property.

Example - Assign a filter

public override Task ConfigurePage()
{
    ...

    // Assigns a listing filter model
    PageConfiguration.FilterFormModel = new UserListFilterModel();
}

If you wish to add a filter to an existing listing page in the Xperience administration, set the FilterFormModel property through a UI page extender.

Example - Assign a filter through an extender

[assembly: PageExtender(typeof(UserListExtender))]

public class UserListExtender : PageExtender<UserList>
{
    // Configures the listing template on which UserList is based
    public override Task ConfigurePage()
    {
        var configuration = Page.PageConfiguration;

        // Assigns a listing filter model
        configuration.FilterFormModel = new UserListFilterModel();

        return base.ConfigurePage();
    }
}

Reference - PageConfiguration property

The following table provides a reference of template configuration options available for listing pages via the PageConfiguration property.

Property

Type

Description

Caption

string

The page’s title.

Callouts

List<CalloutConfiguration>

Configures an optional message box element that can be displayed above the listing. See Add callouts.

ColumnConfigurations

IList<ColumnConfiguration>

See Format displayed data.

FilterFormModel

object

An instance of the model class that defines the filter UI for the listing. See Add a listing filter.

HeaderActions

IList<ActionConfiguration>

Actions available from the table header. See Add header and item actions.

MassActions

IList<ActionConfiguration>

Actions available when selecting more than one item in the listing. See Add mass actions.

MaxVisibleRowActions

int

Determines the maximum number of table actions displayed for each item. Additional actions are hidden behind an expandable menu.

PageSizes

IEnumerable<int>

Available page sizes for the listing.

QueryModifiers

IList<QueryModifier>

Collection of modifications to the displayed data. See Modify loaded data.

RowAction

ActionConfiguration

Action that occurs when a user clicks on a row in the listing.

Set via the AddEditRowAction method. See Add row actions.

TableActions

IList<ActionConfiguration>

Actions available for each item in the listing. See Add header and item actions.

Register listing pages

Listing pages need to specify the TemplateNames.LISTING client template during registration.

Example listing page registration

using Kentico.Xperience.Admin.Base;

[assembly: UIPage(
    parentType: typeof(UsersApplication),
    slug: "list",
    uiPageType: typeof(UserObjectListing),
    name: "Users",
    templateName: TemplateNames.LISTING,
    order: 0)]

Client template – LISTING

Listing pages must use the LISTING client template.

The template contains the following default page commands:

  • LoadData – refreshes the data on the page.
  • Delete – deletes the item with the specified int identifier.