Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a clean pattern for keeping all the JavaScript in the bottom of my page?

Tags:

c#

asp.net

razor

We have a nested layout for our various pages. For example:

Master.cshtml

<!DOCTYPE html>
<html>
   <head>...</head>
   <body>@RenderBody()<body>
</html>

Question.cshtml

<div>
  ... lot of stuff ...
  @Html.Partial("Voting", Model.Votes)
</div>
<script type="text/javascript">
  ... some javascript ..
</script>

Voting.cshtml

<div>
  ... lot of stuff ...
</div>
<script type="text/javascript">
  ... some javascript ..
</script>

This all works fine, but I would like to push all of the JavaScript blocks to be rendered in the footer of the page, after all the content.

Is there a way I can define a magic directive in nested partials that can cause the various script tags to render in-order at the bottom of the page?

For example, could I create a magic helper that captures all the js blocks and then get the top level layout to render it:

Voting.cshtml

<div>
  ... lot of stuff ...
</div>
@appendJSToFooter{
   <script type="text/javascript">
     ... some javascript ..
   </script>
}
like image 781
Sam Saffron Avatar asked Feb 15 '12 00:02

Sam Saffron


2 Answers

I came up with a relatively simple solution to this problem about a year ago by creating a helper to register scripts in the ViewContext.TempData. My initial implementation (which I am in the process of rethinking) just outputs links to the various referenced scripts. Not perfect but here's a walk-through of my current implementation.

On a partial I register the associated script file by name:

@Html.RegisterScript("BnjMatchyMatchy")

On the main page I then call a method to iterate the registered scripts:

@Html.RenderRegisteredScripts()

This is the current helper:

public static class JavaScriptHelper
{
    private const string JAVASCRIPTKEY = "js";

    public static void RegisterScript(this HtmlHelper helper, string script)
    {
        var jScripts = helper.ViewContext.TempData[JAVASCRIPTKEY]
            as IList<string>; // TODO should probably be an IOrderedEnumerable

        if (jScripts == null)
        {
            jScripts = new List<string>();
        }

        if (!jScripts.Contains(script))
        {
            jScripts.Add(script);
        }

        helper.ViewContext.TempData[JAVASCRIPTKEY] = jScripts;
    }

    public static MvcHtmlString RenderRegisteredScripts(this HtmlHelper helper)
    {
        var jScripts = helper.ViewContext.TempData[JAVASCRIPTKEY]
            as IEnumerable<string>;

        var result = String.Empty;

        if (jScripts != null)
        {
            var root = UrlHelper.GenerateContentUrl("~/scripts/partials/",
                    helper.ViewContext.HttpContext);

            result = jScripts.Aggregate("", (acc, fileName) =>
                String.Format("<script src=\"{0}{1}.js\" " +
                    "type=\"text/javascript\"></script>\r\n", root, fileName));
        }

        return MvcHtmlString.Create(result);
    }
}

As indicated by my TODO (I should get around to that) you could easily modify this to use an IOrderedEnumerable to guarantee order.

As I said not perfect and outputting a bunch of script src tags certainly creates some issues. I've been lurking as your discussion about the jQuery Tax has played out with Steve Souders, Stack Overflow, Twitter and your blog. At any rate, its inspired me to rework this helper to read the contents of the script files and then dump them to the rendered page in their own script tags rather than link tags. That change should help speed up page rendering.

like image 172
ahsteele Avatar answered Sep 27 '22 20:09

ahsteele


Can you define a section at the bottom of the body element in your Master.cshtml file as follows:

@RenderSection("Footer", required: false)

Then in the individual .cshtml files you can use the following:

@section Footer {
    <script type="text/javascript">
    ...
    </script>
}
like image 32
sazh Avatar answered Sep 27 '22 20:09

sazh