Code, Code, Revolution!
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.
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 |
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(" ", " "); // 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.
With this blog I try to provide useful tips and solutions for programming .NET, Objective-C and more. My name is Björn Sållarp, and I love writing code.
Shahid
February 16th, 2010 at 8:27 am
Hi,
Great work !
FYI: EPiSever Developer Support have already made a hotfix for xForm issue but that only works for cms-4.62. Send a request to support if you want to get hold of that hotfix.
Jacob
February 25th, 2010 at 10:13 am
Thanks for sharing this!
This seems to fix the issue for 4.x as long as it’s running on .Net 2.0< since the .browser-file is supported from that.
Do you or anyone know how to get this to work for CMS 5? EPiServer.Edit.EditXForm doesn't exist in CMS 5. I would like to use this as a temporary fix before upgrading.
FYI: The replacement of nbsp (I'm guessing that's what it is) is missing from the code posted.
Björn Sållarp
February 26th, 2010 at 8:51 am
Hi Jacob,
The fix proposed in this post is valid for all versions of EPiServer, however, the method of implementing it using a ControlAdapter is only valid for .NET 2.0. If you have a site running 1.1 just use reflector and pull all code from the XForm editor code-behind from EPiServer and create your own code-behind file with the fix in this post.
In CMS 5 you should point the ControlAdapter to EPiServer.UI.Edit.XFormEdit.
I don’t really understand what you mean with the nbsp. Where and why would it be missing? The code on this blog is running perfectly in production.
// Björn
Ben
March 16th, 2010 at 2:40 am
Could you not just force your site to render in compatibility mode by adding this tag to the head of your html?
Björn Sållarp
March 23rd, 2010 at 11:39 am
@Jacob,
I realize what you meant about the nbsp;. The code plugin for wordpress removed it, and it’s very important that the line is correct otherwise it will cause a crash. I Updated the line now.