ListEdit module

The ListEdit module allows you to manage basic item entities, i.e. with a structure not linked to DataWeb functions, with a few configuration steps. The entity must have an Id field and can have a Status field for managing item statuses.

 

The Status field is used to decide when removing the item whether to apply an effective removal from storage or whether to apply a "soft delete" by setting the status to "Deleted" while keeping the entity in the database.

 

To activate a section with a ListEdit module, simply configure the settings by specifying the support table, the element editing form and the fields to be shown in the list.

 

 

ListEdit supports most of the entity management functions except those specific to DataWeb items (such as versions or navigation).

Custom actions are supported on both the individual element and the filter.

 

The module can be used with the configuration in DataWeb only but, if necessary, it can be customized in each function by setting a class inherited from the base class ModuleListEdit and overwriting the methods concerned. The new class is set in the relevant settings in DataWeb.

 

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using DataWeb.Data;
namespace DataWeb.Structure.Modules
{
    public class ModuleSubscriberListEdit(Section section, NavigationContext navigationContext, IServiceProvider serviceProvider) : ModuleListEdit(section, navigationContext, serviceProvider)
    {
        private readonly TimeProvider timeProvider = serviceProvider.GetService<TimeProvider>();
        public override Task ProcessOnSaveDataAsync(BasicItem basicItem, List<Form.ProvidedValue> providedValues, CancellationToken cancellationToken = default)
        {
            if (basicItem.Id == StructureDefinition.VoidId)
            {
                basicItem.Data["SubscriptionDate"] = timeProvider.GetLocalNow();
            }
            return Task.CompletedTask;
        }
    }
}

 

In this example, we are refining the storage of the item so that if it is new, the registration date is also set.

 

This table can be used as a reference to identify areas of the ModuleListEdit base class that can be customized by creating a derived class, such as the ModuleSubscriberListEdit example.

 

MethodDescriptionPossible customization
GetDataAsyncRetrieves item data and returns a ListData object containing item information, data fields, widgets, search filters, and pagination.You can customize how data is retrieved and transformed, or add widget-specific logic, filters, or sorting behavior.
GetDeferredDataAsyncRetrieves additional data asynchronously for specified items. Used to load "deferred" data.It can be overridden to change the lazy data loading behavior or to add custom logic.
ConvertToListItemConverts a BasicItem to a ListItem that contains the data fields, widgets, and other impersonation information.Overwrite to customize the conversion from BasicItem to ListItem (e.g. add custom logic for data fields).
GetDataFieldsAsyncRetrieves the data fields configured for the section.Customize to add or edit data fields that are visible to users.
GetListWidgetsRetrieves the widgets configured for the list.Overwrite to add custom widgets or edit existing widgets.
ProcessListWidgetsValuesProcesses widget values for specified items.Customize logic to process or calculate widget values based on item data or user parameters.
ProcessListWidgetsDeferredValuesAsyncProcess deferred widget values asynchronously.You can customize how deferred values are handled, adding logic for complex calculations or external calls.
GetItemIsWriteDetermines whether an item is editable. Always returns true by default.Overwritable to add logic that limits the possibility of modification (e.g. based on user permissions or item status).
GetFormDataAsyncRetrieves the data necessary for displaying the item modification form.It can be customized to change the behavior of the data load or add dynamic default values.
SaveItemAsyncSave an item, validate its data, update the database and record audit events.Overwritable to add custom logic to the save (e.g. update extra fields or send notifications).
ProcessOnSaveDataAsyncExecuted during saving to process additional item data.It can be overridden to add custom logic for saving, as shown in the example with the subscription date (SubscriptionDate).
CloneItemAsyncCreates a copy of an existing item.Customizable to change cloning behavior or to add extra logic (e.g. logging or editing cloned fields).
DeleteItemsAsyncDelete one or more items. It can perform a "soft delete" (setting the status to Deleted) or an actual removal from storage.Overwritable to change the behavior of the erasure, such as to manage permissions or perform additional actions.
SetSearchAsyncUpdate the search filters based on the parameters provided.Overwritable to add custom logic for search filters.
GetSearchSuggestionsAsyncProvides search suggestions based on the configured fields.Customize to add autocomplete logic or advanced suggestions.
SetPageIndexAsyncSets the current page index for pagination.It can be customized to add control logic on the page index.
SetOrderByAsyncChanges the sort order of the list based on the specified field.It can be customized to add advanced logic for sorting or support new types of sorting.
GetActionsAsyncRetrieves the actions available for the section.Overridable to add custom actions.
ProcessActionAsyncManages the execution of the actions that you define.It can be overridden to add custom logic for custom actions, such as exports or specific processes.
ProcessExportAsyncProcess the export of items to an Excel file.Overwritable to customize the output of the exported file (e.g. add extra columns or change the format).

 

The GetBasicItemsAsync method can be used in various contexts to quickly retrieve both filtered and selected items (specifying the Ids of the items involved via itemIds). For example:

 

        public override async Task<ContextAction.Result> ProcessActionAsync(ContextAction action, IUser user, List<string> itemIds = null, List<Form.ProvidedValue> controlValues = null, NavigationContext navigationContext = null, CancellationToken cancellationToken = default)
        {
            var result = await base.ProcessActionAsync(action, user, itemIds, controlValues, navigationContext, cancellationToken);

            switch (action.Name)
            {
                case "FilteredItemsExport":
                    var basicItems = (await GetBasicItemsAsync(false, cancellationToken: cancellationToken)).BasicItems;
                    result = new ContextAction.Result { IsValid = true, Stream = await ProcessExportAsync(basicItems, cancellationToken), FileName = string.Format("{0}.xlsx", section.Title), ContentType = "application/vnd.ms-excel" };
                    break;
                case "SelectedItemsExport":
                    if (itemIds == null || itemIds.Count == 0)
                    {
                        return new ContextAction.Result { IsValid = false, Error = new ValidationError { Name = "ActionName", Message = "items are required" } };
                    }
                    var selectedBasicItems = (await GetBasicItemsAsync(false, itemIds: itemIds, cancellationToken: cancellationToken)).BasicItems;

                    result = new ContextAction.Result { IsValid = true, Stream = await ProcessExportAsync(selectedBasicItems, cancellationToken), FileName = string.Format("{0}.xlsx", section.Title), ContentType = "application/vnd.ms-excel" };
                    break;
            }

            return result;
        }

 

Example of customization to manage the write permission on the item:

     public override bool GetItemIsWrite(BasicItem basicItem)
        {
            return basicItem.Data.ContainsKey("IsSystem") && !Convert.ToBoolean(basicItem.Data["IsSystem"]);
        }

 

Example of customization to set a list in the filter selector:

        public override async Task<List<SearchInfo.SearchFilter>> GetSearchFiltersAsync(IEnumerable<ListData.DataField> dataFields, IEnumerable<UserSetting> userSettings, CancellationToken cancellationToken = default)
        {
            var searchFilters = await base.GetSearchFiltersAsync(dataFields, userSettings, cancellationToken);

            // Set TemplateContext
            var searchFilterTemplateContext = searchFilters.FirstOrDefault(x => x.Name == "TemplateContext");
            if (searchFilterTemplateContext != null)
            {
                searchFilterTemplateContext.Type = "List";
                searchFilterTemplateContext.ListValues = (await templateService.GetTemplateContextsAsync(cancellationToken)).Select(x => new List.ListItem(x)).ToList();
            }

            return searchFilters;
        }

 

Example of customizing to set an action:

        public override async Task<List<ContextAction>> GetActionsAsync(CancellationToken cancellationToken = default)
        {
            var actions = await base.GetActionsAsync(cancellationToken);

            actions.Add(contextActionService.GetRemoteAction(localizer["DataWeb.Explorer.ModuleResourceListEdit_CachedResourcesUpdate"], "CachedResourcesUpdate", ContextActionType.Update, context: "FilteredItems", isConfirmRequired: true, isReloadAfterProcess: false, isSaveItemBeforeProcess: false));

            return actions;
        }

        public override async Task<ContextAction.Result> ProcessActionAsync(ContextAction action, IUser user, List<string> itemIdMasters = null, List<Form.ProvidedValue> controlValues = null, NavigationContext navigationContext = null, CancellationToken cancellationToken = default)
        {
            var result = await base.ProcessActionAsync(action, user, itemIdMasters, controlValues, navigationContext, cancellationToken);

            switch (action.Name)
            {
                case "CachedResourcesUpdate":
                    result = await UpdateCachedResourcesAsync(cancellationToken);
                    break;
            }

            return result;
        }

 

Example of customization to retrieve an entity other than basic item:

        public override async Task<ListData> GetDataAsync(IEnumerable<UserSetting> userSettings, CancellationToken cancellationToken = default)
        {
            await section.InitListWidgetsAsync(serviceProvider, cancellationToken);

            var dataFields = await GetDataFieldsAsync(cancellationToken);

            var filter = new ResourceFilter
            {
                IsDeletedIncluded = navigationContext.User.PublishMode == PublishMode.Maintenance,
                Culture = userSettings.FirstOrDefault(x => x.Name == "SearchCulture")?.Value,
                CultureMode = userSettings.FirstOrDefault(x => x.Name == "SearchCultureMode")?.Value,
                ResourceContext = userSettings.FirstOrDefault(x => x.Name == "SearchResourceContext")?.Value,
                SearchResourceGroup = userSettings.FirstOrDefault(x => x.Name == "SearchResourceGroup")?.Value,
                SearchResourceKey = userSettings.FirstOrDefault(x => x.Name == "SearchResourceKey")?.Value,
                SearchText = userSettings.FirstOrDefault(x => x.Name == "SearchText")?.Value
            };

            var searchStatusSetting = userSettings.FirstOrDefault(x => x.Name == "SearchStatus");
            if (!string.IsNullOrEmpty(searchStatusSetting?.Value))
            {
                filter.Status = localizationService.ConvertStatus(searchStatusSetting.Value);
            }

            var pageIndexSetting = userSettings.FirstOrDefault(x => x.Name == "PageIndex");

            var pagination = PaginationHelper.GetPaginationInfo(section.Settings.PageSize ?? 50, pageIndexSetting != null ? Convert.ToInt64(pageIndexSetting.Value) : 0, await localizationService.GetResourceCountAsync(filter));
            if (pagination.PageCount > 1)
            {
                filter.IsPagination = true;
                filter.PageIndex = pagination.PageIndex;
                filter.PageSize = pagination.PageSize;
            }

            var orderByModeSetting = userSettings.FirstOrDefault(x => x.Name == "OrderByMode");
            var orderByDataFieldSetting = userSettings.FirstOrDefault(x => x.Name == "OrderByDataField");

            if (orderByDataFieldSetting?.Value != null && orderByDataFieldSetting?.Value != "Position")
            {
                filter.OrderDataFields = [new() { Name = orderByDataFieldSetting.Value, OrderMode = orderByModeSetting?.Value }];
            }

            var resources = await resourceStore.GetResourcesAsync(filter);

            var moduleData = new ListData()
            {
                Items = resources.Select(ConvertToListEditItem).ToList(),
                DataFields = dataFields,
                ListWidgets = GetListWidgets(cancellationToken),
                Actions = (await GetActionsAsync(cancellationToken)).Select(x => x.ToContextActionInfo()),
                Search = new SearchInfo { Filters = await GetSearchFiltersAsync(dataFields, userSettings, cancellationToken) },
                OrderBy = new OrderByInfo
                {
                    Mode = userSettings.FirstOrDefault(x => x.Name == "OrderByMode")?.Value ?? "DESC",
                    DataField = userSettings.FirstOrDefault(x => x.Name == "OrderByDataField")?.Value ?? "Position"
                },
                Pagination = pagination
            };

            ProcessListWidgetsValues(moduleData.Items, userSettings);

            return moduleData;
        }

        private ListItem ConvertToListEditItem(Resource resource)
        {
            return new ListItem()
            {
                Id = resource.Id,
                Label = resource.Name,
                IsWrite = !resource.IsSystem,
                DataFields = [
                    new ListItem.DataField{ Name = nameof(resource.Name), Value = resource.Name},
                    new ListItem.DataField{ Name = nameof(resource.Status), Value = Convert.ToString(resource.Status)},
                    new ListItem.DataField{ Name = nameof(resource.IsSystem), Value = resource.IsSystem},
                    new ListItem.DataField{ Name = nameof(resource.ResourceContext), Value = resource.ResourceContext},
                    new ListItem.DataField{ Name = nameof(resource.ResourceGroup), Value = resource.ResourceGroup},
                    new ListItem.DataField{ Name = nameof(resource.ResourceKey), Value = resource.ResourceKey},
                    new ListItem.DataField{ Name = nameof(resource.CultureItems), Value = resource.CultureItems},
                    new ListItem.DataField{ Name = nameof(resource.Notes), Value = resource.Notes}
                ],
                ListWidgetValues = []
            };
        }