Virtual Happy Hour this month, Jun 28, we'll be getting a sneak preview at our soon to launch SaaS CMS!

Try our conversational search powered by Generative AI!

Anders Hattestad
May 27, 2010
  18398
(3 votes)

Extend EPiServer XForm

There are lot of times you want to extend the XForm. It’s one of the most hardest part of EPiServer to extend to your need, since none of the items you can add inside a xform is hardcoded inside the edit mark-up. You can make your own validation and attach to these events

control.BeforeLoadingForm +=
control.ControlsCreated +=
control.BeforeSubmitPostedData +=
control.AfterSubmitPostedData +=

But if you want to make the choices inside a dropdown list come from the category tree or the page tree you have to make your own logic.

I have done this in some projects, but I think this code is a cool way of extending, since it’s very plug and play :)

image

will be rendered like this:

image

First a made a interface that my extension classes can use. I’m marking them with GuiPlugIn so I can turn them on and off from the Plug-In Managers. More about this method in my presentation on EPiServer meetup in Norway page 40.

public interface ICanExtendXform
{
    void DisplayHelp(Dictionary<string, StringBuilder> help);
    void ExtendItem(XFormControlBase item, StringBuilder debug);
}

Then I attached to the start event (page 24 same presentasjon).  

public class AttachEvents : PlugInAttribute
{
    public static List<ICanExtendXform> Handlers { get; set; }
    public static void Start()
    {
        XFormControl.ControlSetup += new EventHandler(XFormControl_ControlSetup);
        PageBase.PageSetup += new PageSetupEventHandler(PageBase_PageSetup);
        var list=FindPlugIns(typeof(ICanExtendXform).Name,null);
        Handlers=new List<ICanExtendXform>();
        foreach (var item in list) 
           Handlers.Add(item.Create() as ICanExtendXform);
    }
    static void PageBase_PageSetup(PageBase sender, PageSetupEventArgs e)
    {
        if (sender is EPiServer.UI.Edit.XFormEdit)
            sender.PreRender += new EventHandler(EditXform_PreRender);
    }

    static void XFormControl_ControlSetup(object sender, EventArgs e)
    {
        XFormControl control = (XFormControl)sender;
        control.ControlsCreated += new EventHandler(control_ControlsCreated);
    }

I use the PageBase.PageSetup to extend the edit mode of the xform page.The actually change does I like this

static void EditXform_PreRender(object sender, EventArgs e)
{
    Control ctr = FindControl<Panel>(sender as Control, "PropertiesArea");
    if (ctr != null)
    {
        Dictionary<string, StringBuilder> help = new Dictionary<string, StringBuilder>();
        foreach (var item in Handlers)
        {
            item.DisplayHelp(help);
        }
        StringBuilder help2 = new StringBuilder();
        foreach (string key in help.Keys)
        {
            help2.Append("<h3>" + key + "</h3><ul>");
            help2.Append(help[key]);
            help2.Append("</ul>");
        }
        ctr.Controls.Add(new Literal() { Text = help2.ToString(), EnableViewState = false });
    }
}

This will add the help text inside the edit page of a form

image

My extensions are made in separate classes all tagged with a interface and the GuiPlugIn so they can be turn on and off

image

[GuiPlugIn]
public class ExtendXformWithCssClass : ICanExtendXform
{
    public void DisplayHelp(Dictionary<string, StringBuilder> help)
    {
        if (!help.ContainsKey("CssClass"))
            help.Add("CssClass", new StringBuilder());
        help["CssClass"].Append("<li><strong>readonly</strong> inside cssclass will make it readonly</li>");

    }
    public void ExtendItem(XFormControlBase item, StringBuilder debug)
    {
        if (item.Attributes["class"] != null && item.Attributes["class"].Contains("readonly"))
            item.Attributes.Add("readonly", "true");
    }
   
}

[GuiPlugIn]
public class ExtendXformWithPageData:ICanExtendXform
{
    public void DisplayHelp(Dictionary<string,StringBuilder> help)
    {
        if (!help.ContainsKey("Choices"))
            help.Add("Choices",new StringBuilder());
        help["Choices"].Append("<li><strong>EPiChildren_3</strong> in Name value will be replaced with all children of page 3</li>");

    }
    public void ExtendItem(XFormControlBase item,StringBuilder debug)
    {
        ReplaceChoicesWith_EPiChildren_(item, debug);
    }
    #region ReplaceChoicesWith_EPiChildren_
    private static void ReplaceChoicesWith_EPiChildren_(XFormControlBase item, StringBuilder debug)
    {
        SelectControl select = item as SelectControl;
        if (select != null)
        {
            for (int i = 0; i < select.Choices.Count(); i++)
            {
                string val = select.Choices[i].Label;
                if (val.StartsWith("EPiChildren_"))
                {
                    debug.Append("<dir>Found option " + i + " with " + val);
                    string[] s = val.Split("_".ToCharArray());
                    PageDataCollection pages = EPiServer.DataFactory.Instance.GetChildren(PageReference.Parse(s[1]));
                    debug.Append(" getting " + pages.Count + " pages");

                    select.Choices.Remove(select.Choices[i]);
                    foreach (PageData page in pages)
                    {
                        select.Choices.Insert(i, new SelectItem() { Value = page.PageLink.ToString(), Label = page.PageName });
                        i++;
                    }
                    debug.Append("</dir>");
                }
            }
        }
    }
    #endregion
}

[GuiPlugIn]
public class ExtendXformWithCategory : ICanExtendXform
{
    public void DisplayHelp(Dictionary<string,StringBuilder> help)
    {
       if (!help.ContainsKey("Choices"))
            help.Add("Choices",new StringBuilder());
        help["Choices"].Append("<li><strong>EPiCategory_News</strong> in Name value will be replaced with all children of category News</li>");
        
    }
    public void ExtendItem(XFormControlBase item, StringBuilder debug)
    {
        ReplaceChoicesWith_EPiCategory_(item, debug);
    }
     #region ReplaceChoicesWith_EPiCategory_
    private static void ReplaceChoicesWith_EPiCategory_(XFormControlBase item, StringBuilder debug)
    {
        SelectControl select = item as SelectControl;
        if (select != null)
        {
            for (int i = 0; i < select.Choices.Count(); i++)
            {
                string val = select.Choices[i].Label;
                if (val.StartsWith("EPiCategory_"))
                {
                    debug.Append("<dir>Found option " + i + " with " + val);
                    string[] s = val.Split("_".ToCharArray());
                    Category cat = EPiServer.DataAbstraction.Category.Find(s[1]);
                    if (cat != null)
                    {
                        debug.Append(" found category and using children:"+cat.Categories.Count);
                    
                        select.Choices.Remove(select.Choices[i]);
                        foreach (Category subCat in cat.Categories)
                        {
                            select.Choices.Insert(i, new SelectItem() { Value = subCat.ID.ToString(), Label = subCat.LocalizedDescription });
                            i++;
                        }
                    }
                    debug.Append("</dir>");
                }
            }
        }
    }
    #endregion
    
   
}

[GuiPlugIn]
public class ExtendXformWithUserData : ICanExtendXform
{
    string Key="EPiUserData_";
    public void DisplayHelp(Dictionary<string,StringBuilder> help)
    {
        if (!help.ContainsKey("Text in TextBox"))
            help.Add("Text in TextBox",new StringBuilder());
        help["Text in TextBox"].Append("<li><strong>EPiUserData_UserName</strong> will be replaced with Current users UserName</li>");
        help["Text in TextBox"].Append("<li><strong>EPiUserData_Email</strong> will be replaced with Current users Email</li>");
        help["Text in TextBox"].Append("<li><strong>EPiUserData_Company</strong> will be replaced with Current users Company</li>");
    
      
    }
    public void ExtendItem(XFormControlBase item, StringBuilder debug)
    {
        if (item.Value != null && item.Value.StartsWith(Key))
        {
            string rest = item.Value.Substring(Key.Length);
            if (rest == "UserName")
                item.Value = EPiServerProfile.Current.UserName;
            else
            {
                object val = null;
                if (EPiServerProfile.Current.TryGetProfileValue(rest, out val))
                    item.Value = val.ToString();
            }
        }
    }
   
   
}

 

That's all. And easy way of changing parts of XForm and make some code pluggable :)

All code is in one file here

My presentasjon at EPiServer meetup in Norway is here

Looking forward the EPiServer Partner Summit

May 27, 2010

Comments

Sep 21, 2010 10:33 AM

This was a really cool way to extend the built in XForms functionality Anders. As you state, this area has very few extension points, especially if you want to add new/changed controls but this post really solves some problems that developer face and shows how one can extend the base functionality. I hope that we get time to rewrite some parts of the XForms functionality some time in a not to distant future to make it easier to extend but since this means rewriting most of the XForms editor it has been outweighted by other areas so far. My dream is to marry Tiny MCE with the XForms core to get a really dynamic html/forms editor :).

Sep 21, 2010 10:33 AM

Would it be possible to use this technique to add upload functionality to the XForm editor?
/ Stephan

Sep 21, 2010 10:33 AM

Yes

You could change for instance a text field to a file field if the text field name started with upload.....


/ Anders Hattestad

Cecilia Håkansson
Cecilia Håkansson Oct 1, 2012 01:22 PM

Is it possible to get the code in one file (the link is not working anymore...)
/Cecilia

Jonathan Roberts
Jonathan Roberts Jan 18, 2013 10:34 AM

Is there any way you can encrypt the form data before it is submitted into the database?

Mar 4, 2013 03:15 PM

Great post! Thanks! The link to your code samples does not work! Can you please fix it?

Christer Pettersson
Christer Pettersson May 20, 2013 09:11 AM

Is there anyone else who has problems when posting the actual file, my HttpFileCollection is always null, I'm not able to upload any file.

Well I seem to have found it.
the devils contribution to asp.net the form was surrounded by an asp:UpdatePanel.

Feb 11, 2014 01:27 PM

I would love some help getting this to work with EPiServer 7 MVC and Razor. It just wont render the file upload button. Does anyone know of a way to solve this? The code works perfectly in EPiServer MVC and webforms.

nicolaa
nicolaa Mar 23, 2015 08:10 AM

Hi,

Can I please get a copy of the 'code in one file'. The link no longer works.

Thanks,
Nicola

Please login to comment.
Latest blogs
Remove a segment from the URL in CMS 12

Problem : I have created thousands of pages dynamically using schedule jobs with different templates (e.g. one column, two columns, etc..) and stor...

Sanjay Kumar | Jun 21, 2024

Copying property values part 2

After publishing my last article about copying property values to other language versions, I received constructive feedback on how could I change t...

Grzegorz Wiecheć | Jun 18, 2024 | Syndicated blog

Enhancing online shopping through Optimizely's personalized product recommendations

In this blog, I have summarized my experience of using and learning product recommendation feature of Optimizely Personalization Artificial...

Hetaxi | Jun 18, 2024

New Series: Building a .NET Core headless site on Optimizely Graph and SaaS CMS

Welcome to this new multi-post series where you can follow along as I indulge in yet another crazy experiment: Can we make our beloved Alloy site r...

Allan Thraen | Jun 14, 2024 | Syndicated blog

Inspect In Index is finally back

EPiCode.InspectInIndex was released 9 years ago . The Search and Navigation addon is now finally upgraded to support Optimizely CMS 12....

Haakon Peder Haugsten | Jun 14, 2024

Change the IP HTTP Header used for geo-lookup in Application Insights

.

Johan Kronberg | Jun 10, 2024 | Syndicated blog