Listing UI page template
Listing pages facilitate object type (*Info class) listing.
All listing pages must inherit from the ListingPage
base class. Override the ObjectType
property to specify which objects are listed.
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.
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 aDelete
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 theSystem.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.
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.
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.
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.
[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
.
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.
[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.
.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 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:
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.
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 withTableCellComponent
. For the provided example, the client React component must be namedMyComponentTableCellComponent
.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 istrue
. - IDataContainer – contains the entire database record of the object being listed (as
IDataContainer
). Column values can be accessed using indexer notation.
- object – the localized (if localizable) and formatted (if a formatter was provided) value of the column to be listed. Empty if the
The system instantiates the selected component for each row, with the passed props.
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.
You can add a callout via PageConfiguration.Callouts
.
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
Add a model class to your custom admin project with properties representing individual inputs in the filter.
Define the filter UI by assigning Editing components to the properties (like when preparing model-based edit pages).
- Filter model classes currently do not support validation rules, visibility conditions or component state configurators.
Decorate the filter properties with the
FilterCondition
attribute (available in theKentico.Xperience.Admin.Base.Filters
namespace). The attribute has the following parameters:BuilderType
– a type implementingIWhereConditionBuilder
, 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 theIWhereConditionBuilder
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.
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 thannull
. - Use the predefined where condition methods available in the ObjectQuery API to build your conditions.
- The
value
parameter is passed as anobject
, 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 thanWhereContains
. - To avoid security vulnerabilities, never use the
value
parameter to directly pass SQL query text to the where condition.
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);
}
}
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.
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.
[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> | |
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 |
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.
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.