This one is very difficult for me to word, which is what is failing me in my google searching, i brand new to asp.net and it's giving me some difficulties with the logic of this requirement
I have a bunch of results which are displayed primarily as images with some text overlayed.
I have some HTML that is required to show these images, but it uses custom styling to show each "row" of images, it uses div's for a custom look.
What i'm needing to do, is work out a way in the cshtml file display a html block to start a row, and then output the html for 6 of the items, then close off that row and begin another one and rinse repeat until completed, but i cannot for the life of me work out how i would go about that in asp.net.
This might be a very very simple issue, but due to my inability to word this correctly for google, I'm really struggling to find anything online.
The closest visual example i can think of is something like Netflix, but without the ability to scroll the movies, so all the movies are listed in those rows.
i'm currently using the following method,
#foreach (var item in Model)
{
html...
}
My original thought was to have a counter and do a conditional statement when the count hit's six, closing the row and starting a new one, but i cannot work out how to mix that much html into code blocks.
#{
int count = 0;
foreach(var item in model)
{
count++;
//Output current item's html
if(count == 6)
{
//End current row, start new row
count = 0;
}
}
but as i mentioned, i can only find how to mix single html elements in with code blocks using the #: method, and i need a block of html.
Adding my controller code as requested, I have it kinda working using viewbag, but the groupedModel seems to split each movie into a separate row.
// GET: Movies
public ActionResult Index(string movieGenre, string searchString)
{
var GenreList = new List<string>();
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
GenreList.AddRange(GenreQry.Distinct());
ViewBag.movieGenre = new SelectList(GenreList);
var movies = from m in db.Movies
select m;
if(!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if(!string.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
var groupedModel = movies.AsEnumerable().Select((e, i) => new { Element = e, Index = i }).GroupBy(e => e.Index % 6).Select(g => g.Select(e => e.Element));
ViewBag.grouped = groupedModel;
return View(movies);
}
and my view (With most of the HTML removed for ease of reading):
#model IEnumerable<MvcMovie.Models.Movie>
#{
ViewBag.Title = "Index";
}
<p>
#Html.ActionLink("Create New", "Create")
#using (Html.BeginForm("Index", "Movies", FormMethod.Get))
{
<p>
Genre: #Html.DropDownList("movieGenre", "All")
Title: #Html.TextBox("SearchString")
<input type="submit" value="Filter" />
</p>
}
</p>
#foreach(var group in ViewBag.grouped)
{
<div class="MovieRow">
#foreach (var item in group)
{
<div class="bob-title">#Html.Display((item as MvcMovie.Models.Movie).Title)</div>
}
</div>
}
EDIT:
The only problem i have left now, is that i can't seem to get the item variables to output, none of the #Html.Display((item as MvcMovie.Models.Movie) sections output anything.
First, let's group your model to get groups with 6 elements (better to do it in the controller, not in the view):
var groupedModel = Model.Select((e, i) => new { Element = e, Index = i })
.GroupBy(e => (e.Index - 1) / 6)
.Select(g => g.Select(e => e.Element));
than you will iterate these group and display them:
#foreach(var group in groupedModel)
{
<div id="rowDiv">
#foreach(var element in group)
{
<div id="elementDiv">// element div here
// display element info here
</div>// element div end
}
</div> // row div end
}
Related
I'm trying to access the properties of a class dynamically in ASP.NET Razor when generating an HTML Table. This problem is normally easily solved with reflection, but the #Html.DisplayFor method is giving me issues.
I am attempting to generate an HTML table that has 3 cells per row, with the title of the item in bold as the first line of the cell, and the value of the item in the second line of the cell. The contents of the table should not include cells which are on the 'Excluded Fields' list, and I do not want to have to statically reference each column.
<table class="blpSecurityTable">
<tr>
#{
int _rowCount = 0;
foreach (var property in item.GetType().GetProperties())
{
#if (!Model.ExcludedFields.Contains(#property.Name))
{
dynamic test = #property.GetValue(item);
<td><b>#Html.DisplayFor(m => #property.Name)</b><br />#Html.DisplayFor(m => #test)</td>
_rowCount++;
}
#if (_rowCount % numCols == 0)
{
#:</tr><tr>
}
}
}
</tr>
</table>
I've tried calling #Html.DisplayFor(m => #property.GetValue(item)) but that just creates a runtime error. I can simply call #property.GetValue(item) and the value displays, but this is not ideal because I use display templates to do things like set dates to the ShortDateString format.
I understand that DisplayFor is using reflection to determine the type of the property, and that is why I am trying to use the dynamic variable to facilitate reflection for the method. However, when I run the method, it throws errors indicating the variable is not a generic parameter, and therefore cannot share its attributes. The resulting page has mostly blank values, and some cells filled in with unexpected descriptive information.
I feel like I'm getting close, but I don't know how to proceed. The page won't look right if I don't pass the values into an HTML display method, and I cannot think of any other way to get the type of table I want to be generated. Thoughts?
The issue is solved by creating a Display Template for the Security object, which then allowed me to properly use the #Html.Display Method, because the Model for the Display Template has an entry for the property.
Here is what the page code looks like now:
Display Template
#model Interface.Models.Security
#{int numCols = 3;}
<table class="blpSecurityTable">
<tr>
#{
int _rowCount = 0;
foreach (var property in Model.GetType().GetProperties())
{
#if (!BLPDLModel.ExcludedFields.Contains(#property.Name))
{
<td><b>#Html.DisplayFor(m => #property.Name)</b><br />#Html.Display(property.Name)</td>
_rowCount++;
}
#if (_rowCount % numCols == 0)
{
#:</tr><tr>
}
}
}
</tr>
</table>
Razor Page
#foreach (var item in Model.Security)
{
<div class="blpSecurityItem">
<button type="button" class="collapsible">{button text}</button>
#{
<div class="collapsible-content">
<hr />
#Html.DisplayFor(m => item)
</div>
}
</div>
Replace #Html.DisplayFor(m => #test) with #Html.Display(property.Name)
and your model property for date should have [DisplayFormat(DataFormatString = "{0:yyyy/MM/dd}")] or anything you like
I am building a scraper to be used on many sites (too many to scrape manually using a web scraping tool such as Octoparse).
Each site will probably be different in structure. Some sites may have data that I wish to be scraped; some may not. This is to be determined using a list of keywords/keyphrases. Of sites that I wish data to be parsed, these are likely to be presented in a list of some way. However, the HTML elements used to present the list is indeterminate (i.e. could be a ul list, li list, a div list, a table, etc).
If a keyword/keyphrase is found, I wish for not only that element to be parsed, but all others that may be part of the same list/group.
Example 1
<div>
<h1>Random content I am not interested in</h1>
</div>
<div>
<h1>Some more random content I am not interested in</h1>
</div>
<div>
<ul>
<li>Dogs</li>
<li>Cats</li>
<li>Birds</li>
</ul>
</div>
Example 2
<div>
<h1>Random content I am not interested in</h1>
</div>
<div>
<h1>Some more random content I am not interested in</h1>
</div>
<div>
<div>
<div>
<div>
<h1>Bob</h1>
<p>A description of Bob</p>
</div>
<div>
<h1>Ben</h1>
<p>A description of Ben</p>
</div>
<div>
<h1>Bill</h1>
<p>A description of Bill</p>
</div>
</div>
</div>
</div>
From example one, if I had identified the element Dogs, I would like the result to be Dogs, Cats, Birds.
From example two, if I had identified Ben, I would like the result to be 3 div elements, each of which contains the heading and paragraph; the key is that all results are to include HTML, not just text.
Any help/guidance would be much appreciated.
I managed something like this:
static IEnumerable<string> FindSimilarItems(string html, string[] values, int maxDepth)
{
var doc = new HtmlDocument();
doc.LoadHtml(html);
var output = new List<string>();
foreach (var value in values)
{
var rootElement = doc.DocumentNode.SelectSingleNode($"//*[text()='{value}']");
if (rootElement == null) continue;
for (int i = 0; i < maxDepth; i++)
{
var newXpath = RemoveXpathGroupIndex(rootElement.XPath, i);
var newElements = doc.DocumentNode.SelectNodes(newXpath);
if (newElements.Count <= 1) continue;
output.AddRange(newElements.Select(x => x.InnerText));
}
}
return output.GroupBy(x => x).Select(x => x.First()).ToList();
}
static string RemoveXpathGroupIndex(string xpath, int groupElement)
{
var splited = xpath.Split('/');
var pickedElement = splited.Length - 1 - groupElement;
splited[pickedElement] = splited[pickedElement].Substring(0, splited[pickedElement].IndexOf('['));
return string.Join("/", splited);
}
This code:
var similarItems = FindSimilarItems(input1, new string[] { "Dogs" }, 3);
Will return
["Dogs", "Cats", "Birds"]
I have a panel that is created and filled via a vacancy page I have created. Im doing it as follows:
#{
var root = CurrentPage.AncestorOrSelf(1);
var newsNode = root.Descendants("News").First();
var vacanciesNode = root.Descendants("Vacancies").First();
string shortenedSummary = string.Empty;
}
<ul>
#foreach (var vacancyItem in vacanciesNode.Descendants("Vacancy").Take(3).OrderBy("postDate desc"))
{
<p>here we are 2</p>
#vacanciesNode.Count().ToString()
<li>
<h4>#vacancyItem.jobTitle</h4> <span>Posted on #vacancyItem.postDate.ToString("dd/MM/yyyy")</span>
<p>
#if (vacancyItem.jobSummary.Length <= 182)
{
#vacancyItem.jobSummary
}
else
{
shortenedSummary = vacancyItem.jobSummary.Substring(0, 182) + "...";
#shortenedSummary
}
</p>
Read More..
</li>
}
</ul>
However, when there are no vacancy items, my list is empty. Should this be the case, I'm wanting it to read "sorry no vacancies just now" but I don't know how to check if my vacanciesNode has any items in it.
Could someone show me how I could achieve this?
Since the .Descendants() method returns a DynamicContentList (a collection) you can simply do a .Count() on the collection and check whether it's more than or equal to 1.
If there's more than 0 items in the collection, it's not empty.
So, what you need to do is surround your #foreach with an #if statement which checks on this, and an else statement after that prints whatever html you want to show if there's no vacancies
#if( vacanciesNode.Descendants("Vacancy").Take(3).OrderBy("postDate desc").Count() > 0) {
//Do foreach
}
else
{
//Write message about missing vacancies
}
I am using MVC + EF
I have a Feed xml file url that gets updated every 7 minute with items, every time a new item gets added I retrieve all the items to a list variable and then I add these varible to my database table. After that I fill a new list variable which is my ViewModel from the database table. Then I declare the ViewModel inside my view which is a .cshtml file and loop throught all of the objects and display them.
How can I make sure that the newest items get placed on the top and not in the bottom and also the numbers displays in correct order?
This is how I display the items inside my cshtml note that I use a ++number so the newest item needs to be 1 and so on ::
#model Project.Viewmodel.ItemViewModel
#{
int number = 0;
}
<div id="news-container">
#foreach (var item in Model.NewsList.OrderByDescending(n => n.PubDate))
{
<div class="grid">
<div class="number">
<p class="number-data">#(++number)</p>
</div>
<p class="news-title">#(item.Title)</p>
<div class="item-content">
<div class="imgholder">
<img src="#item.Imageurl" />
<p class="news-description">
#(item.Description)
<br />#(item.PubDate) |
Source
</p>
</div>
</div>
</div>
}
</div>
This is how I fill the viewmodel which I use inside the .cshtml file to iterate throught and display the items
private void FillProductToModel(ItemViewModel model, News news)
{
var productViewModel = new NewsViewModel
{
Description = news.Description,
NewsId = news.Id,
Title = news.Title,
link = news.Link,
Imageurl = news.Image,
PubDate = news.Date,
};
model.NewsList.Add(productViewModel);
}
If you check this image thats how it gets displayed with the numbers, thats incorrect.
If you see the arrows thats how it should be, how can I accomplish that?
Any kind of help is appreciated :)
note: When I remove .OrderByDescending, the numbers are correctly on each grid. But I need the .OrderByDescending beacuse i want the latest added item in the top.
Try this:
#model Project.Viewmodel.ItemViewModel
#{
int number = 0;
var NewsItems=Model.NewsList.OrderByDescending(n => n.PubDate).ToList();
}
<div id="news-container">
#foreach (var item in NewsItems)
{
<div class="grid">
<div class="number">
<p class="number-data">#(++number)</p>
</div>
<p class="news-title">#(item.Title)</p>
<div class="item-content">
<div class="imgholder">
<img src="#item.Imageurl" />
<p class="news-description">
#(item.Description)
<br />#(item.PubDate) |
Source
</p>
</div>
</div>
</div>
}
</div>
Looking at your sketch I assume you have float: left or display: inline-block for a grid class. Adding float: right might do the trick.
If that does not help please post CSS you have.
just a quick word..
you are passing NewsViewModel to the view and performing iteration on ItemViewModel ..y?
do u think this may be the cause of the problem..
Regards
You could sort your news list using the CompareTo method:
model.NewsList.Sort((a, b) => b.PubDate.Date.CompareTo(a.PubDate.Date));
Once you have the list sorted correctly, you can simply use CSS to display the news list two items per row. See this fiddle.
The fiddle is a revised one which was provided to me in a similar question I asked before.
Try this one
private void FillProductToModel(ItemViewModel model, News news)
{
var newList = list.OrderByDescending(x => x.News.Date).toList();
var productViewModel = new NewsViewModel
{
Description = newList .Description,
NewsId = newList .Id,
Title = newList .Title,
link = newList .Link,
Imageurl = newList .Image,
PubDate = newList .Date,
};
model.NewsList.Add(productViewModel);
I am new to MVC and have some difficulties understanding this.
To make it simple, I have a "Person" object and this object has an IEnumerable property called "EmailaddressList".
I have generated an edit page through Visual Studio 2012. The main objects properties, are generated on the edit page with textboxes like Name and LastName.
However the list of e-mail addresses in the IEnumerable list of sub-objects are not generated automatically in my view.
This is OK, I have written that code by hand using a tab for each type of e-mailaddress.
So far so good.
Problem:
When I recieve the model (person object) in my HTTP-Post method, the EmailAddressList is null.
Why is it like this, It was not null when I sent it to the view.
I the tab where the e-mailadresses are listed is in a partial view.
Can anyone give me some tips, is it something I'm missing here?*
View-Code
<div id="tabs">
<ul>
#foreach (var item in Model.EmailAddressList)
{
<li>#Html.Label(item.AddressType)</li>
}
</ul>
#foreach (var item in Model.EmailAddressList)
{
<div id="#item.AddressType">
<p>
#Html.TextBoxFor(s => item.EmailAddress, new { #class = "input-xxlarge" })
</p>
</div>
}
</div>
Controller (recieving method)
Here person.EmailAddressList is null
[HttpPost]
public ActionResult Create(Person person)
{
if (ModelState.IsValid)
{
personRepository.InsertOrUpdate(person);
personRepository.Save();
return RedirectToAction("Index");
}
else
{
return View();
}
}
That's because in order to correctly index your fields (so model binder can do it's work), you have to use a for loop.
First, change your IEnumerable to be a List (so we can use an indexor in the view).
Then change your foreach to be the following for loop:
#for (int i = 0; i < Model.EmailAddressList.Count; i++)
{
<div id="#Model.EmailAddressList[i].AddressType">
<p>
#Html.TextBoxFor(m => m.EmailAddressList[i].EmailAddress, new { #class = "input-xxlarge" })
</p>
</div>
}
Based on your update, the reason this doesn't work is because the default model binder only relies on order for a collection of simple data. When it comes to complex type you need to provide the relevant index per item otherwise it doesn't know which item property your referring to e.g.
#for (int i = 0; i < Model.EmailAddressList.Count; i++) {
Html.TextBoxFor(m => m.EmailAddressList[i].EmailAddress) %>
}
See Phil Haack's article on model binding to a list.
It's due to your elements not being ID'd the correct thing for MVC to pick them up on the post back, what you need is:
#Html.EditorFor(model => model.EmailAddressList);
Then, please refer to my post located here on how to make this look to how you want it to.