Updated 2008-01-16:
Check out the refactored version which drops the reflection and uses a straight response filter.
Sergio Pereira and I started writing some stories today for Javascript helpers in Mvc Contrib. During our talk we had to deal with rendering html elements with inner html and javascript blocks ensuring they all got closed but still allowing the ultimate in flexibility for the developer.MvcToolkit goes about this with a using(…) pattern on their FormHelper. The one disadvantage of this is that the using block will be limited to only a single block. What happens when you need to render a block for onSuccess and onFailure?
Sergio brought up how powerful ruby blocks are and how cool it would be if we could use lambdas to do something similar.
Sergio proposed being able to do something like this.
<% Ajax.Tag(“div“, “/controller/action/123“,
new {CssClass=”bigSquare“}, myDiv =>
{ %>
Some Html here
Some helper: <%= Html.Link(“Click me“, “/other/non-ajax/url“) %>
}) %>
What is myDiv? myDiv is the blocks outer HtmlElement object so inside of your lambda you can manipulate its attributes and perform some other cool stuff we are still cooking up. While the lambda executes we capture the output and defer rendering to the response until after the lambda is finished. This allows us to render the HtmlElement with any modifications you made.
So how do we do this, well, we needed a pretty big hack because our delegate is being called from another anonymous delegate which has the HtmlTextWriter as a local variable. This means that we cannot use IHttpContext.SwitchWriter to capture our output.
Any way to get a fix for this MS?
So in reflector I noticed my anonymous type for my delegate had a public field __w which was the HtmlTextWriter. Using some reflection I was able to code up my own SwitchWriter which took care of everything.
The helper code is pretty simple.
public static void FromTag(this AjaxHelper helper
,string tag
,string url
,object options,
Action<Element> innerHtml)
{
Element element = new Element(tag);
HtmlTextWriter responseWriter = null;
if(innerHtml != null)
{
try
{
using(StringWriter innerWriter = new StringWriter())
using (HtmlTextWriter innerHtmlWriter =
new HtmlTextWriter(innerWriter))
{
responseWriter = SwitchWriter(innerHtml, innerHtmlWriter);
innerHtml(element);
innerHtmlWriter.Flush();
element.InnerHtml = innerWriter.ToString();
}
}
finally
{
if(responseWriter != null)
{
SwitchWriter(innerHtml, responseWriter);
}
}
}
RenderElement(new HtmlTextWriter(
helper.ViewContext.HttpContext.Response.Output), element);
}
The switch writer is pretty basic, it was tracking it down that took a little bit of time.
public static HtmlTextWriter SwitchWriter(object obj
, HtmlTextWriter newWriter)
{
Type actionType = obj.GetType();
object target = actionType.GetField("_target",
BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(obj);
Type targetType = target.GetType();
HtmlTextWriter response = targetType.GetField("__w")
.GetValue(target) as HtmlTextWriter;
targetType.GetField("__w").SetValue(target, newWriter);
return response;
}
With the above in place I can call it with something like this.
<% Ajax.FromTag("div", "/Home/About",
new object(),
myDiv => { myDiv.Id = "Justice"; %><h6>
<%=Html.ActionLink("Where is Justice?", "About")%>
</h6>
<% }); %>
And it renders something like this… no we didn’t spike any ajax stuff yet.
<div id="Justice">
<h6><a href="/Home/About">Where is Justice?</a></h6>
</div>
You may wonder why we do not return a string from our helper. Asp.Net currently can’t handle that. It thinks its a block and not a simple expression so using the regular <%= %> will not work. Instead of having you call <%Response.Write(…);%> we thought it would be easier if the helper just rendered it for you.
This got me excited about some the cool things we can really start to do. I like getting an instance of Element to set properties on, this can help reduce the abuse that anonymous types are taking in mvc right now. It would be great if there could be patch so we do not have to resort to reflection to switch writers, but I quote Sergio
how do you feel about that __w hack? For me it looks prettier by the minute
Let me know what you think,
Cheers