Conditional layout of multiple divs in Thymeleaf - html

I am struggling to format this div structure using th:each in Thymleaf.
The desired html format is as follows
Desired HTML
<div class="row interest-featured"> <!--Parent Div -->
<div class="col-md-4 interest">
</div>
<div class="col-md-4 interest">
</div>
<div class="col-md-4 interest">
</div>
<div class="col-md-4 interest">
</div>
</div>
<div class="row interest-featured"> <!--Parent Div -->
<div class="col-md-4 interest">
</div>
<div class="col-md-4 interest">
</div>
<div class="col-md-4 interest">
</div>
<div class="col-md-4 interest">
</div>
</div>
Progress until now
<div th:each="interest,status : ${interest}" th:class="${(status.index + 1 % 3) == 0}? 'row interest-featured' :''">
<div class="col-md-4 interest">
</div>
</div>
Any help would be highly appreciated. Thanks
PS: HTML texts are removed for brevity

I think I understand the issue here. If you have a flat list and you want to display it in a Bootstrap grid, you'll have to create new rows for each n elements.
The solution to this problem isn't as clean as a normal th:each, but you'll have to use it twice and have proper th:if statements on when you want to display it.
For example, for the .row element, you want to only show it for elements with index 0, 3, 6, ... (if you want a 1/3th column grid). This means you should do:
<div th:each="interest,rowStatus : ${interests}" class="row interest-featured" th:if="${rowStatus.index % 3} == 0">
</div>
Now, for the children of the row you'll have to iterate again over your collection, but filter it so that for the first row you only show elements with index 0-2 for the second row the elements 3-5, ... .
To do that you use another th:if:
<div th:each="interest,rowStatus : ${interests}" class="row interest-featured" th:if="${rowStatus.index % 3} == 0">
<div th:each="interest,interestStatus : ${interests}" class="col-md-4 interest" th:text="${interest}"
th:if="${interestStatus.index lt rowStatus.index + 3 and interestStatus.index ge rowStatus.index}"></div>
</div>
If you don't want to use a cumbersome template like this, you can always just iterate over the column itself. If you're using Bootstrap, it will automatically create columns that do not fit within a row on a new line, for example:
<div class="row">
<div class="col-md-4">Column 1</div>
<div class="col-md-4">Column 2</div>
<div class="col-md-4">Column 3</div>
<div class="col-md-4">Column 4</div><!-- This item will be on a new row because the grid does only allow 12 spaces -->
</div>
However, there are some differences between both approaches when the content of the column is variable in height.
NOTE: You're also using the interest variable twice. The collection is named ${interest}, but the result variable is also called interest. You probably have to rename one of those.

You use object.
html
<div class="source_list" th:each="history : ${historys}">
<label>date : <span th:text="${history.date}">2016.06.27</span></label>
<br />
<label>description : </label><span th:text="${history.description}">2016.06.27</span>
<br /> <br />
</div>
contorller
#RequestMapping(value = "/settings/history/{companyIdx}")
public ModelAndView showHistory(ModelAndView mav, #PathVariable int companyIdx, HttpServletRequest request) {
String language = CookieUtil.getCookieValue(request, "lang");
Company company = companyService.findCompanyByIdx(companyIdx);
List<CompanyHistory> historys = companyService.findHistoryByLanguage(company, language);
mav.addObject("company", company);
mav.addObject("historys", historys);
mav.setViewName("/company/company_settings_history");
return mav;
}
dto
#Entity(name = "company_history")
public class CompanyHistory {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int idx;
#ManyToOne
#JoinColumn(name = "company_id")
private Company company;
private String date;
private String description;
...
}

Related

Angular - can i limit the no of dynamically created cards on the page?

<div class="row">
<div class=" col-xl-8 offset-xl-2">
<div class=" row">
<div *ngFor="let pokemon of pokemons | searchFilter: filterName:'name'; let i = index " class=" col-md-6">
<div style="border-radius: 15px 50px;" class="card mb-3 bg-{{getType(pokemon)}}"
[routerLink]="['/view', pokemon.name]">
<div class="card-body">
<div class="row">
<div class="col-7 col-sm-8">
<h3 class="card-title">{{pokemon.name | titlecase}}</h3>
<span *ngFor="let type of pokemon.types "
class="badge border rounded-pill me-1 bg-{{getType(pokemon)}}">
{{type.type.name | titlecase}}
</span>
</div>
<div class="col-5 col-sm-4">
<img src="https://pokeres.bastionbot.org/images/pokemon/{{pokemon.id}}.png"
class="img-fluid" alt="...">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm text-center">
<button class="btn draw-border" (click)="loadMore()">
<div class="spinner-border spinner-border-sm" role="status" *ngIf="loading"></div>
{{loading ? 'Loading...' : 'Load More'}}
</button>
</div>
</div>
</div>
This is my code nothing special just a simple ngfor , currently the ngfor dynamically creates about 30 cards (don't know exactly) before the button to load more appears , I'm using infinite scrolling btw , so can I limit the number of cards created to about say 10-15 before ,and then the button to load more appears , is this doable?
Iā€™m making the assumption that the load more button will fetch more results from the api if it is currently showing all results.
If you are initially loading 30 but only want to show 10 initially and 10 more each button press o would give two possible solutions.
Update API
If you have access to the Api and can pass in Params for pagination results. I would go that route first, since it is the cleanest. If not maybe the second option instead.
Or
Update the component to manage your results array. It can be done as so in the TypeScript file.
pokemons: Pokemon[] =[]
pokemonBuffer: Pokemon[] = [] // holds the Pokemon you have but not yet showing
countToLoad = 10;
ngOnInit() {
this.loadMore();
}
loadMore() {
if(this.pokemonBuffer.length < this.countToLoad) {
this.http.get(.....)
.subscribe(results => {
this.pokemonBuffer.concat(results);
this.pokemon.push(this.pokemonBuffer.splice(0, this.countToLoad)
})
} else { // the buffer has enough, take it from there
this.pokemon.push(this.pokemonBuffer.splice(0, this.countToLoad)
}
}
If you would like to change this just add a 'limit' query parameter to the GET request, e.g. ?=60. You can use 'offset' to move to the next page, e.g. ?limit=60&offset=60.
edit :
for this api this works
from the documentation ^^^

Bootstrap - How can I move a row's div into another row?

I'm learing bootstrap and I'm working with the Grid System since I'm working to a responsive website with lots of tables.
<div class="row">
<div class="col-md-1">A</div>
<div class="col-md-1">B</div>
<div class="col-md-1">C</div>
</div>
<div class="row">
<div class="col-md-1">D</div>
<div class="col-md-1">E</div>
<div class="col-md-1">F</div>
</div>
I will start with this bootstrap "table". I want to move a div of the first row into the second row. Example:
<div class="row">
<div class="col-md-1">A</div>
<div class="col-md-1">B</div>
</div>
<div class="row">
<div class="col-md-1">C</div>
<div class="col-md-1">D</div>
<div class="col-md-1">E</div>
<div class="col-md-1">F</div>
</div>
I think you are misunderstanding how Bootstrap works. Its grid does not work by "moving a row's div into another row". What happens is that the grid is based off of 12 columns per row. If you want 3 elements in 1 row, you would set each one with a class of col-xx-4 (xx is either lg, md, sm or xs), since 12/4 = 3. Each element takes up 4 of the 12 columns.
For your issue at hand - you can put all 6 elements in the same row and adjust the number of columns each one spans based on the screen size using lg, md, sm and xs. Below is an example that does what I believe you are trying to do my manually moving a row's div, but this way utilizes Bootstrap's grid system correctly.
<div class="container">
<div class="row">
<div class="col-sm-4 col-xs-6">
A
</div>
<div class="col-sm-4 col-xs-6">
B
</div>
<div class="col-sm-4 col-xs-6">
C
</div>
<div class="col-sm-4 col-xs-6">
D
</div>
<div class="col-sm-4 col-xs-6">
E
</div>
<div class="col-sm-4 col-xs-6">
F
</div>
</div>
</div>
Here is a Demo JSFiddle
You can slide the screen to make it bigger/smaller and see the 2 rows of 3 collapse to 3 rows of 2, and vice-versa.
If I got what you wanted to ask, the simplest way to do it is by:
1) Checking the browser's width.
2) Then, get the inner element you want to move.
3) Add it to where you want to take it.
4) And remove it from it was before.
The code HTML:
<div class="row" id="row1">
<div class="col-md-1">
A
</div>
<div class="col-md-1">
B
</div>
<div class="col-md-1" id="moveto">
</div>
</div>
<div class="row" id="row2">
<h4>--</h4>
<div class="col-md-1" id="movefrom">
C
</div>
<div class="col-md-1">
D
</div>
<div class="col-md-1">
E
</div>
<div class="col-md-1">
F
</div>
</div>
The JS code:
$(document).ready(function() {
var x = document.getElementById("movefrom").innerHTML // Get the inner HTML (C in this case)
if (window.innerWidth <= 768) { // Check the browser's width
document.getElementById("moveto").innerHTML = x; // Move the element to where you want (moveto id)
document.getElementById("movefrom").innerHTML = "" // Replace the element by an empty string.
}
});
This was the most simple way I found to make it work.
Any feedback is great!
Good luck
It is my hobby to answer too late.
Do this if you want it to respond to resize and work both ways:
window.addEventListener("resize", function(event) {
//grab what to replace
var x = document.getElementById("social")
.innerHTML;
//for when going to smaller screen
if (window.innerWidth <= 560 && !x == "") {
document.getElementById("social-mobile")
.innerHTML = x;
document.getElementById("social")
.innerHTML = "";
}
var y = document.getElementById("social")
.innerHTML;
// if we are at bigger screen and node is "", then put it back
if (window.innerWidth >= 560 && y == "") {
document.getElementById("social")
.innerHTML = document.getElementById("social-mobile")
.innerHTML;
document.getElementById("social-mobile")
.innerHTML = "";
}
});
šŸ˜Ž
Well, I have this simple jQuery function..
(function($){
$.fn.moveTo = function(selector){
return this.each(function(){
var cl = $(this).clone();
$(cl).prependTo(selector);
$(this).remove();
});
};
});
usage $('.element').moveTo('.element2');, you can also change it to appendTo, or send a parameter to change it.

Angular + Typescript: Add to array, ng-repeat adds blank row

Trying to add a new object to an array and have it show up in an ng-repeat div. I add the object, look at the array in Chrome's dev tools and can see the new object in there, but the ng-repeat section just has a blank row. I've search throughout StackOverflow, implemented many solutions and still have this problem.
HTML:
<div ng-repeat="subItem in ec.mainItem.SubItems">
<div class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-3 h4">
<button class="btn-link">{{subItem.Name}}</button>
</div>
<div class="col-md-6">
{{subItem.Description}}
</div>
</div>
</div>
</div>
</div>
ItemCtrl.ts
module MainItemApp {
'use strict'
export class MainItemEntryCtrl implements IMainItemCtrl {
static $inject = ['$scope', '$routeParams', 'ItemSvc'];
public mainItem: MainItem; //this has an array on it named SubItems
public sub_item_name: string;
public sub_item_description: string;
public sub_item_date: Date;
public sub_item_start_date: Date;
public sub_item_end_date: Date;
public sub_item_descriptive_location: string;
public sub_item_short_location: string;
constructor($scope, $routeParams: IItemEntryRouteParams, itemSvc: ItemSvc) {
this.id= this.getId($routeParams.id);
if (this.id!= undefined) {
itemSvc.getMainItem(this.id).then(
(data) => this.mainItem = data);
}
}
addSubItem() {
var subItem = {
id: '',
name: this.sub_item_name,
itemDate: this.sub_item_date,
startDate: this.sub_item_start_date,
endDate: this.sub_item_end_date,
description: this.sub_item_description,
descriptiveLocation: this.sub_item_descriptive_location,
shortLocation: this.sub_item_short_location,
geoLat: '',
geoLong: '',
groupItems: []
};
this.mainItem.SubItems.push(subItem);
}
}
When I click a button, AddSubItem runs, the item gets created and after the push, I can hover over subItems and see the array has two objects, the one I loaded with and the one I pushed, but the HTML that's generated gives me this:
<div ng-repeat="subItem in ec.mainItem.SubItems">
<div class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-3 h4">
<button class="btn-link">Some Item</button>
</div>
<div class="col-md-6">
Some Description
</div>
</div>
</div>
</div>
</div>
<div ng-repeat="subItem in ec.mainItem.SubItems">
<div class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-3 h4">
<button class="btn-link"></button>
</div>
<div class="col-md-6">
</div>
</div>
</div>
</div>
</div>
Any ideas? Any help would be greatly appreciated!
Sorry, bad post, I figured it out (isn't it always like that, as soon as you make a fool of yourself and ask, you figure it out). My data was coming back from the API with capitalized property names (i.e. Name, Description) and I was adding an object where the names were lowercase (i.e. name, description) so of course there was nothing to show in the newly added row. So I synced up the names coming from my API with the names in the Typescript classes and everything is working now. So of course when I searched for the answer, I couldn't find it: no one posted "Hey dummy! Check the casing on your properties!"
So I think I will leave this up just in case someone else runs across the same problem and it sparks them to check their casing.

Bootstrap grid layout issue when populating with items

I'm new to MVC5 and especially to using Bootstrap. I'm trying to create a website in which the first page should feature a grid with student images. Right now it looks like this. I want to have 5 images per row, but I haven't manage to figure out how(it was either 4, or 6).
Also when there is no space between the rows and the images stuck together.
I am populating the grid with items from my Student Model.
<div class="col-md-10">
<div class="row">
#foreach (var item in Model.Students)
{
<div class="col-md-2">
<a href="#Url.Action("Edit", "Students", new {studentId = item.StudentId})">
<img src="../../#item.ProfileImagePath" alt="Profile Image" />
</a>
</div>
}
</div>
</div>
I didn't fully understand how does the bootstrap grid work, especially when I'm populating it with dynamic data. The website looks like this now
http://imgur.com/5fLRZeM
You have
<div class="row">
#foreach (var item in Model.Students)
{
<div class="col-md-2">
...
</div>
}
</div>
Which generates one long <row>. But there is no auto-wrapping, you'll have to break this up into rows yourself.
Assuming that Students is a List<>
// roughly, untested
#for(int r = 0; r < Model.Students.Count; r += 5)
{
<div class="row">
#for (int s = r; s < r+5; s += 1)
{
<div class="col-md-2">
// do stuff with Model.Students[s]
</div>
}
</div>
}

Parse html page with mechanize to receive the appropriate array

I have the following html code on the page received by mechanize (agent.get):
<div class="b-resumehistorylist-views">
<!-- first date start-->
<div class="b-resumehistory-date">date1</div>
<div class="b-resumehistory-company">
<div class="b-resumehistory-time">time1</div>
company1</div>
<!-- second date start -->
<div class="b-resumehistory-date">date2</div>
<div class="b-resumehistory-company">
<div class="b-resumehistory-time">time2</div>
company2
</div>
<div class="b-resumehistory-company">
<div class="b-resumehistory-time">time3</div>
company3</div>
<div class="b-resumehistory-company">
<div class="b-resumehistory-time">time4</div>
company4</div>
<div class="b-resumehistory-company">
<div class="b-resumehistory-time">time5</div>
company5</div>
<div class="b-resumehistory-company">
<div class="b-resumehistory-time">time6</div>
company6</div>
<div class="b-resumehistory-company">
<div class="b-resumehistory-time">time7</div>
company7</div>
...
</div>
I need to search inside the div with class="b-resumehistorylist-views" each date.
Then find all divs between two div-dates and link each item to this particular date.
The problem is that each item (div class = b-resumehistorylist-views) is not inside div=b-resumehistorylist-views.
At final stage I need to receive the following array:
array = [ [date1, time1, company1, companylink1], [date2, time2, company2, companylink2], [date2, time3, company3, companylink3],[date2, time4, company4, companylink4] ]
I know that I must use method search with text() option, but I cannot find the solution.
My code right now can parse all companies information between div class=b-resumehistory-company, but I need to find right date.
It would be the same thing as before, just some of the class attributes have been changed:
doc = agent.get(someurl).parser
doc.css('.b-resumehistory-company').map{|x| [x.at('./preceding-sibling::div[#class="b-resumehistory-date"][1]').text , x.at('.b-resumehistory-time').text, x.at('a').text, x.at('a')[:href]]}