Getting around the ASP.NET Single Form Interface (and Sitecore)

Problem

Recently I had to add a third party widget (a Travel Planner from TFL) to a Sitecore page (ASP.NET). The widget was a piece of HTML containing a couple of input controls and a form tag that generated an HTTP POST to open a page on another website. So far, so easy. In a normal world (PHP for example) it _should_ be possible to just drop the HTML onto the page and Bob’s your uncle.

Not with ASP.NET and Sitecore.

Adding this trivial piece of HTML to a Sitecore page presents its own unique difficulties.

1.) Sitecore: Adding the HTML as a Rich Text field is futile as the form tags are stripped out by Sitecore, unless added to the page layout itself.

2.) ASP.NET: the Single Form Interface used in ASP.NET (Webforms), means that only one server-side form tag is supported, and the page already contained a server-side form.

Solution
The solution is to create a dynamic form on the fly with javascript, and submit it on the button click. The easiest way to do this is to define the form in the .ascx markup as normal in a fieldset, but without the form tags:


<fieldset id="jpForm" style="margin: 5px 0 0 0 !important; padding: 0 !important;"> <input name="language" type="hidden" value="en" /> <!-- in language = english -->
<input name="execInst" type="hidden" /> <input name="sessionID" type="hidden" value="0" /> <!-- to start a new session on JP the sessionID has to be 0 -->
<input name="ptOptionsActive" type="hidden" value="-1" /> <!-- all pt options are active -->
<input style="width: 155px ! important;" name="name_origin" type="text" value="From" />
// more fields etc
</fieldset>

Then using javascript, create a new form tag, and attach this fieldset to it as a child node. There was one further issue, that dropdown list selected items were not being captured, just the initial values.

The solution to this was to rename the the dropdown lists and then add their selected contents to (correctly named) input controls created and added to the dynamic form thus:


<script type="text/javascript">// <![CDATA[
function post_to_url() {

        // The rest of this code assumes you are not using a library.
        // It can be made less wordy if you use one.
        var form = document.createElement("form");
        form.setAttribute("method", "post");
        form.setAttribute("action", "http://journeyplanner.tfl.gov.uk/user/XSLT_TRIP_REQUEST2");
        form.setAttribute("target", "tfl");

        var fieldset = document.getElementById("jpForm");
        var copy = fieldset.cloneNode(true);

        var origin = document.getElementById("type_origin_form");
        var destination = document.getElementById("type_destination_form");

        var originInput = document.createElement("input");
        originInput.setAttribute("name", "type_origin");
        originInput.setAttribute("value", origin.options[origin.selectedIndex].value);

        var destinInput = document.createElement("input");
        destinInput.setAttribute("name", "type_destination");
        destinInput.setAttribute("value", destination.options[destination.selectedIndex].value);

        form.appendChild(copy);
        form.appendChild(originInput);
        form.appendChild(destinInput);

        document.body.appendChild(form);
        form.submit();
    };
// ]]></script>

The resulting HTTP POST therefore contains two extraneous parameters (type_destination_form & type_origin_form), but these are ignored by the target page (unsurprisingly as they are not expected).

POST /user/XSLT_TRIP_REQUEST2 HTTP/1.1

language=en&execInst=&sessionID=0&ptOptionsActive=-1&place_origin=London&place_destination=London&name_origin=SW1X7HH&type_origin_form=stop&name_destination=SE186SX&type_destination_form=stop&type_origin=locator&type_destination=locator

And the page works as expected.