Thymeleaf with recursive fragment for menus - spring-boot

I am trying to create a recursive fragment in Thymeleaf to produce a left-side menu. Core architecture is Spring Boot, and I am passing in a populated Menu object that has children and such. Code for the Menu looks like:
public class MenuItem {
private String displayLabel;
private String actionUri;
private List<MenuItem> children;
// ...getters/setters/etc...
}
And a Thymeleaf fragment defined like:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Menu</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div th:fragment="submenu (menu)" th:remove="tag">
<ul th:if="${not #lists.isEmpty(menu.children)}" th:class="${#objects.nullSafe(depth, 1) > 1}? 'children'">
<li th:each="child : ${menu.children}">
<span th:if="${#strings.isEmpty(child.actionUri)}" th:text="${child.displayLabel}">Addons</span>
<a th:if="${not #strings.isEmpty(child.actionUri)}" th:href="${child.actionUri}" th:text="${child.displayLabel}">Link Item</a>
<div th:replace="fragments/submenu :: submenu(menu=${child}, depth=depth+1)" th:remove="tag" />
</li>
</ul>
</div>
</body>
</html>
If I take the depth code out, everything seems to work fine. But with it I get:
2014-07-30 11:13:00.832 ERROR 45869 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "#objects.nullSafe(depth, 1) > 1" (fragments/submenu:9)] with root cause
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
The purpose of the depth code is so I can optionally put in a class to track when menu items are children of the parent. Is it possible to either fix the depth counter or to do something else to test if the fragment was ran already? Ultimately I just want a recursive menu with the top ul having no class and the child uls having a class of children.

Try these changes
1.Use two parameters in fragment as
<div th:fragment="submenu (menu, depth)" >
2.Pass the depth as
<div th:replace="fragments/submenu :: submenu(menu=${child}, depth=${depth+1})" />

Related

Thymeleaf expression parse exception

I've 2 lists; allkontrols (2 element), risk.kontrols (1 element)
I'm listing all kontrols as checkbox and trying to 'select' them if risk.kontrols list contains it.
The one element in risk.kontrols is also in allkontrols.
So, when I tried to print the results of this:
<div th:each="kontrol : ${allkontrols}" th:text="${#lists.contains(risk.kontrols, kontrol)}"/>
I got results as
true
false
Everything is ok till here. Now, when I tried to populate them with data, I got errors. Here is the code:
<div th:each="kontrol : ${allkontrols}" class="items form-control-lg">
<label>
<input th:value="${kontrol.id}" th:selected="{#lists.contains(risk.kontrols, kontrol)}" type="checkbox" class="flat">
<div style="display: inline-block;" th:utext=" ${kontrol.name}"></div>
</label>
</div>
Error part:
th:selected="{#lists.contains(risk.kontrols, kontrol)}"
Stacktrace:
2018-07-27 20:36:55 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "view/riskkontrol.html")] with root cause
org.thymeleaf.exceptions.TemplateProcessingException: Could not parse as expression: "{#lists.contains(risk.kontrols, kontrol)}" (template: "riskkontrol" - line 44, col 140)
It looks like you're missing a $ before the { in your th:selected, try changing it to: th:selected="${#lists.contains(risk.kontrols, kontrol)}"

Search for string in a webpage with frames

I am trying to search the webpage for particular strings, and I am using the following code:
strPageContent = appIE.document.documentElement.InnerHTML
However, the page is quite complicated and has got a lot of frames and framesets, and the command above returns only the contents of some parents tag. How can I access the contents of the particular div element - please see the code below:
<html>
<head>...</head>
<frameset id="NavContent_Workhorse" frameborder="0" framespacing="0" rows="*,0">
<frameset id="Nav_Content" border="3" frameborder="1" framespacing="3" cols="240,*">
<frame name="nav" src="/interface/sidebar/sidebar.def" scrolling="no">
<frame name="content" src="/interface/home.def" frameborder="1" border="3" marginheight="0" marginwidth="0" scrolling="no">
#document
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang>
<head> … </head>
<body onload="LoadAdd();">
<div class="PageTitle" id="PageTitle">...</div>
<div class="ToolBar" id="PageBody">...</div>
<div id="error" class="none"> … </div>
<div id="content" style="display: block; height: 445px;">
<form id="frmUSR" method="post" target="workhorse" onsubmit="return false;" action="/setup/users_groups/users/insert.sdl?parentid=14">
<div id="wiz_1" class="wiz_vis">
<table class="frmTbl">
<thead class="title">
<tr>
<th class="label">
TEXT THAT I AM LOOKING FOR
</th>
Thanks in advance!
Edit:
I forgot to add that I did try the code below, but I get a null value
Set div = appIE.document.getElementById("wiz_1")
Edit 2:
The purpose of the script is to automate filling out the user creation forms on my company's system (webpage UI). I don't know why, but I cannot get a reference to anything that is below the main <frameset id="Nav_Content" border="3". I keep getting null values.
You can access the content of an element with a particular ID inside a frame like this:
Set frame = appIE.Document.parentWindow.window.frames("content")
Set div = frame.document.getElementById("wiz_1")
WScript.Echo div.innerHTML
WScript.Echo div.innerText

Phantom <span> element using ImportXML with XPath in Google Spreadsheet

I am trying to get the value of an element attribute from this site via importXML in Google Spreadsheet using XPath.
The attribute value i seek is content found in the <span> with itemprop="price".
<div class="left" style="margin-top: 10px;">
<meta itemprop="currency" content="RON">
<span class="pret" itemprop="price" content="698,31 RON">
<p class="pret">Pretul tau:</p>
698,31 RON
</span>
...
</div>
I can access <div class="left"> but i can't get to the <span> element.
Tried using:
//span[#class='pret']/#content i get #N/A;
//span[#itemprop='price']/#content i get #N/A;
//div[#class='left']/span[#class='pret' and #itemprop='price']/#content i get #N/A;
//div[#class='left']/span[1]/#content i get #N/A;
//div[#class='left']/span/text() to get the text node of <span> i get #N/A;
//div[#class='left']//span/text() i get the text node of a <span> lower in div.left.
To get the text node of <span> i have to use //div[#class='left']/text(). But i can't use that text node because the layout of the span changes if a product is on sale, so i need the attribute.
It's like the span i'm looking for does not exist, although it appears in the development view of Chrome and in the page source and all XPath work in the console using $x("").
I tried to generate the XPath directly form the development tool by right clicking and i get //*[#id='produs']/div[4]/div[4]/div[1]/span which does not work. I also tried to generate the XPath with Firefox and plugins for FF and Chrome to no avail. The XPath generated in these ways did not even work on sites i managed to scrape with "hand coded XPath".
Now, the strangest thing is that on this other site with apparently similar code structure the XPath //span[#itemprop='price']/#content works.
I struggled with this for 4 days now. I'm starting to think it's something to do with the auto-closing meta tag, but why doesn't this happen on the other site?
Perhaps the following formulas can help you:
=ImportXML("http://...","//div[#class='product-info-price']//div[#class='left']/text()")
Or
=INDEX(ImportXML("http://...","//div[#class='product-info-price']//div[#class='left']"), 1, 2)
UPDATE
It seems that not properly parse the entire document, it fails. A document extraction, something like:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<div class="product-info-price">
<div class="left" style="margin-top: 10px;">
<meta itemprop="currency" content="RON">
<span class="pret" itemprop="price" content="698,31 RON">
<p class="pret">Pretul tau:</p>
698,31 RON
</span>
<div class="resealed-info">
» Vezi 1 resigilat din aceasta categorie
</div>
<ul style="margin-left: auto;margin-right: auto;width: 200px;text-align: center;margin-top: 20px;">
<li style="color: #000000; font-size: 11px;">Rata de la <b>28,18 RON</b> prin BRD</li>
<li style="color: #5F5F5F;text-align: center;">Pretul include TVA</li>
<li style="color: #5F5F5F;">Cod produs: <span style="margin-left: 0;text-align: center;font-weight: bold;" itemprop="identifier" content="mol:GA-Z87X-UD3H">GA-Z87X-UD3H</span> </li>
</ul>
</div>
<div class="right" style="height: 103px;line-height: 103px;">
<form action="/?a=shopping&sa=addtocart" method="post" id="add_to_cart_form">
<input type="hidden" name="product-183641" value="on"/>
<img src="/templates/marketonline/images/pag-prod/buton_cumpara.jpg"/>
</form>
</div>
</div>
</html>
works with the following XPath query:
"//div[#class='product-info-price']//div[#class='left']//span[#itemprop='price']/#content"
UPDATE
It occurs to me that one option is that you can use Apps Script to create your own ImportXML function, something like:
/* CODE FOR DEMONSTRATION PURPOSES */
function MyImportXML(url) {
var found, html, content = '';
var response = UrlFetchApp.fetch(url);
if (response) {
html = response.getContentText();
if (html) content = html.match(/<span class="pret" itemprop="price" content="(.*)">/gi)[0].match(/content="(.*)"/i)[1];
}
return content;
}
Then you can use as follows:
=MyImportXML("http://...")
At this time, the referred web page in the first link doesn't include a span tag with itemprop="price", but the following XPath returns 639
//b[#itemprop='price']
Looks to me that the problem was that the meta tag was not XHTML compliant but now all the meta tags are properly closed.
Before:
<meta itemprop="currency" content="RON">
Now
<meta itemprop="priceCurrency" content="RON" />
For web pages that are not XHTML compliant, instead of IMPORTXML another solution should be used, like using IMPORTDATA and REGEXEXTRACT or Google Apps Script, the UrlFetch Service and the match JavasScript function, among other alternatives.
Try smth like this:
print 'content by key',tree.xpath('//*[#itemprop="price"]')[0].get('content')
or
nodes = tree.xpath('//div/meta/span')
for node in nodes:
print 'content =',node.get('content')
But i haven't tried that.

How to convert Spring <form:checkboxes> to AngularJS equivalent?

I am migrating segments of Spring MVC code into AngularJS and hit the following problem:
In Spring, there is a nice tag that will take a Collection (or Map) of items and a property path to magically generate a list of checkboxes and have the selected ones checked;
<form:checkboxes path="selectedItems" items="${items}" />
where selectedItems is a List of value and items is Map of value and name.
Yes I can display all the checkboxes using this code:
<span ng-repeat="(key, value) in items" >
<input type="checkbox" ng-value="key" > <label class="label" >{{value}}</label>
</span>
But the trick is how we can auto select the checkboxes based on the values in the selectedItems and then bind it when the user select/unselect other items?
Directive give your html tag more power. I wrote a simple directive which will take a property "items" to generate a list of checkboxes and checked the selected ones according to item's status.
HTML: define data in your controller and add tag < checkboxes >
<!DOCTYPE html>
<html lang="en" ng-app="myApp">
<head>
<meta charset="utf-8">
<title>Angular test</title>
</head>
<body>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script>
<script src="js/app.js"></script>
<div ng-controller="CheckboxesCtrl">
<checkboxes items="items"></checkboxes>
<button ng-click="changeData()">change data</button>
</div>
</body>
</html>
App.js define controller and directive
var app = angular.module('myApp',[]);
app.controller('CheckboxesCtrl',function($scope){
//fake data
$scope.items = [{label:"A",checked:true},{label:"B",checked:true},{label:"C",checked:false}];
//data binding test
$scope.changeData = function(){
$scope.items[0].checked=false;
$scope.items[0].label="changed A";
}
});
//checkboxes directive
app.directive('checkboxes',function(){
return {
restrict: "E",
scope:{
items: "="
},
template: '<div ng-repeat="item in items">'+
'<input type="checkbox" ng-value="{{item.label}}" ng-checked="item.checked" />'+
' <lable class="label"> {{item.label}} </label>'+
'</div>'
};
});
I used ng-checked directive to process checkbox status binding. You could try my test JSFiddle.
Hope this is helpful for you.

Spring MVC: Url path appending when posting the form

i am new to spring mvc. i created a simple login application. but in my case the first time the for posting url and calling controller method correctly. in second time it's appending path with one more time of controller. first time post:
//localhost:8090/springmvc/account/login secong time in same page: //localhost:8090/springmvc/account/account/login. how do i fix this redirecting problem?
this my controller page:
#Controller
#RequestMapping("account")
public class AccountController {
AccountService service = new AccountService();
#RequestMapping(value = "account/default", method = RequestMethod.GET)
public ModelAndView RegisterUser() {
return new ModelAndView("/Account/Index","command",new User());
}
#RequestMapping(value = "/registeruser", method = RequestMethod.POST)
public ModelAndView RegisterUser(User user) {
user.setMessage(service.Register(user));
return new ModelAndView("/Account/Index", "command", user);
}
#RequestMapping(value = "/register", method = RequestMethod.POST)
public ModelAndView RegisterUer(User user) {
user.setMessage(service.Register(user));
return new ModelAndView("/Account/create", "command", user);
}
#RequestMapping(value = "/login", method = RequestMethod.POST)
public ModelAndView LoginUser(User user, ModelMap model) {
String msg = service.isAuthendicated(user) ? "Logged in" : "Failed";
user.setMessage(msg);
return new ModelAndView("/Account/Index", "command", user);
}
}
this my jsp page:
<%#taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%#taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%#taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%#taglib prefix="t" tagdir="/WEB-INF/tags"%>
<t:genericpage>
<jsp:body>
<h2>Login</h2>
<div>
${command.message} </div>
Register
<form:form action="account/login" method="post">
<div>
<form:input path="username" />
</div>
<div>
<form:input path="password" />
</div>
<input type="submit" value="Login">
</form:form>
</jsp:body>
</t:genericpage>
i used the tag library for common page:
<%#tag description="Master Page" pageEncoding="UTF-8"%>
<html>
<body>
<div id="pageheader">
<h2>WElcome</h2>
</div>
<div id="body">
<jsp:doBody />
</div>
<div id="pagefooter">
<p id="copyright">Copyright</p>
</div>
</body>
</html>
Depending on which version of Spring you're using, here are some options:
Spring 3.1 and lower OR Spring 3.2.3 and higher
You should have your urls/actions root-relative specific to your context path.
<form:form action="${pageContext.request.contextPath}/account/login" method="post">
Note: Spring 3.2.3 introduced servletRelativeAction but I've never used it.
Spring 3.2
Don't do anything, context path is prepended - this was actually a breaking change and eventually rolled back.
<form:form action="/account/login" method="post">
//will produce action="/springmvc/account/login"
Start your form action with a /.
<form:form action="/account/login" method="post">
By not doing it, you're telling the browser to append the action to the already existing URL on the address bar.
And where you have such links directly in HTML (by not using Spring's form:form), try to use c:url to properly construct the URL including the context path etc. This takes a lot of pain away from building proper relative URLs.
Register
Use ../ to get URL of your current context root:
<form:form action="../account/login" method="post">
from here
I tried with the spring tag bysetting only the relative path, it appends automatically the context path like:
<!DOCTYPE html>
<%# taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<html lang="en">
<head>
<!-- ... -->
<spring:url value="/account/login" var="loginUrl" />
<form:form action="${loginUrl}" method="post">
The context path is set in application.properties as bellow:
server.servlet.contextPath=/MyApp
In the jsp page, it produces:
<a class="nav-link" href="/MyApp/account/login"> <i class="fas fa-play"></i> <span>Click here</span></a>

Resources