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>
}
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.
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>
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With