computer

Custom rendering of Episerver Forms

Den här artikeln är inte översatt till svenska och visas därför på engelska istället.


This is the first part of a tutorial series on Episerver Forms. We will go through custom rendering, event handling, processing submitted data, and implementing custom actors. To adapt how forms look, behave, and integrate with other systems.

Uppskattad lästid : 5 minuter

Gå till avsnitt

Key takeaways

  • Always create a new Form Container
  • Render form types without copy/paste
  • Hide old elements

Wrapping the container

I started by wrapping the default form container. I always do this even if there aren’t any properties to add to the container. Why? Simply for future reference. If we need to add properties to the container later on, we will need to have an overriding wrapping container so we can apply changes for existing forms. This will improve project longevity and maintainability so just wrap it.

How to properly extend Episerver Forms to avoid difficult upgrades

I have read so many posts regarding rendering forms where you should copy the code from the nuget zip views files and add the code to your view. I don’t think this is a good point of view to start with because we want to keep the original code where it belongs for as long as possible, so we can support future updates from Episerver regarding the forms and not build ourselves into a corner. Again, build solutions with longer longevity.

Custom form container type

I’m going to show you how to wrap the container and still keep the code in the original files.

[ServiceConfiguration(typeof(IFormContainerBlock))]
[ContentType(DisplayName = "Form container", GUID = "693e8587-bb32-411a-9e27-0ae57cf91268", Description = "Add a new form")]
[ImageUrl("/Static/gfx/blocks/FormContainerBlock.png")]
public class FormContainerWrapperBlock : EPiServer.Forms.Implementation.Elements.FormContainerBlock
{
}
[TemplateDescriptor(ModelType = typeof(FormContainerWrapperBlock))]
public class FormContainerWrapperBlockController :  FormContainerBlockController
{
    public override ActionResult Index(FormContainerBlock currentBlock)
    {
        var partialViewResult = base.Index(currentBlock);
            
        if (currentBlock is FormContainerWrapperBlock formContainerWrapper)
        {
           // add you own code to the view model
            FormContainerWrapperBlockViewModel viewModel =
                new FormContainerWrapperBlockViewModel(formContainerWrapper);

            return PartialView("FormContainerWrapper", viewModel);
        }

        return partialViewResult;
    }

}

Create a view for rendering the form container wrapper

Now in your view, you add the wrapping element you need. And you render the form as a partial. This lets you add code and still render the form. 

@model FormContainerWrapperBlockViewModel

<div class="design2020">
    <div class="form-wrapper @Model.CurrentBlock.FormTheme @Model.CurrentBlock.EpiFormColorTheme">

        @Html.Partial("FormContainerBlock", Model.CurrentBlock)
    </div>
</div>


Creating custom form elements

We can read posts on world on how to render the form in two columns, but in this example the elements are going to contain a property to determine if the element should be rendered in half width. But the form will still have full width elements, this will look something like this: 

Note: you can also use display options if you need several width sizes as in this post

For this, I also wrapped the elements just as I did with the container. Making a new element, adding my properties, and rendered the view once again referencing to the original code for the elements. I add the views in the folder for the elements Shared/ElementBlocks. If you'd like to use another folder, you can change the configurations for this in the file modules/_protected/EPiServer.Forms/Forms.config, under formElementViewsFolder.

[ContentType(DisplayName = "Textbox", GUID = "8D3396C5-047C-4765-8E27-83A76B1C31A1", Description = "", GroupName = GroupNames.Form_Basic_Element, Order = 1)]
[ImageUrl("/Static/gfx/blocks/TextboxElementBlock.png")]
public class CustomTextBoxElementBlock : TextboxElementBlock
{
    [Display(Name = "Half Width")]
    public virtual bool HalfWidth { get; set; }

    public string HalfWidthCss()
    {
        if (HalfWidth)
        {
            return "half-width";
        }

        return "full-width";
    }

    public override void SetDefaultValues(ContentType contentType)
    {
        base.SetDefaultValues(contentType);
        this.Label = "Text";
    }
}

Note: You can find the content type images in the folder modules/_protected/EPiServer.Forms.UI/EPiServer.Forms.UI.zip/[version number]/ClientResources/epi-forms/themes/sleek/images/contenttypes

@model DSSmith.Web.Models.Forms.CustomTextBoxElementBlock


<div class="@Model.HalfWidthCss()">
    @Html.Partial("TextboxElementBlock", Model)
</div>

Hide the elements we no longer need

So now we have duplicated elements and containers... So we need to hide the ones we don’t want to use (the original ones). I will show you one way to hide them. Original post on how to hide Episerver Forms elements if you need a more detailed version of hiding elements (or container).

[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
[ModuleDependency(typeof(DependencyResolverInitialization))]
public class FormEventInitializer : IInitializableModule
{
    private static readonly ILogger _log = LogManager.GetLogger(typeof(FormEventInitializer));
    public static readonly Guid OldTextElement = new Guid("39547DD4-6045-4EB1-968F-80921BD91E36");
        
    public void Initialize(InitializationEngine context)
    {
        var contentTypeRepository = context.Locate.ContentTypeRepository();
        HideFromEditors(contentTypeRepository, OldTextElement);
    }

       
    public void Uninitialize(InitializationEngine context)
    {

    }

    private static void HideFromEditors(IContentTypeRepository repo, Guid contentGuid)
    {
        if (repo == null)
        {
            throw new ArgumentNullException(nameof(repo));
        }

        // get content with Guid (returns null if not found)
        ContentType content = repo.Load(contentGuid);

        if (content != null)
        {
            if (content.IsAvailable)
            {
                try
                {
                    // make writable clone, hide and save it
                    ContentType writable = content.CreateWritableClone() as ContentType;
                    writable.IsAvailable = false;
                    repo.Save(writable);

                    _log.Information($"Content type with guid '{contentGuid}' (name: {writable.Name}) hidden from editors.");
                }
                catch (Exception ex)
                {
                    _log.Error($"Failed to hide content type with guid '{contentGuid}' (name: {content.Name}).", ex);
                }
            }
            else
            {
                _log.Information($"Content type with guid '{contentGuid}' (name: {content.Name}) is already hidden from editors.");
            }
        }
        else
        {
            _log.Warning($"Cannot hide content type with guid '{contentGuid}' because it was not found from content type repository.");
        }
    }
}

Hope you find this post helpful and that you will check out the upcoming post regarding Episerver Forms.