Sort direction in url Spring Pageable - spring-mvc

I have a controller with following method. I can add a property 'sort' to my form and it will automatically use that property to sort. Now I'm trying to add the sort direction to it, but I can't seem to find the correct request param to add to my form.
public String overview(Criteria criteria, #PageableDefault(sort = "name") Pageable page, Model model)
This is my form:
<form action="">
<div>
<label for="sort">Sort by</label>
<select name="sort" id="sort">
<option value="name" selected>Name</option>
<option value="description">Description</option>
</select>
</div>
<div>
<label for="ascending">
<input name="order" id="ascending" type="radio" value="ASC" />
<div>Ascending</div>
</label>
<label for="descending">
<input name="order" id="descending" type="radio" value="DESC" />
<div>Descending</div>
</label>
</div>
<div>
<button type="submit">Search</button>
</div>
</form>
The url looks like this:
/overview?sort=name&order=DESC
I've tried some other parameter names than order, but nothing seems to work.

Extend your controller method like this:
public String overview(Criteria criteria, #PageableDefault(sort = "name", final #RequestParam(value = "order", defaultValue = "ASC") String order, Pageable page, Model model) {
// process the order argument here
}
Now you can process the argument "order".

The direction should be appended to the sort query parameter such that it will look like this
/overview?sort=name,DESC
Just repeat for multiple sort fields like
/overview?sort=name,DESC&sort=description,ASC

Related

How can I make dropdown list with freemarker?

I am trying to get a list from datatbase using Freemarker. I want to make select dropdown list, but I don't undestand what I missed.
I did this:
<div class="form-group">
<select name="category" class="form-control" required>
<#list categories! as category>
<option value="${category.id}">${category.name}</option>
</#list>
</select>
</div>
I have a form but I don't see any options.
With Thymeleaf I could do this but in the project I want to use freemarker
<div class="form-group">
<select th:field="*{category}" class="form-control" required>
<th:block th:each="category : ${categories}">
<option th:text="${category.name}" value="${category.id}"/>
</th:block>
</select>
</div>
In fact I need "translate" this part from Thymeleaf to Freemarker and I don't know how.
Yes, the code fragement was correct, I just placed in a PostMapping instead of GetMapping. Now it works good with controller below:
#GetMapping("/items")
public String main(Model model) {
Iterable<Item> items;
model.addAttribute("items", items);
List<Category> categories = categoryRepository.findAll();
model.addAttribute("categories", categories);
return "items";
}

Saving record in a proper way

I have a problem with saving records to DB with Spring-Mvc and Thymeleaf.
When I click "Update" button on record, to enter the update form (included beneath), all values are in place correctly, but, when I want to subbmit, an error occur. There is no any stacktrace in console, only error in web page, that I am not able to solve.
This is my code:
Controller:
#GetMapping("/{maltId}")
public ModelAndView showMalt(#PathVariable("maltId") Long maltId) {
ModelAndView mav = new ModelAndView("malt/malt-show");
mav.addObject(maltService.findById(maltId));
return mav;
}
#GetMapping("/{maltId}/edit")
public String initUpdateMaltForm(#PathVariable("maltId") Long maltId, Model model) {
model.addAttribute("malt", maltService.findById(maltId));
return VIEWS_MALT_CREATE_OR_UPDATE_FORM;
}
#PostMapping("/{maltId}/edit")
public String processUpdateMaltForm(#Valid Malt malt, BindingResult result, #PathVariable("maltId") Long maltId) {
if (result.hasErrors()) {
return VIEWS_MALT_CREATE_OR_UPDATE_FORM;
} else {
malt.setId(maltId);
Malt savedMalt = maltService.save(malt);
return "redirect:/malt/" + savedMalt.getId();
}
}
Model:
#Column(name="malt_name")
private String maltName;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="producer_id")
private Producer producer;
#Column(name="malt_filling")
private int maltFilling;
#Column(name="malt_ebc")
private int maltEbc;
#Column(name="malt_usage")
private String maltUsage;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="country_id")
private Country country;
#ManyToMany(mappedBy="malts")
private Set<Batch> batches;
This is the view:
<body>
<form th:object="${malt}" th:action="#{/malt/}" method="post">
<input type="hidden" th:field="*{id}" />
<label>Malt name:</label>
<input type="text" class="form-control" th:field="*{maltName}" />
<label>Producer:</label>
<input type="text" class="form-control"
th:field="*{producer.producerName}" />
<label>Country:</label>
<select class="form-control" th:field="*{country.id}">
<option value="0">Select country</option>
<option th:each="country : ${countries}"
th:value="${country?.id}"
th:text="${country?.countryName}">
</option>
</select>
<label>Malt filling:</label>
<input type="text" class="form-control"
th:field="*{maltFilling}" />
<label>Malt usage:</label>
<input type="text" class="form-control"
th:field="*{maltUsage}" />
<label>Malt EBC:</label>
<input type="number" class="form-control"
th:field="*{maltEbc}" />
<button class="submit-button" type="submit">Submit</button>
</form>
</body>
When I hit Submit button, I get this error:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Wed May 15 22:46:22 CEST 2019
There was an unexpected error (type=Not Found, status=404).
No message available
I have tried couple of different approaches, but nothing helps, and since there is no stacktrace in console, I have no idea what is wrong here.
Link to repo: https://github.com/fangirsan/maruszka-new
No stack trace 404 normally indicates that there is no mapping. Since you have, potentially, provided only a part of your Controller I assume that the causation for this is the code in your view right here:
<form th:object="${malt}" th:action="#{/malt/}" method="post">
The action takes to ("/malt/"), however, your controller has not got mapping for this?!
I expect that this should fix it:
<form th:object="${malt}" th:action="#{${'/' + malt.id + '/edit'}}" method="post">
Update
Had a look at your controller and you have the following annotations on your class
#Controller
#RequestMapping("/malt")
public class MaltController{..
#RequestMapping("/malt") will now make your path to save ../malt/{id}/edit'. The code below now should work:
<form th:object="${malt}" th:action="#{${'/malt/' + malt.id + '/edit'}}" method="post">
On using "#{${...}}"
#{} is a link variable, contents within this tag will be appended to the applications root context, e.g., at Stack Overflow #{'/posts'} would result with https://stackoverflow.com/posts
The ${} is a variable expression which will return a String or the object's .toString() value.
If we want to pass a variable within #{} link variable we must include the ${} variable within it, thus resulting in :
#{${'/hope/this/helps' + yourVariable}}

Cannot upload an image

I'm trying to update an image to my database, I defined as property model (bounded by database) the following:
public byte[] AvatarImage { get; set; }
then I created another property which store the value in the ViewModel:
public IFormFile AvatarImage { get; set; }
this steps are also described here in the doc.
Iside my form, I added the following html:
<div class="form-group text-center col-lg-12">
<img src="#Model.AvatarImage" class="avatar img-circle" alt="avatar" />
<h6>#Localizer["UploadNewAvatar"] ...</h6>
<input type="file" class="form-control" id="avatarUrl" asp-for="#Model.AvatarImages" />
</div>
when I submit the form the property AvatarImage is even null. But I don't understand why happen this, because all the other form properties are valorized correctly
Sounds like you are missing the form enctype.
Make sure you have:
<form enctype="multipart/form-data">
... inputs
<form>
Your <input type="file"> element assignment below seems to be wrong, because it uses #Model directive which outputs value of AvatarImages property (and the property is not exist in viewmodel class):
<input type="file" class="form-control" id="avatarUrl" asp-for="#Model.AvatarImages" />
The correct way is just using the property name like example below, because asp-for="PropertyName" is equivalent to model => model.PropertyName in HTML helper (assumed you have #model directive set to a viewmodel class):
<input type="file" class="form-control" asp-for="AvatarImage" />
Also don't forget to specify enctype="multipart/form-data" attribute in <form> tag helper:
<form asp-controller="ControllerName" asp-action="ActionName" method="post" enctype="multipart/form-data">
<!-- form contents here -->
</form>
Reference: Tag Helpers in forms in ASP.NET Core
First add enctype="multipart/form-data" to form ;
Then,check your #model, two situations :
1.Use Model directly, since the image is a byte array type, you need to convert the file type to byte[] during the submission process.
2.Or you could use ViewModel, and change the parameter type to viewmodel in the method.

ASP.NET Core using two models in single form

I am using Tuple to pass two models inside the view like code given below.
#model Tuple<AdvanceSearchModel, List<SearchUserModel>>
<form role="search" method="post" action="/Public/AdvanceSearch">
<div class="form-group">
<label>Name</label>
<input name="FullNames" type="text" class="form-control" value=""/>
</div>
<div class="form-group">
<label>Product</label>
<input name="Products" type="text" class="form-control" value="" />
</div>
<div class="form-group">
<label>Location:</label>
<input name="Location" type="text" class="form-control" value="" />
</div>
<div class="form-group">
<label>State</label>
<input name="States" type="text" class="form-control" value="" />
</div>
<div class="form-group">
<label>Country</label>
<input name="Countries" type="text" class="form-control" value=""/>
</div>
</form>
All the name attributes inside inputs are of AdvanceSearchModel. How do I use tag helper such as asp-for when passing multiple model to the views containing one or multiple forms? Also how do I retain values of the form after submitting the form in above scenario?
As you can see in the source code of InputTagHelper
You can see it creates the name attribute based on the (lambda) expression in html-tag:asp-for.
what you need
You need a form name tag like this SearchUserModel[0].Location
Where:
SearchUserModel is the property name on the model which is in the controller method you post to
[0] is the index in the list
Location is the property on the iten in the list the SearchUserModel instance
My suggestion
Not to do
Extend the InputTagHelper and add a prefix option (which adds a prefex to the name).
Use a view model Not a tuple!
Create a partial view that only takes SearchUserModel + a prefix (like an int for which row in the list it is for example: usermodel[1])
In your view loop over the list and call the partial.
result
#model SearchUserModel
<input asp-for="Location" my-prefix="ListItem[#Model.Id]" class="form-control" />
Better longterm option
Make a HTML template on how SearchUserModel part of the form should look.
Do ajax call to get the data or put the data as json in your view. (or take step 3 from what not to do)
Generate the form with well structured javascript.
On submit Instead of submitting the form, parse the from to json and send this as json ajax call.
Why do i say this? It is easier to debug if you get weird databindings in your controller.
That said, option 1 is perfectly fine but it might lead to problems later, as it is very static template, you wont be able to add or remove rows easily.
References for proper html name tags for lists:
http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
How does MVC 4 List Model Binding work?

Strange behavior of ASP.NET MVC Razor engine with unobtrusive validation attributes

I've just faced a really strange behavior of Razor engine regarding adding unobtrusive validation attributes to inputs in forms. In some cases attributes are not added.
So, I have two similar forms on the same page with similar input elements. They must be submitted to different url's.
From model side, I have a few DataAnnotations Attributes, applied to properties to have a client-side validation.
Here is my a bit simplified code of ViewModel:
public class ApplicantPersonalInfo
{
[Required]
[Display(Name = "First Name")]
[StringLength(20, MinimumLength = 2)]
[RegularExpression(#"^[ÀàÂâÆæÇçÉéÈèÊêËëÎîÏïÔôŒœÙùÛûÜüŸÿa-zA-Z \.‘'`-]+$", ErrorMessage = "First Name is in incorrect format")]
public string FirstName { get; set; }
}
Now I want to create two forms. For every of them action url can differ dynamically (I submit them using ajaxSubmit from jQuery Form Plugin), that's why I decided not to use Html.BeginForm helper method, instead creating them with simple <form> tag. So my code is:
<form id="form1">
#Html.TextBoxFor(m => Model.FirstName, new { id = "firstName1" })
</form>
<form id="form2">
#Html.TextBoxFor(m => Model.FirstName, new { id = "firstName2" })
</form>
And the most interesting is the resulting html code:
<form id="form1">
<input data-val="true" data-val-length="The field First Name must be a string with a minimum length of 2 and a maximum length of 20." data-val-length-max="20" data-val-length-min="2" data-val-regex="First Name is in incorrect format" data-val-regex-pattern="^[ÀàÂâÆæÇçÉéÈèÊêËëÎîÏïÔôŒœÙùÛûÜüŸÿa-zA-Z \.‘'`-]+$" data-val-required="The First Name field is required." id="firstName1" name="FirstName" type="text" value=""/>
</form>
<form id="form2">
<input id="firstName2" name="FirstName" type="text" value=""/>
</form>
See? All those validation attributes are not applied to the second input!
BUT if the form is generated using Html.BeginForm, like this:
#using (Html.BeginForm("NotExistingController", "NotExisitngAtion", FormMethod.Post, new { id = "form1" }))
{
#Html.TextBoxFor(m => Model.HomeOwner.FirstName, new { id = "firstName1" })
}
<form id="form2">
#Html.TextBoxFor(m => Model.HomeOwner.FirstName, new { id = "firstName2" })
</form>
the resulting html code is with attributes in both inputs:
<form action="/NotExisitngAtion/NotExistingController" id="form1" method="post">
<input data-val="true" data-val-length="The field First Name must be a string with a minimum length of 2 and a maximum length of 20." data-val-length-max="20" data-val-length-min="2" data-val-regex="First Name is in incorrect format" data-val-regex-pattern="^[ÀàÂâÆæÇçÉéÈèÊêËëÎîÏïÔôŒœÙùÛûÜüŸÿa-zA-Z \.‘'`-]+$" data-val-required="The First Name field is required." id="firstName1" name="FirstName" type="text" value="" />
</form>
<form id="form2">
<input data-val="true" data-val-length="The field First Name must be a string with a minimum length of 2 and a maximum length of 20." data-val-length-max="20" data-val-length-min="2" data-val-regex="First Name is in incorrect format" data-val-regex-pattern="^[ÀàÂâÆæÇçÉéÈèÊêËëÎîÏïÔôŒœÙùÛûÜüŸÿa-zA-Z \.‘'`-]+$" data-val-required="The First Name field is required." id="firstName2" name="FirstName" type="text" value="" />
</form>
I'm really confused by this behavior. And I've tried - you can add as much forms as you want using Html.BeginForm - every of them will have set of validation attributes; but if you add >1 form using simple <form> tag, starting from the second one attributes are missing.
So am I missing something or it's a bug in Razor engine?

Resources