I am setting up an architecture for an MVC6 app, and I'm relying heavily on ViewComponents. My goal is to let each ViewComponent have its own javascript section, but from reading here rendersection does not work with ViewComponents so I've been trying to do it in another way.
In my _Layout.cshtml
I have this part in just before the closing of the body tag:
@{Html.RenderPartial("_LayoutScriptsPartial"); }
In _LayoutScriptsPartial
<environment names="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/js/app.js" asp-append-version="true"></script>
</environment>
<environment names="Staging,Production">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js">
</script>
<script src="~/js/app.min.js" asp-append-version="true"></script>
</environment>
Now in one of my components at /Views/Shared/Components/MyComponent/Default.cshtml I reference another partial view that has this content, it's for a carousel ViewComponent
@model MyProj.MyViewModel
@{Html.RenderPartial("_LayoutScriptsPartial"); }
@if (!string.IsNullOrEmpty(Model.ImageUrl))
{
<script type="text/javascript">
var cssClass= "@Model.cssClass";
var imageUrl = "@Model.ImageUrl";
webbCore.carouselSetBackgroundImage(cssClass, imageUrl);
</script>
}
Only reason I had to do this was to have all required js files available for my view component. As you can see, I reference _LayoutScriptsPartial multiple times. When I debug using chromes f12 and watching the network section, I do not se the same javascript files being downloaded multiple times. Still I have a bad feeling about this solution. I have looked around and have not found any good solutions for working with js files and ViewComponents that I really liked. Something like this would suit my needs.
My question: how good is this solution, whats the pros and cons and are there any better ways to work with js files and ViewComponents?
There are 3 main cases for an ideal solution to your problem:
So in your example, jquery and app.js are your libraries while your dynamically created initialization JavaScript is the part that references @Model in your <script type="text/javascript">
tag. Let's say we added your component to a view 3 times (I'll replace @RenderBody() with the resulting html from a view that invokes your component 3 times):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - MyProj.Web</title>
<environment names="Development,Staging,Production">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/lib/font-awesome/css/font-awesome.css" />
<link rel="stylesheet" href="~/css/site.css" />
<link rel="stylesheet" href="~/css/culture-flags.css" />
</environment>
</head>
<body>
<header>@Html.Partial("~/Views/Shared/_Header.cshtml")</header>
<main>
<nav></nav>
<article>
<div class="container body-content">
<!--@RenderBody()-->
<!--@await Component.InvokeAsync("MyComponent", new MyViewModel {cssClass="t1", ImageUrl="1.jpg"})-->
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/js/app.js" asp-append-version="true"></script>
<script type="text/javascript">
var cssClass= "t1";
var imageUrl = "1.jpg";
webbCore.carouselSetBackgroundImage(cssClass, imageUrl);
</script>
<!--@await Component.InvokeAsync("MyComponent", new MyViewModel {cssClass="t2", ImageUrl="2.jpg"}) -->
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/js/app.js" asp-append-version="true"></script>
<script type="text/javascript">
var cssClass= "t2";
var imageUrl = "2.jpg";
webbCore.carouselSetBackgroundImage(cssClass, imageUrl);
</script>
<!--@await Component.InvokeAsync("MyComponent", new MyViewModel {cssClass="t3", ImageUrl="3.jpg"}) -->
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/js/app.js" asp-append-version="true"></script>
<script type="text/javascript">
var cssClass= "t3";
var imageUrl = "t3.jpg";
webbCore.carouselSetBackgroundImage(cssClass, imageUrl);
</script>
</div>
</article>
<aside></aside>
</main>
<footer>@Html.Partial("~/Views/Shared/_Footer.cshtml")</footer>
<!-- These are the libraries that should only be loaded once -->
<environment names="Development,Staging,Production">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
</environment>
<!-- Ideally this is where the dynamically create scripts would go -->
@RenderSection("scripts", required: false)
</body>
</html>
As you can see, you would be loading the jquery library 4 times, once for your Layout and 3 times for your ViewComponents. Any good browser should only download the file once but will be loaded into memory multiple times and will just overwrite the the same global variables multiple times($ for example).
You might also be tempted to move the library to the top of the Layout and remove the references from the View Component but that is not a best practice either.
The main issue is that section
doesn't work with ViewComponents and that appears to be by design. You should think of a ViewComponent as a fancy html helper. I haven't seen any great solutions to this problem but here are a couple of ideas.
$(".carousel").each(function() {
var css = $(this).data("carousel-css");
var image = $(this).data("carousel-image");
});
<input class="carousel" type=hidden data-carousel-css="@Model.cssClass" data-carousel-image="@Model.imageURL" />