How can i close <tr>
and open <tr>
after 3 loop iterations? I have MVC 3 in .NET 4.0. How can I count loop iterations in MVC 3?
Current Code:
@foreach (var articleOnFirstPage in Model.ArticlesOnFirstSite)
{
<tr>
<td><div class="productsFrame"></div></td>
</tr>
}
I want to get this:
<tr>
<td><div class="productsFrame"></div></td>
<td><div class="productsFrame"></div></td>
<td><div class="productsFrame"></div></td>
</tr>
<tr>
<td><div class="productsFrame"></div></td>
<td><div class="productsFrame"></div></td>
<td><div class="productsFrame"></div></td>
</tr>
<tr>
<td><div class="productsFrame"></div></td>
<td><div class="productsFrame"></div></td>
<td><div class="productsFrame"></div></td>
</tr>
You could perform the following pornography in your view:
@model IEnumerable<Foo>
<table>
@foreach (var item in from i in Model.Select((value, index) => new { value, index }) group i.value by i.index / 3 into g select g)
{
<tr>
@foreach (var x in item)
{
<td><div class="productsFrame">@x.SomeProperty</div></td>
}
</tr>
}
</table>
or simply use view models and do the grouping in your controller action which obviously is what I would recommend you. The sole fact that you need to do this means that your view model is not adapted to your view's requirements which is to group results by 3. So adapt it. Don't pass IEnumerable<Foo>
to your view. Pass IEnumerable<MyViewModel>
where obviously MyViewModel will contain the necessary grouping so that in your views you could simply loop or since I hate writing for and foreach loops in views simply use display templates. They will take care of everything and your view will simply look like this:
<table>
@HtmlDisplayForModel()
</table>
Looks better than the initial pornography isn't it?
As requested in the comments section here's how I would implement this using view models.
As always in an ASP.NET MVC application you start by defining the view models that will reflect the requirements of your view (which I repeat are: show a table with 3 columns):
public class ItemViewModel
{
public string Title { get; set; }
}
public class MyViewModel
{
public IEnumerable<ItemViewModel> Items { get; set; }
}
then you move on to the controller that will fill and pass this view model to the view:
public class HomeController : Controller
{
public ActionResult Index()
{
// Obviously in a real world application the data is your domain model
// and comes from a repository or a service layer depending on the complexity
// of your application. I am hardcoding it here for the
// purposes of the demonstration
var data = Enumerable.Range(1, 30).Select(x => new { Title = "title " + x });
var model =
from i in data.Select((value, index) => new { value, index })
group i.value by i.index / 3 into g
select new MyViewModel
{
Items = g.Select(x => new ItemViewModel { Title = x.Title })
};
return View(model);
}
}
and finally you write the corresponding view (~/Views/Home/Index.cshtml
):
@model IEnumerable<MyViewModel>
<table>
@Html.DisplayForModel()
</table>
and the ~/Views/Home/DisplateTemplates/MyViewModel.cshtml
display template:
@model MyViewModel
<tr>
@Html.DisplayFor(x => x.Items)
</tr>
and finally the corresponding ~/Views/Home/DisplateTemplates/ItemViewModel.cshtml
display template:
@model ItemViewModel
<td>@Html.DisplayFor(x => x.Title)</td>
and that's pretty much it. Simple, clean, following good practices and conventions.
Obviously to bring this a step further you would introduce AutoMapper to perform the actual mapping between your domain models and view models and you will end up with a very elegant solution that will look like this:
public ActionResult Index()
{
IEnumerable<DomainModel> data = ...
var viewModel = Mapper.Map<IEnumerable<DomainModel>, IEnumerable<MyViewModel>>(data);
return View(viewModel);
}
or a step further:
[AutoMap(typeof(IEnumerable<DomainModel>), typeof(IEnumerable<MyViewModel>))]
public ActionResult Index()
{
IEnumerable<DomainModel> data = ...
return View(data);
}
Now we are starting to get into serious business.
The first thing that comes to mind is Phil Haack's better foreach loop
Using it you gain an index and can use it like
<ol>
@Model.Each(@<li>Item @item.Index of @(Model.Count() - 1): @item.Item.Title</li>)
</ol>
What you're specifically looking for should be something like:
@Model.ArticlesOnFirstSite.Each(@<td><div class="productsFrame"></div></td>@(@item.Index % 3 == 0 ? "</tr><tr>" : ""))
Something like this may work.
@{int i = 0;}
@foreach (var articleOnFirstPage in Model.ArticlesOnFirstSite)
{
@if ((i++ % 3) == 0) {
@if (i != 1) {
@:</tr>
}
@:<tr>
}
@:<td><div class="productsFrame"></div></td>
}
@if (i != 0) {
@:</tr>
}
And this is a brute force solution to your problem.
As other have suggested and as I suggest, you should change your methodology: use view models, group items by 3.
About how to correctly use model-view-controller pattern you can look on the web.
http://msdn.microsoft.com/en-us/library/gg416514(v=vs.98).aspx is a good start.
Like the others said, the best and prettiest solution is probably to do the grouping in the controller, but this might get the job done:
@for (int i = 0; i < Model.ArticlesOnFirstSite.Count; i += 3)
{
<tr>
@foreach (Article article in Model.ArticlesOnFirstSite.Skip(i).Take(3))
{
<td>@article.Title</td>
}
</tr>
}