Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Working around Controls.Add() and <%= %> Script Limitations with JavaScript


:P
On this page:

A while back I had run into a problem with script expressions (<% %> and <%= %>) and Controls.Add() functionality in ASP.NET pages. The problem is that if you have any sort of markup script inside of a container object that you’re calling Controls.Add() (or InsertAt())on, ASP.NET will complain that it can’t add any controls because of the script code.

 

This is a fairly major problem for generic code that wants to inject controls into the page dynamically. My scenario I’ve been working on on and off is a DataBinding validation routine that automatically shows error information next to controls that have binding errors without the use of validators (although Validators can be used with it as well although they’re not as flexible). Rather the databinding mechanism can automatically detect binding errors and display error information next to the control or below or wherever:

 

http://west-wind.com/weblog/images/21/o_ValidationFields.png

 

The warning icons and text are injected at runtime if an error occurs – there are no placeholders or validators in place here.

 

The original code I used purely did a Controls.InsertAt() to create a new literal control that contains some HTML markup that basically generates the desired output. But that failed IF there was <%= %> script markup in the page.

 

The control that handles this databinding is a generic DataBinder control that extends existing controls. So it’s a truly generic control that can sit on any page and can bind anything (object properties, DataRow Fields etc.) to any control property (ie. txtName.txt chkOutput.Checked etc.). Bottom line is, there’s no explicit control over what pre-exists on the page so I can’t know whether script tags exist or not. Failure is graceful if there are but you just won’t see the warning notices which is not optimal.

 

To make this truly generic I decided to use some JavaScript code on the client to inject the code into the page that gets inserted into the page by the control. Basically I added this function:

 

function AddHtmlAfterControl(ControlId,HtmlMarkup)

{

    var Ctl = document.getElementById(ControlId);

    if (Ctl == null)

       return;

      

    var Insert = document.createElement('span');

    Insert.innerHTML = HtmlMarkup;

 

    var Sibling = Ctl.nextSibling;

    if (Sibling != null)

       Ctl.parentNode.insertBefore(Insert,Sibling);

    else

       Ctl.parentNode.appendChild(Insert);

}

 

which basically allows adding HTML after any control. The HTML DOM doesn’t have a simple way to insert a new control before or after a control, so there’s a little work involved finding the next control by skipping forward and then inserting before that item. If there’s no next control, then it’s inserted last.

 

If you want to inject markup before the control that’s a little easier as you can just use insertBefore().

 

The control code then injects this JavaScript function into the page, and generates a function call to this function for each of the warning icons to generate by outputting some startup script code.

 

The control code that accomplishes all of this looks like this:

 

// <summary>

/// Creates the text for binding error messages based on the

/// BindingErrorMessage property of a data bound control.

///

/// If set the control calls this method render the error message. Called by

/// the various controls to generate the error HTML based on the <see>Enum

/// ErrorMessageLocations</see>.

/// <seealso>Class wwWebDataHelper</seealso>

/// </summary>

/// <param name="control">

/// Instance of the control that has an error.

/// </param>

/// <returns>String</returns>

internal string GetBindingErrorMessageHtml(wwDataBindingItem Item)

{

    string Image = null;

    if (string.IsNullOrEmpty(this.ErrorIconUrl) || this.ErrorIconUrl == "WebResource")

        Image = this.ErrorIconWebResource;  // Retrieve Resource Url

    else

        Image = this.ResolveUrl(this.ErrorIconUrl);

 

    string Message = "";

 

    if (Item.ErrorMessageLocation == BindingErrorMessageLocations.WarningIconRight)

        Message =  string.Format("&nbsp;<img src=\"{0}\" alt=\"{1}\" />", Image, Item.BindingErrorMessage);

    else if (Item.ErrorMessageLocation == BindingErrorMessageLocations.RedTextBelow)

        Message = "<br /><span style=\"color:red;\"><smaller>" + Item.BindingErrorMessage + "</smaller></span>";

    else if (Item.ErrorMessageLocation == BindingErrorMessageLocations.RedTextAndIconBelow)

        Message = string.Format("<br /><img src=\"{0}\"> <span style=\"color:red;\" /><smaller>{1}</smaller></span>", Image, Item.BindingErrorMessage);

    else if (Item.ErrorMessageLocation == BindingErrorMessageLocations.None)

        Message = "";

    else

        Message = "<span style='color:red;font-weight:bold;'> * </span>";

 

    // *** Fix up message so ' are allowed

    Message = Message.Replace("'",@"\'");

 

    if (this.UseClientScriptHtmlInjection && Item.ControlInstance != null)

    {

        if (!this._ClientScriptInjectionScriptAdded)

            this.AddScriptForAddHtmlAfterControl();

 

        this.Page.ClientScript.RegisterStartupScript(this.GetType(), Item.ControlId,

            string.Format("AddHtmlAfterControl('{0}','{1}');\r\n", Item.ControlInstance.ClientID, Message) ,true);

 

        Message = "";  // No message to embed

    }

 

    return Message;

}

 

private void AddScriptForAddHtmlAfterControl()

{

    this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "AddHtmlAfterControl",

@"function AddHtmlAfterControl(ControlId,HtmlMarkup)

{

var Ctl = document.getElementById(ControlId);

if (Ctl == null)

return;

 

var Insert = document.createElement('span');

Insert.innerHTML = HtmlMarkup;

 

var Sibling = Ctl.nextSibling;

if (Sibling != null)

Ctl.parentNode.insertBefore(Insert,Sibling);

else

Ctl.parentNode.appendChild(Insert);

}", true);

 

}

 

And now I can have reliable insertion of these messages. The AddScriptForAddHtmlAfterControl() function is fairly generic and can be plugged into any page, but you have to be careful that the HtmlMarkup encodes single quotes with \' to avoid conflicts with the parameter that is passed as a string literal.

 

In my case I only need after control insertion. I suppose it’d be a good idea to add before control insertion with a parameter as well, but for this specific control the above works fine.

 

This isn’t a pretty solution but it works. I now have an ASP.NET script code independent solution. The only problem now might be that a user has JavaScript off. In that case the icons won’t show up but the error message still displays the binding error information and jump points.


The Voices of Reason


 

Carl Olson
June 20, 2006

# re: Working around Controls.Add() and &amp;lt;%= %&amp;gt; Script Limitations with JavaScript

I ran into this a couple of years ago with MM.NET but never was able to convince any one of the problem. Thought I was losing my mind! Thanks for clearing this up.

Rick Strahl
June 20, 2006

# re: Working around Controls.Add() and &amp;lt;%= %&amp;gt; Script Limitations with JavaScript

Aeh, yes I built that code in MM.NET, so it makes sense you'd run into that problem <g>. It's interesting that I didn't run into this sooner as I use script markup to some degree in many of my pages especially in client script (to do those <%= Control.ClientID %> references.

I don't quite understand all the limitations on why this has to be although I've had a discussion about this with a couple of Microsoft folks on several occasions, but apparently there are some limitations that can't be worked around.

So this workaround using script code is a decent alternative for generic code. Since we're heading down the path of more JavaScript code rather than less I suppose this is a valid workaround.

Mads Mayntz Kierulff
June 21, 2006

# re: Working around Controls.Add() and &amp;lt;%= %&amp;gt; Script Limitations with JavaScript

Hey Rick. This is good stuff, just what I needed. Thanks! By the way you got some nice articles here!

Rick Strahl
October 04, 2006

# Adding Rendering with SetRenderMethodDelegate() - aeh, sort of - Rick Strahl

I ran into SetRenderMethodDelegate today and was thinking it'd be very useful for injecting HTML into a control. Unfortunately it turns out that the delegate is not fired on all controls as there's a dependency on base.Render() calls which apparently are not made by all controls even the stock controls.

# DotNetSlackers: Working around Controls.Add() and &lt;%= %&gt; Script Limitations with JavaScript


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024