I am using thymeleaf to develop a website and it's been working fine for me so far however when I want to present multiple items in single in the table it will instead add extra seperate cells on the row where the values exist. I have no solution to that problems so far, if anyone else might see what I'm missing I'd greatly appreciate it.
Thanks in advance!
Code(EDITED)
Here I first check if there are handlers(in the database) then I put them all in a list using the foreach, but I have no way of putting all the items in the single cell(or table data represented here by the tag ). I've put the logic inside the tag which worked somewhat well however rows with no data get extra cells, like in the picture below.
<td > th:if="${place.getHandler} != null" th:each="handlerList : ${place.getHandler}" th:text="${handlerList.name}"></td>
New Code
<td> <span th:if="${place.getHandler} != null" th:each="handlerList : ${place.getHandler}" th:text="${handlerList.name}"></span></td>
The entire code
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" class="has-navbar-fixed-top">
<head th:replace="common_fragments/header :: header">
<meta charset="utf-8">
<link rel="stylesheet" href="../../../../public/css/font-awesome.min.css"/>
<link rel="stylesheet" href="../../../../public/css/bulma_custom.min.css"/>
</head>
<body>
<div id="navbar-top">
<nav th:replace="logged_in/admin/fragments/navbar :: nav"></nav>
</div>
<main>
<section class="section">
<div class="container">
<h1 class="title">
matchade studenter
</h1>
<hr>
<div class="content is-medium">
<table id="table" class="table is-bordered is-narrow is-hoverable">
<thead>
<tr>
<th>Student</th>
<th>Student email</th>
<th>Student mobilnummer</th>
<th>Enhet</th>
<th>Handledare</th>
<th>Handledare email</th>
<th>Handledare mobilnummer</th>
</tr>
</thead>
<tbody>
<tr th:each="place : ${places}">
<td th:text="${place.student.studentData.name}"></td>
<td th:text="${place.student.studentData.email}"></td>
<td th:if="${place.student.studentData.phoneNumber} != ''" th:text="${place.student.studentData.phoneNumber}"></td>
<td th:if="${place.student.studentData.phoneNumber} == ''" >
<p class="icon has-text-danger">
<i class="fa fa-lg fa-times"></i>
</p>
</td>
<td th:text="|${place.unit.name} (Regioner: ${place.unit.municipality.getRegionNamesString}, Kommuner: ${place.unit.municipality.name})|"></td>
<td> <span th:if="${place.getHandledare} != null" th:each="handledareList : ${place.getHandledare}" th:text="${handledareList.name}"></span></td>
<td th:if="${place.getHandledare} != null" th:text="${place.getHandledare[0].email}"></td>
<td th:if="${place.getHandledare} == null">
<p class="icon has-text-danger">
<i class="fa fa-lg fa-times"></i>
</p>
</td>
<td th:if="${place.getHandledare} == null">
<p class="icon has-text-danger">
<i class="fa fa-lg fa-times"></i>
</p>
</td>
<div th:if="${place.getHandledare} != null">
<td th:if="${place.getHandledare[0].phoneNumber} != ''" th:text="${place.getHandledare[0].phoneNumber}"></td>
</div>
<div th:if="${place.getHandledare} == null">
<td >
<p class="icon has-text-danger">
<i class="fa fa-lg fa-times"></i>
</p>
</td>
</div>
<div th:if="${place.getHandledare} != null">
<td th:if="${place.getHandledare[0].phoneNumber} == '' ">
<p class="icon has-text-danger">
<i class="fa fa-lg fa-times"></i>
</p>
</td>
</td>
</div>
</tr>
</tbody>
</table>
<br>
<button class="button is-large is-success" id="download-button">ladda ner matchning resultat</button>
<br>
<br>
</div>
</div>
</section>
</main>
<footer th:replace="common_fragments/footer :: footer"></footer>
<script>
function htmlToCSV(html, filename) {
var data = [];
var rows = document.querySelectorAll("table tr");
for (var i = 0; i < rows.length; i++) {
var row = [], cols = rows[i].querySelectorAll("td, th");
for (var j = 0; j < cols.length; j++) {
row.push(cols[j].innerText);
}
data.push(row.join(";"));
}
downloadCSVFile(data.join("\n"), filename);
}
</script>
<script>
function downloadCSVFile(csv, filename) {
var csv_file, download_link;
csv_file = new Blob(["\uFEFF"+csv], {type: "text/csv"});
download_link = document.createElement("a");
download_link.download = filename;
download_link.href = window.URL.createObjectURL(csv_file);
download_link.style.display = "none";
document.body.appendChild(download_link);
download_link.click();
}
</script>
<script>
document.getElementById("download-button").addEventListener("click", function () {
var html = document.querySelector("table").outerHTML;
htmlToCSV(html, "matchning.csv");
});
</script>
</body>
</html>
You can move your Thymeleaf logic from the <td> tag into a tag inside the <td> tag - for example, a <span>:
<td>
<span th:if="${place.getHandler} != null"
th:each="handlerList : ${place.getHandler}"
th:text="${handlerList.name}"></span>
</td>
From there you can add whatever CSS you may need to format the spans.
If you have extra <td> cells you need to suppress, then move the th:if expression to inside the <td> tag:
<td th:if="${place.getHandler} != null">
<span th:each="handlerList : ${place.getHandler}"
th:text="${handlerList.name}"></span>
</td>
Related
I'm developing an Automation test using Selenium WebDriver and Java, I need to assured that there are items in the web table, and select one of those items, but the ID is dynamic.
HTML code:
<table class="datagrid-btable" cellspacing="0" cellpadding="0" border="0" style="table-layout: auto;"> <tbody>
<tr id="datagrid-row-r4-2-0" datagrid-row-index="0" class="datagrid-row datagrid-row-selected">
<td field="PLANT_CODE" style="display:none;">
<div style="text-align: left;" class="datagrid-cell datagrid-cell-c4-PLANT_CODE">1001</div>
</td>
<td field="PLANT_NM">
<div style=";text-align:center;" class="datagrid-cell datagrid-cell-c4-PLANT_NM">TESTE1</div>
</td>
<td field="PU_NAME" style="display:none;">
<div style=";text-align:left;" class="datagrid-cell datagrid-cell-c4-PU_NAME"></div>
</td>
<td field="SUPPLIER_CODE">
<div style=";text-align:center;" class="datagrid-cell datagrid-cell-c4-SUPPLIER_CODE">SUP001AR</div>
</td>
<td field="SUPPLIER_NM">
<div style=";text-align:center;" class="datagrid-cell datagrid-cell-c4-SUPPLIER_NM">SUPPLIER 001 AR</div>
</td>
<td field="ITEM_CODE">
<div style=";text-align:center;" class="datagrid-cell datagrid-cell-c4-ITEM_CODE">ITEM001AR</div>
</td>
<td field="ITEM_NM">
<div style=";text-align:left;" class="datagrid-cell datagrid-cell-c4-ITEM_NM">ITEM1 AR</div>
</td>
<td field="WRHOUSNG_NO" style="display:none;">
<div style=";text-align:center;" class="datagrid-cell datagrid-cell-c4-WRHOUSNG_NO"></div>
</td>
<td field="ORDE_NO" style="display:none;">
<div style=";text-align:center;" class="datagrid-cell datagrid-cell-c4-ORDE_NO"></div>
</td>
</tr>
<tr id="datagrid-row-r4-2-1" datagrid-row-index="1" class="datagrid-row">
<td field="PLANT_CODE" style="display:none;">
<div style="text-align: left;" class="datagrid-cell datagrid-cell-c4-PLANT_CODE">1001</div>
</td>
<td field="PLANT_NM">
<div style=";text-align:center;" class="datagrid-cell datagrid-cell-c4-PLANT_NM">BOCAR LERMA</div>
</td>
<td field="PU_NAME" style="display:none;">
<div style=";text-align:left;" class="datagrid-cell datagrid-cell-c4-PU_NAME"></div>
</td>
<td field="SUPPLIER_CODE">
<div style=";text-align:center;" class="datagrid-cell datagrid-cell-c4-SUPPLIER_CODE">SUP001AR</div>
</td>
<td field="SUPPLIER_NM">
<div style=";text-align:center;" class="datagrid-cell datagrid-cell-c4-SUPPLIER_NM">SUPPLIER 001 AR</div>
</td>
<td field="ITEM_CODE">
<div style=";text-align:center;" class="datagrid-cell datagrid-cell-c4-ITEM_CODE">ITEM001AR</div>
</td>
<td field="ITEM_NM">
<div style=";text-align:left;" class="datagrid-cell datagrid-cell-c4-ITEM_NM">ITEM1 AR</div>
</td>
<td field="WRHOUSNG_NO" style="display:none;">
<div style=";text-align:center;" class="datagrid-cell datagrid-cell-c4-WRHOUSNG_NO">PUR1</div>
</td>
<td field="ORDE_NO" style="display:none;">
<div style=";text-align:center;" class="datagrid-cell datagrid-cell-c4-ORDE_NO">PUR1</div>
</td>
</tr> </tbody> </table>
I tried "My code":
WebElement tbody = driver.findElement(By.xpath("//*[#class='datagrid-body']/tbody/tr"));
In this case, my request is returning two rows so how can I count the rows and select one of these?
Thanks
Try this step for get you want.
1. Table initialize
In your case, the table has the class datagrid-btable, the way to initialize it :
WebElement tbl = driver.findElement(By.className("datagrid-btable"));
2. Row initialize
The name tag for the web table row in general is tr, the way to initialize it :
List<WebElement> rows = tbl.findElements(By.tagName("tr"));
You can get rowCount with :
int count = rows.size();
System.out.println("count rows :" +count);
3. Column initialize
The name tag for the web table column in general are th or td, the way to initialize it :
td tag
List<WebElement> cols = rows.get(rowIndex).findElements(By.tagName("td"));
So you can select particular cell by :
String cell = cols.get(indexCol).getText();
Example for select col1 in row1 :
List<WebElement> cols = rows.get(0).findElements(By.tagName("td"));
String cell = cols.get(0).getText();
System.out.println("cell value :" +cell);
Or try this iteration for select all cell table :
for(int i=0; i<rows.size(); i++) {
//check column each in row, identification with 'td' tag
List<WebElement> cols = rows.get(i).findElements(By.tagName("td"));
//column iteration
for(int j=0; j<cols.size(); j++) {
System.out.println("row " +(i+1) +" col " +(j+1) +" : " +cols.get(j).getText());
}
}
You can get count of the rows using findElements method:
List<WebElement> rows = driver.findElements(By.cssSelector(".datagrid-btable tr.datagrid-row"));
System.out.println(rows.size());
Easy way to select row with specific text using xpath:
// get row contains text 1001
driver.findElement(By.xpath("//table[#class='datagrid-btable']//tr[#class='datagrid-row' and contains(.,'1001')]")).click();
// get row by PLANT_CODE and exact cell value
driver.findElement(By.xpath("//table[#class='datagrid-btable']//tr[#class='datagrid-row' and .//td[#field='PLANT_CODE' and div[text()='1001']]]")).click();
To find row you need, you can use stream().filter(). In the example below, rows filtered by PLANT_NM text. Using filter or rows iteration, you can write a method to filter rows by any/multi cells:
List<WebElement> rows = driver.findElements(By.cssSelector(".datagrid-btable tr.datagrid-row"));
System.out.println(rows.size());
List<WebElement> filteredRows = rows.stream().filter(element ->
element.findElement(By.cssSelector("td[field='PLANT_NM'")).getText().equals("BOCAR LERMA"))
.collect(Collectors.toList());
Assert.assertTrue(filteredRows.size() > 0, "Row with \"BOCAR LERMA\" PLANT_NM exist.");
filteredRows.get(0).click();
when i search specific word only first page is classified. it shows pages and posts well on first page.
but when i go to page 2 or next page, seaching keyword doesn't apply on
is this address problem?
i guess this is sql or Paging.java problem because when i print log of page at BDAO it shows page well which i clicked.
also I don't know how can i transfer keyWord &keyField for that..!
I use oracle DB.
<%
String keyWord = (String)request.getParameter("keyWord");
String keyField = (String)request.getParameter("keyField");
%>
<script>
function searchCheck(frm){
//검색
if(frm.keyWord.value ==""){
alert("검색 단어를 입력하세요.");
frm.keyWord.focus();
return;
}
frm.submit();
}
function PageMove(page){
var keyWord = '<%=keyWord%>'
var keyField = '<%=keyField%>'
console.log(keyWord);
if(keyWord !=''){
location.href = "list.do?page="+page+"&keyWord=" + keyWord + "&keyField=" + keyField;
}
location.href = "list.do?page="+page;
}
</script>
</head>
<body>
<table width="800" cellpadding="0" cellspacing="0" border="1">
<tr>
<td>번호</td>
<td>이름</td>
<td>제목</td>
<td>날짜</td>
<td>히트</td>
</tr>
<c:forEach items="${list}" var="dto">
<tr>
<td>${dto.bId}</td>
<td>${dto.bName}</td>
<td>
<c:forEach begin="1" end="${dto.bIndent}">-</c:forEach>
${dto.bTitle}</td>
<td>${dto.bDate}</td>
<td>${dto.bHit}</td>
</tr>
</c:forEach>
<tr>
<td colspan="5">
<form action="list.do" method="post" name="search">
<select name="keyField">
<option value="bTitle">글 제목</option>
<option value="bContent">글 내용</option>
<option value="bName">작성자</option>
</select>
<input type="text" name="keyWord">
<input type="button" value="검색" onclick="searchCheck(form)">
</form>
</td>
</tr>
<tr>
<td colspan="5"> 글작성 </td>
</tr>
</table>
<div class="toolbar-bottom">
<div class="toolbar mt-lg">
<div class="sorter">
<ul class="pagination">
<li>맨앞으로</li>
<li>앞으로</li>
<c:forEach var="i" begin="${paging.startPageNo}" end="${paging.endPageNo}" step="1">
<c:choose>
<c:when test="${i eq paging.pageNo}">
<li class="active">${i}</li>
</c:when>
<c:otherwise>
<li>${i}</li>
</c:otherwise>
</c:choose>
</c:forEach>
<li>뒤로</li>
<li>맨뒤로</li>
</ul>
</div>
</div>
</div>
You never seem to be passing the keyword or keyfield when you call pageMove(). You might as well look up their values inside the function instead of having them as parameters:
function PageMove(page){
var keyWord = document.getElementById("keyWord").value;
var keyField = document.getElementById("keyField").value;
location.href = "list.do?page=" + page + "&keyWord=" + keyWord + "&keyField=" + keyField;
}
I have a very strange problem with reading data from a table row. This particular row has a few cells. First two are datetime (03/27/2017 08:30), code:
<div class="container-fluid container-full" id="OUTPUTSECTION">
<div class="row">
<div class="col-xs-12">
<div class="row">
<div class="col-xs-12">
<div class="row">
<div class="col-xs-12">
<table border="0" cellpadding="2" cellspacing="1" width="1080" class="table table-condensed-extra table-striped table-hover">
<tbody><tr><td colspan="50" style="text-align: center" class="tableHead">Document History Report</td></tr><tr bgcolor="#ffffff">
<td class="tableSubhead">
<span class="table-header">Start Time</span></td>
<td class="tableSubhead">
<span class="table-header">End Time</span></td>
<td class="tableSubhead">
<span class="table-header">Machine</span></td>
<td class="tableSubhead">
<span class="table-header">Site</span></td>
<td class="tableSubhead">
<span class="table-header">Operator</span></td>
<td class="tableSubhead">
<span class="table-header">Disposition</span></td>
<td class="tableSubhead">
<span class="table-header">Status</span></td>
<td class="tableSubhead">
<span class="table-header">Result</span></td>
<td class="tableSubhead">
<span class="table-header">Data Source</span></td>
</tr>
<tr class="tableTextWhite">
<td align="CENTER">03/27/2017 08:30</td>
<td align="CENTER">03/27/2017 08:30</td>
<td align="CENTER">TMX_01</td>
<td align="CENTER">Techmex</td>
<td align="CENTER">Anne</td>
<td align="CENTER">Completed</td>
<td align="CENTER">Good</td>
<td align="CENTER">Finished</td>
<td align="CENTER">D:\TMX_01\WORKING\27003001.txt/1</td>
</tr>
</tbody></table>
</div>
</div>
</div>
</div>
<script type="text/javascript">
parent.endTimems = new Date().getTime();
if( parent.startTimems )
{
parent.timeTakenms = parent.endTimems - parent.startTimems;
parent.startTimems = null;
if (parent.debugdiv && parent.timeTakenms )
parent.debugdiv.innerHTML = parent.timeTakenms/1000 + ' sec';
}
</script>
</div>
</div>
</div>
A basic table really... Here is my method, which reads that code using xpath.
public String[] getTimesFromDocumentHistoryReportPage() {
String XPATH_DETAILS_BASE = "//div['OUTPUTSECTION']/table/tbody/tr[3]/td";
String[] data = new String[2];
for (int i = 0; i < 2; i++) {
String XPATH_DETAILS = XPATH_DETAILS_BASE + "[" + (i + 1) + "]";
data[i] = getElement(By.xpath(XPATH_DETAILS)).getText();
}
return data;
}
For data[0] I am getting an empty value, even though copying and pasting the html and xpath into one of the online testers: videlibri.sourceforge.net/cgi-bin/xidelcgi returns a valid result. Next pass returns data[1], which has correct value. What am I missing here?
Ok, I'm not sure what happened, but since you successfully get the data from page source, I suggest you paste it to html parser (I use jsoup) then extract the data.
public String[] getTimesFromDocumentHistoryReportPage() {
Document document = Jsoup.parse(driver.getPageSource());
Elements elements = document.select("#OUTPUTSECTION .tableTextWhite > td");
String[] data = new String[2];
for (int i = 0; i < 2; i++) {
data[i] = elements.get(i).text()
}
return data;
}
I'm not on my computer now and I haven't tried my code yet, so maybe there is syntax errors, do let me know and I'll fix it.
you can download Jsoup here :https://jsoup.org/download
Given the piece of code code I was struggling with:
<tr class="tableTextWhite">
<td align="CENTER">03/27/2017 08:30</td>
<td align="CENTER">03/27/2017 08:30</td>
<td align="CENTER">TMX_01</td>
<td align="CENTER">Techmex</td>
<td align="CENTER">Anne</td>
<td align="CENTER">Completed</td>
<td align="CENTER">Good</td>
<td align="CENTER">Finished</td>
<td align="CENTER">D:\TMX_01\WORKING\27003001.txt/1</td>
</tr>
This is my final, working solution as proposed by #SDBot:
public String[] getTimesFromDocumentHistoryReportPage() {
String htmlSource = driver.getPageSource();
final Pattern pattern = Pattern.compile("<td align=\"CENTER\">(.+?)</td>");
final String[] tagValues = new String[2];
final Matcher matcher = pattern.matcher(htmlSource);
for (int i = 0; i < 2; i++) {
matcher.find();
tagValues[i] = matcher.group(1);
}
return tagValues;
}
The method does a search within given htmlSource and finds all instances of data (.+?) located between and html tags. This was enough in my case.
Since I am interested in first two cell values it is sufficient to make 2 iterations and return result. Test passed. Thank you!
I'm trying to extract the e-mail adress and the phone number from a linkedin profile using jsoup, each of these informations is in a table. I have written a code to extract them but it doesn't work, the code should work on any linkedin profile. Any help or guidance would be much appreciated.
public static void main(String[] args) {
try {
String url = "https://fr.linkedin.com/";
// fetch the document over HTTP
Document doc = Jsoup.connect(url).get();
// get the page title
String title = doc.title();
System.out.println("Nom & Prénom: " + title);
// first method
Elements table = doc.select("div[class=more-info defer-load]").select("table");
Iterator < Element > iterator = table.select("ul li a").iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next().text());
}
// second method
for (Element tablee: doc.select("div[class=more-info defer-load]").select("table")) {
for (Element row: tablee.select("tr")) {
Elements tds = row.select("td");
if (tds.size() > 0) {
System.out.println(tds.get(0).text() + ":" + tds.get(1).text());
}
}
}
}
}
here is an example of the html code that i'm trying to extract (taken from a linkedin profile)
<table summary="Coordonnées en ligne">
<tr>
<th>E-mail</th>
<td>
<div id="email">
<div id="email-view">
<ul>
<li>
adam1adam#gmail.com
</li>
</ul>
</div>
</div>
</td>
</tr>
<tr class="no-contact-info-data">
<th>Messagerie instantanée</th>
<td>
<div id="im" class="editable-item">
</div>
</td>
</tr>
<tr class="address-book">
<th>Carnet d’adresses</th>
<td>
<span class="address-book">
<a title="Une nouvelle fenêtre s’ouvrira" class="address-book-edit" href="/editContact?editContact=&contactMemberID=368674763">Ajouter</a> des coordonnées.
</span>
</td>
</tr>
</table>
<table summary="Coordonnées">
<tr>
<th>Téléphone</th>
<td>
<div id="phone" class="editable-item">
<div id="phone-view">
<ul>
<li>0021653191431 (Mobile)</li>
</ul>
</div>
</div>
</td>
</tr>
<tr class="no-contact-info-data">
<th>Adresse</th>
<td>
<div id="address" class="editable-item">
<div id="address-view">
<ul>
</ul>
</div>
</div>
</td>
</tr>
</table>
To scrape email and phone number, use css selectors to target the element identifiers.
String email = doc.select("div#email-view > ul > li > a").attr("href");
System.out.println(email);
String phone = doc.select("div#phone-view > ul > li").text();
System.out.println(phone);
See CSS Selectors for more information.
Output
mailto:adam1adam#gmail.com
0021653191431 (Mobile)
I was trying to figure out a way to check the checkboxes in the table grid. Usually they are defined as type='checkbox'. So I'm finding it difficult to implement using the webDriver to check the checkboxes since they are in the tags.
A sample HTML code is given below.
<tbody id="gridview-2345-body">
<tr id="gridview-2345-record-/DNA/Study1_HS.xml" class="x4-grid-row x4-grid-data-row x4-grid-row-selected" data-boundview="gridview-1270" role="row">
<td id="ext4-ext-gen1234" class="x4-grid-cell x4-grid-td" role="gridcell">
<div class="x4-grid-cell-inner " style="text-align:left;" unselectable="on">
<div class="x4-grid-row-checker"/>
</div>
</td>
<td id="ext4-ext-1235" class="x4-grid-cell x4-grid-td" role="gridcell">
<div class="x4-grid-cell-inner " style="text-align:left;" unselectable="on">
<span id="ext4-icon1568" class="fa fa-file-code-o labkey-file-icon"/>
</div>
</td>
<td id="ext4-ext-gen1236" class="x4-grid-cell x4-grid-td" role="gridcell">
<div class="x4-grid-cell-inner " style="text-align:left;" unselectable="on">
<div width="100%" height="16px">
<div style="float: left;"/>
<div style="padding-left: 8px; white-space:normal !important;">
<span style="display: inline-block; white-space: nowrap;">Study1_HS.xml</span>
</div>
</div>
</div>
</td>
</tr>
</tbody>
I tried using 'contains' in the xpath
driver.findElement(By.xpath("//*[contains(#id, 'Study1_HS.xml')]/td[1]/div/div")).click();
I'm wondering... since the TR contains the class change when the checkbox is checked, maybe clicking the TR will trigger the check. Try this and see.
String searchText = "Study1_HS.xml";
List<WebElement> rows = driver.findElements(By.tagName("tr"));
for (WebElement row : rows)
{
if (row.getText().contains(searchText))
{
row.click();
break;
}
}
So I used 'preceding' in the xpath to make it work
//span[text()='Study1_HS.xml']/preceding::td/div/div[#class='x4-grid-row-checker']
http://www.xpathtester.com/xpath/b1d50008dd4be8ab7545548c4b8238f5