After trying to fill a html table with data from a MVC model, we ran into a problem. This code below generates a table, which is supposed to represent a schedule. The first row displays the days of the week, and the first column displays the number representation of the school hours.
<table class="table2">
<tr>
<th></th>
#{ string[] days = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };}
#for (int i = 0; i < days.Length; i++)
{
<th>#(days[i])</th>
}
</tr>
#{
TimeSpan timeSpan = new TimeSpan(8, 30, 0); //08:30:00
TimeSpan AddTime = new TimeSpan(0, 50, 0); //00:50:00
TimeSpan timeSpan2 = new TimeSpan();
}
#{ for (int i = 1; i < 16; i++)
{
<tr>
<td>
<h3>#(i)</h3>
<a>
#{timeSpan.ToString();}
#(string.Format("{0:00}:{1:00}", timeSpan.Hours, timeSpan.Minutes))
</a>
#{timeSpan2 = timeSpan.Add(AddTime);}
<div>
<a>
#(string.Format("{0:00}:{1:00}", timeSpan2.Hours, timeSpan2.Minutes))
#{timeSpan = timeSpan2;}
</a>
</div>
</td>
#foreach (var item in Model)
{
if (item.Hours.Contains(i))
{
for (int x = 0; x < days.Length; x++)
{
if (item.Day != days[x])
{
<td>
</td>
}
else
{
<td>
#(item.Class)
#(item.Classroom)
</td>
}
}
}
}
</tr>
}
}
</table>
Our model looks like this;
public class ScheduleModel
{
[Display(Name = "Lesson Code")]
public string LessonCode { get; set; }
[Display(Name = "Day")]
public string Day { get; set; }
[Display(Name = "Classroom")]
public string Classroom { get; set; }
[Display(Name = "Hours")]
public int[] Hours { get; set; }
[Display(Name = "Class")]
public string Class { get; set; }
}
Ill try to explain what we're trying to do. Our view gets a list of models, filled with the data specified above. We want to display this data in the html table we tried to create above. The "Day" variable corresponds to the days in the top row of the table, and the Hours int[] corresponds to the hours in the first column of the table. These values should be compared with eachother to find the correct spot in the table. We almost got it to work, with the code displayed above, but we ran into a problem with duplicate hours and empty cells.
Lets say we get 2 instances of the model, one looks like this;
Day : Monday
Hours : [1,2,3]
and the second looks like this :
Day : Tuesday
Hours: [1,2,3]
The problem with this is that the foreach loop goes through the first model completly, and fills all cells in the first row with empty cells, then when the foreach loops through the second model, it cannot fill the already created empty cells, so it just sticks them on at the end; screenshot to visualize the problem ;
So it all boils down to this; how do we generate the rows, but in such a way that we can still add new data into the empty fields.
This works perfectly!
#for (int x = 0; x < 7; x++)
{
<td>
#foreach(var item in Model)
{
if (item.Hours.Contains(i))
{
if(item.Day == days[x]){
#(item.Class)
#(item.Classroom)
}
else
{
#("")
}
}
}
</td>
}
Related
Good day,
I'm trying to create a dynamic calendar using HTML table. I have a few problems that I'm facing right now.
I have no idea how to specify if the specific first day of the month is Sunday, Monday, Tuesday, Wednesday, Thursday or Saturday.
How to fix my table body to show the table properly layout like a calendar.
So far, these are my codes in my razor page.
#{ // responsible for getting the first and last days of the month
var getDate = DateTime.Now;
var firstDayOfTheMonth = new DateTime(getDate.Year, getDate.Month, 1);
var lastDayOfTheMonth = firstDayOfTheMonth.AddMonths(1).AddDays(-1);
var numberOfDays = Convert.ToInt16(lastDayOfTheMonth.ToString("dd"));
}
// My HTML table
<table border="1">
<thead>
<tr>
<th>Sunday</th>
<th>Monday</th>
<th>Tuesday</th>
<th>Wednesday</th>
<th>Thursday</th>
<th>Friday</th>
<th>Saturday</th>
</tr>
</thead>
<tbody>
<tr>
#for (var i = 0; i < numberOfDays + 1; i++)
{
<td>#i</td>
}
</tr>
</tbody>
Here's the current output:
Thank you in advance.
To generate a calendar as a table, you need to generate a 7 column x 6 row grid to allow for all possible months so your loop needs to iterate 42 times (not the number of days in the month), where the first cell is the last Sunday of the previous month (unless the current month starts on a Sunday)
To calculate the date in the first cell, use
DateTime startDate = firstDayOfTheMonth.AddDays(-(int)firstDayOfTheMonth.DayOfWeek);
Then to generate the table in your view
<table>
<thead>
.... // add day name headings
</thead>
<tbody>
<tr>
#for (int i = 0; i < 42; i++)
{
DateTime date = startDate.AddDays(i);
if (i % 7 == 0 && i > 0)
{
#:</tr><tr> // start a new row every 7 days
}
<td>#date.Day</td>
}
</tr>
</tbody>
</table>
You might also want to style any days not in the current month differently, in which case you could conditional add a class name, for example
if (startDate.Month == getDate.month)
{
<td class="current">#date.Day</td>
}
else
{
<td>#date.Day</td>
}
Asp.net MVC Example:
#{
int currentMonth = DateTime.Now.Month;
int currentYear = DateTime.Now.Year;
DateTime firstDay = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
int daysInCurrentMonth = DateTime.DaysInMonth(firstDay.Year, firstDay.Month);
DateTime lastDay = new DateTime(currentYear, currentMonth, daysInCurrentMonth);
// Sunday casted to int gives 0 but that will not work for us, we need 7 to be able to calculate number of empty cells correctly
int dayOfWeekFirst = ((int)firstDay.DayOfWeek > 0) ? (int)firstDay.DayOfWeek : 7;
int dayOfWeekLast = ((int)lastDay.DayOfWeek > 0) ? (int)lastDay.DayOfWeek : 7;
}
HTML:
<tr align="center">
<!-- filling up space of previous month -->
#for (int a = 1; a < dayOfWeekFirst; a++)
{
#:<td></td>
}
<!-- filling up space of current month -->
#for (int i = 1; i <= daysInCurrentMonth; i++)
{
DateTime renderedDay = new DateTime(firstDay.Year, firstDay.Month, i);
// if Sunday
if (renderedDay.DayOfWeek == DayOfWeek.Sunday)
{
#:<td class="calendar-holiday">#i</td></tr><tr align="center">
}
// if Saturday
else if (renderedDay.DayOfWeek == DayOfWeek.Saturday)
{
#:<td class="calendar-holiday">#i</td>
}
// if normal day
else
{
#:<td>#i</td>
}
}
<!-- filling up space of next month -->
#for (int a = 1; a <= 7-dayOfWeekLast; a++)
{
#:<td></td>
}
</tr>
Here is full example details
https://forums.asp.net/t/1695201.aspx?Making+a+Calendar+table+
I'm using this model:
public class ListCoins
{
public Dictionary<string,Dictionary<string,float>> listCoins{ get; set; }
}
when I try to display them in the view using this method:
model IEnumerable<Crytocurrency_Web___Main.JResult.ListCoins>
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<h4>Coin Value: #ViewBag.WalletValue</h4>
<table class="table">
<tr>
<th>
#Html.DisplayName("Name"))
</th>
<th>
#Html.DisplayName("CoinValue")
</th>
<th>
#Html.DisplayName("CoinAmmount")
</th>
<th></th>
</tr>
#for (var i = 0; i < Model.ElementAt(i).listCoins.Count; i++)
{
foreach (var coin in Model.ElementAt(i).listCoins)
{
<p>#coin.Key + " " + #coin.Value)</p>
}
}
i get the error on the line:
#for (var i = 0; i < Model.ElementAt(i).listCoins.Count; i++)
but in the controller i have checked that count is over one:
int counter = listOfCoins.Count;
which equals four and all the data is there.
i may just be displaying them wrong but i cant figure it out
controller code:
List<ListCoins> newListCoins = new List<ListCoins>();
foreach (var coin in listOfCoins)
{
using (WebClient client = new WebClient())
{
var json = client.DownloadString(
"https://min-api.cryptocompare.com/data/pricemulti?fsyms=" + queryString + "&tsyms=" +
queryString);
var result = Newtonsoft.Json.JsonConvert
.DeserializeObject<Dictionary<string, Dictionary<string, float>>>(json);
newListCoins.Add(new ListCoins
{
listCoins = result,
});
}
}
return View(newListCoins.ToList());
I don't think you need to use: Model.ElementAt(i). You can do this:
#for (var i = 0; i < Model.listCoins.Count; i++)
rather than this:
#for (var i = 0; i < Model.ElementAt(i).listCoins.Count; i++)
You can then loop through all of the elements in listCoins and display the values in the view.
You are getting ArgumentOutOfRangeException because you are trying to access an item in the collection with an index which does not exist!
This part in your for loop
i < Model.ElementAt(i).listCoins.Count
You are trying to loop until n where n is the Count of your child property listCoins of each item.
Your view is strongly typed to a collection of ListCoins. So Your outer loop should be from 0 to Model.Count
#for (var i = 0; i < Model.Count; i++)
{
foreach (var coin in Model.ElementAt(i).listCoins)
{
<p>#coin.Key + " " + #coin.Value)</p>
}
}
Here , the coin.Value is again another dictionary(Dictionary<string,float>), so you probably want to loop through the items in that as well.
I feel the foreach version could be more readable.
#foreach (var item in Model)
{
foreach (var coin in item.listCoins)
{
<h1>#coin.Key</h1>
foreach (var v in coin.Value)
{
<p>#v.Key - #v.Value</p>
}
}
}
I've struggled with this for quite some time. Today I finally wrote the following code.
The ViewModel contains an int property which later tells the view how many pages the data has been split into.
The controller splits the data by taking a specified amount of rows and, in the event of paging, splits by pageNumber * recordsPerPage
Take a look:
The ViewModel
public class ThreadPostsViewModel
{
public Thread Thread { get; set; }
public List<Post> Posts { get; set; }
public int Pages { get; set; }
}
The Controller
private int PostsPerPage = 10;
public ActionResult Thread(int id, int page = 1)
{
using (OrtundEntities Db = new OrtundEntities())
{
// get the thread and its parent data (parent for breadcrumbs)
var Thread = Db.Threads.Include(t => t.Title).FirstOrDefault(x => x.Id == id);
// create a list for the Posts
List<Post> Posts = new List<Post>();
// select based on paging
if (page == 1)
// no paging has happened, get the first set of records
Posts = Db.Posts.Include(x => x.User).Where(x => x.ThreadId == id).OrderByDescending(x => x.Date).Take(PostsPerPage).ToList();
else
// we're on a new page. Skip however many rows we've already seen
Posts = Db.Posts.Include(x => x.User).Where( x=> x.ThreadId == id).OrderByDescending(x => x.Date).Take(PostsPerPage).Skip(PostsPerPage * page).ToList();
// create and return the view model
ThreadPostsViewModel Model = new ThreadPostsViewModel
{
Thread = Thread,
Posts = Posts,
Pages = Posts.Count / PostsPerPage
};
return View(Model);
}
}
The View
#model Ortund.Models.ThreadPostsViewModel
<div class="paging">
#for (int i = 1; i < Model.Pages; i++)
{
string Url = String.Format("/View/Thread/{0}?page={1}", Model.Thread.Id, i);
#i
}
</div>
<div class="posts-list">
#foreach (var Post in Model.Posts)
{
<div class="post" id="#Post.Id">
</div>
}
</div>
In this code, assuming 300 posts are selected from the database and 10 posts are specified per page, then there should be 30 pages.
Even that's a hefty amount of links to fit into your page design so how can I minimize these paging links and display, say, 10 paging links only where, when you get to say, page 8, the links will change to show you 3-13, for example?
Even having the paging links display as follows would be preferable:
1 2 3 4 5 ... 90 91 92 93 94
In controller put value of current page:
ViewBag.currentPage = page;
In view you can do something like this (not tested):
<div class="paging">
#if (Model.Pages > 11 && ViewBag.currentPage > 6)
{
for (int i = ViewBag.currentPage - 6; i < ViewBag.currentPage -1; i++)
{
string Url = String.Format("/View/Thread/{0}?page={1}", Model.Thread.Id, i);
#i
}
for (int i = ViewBag.currentPage + 1; i < ViewBag.currentPage + 6; i++)
{
string Url = String.Format("/View/Thread/{0}?page={1}", Model.Thread.Id, i);
#i
}
}
else
{
for (int i = 1; i < Model.Pages; i++)
{
string Url = String.Format("/View/Thread/{0}?page={1}", Model.Thread.Id, i);
#i
}
}
</div>
I'm trying to implement this algorith in a View page using Razor, but, it does not display the expected result and I don't get any compilation errors. Any suggestion please ?
Edit : I apologize I was not very clear, I confess. My problem is that I do not understand why ViewBag.NbrePages is equal to 0. However, the database had been filled.
Action();
[HttpGet]
public ActionResult Rechercher(string rech, string type, int num = 1)
{
int nbLignesDepassees = 10 * (num - 1);
ViewBag.Recherche = Server.HtmlEncode(rech);
ViewBag.Type = Server.HtmlEncode(type);
ViewBag.NumPgeCourrante = num;
if (type == "nomAppMetier")
{
var appsMetiers = _db.AppsMetiers
.Where(x => SqlFunctions.PatIndex("%" + rech + "%", x.nomApplication) > 0)
.OrderBy(x => x.nomApplication)
.Skip(nbLignesDepassees)
.Take(10);
ViewBag.NbrePages = (int)(appsMetiers.Count() / 10) ;
return View("RechercheAppsMetiers",appsMetiers);
}
if (type == "nomPoste")
{
var postes = _db.Postes
.Where(x => SqlFunctions.PatIndex("%" + rech + "%", x.nomPoste) > 0)
.OrderBy(x => x.nomPoste)
.Skip(nbLignesDepassees)
.Take(10);
ViewBag.NbrePages = (int)(postes.Count() / 10);
return View("RecherchePostes", postes);
}
return HttpNotFound();
}
View();
<ul>
#{
for (int i = 0; i < ViewBag.NbrePages; i++)
{
if(i==1 || i==2 || i==3){
<li class="disabled">&maquo;</li>
}else{
<li>«</li>
}
if (i == ViewBag.NumPgeCourrante)
{
<li class="active">#i <span class="sr-only">(current)</span></li>
}
else
{
<li>#i </li>
}
if(i==ViewBag.NbrePages || i==ViewBag.NbrePages-1 || i==ViewBag.NbrePages-2){
<li class="disabled">»</li>
}else{
<li>»</li>
}
}
}
</ul>
Thanks a lot !
Rather than having so much logic in the view, consider the following:
A model
public class PagesModel
{
public int NumberOfPages { get; set; }
public int CurrentPage { get; set; }
}
A helper method in a class
public static class Helpers
{
public static bool GetClassNames(int page, int totalPages, int currentPage)
{
var classNames = new List<string>();
var isWithinFirstOrLastThree = page <= 2 || page >= (totalPages - 2);
if (isWithinFirstOrLastThree)
{
classNames.Add("disabled");
}
if (page == currentPage)
{
classNames.Add("active");
}
return string.Join(" ", classNames.ToArray());
}
}
And then your view could be as simple as
#model PagesModel
#for (int i = 0; i < Model.NumberOfPages; i++)
{
<li class="#Helpers.GetClassNames(i, Model.NumberOfPages, Model.CurrentPage)">
&maquo;
#i
</li>
}
This doesn't exactly match what you are trying to achieve, but I hope that it is helpful nonetheless.
NbrePages will be either 0 or 1 (if you have more that 10 records) due to Take(10) and using integer division:
ViewBag.NbrePages = (int)(appsMetiers.Count() / 10) ;
So most likely you get less that 10 items in appsMetiers.
Suggestion to improve source based on original misspelling of the variable in CSHTM:
Using good names or strongly typed model would help to avoid spelling like NbrePges in for condition in original post:
for (int i = 0; i < ViewBag.NbrePages; i++)
CSHTML files are not compiled till run-time access, so no compile errors. Since ViewBag allows any property to be used you are not getting any intellisense warning either.
Instead of ViewBag consider some strongly typed model or at least put strongly typed object for paging into ViewBag:
class PagingState
{
public int NumberOfPages { get;set;}
public int CurrentPage { get;set;}
}
and in view:
var pageingState = (PagingState)(ViewBag.Paging);
for(int I = 0; i < pageingState.NumberOfPages; i++)...
I'm stuck on a problem trying to get a foreach block of code to work within an mvc controller and viewmodel.
What i'm trying to achieve is loop through the datetime values and if less than 60 seconds, show seconds. if more than 60 seconds but less than 1 hour show minutes. else show full datetime.
I can get the above to work, but it only displays the 1st record. I have tried putting foreach loops in various places but just cannot seem to get it to work.
Would appreciated a fresh pair off eyes to help with this.
public class MostRecentPostsViewModel
{
public List<MembersForumProperties> SelectMostRecentForumPosts { get; set; }
public string DateAndTimeOfForumPosts { get; set; }
}
public class IsPostLessThanOneHour
{
public static string DisplayPostInMinutesOrSeconds(string displyMostRecentForumPosts)
{
string displayTime = string.Empty;
//foreach (var postTime in mv.SelectMostRecentForumPosts)
//{
// dte = postTime.ForumMemberDateTimePostedPost;
//}
DateTime dtn = DateTime.Now;
DateTime timeOfPost = Convert.ToDateTime(displyMostRecentForumPosts);
TimeSpan ts = dtn - timeOfPost;
if (ts.TotalSeconds > 0 && ts.TotalSeconds < 60)
{
displayTime = "about " + ts.Seconds + " seconds ago";
}
else if (ts.TotalSeconds > 61 && ts.TotalSeconds < 3600)
{
displayTime = "about " + ts.Minutes + " minutes ago";
}
else
{
displayTime = displyMostRecentForumPosts;
}
return displayTime;
}
}
Controller
public PartialViewResult MostRecentMembersPosts()
{
var displyMostRecentForumPosts = _imf.DisplayMostRecentForumPosts().ToList();
var loopThroughDateTimes = displyMostRecentForumPosts.ToList();
var test = "";
foreach (MembersForumProperties t in loopThroughDateTimes)
{
test = t.ForumMemberDateTimePostedPost;
}
var membersMostRecentPost = new MostRecentPostsViewModel
{
SelectMostRecentForumPosts = displyMostRecentForumPosts,
DateAndTimeOfForumPosts = IsPostLessThanOneHour.DisplayPostInMinutesOrSeconds(test)
};
return PartialView("pvMostRecentMembersPost",membersMostRecentPost);
}
Why not just send the dates down as is and use a JS plugin like TimeAgo e.g.
public PartialViewResult MostRecentMembersPosts()
{
return PartialView("pvMostRecentMembersPost", _imf.DisplayMostRecentForumPosts().ToList());
}
Then in your view
#model IEnumerable<MemberForumProperties>
<!-- Head section would need to be defined in your master page first -->
#section Head {
<script src="jquery.timeago.js" type="text/javascript"></script>
<script type="text/javascript">
$.ready(function() {
$("abbr.timeago").timeago();
});
</script>
}
#foreach (var m in Model)
{
<abbr class="timeago" title='#m.ForumMemberDateTimePostedPost.ToString("s")' />
}
TimeAgo will take care of converting your DateTime values into a fuzzy timestamp.
The Problem
If you don't want to go for the client-side approach, then to fix your current server-side issue you need to send down a list of relative times, at the minute you only appear to be sending down the last relative time i.e.
var test = "";
foreach (MembersForumProperties t in loopThroughDateTimes)
{
test = t.ForumMemberDateTimePostedPost;
}
// test now contains the date/time of the last item in the `loopThroughDateTimes` list
var membersMostRecentPost = new MostRecentPostsViewModel
{
SelectMostRecentForumPosts = displyMostRecentForumPosts,
DateAndTimeOfForumPosts = IsPostLessThanOneHour.DisplayPostInMinutesOrSeconds(test)
};
// DateAndTimeOfForumPosts only contains the relative string for the last date/time
Your current setup just appears a bit messy & cluttered and not very readable.
The Solution
To tidy it up a bit here's what I would do
public static class DateTimeExt
{
public static string ToRelativeTime(this DateTime value)
{
// you could move the entire implementation of `DisplayPostInMinutesOrSeconds` to here
return IsPostLessThanOneHour.DisplayPostInMinutesOrSeconds(value);
}
}
...
public PartialViewResult MostRecentMembersPosts()
{
return PartialView("pvMostRecentMembersPost", _imf.DisplayMostRecentForumPosts().ToList());
}
And then in your view
#model IEnumerable<MemberForumProperties>
#foreach (var props in Model)
{
<p>#props.ForumMemberDateTimePostedPost.ToRelativeTime()</p>
}