in thymeleaf, how can write th:each to combine rows and columns? - thymeleaf

I want to write 4 columns in a row like this
<div class="row">
<div class="span3">Something</div>
<div class="span3">Something</div>
<div class="span3">Something</div>
<div class="span3">Something</div>
</div>
<div class="row">
<div class="span3">Something</div>
<div class="span3">Something</div>
<div class="span3">Something</div>
<div class="span3">Something</div>
</div>
data sizes are dynamic, so it can be 4, 8 or more.
this is archived in other template engine
{{#each list}}
{{#if #index % 4 == 0}}
<div class="row">
{{/if}}
<div class="span3">{{this.name}}</div>
{{#if #index % 4 == 0}}
</div>
{{/if}}
{{/each}}
but how can I archive this in thymeleaf?
I can't find the way because th:each is in tag(<div class="row"> or <div class="span3">) as attribute.

Model code
List<String> data = new ArrayList<String>();
data.add("1");
data.add("2");
data.add("3");
data.add("4");
data.add("5");
data.add("6");
data.add("7");
data.add("8");
model.addAttribute("datas", data);
Thymeleaf view code
<div th:each="data, row: ${datas}" th:with="numList=${#strings.listSplit('3,2,1,0', ',')}" th:if="${row.current} % 4 == 0" class="span3">
<span th:each="num : ${numList}" th:with="dataIndex=(${row.index} - ${num})" th:text="${datas[dataIndex]}">data</span>
</div>
Result
<div class="span3">
<span>1</span><span>2</span><span>3</span><span>4</span>
</div>
<div class="span3">
<span>5</span><span>6</span><span>7</span><span>8</span>
</div>
I used an array to solve this problem.
I think you will find a better way.

This can be done using numbers.sequence too. Set colCount to whatever number of columns you'd like:
<th:block th:with="colCount=${4}">
<div th:each="r : ${#numbers.sequence(0, datas.size(), colCount)}" class="row">
<div th:each="c : ${#numbers.sequence(0, colCount - 1)}" th:if="${r + c < datas.size()}" th:text="${datas.get(r + c)}" class="span3"></div>
</div>
</th:block>

I just created an account here to correct the accepted answer. The accepted answer works great so long as the "datas" being passed in is an array of consecutive integers. However, to make it work with any kind of data structure, "row.current" needs to change to "row.count", as follows:
<div th:each="data, row: ${datas}" th:with="numList=${#strings.listSplit('3,2,1,0', ',')}" th:if="${row.count} % 4 == 0" class="span3">
<span th:each="num : ${numList}" th:with="dataIndex=(${row.index} - ${num})" th:text="${datas[dataIndex]}">data</span>
</div>
If you use row.current, then it uses the actual item in the list, which is great in the example shown, but not so great for any other kind of data structure. Hope this helps.
EDIT:
I have to further refine this because the accepted answer also does not work if the number of items in the list is not evenly divisible by 4. Here is a better (though probably not perfect) solution:
<div th:each="data, row: ${datas}" th:with="numList=${ {3,2,1,0} }" th:if="${row.count % 4 == 0 or row.last}" class="span3">
<!-- Show all rows except the leftovers -->
<span th:each="num : ${numList}" th:with="dataIndex=(${row.index} - ${num})" th:if="${row.count % 4 == 0}" th:text="${datas[dataIndex]}">data</span>
<!-- Show the remainders (eg, if there are 9 items, the last row will have one item in it) -->
<span th:each="num : ${numList}" th:with="dataIndex=(${row.index} - ${num})" th:if="${row.last} and ${row.count % 4 != 0} and ${num < row.count % 4}" th:text="${datas[dataIndex]}">data</span>
</div>
This may be able to be refactored to eliminate one of the spans, but I have to move on now.

th:each can be used on any element basically. So something like this:
<div class="row" th:each="row : ${rows}">
<div class="span3" th:each="name : ${row.names}" th:text="${name}">Something</div>
</div>

<div class="row" th:each="museum,step : ${museums}">
<span th:if="${step.index % 2 == 0}">
<div class="column" style="background-color:#aaa;" >
<h2 th:text="'Name: ' + ${museum.name}"></h2>
<p th:text="'Address: ' + ${museum.address}"></p>
<p th:text="'Capacity: ' + ${museum.capacity}"></p>
</div>
</span>
<span th:if="${step.index < 3 and step.index %2 == 0} ">
<div class="column" style="background-color:#bbb;">
<h2 th:text="'Name: ' + ${museums[step.index+1].name}"></h2>
<p th:text="'Address: ' + ${museums[step.index+1].address}"></p>
<p th:text="'Capacity: ' + ${museums[step.index+1].capacity}"></p>
</div>
</span>
</div>
This is how I would approach the problem. Using a simple odd or even trick

I had the same problem and I saw the accepted answer but it was not easy enough for me so I tried a new solution and it does the job with much less code and much easier to understand
this is what I came up with:
// rowNum your situation, but be aware that you have to change all its instances to
<div th:each="i: ${#numbers.sequence(1, rowNum)}" class="row">
<div th:each="j: ${#numbers.sequence(((i-1) * rowNum), ((i-1) * rowNum) + (rowNum - 1)) }"
th:if="${j < #lists.size(list)}"
class="col col-md-5">
<span th:text=${list.get(j)}>item</span>
</div>
</div>

These are all so complicated when the answer is so simple as answered here.
<div colCount=${4} class="row">
<div class="span3" th:each="data : ${data}">...</div>
</div>
It limits the for each to 4 element blocks. I'm guessing that's a relatively new feature. Pretty cool.

Related

Grouping/GROUP BY With LINQ in MVC

I am new in MVC. In this page; I list the projects belonging to the "unit name" by loop. And what I want to do: I want to group projects belonging to the same "unit name". Each project has a "unit name" to which it belongs. There can be more than one project belonging to one unit. So I want to group it.
My cshtml code is as follows:
#if (Model.Projects.Any())
{
foreach (var item in Model.Projects.ToList())
{
<div class="ProjectPartialBody" data-id="#item.ID">
<div class="portlet box blue">
<div class="portlet-title">
<div class="caption">
<span><i class="glyphicon glyphicon-asterisk"></i> #item.tbl_Unit.Name</span>
</div>
<div class="tools">
</div>
<div class="actions">
Details
</div>
</div>
<div class="portlet-body">
<span><i class="fa fa-file"></i> #item.Name</span>
<span><i class="fa fa-calendar"> Contract Start and End Dates: #(item.ContractStartDate != null ? item.ContractStartDate.Value.ToString("dd.MM.yyyy") : "-") / #(item.ContractEndDate != null ? item.ContractEndDate.Value.ToString("dd.MM.yyyy") : "-")</i></span>
<div class="well">
<div class="form-group">
<label>Cash Completion Rate: %#item.CashCompletionRate</label>
<div class="progress progress-striped active">
<div class="progress-bar progress-bar-info" role="progressbar" style="width: #(item.CashCompletionRate)%;"></div>
</div>
</div>
</div>
<div class="well">
<div class="form-group">
<label>Physical Completion Rate: %#item.PhysicalCompletionRate</label>
<div class="progress progress-striped active">
<div class="progress-bar progress-bar-info" role="progressbar" style="width: #(item.PhysicalCompletionRate)%;"></div>
</div>
</div>
</div>
</div>
</div>
</div>
}
}
And the view of the page is as follows (my view is in Turkish language but you'll understand what I am talking about):
My Page View
Can you please help me? And if there is any other code block you want to insert, please tell me.
You first need to group by in your for statement:
foreach(var unitGroup in Model.Projects.GroupBy(g => g.tbl_Unit.Name))
{
//...your MVC markup here
}
Now you actualy need to change the markup as you would need to put in another foreach loop that would generate markup for projects.
So something simillar to this.
<div class="ProjectPartialBody" data-id="#item.ID">
<div class="portlet box blue">
<div class="portlet-title">
<div class="caption">
<span><i class="glyphicon glyphicon-asterisk"></i> #item.tbl_Unit.Name</span>
</div>
<div class="tools">
</div>
<div class="actions">
Details
</div>
</div>
#foreach(var project in unitGroup.ToList())
{
<div class="portlet-body">
<span><i class="fa fa-file"></i> #project.Name</span>
<span><i class="fa fa-calendar"> Contract Start and End Dates: #(project.ContractStartDate != null ? project.ContractStartDate.Value.ToString("dd.MM.yyyy") : "-") / #(project.ContractEndDate != null ? project.ContractEndDate.Value.ToString("dd.MM.yyyy") : "-")</i></span>
<div class="well">
<div class="form-group">
<label>Cash Completion Rate: %#project.CashCompletionRate</label>
<div class="progress progress-striped active">
<div class="progress-bar progress-bar-info" role="progressbar" style="width: #(project.CashCompletionRate)%;"></div>
</div>
</div>
</div>
<div class="well">
<div class="form-group">
<label>Physical Completion Rate: %#project.PhysicalCompletionRate</label>
<div class="progress progress-striped active">
<div class="progress-bar progress-bar-info" role="progressbar" style="width: #(project.PhysicalCompletionRate)%;"></div>
</div>
</div>
</div>
</div>
}
</div>
</div>
There should be some errors here as I am not very familliar with your model but this should be the general idea behind it.

Unbalanced DIV Closing DIV with MVC Razor For Each Loop

I'm using Bootstrap 3 on my site and I am using an MVC Razor For Each Loop to retrieve all of the child items on a blog homepage as follows:
#{int i = 0;}
#foreach (var subPage in Model.Content.AncestorOrSelf(2).Children.OrderBy("createDate descending"))
{
if (i % 2 == 0)
{
#:<div class="row row-eq-height">
}
<div class="col-sm-12 col-md-6">
</div><!-- col-sm-12 col-md-6 -->
if (i % 2 != 0)
{
#:</div><!-- row row-eq-height -->
}
i++;
}
This works fine when I have an even number of child pages however when I have an odd number of child pages, it's not hitting the if % 2 != 0 block which means the last row doesn't get closed which then breaks the flow of the remainder of the page.
I tried to fix this by adding the following if statement after the For Each loop hoping that if the final value of i was not 2, it would add a closing DIV tag as needed but instead that causes an inbalanced DIV tag error.
#if (i !% 2 == 0)
{
</div><!-- row row-eq-height -->
}
Anyone got any thoughts or ideas? If I have an even number, it works just fine and the page flow is as expected.
Your foreach loop needs to be inside a the containing element (row) and then withing the loop, close and start a new row for every second child element (column)
#{int i = 0;}
<div class="row row-eq-height">
#foreach (var subPage in Model.Content.AncestorOrSelf(2).Children.OrderBy("createDate descending"))
{
if (i > 0 && i % 2 == 0)
{
#:</div><div class="row row-eq-height">
}
<div class="col-sm-12 col-md-6">xxxxx</div>
i++;
}
</div>
The output, assuming you have 5 items in the collection, will be
<div class="row row-eq-height">
<div class="col-sm-12 col-md-6">xxxxx</div>
<div class="col-sm-12 col-md-6">xxxxx</div>
</div>
<div class="row row-eq-height">
<div class="col-sm-12 col-md-6">xxxxx</div>
<div class="col-sm-12 col-md-6">xxxxx</div>
</div>
<div class="row row-eq-height">
<div class="col-sm-12 col-md-6">xxxxx</div>
</div>

Using Count method on a for loop in Razor View

The issue I am having is that I am trying to generate a series of text boxes using a for loop to prevent verbose code.
for (int i = 0; i < Model.frames(); i++)
{
<div class="form-group">
<div class="control-label col-sm-1 text-left ">
frame Number
</div>
<div>
<div class="col-sm-1">
#Model.frames
</div>
</div>
</div>
}
The "count" is not recognized for Integer. Any help is appreciated.

How do I disable Razor being clever with opening and closing tags?

In a loop I need to wrap divs after first one into mechanism that hides them and shows when expanded, easy right.
//above this lives code that draws a product section
if (product.DisplayOrder == 1 && Model.Count > 1)
{
<div class="arrow-with-text" style="cursor: pointer;">
<div class="arrow-right"></div>
<div class="arrow-down" style="display: none"></div>
<div style="margin-left: 2em;margin-top: -1.3em;font-size: 1.2em;color: #2280c4;">Other available Wills…</div>
</div>
<div class="other-wills" style="display: none">
}
else (product.DisplayOrder == Model.Count)
{
</div>
}
What I expected was all sections that go after first being wrapped in other-wills div and then being closed on last one. What it actually get's rendered to is this.
<div class="arrow-with-text" style="cursor: pointer;">
<div class="arrow-right"></div>
<div class="arrow-down" style="display: none"></div>
<div style="margin-left: 2em;margin-top: -1.3em;font-size: 1.2em;color: #2280c4;">Other available Wills…</div>
</div>
<div class="other-wills" style="display: none">
}
else (product.DisplayOrder == Model.Count)
{
</div>
<div class="sections-wrapper">
which isn't exactly what I expected (it reendered }else product.DisplayOrder == Model.Count){ as text).
How do I make razor interpret my closing of if statement and else correctly?
Use
#Html.Raw("</div>")
and
#Html.Raw("<div class=\"your-class\">")
for the 'stray' dynamic divs

HAML + Bootstrap div.row and div.spanX, new row every N elements

I found this question, which has an answer which is exactly what I tried to start. However, it does not yield the intended results.
Given array #items, I want to create a new row for every 3 items. This is currently my markup:
- #items.each_with_index do |item, index|
- if index % 3 == 0
%div.row-fluid
%div.span4
%div.item-container
use item
This results in html being rendered like this
<div class="row-fluid"></div>
<div class="span4">...</div>
<div class="span4">...</div>
<div class="span4">...</div>
<div class="row-fluid"></div>
<div class="span4">...</div>
<div class="span4">...</div>
<div class="span4">...</div>
Of course, what I want is:
<div class="row-fluid">
<div class="span4">...</div>
<div class="span4">...</div>
<div class="span4">...</div>
</div>
<div class="row-fluid">
<div class="span4">...</div>
<div class="span4">...</div>
<div class="span4">...</div>
</div>
Where am I going wrong here?
Divide collection using Array#in_groups_of an then iterate it:
- #items = (1..20).to_a
- #items.in_groups_of(3, false).each_with_index do |group, index|
.row-fluid
- group.each do |item|
.span4= "group: #{index}; item: #{item}"
hint: You can leave off the tag definition and have it default to %div. %div.span4 became just .span4 and so on.

Resources