Thymeleaf + Spring: get rid of the default element id - spring

Is there any way to suppress auto-generating ID attribute for elements while using th:field in Thymeleaf (2.1.4.RELEASE)? For example, given code:
<input type="text" th:field="*{year}" />
will produce the following HTML:
<input type="text" id="year" name="year" value="" />
What I want to achieve is (no id attribute):
<input type="text" name="year" value="" />
In JSP it was as easy as setting empty id:
<form:input path="year" id="" />
but Thymeleaf just replaces this empty attribute with the default-generated one.

Ok, I have looked inside the source code of Thymeleaf (2.1.4.RELEASE) and the method responsible for setting element id in Spring dialect is org.thymeleaf.spring4.processor.attr.SpringInputGeneralFieldAttrProcessor.doProcess(...) (source on Github) that calls org.thymeleaf.spring4.processor.attr.AbstractSpringFieldAttrProcessor.computeId(...) (source on Github). If you look at computeId(...), you will see that there is no simple way to set empty id.
So we need to do it in not a simple way :) Here it is:
I created a custom dialect and defined a custom attribute noid. The markup looks like this:
<input type="text" th:field="*{year}" thex:noid="true" />
There is a great tutorial explaining how to create and use custom dialects in Thymeleaf and below is the most important part: attribute processor responsible for removing id attribute from given element.
Important things to note:
high precedence value (9999) guarantees that this processor will be executed as the last one (so no other processors will modify id after this one is executed)
modification type is set to substitution so we are completely replacing value of id element
removeAttributeIfEmpty(...) returns true, rather self-explanatory, remove attribute if empty
getModifiedAttributeValues(...) sets id to empty value and because above-mentioned method returns true, id attribute is removed
Code:
public class NoIdAttrProcessor extends AbstractAttributeModifierAttrProcessor {
public NoIdAttrProcessor() {
super("noid");
}
#Override
public int getPrecedence() {
return 9999;
}
#Override
protected ModificationType getModificationType(Arguments arguments, Element element, String attributeName, String newAttributeName) {
return ModificationType.SUBSTITUTION;
}
#Override
protected boolean removeAttributeIfEmpty(Arguments arguments, Element element, String attributeName, String newAttributeName) {
return true;
}
#Override
protected boolean recomputeProcessorsAfterExecution(Arguments arguments, Element element, String attributeName) {
return false;
}
#Override
protected Map<String, String> getModifiedAttributeValues(Arguments arguments, Element element, String attributeName) {
Map<String, String> values = new HashMap<>(1);
values.put("id", "");
return values;
}
}

If you dont want to use this in id of you input field just assign the value to only the th:name field,
<input type="text" th:name="*{year}" />
will give you output like,
<input type="text" name="2015" />
Or You can use a string at the end to make the id generate different from the name attribute like this
<input type="text" th:name="*{year}" th:id="*{year} + '-year' " />
will give you the output,
<input type="text" name="2015" id="2015-year"/>

Related

Why do I lose information after submit a form with Spring MVC?

As I say int the title I loose information in the object that comes back from JSP to Controller.
From my Controller I pass a ModelAndView with an object of class Historic.
In the JSP page I have access to all of the values of this object, but when I submit I just get part of this information, some looses on the way on.
Controller:
#GetMapping("/tt")
public ModelAndView index(Model model) {
HistoricBO historic = new HistoricBO();
// ... I fulfill this object ...
return new ModelAndView("tt", "historic", historic);
}
In JSP I have access to all the information that I passed.
I use the values in two different ways. The first one (information that later I won't be able to recover) is:
<form:form method="POST" action="/addInput" modelAttribute="historic">
....
<form:label path="userHistoric[0].user.name" />
<form:input path="userHistoric[0].user.name" disabled="true" />
Being userHistoric a list inside HistoricBO object.
And the other way that I use the object values is daoing loop to the registers and show them. I can have these values after submit:
c:forEach items="${historic.userHistoric[0].periods[0].registers}" var="reg" varStatus="rog">
...
<td class="tab-odd">
<form:input path="userHistoric[0].periods[0].registers[${rog.index}].hours[0]" class="monin" type="number" />
</td>
The method that catch the submit is as follows:
#PostMapping("/addInput")
public String savePeriod(
#ModelAttribute("historic") HistoricBO inputs,
BindingResult result, ModelMap model) {
if (result.hasErrors()) {
return "error";
}
...
And here the object inputs only has setted the hours values, the rest of the object is empty.
Can you please why is the info loosing and how to solve it?
Thanks
Remove disabled="true" and use readonly="true" or readonly="readonly" instead like below.
<form:input path="userHistoric[0].user.name" readonly="readonly" />
Disabled values will not be submitted with the form.
See this values-of-disabled-inputs-will-not-be-submitted and demo here.

How do I iterate over all my model attributes on my JSP page?

I'm using Spring 3.2.11.RELEASE with JBoss 7.1.3.Final and Java 6. I have this method in a controller
#RequestMapping(value = "/method", method = RequestMethod.GET)
public String myMethod(final Model model,
final HttpServletRequest request,
final HttpServletResponse response,
final Principal principal)
...
model.addAttribute("paramName", "paramValue");
Notice how I add attributes into my model. My question is, on the JSP page that this page serves, how do I iterate over all the attributes in my model and output them as HIDDEN input fields with the name of the INPUT being the attribute name and the value being what I inserted in the model using that attribute?
Edit: In response to the answer given, here was the output to the JSP solution. Note there are no model attributes in there.
<input type='hidden' name='javax.servlet.jsp.jspRequest' value='org.springframework.web.context.support.ContextExposingHttpServletRequest#7a0a4c3f'>
<input type='hidden' name='javax.servlet.jsp.jspPageContext' value='org.apache.jasper.runtime.PageContextImpl#3939794a'>
<input type='hidden' name='appVersion' value='???application.version???'>
<input type='hidden' name='javax.servlet.jsp.jspResponse' value='org.owasp.csrfguard.http.InterceptRedirectResponse#722033be'>
<input type='hidden' name='javax.servlet.jsp.jspApplication' value='io.undertow.servlet.spec.ServletContextImpl#14c1252c'>
<input type='hidden' name='org.apache.taglibs.standard.jsp.ImplicitObjects' value='javax.servlet.jsp.el.ImplicitObjectELResolver$ImplicitObjects#23c27a49'>
<input type='hidden' name='javax.servlet.jsp.jspOut' value='org.apache.jasper.runtime.JspWriterImpl#b01a1ba'>
<input type='hidden' name='javax.servlet.jsp.jspPage' value='org.apache.jsp.WEB_002dINF.views.lti.launch_jsp#1dcc48bf'>
<input type='hidden' name='javax.servlet.jsp.jspConfig' value='io.undertow.servlet.spec.ServletConfigImpl#3fd40806'>
Model attributes are "request scope" objects
you can do the following (I use JSTL):
<c:forEach items="${requestScope}" var="par">
<c:if test="${par.key.indexOf('attrName_') > -1}">
<li>${par.key} - ${par.value}</li>
</c:if>
</c:forEach>
Since with no filter you will have all the request scope objects, I filtered by the model attributes we wanted to check
I tested by using this code:
#RequestMapping(method = { RequestMethod.GET }, value = { "/*" })
public String renderPage(Model model) throws Exception
{
String requestedUrl = req.getRequestURI();
int indice = requestedUrl.lastIndexOf('/');
String pagina = requestedUrl.substring(indice + 1);
try
{
String usernameUtente = "default username utente";
if (StringUtils.hasText(getPrincipal()))
{
usernameUtente = getPrincipal();
}
model.addAttribute("usernameUtente", usernameUtente);
model.addAttribute("webDebug", webDebug);
for(int i = 0; i<10; i++)
{
model.addAttribute("attrName_"+i, "attrValue_"+i);
}
return pagina;
}
catch (Exception e)
{
String message = "Errore nell'erogazione della pagina " + pagina;
logger.error(message, e);
return "genericError";
}
}
And this is what I see as output (I omitted the not relevant prints but please note you'll print ALL the request scope objects:
attrName_0 - attrValue_0
attrName_1 - attrValue_1
attrName_2 - attrValue_2
attrName_3 - attrValue_3
attrName_4 - attrValue_4
attrName_5 - attrValue_5
attrName_6 - attrValue_6
attrName_7 - attrValue_7
attrName_8 - attrValue_8
attrName_9 - attrValue_9
I hope this can help
Angelo
For avoid headache with parameters added by Spring and Servlet container, it is better to use separate map for pass values into the model. Just use #ModelAttribute and Spring will create and add it to the model automatically:
#RequestMapping(value = "/method", method = RequestMethod.GET)
public String myMethod(final Model model, #ModelAttribute("map") HashMap<String, Object> map) {
map.put("paramName1", "value1");
map.put("paramName2", "value2");
//...and so on
}
Now you can iterate this map in JSP:
<c:forEach items="${map.keySet()}" var="key">
<input type="hidden" name="${key}" value="${map[key]}"/>
</c:forEach>
Also you can access to every item of map next way:
<c:out value="${map.paramName1}"/>
<c:out value="${map.paramName2}"/>
...
If you don't need some parameter to be iterable, add it into the original ModelMap istead of separate map.
In essence all you need is to itterate on all the page attributes. Depending on what you use on your jsp (scriptlets, jstl, or smthing like thymeleaf for html):
Scriptlet:
<form>
<% Session session = request.getSession();
Enumeration attributeNames = session.getAttributeNames();
while (attributeNames.hasMoreElements()) {
String name = attributeNames.nextElement();
String value = session.getAttribute(name);
%>
<input type='hidden' name="<% name %>" value="<% value %>">
<%
}
%>
</form>
JSTL:
<%# taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<h3>Page attributes:</h3>
<form>
<c:forEach items="${pageScope}" var="p">
<input type='hidden' name='${p.key}' value='${p.value}'>
</c:forEach>
</form>
Thymeleaf:
<form>
<input th:each="var : ${#vars}" type='hidden' name="${var.key}" value="${var.value}">
</form>
Simply you can iterate using foreach tag of Jstl.
<c:forEach items="${requestScope}" var="var">
<c:if test="${ !var.key.startsWith('javax.') && !var.key.startsWith('org.springframework')}">
<input type="hidden" name="${var.key}" value="${var.value}" />
</c:if>
</c:forEach>
Request attributes from spring framework and from Servlet do have prefixes, you don't need to add prefix to your request attributes.
Rather you can ignore all those attributes which have prefix "org.springframework" or "javax.".
You can try this:
#RequestMapping(value = "/method", method = RequestMethod.GET)
public String myMethod(final Model model,
final HttpServletRequest request,
final HttpServletResponse response,
final Principal principal)
...
//Create list for param names and another list for param values
List<String> paramNames = new ArrayList();
List<String> paramValues = new ArrayList();
paramNames.add("paramName1");
paramValues.add("paramValue1");
paramNames.add("paramName2");
paramValues.add("paramValue2");
//paramValue1 is the value corresponding to paramName1 and so on...
//add as many param names and values as you need
...
//Then add both lists to the model
model.addAttribute("paramNames", paramNames);
model.addAttribute("paramValues", paramValues);
Then in the JSP, you can iterate over paramNames list, and use the varStatus.index to get the index of current round of iteration and use it to pull the value of corresponding param value from the paramValues list. Like this -
<form id='f' name='myform' method='POST' action='/path/to/servlet'>
<c:forEach items="${paramNames}" var="paramName" varStatus="status">
<input type='hidden' name='${paramName}' value='${paramValues[status.index]}'>
</c:forEach>
</form>
You can add other input elements to the form as needed but the above should generate all the hidden input elements for each of your parameter that you set in the Model.

Why does the Spring form:hidden tag print two value attributes?

Basic question, but haven't found any other relevant posts on SO.
I have this Spring code in my JSP with a property called vacant:
<form:hidden path="vacant" value="false"/>
And here is the generated output:
<input id="vacant" name="vacant" value="false" type="hidden" value=""/>
Why would value get printed twice with an empty second one?
(It's relevant because I'm trying to use the value in some Javascript.)
What you're seeing is "normal". According to the doc:
This tag renders an HTML 'input' tag with type 'hidden' using the
bound value
Let's assume vacant is Boolean vacant; in your DTO, since its value is null, the tag'll print it as value="". In addition, it'll print any other field you pass to it, e.g:
<form:hidden path="vacant" my-field="test"/>
<input id="vacant" name="vacant" my-field="test" type="hidden" value=""/>
So if you happen to use value in <form:hidden path="vacant" my-field="test" value="true"/>, it'll consider it an additional field:
<input id="vacant" name="vacant" my-field="test" value="false" type="hidden" value=""/>
Here is what happens in the source:
org.springframework.web.servlet.tags.form.HiddenInputTag
protected int writeTagContent(TagWriter tagWriter) throws JspException {
...
writeDefaultAttributes(tagWriter); // *) Here it'll print the value that you passed to the tag
...
//The next two statements get the bound value of vacant (null) and print it as value=""
String value = getDisplayString(getBoundValue(), getPropertyEditor());
tagWriter.writeAttribute("value", processFieldValue(getName(), value, "hidden"));
*) writeDefaultAttributes() calls writeOptionalAttributes(), where your passed value is printed (it's in this.dynamicAttributes along with my-field):
if (!CollectionUtils.isEmpty(this.dynamicAttributes)) {
for (String attr : this.dynamicAttributes.keySet()) {
tagWriter.writeOptionalAttributeValue(attr, getDisplayString(this.dynamicAttributes.get(attr)));
}
}
So, http:input is intended to be used for bound values, so set the value you need in the DTO before rendering the JSP.

Spring MVC return object from JSP

I have a Spring MVC controller that sends a list to the view:
modelAndView.addObject("myList", List<foo>);
On the JSP, I iterate over this list creating a table where each row is a form with a submit button representing one foo instance. How can I get the single instance of foo represented by the row into the next controller? I tried putting it in an input type="hidden" but that didn't work.
A JSP is processed and rendered into HTML, which is just text.
When you submit a form, the browser takes the values of the <input> fields and serializes them in a specific format. With the typical application/x-www-form-urlencoded content type, the following <input> elements
<form action="/some/url" method="POST">
<input name="someField" value"someValue" type="text" />
<input name="someOtherField" value"someOtherValue" type="text" />
<input name="submit" value"submit" type="submit" />
</form>
The serialized format will look something like
someField=someValue&someOtherField=someOtherValue&submit=submit
Spring will take this String from the body of the request as request parameters and try to reconstruct your command object (ex. foo) from its values. The request above could be mapped to a class like
class Foo {
private String someField;
private String someOtherField;
public String getSomeField() {
return someField;
}
public void setSomeField(String someField) {
this.someField = someField;
}
public String getSomeOtherField() {
return someOtherField;
}
public void setSomeOtherField(String someOtherField) {
this.someOtherField = someOtherField;
}
}
Any fields it can't map, it ignores.

Internationalized Labels for Form Components in Wicket

How do I correctly create internationalized labels for my form components so that when displaying feedback messages an internationalized field name is displayed instead of the name of the field in the java code?
I've read this:
https://cwiki.apache.org/WICKET/everything-about-wicket-internationalization.html
as well as the documentation for wicket's xhtml tags:
https://cwiki.apache.org/WICKET/wickets-xhtml-tags.html
<label wicket:for="name">
<wicket:label>
<wicket:message key="label.name"/>
</wicket:label>
</label>
<input wicket:id="name" type="text" wicket:message="placeholder:label.name" />
This results in the following error:
Last cause: Expected close tag for '<wicket:label>' Possible attempt to embed
component(s) '<wicket:message key="label.name"/>' in the body of this
component which discards its body
If I replace the wicket:message with some arbitrary text it displays the text in any associated feedback messages.
(There's a related jira issue: https://issues.apache.org/jira/browse/WICKET-3903 however I still do not understand what has been done to fix this and what I must do ...)
Just found out there is a way to do this in java:
add(new TextField<String>("name").setRequired(true).setLabel(new Model<String>(getString("label.name"))));
Is it possible to somehow do this in a more comfortable way?
I just tested the following:
<form wicket:id="form">
<label for="input"><wicket:message key="input">some input</wicket:message></label>
<input wicket:id="input" type="text" name="input">
<input type="submit" value="submit">
</form>
And in the java class:
Form<HomePage> form = new Form<HomePage>("form"
, new CompoundPropertyModel<HomePage>(this));
wmc.add(form);
TextField textField = new TextField("input");
textField.setRequired(true);
form.add(textField);
In the property file I provided:
input=SomeInputField
This led to the following screen (if I leave the requiered field empty and press submit.
Is this what you are looking for?
Here is an alternative approach to #bert's that has always worked for me (wasn't aware of <wicket:label>)
The text shown for a FormComponent when a validation error occurs can be specified by means of FormComponent.setLabel(IModel). The shown text will be the result of the IModel's getObject().
TextField comp = new TextField("comp");
// Use internationalized text from XML resource file
comp.setLabel(new StringResourceModel("formResources.comp.label", this, null));
Notice this has nothing to do with <label> nor FormComponentLabel. FormComponentLabel is a component that can be used to model <label> tags.
You could even subclass FormComponentLabel to provide the label text based on FormComponent.getLabel(), and maybe output an extra mark when the field is required:
public class MyLabel extends SimpleFormComponentLabel{
private boolean required;
public MyLabel (String id, LabeledWebMarkupContainer labelProvider) {
super(id, labelProvider);
if (labelProvider instanceof FormComponent){
required = ((FormComponent)labelProvider).isRequired();
}
}
protected void onComponentTagBody(final MarkupStream markupStream,
final ComponentTag openTag) {
String mark = "";
if (required){
// could be for instance "*"
mark = getString("formResources.requiredField");
}
String text = getModelObjectAsString() + mark;
replaceComponentTagBody(markupStream, openTag, text);
}
}
{
TextField component = new TextField("component");
component.setRequired(true);
component.setOutputMarkupId(true);
IModel labelModel = new StringResourceModel("formResources.component.label",
this, null);
component.setLabel(labelModel);
add(component);
add(new MyLabel("componentLabel", component);
}
<label wicket:id="componentLabel"/>
<input type="text" wicket:id="component"/>
This way you would have clean way of
Setting the FormComponent's text to an internationalized resource string
Reusing exactly the same resource string transparently for the <label> tag and even adding custom marks to it based on FormComponent's properties.
Another alternative is to use the key attribute of <wicket:label/>, like so:
<label wicket:for="name">
<wicket:label key="label.name">Placeholder label</wicket:label>
</label>
<input wicket:id="name" type="text"/>
Unfortunately this attribute is not documented on the wiki page describing wicket's xhtml tags. All attributes supported are documented using JavaDoc in the class handling the tag (org.apache.wicket.markup.html.form.AutoLabelTextResolver).
The advantage of this alternative is that there is no additional coding required.
Wicket throws an exception to tell you that your <wicket:message> tag will be removed because the body of the <wicket:label> tag is replaced. The problem is you cannot nest the <wicket:message> tag inside the <wicket:label> tag (and shouldn't need to).
either this (Option 1):
<label wicket:for="name">
<wicket:label key="label.name"/>
</label>
<input wicket:id="name" type="text />
or this (Option 2):
<label wicket:for="name">
<wicket:message key="label.name"/>
</label>
<input wicket:id="name" type="text />
should work for you and result in HTML something like the following (assuming the properties file contains label.name=Name):
<label for="someMarkupId">
Name
</label>
<input id="someMarkupId" type="text" />
The difference is that if you set the label for the component through the Java code like so:
component.setLabel(new Model("value set in code"));
then using the Option 1 will result in the label being set to "value set in code", while using Option 2 will still result in the label set to "Name". Also if the label is set through Java code, and the key is missing from the properties file the Option 2 will throw an exception, while Option 1 will simply use the value set in the code.
I prefer this:
<label wicket:for="name"><wicket:label />:</label>
<input type="text" wicket:id="name"></input>
Just make sure to set the label in the FormComponent using setLabel, so the only java needed is:
add(new TextField("name", nameModel).setLabel(Model.of("i18n.name")));
This will be rendered as (in Dutch):
<label id="name63-w-lbl" for="name63">Naam:</label>
<input type="text" value="" name="name" id="name63">

Resources