Creating Custom ASP.NET Server Controls with Embedded JavaScript

I did some consulting work recently for a company that had a lot of JavaScript embedded in pages that was used used to perform advanced client-side functionality and make AJAX calls back to the server.  The company needed additional team members to be able to contribute to the application without spending a lot of time learning client-side Web technologies.  One solution was to provide good documentation of the JavaScript objects and methods that could be called.  This still required some fundamental knowledge of JavaScript though.  The focus, however, seemed to be on getting other team members involved with learning C# and server-side technologies so that they could also build back-end code tiers rather than having everyone spend time learning JavaScript and other related client-side technologies such as CSS and DHTML/DOM.

After seeing the existing application and hearing about the challenges the team was facing, I provided a demo of how custom ASP.NET server controls could have JavaScript embedded in them fairly easily.  By going this route the people that knew JavaScript could still leverage their existing skills, but they could wrap the client-side functionality in a server control that other developers could more easily consume and use without having to be client-side gurus.  Going this route also allowed properties that scripts may utilitize to be exposed along with documentation of what the properties do.

The following steps show how to accomplish this type of JavaScript encapsulation.  The steps extend ASP.NET's standard GridView control and customize it by adding additional client-side functionality.  I'll admit upfront that this is a fairly simple example designed only as a starting point.  However, the concepts can be applied to more advanced cases.  I'm using the exact same principles in version 2.5 of my OrgChart.NET server control (due out soon).

Step 1: Choose how to create your custom control.  You have two main choices when creating custom server controls.  You can choose to write a control from scratch, or you can extend an existing control provided by ASP.NET or a 3rd party vendor.  Let's say that you'd like to create a "fancy" grid control that highlights rows as a user mouses over them and allows users to select one or more rows and highlight them.  All of this is done on the client-side without any postback operations.  While you could write the grid functionality from scratch, why not leverage what Microsoft has already done if it gets you to the desired end result faster and satisfies project requirements?  This is easily done by creating a new Class Library project in VS.NET 2005 and deriving the custom control class from GridView as shown next:

[ToolboxData(@"<{0}:CustomGridView runat=""server"" \>")]
public class CustomGridView : GridView
{

}

The ToolboxData attribute defines what markup code should be added to an ASP.NET page as a developer drags the control onto a page's design surface.

Step 2.  Write the client-side code.  This step could certainly be performed later, but I like to write the client-side enhancements upfront.  In this case, if we want end users to be able to see highlighted rows as they mouse over them, or select rows by clicking them, we can use code similar to the following. 

function MouseEnter(row,bgColor,textColor)
{
    
if (row.getAttribute("Selected"== "true"return;
    
row.style.backgroundColor bgColor;
    
row.style.color textColor;
}

function MouseLeave(row)
{
    
if (row.getAttribute("Selected"== "true"return;
    
row.style.backgroundColor row.getAttribute("OriginalColor");
    
row.style.color row.getAttribute("OriginalTextColor");
}

function MouseDown(row,bgColor,textColor)
{
    
if (row.getAttribute("Selected")=="true")
    { 
        row.style.backgroundColor 
row.getAttribute("OriginalColor");
        
row.style.color row.getAttribute("OriginalTextColor");
        
row.setAttribute("Selected","false");
    
}
    
else
    
{
        row.style.backgroundColor 
bgColor;
        
row.style.color textColor
        row.setAttribute(
"Selected","true");
    
}
}

Since we want to embed the JavaScript code directly into the control, add a new JavaScript file into the Class Library project and set its Build Action to "Embedded Resource".  This can be done by right-clicking on the .js file in the Solution Explorer, selecting Properties, and changing the Build Action property. 

Step 3.  Define the JavaScript file as a Web Resource.  There are two main ways to output JavaScript from a server-side control.  First, you can embed JavaScript in the control's code and then output it using the ClientScriptManager class's RegisterClientScriptBlock() method (or another custom technique).  This works, but makes the control's code less maintainable since JavaScript is embedded in VB.NET or C# code.  Also, when the JavaScript is output it is embedded in the page's HTML which isn't a good option when you want to leverage client-side caching of JavaScript files.

The second option (the better option in my opinion) is to use ASP.NET 2.0's WebResource.axd HttpHandler in conjunction with the ClientScriptManager class to dynamically output embedded resources such as JavaScript files.  By using WebResource.axd the custom control can output a <script src="..."></script> tag into the page that dynamically references the JavaScript resource on the server.  This allows the client browser to cache the script which should lead to faster page loads especially if the script is large.  By embedding the JavaScript file as a resource rather than putting it in the control's code, the overall code base is also much easier to maintain.

To allow the custom control to write out <script src="..."></script> tags, the target JavaScript file must first be added as a resource into the project as shown in the previous step.  After doing that, the following code should be added into the AssemblyInfo.cs file (this file is added automatically when you create a Class Library project). 

[assembly: WebResource("CustomControls.GridViewScript.js""text/javascript")]

This code uses the WebResourceAttribute class to give the embedded script a name and define the appropriate content type of the resource.  The name starts with the namespace of the project and ends with the name of the script that is embedded in the project as a resource.  This technique can also be used to embed images, CSS files or other types of resources. 

Step 4:  Render the script to the browser.  Once the WebResource attribute has been added into AssemblyInfo.cs, the following code can be added into the custom control to cause a <script src="..."></script> tag referencing the embedded script resource to be output to the page.  The call to the ClientScriptManager's RegisterClientScriptResource() method does all the work.  It takes the type of control being rendered as well as the resource name defined in AssemblyInfo.cs as parameters.

protected override void OnPreRender(EventArgs e)
{
    
base.OnPreRender(e);
    string 
resourceName "CustomControls.GridViewScript.js";

    
ClientScriptManager cs = this.Page.ClientScript;
    
cs.RegisterClientScriptResource(typeof(CustomControls.CustomGridView), resourceName);
}

Step 5:  Hook the custom control to JavaScript functions.  Now that the JavaScript is ready to be output, the custom control needs to be "hooked" to the client-side functions shown earlier in Step 2 in order to perform the desired behaviors.  The following code uses the GridView's RowCreated event to add client-side mouse over, mouse out and mouse down attributes to rows in the grid.  Adding these event handlers could also be done entirely on the client-side in cases where the size of the HTML markup sent from the server to the browser needs to be kept to a minimum.

protected override void OnRowCreated(GridViewRowEventArgs e)
{
    
base.OnRowCreated(e);
    
GridViewRow row e.Row;
    
Color bgColor Color.Empty;
    
Color color Color.Empty;

    if 
(row.RowIndex % == 0)
    {
        bgColor 
(this.RowStyle.BackColor == Color.Empty) ? Color.White : this.RowStyle.BackColor;
        
color (this.RowStyle.ForeColor == Color.Empty) ? Color.Black : this.RowStyle.ForeColor;
    
}
    
else
    
{
        bgColor 
(this.AlternatingRowStyle.BackColor == Color.Empty) ? Color.White : this.AlternatingRowStyle.BackColor;
        
color (this.AlternatingRowStyle.ForeColor == Color.Empty) ? Color.Black : this.AlternatingRowStyle.ForeColor;
    
}
    
if (row.RowType == DataControlRowType.DataRow)
    {
        row.Attributes.Add(
"onmouseover", String.Format("MouseEnter(this,'{0}','{1}')",
            ColorTranslator.ToHtml(
this.MouseOverColor),
            ColorTranslator.ToHtml(
this.TextOverColor)));
        
row.Attributes.Add("onmouseout""MouseLeave(this)");
        
row.Attributes.Add("onmousedown", String.Format("MouseDown(this,'{0}','{1}')",
            ColorTranslator.ToHtml(
this.SelectedRowColor),
            ColorTranslator.ToHtml(
this.SelectedRowTextColor)));
        
row.Attributes.Add("OriginalColor", ColorTranslator.ToHtml(bgColor));
        
row.Attributes.Add("OriginalTextColor", ColorTranslator.ToHtml(color));
    
}
}

The end result of following these steps is that JavaScript is dynamically output from the control in an efficient manner.  An example of what the ClientScriptManager outputs to reference the JavaScript resource is shown next (some characters were removed for the sake of brevity):

<script src="/ControlDemo/WebResource.axd?d=3SnAJwuH.......2&t=633134805717880000" type="text/javascript"></script>

The control is now self-contained and does not require any additional files to be deployed before it can be used.  It's also easy to maintain since the JavaScript is stored as an external resource rather than being embedded in VB.NET or C# code.  While this is a simple example, more complex scripts and code can be written to handle the needs of any Web application.  The complete code for the custom control discussed in this article can be downloaded here.

 

comments powered by Disqus

13 Comments

  • Dan, this is great information. This is a practical example (and derived from a real-world challenge) that really demonstrates the power of embedding resources. As always, thanks for the great explainations and source code!

  • Hi Dan,

    Thanks for posting this article, I'm trying to figure out better ways of incorporating Javascript into my applications and this really got me thinking.

    Two issues I ran into with your sample code: I needed to remove some stray characters to get it to compile and the Javascript effect doesn't work with Firefox.

  • No problem. I fixed the stray characters (not sure how those got in there when I zipped up the folders) and changed the onmouseenter and onmouseleave calls (which were there intentially since the company I was working with only used IE internally) to onmouseover and onmouseout so that FireFox can still play. The new download has the changes.

  • Dynamic scripts make it a little trickier. However, you can still add the embedded script through the technique shown here, but then dynamically output another piece of script that calls into the one you're embedding. Similar to what you're doing above, but instead have it call a well-known function in the embedded script and pass in the ClientID. That way you still get the dynamic code you need, but also get the benefits of cached script files that are embedded in the assembly and less messy to maintain.

  • Hi
    this article is really very helpfull.....but i m not able to call my javascript function it gives me an error object required.

  • Thanks!
    I took your code and rewrote it in VB, hoping to learn how to do this.
    After fighting a bit with the RegisterClientScriptResource type and namespaces for the resource, I got it working, and now am extending it my way.

    Is there a reason not to use CSS styles instead of the background colors?

    Thanks!

  • No problem, glad that helped.

    The JavaScript is actually using CSS style properties although I'm assuming you're referring to CSS classes. You could certainly tweak the code to do that...it would actually simplify things a bit. Having both options (the ability to set the colors directly and the ability to assign a CSS class) would be a good thing to have since some people may not want to go to the trouble to embed a CSS class somewhere in a page or external .css file.

  • Fantastic! - just what I was looking for. Need one enhancement and not sure the best way...

    Let's say the CustomGridView is wrapped in an ASP.NET AJAX UpdatePanel. When I mousedown, I get the client side selection and then on the server side, I set the color of the selected row so that it shows up on the partial postback. No probs.

    The issue is when I mousedown the second time, I am not clearing the previous selected row until I get the partial postback (i.e. set from the server side). How do I clear the the previously selected row from the client side?

    Thanks!

  • I'm guessing that there's a reason you need the partial-page postback to occur (such as tracking the currently highlighted row on the server-side to perform an action). If that's not actually needed I'd recommend just doing it client-side.

    Before the partial-page update occurs you'd need to write some JavaScript to perform the clearing of the previous row which means you'd need to track the currently highlighted row in a client-side variable or on the GridView as an HTML attribute used to track state. You could handle this with some script associated with the GridView, or use the PageRequestManager from ASP.NET AJAX to know when a partial-page update is occurring and then clear the previous row. You can read more about the PageRequestManager here and I have a video on my blog about using it as well.

    http://ajax.asp.net/docs/ClientReference/Sys.WebForms/PageRequestManagerClass/default.aspx

    It would probably be easier to handle it within the GridView if possible though.

  • This article has given me the new market for Custom Web Control, on basis of this article, i have started my business of creating and selling customize web control.

    Thanks a lot

  • You'll find an example of doing that with the control described in this post actually. You can call getAttribute() and setAttribute() to add data directly to a GridView on the client-side. That way you don't have to worry about having more than one GridView since each one tracks its own state. It's a nice little trick that avoids having to track state using JavaScript arrays that I use where possible.

  • Paresh,

    I'm glad you've decided to make a business out of controls and honored that my post helped you get there. Best of luck!

  • Hey thanks for the walkthrough. This made it alot easier for me to include the javascript that I wanted with my control. I do have one question Is there any way to have the link to the external javascript appear with the tags? I read some post about javascript not always working correctly when thay are outside the tags. Also I was wondering if there would be an easy way to add a comment /* javascript required to support (x) control */. I think that this would be very cool.

Comments have been disabled for this content.