Try our conversational search powered by Generative AI!

Le Giang
Feb 8, 2018
  5135
(6 votes)

Custom FieldSet emelent block for EPiServer.Form

Hi there.

In this post, I will show you how to create a custom field set element block for FORM addon. This block aims to group some of form elements and add some pretty appearances for the form. And this is what we will achieve.

Image custom_form_1.pngImage custom_form_submissions_1.png

  1. Create a new form element block called "FieldsetBlock" which cotains a element area allow user to drag and drop other form elements into. I also introduce a new interface called "IFieldSet", we will use this for some additional logic later. 
    [ContentType(GUID = "2A4F56F9-2D3A-4C0A-AD78-1381D6A57808", GroupName = "Layout", DisplayName = "Field set")]
        public class FieldsetBlock: ElementBlockBase, IFieldSet, IExcludeInSubmission
        {
            [AllowedTypes(new[] { typeof(DataElementBlockBase) })]
            [Display(GroupName = "Elements")]
            [UIHint("FormsContentAreaEditor")]
            public virtual ContentArea ElementsArea { get; set; }
    
            public virtual string Title { get; set; }
        }
    
        public interface IFieldSet
        {
            ContentArea ElementsArea { get; set; }
        }
  2. Create a view for the FieldsetBlock
    @model EpiserverSite2.Models.FormElement.FieldsetBlock
    
    <div class="Form__Element fieldsetBlock">
        <fieldset>
            <legend class="fieldset_title" @Html.EditAttributes(x => x.Title)>@Model.Title</legend>
            @Html.PropertyFor(x => x.ElementsArea)
        </fieldset>
    </div>  
  3. By the default, form elements have no on-page edit view, but the field set element need one. Add this code to restore the on-page edit view for the field set element:
    [UIDescriptorRegistration]
        public class FieldsetBlockUIDescriptor: UIDescriptor<FieldsetBlock>
        {
            public FieldsetBlockUIDescriptor()
            {
                DisabledViews = new List<string>();
            }
        }

  4. We need to override the way the FORM retrieves form elements to include "sub elements" contained in the field set blocks. We have to because by the default, FORM only retrieves "first-level" elements.
    [ServiceConfiguration(ServiceType = typeof(DataSubmissionService), Lifecycle = ServiceInstanceScope.Singleton)]
        public class CustomDataSubmissionService : DataSubmissionService
        {
            protected override IFormStep GetCurrentStep(NameValueCollection rawSubmittedData, FormContainerBlock formContainer)
            {            
                var step = base.GetCurrentStep(rawSubmittedData, formContainer);
                var lang = (formContainer as ILocale).Language.Name;
                var elements = new List<IFormElement>(step.Elements);
    
                foreach (var el in step.Elements)
                {
                    if (el.SourceContent is IFieldSet)
                    {
                        var allElements = (el.SourceContent as IFieldSet).ElementsArea.Items;
                        foreach (var item in allElements)
                        {
                            var formElementBlock = item.ContentLink.GetContent(lang) as ElementBlockBase;
                            
                            if (formElementBlock != null && !elements.Contains(formElementBlock.FormElement))
                            {
                                elements.Add(formElementBlock.FormElement);
                            }
                        }
                    }
                }
                step.Elements = elements;
                return step;
            }
        }
     [ServiceConfiguration(ServiceType = typeof(FormBusinessService), Lifecycle = ServiceInstanceScope.Singleton)]
        public class CustomFormBusinessService: FormBusinessService
        {
            public override IEnumerable<ElementBlockBase> GetDisplayableFormElementBlocks(FormContainerBlock formContainerBlock)
            {
                var allElements = GetFormElementBlocks(formContainerBlock, false);
                var elementList = new List<ElementBlockBase>(allElements);
                var lang = (formContainerBlock as ILocale).Language.Name;
    
                foreach (var el in allElements)
                {
                    if (el is IFieldSet)
                    {
                        var allSubElements = (el as IFieldSet).ElementsArea?.Items;
                        if (allSubElements != null && allSubElements.Count > 0)
                        {
                            foreach (var item in allSubElements)
                            {
                                var formElementBlock = item.ContentLink.GetContent(lang) as ElementBlockBase;
    
                                if (formElementBlock != null && !elementList.Contains(formElementBlock))
                                {
                                    elementList.Add(formElementBlock);
                                }
                            }
                        }
                        
                    }
                }
    
                return elementList;
            }
    
            /// <summary>
            /// Return all FormElementBlock of this FomrContainer, regardless the publish, visibility status or personalization content condition.
            /// </summary>
            /// <returns></returns>
            public override IEnumerable<ElementBlockBase> GetAllInnerFormElementBlocks(FormContainerBlock formContainerBlock)
            {
                var allElements = GetFormElementBlocks(formContainerBlock, true);
                var elementList = new List<ElementBlockBase>(allElements);
                var lang = (formContainerBlock as ILocale).Language.Name;
    
                foreach (var el in allElements)
                {
                    if (el is IFieldSet)
                    {
                        var allSubElements = (el as IFieldSet).ElementsArea?.Items;
                        if (allSubElements != null && allSubElements.Count > 0)
                        {
                            foreach (var item in allSubElements)
                            {
                                var formElementBlock = item.ContentLink.GetContent(lang) as ElementBlockBase;
    
                                if (formElementBlock != null && !elementList.Contains(formElementBlock))
                                {
                                    elementList.Add(formElementBlock);
                                }
                            }
                        }
                    }
                }
    
                return elementList;
            }
        }


    Make sure these custom services take place the original ones.

    context.Services.AddTransient<DataSubmissionService, DataSubmissionService>()
                        .AddTransient<DataSubmissionService, CustomDataSubmissionService>();
    
                    context.Services.AddTransient<FormBusinessService, FormBusinessService>()
                        .AddTransient<FormBusinessService, CustomFormBusinessService>();
  5. Since we have changed the way FORM retrieves form elements,  so we have to remove redundant elements and empty field sets before rendering the form. To do this, we need a custom form container block controller.
    [TemplateDescriptor(AvailableWithoutTag = true, Default = true, ModelType = typeof(FormContainerBlock), TemplateTypeCategory = TemplateTypeCategories.MvcPartialController)]
        public class CustomFormContainerBlockController: FormContainerBlockController, IRenderTemplate<FormContainerBlock>
        {
            public override ActionResult Index(FormContainerBlock currentBlock) // Note: convention, the parameter must be "currentBlock", not formBlock or another
            {
                var result = base.Index(currentBlock) as PartialViewResult;
                var formModel = result.Model as FormContainerBlock;
    
                if (formModel != null)
                {
                    var steps = new List<IFormStep>(formModel.Form.Steps);
                    var allSubItems = new List<ContentReference>();
    
                    // find all sub items (child of FieldSetElement)
                    foreach (var step in steps)
                    {
                        foreach(var el in step.Elements)
                        {
                            if (el.SourceContent is IFieldSet)
                            {
                                var fieldSetElement = el.SourceContent as IFieldSet;
                                if (fieldSetElement.ElementsArea != null && fieldSetElement.ElementsArea.Items.Count > 0)
                                {
                                    allSubItems.AddRange(fieldSetElement.ElementsArea.Items.Select(i => i.ContentLink));
                                }                            
                            }
                        }
                    }
    
                    // Remove redundant elements
                    foreach (var step in steps)
                    {
                        var elements = new List<IFormElement>(step.Elements);
                        foreach (var el in step.Elements)
                        {
                            if (el.SourceContent is IFieldSet)
                            {
                                var fieldSetElement = el.SourceContent as IFieldSet;
                                if (fieldSetElement.ElementsArea == null || fieldSetElement.ElementsArea.Items.Count == 0)
                                {
                                    elements.Remove(el);
                                }
                            }
    
                            if (allSubItems.Contains(el.SourceContent.ContentLink))
                            {
                                elements.Remove(el);
                            }
                        }
                        step.Elements = elements;
                    }
    
                    formModel.Form.Steps = steps;
                }
                return result;
            }
  6. Finally, add some style for the field set element.
    .fieldsetBlock fieldset {
        border: 1px groove red;
        padding: 1em;
    }
    
    .fieldsetBlock fieldset legend {
        width: auto;
        margin: 0 5px;
        padding: 0 5px;
    }
Feb 08, 2018

Comments

Quan Tran
Quan Tran Feb 8, 2018 09:38 AM

awesome :). Our clients will love this.

Dao Le
Dao Le Feb 8, 2018 09:56 AM

This is amazing !. Greate work man. 

Trung Pham
Trung Pham Feb 8, 2018 01:07 PM

Great post. Thanks for sharing.

Please login to comment.
Latest blogs
Optimizely Opal... what it does actually do?

At Opticon 2023, Optimizely announced its first AI product Opal. AI is definitely the new tech buzzword in 2024 and with promises that AI will be...

Jon Jones | Feb 25, 2024 | Syndicated blog

How to add more Content Area Context Menu Item in Optimizely CMS 12

Hey folks, today I will share something related to Context Menu customization in the Content Area of Optimizely CMS. As you know, the content area ...

Binh Nguyen | Feb 25, 2024

Developer meetups in Stockholm & Helsinki

It's time for developer meetups! Next month we will be in Stockholm and Helsinki. Join us for getting the latest updates from Optimizely, be inspir...

Magnus Kjellander | Feb 23, 2024

Roll Your Own Security Headers

Proper security headers are a must for your Optimizely driven website. There are a variety of tools out there that will help with this, but when...

Ethan Schofer | Feb 21, 2024

Migrate Catalog content properties

A colleague asked me yesterday – how do we migrate properties of catalog content. There is, unfortunately, no official way to do it. There are...

Quan Mai | Feb 20, 2024 | Syndicated blog

Adjust log levels in Optimizely DXP

You may adjust the log levels for your site in Optimizely DXP yourself, but only for the Integration environment. Follow this step-by-step guide.

Tomas Hensrud Gulla | Feb 20, 2024 | Syndicated blog