Including JavaScript at bottom of page, from Partial Views - c#

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?

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).

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:
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:
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:
Gradually build script objects onto your request from within any page, partial view, or template, probably leveraging some kind of HtmlHelper extension for reusability.
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()

Related

Repeating HTML section in Razor Pages

I have a block of HTML I need to render in multiple places on a page, and I'm looking for a way to only define that HTML once. I can't rely simply on a loop because the HTML appears in different areas.
I know I can use a partial view. But since the block of HTML will only be displayed one one page, I'd prefer to define it there.
I know I can create a #functions block to create a function to render the markup, but this is geared towards code and not markup. I'd like something more like #helper functions in MVC, but those don't appear to be available in Razor Pages.
Can anyone offer other suggestions for defining a block of HTML in one place so it can be shown anywhere on the page?
If you are working with .NET Core 3, you can include HTML tags in methods declared in an #functions block e.g.
#functions{
void Greeter()
{
<h3>Hello World</h3>
}
}
Then in the content part of the page:
#{ Greeter(); }
The kind of helper can also take parameters:
void Greeter(string greeting)
{
<div>#greeting World</div>
}
#{ Greeter("Hello"); }
If you are working with ASP.NET Core 2.x, your "helper" method is a Func<someType, IHtmlString>. In the following example, the someType is a string:
Func<string, IHtmlContent> Greeter = #<h1>Hello #item</h1>;
Then in the content part of the page:
#Greeter("World");
someType can be a complex type:
Func<Contact, IHtmlContent> Greeter = #<h1>Hello #item.FirstName</h1>;
Template tag can help:
<template id="block-template">
<div>
<p>template contents...</p>
</div>
</template>
<div id="target1"></div>
<script>
var tmplt = $("#block-template").html();
$("#target1").append(tmplt);
</script>
the template tag is available in HTML5, you can also use script template :
<script id="block-template" type="text/template">
<div>
<p>template contents...</p>
</div>
</template>
there is a lot of plugins to use if you need to bind data to the template :
http://handlebarsjs.com/
https://github.com/jcgregorio/stamp/
https://knockoutjs.com/documentation/template-binding.html
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template

The following sections have been defined but have not been rendered for the layout page “~/Views/Shared/_Layout.cshtml”

I've this ActionResult:
[EncryptedActionParameter]
[CheckExternalUserRegisterSigned]
public ActionResult ExpedienteIIT(int idExpediente)
{
ExpedienteContainerVM model = this.GetExpedienteVMByIdExpediente(idExpediente);
return View("ExpedienteIIT", model);
}
ExpedientIIT View:
https://jsfiddle.net/pq16Lr4q/
_Layout.cshtml:
https://jsfiddle.net/1ksvav43/
So when I return the view I got this error:
I tried to put console.logs to see if the view is rendered but is not rendered...
Ok the error is here:
#model PortalSOCI.WEB.ViewModels.IIT.ExpedienteContainerVM
#{
ViewBag.Title = String.Format(PortalSOCI.WEB.Resources.ExpedienteIIT.TituloExpedienteIIT, Model.Expediente.NumeroExpediente);
}
#section JavaScript /// <-------------------- ERROR
{
#Html.Raw(ViewBag.message)
#
Can you please help me.
edit:
After reading your code, i feel like
#RenderSection("scripts", required: false)
should be
#RenderSection("JavaScript", required: false)
an other thing that I think will give you trouble is the fact that you define your "JavaScript" section in the body. This means that if any of your views you forget to add that
#section JavaScript
{
#Html.Raw(ViewBag.message)
}
you'll get a Section JavaScript not defined error. In your case, feels like the section's definition should be in the _layout.cshtml.
This error most likely means that you have defined the JavaScript section but have not rendered it anywhere.
You need to call #RenderSection("JavaScript") somewhere in your layout.cshtml
the
#section JavaScript
{
}
will let you create a section called "JavaScript", but to actually "print" the content of this section to the output HTML file (that will be sent to the client) you need to call #RenderSection("JavaScript"). The content of the section will be printed where the call to RenderSection is located.
You need to put the missing section inside your ExpedienteIIT view. According to the error message, that missing section is JavaScript.
Code sample, put this at the bottom of your view:
#section JavaScript
{
// put javascript here
}
EDIT:
Thank you for providing a code sample of your views. There is a mismatch between how the JavaScript section in your layout page is defined and how it is being included in your view.
To fix this, do either one of the following:
In your _Layout page, change #RenderSection("scripts", required: false) to #RenderSection("JavaScript", required: false), OR
In your ExpedienteIIT view, change #section JavaScript to #section scripts
The important thing is that the two should match.

How ASP.NET MVC _layout variables work?

When I create a new ASP.NET 4.5 web application MVC in Visual Studio, it starts with a introduction template.
The relavant parts are:
Views/Home/About.cshtml
#{
ViewBag.Title = "About";
}
<h2>#ViewBag.Title.</h2>
...
Views/Shared/_Layout.cshtml
<!DOCTYPE html>
<html>
<head>
<title>#ViewBag.Title - My ASP.NET Application</title>
...
#RenderBody()
...
Views/_ViewStart.cshtml
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
If I understand right, and assuming the About page is opened in the browser:
The _ViewStart.cshtml is called first, and it says there is a layout _Layout.cshtml to be called.
It renders the _Layout.cshtml, until the call #RenderBody() is reached, on that point he will render the About.cshtml. When that ends, he will render the rest of the _Layout.cshtml.
But here is my doubt, if the _Layout.cshtml starts first, how does it print on the <title> the variable #ViewBag.Title, which is assigned only in About.cshtml?
When you hit a URL an Action is called on a controller. The view is a result of this, so you don't call views directly (my guess is your coming from a webforms background where you call an aspx page, MVC uses a different model that doesn't rely on physical files). The action then specifies which view to render (and passes it the model). This view then specifies a layout to use when rendering the view.
So the control mechanism is inverted compared to what your used to.
URL (via routing) specifies a controller and an action -> the action says render me using this view -> the view then says render me using this layout. So the hierachy is:
Controller
Action
View
Layout
So to answer your specific questions:
But here is my doubt, if the _Layout.cshtml starts first, how does it
print on the <title> the variable #ViewBag.Title, which is assigned
only in About.cshtml?
The layout isn't called first, the view specifies a layout that should be used to render itself.
It doesn't. The View is rendered before the layout.

How to share information between partial views in JS

Two partials loaded on the same page
For example, if I hit a button in one partial then I want some value to appear in the other partial.
I want all the work to be done on the client side. I am sure I would call a method in js but I am not sure how to connect it to another js var on another partial within the same page. In other words how do I get both the partials to talk to eachother on the client side.
Once your razor view is rendered to the browser, It is just HTML markup. That means, you can use javascript to access the elements in the DOM and update the values as needed.
Keep your script in the main view which holds the 2 partial views.
$(function(){
$("#ButtonInFirstParial").click(function(e){
e.preventDefault();
$("#DivInSecondPartial").html("Updated");
});
});
Also if you declare your javascript variable in a global scope, you can access it in other places also. So if you have a variable like this in your layout page,
<body>
#RenderBody()
<script type="text/javascript">
var global_SiteUrl="Some value i want to access in all pages";
</script>
</body>
You can access it in other views (which uses the above one as the Layout), or js files which are a part of other views who has the layout value set as the above layout.

MVC3 Layout Page, View, RenderPartial and getting script files into the Header (from the partial view)

So I have a Layout page
<head>
#RenderSection("HeaderLast", required: false)
</head>
A view
#section HeaderLast
{
<script src="#Url.Content("~/Scripts/knockout-1.2.0.js")"
type="text/javascript"></script>
}
<div id="profile-tab">
#{ Html.RenderPartial("_userProfile"); }
</div>
And a Partial view
#section HeaderLast
{
<script type="text/javascript">
alert('test');
</script>
}
<div......
I figured it couldn't be that simple. Is there a proper way to do this out of box or will this always require some kind of mediator and passing stuff around ViewData to manually make the content bubble up to the layout page?
Bounty started: The bounty will be rewarded to the best solution provided for this short coming. Should no answers be provided I will award it to #SLaks for originally answering this question.
You cannot define sections in partial views.
Instead, you can put the Javascript in ViewBag, then emit any Javascript found in ViewBag in the layout page.
#JasCav: If a partial needs its own CSS, it has no good way to get it rendered.
If that's the reason for its use, it could very well be by design.
You don't want to have a separate CSS file x partial/helper. Remember, each separate CSS file means a separate request to get it from the server, thus an additional round-trip that affects time to render your page.
Also you don't want to emit direct CSS to the HTML from the partial/helper. Instead you want it to have appropriate hooks you can use to define all the look in your site's CSS file.
You can use the same hooks you have available for CSS to activate custom JavaScript behaviors for the elements involved When JavaScript is enabled.
Finally it may be the case what you need is not a Partial View, but an extra Layout you use for some pages. With that approach you would have:
A master Layout that gets set automatically on _ViewStart like you probably has now. This defines the sections like in your sample.
A children Layout page. Here you have both the extra html, css, js you need to have for these views. This uses both #RenderBody() and #section SomeSection { } to structure your common extra layout.
Some views that point to the children layout, and others that use the default master layout.
How to get extra data to the children Layout is out of the scope of the question, but you have several options. Like having a common base for your entities; using ViewBag or calling Html.RenderAction to get that shared logic related to shared dynamic elements in the layout.
It looks like there was a similar question on SO - How to render JavaScript into MasterLayout section from partial view?.
Unfortunately, there is no possibility of declaring sections inside Partial Views. That is because RenderPartial ends up rendering totally separate view page. There is a workaround to this, though a bit ugly. But it can look better if using strongly-typed model instead of ViewData.
Basically, you need to keep track of the reference to the view which called RenderPartial and use the DefineSection method on the object passed to push data to that view.
UPDATE: There is also a blog post about dealing with RenderSection you may find useful.
Here is another approach using helper methods and templated delegate
http://blogs.msdn.com/b/marcinon/archive/2010/12/15/razor-nested-layouts-and-redefined-sections.aspx
As a follow up to my question, the JavaScript/CSS combiner/minifier tool Cassette supports this functionality to allow you to compartmentalize your JavaScript and other assets that are required for partials.
I purchased a site license and use this in all of my MVC applications now.

Categories

Resources