Click here to Skip to main content
Email Password   helpLost your password?

Notes:

Introduction

When you start writing WebControls or even UserControls in ASP.NET, you will quickly discover the need to register pieces of JavaScript with the container page.

Doing so is reasonably easy, using the API included as part of the Page object, specifically:

IsClientScriptBlockRegistered Has a given piece of ClientScript been registered?
RegisterClientScriptBlock Register a piece of ClientScript
IsStartupScriptRegistered Has a given piece of StartupScript been registered?
RegisterStartupScript Register a piece of StartupScript.
RegisterArrayDeclaration Register an element in a global array which can be accessed by scripts.
RegisterHiddenField Register a hidden field for use within JavaScript (useful for postback data).
RegisterOnSubmitStatement Registers some JavaScript to be executed in the onsubmit event of the form.

The intent of this article is not to cover these individually or in detail, that may be done in another article.

This article and the downloadable class libraries are intended to address specific problems common to RegisterClientScriptBlock, RegisterStartupScript and RegisterOnSubmitStatement and to offer a remarkably simple solution.

As an overall theme, the problems involve the age-old development issues of maintenance vs. efficiency. This article attempts to maximize both in an environment that encourages neither.

Problem 1: String Handling

Each of the above methods accepts a piece of JavaScript code in string format as an argument. But building a string using the ASP.NET string object can be a horrible waste of resources. Consider the three best options:

  1. Simply concatenating strings using the + operator or even String.Concat will create a new string for each concatenation, using massive resources when building an entire slice of JavaScript code.
  2. String.Format or StringBuilder.AppendFormat can be powerful and more efficient, but tend to make JavaScript code within C# (or indeed VB.NET) less readable. It also poses a problem with opening/closing code blocks ("{" / "}") against formatting place-markers ("{0}").
  3. Repeated calls to StringBuilder.Append would be the most efficient option, but that can lead to painfully unmanageable code.

Take this simple piece of JavaScript, for example (we will refer back to this throughout the article):

<script language="javascript">
<!--
function MyWebControl_AlertText ()
{
    alert("MyWebControl");
}
// -->

</script>

All this script would do is throw up a message box containing the name of the WebControl. It would be registered by the control, to avoid multiple occurrences, but the function name and literal text must be overridable by inheritors, in case they decide to change the script.

So to create this code using option 1 (string + string), you would need to write the following code:

string script = "<script language="\""javascript\">" + Environment.NewLine
    + "<!--" + Environment.NewLine
    + "function " + FunctionName + " ()" + Environment.NewLine
    + "{" + Environment.NewLine
    + "\talert(\" + ControlName + ");" + Environment.NewLine
    + "}" + Environment.NewLine
    + "// -->" + Environment.NewLine

    + "</script>";

This is all quite readable, but the CLR is going to create as many as 19 string objects, which are all going to sit around until the Garbage Collector gets around to picking them up. On a busy website, this could be a serious problem.

Let's have a look at option 2 (Formatting strings via StringBuilder).

StringBuilder sb = new StringBuilder();
sb.AppendFormat("<script language="\""javascript\">{0}", Environment.NewLine);
sb.AppendFormat("<!--{0}", Environment.NewLine);
sb.AppendFormat("function {0} (){1}", FunctionName, Environment.NewLine);
sb.Append("{");
sb.Append(Environment.NewLine);
sb.AppendFormat("\talert(\"{0}\");{1}", ControlName, Environment.NewLine);
sb.Append("}");
sb.Append(Environment.NewLine);
sb.AppendFormat("// -->{0}", Environment.NewLine);
sb.Append("</script>");
string script = sb.ToString();

Formatting strings this way is not a bad option, it is certainly a fair compromise between the other two. It is still readable, though it was nicer when strings were placed where they should be; it is certainly more efficient than concatenating strings, but it's not as efficient as repeated calls to Append.

So let's take a look at that third and final option:

StringBuilder sb = new StringBuilder();
sb.Append("<script language="\""javascript\">");
sb.Append(Environment.NewLine);
sb.Append("<!--");
sb.Append(Environment.NewLine);
sb.Append("function ");
sb.Append(FunctionName);
sb.Append(" ()");
sb.Append(Environment.NewLine);
sb.Append("{");
sb.Append(Environment.NewLine);
sb.Append("\talert(\"");
sb.Append(ControlName);
sb.Append("\");");
sb.Append(Environment.NewLine);
sb.Append("}");
sb.Append(Environment.NewLine);
sb.Append("// -->");
sb.Append(Environment.NewLine);
sb.Append("</script>");
string script = sb.ToString();

This is definitely the ideal option for a marketable server control, using StringBuilder to maximum efficiency, but just imagine for a moment coming back to a complex piece of JavaScript, built like this, some six months after you've written it. This is not a pretty image.

Problem 2: Differing Styles

Of all the above styles, I would almost certainly choose option 2. You might opt for another one entirely, for reasons that are very acceptable but do not fit my way of thinking.

Likewise, you may choose not to use Environment.NewLine, preferring \r\n or even just \n (most browsers don't really care) for its simplicity and efficiency. Personally, I would rather stick nails in my head than deal with streams of escape codes. Even the tabs I would use for indenting can be sore on the eyes after a while.

There are many other ways the above pieces of code could be improved or degraded, depending on your point of view. Again, it comes down to maintenance vs. efficiency.

Every decision you make is going to affect anyone picking up your code later (and in a work environment, this will almost certainly happen, no matter how much we all like to pretend it will not). The effect could be good or bad and at the time you are developing your control, you cannot possibly know.

Problem 3: Comments and Whitespace

JavaScript development always leaves you with a predicament when it comes to whitespace and inline comments.

Both are a waste of bandwidth and will rarely even be seen. Sometimes you may not even want the end-user to see the comments you have used in your JavaScript, or even read the code without an immense amount of effort.

On the other hand, if you choose not to use them then reading your own code can be a daunting prospect.

All of this is particularly pertinent when you are hoping to sell a server control to a web space service provider which may then be reused by dozens of amateur web developers.

A Simple Solution to Three Problems

What we really need is a class that encapsulates the StringBuilder, for the sake of efficiency, but allows us different options designed more specifically for a script-writing environment.

We also need a class that limits our options and makes it clear to any developer (even one unfamiliar with the class), what we are aiming to achieve. If we can duplicate the best features of all our earlier options then that would remove the need for different styles of JavaScript building.

Finally, we need a class with which we can easily switch from highly manageable code to highly efficient code (as we all commonly do when compiling DEBUG and RELEASE versions of software).

If we can implement just a tiny bit of debugging into that class, because JavaScript is so hard to debug, wouldn't that be great?

And so, enter the JavaScriptBuilder class.

Step 1: Create the Class

using System;
using System.Text;

namespace CP.WebControls
{
    public class JavaScriptBuilder
    {
        private StringBuilder sb = new StringBuilder();
        private int currIndent = 0;
        private int openBlocks = 0;
        private bool format = false;

        public JavaScriptBuilder()
        {
        }

        public JavaScriptBuilder(bool Formatted)
        {
            format = Formatted;
        }

// Script handling code here


    }
}

There is nothing unusual about the basic structure of the class.

Apart from System, the only .NET Framework namespace we need is System.Text, for the StringBuilder class.

We need a StringBuilder object, created when the JavaScriptBuilder is instantiated. The rest of the private members handle the formatting, and whether there is going to be any. This latter question must be answered when we create the object because it is used throughout, the rest is handled as we go along.

Step 2: Add Lines and Retrieve the Result

public void AddLine(params string[] parts)
{
    // Append parts of the line to StringBuilder individually

    // - much more efficient than sb.AppendFormat

    foreach (string part in parts)
        sb.Append(part);

    // Add a new line

    sb.Append(Environment.NewLine);
}
public override string ToString()
{
    // Add the <script> tags and some comment blocks, so that

    // browsers that don't support scripts will not crash horribly

    return String.Format(
        "<script language="\""javascript\">{0}<!--{0}{1}{0}// -->{0}</script>", 
        Environment.NewLine, 
        sb
    );
}

The only trick here is to learn as many lessons from our first problem definition as possible.

What we truly want is the power and readability of string concatenation, combined with the efficiency of StringBuilder.Append(). Using C#'s params keyword we can do just that.

We can also add the open and close sequence for every piece of JavaScript in the ToString() function, because they should be the same in every block of script we register.

These two bits of code alone allow us to use the following code to produce our initial simple script block:

JavaScriptBuilder jsb = new JavaScriptBuilder(true);
jsb.AddLine("function ", FunctionName, " ()");

jsb.AddLine("{");
jsb.AddLine("\talert(\"", ControlName, "\");");
jsb.AddLine("}");

string script = jsb.ToString();

Already much simpler and more readable than any of our initial three options and we have not lost any efficiency at all over our best-case option.

Step 3: Track the Indentation

public int Indent
{
    get { return currIndent; }
    set { currIndent = value; }
}
public void OpenBlock()
{
    AddLine("{");
    currIndent++;
    openBlocks++;
}
public void CloseBlock()
{
    // Check that there is at least one block open

    if (openBlocks < 1)
        throw new InvalidOperationException(
            "JavaScriptBuilder.CloseBlock() called when no blocks open"
        );

    currIndent--;
    openBlocks--;
    AddLine("}");
}
public void AddLine(params string[] parts)
{
    // Open line with tabs

    for (int i=0; i < currIndent; i++)
        sb.Append("\t");

    // Append parts of the line to StringBuilder individually

    // - much more efficient than sb.AppendFormat

    foreach (string part in parts)
        sb.Append(part);

    // Add a new line

    sb.Append(Environment.NewLine);
}

This code allows the developer to open and close blocks using the JavaScriptBuilder class and not have to remember how many blocks are open at any given time to insert their own tabs. It also means that you do not have to deal with streams of escape codes at the beginning of each line, which for me was a significant benefit.

Another, not so obvious, advantage of this is that you can move a piece of code from inside a block to the outside (or vice versa) and not have to worry about adjusting the number of tabs on each line.

The more observant reader is probably asking themselves why we would need to keep track of both indentation and the number of open blocks. There are situations where you might want to indent a piece of code without opening a new block, for example if one line of code is stretched across a number of lines of text.

So we need to expose the Indent property to the calling program without breaking the validation line we used in CloseBlock(). We can also include a similar validation in ToString(), so that the calling program can only convert the script to a string once all code blocks have been closed. We do not, however, care if the calling program has left indents active, because it will not make the script fail.

Note that openBlocks is not exposed in any way, it is handled entirely internally.

Going back yet again to our earlier simple script, it can now be written as easily as:

JavaScriptBuilder jsb = new JavaScriptBuilder(true);
jsb.AddLine("function ", FunctionName, " ()");

jsb.OpenBlock();
jsb.AddLine("alert(\"", ControlName, "\");");
jsb.CloseBlock();

string script = jsb.ToString();

Step 4: Handle the Formatting Flag

public void AddLine(params string[] parts)
{
    // Open line with tabs, where formatting is set

    if (format)
        for (int i=0; i < currIndent; i++)
            sb.Append("\t");

    // Append parts of the line to StringBuilder individually

    // - much more efficient than sb.AppendFormat

    foreach (string part in parts)
        sb.Append(part);

    // Append a new line where formatting is set or a space

    // where it isn't

    if (format)
        sb.Append(Environment.NewLine);
    else
        if (parts.Length > 0)
            sb.Append(" ");
}
public void AddCommentLine(params string[] CommentText)
{
    if (format)
    {
        // Open the line with tab indent

        for (int i=0; i < currIndent; i++)
            sb.Append("\t");

        // ... and a comment marker

        sb.Append ("// ");

        // Append all the parts of the line

        foreach (string part in CommentText)
            sb.Append(part);

        // Throw in a new line

        sb.Append(Environment.NewLine);
    }
}

The changes to AddLine() mean that tabs and new line will only be added where the format flag is set. If the flag is not set then we simply separate each line of code with a single space. There is also a new method AddCommentLine() which allows us to add a comment only when the format flag is set.

To demonstrate, let's add a single comment to our simple script block:

JavaScriptBuilder jsb = new JavaScriptBuilder(true);
jsb.AddLine("function ", FunctionName, " ()");

jsb.OpenBlock();
jsb.AddCommentLine("A message box showing the Control name");
jsb.AddLine("alert(\"", ControlName, "\");");
jsb.CloseBlock();

string script = jsb.ToString();

Currently, this JavaScriptBuilder will produce the following script block:

<script language="javascript">
<!--
function MyWebControl_AlertText ()
{
    // A message box showing the Control name

    alert("MyWebControl");
}
// -->

</script>

But if you change true to false (or allow it to default) in the constructor, the script block is unformatted as follows:

<script language="javascript">
<!--
function MyWebControl_AlertText () { alert("MyWebControl"); }
// -->

</script>

This saves a few bytes of bandwidth on every page that uses your control. But with a simple one-word change to your code, you can return the script to its original state, should you need to edit or debug it.

One easy way of handling this is to write the constructor as follows:

#if DEBUG
JavaScriptBuilder jsb = new JavaScriptBuilder(true);
#else
JavaScriptBuilder jsb = new JavaScriptBuilder();
#endif

This then allows us to test the control using neat, readable JavaScript and, without a single line change in the code, create an efficient release version of the control.

Example: The ClickCounter Control

To show fully the power of the JavaScriptBuilder, we should create a control with a more complex script block.

The ClickCounter control will display some text along with a click counter. Each time the control is clicked, it will increment the counter and retain the initial text.

The code for this control can be downloaded at the top of the article.

Note: this could more easily be handled by using two <span> tags and only adjusting the contents of the second, but that would not suit the purposes of this article as the JavaScript would only be a couple of lines.

The control itself is simple enough, two ViewState-enabled properties (.Text and .InitialValue) with Render() overridden. I suspect that if you have read this far, you already know how to do that. This article is only of interest to Control Developers.

The script is registered as follows:

[
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
]
protected virtual string IncrementScriptName
{
    get
    {
        return "ClickCounter_IncrementValue";
    }
}

protected override void Render(HtmlTextWriter output)
{
    if (!Page.IsStartupScriptRegistered(IncrementScriptName))
        Page.RegisterStartupScript(IncrementScriptName, IncrementScript);

// standard rendering code here

}

The IncrementScriptName property is designed to allow inheritors to override the script name along with the script text and, where a page includes both types of controls, both scripts can be included in the generated page without overriding the more complicated Render() method.

Now take a look at the IncrementScript property code, which is the code more relevant to this article.

[
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
]
protected virtual string IncrementScript
{
    get
    {
#if DEBUG
        JavaScriptBuilder jsb = new JavaScriptBuilder(true);
#else
        JavaScriptBuilder jsb = new JavaScriptBuilder();
#endif
        jsb.AddCommentLine("Splits the inner text" + 
                         " into Text (txt) and Counter (c) parts,");
        jsb.AddCommentLine("increments (c) and" + 
                         " joins them back together again.");
        jsb.AddLine("function ", IncrementScriptName, "(elmt)");

        jsb.OpenBlock(); // function (elmt)

        jsb.AddCommentLine("Initialize variables");
        jsb.AddLine("var inner = elmt.innerText;");
        jsb.AddLine("var c = 0;");
        jsb.AddLine("var txt = \"\";");

        jsb.AddLine();

        jsb.AddCommentLine("Run through inner text string");
        jsb.AddLine("for (idx = 0; idx < inner.length; idx++)");

        jsb.OpenBlock(); // for (idx...)

        jsb.AddCommentLine("Split string into text and counter parts");
        jsb.AddLine("c = parseInt( inner.substring (idx, inner.length + 1) );");
        jsb.AddLine("txt = inner.substring(0, idx);");

        jsb.AddLine();

        jsb.AddCommentLine("If we have a number, get out of the loop");
        jsb.AddLine("if ( ! isNaN( c ) )");

        jsb.OpenBlock(); // if ( ! isNaN( c ) )

        jsb.AddLine("break;");
        jsb.CloseBlock(); // if ( ! isNaN( c ) )


        jsb.CloseBlock(); // for (idx...)


        jsb.AddLine();
        jsb.AddCommentLine("Increment counter");
        jsb.AddLine("c++;");
        jsb.AddCommentLine("Rebuild the string and put it in the inner text"); 
        jsb.AddLine("elmt.innerText = txt + \" \" + c;");
        jsb.CloseBlock(); // function (elmt)


        return jsb.ToString();
    }
}

This generates the following piece of JavaScript when compiled in DEBUG mode:

<script language="javascript">
<!--
// Splits the inner text into Text (txt) and Counter (c) parts,

// increments (c) and joins them back together again.

function ClickCounter_IncrementValue(elmt)
{
    // Initialize variables

    var inner = elmt.innerText;
    var c = 0;
    var txt = "";
    
    // Run through inner text string

    for (idx = 0; idx < inner.length; idx++)
    {
        // Split string into text and counter parts

        c = parseInt( inner.substring (idx, inner.length + 1) );
        txt = inner.substring(0, idx);

        // If we have a number, get out of the loop

        if ( ! isNaN( c ) )
        {
            break;
        }
    }
    
    // Increment counter

    c++;
    // Rebuild the string and put it in the inner text

    elmt.innerText = txt + " " + c;
}
// -->

</script>

This is quite readable within the control itself and very readable in the generated page. Meanwhile it is achieved with all the efficiency of repeated calls to StringBuilder.Append() and without a single escape character or Environment.NewLine in sight.

However, it does generate a lot of unnecessary code for the end user. They will rarely look at the code and again, if 1000 pages include your control and are accessed 1000 times a day, that's 1Mb of bandwidth per extra byte of code every day.

The release version of the control will have a much shorter piece of code:

<script language="javascript">
<!--
function ClickCounter_IncrementValue(elmt) { var inner = elmt.innerText; var c = 
0; var txt = ""; for (idx = 0; idx < inner.length; idx++) { c = parseInt( 
inner.substring (idx, inner.length + 1) ); txt = inner.substring(0, idx); if ( 
! isNaN( c ) ) { break; } } c++; elmt.innerText = txt + " " + c; }
// -->

</script>

Totally unreadable, but the functionality has not changed. This new version is considerably shorter with just under 400 bytes saved. That totals as much as 400Mb of bandwidth on your 1000 pages accessed 1000 times a day. Imagine the effect on a really complicated piece of JavaScript.

Summary

JavaScriptBuilder can be included in your code as a .cs (C#) class file or linked in from a very small (16Kb) DLL. It can be used to create JavaScript code that is readable in a generated page and easily maintained in the control's source, but without any loss of efficiency in terms of either server memory or bandwidth.

Forget about choosing between maintenance and efficiency, choose both.

Note: As with anything available on CodeProject, it is free to use, but it would be nice to hear from you if you decide to use it. If you have any ideas for improvements, I would like to hear that too.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralQuestion
chris175
15:03 2 Nov '06  
I came across the same problem. How can I make long and slightly complex javascript functions that are needed by custom controls that are EASY to MODIFY and MAINTAIN.

I found it easy to make a .js file and store it as an embedded resource with in the custom control assembly. Then you can do one of two things. You can either take the code found within the .js file and write it directly to the page (using one of the functions you mentioned) OR write the file to a directory that web page can access and just write a javascript reference to the javascript file that contains the functions.

I took the second approach and have the custom control actually copy the .js file to a special directory (MyCustomControl1 directory) and just add a reference on the web page that needs to use it.

Why I didn't copy the function to the web page? I ran into some problems doing it this way which forced me to copy the .js files to a directory.

I know what you are think. "All that file coping for every mycustomcontrol1...What a terrible method." To prevent this I used a check sums class on the file within the assembly and the file in the directory. This seems to work pretty well.

Any Comments?

Chris
GeneralTwo little bugs
DeKale
12:07 6 Dec '05  
The first bug I found is a little bug when adding strings that are partly comments. Like the the next code snippet:
var a = 3; // this is a variable
var b = 4;

When adding 'var a = 3; // this is a variable' and 'var b = 4;' things go wrong when formatting is off. The lines are concatenated, resulting that 'var b = 4;' becomes part of the comment.

To resolve this problem the next code from the AddLine(params string[] parts) method should be changed.

Old code:
foreach (string part in parts)
sb.Append(part);

if (format)
sb.Append(Environment.NewLine);
else if (parts.Length > 0)
sb.Append(" ");

New code:
bool f = format;

foreach (string part in parts)
{
sb.Append(part);
if (part.IndexOf("//") != -1) f = true;
}

if (f)
sb.Append(Environment.NewLine);
else if (parts.Length > 0)
sb.Append(" ");


Next next bug is even smaller than the previous and maybe will never get you in trouble, but you should use the System.Globalization.CultureInfo.InvariantCulture when using String.Format, else your javascript can maybe be messed up, when running
in a different cultural setting.

I'm using your JavaScriptBuilder for months now and it's it has served me well! Keep up the good work!!!
GeneralAdding DEBUG to Constructor
St€v€n
7:25 19 May '05  
Why not put the #if DEBUG code in the first constructor?
Like this:
/// /// Instantiate a JavaScriptWriter for unformatted code
///
public JavaScriptBuilder()
{
#if DEBUG
format = true;
#else format = false;
#endif }

This saves you from having to write the #if DEBUG lines every time over and over.
GeneralRe: Adding DEBUG to Constructor
Paul Riley
10:48 19 May '05  
Originally, I designed it this way so I could have the JavaScriptBuilder in its own DLL - just one release version that could link into the control DLLs at will, and even allow me to use custom compilation constants (such that I could have a debug-able release version) and make simple changes to the JavaScriptBuilder without need to copy it around everywhere.

As it turned out, I didn't use it that way at all. Didn't make any changes, copied it around DLL to DLL, could have done exactly what you suggest Smile . Just goes to show, sometimes the simple solutions are the best.

Paul
GeneralJavaScriptBuilder as VB.NET port
Cello
6:10 16 Nov '03  
Hi Paul

Great work! Since I'm writing quite some custom controls at the moment I found your work very useful! I'm not a great C# programmer Sigh , that's why I stick to VB. So I needed your class in VB and did the port!
If your interested in putting it up into your article, I would be happy to send the vb file to you!

Greetings from Switzerland

Marcel Spring
Senior Software Architect
ADVIS AG
Switzerland
GeneralRe: JavaScriptBuilder as VB.NET port
Paul Riley
9:36 16 Nov '03  
Hi Marcel,

That would be great! I'm planning to update several of my articles over the next month or so and one thing I was considering doing was a VB port of this.

You'll be saving me some considerable effort if you've done that already.

Thanks a lot for the offer and I'm glad this class has been of use to you, even if you did have to translate it Big Grin .

Paul
GeneralRe: JavaScriptBuilder as VB.NET port
DJ Double K
3:55 20 Jan '05  
Hi Paul,

I did the same and added another two methods to your class, which I find helpful:

OpenFunction and CloseFunction Cool

If you would like me to send you my VB-Version too, just let me know.

Regards

Constantin
GeneralRe: JavaScriptBuilder as VB.NET port
Oakman
7:49 1 Mar '09  
I'd love to see it.

Jon
Smith & Wesson: The original point and click interface

Algoraphobia: An exaggerated fear of the outside world rooted in the belief that one might spontaneously combust due to global warming.

GeneralReturn values
Salil Pitkar
4:32 13 Oct '03  
Paul,

I would suggest the return values of all utility methods on your JavaScriptBuilder class should return the the current instance like StringBuilder class do for convenience.

It would be easy to just write...

JavaScriptBuilder jsb = new JavaScriptBuilder(true);
jsb.AddLine("function ", FunctionName, " ()")
.OpenBlock()
.AddCommentLine("A message box showing the Control name")
.AddLine("alert(\"", ControlName, "\");")
.CloseBlock();

GeneralRe: Return values
Paul Riley
4:46 13 Oct '03  
Good idea, Salil. I have a couple of other minor updates lined up for this article soon; I'll throw that in with it.

Thanks.

Paul
GeneralYet another alternative
panmanphil
4:27 5 Sep '03  
Though I like the idea of controlling the indents and blocks, in your arguments for why this class is a good idea, you missed the option I use quite a lot Smile

string script = @"
<script
     function xAdd(x, y) {
          return x + y;
     }
</script>";

You get to type your javascript exactly as you want it, there is only one string to gc and it's easy to pass around to other methods, would be easy to usein a locale like fashion for multiple user agents.


Philip Nelson
GeneralRe: Yet another alternative
Paul Riley
19:55 5 Sep '03  
panmanphil wrote: Though I like the idea of controlling the indents and blocks, in your arguments for why this class is a good idea, you missed the option I use quite a lot
You know... you really do learn something new every day. I didn't even know C# could do that (and I've used it enough that I'd expect to know by now Blush ).

To be honest though, it's not something I'd do if I could possibly avoid it. Strikes me as hideously easy to get wrong, especially if you were a maintenance developer picking up someone else's work. Copy one line to the wrong place (ie. a line from outside the quote to inside) and it could be days before you figure out what happened. Eek!

If anything this demonstrates my point about differing styles - might work fine for you, but I would screw it up in a heartbeat Laugh

Copy/Cut/Paste: the developer's equivalent to a two-edged sword.

But you are right, I hadn't considered that option.

Paul
GeneralAnother alternative
Eric Woodruff
17:34 3 Sep '03  
Nice idea, but instead of writing code that generates code, you could embed the script file in the control assembly as a resource. That way, the script is separate, easily maintained, and if you use an IHttpHandler the script can also be made cacheable on the client side thus further reducing bandwidth usage. It also takes a lot less code to generate a link to the script than writing out the code verbatim thus speeding up rendering a bit and reduces the overall page size. It also works for non-script resources like image files.

With apologies for seeming to put in a plug for my own articles, see http://www.codeproject.com/useritems/ressrvpage.asp[^] for an example and if you want script compression, see http://www.codeproject.com/useritems/JSCompress.asp[^]. I've been using both in my own controls quite effectively.

Eric



GeneralRe: Another alternative
Paul Riley
2:10 4 Sep '03  
I see your point, Eric.

I've only had a quick 10 minutes to scan your article, I promise to take a closer look later. But a couple of questions spring to mind:

1) What if you want to insert a property into the middle of your code to make it easy for inheritors (or even to emit different javascript depending on the js level of the browser)? I have at least one example where that is fundamental to the design.

2) Am I right in thinking that your javascript is being linked in via an external ASPX file? If so, what are the advantages over simply <script src=...> (as WebUIValidation.js, which I believe also only loads once)

As I say, I haven't read properly, I may have missed the point entirely Blush

Paul
We all will feed the worms and trees
So don't be shy
- Queens of the Stone Age, Mosquito Song

GeneralRe: Another alternative
Kiliman
13:18 4 Sep '03  
I'll answer your second question first.

Paul Riley wrote: 2) Am I right in thinking that your javascript is being linked in via an external ASPX file? If so, what are the advantages over simply <script src=...> (as WebUIValidation.js, which I believe also only loads once)
Because he stores the Javascript file as a resource, he needs to execute .NET code to get it. He simply created a custom HttpHandler to load his resource. Just referencing the Javascript file would make IIS send the file directly and you wouldn't be able to dynamically process it.

Which brings me to your first question.

Paul Riley wrote: 1) What if you want to insert a property into the middle of your code to make it easy for inheritors (or even to emit different javascript depending on the js level of the browser)? I have at least one example where that is fundamental to the design.
In his handler, he implements ProcessRequest and this is where he decides how to handle the specific resource. You could look at the HttpBrowserCapabilities class to decide which version of the script file to load.

You could do simple search and replace to customize your script here as well.

Anyway, both ways seem acceptable. If you have a lot of Javascript though, you may want to add it as a resource instead of hard-coding it.

Kiliman

GeneralRe: Another alternative
Paul Riley
19:26 5 Sep '03  
Kiliman,

I think you may have missed the tone of my message slightly. I wasn't trying to attack Eric's way of handling scripts, I was trying to establish whether my way was even worth holding onto. This article was actually designed to explain something I'm doing in an upcoming article, to save me distracting the reader from the real point of the main article.

If I was going to implement Eric's method instead, there would be very little reason for this article except maybe to offer an easier-to-understand way (in which case I would have changed the article to reflect that).

However, I've come to the conclusion that there are other justifications for both methods to be considered in any given case (certainly for the example I'm talking about, you'd have a hard time justifying search and replace), so I'm comfortable enough leaving this as-is. Smile

Paul
GeneralRe: Another alternative
Kiliman
5:38 6 Sep '03  
No problem. I was simply answering the questions he had. I think everyone agrees there are alternative solutions. It's great that we can share and discuss them.

Kiliman


GeneralRe: Another alternative
Eric Woodruff
16:59 4 Sep '03  
> What if you want to insert a property into the middle of your code...

As noted in the reply by Kiliman, because it's a resource, you could modify it after retrieval and before sending it back by doing string or regexp replacements or load a different script altogether based on browser settings or query string options passed to the handler. If you want to let inheritors somehow alter the script in their derived class, that could be a problem depending on what it is you need to do. However, in cases like that, you could still store the basic script as a resource and at some point prior to rendering, retrieve it, modify it, and register it with the page in the normal way.

> Am I right in thinking that your javascript is being linked in via
> an external ASPX file?

The script tag ends up looking like the following:

<script type='text/javascript' src='EWSoftware.Web.aspx?Res=SetFocus.js'></script>

The page doesn't really exist. A setting is added to Web.config that redirects the reference to the aspx page to the resource handler class in the assembly. It looks at the query string to get the resource and figure out what to send back and how. It gives you the benefits of a linked script without the problems of having to distribute and install it separately, versioning the folders, etc.

Eric



GeneralRe: Another alternative
Paul Riley
19:42 5 Sep '03  
Hi Eric

Eric Woodruff wrote: As noted in the reply by Kiliman, because it's a resource, you could modify it after retrieval and before sending it back by doing string or regexp replacements or load a different script altogether based on browser settings or query string options passed to the handler.
And as I've responded - that's a fair point but there will be cases where this just isn't good enough.

Replace again uses up more memory than you might guess, especially if your script (and particularly the bit you are replacing) is long. And I'd certainly avoid using Regex anywhere that it wasn't entirely necessary if you're looking for efficiency Smile .

Eric Woodruff wrote: The script tag ends up looking like the following:

<script type='text/javascript' src='EWSoftware.Web.aspx?Res=SetFocus.js'></script>

Interesting. I see where you're coming from now (and may well be inclined to use this method at times). However, if your code is pretty static and held outside of the main source, there's surely a good argument for the Microsoft recommended approach of keeping a JS file in aspnet_client where you can version things nicely, etc.

I suppose it depends on your target market.

But as I said to Kiliman, I wasn't trying to attack your way of thinking. Quite the opposite, I was wondering if I could completely scrap my own methodology. I don't think I can, but I will be adding your technique to the list of things I consider for each control and I'm glad you added a link to your article from my own - give people the choice, I say.

Paul
GeneralRe: Another alternative
Eric Woodruff
13:25 7 Sep '03  
No problem or misunderstanding here, I didn't see it as an attack. Smile
I'm always open to discuss the reasons for and against the different approaches. It helps clarify when they may or may not be the best way to do something.

Eric

GeneralThanks to you all...
Anonymous
16:02 19 Nov '03  
Trying to learn about about data driven web applications, I incidentally saw Paul's article.

Although it's something different than what I was searching I should tell you that I learnt a lot from the article, your comments and I thank you not only for that, but for the positive way you conducted the discussion.

Best greetings,
nice2cu
GeneralRe: Another alternative
Simone Busoli
5:19 3 Feb '06  
Hi Eric, I find your articles very useful; I am using the resource handler tecnique in most of my projects, and I thank you for making me know that great thing that are HttpModules.

Anyway I find Paul's helper class useful as well, because sometimes it's easier to write client code using server code, especially when you have to inject it with server side variables values.

Anyway, as you suggested, you could even do it in the reshandler way and modify the script after retrieval. In such cases, I suggest not to use regex or strings, but a template engine like NVelocity, about which I've recently written a brief tutorial here on Codeproject. Here is the link: http://www.codeproject.com/useritems/nvelocityaspnet.asp[^]

Simone Busoli
GeneralRe: Another alternative
RFM
1:43 16 Nov '04  
To address #1, I created a Template class which allows any "tags" - {{TAGNAME}} - to be replaced using one of the following calls:

Template template = new Template( resource )
template .SetTemplateItem( tagName, value )
template .SetTemplateFromObject( object )

Response.Write( template );

The SetTemplateFromObject uses reflection to replace all tags with corresponding property values in Object. The tag replacement uses regular expressions. The template constructor allows templates to be read from compiled resources or files. The approach allow you to maintain complex JavaScript in it's native format yet substitute parameters dynamically. The cost of reading the resource or file can easily be reduced by caching.


Last Updated 3 Sep 2003 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010