Repeating HTML section in Razor Pages - c#

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

Related

jquery function, make call in aspx razor

I would like to call a jquery function in razor, but i can't do it, why?
ClientScript.RegisterStartupScript(Page, GetType(), "success", "alert("");", true);
It says that it's not defined...
ClientScript.RegisterStartupScript() is for web forms. If you want to call a JavaScript function in a Razor template, embed the call in the template.
There are many different ways to accomplish this. In this example, the view model contains a flag that is used to conditionally output a block of script to the client.
#model MyModel
<div>
Some code in the view...doesn't matter
</div>
#if( MyModel.ShowSuccessAlert ){
<script>
alert("Success!");
</script>
}

MVC: Dynamically create new element from template

I have an MVC app. Some elements are rather complex, so I created a template for them.
I use them calling #Html.EditorFor(). The problem is that I need a possibility to create such elements dynamically, I mean after clicking on a button 'Create New' I want to generate an empty template and let user to fill it. I can use mustache template engine or smth like this, but in this case I need to duplicate my html - in razor template and in html. I don't want to repeat myself, what is the best way to achieve this?
Another problem is that when I generate view for IEnumerable<> - razor created proper names for elements with proper indexes. In a case I want to create new element - how should I set these indexes to let binder properly work on POST? Is there is a better solution than using jQuery for this?
Thanx in advance.
UPDATE:
Here is an editor template:
#model FakeViewModel
<li>
<div>
<h3><span>#Model.Title</span><span class="icon-remove"></span><span class="icon-move"></span></h3>
<div>
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(mp => mp.Category)
<div>
<span class="font-small">Title</span>
</div>
<div>#Html.TextBoxFor(m => m.Title, new { #maxlength = FakeViewModel.MaxTitleLength, #class = "title-textbox" })</div>
<div>
#Html.TextAreaFor(m => m.Description, new { #placeholder = "short description", #data_max_length = "90" })
</div>
</div>
</div>
</li>
This is how I render it, "Special" is a type of IEnumerable<FakeViewModel>
<ul class="container" id="special">
#Html.EditorFor(m => m.Special)
</ul>
So, after rendering this I have got a layout with correct names, I mean Special[0].Id, Speacial[0].Category, etc.
Now, I want to create an empty template. For now I am using mustache template, it should have the same layout as editor template:
<script type="text/template">
<input name="Special[{{itemIndex}}].Category"
</script>
The problem is, that I use the same layout in two different places - in mustache template as well as in razor editor template.
If I need to change this layout - I need to change it in two places. I want to avoid this.
Another problem is that I need hardcode names ("Speacial" in this example) and manually put index, category and etc.
Whatever solution you use, JavaScript will be required. jQuery is just a JavaScript framework, so you could switch it out with something else, if you preferred, but you should stick with some sort or JavaScript framework, as the code for doing XMLHttpRequest cross-browser is tedious and prone to error.
As for your HTML, you can choose to either include it as a template on page load or request it fresh via an AJAX request. Personally, I'd go with a template because AJAX doesn't really buy you anything in this scenario, and it's just an extra request.
Either way, you're unfortunately not going to be able to use the Razor HtmlHelper methods, at least without mangling the HTML afterwards via JavaScript anyways; they simply won't generate the proper naming convention required for binding list objects outside of a for loop. The model-binding convention is easy to replicate manually, though, it's just:
YourCollection[N].FieldName
So if you had a list of Cars on your model and you wanted a field to edit the Color of the 3rd car, you would use:
Cars[2].Color
Any good JavaScript templating solution should be able to automatically insert the N value based on the object's position in the list of other objects.
You can use a string builder to dynamically create the html in the get method of your controller and then post that to the view. The other way is to create your own htmlhelper.
Partialview is the solution:
you can create a partial view and have it added every time you want it to be used or injected into the view. you can run a for or foreach look on that partial view and have it repeat as many times as you want.
1: create a partialview.
2: put everything you want to be rendered 2 times in the partial view.
3: replace the view code with partial view. like:
#html.Partial("~/views/partials/_samplename.cshtml")
you can repeat this partial view anywhere in the project and by changing anything in the partialview it would change everywhere.

Change the html layout generated by EditorForModel without having 2 views

I render my ViewModel with
Create.cshtml:
#using (Html.BeginForm("Create","TestTemplate")) {
#Html.EditorForModel()
}
I do not like the output content because all the divs are one below the other. What if I want a horizontal layout where one div floats lefthandside to the other div?
How can I modify such layout changes using the EditorForModel() helper when there is no html in my Create.cshtml view ?
UPDATE:
Why do I have to create TWO views. In the one I call #Html.EditorForModel() in the other EditorTemplate-view I do all the layout stuff which I could also do within the first view via EditorFor( x => x....) ??? This seems strange to me.
You want to create an EditorTemplate. Basically create a new folder called EditorTemplates in ~/Views/Shared and then create a new view within that folder that has the name of your model, e.g. Template.cshtml or whatever.
When you call #Html.EditorForModel() it will use this view as the template.
The question is pretty old, but to my eyes there is another approach that should be mentioned. Although MVC is not MVVM try to think a little more in that pattern. Since the generated HTML is nothing more than the content of a page it will be used like a view model in the browser. It is not the job of HTML to Format your page. Try to modify the layout of your page by using CSS which will be used to render the view in the browser.
Just use a CSS selectors like the following to change the layout of your form, the input elements or just a specific input element:
form { your CSS here }
form input { your CSS here }
form input[name="NameOfInput"] { your CSS here }
In these days the power of CSS is nearly unlimited: Let your items flow left, right or alternating and so on ;-) You won't need to create two views either.

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?
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()

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