I'm building an app with Java and Thymeleaf and I need to pass a HashMap to a partial. I don't want to pass it from a controller.
This is what I tried so far:
user.html
<div th:replace="partials/icons.html :: icons(icons=${ {name='user', title='User'}, {name='blog', title='Blog'} })"></div>
/partials/icons.html
<div th:fragment="icons">
<th:block th:each="icon : ${icons}">
<button th:class="'icon-' + ${icon.name}" th:text="${icon.title}"></button>
</th:block>
</div>
It gives me an error that = is unexpected. What would be the correct syntax?
EDIT: I should have clarified up-front: there is no way to do what you are trying to do with the syntax of fragment parameters. My approach below is a work-around.
Assuming you have the following object as a starting point:
public class Icon {
private String name;
private String title;
// getters and setters...
}
Assuming you then have a List of such objects, called icons...
This list is passed to your Thymeleaf renderer in the usual way. I don't use Spring, so how you do that is (I assume) through an annotation. I may be wrong - it shouldn't affect the approach below, however. In my no-Spring approach the list is added to the Thymeleaf model as map.put("icons", icons);
I have the following in my parent Thymeleaf template:
<div th:replace = "/iconsfragment.html :: icons(iconlist='icons')">
</div>
I have the following in iconsfragment.html:
<div th:fragment="icons(iconlist)">
<th:block th:each="icon : ${__${iconlist}__}">
<div th:class="'icon-' + ${icon.name}" th:text="${icon.title}"></div>
</th:block>
</div>
It uses a preprocessor __${...}__ to convert the parameter (a string) back into an iterable object.
The resulting HTML that you care about is:
<div class="icon-firstName">firstTitle</div>
<div class="icon-secondName">secondTitle</div>
This only really makes sense if you want to re-use that fragment in various different ways, of course. Otherwise just pass the ${icons} object directly to the fragment.
And my <div> example would of course need to be adapted to your <button> example.
Related
I'm trying to create a fragment that represents a card with custom content. I would like to do something like:
<div class="card" th:fragment="myfragment" th:utext="${content}">
</div>
And then use as
<th:block th:replace="myfragment">
<p>Some custom content that would be the value of 'content'</p>
</th:block>
This would make it a lot easier to work with bigger html that would be kinda ugly to write in an attribute. (Basically I'm looking for a similar functionality to Blade's views and slots)
EDIT:
I'm aware of fragment parameterisation but passing long and complex html code in an attribute is pretty ugly and hard to manage.
A more descriptive example would be a card where the card body is not a p but a table for example.
Soo, maybe not the best solution but I managed to get this working based on this Other SO thread and this
Example code
I've created a new dialect so I can say this:
<zms:card header="'ASD Title'">
<div th:text="${first_name}"></div>
asdasd card works asdasd
</zms:card>
And it will render this:
<div class="card shadow mb-4">
<div class="card-header py-3">
<div class="d-inline-block">
<h6 class="m-0 font-weight-bold">ASD Title</h6>
</div>
</div>
<div class="card-body">
<div>Name</div>
asdasd card works
</div>
</div>
Thymeleaf supports parameterized fragments. Here's how you can use it:
Fragment Definition:
_fragments/my_fragments.html:
<th:block th:fragment="myFragment(p1, p2, p3)">
//do sth with p1, p2, p3
</th:block>
Using it:
<th:block th:replace="_fragments/my_fragments :: myFragment(${p1}, ${p2}, ${p3} )">
</th:block>
Ref:
https://ganeshtiwaridotcomdotnp.blogspot.com/2020/09/thymeleaf-pass-parameter-to-fragment.html
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#parameterizable-fragment-signatures
https://github.com/gtiwari333/spring-boot-web-application-seed/blob/master/main-app/src/main/resources/templates/_fragments/_utils.html
I'm using Spring Boot with some templates to help generate some dynamic emails. Unfortunately, the templating engine isn't rendering my variables.
Backend Call
public String generateProblemOfTheDay(Model model) throws IOException {
Context ctx = new Context();
ctx.setVariable("potd", "Test Value");
//Process the template with the proper context variables
String html = templateEngine.process("index", ctx);
PrintWriter pWriter = new PrintWriter(Paths.PROBLEM_OF_THE_DAY_OUTPUT, "UTF-8");
pWriter.println(html);
pWriter.close();
log.info("done!");
log.info(html);
return html;
}
Segment of my template
.
.
<tr>
<td style="font-family:'Open Sans', Arial, sans-serif; font-size:15px; line-height:18px; color:#30373b;">
<br />
<div class="question-description">
[[${potd}]]
</div>
</td>
</tr>
.
.
I'm not sure why the template engine isn't processing the variables correctly. Is this the best way to add variables?
What I found does work
<label style="font-size: 12px;padding-bottom: 1em;" th:text="${potd}">Test</label>
Adding something like the following does indeed work.. I've seen many people use t he standard curly bracket notation without issues and wondering what is appropriate where.
Inlined expressions were changed from thymeleaf 2 to 3. I'm guessing you're using thymeleaf 2, which means you need the attribute th:inline="text" in order to get your expression to work.
<div class="question-description" th:inline="text">
[[${potd}]]
</div>
If you upgrade to thymeleaf 3, those expressions will work out of the box (and it even recommends you remove th:inline="text"). As for which way you should write expressions... it is pretty much opinion based. For the most part, I like using th:text directly in a tag. If you are appending a lot of strings together, you might use the other way. For example:
<span>Your answer was: [[${answer}]]</span>
is easier to read than
<span th:text="${'Your answer was:' + answer}"/>
I am developing an MVC web app using spring framework.
At some point I have a jsp that is listing study case objects as sown below
<tbody>
<c:forEach items="${studycases}" var="studycase">
<tr>
<td>
<a href='<spring:url value="/studycase/${studycase.study_case_number}.html"/>'>
${studycase.study_case_number}
</a>
</td>
<td>
${studycase.dateOFHospitalAdmission}
</td>
<td>
${studycase.dateOfWardAdmission}
</td>
<td>
${studycase.dateOfWardDischarge}
</td>
</tr>
</c:forEach>
</tbody>
as you can see there is a spring:url that is directing to a jsp with details about the spesific study case, that is being handled by the below controller :
#RequestMapping("/studycase/{studyCaseNumber}")
public String detail(Model model, #PathVariable String studyCaseNumber)
{
model.addAttribute("studyCase", studyCaseService.findOne(studyCaseNumber)) ;
model.addAttribute("measurements", measurementService.findAllOfThem(studyCaseNumber)) ;
return "study-case-detail" ;
}
The problem is that as the study cases listed in the first jsp could be thousands I will need to make it possible for the user to enter the study_case_number of a study case in a input field and get the details of the study case having the study case number inputed . So what I am doing is this:
<form action="<spring:url value="/studycase/study-case-detail2"/>" method="GET">
Study Case Number : <input type="text" name="studyCaseNumber">
<br/>
<input type="submit" value="Submit" />
</form>
That is being handled by an other controller and directs to an other jsp with more or less the same structure and data :
#RequestMapping("/studycase/study-case-detail2")
public String detail2(Model model, #RequestParam("studyCaseNumber") String std)
{
model.addAttribute("measurements", measurementService.findAllOfThem(std)) ;
return "study-case-detail2" ;
}
My question is this : Is this a good way to go having different controllers and different views even if they are presenting more or less the same thing ? What are other alternatives ??
Is there any source that you can direct me to containing best practices catalogue or guide on how to handle similar situations ??
Looking at your code, your controllers are not doing exactly the same thing (e.g. different model attributes, and they return different views).
Other than that, in general, it's not the best practice to do what you're doing as it somewhat in conflict with "Do Not Repeat Yourself" principle (also known as DRY principle).
One suggestion would be to use javascript to do redirect on id from input box rather than a form.
It seems you're using Spring Data too. If that's the case then one additional (not exactly related) suggestion to DRY up your code would be to use Spring Data's domain class converters to avoid calls studyCaseService.findOne(studyCaseNumber) in your StudyCase related controllers.
With domain class converters in place, you could then write controller method like this:
public String detail(Model model, #PathVariable("studyCaseNumber") StudyCase studyCase)
and avoid call to repository's findOne method as the converters would automatically convert ID to entity.
Take a look at DomainClassConverter in "Basic Web Support" section of Spring Data manual:
http://docs.spring.io/spring-data/data-commons/docs/1.6.1.RELEASE/reference/html/repositories.html
The same EL expression ${taskId} gives two different values in different places.
I am using the Stripes framework, along with MongoDB and Morphia for Object-Mapping (and of course Java/JSP/etc).
Using the following JSP/Expression Language code:
<c:forEach items="${actionBean.tasks}" var="listTask">
<c:set var="taskId" scope="page" value="${listTask.id}"/>
<s:form method="post" beanclass="action.TaskActionBean">
${taskId}<s:hidden name="task.id" value="${taskId}"/>
<s:submit name="promoteTask" value="Up" />
</s:form>
</c:forEach>
Gives the following generated code:
<form method="post" action="/scrumyogi/">
4ef99b730364de7ec70dbd68
<input type="hidden" value="4ef99b6c0364de7ec70dbd67" name="task.id">
<input type="submit" value="Up" name="promoteTask">
<div style="display: none;">
<input type="hidden" value="NPNEJw6tUWfRBXf-vVOLTw==" name="_sourcePage">
<input type="hidden" value="XbfUDiSHGrU=" name="__fp">
</div>
</form>
As you can see ${taskId} is printing out 4ef99b730364de7ec70dbd68 and then 4ef99b6c0364de7ec70dbd67, which makes no sense to me, I need ${taskId} to print out the same value 4ef99b730364de7ec70dbd68 is the correct one.
Is there some known issue that could cause this.
EDIT: the real problem is that the ${taskId} within the hidden form tag is incorrect, I printed the other value to see what the expression contains, and then found that it's different in the different locations - which make things seriously confusing.
ActionBean code:
#UrlBinding("/")
public class TaskActionBean extends BaseActionBean{
String taskId;
Task task = new Task();
List<Task> tasks;
public final static String DISPLAY = "/index.jsp";
#DefaultHandler
public Resolution listTasks(){
tasks = Dao.datastore().find(Task.class).order("rank").asList();
return new ForwardResolution(DISPLAY);
}
public Resolution promoteTask(){
task.promoteTask();
tasks = Dao.datastore().find(Task.class).order("rank").asList();
return new ForwardResolution(DISPLAY);
}
// ... getters and setters
You have a taskId field in you action bean, and according to stripes taglib documentation:
The hidden tag assigns the value attribute by scanning in the following order:
for one or more values with the same name in the HttpServletRequest
for a field on the ActionBean with the same name (if a bean instance is present)
by collapsing the body content to a String, if a body is present
referring to the result of the EL expression contained in the value attribute of the tag.
So it probably finds the field in your action bean and takes the value from there.
The other (jsp el) ${taskId} is assigned from task list element.
Change the taskId to some name that doesn't coincide with your action bean field and it should work.
Embedded custom-tag in dynamic content (nested tag) not rendering.
I have a page that pulls dynamic content from a javabean and passes the list of objects to a custom tag for processing into html. Within each object is a bunch of html to be output that contains a second custom tag that I would like to also be rendered. The problem is that the tag invocation is rendered as plaintext.
An example might serve me better.
1 Pull information from a database and return it to the page via a javabean. Send this info to a custom tag for outputting.
<jsp:useBean id="ImportantNoticeBean" scope="page" class="com.mysite.beans.ImportantNoticeProcessBean"/> <%-- Declare the bean --%>
<c:forEach var="noticeBean" items="${ImportantNoticeBean.importantNotices}"> <%-- Get the info --%>
<mysite:notice importantNotice="${noticeBean}"/> <%-- give it to the tag for processing --%>
</c:forEach>
this tag should output a box div like so
*SNIP* class for custom tag def and method setup etc
out.println("<div class=\"importantNotice\">");
out.println(" " + importantNotice.getMessage());
out.println(" <div class=\"importantnoticedates\">Posted: " + importantNotice.getDateFrom() + " End: " + importantNotice.getDateTo()</div>");
out.println(" <div class=\"noticeAuthor\">- " + importantNotice.getAuthor() + "</div>");
out.println("</div>");
*SNIP*
This renders fine and as expected
<div class="importantNotice">
<p>This is a very important message. Everyone should pay attenton to it.</p>
<div class="importantnoticedates">Posted: 2008-09-08 End: 2008-09-08</div>
<div class="noticeAuthor">- The author</div>
</div>
2 If, in the above example, for instance, I were to have a custom tag in the importantNotice.getMessage() String:
*SNIP* "This is a very important message. Everyone should pay attenton to it. <mysite:quote author="Some Guy">Quote this</mysite:quote>" *SNIP*
The important notice renders fine but the quote tag will not be processed and simply inserted into the string and put as plain text/html tag.
<div class="importantNotice">
<p>This is a very important message. Everyone should pay attenton to it. <mysite:quote author="Some Guy">Quote this</mysite:quote></p>
<div class="importantnoticedates">Posted: 2008-09-08 End: 2008-09-08</div>
<div class="noticeAuthor">- The author</div>
</div>
Rather than
<div class="importantNotice">
<p>This is a very important message. Everyone should pay attenton to it. <div class="quote">Quote this <span class="authorofquote">Some Guy</span></div></p> // or wahtever I choose as the output
<div class="importantnoticedates">Posted: 2008-09-08 End: 2008-09-08</div>
<div class="noticeAuthor">- The author</div>
</div>
I know this has to do with processors and pre-processors but I am not to sure about how to make this work.
Just using
<bodycontent>JSP</bodycontent>
is not enough. You should do soimething like
JspFragment body = getJspBody();
StringWriter stringWriter = new StringWriter();
StringBuffer buff = stringWriter.getBuffer();
buff.append("<h1>");
body.invoke(stringWriter);
buff.append("</h1>");
out.println(stringWriter);
to get inner tags rendered (example is for SimpleTag doTag method).
However, in the question's code I see that inner tag is comming from a string which is not rendered as a part of JSP, but just some random string. I do not think you can force JSP translator to parse it.
You can use regexp in your case or try to redesign your code in a way to have a jsp like this:
<jsp:useBean id="ImportantNoticeBean" scope="page class="com.mysite.beans.ImportantNoticeProcessBean"/>
<c:forEach var="noticeBean" items="${ImportantNoticeBean.importantNotices}">
<mysite:notice importantNotice="${noticeBean}">
<mysite:quote author="Some Guy">Quote this</mysite:quote>
<mysite:messagebody author="Some Guy" />
</mysite:notice>
</c:forEach>
I whould go with regexp.
I would be inclined to change the "architecture of your tagging" in that the data you wish to achieve should not be by tag on the inside of the class as it is "markup" designed for a page(though in obscurity it is possible to get the evaluating program thread of the JSP Servlet engine).
What you would probably find better and more within standard procedure would be using "cooperating tags" with BodyTagSupport class extension and return EVAL_BODY_BUFFERED in doStartTag() method to repeat process the body and/or object sharing such as storing retrived data in the application hierarchy of the session or on the session for the user.
See oracle j2ee custom tags tutorial for more information.