My main motivation for trying to do this is to get Javascript that is only required by a partial at the bottom of the page with the rest of the Javascript and not in the mid
The more elegant way to do this is to move partial view scripts into separate file and then render it in Scripts section of view:
<h2>This is the view</h2>
@Html.RenderPartial("_Partial")
@section Scripts
{
@Html.RenderPartial("_PartialScripts")
<script type="text/javascript">
alert("I'm a view script.");
</script>
}
The partial view _Partial.cshtml:
<p>This is the partial.</p>
The partial view _PartialScripts.cshtml with scripts only:
<script type="text/javascript">
alert("I'm a partial script!");
</script>
Base on the answer from Mr Bell And Shimmy above, I add on extra function for Bundle script.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Text;
using System.Web.Mvc;
namespace ABC.Utility
{
public static class PartialViewHelper
{
public static string RequireScript(this HtmlHelper html, string path, int priority = 1)
{
var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as List<ResourceInclude>;
if (requiredScripts == null) HttpContext.Current.Items["RequiredScripts"] = requiredScripts = new List<ResourceInclude>();
if (!requiredScripts.Any(i => i.Path == path)) requiredScripts.Add(new ResourceInclude() { Path = path, Priority = priority });
return null;
}
public static string RequireBundleStyles(this HtmlHelper html, string bundleName)
{
var a = System.Web.Optimization.Styles.Render(bundleName);
var requiredStyles = HttpContext.Current.Items["RequiredStyles"] as IHtmlString;
if (requiredStyles == null) HttpContext.Current.Items["RequiredStyles"] = requiredStyles = a;
return null;
}
public static string RequireBundleScripts(this HtmlHelper html, string bundleName)
{
var a=System.Web.Optimization.Scripts.Render(bundleName);
var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as IHtmlString;
if (requiredScripts == null) HttpContext.Current.Items["RequiredScripts"] = requiredScripts = a;
return null;
}
public static HtmlString EmitRequiredBundleStyles(this HtmlHelper html)
{
var requiredStyles = HttpContext.Current.Items["RequiredStyles"] as IHtmlString;
if (requiredStyles == null) return null;
return MvcHtmlString.Create(requiredStyles.ToHtmlString()) ;
}
public static HtmlString EmitRequiredBundleScripts(this HtmlHelper html)
{
var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as IHtmlString;
if (requiredScripts == null) return null;
return MvcHtmlString.Create(requiredScripts.ToHtmlString());
}
public static HtmlString EmitRequiredScripts(this HtmlHelper html)
{
var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as List<ResourceInclude>;
if (requiredScripts == null) return null;
StringBuilder sb = new StringBuilder();
foreach (var item in requiredScripts.OrderByDescending(i => i.Priority))
{
sb.AppendFormat("<script src=\"{0}\" type=\"text/javascript\"></script>\n", item.Path);
}
return new HtmlString(sb.ToString());
}
public class ResourceInclude
{
public string Path { get; set; }
public int Priority { get; set; }
}
}//end class
}// end namespace
Sample on PartialView :- @Html.RequireBundleStyles("~/bundles/fileupload/bootstrap/BasicPlusUI/css"); @Html.RequireBundleScripts("~/bundles/fileupload/bootstrap/BasicPlusUI/js");
Sample on MasterPage :- @Html.EmitRequiredBundleStyles()
Use @using(Html.Delayed()){ ...your content... }
extensions from answer https://stackoverflow.com/a/18790222/1037948 to render any content (scripts or just HTML) later in the page. Internal Queue
should ensure correct ordering.
The way I dealt with this is to write a couple extension methods to the HtmlHelper class. That allows partials views to say that they require a script, and then in the layout view that writes the tag I call to my helper method to emit the required scripts
Here are the helper methods:
public static string RequireScript(this HtmlHelper html, string path, int priority = 1)
{
var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as List<ResourceInclude>;
if (requiredScripts == null) HttpContext.Current.Items["RequiredScripts"] = requiredScripts = new List<ResourceInclude>();
if (!requiredScripts.Any(i => i.Path == path)) requiredScripts.Add(new ResourceInclude() { Path = path, Priority = priority });
return null;
}
public static HtmlString EmitRequiredScripts(this HtmlHelper html)
{
var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as List<ResourceInclude>;
if (requiredScripts == null) return null;
StringBuilder sb = new StringBuilder();
foreach (var item in requiredScripts.OrderByDescending(i => i.Priority))
{
sb.AppendFormat("<script src=\"{0}\" type=\"text/javascript\"></script>\n", item.Path);
}
return new HtmlString(sb.ToString());
}
public class ResourceInclude
{
public string Path { get; set; }
public int Priority { get; set; }
}
Once you have that in place your partial view just needs to call @Html.RequireScript("/Path/To/Script")
.
And in the layout view's head section you call @Html.EmitRequiredScripts()
.
An added bonus of this is that it allows you to weed out duplicate script requests. If you have multiple views/partial views that need a given script you can safely assume that you will only output it once
This functionality is also implemented in ClientDependency.Core.Mvc.dll. It provides the html helpers: @Html.RequiresJs and @Html.RenderJsHere(). Nuget package: ClientDependency-Mvc
You could have a second partial that is only in charge of injecting the necessary javascript. Place several scripts in there around @if
blocks, if you want:
@model string
@if(Model == "bla") {
<script type="text/javascript">...</script>
}
@else if(Model == "bli") {
<script type="text/javascript">...</script>
}
This could obviously be cleaned up a bit, but then, in the Scripts
section of your view:
@section Scripts
{
@Html.Partial("_Scripts", "ScriptName_For_Partial1")
}
Again, it might not win a beauty prize but it will work.