Semantic markup with EPiServer XForms
Posted on November 26, 2010 by Frederik Vig in EPiServerEPiServer’s XForm editor is used for letting editors easily build forms inside EPiServer. The HTML markup that XForm renders uses tables to layout the form. Semantically this is completely wrong, so in this post I’m going to show you how easy it is to replace the HTML markup rendered by XForms with your own.
Before we start I recommend reading the tech-note: Developing with XForms.
There are two ways to override the rendering of XForms. We can use the BeforeLoadingFormsEvent event or the ControlsCreated event (see the tech-note for more information)
Coding time
Open up Global.asax.cs – here you’ll find methods that are already attached to various XForm events. Go to the method XForm_ControlsCreated and add this code at the bottom, inside the method.
public void XForm_ControlsCreated(object sender, EventArgs e) { ... this.CleanupXFormHtmlMarkup(formControl); } |
Now we need to create the CleanupXFormHtmlMarkup method.
private void CleanupXFormHtmlMarkup(XFormControl formControl) { } |
Before we start coding away, let’s decide on the markup we’d like to have.
<div class="xform> <fieldset> <p> <label for="name">Name:</label> <input type="text" id="name" name="name" size="20" value="" /> </p> ... </fieldset> </div> |
After reading the tech-note we know that all the LiteralControls in the Control collection contain the table markup for the form. What we need to do is traverse through the Control collection and replace this markup with our own.
private void CleanupXFormHtmlMarkup(XFormControl formControl) { if (formControl.EditMode) { // We need to render the default table when in edit mode, otherwise the form wizard won't work return; } bool firstLiteralControl = false; LiteralControl literalControl = null; ControlCollection controls = formControl.Controls; foreach (object control in controls) { if (control is LiteralControl) { literalControl = control as LiteralControl; // find the first literal control if (!firstLiteralControl) { literalControl.Text = "<fieldset><p>"; firstLiteralControl = true; } else { literalControl.Text = "</p><p>"; } } } // update markup for the last literal control if (literalControl != null) { literalControl.Text = "</p></fieldset>"; } } |
Here’s the HTML markup rendered for the Public Template’s contact form after our updates:
<div id="ctl00_MainRegion_MainContentRegion_MainBodyRegion_ctl01_FormPanel" class="xForm"> <fieldset> <p> <label for="ctl00_MainRegion_MainContentRegion_MainBodyRegion_ctl01_FormControl_Name"> Your name:</label><input type="text" class="value" title="Your name" id="ctl00_MainRegion_MainContentRegion_MainBodyRegion_ctl01_FormControl_Name" name="ctl00$MainRegion$MainContentRegion$MainBodyRegion$ctl01$FormControl$Name" value="" /> </p> <p> <label for="ctl00_MainRegion_MainContentRegion_MainBodyRegion_ctl01_FormControl_Email"> Your e-mail address:</label><input type="text" class="value" title="Your e-mail address" id="ctl00_MainRegion_MainContentRegion_MainBodyRegion_ctl01_FormControl_Email" name="ctl00$MainRegion$MainContentRegion$MainBodyRegion$ctl01$FormControl$Email" value="" /> </p> <p> <label for="ctl00_MainRegion_MainContentRegion_MainBodyRegion_ctl01_FormControl_Message"> Your message:</label><textarea class="textbox" title="Enter your messgage here" id="ctl00_MainRegion_MainContentRegion_MainBodyRegion_ctl01_FormControl_Message" name="ctl00$MainRegion$MainContentRegion$MainBodyRegion$ctl01$FormControl$Message" cols="20" rows="2"></textarea> </p> <p> <input type="submit" value="Send" name="ctl00$MainRegion$MainContentRegion$MainBodyRegion$ctl01$FormControl$ctl00" class="button" title="Send you message" onclick="WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions("ctl00$MainRegion$MainContentRegion$MainBodyRegion$ctl01$FormControl$ctl00", "", false, "XForm", "", false, false))" /> </p> </fieldset> </div> |
With only a few code changes we easily changed all the XForm HTML markup. We could also customize validation (we have access to all the controls) and the form even more, but that is for another post :).
Daniel Berg says:
Post Author November 26, 2010 at 17:54That saved me some work. Thanks Fredrik!
Daniel Berg says:
Post Author November 26, 2010 at 17:55And by Fredrik I of course mean Frederik. ๐
Per Nergรฅrd says:
Post Author November 26, 2010 at 20:32Great work! Now if we could get the editors could stop using the the editor to insert crap html ๐
/Per
Per Nergรฅrd says:
Post Author November 26, 2010 at 21:23Great post as usual! Now if we only hade a magic adapter that magically transformed all crap html added in the xhtmleditor by the ediors.
jamo says:
Post Author November 26, 2010 at 23:49Ey, you need to brush up your C#-fu:
ControlCollection controls = formControl.Controls;
LiteralControl lastLiteralControl;
foreach (object control in controls)
{
if (control is LiteralControl)
{
lastLiteralControl = control as LiteralControl;
Saves you those last backwards iterations…
Frederik Vig says:
Post Author November 27, 2010 at 13:08@Jamo – nice improvement. Updated the code now.
Kim Medin says:
Post Author November 27, 2010 at 17:32Nice one, thanks.
This can also be done using Linq, here is an example using DIVs instead of Ps.
ControlCollection controls = formControl.Controls;
// Get all LiteralControls
var controls = controlCollection.OfType();
// Get the first item
controls.First().Text = “”;
// Get the last item
controls.Last().Text = “”;
// Get the items in between
controls.Where(c => c != controls.First() && c != controls.Last()).ToList().ForEach(delegate(LiteralControl literal)
{
literal.Text = “”;
});
Kim Medin says:
Post Author November 27, 2010 at 17:46hm, something screwed up the x.Text = value, but you get the point.
Anway..:
controls.First().Text = fieldset div
controls.Last().Text = /div /fieldset
literal.Text = /div div
Erik says:
Post Author November 30, 2010 at 10:00Nice Fredrik. A lot cleaner then the old solution back in the days.. ๐
Daniel says:
Post Author February 22, 2011 at 17:18A problem I’m having with this code is when I add a heading to the form (which is a literal control). It will not render it. A solution for this? Or am I doing it wrong? ๐
Frederik Vig says:
Post Author February 22, 2011 at 20:54@Daniel – headings, horizontal rulers etc get added with the rest of the table markup that I replace in my code. No easy way of excluding and saving that part of the markup, but you could try using Regex or the Html Agility Pack (http://htmlagilitypack.codeplex.com/).
Daniel says:
Post Author February 23, 2011 at 11:05Thanks for the advice! Maybe I’ll give regex a try then. Oh n btw, great post! ๐
Andrew Sanin says:
Post Author June 1, 2011 at 17:06Very useful post and comments as well. Also had a fight with heading(text) inserted into the xform. I’ve used this regex to extract Heading:
[<]span\s*(.+?)\s*[]
Andrew Sanin says:
Post Author June 1, 2011 at 17:09Regex above was displayed with mistake. It should be like this:
[<]span\s*(.+?)\s*[]
Andrew Sanin says:
Post Author June 1, 2011 at 17:10[<]span\s*(.+?)\s*[<]/span[>]
Johan F says:
Post Author June 9, 2011 at 08:50How do you hook in to the the html where you get the headings with regex or the Html Agility Pack.
Johan F says:
Post Author June 9, 2011 at 09:16Hehhe nevermind in the literal control of course ๐