How to determine if a RenderFragement has a content - razor

I've just built a very simple razor component for templating a page header. Now I've noticed that my condition regarding the ChildComponent != null is always true. Is there a way to check if the ChildContent has any real content defined?
In my example the PageTitleSecondaryRow will always be rendered into my page, even if ChildContent is empty (but it is not NULL). Which Options do I have. As a workaround I will now make an custom RenderFragment property which is null by initialization. But I do not think that this is the final solution.
<div class="wrapper border-bottom white-bg page-heading">
<div class="row" id="PageTitlePrimaryRow">
<div id="PageTitleIconColumn">
#(IconMarkup())
</div>
<div id="PageTitleTextColumn">
<h2>
#Title
</h2>
#if (Elements != null && Elements.Count > 0)
{
<TitleBreadcrumbs Elements="#Elements" />
}
</div>
<div id="PageTitlePostColumn">
</div>
</div>
#if (ChildContent != null )
{
<hr />
<div id="PageTitleSecondaryRow">
#ChildContent
</div>
}
</div>

As Peter Morris said, ChildContent is only null is you use the self-closing syntax <MyComponent />. This is because that is the default render fragment any content gets assigned to, even if it's empty content.
It might be advisable to use a different name for your RenderFragment. This way if it is null, you know it has not been explicitly set. An additional benefit is that more accurately conveys what the render fragment is about.
Consider you have the following component MyComponent.razor:
#if (ListItems != null)
{
<ul>
<li> default item </li>
#ListItems
</ul>
}
else
{
<p>alternative content</p>
}
#code {
[Parameter]
public RenderFragment ListItems { get; set; }
}
By having a different name for the RenderFragment, you have to explicity set it when using this component. This catches the unintended cases where ChildContent might result in empty html:
#* displays only the alternative content *#
<MyComponent></MyComponent>
#* displays the ul element *#
<MyComponent>
<ListItems>
<li> other item </li>
</ListItems>
</MyComponent>
It's basically the same, just a different name for the parameter. This prevents Blazor from assuming anything between the opening an closing belongs inside the ChildContent whenever you do not use the self-closing syntax.

Related

if else statement is not being treated as code inside #foreach - 2sxc v11 DNN 9.8

I'm currently learning 2sxc and am building a directory app as my initial project.
I'm trying to use the following code in a list view to change the way an item is displayed based on the Boolean "UpgradedListing"
#foreach(var listing in AsList(Data)) {
<div #Edit.TagToolbar(listing)>
if(listing.UpgradedListing == 'true'){
<strong>#listing.ListingName</strong<br/>
<a href='mailto:#listing.Email'>#listing.Email</a>
<hr/>
} else {
#listing.ListingName<br/>
<a href='mailto:#listing.Email'>#listing.Email</a>
<hr/>
}
</div>
}
the resulting output looks like this:
if(listing.UpgradedListing == 'true'){ Techmedics Ltd office#techmedics.co.nz
} else { Techmedics Ltd
office#techmedics.co.nz
}
if(listing.UpgradedListing == 'true'){ Solutions Online NZ Ltd enquiries#solutions-online.co.nz
} else { Solutions Online NZ Ltd
enquiries#solutions-online.co.nz
}
in other words the if else isn't being seen as code.
Can any one explain why this is?
You just need an # symbol in front of the first if, so
#if(listing.UpgradedListing == 'true'){
Also you've got a typo, your closing strong tag is missing its right >
And 'true' is not the same as true (boolean). 2sxc will know and return a boolean for .UpgradedListing (if you have it set AS a boolean field... if you have it as a string, then you need == "true"
and you can also move the stuff that doesn't change outside the if/else to make it more readable...
#foreach (var listing in AsList(Data))
{
// here you can still write C# without an #
// because it's still in code-mode
var x = 7; // this still works here
<div #Edit.TagToolbar(listing)>
<!-- here Razor switches to HTML because we had a Tag around it -->
<!-- so we need to really introduce the code-mode again -->
#if (listing.UpgradedListing)
{
<strong>#listing.ListingName</strong><br />
}
else
{
#listing.ListingName<br />
}
<a href='mailto:#listing.Email'>#listing.Email</a>
<hr />
</div>
}

What is the proper way to show different elements based on function return

So basically what I want is to show
'Something1' when the job is not processing
'Something2' when the job is running and status is '0'
'Something3' when the job is running but status is something else
I tried the following code snippet, but it looks like let-status in the outer template will never get assigned. Not sure whether the implementation is correct or not, could anyone give me two cents on how to make this logic work?
Thanks.
<span *ngIf="!isProcessing(); else elseBlock">
Something1
</span>
<ng-template #elseBlock let-status="queryPlaybackStatus()" *ngIf="queryStatus() === '0'; else innerElseBlock">
<span>
Something2
</span>
<ng-template #innerElseBlock>
<span>
Something3
</span>
</ng-template>
</ng-template>
I would suggest defining a string in your component, where you have much better control over your logic. In the component, set the string to the appropriate text.
Then bind to that string in the template.
I don't have all of your needed logic here, but something like this:
isImage = false;
get statusText(): string {
if (!isProcessing()) {
this.isImage = false;
return 'Something1';
} else {
this.isImage = true;
return 'path to image';
}
}
This uses a getter, which provides a way for a component property to have logic.
Then just bind to statusText in the template.
<span *ngIf='!isImage'>
{{statusText}}
</span>
<span *ngIf='isImage>
<img ...>
</span>
<span *ngIf="!isProcessing(); else elseBlock">
Something1
</span>
<ng-container #elseBlock *ngIf="queryStatus() as status">
<span *ngIf="status === '0'; else innerElseBlock">
Something2
</span>
<ng-template #innerElseBlock>
<span>
Something3_with_{{status}}%
</span>
</ng-template>
</ng-container>
So basically this need a magic combination of ng-container and ng-template.
It sounds to me like you want ngSwitch. This allows you to switch based on logic, which you should encapsulate in your component, not your template. First, let's create a property that encapsulates our logic in our component:
public get currentStep(): number {
if (!this.isProcessing) {
return 1;
} else if (this.queryStatus === 0) {
return 2;
} else {
return 3;
}
}
Next, let's bind our ngSwitch statement to this newly-created property:
<div [ngSwitch]="currentStep">
<div *ngSwitchCase="1">
<p>Something1</p>
<div>Put whatever you want in here! Images, etc.</div>
</div>
<div *ngSwitchCase="2">
<p>Something2</p>
<p>Loading....</p>
</div>
<div *ngSwitchCase="3">
<p>Something3</p>
<p>All done!</p>
</div>
</div>
That should get you where you need to go. Since this is simple, I created a stackblitz example that will demonstrate a working version of this. In the example, you can click a button and watch the app cycle through all the steps (I'm using setTimeout to simulate a long-running server query).

Umbraco AncestorOrSelf missing from Model?

I have a news list under which there are a load of News Items. I'm trying to get the page name of the news list to display on each news item but this code isn't cutting it. I get an error saying "Umbraco.Web.Models.RenderModel' does not contain a definition for 'AncestorOrSelf'"
I want this to use levels rather than nodeID so it's reuseble on other pages. This is what I've got so far:-
#inherits Umbraco.Web.Mvc.UmbracoTemplatePage
#{
Layout = "BasePage.cshtml";
var sectionTitle = Model.AncestorOrSelf(2).pageName;
}
<div id="contentHeader">
<div class="row contentHeader">
<div class="col-md-6 page-title no-left-pad">
<h1>#sectionTitle</h1>
</div>
<div class="col-md-6 no-right-pad">
Use our CareFinder
</div>
</div>
</div>
#RenderBody()
Any advice appreciated as I can't find any reason for the error anywhere.
Thanks
I think what you should be looking for is:
Model.Content.AncestorOrSelf(2).Name
Model returns a RenderModel object but what you want is the IPublishedContent object which you will find in the Model.Content property.
You should of course perform a null check before attempting to access the name e.g.
if(Model.Content.AncestorOrSelf(2) != null)
{
sectionTitle = Model.Content.AncestorOrSelf(2).Name;
}
Try using
Model.Content.AncestorOrSelf or Model.Content.AncestorsOrSelf
The available input variables are int (level) and string (NodeTypeAlias)

How to add a second css class with a conditional value in razor MVC 4

While Microsoft has created some automagic rendering of html attributes in razor MVC4, it took me quite some time to find out how to render a second css class on an element, based on a conditional razor expression. I would like to share it with you.
Based on a model property #Model.Details, I want to show or hide a list item. If there are details, a div should be shown, otherwise, it should be hidden. Using jQuery, all I need to do is add a class show or hide, respectively. For other purposes, I also want to add another class, "details". So, my mark-up should be:
<div class="details show">[Details]</div> or <div class="details hide">[Details]</div>
Below, I show some failed attempts (resulting mark-up assuming there are no details).
This: <div #(#Model.Details.Count > 0 ? "class=details show" : "class=details hide")>,
will render this: <div class="details" hide="">.
This: <div #(#Model.Details.Count > 0 ? "class=\"details show\"" : "class=\"details hide\"")>.
will render this: <div class=""details" hide"="">.
This: <div #(#Model.Details.Count > 0 ? "class='details show'" : "class='details hide'")>
will render this: <div class="'details" hide'="">.
None of these are correct mark-up.
I believe that there can still be and valid logic on views. But for this kind of things I agree with #BigMike, it is better placed on the model. Having said that the problem can be solved in three ways:
Your answer (assuming this works, I haven't tried this):
<div class="details #(#Model.Details.Count > 0 ? "show" : "hide")">
Second option:
#if (Model.Details.Count > 0) {
<div class="details show">
}
else {
<div class="details hide">
}
Third option:
<div class="#("details " + (Model.Details.Count>0 ? "show" : "hide"))">
This:
<div class="details #(Model.Details.Count > 0 ? "show" : "hide")">
will render this:
<div class="details hide">
and is the mark-up I want.
You can add property to your model as follows:
public string DetailsClass { get { return Details.Count > 0 ? "show" : "hide" } }
and then your view will be simpler and will contain no logic at all:
<div class="details #Model.DetailsClass"/>
This will work even with many classes and will not render class if it is null:
<div class="#Model.Class1 #Model.Class2"/>
with 2 not null properties will render:
<div class="class1 class2"/>
if class1 is null
<div class=" class2"/>
You can use String.Format function to add second class based on condition:
<div class="#String.Format("details {0}", Details.Count > 0 ? "show" : "hide")">
A more reusable approach inside your Razor code:
#functions {
public static string Displayed(int count)
{
return count > 0 ? "show" : "hide";
}
}
Using it:
<div class="#Displayed(Details.count)">Details content</div>
Which you can reuse anywhere in your code.

How to Get Model Data from Partial View?

I am creating a site in which I utilize partial views to display various bits of data about a single Model. Here is a bit of the HTML. (Note, all of these are contained within a single form and the Index page that these partials are rendered in is strongly typed to the main model. The main model contains various lists of data.)
<div id="tab1"><% Html.RenderPartial("Tab1", Model); %></div>
<div id="tab2"><% Html.RenderPartial("Tab2", Model.AnItemList1.FirstOrDefault<AnItemList1>()); %></div>
<div id="tab3"><% Html.RenderPartial("Tab3", Model.AnItemList2.FirstOrDefault()); %></div>
Here is ONE of the partial views headers (for 'tab2'):
<%# Language="C#" Inherits="System.Web.Mvc.ViewUserControl<AnItem1>" %>
The pages display correctly. The issue is that, when I enter data into the various parts of the partial pages and then submit the entire form (via POST), the data is not making it back to my data store (MSSQL) - but this only happens for any of the list items (that are contained within the Model). The first partial page does properly have its data set within the data store.
What am I doing wrong here? Should I only be passing the model to Html.RenderPartial and then get the specific model I need on the partial page? Should I pass the entire list and then get the first (right now, I only care about the first item in the list - that will EVENTUALLY change, but not any time soon).
Suggestions or thoughts appreciated.
Update: Here is how I accessing the properties on the partial views.
<div class="data-group">
<%: Html.CheckBoxFor(model => model.Property1) %>
<%: Html.LabelFor(model => model.Property1) %>
</div>
Update 2: Per request...
Controller Action (ScenarioController):
public ActionResult Index(int id = 0)
{
if (id == 0)
{
SavedScenario scenario = new SavedScenario();
scenario.AnItemList1.Add(new AnItem1());
scenario.AnItemList2.Add(new AnItem2());
return View("Index", scenario);
}
else
{
SavedScenario scenario = repository.GetScenario(id);
if (scenario == null)
return View("NotFound");
else
return View("Index", scenario);
}
}
[HttpPost]
public ActionResult Index(SavedScenario scenario)
{
if (ModelState.IsValid && TryUpdateModel(scenario, "SaveScenario"))
{
repository.Add(scenario);
repository.Save();
}
return View(scenario);
}
Rendered HTML (I can only include parts of it - this is a small sample of what is in the form):
<form action="/Scenario" id="form0" method="post">
<!-- This is the one that works - the basic Scenario. Top level. -->
<fieldset>
<legend>Scenario Information</legend>
<div class="data-group">
<div class="editor-label">
<label for="ScenarioName">Scenario Name</label>
</div>
<div class="option1">
<input class="wide" id="ScenarioName" name="ScenarioName" type="text" value="" />
</div>
<div class="validation">
<div><span class="field-validation-valid" id="ScenarioName_validationMessage"></span></div>
</div>
</div>
</fieldset>
<!-- This does not work or get submitted (as far as I can tell). -->
<div id="tab2">
<fieldset>
<legend>Tab2</legend>
<div class="data-group">
<input id="Property1" name="Property1" type="checkbox" value="true" /><input name="Property1" type="hidden" value="false" />
<label for="Property1" />
</div>
</div>
</fieldset>
</form>
My apologies for having to keep this so generic.
Hard to guess from this much code. However you should make sure that all properties of your models have the same prefix when they are posted back to the server
Edit: form field names should match property names of your model to correctly bind all values. You have two fields with the same name that you can bind in following way
[HttpPost]
public ActionResult Index(SavedScenario scenario, List<bool> Property1)
{
// here you can do with values coming in property1
if (ModelState.IsValid && TryUpdateModel(scenario, "SaveScenario"))
{
repository.Add(scenario);
repository.Save();
}
return View(scenario);
}
It might be issue with naming the fields on your partial forms. Try naming the fields on your partial views by prefixing it with the name of the Model passed into it...like 'AnItemList1.name' instead of just 'name'..I am just guessing here though...but that's what I did sometimes to fix the problem when I was getting values as null..