If your EPiServer site is running a version older than CMS5 R2 and you (or your clients) are using Internet Explorer 8 you’re having problems creating XForms. There are multiple issues with XForms in versions prior to CMS5 R2 that can be somewhat confusing. A form created with IE6 will work for a visitor using IE8, a form created with IE8 will not work for anyone. If you use IE8 you cannot save your form if it contains radio buttons or checkboxes. There is a posts on EPiServers forum where Vladimir Ljepoja claims a solution for EPiServer CMS 5 R1 SP2 and DropIT has published a blog post on how to fix the problem for EPiServer CMS 5 RC1.

I’ve investigated this problem thoroughly and found the reason why IE8 is causing problems and how you can solve it for all versions of EPiServer. This work is based on investigations in EPiServer 4.61.5.

What’s causing the problem?

When I started investigating this problem I obviously started in font-end with javascript debugging. The XForms editor javascript code is in a file named xformedit.js which is located at different places depending on your EPiServer version. For 4.6.1 it’s located in util\javascript. What’s causing the problem on the front-end is this line of code:

content.value = xformcontrol.innerHTML;

EPiServer rely on innerHTML to get the markup for the form that the user created. This should be all well because Internet Explorer 6/7/8 will return valid HTML. However, there’s a slight difference between the markup from innerHTML in IE6 and IE8. Here’s an example:

IE6

<input type=”checkbox” value=”on” name=”test2″ />

IE8

<input value=”on” type=”checkbox” name=”test2″ />

Notice the difference in the order of the attributes in the otherwise correct markup? This leads us to the next part of the problem:
EPiServer.XForms.Util.XFormConvert inside the EPiServer.XForm assembly.
Through the magic of reflection (Reflector) it’s evident that this is where the real problem lie. The convert method turn valid HTML-markup into XForms compatible XML markup, or does it? The convert method treat the form html markup as text and use regular expressions to replace elements with XForm counterpart markup to later store it in an XML document. Both IE6 and IE8 pass valid HTML back to EPiServer but the problem is that the regular expressions use the “type” attribute for replacing the various controls and to get a match the expression require the type attribute to in a specific order.

Perhaps using XSLT or XPath to convert the XForm would be a better and more robust solution?

For the conversion to work the type attribute has to be on the following indexes for the following elements:

Element Position of type attribute
checkbox 0
radio 0
submit 1

How to fix it?

There are two ways to work around this. Either fix the javascript code in xformedit.js to always return markup with attributes in the correct order or write some back-end code to fix the problem. Because it’s tedious to string manipulate markup that obviously can change between versions I’ve written a back-end solution for this problem.

I’ve created a PageAdapter that augment EPiServers EPiServer.Edit.EditXForm class with code to correct the form markup before it hits the save function. Unfortunately everything in the EditXForm class is either private or protected and the pesky save method pulls the posted markup straight from the Request object, which is read only.

using System;
using EPiServer.Core.Html;
using System.Xml;
using System.Reflection;
using System.Web.UI.Adapters;
 
namespace Blog.Sallarp.Com.EPiServer
{
    ///
    /// Page adapter for EPiServer.Edit.EditXForm that solve problems
    /// occuring when saving forms using IE8.
    ///
    /// For more in-depth information on the problem, check out
    /// my blog @ http://blog.sallarp.com
    ///
    /// No copyright etc but please don't steal and post as your
    /// own solution. Show appriciation by linking to my blog, thanks!
    ///
    public class EditXFormPageAdapter : PageAdapter
    {
        protected override void OnLoad(EventArgs e)
        {
            // We're only interested in postbacks
            if (this.Page.IsPostBack)
            {
                // Read the posted xform content
                string htmlToIndex = this.Page.Request.Form["__formcontent"];
 
                // If the posted content isn't empty, run a correction round.
                if (!string.IsNullOrEmpty(htmlToIndex))
                {
                    /* Unfortunately EPiServer read the posted content directly from the Request.Form in
                     * EPiServer.Edit.EditXForm.SaveForm() (EPiServer.CodeBehind assembly). To avoid
                     * re-writing lots of EPiServer code we fix the posted content and store it back for EPiServer to read.
                     * Because the NameValueCollection in Request.Form is read only we have to use reflection to disable the
                     * write protection.
                     *
                    */
                    PropertyInfo info = this.Page.Request.Form.GetType().GetProperty("IsReadOnly", BindingFlags.Instance | BindingFlags.NonPublic);
                    info.SetValue(this.Page.Request.Form, false, null);
 
                    // Store the new corrected HTML back for EPiServer.
                    this.Page.Request.Form.Set("__formcontent", CorrectXFormMarkup(htmlToIndex));
 
                    // Set the read only lock back on the form.
                    info.SetValue(this.Page.Request.Form, true, null);
                }
            }
 
            base.OnLoad(e);
        }
 
        ///
        /// Corrects possible problems in the HTML posted from the Xform edit page.
        /// EPiServer require that node attributes have the correct collection index
        /// so this method re-arrange attributes if needed.
        ///
        private string CorrectXFormMarkup(string htmlMarkup)
        {
            if (!string.IsNullOrEmpty(htmlMarkup))
            {
                // This code is copied from EPiServers XForm code.
                HtmlParser parser = new HtmlParser(htmlMarkup, false);
                string hTML = parser.ToString().Replace("&nbsp;", "&#160;");
 
                // Set up a xml document and load the parsed html.
                XmlDocument xmlFormatter = new XmlDocument();
                xmlFormatter.LoadXml(hTML);
 
                // Move attributes for checkbox, radio and submit buttons.
                MoveAttribute(xmlFormatter, "input", "type", "checkbox", 0);
                MoveAttribute(xmlFormatter, "input", "type", "radio", 0);
                MoveAttribute(xmlFormatter, "input", "type", "submit", 1);
 
                // Put the "corrected" markup back in the form so EPiServers code
                // use the it instead of what was actually posted.
                htmlMarkup = xmlFormatter.InnerXml;
            }
 
            return htmlMarkup;
        }
 
        ///
        /// Find nodes of a given type with a given attribute value and move that
        /// attribute to different index in the attribute sequence
        ///
        private void MoveAttribute(XmlDocument xmlFormatter, string nodeType, string attrName, string attrValue, int correctPosition)
        {
            foreach (XmlNode inputNode in xmlFormatter.SelectNodes(string.Format("//{0}[@{1}='{2}']", nodeType, attrName, attrValue)))
            {
                // Check if the attribute already has the correct position
                if (inputNode.Attributes[correctPosition].Name != attrName)
                {
                    // Remove the attribute
                    inputNode.Attributes.Remove(inputNode.Attributes[attrName]);
 
                    // If the position is somewhere in a collection of attributes we add it at the correct position
                    if (correctPosition > 0)
                    {
                        inputNode.Attributes.InsertBefore(xmlFormatter.CreateAttribute(attrName), inputNode.Attributes[correctPosition]);
                    }
                    else
                    {
                        // if the attribute is to be placed first we can easily prepend the attribute collection
                        inputNode.Attributes.Prepend(xmlFormatter.CreateAttribute(attrName));
                    }
 
                    // Set the attribute value, we deleted it above.
                    inputNode.Attributes[attrName].Value = attrValue;
                }
            }
        }
    }
}

You will need to reference your EPiServer dll in order to build this. To enable the adapter you must edit your Browser.browser file in the App_Browsers folder. Example:

<browsers>
  <browser refID="Default">
    <controlAdapters>
      <adapter controlType="System.Web.UI.WebControls.Menu" adapterType="CSSFriendly.MenuAdapter" />
      <adapter controlType="EPiServer.Edit.EditXForm" adapterType="Blog.Sallarp.Com.EPiServer.EditXFormPageAdapter" />
    </controlAdapters>
  </browser>
</browsers>

Hopefully I have shed some light on the problem and the possible ways to solve it. If you are on EPiServer CMS 5 already you’re better off upgrading to the latest version, but if you’re on EPiServer 4, upgrading to CMS 5 isn’t exactly done in a jiffy and users tend to get irritated quickly when an important function isn’t working. If someone writes a javascript solution (before I get the time to do it myself) please post me a message.

Related posts