For the following examples, I'm using a content tree which looks like this:
Content tree
#inherits Umbraco.Web.Mvc.UmbracoTemplatePage
#{
var home = Model.Content.Descendants().Where(x => x.DocumentTypeAlias == "BlogContainer");
<div class="container">
<ul>
#foreach (var item in home)
{
foreach (var items in item.Children)
{
foreach (var baby in items.Children.OrderBy("date desc"))
{
var date = baby.GetPropertyValue<DateTime>("date");
<li>#items.Name - #baby.Name - #date</li>
}
}
}
</ul>
</div>
}
And the result is Result three I need to collect all items and set order by date
Try and do something like
var allItems = homePage.Descendants("YourItemNodeType")
.Where(item => item.HasValue("date")
&& item.GetPropertyValue<DateTime>
("date") != DateTime.MinValue)
.ToList()
.OrderByDescending(item => item.GetPropertyValue<DateTime>("date"));
This should get you all your items in both category 1 and category 2, i always tend to check if my date is actually set ( you wouldnt need to do that for create date mentioned by #bowserm as that is always there with a value).
Once u got them to List then you can sort them by their set date, i do this on when i list news articles in different parent pages, then you can just have one loop to go through all of them.
First of all, what Umbraco version are you using? It looks like you are using 6+? Is that right? My answer below should work for 6 and 7.
The property you are looking for is called createDate, so you would use something like baby.GetPropertyValue<DateTime>("createDate"). Even better, you should be able to just type baby.CreateDate. Umbraco has exposed all of the default properties that you might want on the IPublishedContent as properties, so you can get at those without having to use GetPropretyValue(...).
Take a look at this Umbraco v6 MVC Razor Cheatsheet. It lists the default properties you can get off of the nodes in Umbraco. The razor syntax for v6 will also be applicable to v7, so this cheat sheet works for both.
Related
I'm tinkering with a ASP.NET MVC 4 template, and I need guidance on how to design a complex view layout.
I already created a model that is getting data returned by a stored procedure in a SQL Server DB. A view is getting data from the model in an IEnumerable<> object. If you were to view the raw output of the stored procedure it would look something like this:
**Name** **Objects**
John Orange
John Banana
John Apple
I used a view template to create a simple table based on logic below, but as expected, it is rendered exactly like it is stored in the IEnumerable<> object. The name appears on each row of output in the table, like above.
#model IEnumerable<projectname.Models.ObjectsModel>
<table class="table">
...
...
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Objects)
</td>
...
Instead, I want to create a view layout that is not in table form, that would display the Name as a heading, the objects as a bulleted list, and other attributes elsewhere, with no redundant data. My problem is that if I display the items in the IEnumerable<> using #foreach (var item in Model) I end up getting three duplicate entries, since the values in the Name field are not unique.
What is the 'best practice' way to do this? Can someone please point me in the right direction?
Not sure what your controller looks like, or even your view model, but maybe i can point you in the right direction.
foreach is just that a loop of objects and should be used for just that, i have seen many people manipulate foreach loops with little or no success, You should be using a #for loop. besides The for-loop is approximately 2 times faster than the foreach-loop as it only has to call get_Item for every element in the list.
there are a few ways to acomplis what you want, one way would be what #colinD stated above by using LINQ in the controler or viewmodel to pass the proper data. the other way would be somthing similar to the following.
var models = ObjectsModel();
var grouped = models.GroupBy(s => s.ObjectsModel.Name)
.Select(x => x.Select(y => y))
.ToList();
return View(grouped);
#for(int i = 0; i < Model.Count; i++)
{
<h2>#Html.DisplayFor(model => model[i].First().ObjectsModel.Name)</h2>
<ul>
for(int j = 0; j < Model[i].Count; j++)
{
<li>#Html.DisplayFor(model => model[i][j].Object)</li>
}
</ul>
}
I haven't tested the code but I hope this helps you get the right solution you are looking for.
The main thing i'm trying to figure out is how to display this data
without having the #foreach (var item in Model) loop generate
duplicate entries for each row in the SP output.
Usually processing data is done in action, like groupping data, and then make some loop to display. As you want to avoid #foreach I thought using linq. This is just an idea but keep in mind you should separate concerns in MVC. I hope this helps ;)
IEnumerable<ObjectsModel> model = new List<ObjectsModel>()
{
new ObjectsModel(){ Name = "John", Objects = "Orange" },
new ObjectsModel(){ Name = "John", Objects = "Banana" },
new ObjectsModel(){ Name = "John", Objects = "Apple" }
};
var htmlModel = model
.GroupBy(a => a.Name)
.Select(a => new
{
Name = a.Key,
GroupObjects = string.Join("", a.Select(b => $"<li>{b.Objects}</li>"))
})
.Select(a => $"<h1>{a.Name}</h1><ul>{a.GroupObjects}</ul>")
.ToList();
var result = string.Join("", htmlModel); // <h1>John</h1><ul><li>Orange</li><li>Banana</li><li>Apple</li></ul>
Final result:
<h1>John</h1>
<ul>
<li>Orange</li>
<li>Banana</li>
<li>Apple</li>
</ul>
I'm parsing site content using AngleSharp and i've got an issue with anonymous block.
See the sample code:
var parser = new HtmlParser();
var document = parser.Parse(#"<body>
<div class='product'>
<a href='#'><img src='img1.jpg' alt=''></a>
Hello, world
<div class='comments-likes'>1</div>
</div>
<div class='product'>
<a href='#'><img src='img2.jpg' alt=''></a>
Yet another helloworld
<div class='comments-likes'>25</div>
</div>
<body>");
var products = document.QuerySelectorAll("div.product");
foreach (var product in products)
{
var productTitle = product.Text();
productTitle.Dump();
}
So, productTitle contains numbers from div.comments-likes, output is:
Hello, world 1
Yet another helloworld 25
I've tried something like product.FirstElementChild.NextElementSibling.Text(); but next sibling for link element is div.comments-likes, not anonymous block. It shows:
1
25
So, anonymous blocks are skipped. :(
The best workaround i've found is deleting all preventing blocks, for my example:
product.QuerySelector(".comments-likes").Remove();
var productTitle = product.Text().Trim();
Is better way for parsing text from anonymous block?
Text is modeled as a TextNode, it is a type of node beside element, comment node, processing instruction, etc. That's why NextElementSibling you tried didn't include the text in the result since it intended to return elements only, as the name suggests.
You can get text nodes located directly within product div by traversing through the div's ChildNodes and then filter by NodeType, for example :
var products = document.QuerySelectorAll("div.product");
foreach (var product in products)
{
var productTitle = product.ChildNodes
.First(o => o.NodeType == AngleSharp.Dom.NodeType.Text
&& o.TextContent.Trim() != "");
Console.WriteLine(productTitle.TextContent.Trim());
}
dotnetfiddle demo
Notice that newlines between elements are also text nodes, so we need to filter those out in the demo above.
I'm using a ViewModel to pass the data from two tables to the view. One table contains name of the staff and the second table contains data like number of hours they work per week. When I list the names from one table I also want to show the working hours that match that persons ID. Is it possible to use LINQ like where or equal to make this possible? Could someone show me a simple sample?
EDIT: Are there better ways to do this? Should I handle this in the Controller instead?
This is the code I'm using so far in the View:
#foreach (var item in Model.resourceList)
{
<p>#item.FirstName</p>
}
#foreach (var item in Model.activityList)
{
<p>#item.NumberOfHoursPerWeek</p>
}
If you're looping through this:
#foreach (var item in Model.resourceList)
{
...
}
Then within that loop you can match any element in Model.activityList just like any other LINQ query. Perhaps something like this?:
#Model.activityList.Single(a => a.SomeID == item.SomeID).NumberOfHoursPerWeek
The actual comparison (in this example, a.SomeID == item.SomeID) is up to you, as is the logic for the records you want to find (.Where(), .Single(), .First(), etc. depending on the behavior you expect) is up to you. But finding an element in a collection is the same in this view code as it would be in any server-side code.
Ignoring all design issues, here is a solution:
#foreach (var item in Model.resourceList)
{
#{
var numberOfHours = Model.activityList.First(_ => _.UserID == item.ID).NumberOfHoursPerWeek;
}
<p>#item.FirstName</p>
<p>#numberOfHours</p>
}
However, the View should usually be kept as simple as possible. The ViewModel is responsible for preparing the data for the View in an easily consumable form. If done right, you should not need any linq queries in your Views.
you can use it like this
with if
//just an example to make condition true not displaying the exact Property or condition
#if (true)
{
Model.Where(model=>model.UserID==User.UserID)
}
or with loop
#foreach (var item in Model.resourceList)
{
//just an example not displaying the exact Property
Model.Where(model=>model.UserID==User.UserID)
}
I have news posts within a news page within a homepage on my content structure
Example:
Homepage
- News
-- News Posts
I'm looking to have some of the news feed on my homepage in a foreach statement. In my head it should be as simple as:
#foreach (var homenews in CurrentPage.Children.Children)
{
if (homenews.Name == "News Post")
{
//Do some stuff//
}
}
Obviously that doesn't work so has anybody got any ideas? Thanks
When you're walking the tree you have to remember that a property (or method) like Children or Descendants() will return a collection of objects, so you can't just call Children of a collection. You can only call Children on a single object.
You can find the correct child of the homepage by using something like var newsPage = CurrentPage.Children.Where(x => x.DocumentTypeAlias == "NewsListingPage") and then extract the children of that page.
I ended up getting the news page by its id and then getting it's children from there. The below code worked for me. Thanks guys.
#{
var node = Umbraco.Content(1094);
<p>#node.Id</p> // output is 1094
foreach (var item in node.Children.Where("Visible").Take(3))
{
<p>#item.exampleText</p>
}
}
You need to reference the required node by NodeTypeAlias of it's Document Type.
So assuming the alias of the DocType of your News Posts is “NewsPosts” then...
#foreach (var homenews in #Model.Descendants().Where("NodeTypeAlias == \"NewsPosts\"")).Take(3)
{
<p>#homenews.Name<p>
}
...should return the name of the first 3 news posts.
I had the exact scenario, this is how I got mine working. NodeByID was very useful nad -1 indicates the root
#foreach(var item in Model.NodeById(-1).Children)
{
string itemName = item.Name;
switch(itemName)
{
case "News":
#* News *#
<div id="News">
<h3>#item.Name</h3>
#foreach (var newsPost in item.Children.OrderBy("UpdateDate desc").Take(4).Items)
{
<p>
#newsPost.Title
</p>
}
</div>
}
}
This code obtains a listing of unique org names for display within my .cshtml page:
IEnumerable<dynamic> data = db.Query("Select * from provider
where submitter_element = '210300'");
//the 210300 could be any value passed to the query
var items = data.Select(i => new { i.org_name }).Distinct();
foreach(var name in items){
<text>#name.org_name<br></text>
The records in data are each unique themselves, but the data in each field contains the same values i.e. multiple providers have the same org_name.
I want to be able to reuse the data multiple times to create multiple unique lists. I was hoping to pass this to a #helper for display. To that end, I have the following:
#helper ListBoxDistinctDisplay(IEnumerable<dynamic> queryResult)
{
IEnumerable<dynamic> distinctItems = queryResult.Select(i => new { i.org_name }).Distinct();
foreach(var listItem in distinctItems){
<text>#listItem.org_name<br></text>
}
Then in my .cshtml page I do this:
#DisplayHelpers.ListBoxDistinctDisplay(data)
...and BINGO, I get my unique list on my "view" page.
The works perfectly, except as you see I am having to indicate .org_name within the helper.
My question is how can I pass the field name (org_name) into the helper so that my helper can be re-used no matter he field name?
OR...is there a totally different approach all together that I am unaware of?
THANKS!
Since you like to use dynamic, I'll stick with that.
You may want to pass selector:
#helper ListBoxDistinctDisplay(IEnumerable<dynamic> queryResult, Func<dynamic, dynamic> selector)
{
IEnumerable<dynamic> distinctItems = queryResult.Select(x => new {selectedField = selector(x)}).Distinct();
foreach (var listItem in distinctItems)
{
<text>#listItem.selectedField<br/></text>
}
}
Call it:
#DisplayHelpers.ListBoxDistinctDisplay(data, x => x.org_name)