Spring Thymeleaf + Textarea - html

I created form with one input text and one textarea. The input text works fine, but textarea isn't even displayed:
<div id="news" th:fragment="admin_panel">
<form method="POST" th:action="#{/addNews}" th:object="${news}" id="myform">
Tytuł:
<input type="text" th:field="*{title}"/>
<input type="submit" value="Wstaw"/>
</form>
<textarea name="news_content" rows="20" cols="80" th:field="${news.content}" form="myform">
...
</textarea>
</div>
When I delete the th:field the textarea is displayed and when I use th:value instead of th:field it's displayed too but doesn't save the written text to news.content (news.title is saved ok).
I don't have any idea... I read thymeleaf references but can't find answer, so please help good people!

You have to use the selected object expression *{content} AND place the textarea tag inside the form tag!
In the end its all about the generated name attribute in the resulting form. The name needs to correspond to the propertyAccessor from the selected root object th:object.
The form is processed by spring (without any thymeleaf interception).
The docs about spring integration are really good: http://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html
They state this:
Values for th:field attributes must be selection expressions (*{...}), which makes sense given the fact that they will be evaluated on the form-backing bean and not on the context variables (or model attributes in Spring MVC jargon).
EDIT:
Thanks to the link to the project, the fix was easy:
Thymeleaf 3.0.0.BETA03 had a bug in the textarea processor, moving to 3.0.0.RELEASE fixed this issue
Additionally I have moved the textarea inside the form element.

My textarea input was working fine when i was saving some data through it(in clean state when i was performing a save to DB) but in case of edit form(where my textarea input was supposed to show the prefilled description from the model property book.description) was blank, the reason for that was the th:value attribute, i changed it to the th:field attribute and it started working as expected.
<textarea class="form-control" id="description" rows="5"
name="description"
placeholder="Description" th:field="${book.description}"
required="required"></textarea>

You don't need the form text field. Linking the textarea with the form by the form id is sufficient.
<textarea rows="8" cols="120" name="lines" form="usrform" th:text="${message}"></textarea>
<form method="POST" enctype="multipart/form-data" th:action="#{/}" id="usrform">
<button type="submit" name="action" value="submitlines">Submit</button>
</form>
and the controller:
#RequestMapping(value="/", method=RequestMethod.POST, params="action=submitlines")
public String handleForm(
#RequestParam("lines") String input,
RedirectAttributes redirectAttributes) {
}

About exceptions :
SEVERE: Servlet.service() for servlet [dispatcher] in context with path [/eniupage] threw exception [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Error during execution of processor 'org.thymeleaf.spring4.processor.SpringTextareaFieldTagProcessor' (template: "templates/fragments" - line 144, col 60)] with root cause
java.lang.StringIndexOutOfBoundsException: String index out of range: 0
In my form, there are text input and textarea as You see. news.title is saved ok, but news.content don't. When I replace for test these parameters (in the text input I use news.content and in the textarea there is th:field = ${news.title}) it works well too. Maybe should I use another expression instead of th:field?
News.java
package eniupage.domain;
public class News
{
private String title;
private String content;
private Date date;
public String getTitle()
{
return title;
}
public void setTitle(String title)
{
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content)
{
this.content = content;
}
public Date getDate()
{
return date;
}
public void setDate(Date date)
{
this.date = date;
}
}
HomeController.java
package eniupage.web;
#Controller
#RequestMapping( "/" )
public class HomeController
{
#Autowired
AddNewsService addNewsService;
#RequestMapping( method = GET )
public String home( Model model )
{
model.addAttribute( "newses", addNewsService.getNewses() );
return "home";
}
#RequestMapping( value = "/addNews", method = POST )
public String addNews( News news )
{
addNewsService.addNews( news );
return "redirect:/";
}
}
AdminController.java
#Controller
#RequestMapping( "/admin" )
public class AdminController
{
#RequestMapping( method = GET )
public String admin( Model model )
{
model.addAttribute( new News() );
return "admin";
}
}
There isn't any resulting of HTML form, because it isn't even displayed in div. There are only text input and submit button.
Edited html:
<form action="#" method = "POST" th:action="#{/addNews}" th:object = "${news}" id = "myform">
Tytuł: <input type = "text" th:field = "*{title}" />
<input type = "submit" value = "Add" /></br>
<textarea rows = "20" cols = "80" th:field = "*{content}" form = "myform" >... </textarea>
</form>
I'm using thymeleaf 3.0. Maybe this is the reason?
In reference I read :
The th:field attribute behaves differently depending on whether it is attached to an , or tag (and also depending on the specific type of tag).
But I can't find what is this difference between using th:field in input and textarea.

Related

Thymleaf how to take an input and then redirect to another page

I'm learning Spring boot. I have a list of products with unique ids, and I want to implement a "lookup by id" functionality, but I don't know how to do it, I searched but got totally different stuff.
I already have a #Getmapping method like this:
#Getmapping(/products/{id})
If I manually type in the id in the url I'll get what I what. But I want to have an input box in the HTML page like:
<form>
Look up by id: <input/>
</form>
and after I submit the form it'll redirect to that page. For example, if I enter input of 1, it'll go to localhost:8080/products/1
I've been searching but all I got was stuff about #Postmapping.
Add a #PostMapping to your controller:
#Controller
#RequestMapping("/products")
public class ProductController {
#GetMapping //Controller method for showing the empty form
public String index(Model model) {
model.addAttribute("formData", new SearchFormData()); // Create an empty form data object so Thymeleaf can bind to it
return "index";
}
#PostMapping
public String searchById(SearchFormData formData) {
return "redirect:/products/" + formData.getId(); //Use the value the user entered in the form to do the redirect
}
#GetMapping("/{id}")
public String showProduct(#PathVariable("id") long id) {
...
}
}
With SearchFormData representing the form fields (there is only 1 field in this case):
public class SearchFormData {
private long id;
// getters and setters
And update Thymeleaf template like this:
<form th:action="#{/products}" th:method="post" th:object="${formData}">
<input th:field="*{id}" type="number">
<button type="submit">Search</button>
</form>
Note that the value of th:object needs to match with the name used to add the SearchFormData instance to the model.
See Form handling with Thymeleaf for more info.
The following simple code will direct you to a URL that is generated from a concatenation of the base address of the <form>'s action attribute and the value of its first <input>:
document.querySelector("form").addEventListener("submit",function(ev){
ev.preventDefault();
this.action="/product/"+this.querySelector("input").value;
console.log(this.action);
// in real code: uncomment next line!
// this.submit()
})
<form>
Look up by id: <input type="text" value="123" />
</form>
In the real code you will delete the console.log() und uncomment the following line: this.submit().
Alternatively you could also do:
document.querySelector("form").addEventListener("submit",function(ev){
ev.preventDefault();
location = "/product/"+this.querySelector("input").value;
})
This will redirect you to the page without actually submitting the form.

upload multiple files with html5 multiple attribute - spring mvc

I am trying to upload multiple files using html5 attribute multiple. This link provide me a good start. However, I am facing a problem that I am not be able to read multipartFile in my controller.
Here is my POjO class
public class FileProduct {
private String name;
private List<MultipartFile> images;
}
My Controller
public String processNewListing(Model model
, #ModelAttribute FileProduct product
, HttpServletRequest request
) {
List<MultipartFile> files = product.getImages();
List<String> fileNames = new ArrayList<String>();
log.info("Files legnth: " + files.size());
log.info("name: " + product.getName());
}
And this if my form:
<form:form commandName="product" action="${newListingForm }" method="POST" enctype="multipart/form-data">
<form:input path="name" type="text"/>
<form:input path="images" type="file" multiple=""/>
<input type="submit">
</form:form>
So I am able to print out the "name" in my controller but my "files" is always has size of 1 regardless I have selected any file or not. I have followed the suggestion in the link to include common-fileupload and common-io, but the problem is not fix.
in jsp use following
<form ation="your/path" enctype="multipart/form-data" method="POST">
in controller use following
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
public String yourMethod(MultipartHttpServletRequest request){
}
this should resolve your problem

Incorrect byte[] value when html tag is used

I have an html helper method for a hidden field. It is bound to a byte[] and I have no problem as it displays the result correctly. But instead of the helper function if I use an html tag, the correct value is not displayed. Instead it displays its type.
following code and image will clarify what I am trying to say.
HTML code:
foreach (var path in Model.PathToImages)
{
<div class="form-group">
<div class="col-sm-6" style="vertical-align:central;">
<input type="button" value="Delete" class="btn btn-primary delete-property" name="#path.ImagePath" />
#Html.HiddenFor(m => path.ConcurrencyCheck)
<input id="#path.ImagePath" name="#path.ImagePath" type="hidden" value="#path.ConcurrencyCheck">
</div>
</div>
}
Property in my model:
public byte[] ConcurrencyCheck { get; set; }
Ignoring the names and id's of the control (this is just to reproduce the problem), following is the html generated:
Now as the image says when i use #Html.HiddenFor(m => path.ConcurrencyCheck) the value is correctly displayed but when I use <input id="#path.ImagePath" name="#path.ImagePath" type="hidden" value="#path.ConcurrencyCheck"> the value is the type System.Byte[].
So why I am not getting the value when I am using the html input tag or the problem is with the way model value should be displayed.
This because byte[] is a a complex array and needs to be converted to Base64String. The Html.HiddenFor() method takes this into account but #path.ConcurrencyCheck does not, and is using the .ToString() method of the properties value to generate the output.
You can view the source code here, but the relevant lines of code are
private static MvcHtmlString HiddenHelper(HtmlHelper htmlHelper, ModelMetadata metadata, object value, bool useViewData, string expression, IDictionary<string, object> htmlAttributes)
{
....
byte[] byteArrayValue = value as byte[];
if (byteArrayValue != null)
{
value = Convert.ToBase64String(byteArrayValue);
}
....

JSF 2.2 HTML5 Friendly Markup for number fields

I'm learning JSF with HTML5 friendly markup. I want to receive a number in a text field:
<form jsf:id="form_item">
<label for="nombre">Nombre:</label><input type="text" jsf:id="nombre" value="#{backend.item.nombre}"/>
<label for="edad">Edad:</label> <input type="text" jsf:id="edad" value="#{backend.item.edad}"/>
<input type="button" jsf:action="#{backend.addItem}" value="Agregar"/>;
</form>
Backend bean:
#Named(value = "backend")
#ViewScoped
public class Backend implements Serializable{
private Item item;
private List<Item> items;
/**
* Creates a new instance of Backend
*/
public Backend() {
this.items = new ArrayList<>();
this.item = new Item();
}
public void addItem(){
this.getItems().add(item);
}
//Setters and getters
Item bean:
public class Item {
private String nombre;
private Integer edad;
public Item(){
this.nombre="";
this.edad=0;
}
//Setters and getters
This code results in java.lang.Integer cannot be cast to java.lang.String.
Replacing the input text by h:inputText works:
<h:inputText id="edad" value="#{backend.item.edad}"/>
Do I have to give up HTML5 friendly markup in this case?
The app is running in GF4 Full profile.
I finally was able to make this work using
<input type="text" jsf:id="edad" jsf:value="#{backend.item.edad}"/>
By adding jsf: before the value attribute, everything worked as expected. The JEE7 tutorial says in 8.9.1 Using Pass-through Elements that "specify at least one of its attributes using the http://xmlns.jcp.org/jsf namespace", with an example showing the value property without the namespace. But it seems this only works if the bean property is a String.
Any time you want to make your attribute be "JSF-ish" you need to prefix it with the JSF namespace. I'm glad you found the answer anyway!
Ed

Webmatrix: passing ModelState to partial page

I am building a WebPages site and have an issue when I try to pass ModelState data to a partial page.
Here is the code for Create.cshtml:
#{
Page.Title = "Create Fund";
var name = "";
if (IsPost) {
name = Request.Form["name"];
if (!name.IsValidStringLength(2, 128)) {
ModelState.AddError("name", "Name must be between 2 and 128 characters long.");
}
}
}
#RenderPage("_fundForm.cshtml", name)
Here is the code for _fundForm.cshtml:
#{
string name = PageData[0];
}
<form method="post" action="" id="subForm">
<fieldset>
#Html.ValidationSummary(true)
<legend>Fund</legend>
<p>
#Html.Label("Name:", "name")
#Html.TextBox("name", name)
#Html.ValidationMessage("name", new { #class = "validation-error" })
</p>
<p>
<input type="submit" name="submit" value="Save" />
</p>
</fieldset>
</form>
The issue I am having is when there is an error for "name", the validation error does not display. Is there a special way to pass ModelState between the two pages?
_fundForm is going to be shared between Create.cshtml and Edit.cshtml.
ModelState is a readonly property of System.Web.WebPages.WebPage class. Its backing field is a private ModelStateDictionary and is initialized at first access. I can't see any way to force ModelState across pages, apart from doing it via reflection as seen in SO question: Can I change a private readonly field in C# using reflection?
Otherwise, you can simply use a third parameter in the invocation, like this:
#RenderPage("_fundForm.cshtml", name, ModelState);
In effect, the first parameter after the page name will become the Model of the new page, so there is enough space (i.e. the next parameter) to pass the ModelState.
In your "_fundForm.cshtml" merge the ModelState received by the calling page with the local one, like this:
#{
//In _fundForm.cshtml
var ms = PageData[1];
ModelState.Merge((ModelStateDictionary)ms);
}