Confinity Documentation
  • Latest Version
  • Latest Version
  • Getting Started

    • Introduction
    • Core Concepts
    • Create an Application
    • Glossary
  • Essentials

    • Authentication & SSO
    • Breaking Changes
    • Roslyn Source Analyzers
    • Changelog
    • ConfinityContent
    • ConfinitySelectable
    • Confinity Schedules
    • Data Seeding
    • Development guidelines [WIP]
    • Entity App
    • Entity Form
    • Entity Permissions
    • Frontend Configuration
    • Images
    • Known Issues
    • Localization
    • Migrations
    • Modules [WIP]
    • On-Site Editing
    • Settings
    • Cascade Delete
    • Replication
    • Infrastructure
  • Modules

    • Analytics Module
    • Assets Module
    • Blog Module
    • Cookie Consent Module
    • Forms Module
    • Friendly Captcha (Forms Module )
    • GeoIP Module
    • Htmx
    • Mail Module
    • Mailing Module
    • MediaPlayer Module
    • GoogleMyBusiness Module
    • OpenTelemetry Module
    • Pages Module [WIP]
    • Pattern Library Module
    • SIX Saferpay (worldline) Module
    • Products Module
    • Search Module
    • Wizard Module
  • Guides

    • Create a Custom Entity App Form Element
    • Date and Time
    • Entity Change Listener
    • File Upload / Temp File
    • HTTP security headers
    • conventions [WIP]
    • How to use Confinity with nginx
    • Notifications
    • Nullability
    • Rename Entity
    • Schedules
    • Useful snippets
    • Content Localization
  • Design Guidelines

    • Introduction
    • Page Components
    • Forms Module

Forms Module

Enables authors to define forms that can be added to pages. The form fields are configurable with validations. Form submissions can be stored in the database and/or processed by actions.

Configuration

appsettings Key: ConfinityForms

Configuration KeyDescriptionDefault
UrlParameterEncryptionKeySecret used to encrypt the url parameters passed to form inputs.null

Actions

Actions can be added to the form configuration. They will be executed in the sequence they are configured and can change the submitted data. Developers can implement custom actions. Confinity has an action that sends a mail for every form submission.

Custom Actions

Developers can implement custom actions which can be added to forms by authors. To implement a custom action, follow these steps:

  1. Define a model with the settings for your action by inheriting from FormSubmitEventListenerModel. The author needs to provide those settings when adding the action.

public sealed class HappinessApiModel : FormSubmitEventListenerModel
{
    public string? RestEndpoint { get; set; }
}

  1. Implement your listener by inheriting from IFormSubmitEventListener<T> where T is your model.

public class HappinessApiFormSubmitEventListener : IFormSubmitEventListener<HappinessApiModel>
{
    private readonly ILogger<HappinessApiFormSubmitEventListener> _logger;
    private readonly HttpClient _httpClient;

    public HappinessApiFormSubmitEventListener(
        ILogger<HappinessApiFormSubmitEventListener> logger,
        HttpClient httpClient)
    {
        _logger = logger;
        _httpClient = httpClient;
    }

    public async Task<SubmitListenerResult> BeforeSubmitAsync(FormSubmit<HappinessApiModel> submit)
    {
        try
        {
            var request = submit.Request;
            int happinessLevel = (int)request.FormData["happiness-level"];
            var data = new { Ip = request.ClientIpAddress?.ToString() ?? "unknown", HappinessLevel = happinessLevel };
            string json = JsonSerializer.Serialize(data);
            var httpContent = new StringContent(json, System.Text.Encoding.UTF8, MediaTypeNames.Application.Json);

            await _httpClient.PostAsync(submit.Config.RestEndpoint, httpContent);
        }
        catch (Exception e)
        {
            _logger.LogError(e, "Error while transferring happiness data");
            return new SubmitListenerResult(false, "Error while transferring happiness data.");
        }

        return new SubmitListenerResult(true);
    }
}

  1. Register your model and define an editor (if needed). Register your listener with with the services collection.

public class CustomActionExampleModule : IModuleConfiguration
{
    public string ModuleKey => "my-module";

    public void AddModule(IModuleContext module)
    {
        module.Configure.RegisterConfinityContent<HappinessApiModel>("Happiness API")
            .Icon(Icon.CakeCandles)
            .Editor(form => form.AddTab(tab =>
            {
                tab.AddInput(p => p.RestEndpoint);
            }));

        module.Services
            .AddTransient<IFormSubmitEventListener<HappinessApiModel>, HappinessApiFormSubmitEventListener>();
    }

    public void UseModule(IApplicationBuilder app)
    {
    }
}

Form Field Types

Confinity offers a bunch of different form field types like Input, Checkbox, or Conditional Group out of the box.

Custom Form Field Types

Developers can implement custom form field types which can be added to forms by authors. To do so, follow these steps:

  1. Chose the right base type for you Model:
    • InputFieldModelBase or FormFieldModel for inputting data
    • ViewComponentModel with attribute [FormItem] for generic form elements that should be selectable directly on the form
    • ConfinityContent when your form element is a child element for a specific parent (e.g. an option for a select single)
  2. (optional) Implement IFormItemCollectionModel if your element groups a bunch of form fields together
  3. (optional) Implement IFormItemConditionModel if your element is only shown based on a condition
  4. (optional) Implement IListOfOptionModels if your element has multiple options which can be selected (this is needed to properly format the selected options in E-Mails)

public sealed class DateModel : InputFieldModelBase
{
    public DateOnly Min { get; set; }
    public DateOnly Max { get; set; }
}

  1. Implement a view component for your model.

public class DateInputViewComponent : ViewComponent
{
    public IViewComponentResult Invoke(DateModel model)
    {
        return View(model);
    }
}


@using Confinity.Forms.Tooltip
@using Confinity.Pages
@model DocsDemos.Modules.Forms.DateModel

@{
    string fieldId = Model.Id.ToString();

    static string ToJsString(DateOnly date)
    {
        return date.ToDateTime(TimeOnly.MinValue).ToString("ddd, dd MMM yyyy HH:mm:ss") + " GMT";
    }
}

<div class="form-group">
    @if (!string.IsNullOrWhiteSpace(Model.Label))
    {
        <label for="cfy-f-@fieldId">
            @Model.Label
            @if (Model.IsRequired())
            {
                <span class="required-marker">*</span>
            }
            @if (!string.IsNullOrWhiteSpace(Model.Tooltip))
            {
                @await Component.InvokeConfinityViewComponentAsync(new TooltipModel(Model.Tooltip))
            }
        </label>
    }
    <div class="input-group">
        <input type="date" id="cfy-f-@fieldId" name="@Model.FieldName" class="form-control" aria-describedby="@(!string.IsNullOrWhiteSpace(Model.Description) ? $"help{fieldId}" : "")"
               min="@ToJsString(Model.Min)" max="@ToJsString(Model.Max)">
    </div>
    <div class="invalid-feedback"></div>
    @if (!string.IsNullOrWhiteSpace(Model.Description))
    {
        <small id="help@(fieldId)" class="form-text text-muted">@Model.Description</small>
    }
</div>

  1. Register your model, view component and and define an editor (if needed).

public void AddModule(IModuleContext module)
{
    module.Configure.RegisterConfinityContent<DateModel>("Date")
        .ViewComponent<DateInputViewComponent>()
        .Icon(Icon.Calendar)
        .Editor(form => form.AddTab(tab =>
        {
            tab.AddDateOnly(p => p.Min!);
            tab.AddDateOnly(p => p.Max);
        }));
}

Custom validations

Developers can implement custom validations for their custom form fields which can be added to forms by authors. To do so, follow these steps:

  1. Implement the model, it must inherit ValidationModel

public sealed class EqualsFieldValidationModel : ValidationModel
{
    public string? FieldName { get; set; }
}

  1. The validator has to implement IFormFieldValidator

public class EqualsFieldValidator : IFormFieldValidator
{
    public bool CanValidate(ValidationModel model)
    {
        return model is EqualsFieldValidationModel;
    }

    public FieldValidatorResult IsValid(ValidationModel model, string? value,
        IReadOnlyDictionary<string, string> formValuesPerField)
    {
        if (string.IsNullOrEmpty(value)
            || model is not EqualsFieldValidationModel equalsValidationModel
            || string.IsNullOrEmpty(equalsValidationModel.FieldName)
            || !formValuesPerField.TryGetValue(equalsValidationModel.FieldName, out string? otherValue))
            return new FieldValidatorResult { IsValid = true };

        var result = new FieldValidatorResult { IsValid = value.Equals(otherValue) };
        if (!result.IsValid)
        {
            result.ErrorMessage = "Values do not match";
        }

        return result;
    }
}

  1. Register your model, register your validator with the DIC and add a condition for your validator in the editor configuration of the custom form field.
public void AddModule(IModuleContext module)
{
    //Register the validation model
    module.Configure.RegisterConfinityContent<EqualsFieldValidationModel>("Equals field")
        .Icon(Icon.Equals)
        .Editor(form => form.AddTab(tab =>
        {
            tab.AddInput(p => p.FieldName);
        }));

    //Register the validator
    module.Services.AddTransient<IFormFieldValidator, EqualsFieldValidator>();

    module.Configure.RegisterConfinityContent<DateModel>("Date")
        .ViewComponent<DateInputViewComponent>()
        .Icon(Icon.Calendar)
        .Editor(form => form.AddTab(tab =>
        {
            tab.AddDateOnly(p => p.Min!);
            tab.AddDateOnly(p => p.Max);
            //Add the validation
            tab.AddConfinityContent(p => p.Validations)
                .Where(t => t == typeof(EqualsFieldValidationModel));
        }));
}

File upload

The file upload brings it's own UI. Limited styling is possible by overwriting css. Here is a list of selectors to overwrite certain styles.

  • Drop area: .confinity-form-file-upload-component [data-cfy-drop-area]
  • Upload label: .confinity-form-file-upload-component .upload-label
  • Info label: .confinity-form-file-upload-component .info-label
  • File extension for upload: .confinity-form-file-upload-component [data-cfy-file-extension]
  • action remove file: .confinity-form-file-upload-component [data-cfy-file-remove]
  • File is uploading: .confinity-form-file-upload-component [data-cfy-file-uploading]
  • File size: .confinity-form-file-upload-component [data-cfy-file-size]
  • File name: .confinity-form-file-upload-component [data-cfy-file-name]

Error messages must be translated on the UI. The attribute data-cfy-has-error contains one of the following error keys:

  • wrong sessionId: the provided session id is no longer valid / does not exist. see TempFile for details
  • missing fileId: the file id was not provided. see TempFile for details
  • missing content: the file does not have content (size is zero).
  • too big: the file ist bigger than what is configured as allowed size.
  • extension not allowed: the file extension is not on the allowed file list.
  • content not allowed: the content is not allowed based on the configuration.
  • chunk out of order: the chunks did arrive out of order. This is ensured by the client so someone tampered with it.
  • space capacity exceeded: the configured space capacity is exceeded. see TempFile for details
  • session capacity exceeded: the configured session capacity is exceeded. see TempFile for details
  • file capacity exceeded: the configured file capacity is exceeded.
  • unable to process: there was an internal processing error or the virus scanner detected something.
  • unknown error: multiple reasons -> check console for more details.

Customize buttons

Buttons can be customized by overwriting the file Shared/Components/ConfinityFormButton/Default.cshtml.

If all the buttons should look the same, you can simply return a button:

@model Confinity.Forms.FormButtonModel
<button type="@Model.ButtonType()" @Model.DataAttributes() class="btn btn-primary">@Model.Label</button>

Warning

make sure to set the type and data attributed used by Confinity to link the buttons together.

To customize the buttons based on it's usage, you can switch over the usage property:

@using Confinity.Forms
@model Confinity.Forms.FormButtonModel

@switch (Model.Usage)
{
    case FormButtonUsage.WizardBack:
        <button type="@Model.ButtonType()" @Model.DataAttributes() class="btn btn-link d-none">@Model.Label</button>
        break;
    case FormButtonUsage.WizardForward:
        <button type="@Model.ButtonType()" @Model.DataAttributes() class="btn btn-primary ms-auto">@Model.Label</button>
        break;
    case FormButtonUsage.Submit:
    default:
        <button type="@Model.ButtonType()" @Model.DataAttributes() class="btn btn-primary">@Model.Label</button>
        break;
}

Customisation for javascript frameworks

When using javascript frameworks it can get hard to serve the dom for the Confinity forms module to work. Therefore we pass javascript events around so you can react on them accordingly. You have to add the attribute data-cfy-forms-event on the elements where you want to receive the events. the recommended way is to define them as close to the native html input elements as possible. The events bubble up the dom so that you can catch the events at any point.

Validation Events:

The attribute data-cfy-forms-event is expected to be as close to the input for the given validation as possible.

event type: cfy-forms-validation
payload: {isValid: boolean, isTouched: boolean, message?: string (the error message)}

In order for the validation events to work, you have to ensure, that isTouched is set. To do this you have to emit an event on the input yourself.

event type: cfy-forms-input event detail: {bubbles: true, detail: {type: 'change'}} the expected values for detail.type is 'change' (for changes) or 'blur' (when the user leaves the input)

example: myInputElement.dispatchEvent(new CustomEvent('cfy-forms-input', {bubbles: true, detail: {type: 'change'}}))

Wizard Events

Add the attribute data-cfy-forms-event to all the elements which should receive this event.

event type: cfy-forms-wizard
payload: {active: string (the id of the active step)}

Submit Event

Add the attribute data-cfy-forms-event to all the elements which should receive this event.

event type: cfy-forms-submit
payload: none

Prev
Cookie Consent Module
Next
Friendly Captcha (Forms Module )