Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Including JavaScript at bottom of page, from Partial Views

Let's say I have a javascript slide-show in a partial view...

_Slideshow.cshtml:

@{
    ViewBag.Title = "Slide Show";
}
<div id="slides">
</div>
<script src="js/slides.min.jquery.js"></script>
<script type="text/javascript">
$(function(){
    $('#slides').slides({
        // slide show configuration...
    });
});
</script>

But I want to be a good little web developer, and make sure all of my scripts go at the bottom of the page. So I'll make my *_Layout.cshtml* page look like this:

_Layout.cshtml:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/css/global.css")" rel="stylesheet" type="text/css" />
</head>
<body>
    <div id="wrapper">
    @RenderBody
    </div>
    <!-- Being a good little web developer, I include my scripts at the BOTTOM, yay!
    -->
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js" type="text/javascript"></script>
</body>
</html>

But UH OH! What do I do now, because my slide show script ends up above my jQuery inclusion?! It wouldn't be a big deal if I wanted my slide show on every page, but I only want the slide show partial view to be rendered on a certain page, and.... I WANT MY SCRIPTS AT THE BOTTOM! What to do?

like image 617
Didaxis Avatar asked Nov 17 '11 20:11

Didaxis


2 Answers

You could define a section in your layout page for the scripts like this:

<body>
    <div id="wrapper">
    @RenderBody
    </div>
    <!-- Being a good little web developer, I include my scripts at the BOTTOM, yay!
    -->
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js" type="text/javascript"></script>
    @RenderSection("myScripts")
</body>

Then on your pages you define what goes in that section:

@{
    ViewBag.Title = "Slide Show";
}
<div id="slides">
</div>
@section myScripts { //put your scripts here
    <script src="js/slides.min.jquery.js"></script>
    <script type="text/javascript">
    $(function(){
        $('#slides').slides({
            // slide show configuration...
        });
    });
    </script>
}

Then when the page renders, it will take everything in your section and add it to where it is supposed to go on your layout page (in this case, at the bottom).

like image 55
Becuzz Avatar answered Sep 17 '22 11:09

Becuzz


Note: The accepted solution won't work for partial views as the question asks for.

The Problem

In the normal flow, you can define the contents for a particular section from inside of the parent view on your ActionResult using a @section SectionName {} declaration. And when that view is finally inserted into its LayoutPage, it can call RenderSection to place those contents anywhere within the page, allowing you to define some inline JavaScript that can be rendered and parsed at the bottom of the page after any core libraries that it depends on like this:

Layout > Full View

The problem arises when you want to be able to reuse the full page view inside of a partial view. Perhaps you'd like to also re-use the view as a widget or dialog from inside of another page. In which case, the full Partial View is rendered in its entirety wherever you've placed the call to @Html.EditorFor or @Html.Partial inside of the Parent View like this:

Layout > Parent View > Partial View

According to the MSDN Docs on Layouts with Razor Syntax:

  • Sections defined in a view are available only in its immediate layout page.
  • Sections cannot be referenced from partials, view components, or other parts of the view system.
  • The body and all sections in a content page must all be rendered by the layout page

In that scenario, it becomes tricky to get the script defined into the partial view to the bottom of the page. Per the docs, you can only call RenderSection from the layout view and you cannot define the @section contents from inside of a partial view, so everything gets lumped into the same area and your script will be rendered, parsed, and run from the middle of your HTML page, instead of at the bottom, after any libraries it might depend on.

The Solution

For a full discussion of the many ways to inject sections from partial views into your page, I'd start with the following two questions on StackOverflow:

  • Injecting content into specific sections from a partial view with Razor View Engine
  • Using sections in Editor/Display templates

The varying solutions therein differ on support for nesting, ordering, multiple script support, different content types, calling syntax, and reusability. But however you slice it, pretty much any solution will have to accomplish two basic tasks:

  1. Gradually build script objects onto your request from within any page, partial view, or template, probably leveraging some kind of HtmlHelper extension for reusability.
  2. Render that script object onto your layout page. Since the layout page actually renders last, this is simply emitting the object we've been building onto the master page.

Here's a simple implementation by Darin Dimitrov

Add the Helper Extension Methods which will allow you to build arbitrary script objects into the ViewContent.HttpContext.Items collection and subsequently fetch and render them later.

Utilities.cs

public static class HtmlExtensions
{
    public static MvcHtmlString Script(this HtmlHelper htmlHelper, Func<object, HelperResult> template)
    {
        htmlHelper.ViewContext.HttpContext.Items["_script_" + Guid.NewGuid()] = template;
        return MvcHtmlString.Empty;
    }

    public static IHtmlString RenderScripts(this HtmlHelper htmlHelper)
    {
        foreach (object key in htmlHelper.ViewContext.HttpContext.Items.Keys)
        {
            if (key.ToString().StartsWith("_script_"))
            {
                var template = htmlHelper.ViewContext.HttpContext.Items[key] as Func<object, HelperResult>;
                if (template != null)
                {
                    htmlHelper.ViewContext.Writer.Write(template(null));
                }
            }
        }
        return MvcHtmlString.Empty;
    }
}

Then you can use like this within your application

Build this script objects like this inside of your Partial View like this:

@Html.Script(
    @<script>
         $(function() {
             $("#@Html.IdFor(model => model.FirstName)").change(function() {
                 alert("New value is '" + this.value + "'");
             });
         })
     </script>
)

And then render them anywhere within your LayoutPage like this:

@Scripts.Render("~/bundles/jquery")
@RenderSection("scripts", required: false)
@Html.RenderScripts()
like image 44
KyleMit Avatar answered Sep 21 '22 11:09

KyleMit