Hide menu Last updated: Jan 23 2017
Area: Episerver Add-ons Applies to versions: Forms 4.3.0 and higher

Creating a form element with a custom validator

Note: Episerver Forms is only supported by MVC-based websites and HTML5-compliant browsers.

This topic shows how to create a form element with a custom validator.

Form elements inherit from an ElementBlockBase class, which is the only type allowed in the form element Content Area. Inherit from ValidatableElementBlockBase to support custom validator.

By default, you can choose from available form element validators (that inherit ElementValidatorBase class). If you only need some of these validators, or you have your own validator, you can include or exclude them selectively by using the AvailableValidatorTypesAttribute attribute. The following element includes only the RequiredValidator along with its own validator. Apply MyCustomValidator to this element; do not make it available for others.

[ContentType(GUID = "{A4EE6053-3932-4300-8B3B-7BABF9AEAB67}", GroupName = ConstantsFormsUI.FormElementGroup, Order = 2230)]
[AvailableValidatorTypesAttribute(Include = new Type[] { typeof(RequiredValidator) })]
public class MyCustomElementBlock : ValidatableElementBlockBase {
	public virtual string CustomProperty{get; set;}
	
	/// 
	/// Always use a custom Validator to validate this element (along with builtin Validator RequiredValidator)
	/// The custom Validator is not visible to Editor (in EditView), but it still works to validate element value
	/// 
	[Display(GroupName = SystemTabNames.Content, Order = -5000)]
	public override string Validators
	{
		get
		{
			var customValidator = typeof(MyCustomValidator).FullName;
			var validators = this.GetPropertyValue(content => content.Validators);
			if (string.IsNullOrEmpty(validators))
			{
				return customValidator;
			}
			else
			{
				return string.Concat(validators, EPiServer.Forms.Constants.RecordSeparator, customValidator);
			}
		}
		set
		{
			this.SetPropertyValue(content => content.Validators, value);
		}
	}
}

MyCustomValidator:

public class MyCustomValidator: ElementValidatorBase
    {        
        private Injected<LocalizationService> _localizationService;
        protected LocalizationService LocalizationService { get { return _localizationService.Service; } }

        public override bool? Validate(IElementValidatable targetElement)
        {           
            return true;
        }

        /// 
        /// return false if we don't want to show this specific-validators to Editor. This validators always work programatically for AddressElement.
        public override bool AvailableInEditView
        {
            get
            {
                return false;
            }
        }

        /// 
        public override IValidationModel BuildValidationModel(IElementValidatable targetElement)
        {
            var model = base.BuildValidationModel(targetElement);
            if (model != null)
            {
                model.Message = this.LocalizationService.GetString("/episerver/forms/validators/elementselfvalidator/unexpectedvalueisnotaccepted");
            }

            return model;
        }
    }

Only the RequiredValidator appears in edit view; MyCustomValidator is included transparently.

To validate form element at the client side, you must specify a validate handler. Serverside's Fullname of the Validator instance is used as object key (case-sensitve) to lookup the Clientside validate function. The EPiForm client code calls this function to validate the form element’s value before submitting the form.

// extend the EpiForm JavaScript API in ViewMode
$.extend(true, epi.EPiServer.Forms, {
    /// extend the Validator to validate Visitor's value in Clientside.        
    Validators: {
         "EPiServer.Forms.Samples.Implementation.Validation.MyCustomValidator": function(fieldName, fieldValue, validatorMetaData) {
	 // this function is used to validate visitor's data value in ViewMode
            
          if (!condition) {
              return { isValid: false, message: validatorMetaData.model.message };
          }
          return { isValid: true };
      },
   },
})

The element's view

The default path of element views are found at modules\_protected\EPiServer.Forms\Views\ElementBlocks, but you can overide this by adding custom views in the Views\Shared\ElementBlocks folder. If you want a custom view location, you can override the GetDefaultViewLocation method of CustomViewLocationBase class: 

[ServiceConfiguration(typeof(ICustomViewLocation))]
public class FormsSamplesViewLocation : CustomViewLocationBase
{
        public virtual string GetDefaultViewLocation()
        {            
            var defaultPathToBlockViews = "~" + ModuleHelper.ToResource(this.GetType(), "Views/ElementBlocks");

            return defaultPathToBlockViews;
        }
}

Form element's resources

EPiServerForm 4.3 improves the way resources (scripts, css) are loaded. If a form element needs its own resource, it must implement the IElementRequireClientResources interface. The form element's resource is loaded after form's resources and external's resources. For example, the datetime element requires jquery datetime to work but it is not loaded until the element is dragged into the form. For example: 

public IEnumerable<Tuple<string, string>> GetExtraResources()
{
     if (!string.IsNullOrWhiteSpace(Settings.Instance.GoogleMapsApiV3Url))
     {
        var publicVirtualPath = ModuleHelper.GetPublicVirtualPath(Constants.ModuleName);
        var currentPageLanguage = FormsExtensions.GetCurrentPageLanguage();
        return new List<Tuple<string, string>>() {
            new Tuple<string, string>("script", string.Format(Settings.Instance.GoogleMapsApiV3Url, currentPageLanguage)),
            new Tuple<string, string>("script", publicVirtualPath + "/ClientResources/ViewMode/AddressesElementBlock.js")
        };
     }
     return new List<Tuple<string, string>>();
}

Customize element's icon and image

To display element's image in element picker screen, add the image with the same name as element's name inside modules\_protected\EPiServer.Forms.UI\{version}\ClientResources\epi-forms\themes\sleek\images\contenttypes. If you want a different location for form element's big icons, you can override the default one (dojo part): 

var contentTypeService = dependency.resolve('epi.cms.ContentTypeService'),
    self = this;
aspect.around(contentTypeService, '_getImagePathByTypeIdentifier', function (originalFunction) {
    return function (/*String*/typeIdentifier, /*Array*/registeredTypes, /*String*/clientResourcePath) {
        var types = self._settings.registeredElementContentTypes;
        if (types instanceof Array && types.indexOf(typeIdentifier) >= 0) {
            // show the .png as big icon for creating FormElement in the ContentArea
            return self._settings.clientResourcePath + '/ClientResources/epi-formssamples/themes/sleek/images/contenttypes/' + typeIdentifier.split('.').pop() + '.png';
        }

        return originalFunction.apply(contentTypeService, arguments);
    };
});

For the small icon in Form Element widget, add a class as follows:

.epi-forms-icon.epi-forms-icon--small.epi-forms-mycustomelementblock__icon
{
background:url(images/mycustomelemeticon.png) 0px 0px no-repeat
}

For examples about custom form elements, see https://github.com/episerver/EPiServer.Forms.Samples.

Comments

I've created a basic postcode validator using this and I'm getting "The PostcodeFormElement cannot be displayed". Anyone else had this? 

Le Giang
Post by Le Giang deleted, 2/7/2017 4:30:08 AM

We checked and it still works fine.

The root cause here is "The view of the element could not be found."

The default path of element view is found in modules\_protected\EPiServer.Forms\Views\ElementBlocks, but you can override this by place the view in the Views\Shared\ElementBlocks folder. See this article.

Please check the location of the view of the element.

Also note that the view’s file name must be the same with element block’s file name.