Proper way of handling 'null' in 'select' form - spring-mvc

I have a problem with handling null value in select form and returning proper message, that this value can't be null.
My view (part of the form):
<label>Producer:</label>
<select class="form-control" th:field="*{producer.id}" th:errorclass="has-error">
<option value="0">Select producer</option>
<option th:each="producer : ${producers}"
th:value="${producer?.id}"
th:text="${producer?.producerName}">
</option>
</select>
<span th:if="${#fields.hasErrors('producer')}">
<ul>
<li th:each="err : ${#fields.errors('producer')}" th:text="${'- ' + err}"/>
</ul>
</span>
Part of Malt controller:
#NotNull
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="producer_id")
private Producer producer;
Spring (or thymeleaf?) requires value, so I set it to 0 - This relation is ManyToMany, and values are taken from database - there is no 0, so error is thrown:
org.thymeleaf.exceptions.TemplateProcessingException: Attribute "value" is required in "option" tags (template: "malt/createOrUpdateMaltForm" - line 55, col 9)
I have tried few different approaches, but I am not able to return any error message to the form, informing user to choose one of the available option.
How can I achieve this?

Related

ASP.NET Core View - passing altered output value based on user input

In my model, among other columns, I have Status column defined like this:
[Column(TypeName = "BIT")]
[Display(Name = "Status")]
public bool Status { get; set; }
So, it can have values only 1 or 0.
Instead of 1 or 0 in Index view I have to show text value in rows: Activated (for 1) and Deactivited (for 0). I've done that with:
<td>#(item.Status ? "Activated " : "Deactivted ")</td>
My question is - in Edit view, how do I show values "Activated" and "Deactivated" in drop list and on submit actually pass 1 or 0?
I have this code so far:
<div class="form-group">
<label asp-for="Status" class="control-label"></label>
<select class="form-control" asp-for="Status">
<option>Activated</option>
<option>Deactivated</option>
</select>
</div>
But this code is passing text (Activated or Deactivated) instead of 0 or 1.
So I need to insert something like if "Activated" pass 1 othervise pass 0.
How do I do that?
Try using value attribute with true and false values correspondingly:
<select class="form-control" asp-for="Nkz10Status">
<option value="true">Activated</option>
<option value="false">Deactivated</option>
</select>

Tag Helper asp-validation-for is always showing

I tried using the Asp.Net Core TagHelper but it doesn't seem to work. However, when using HtmlHelpers it works as expected. My issue is that it always display the error message although the ModelState is valid. Am I doing something wrong or can someone reproduce this error?
<label class="control-label" asp-for="Firstname">Firstname</label>
<input type="text" class="form-control" asp-for="Firstname">
<span class="form-control-feedback" asp-validation-for="Firstname"> This field has an error. </span>
The property Firstname has a Required attribute in the ViewModel.
It works like this:
<label class="control-label" asp-for="Firstname">Firstname</label>
<input type="text" class="form-control" asp-for="Firstname">
#Html.ValidationMessageFor(x => x.Firstname)
Edit:
It seems to work if I don't add the custom error message to the Html element but instead to the ViewModel DataAnnotation, like this:
<label class="control-label" asp-for="Firstname">Firstname</label>
<input type="text" class="form-control" asp-for="Firstname">
<span class="form-control-feedback" asp-validation-for="Firstname"></span>
Model:
[Required(ErrorMessage = "This field has an error.")]
public string Firstname { get; set; }
TL;DR:
Consider putting text inside the tag helpers in scenarios when you really want something
different from the generated value.
Full answer
You practically find the solution on your own, but I think I can still throw in my two cents here.
Most tag helpers work in a manner of generating content on a condition when its content is empty or contain only whitespace characters. For example, the ValidationMessageTagHelper checks it in this way:
var tagHelperContent = await output.GetChildContentAsync();
// We check for whitespace to detect scenarios such as:
// <span validation-for="Name">
// </span>
if (!tagHelperContent.IsEmptyOrWhiteSpace)
{
message = tagHelperContent.GetContent();
}
It gets tag content and then fills up message variable if the content is null, empty or whitespace. The message variable is then used to generate the validation message:
var tagBuilder = Generator.GenerateValidationMessage(
ViewContext,
For.ModelExplorer,
For.Name,
message: message,
tag: null,
htmlAttributes: htmlAttributes);
If the message is null or empty then the generator will provide the model error (see line 858 of DefaultHtmlGenerator);
if (!string.IsNullOrEmpty(message))
{
tagBuilder.InnerHtml.SetContent(message);
}
else if (modelError != null)
{
modelExplorer = modelExplorer ?? ExpressionMetadataProvider.FromStringExpression(
expression,
viewContext.ViewData,
_metadataProvider);
tagBuilder.InnerHtml.SetContent(
ValidationHelpers.GetModelErrorMessageOrDefault(modelError, entry, modelExplorer));
}
The GetModelErrorMessageOrDefault() of ValidationHelpers:
public static string GetModelErrorMessageOrDefault(
ModelError modelError,
ModelStateEntry containingEntry,
ModelExplorer modelExplorer)
{
Debug.Assert(modelError != null);
Debug.Assert(containingEntry != null);
Debug.Assert(modelExplorer != null);
if (!string.IsNullOrEmpty(modelError.ErrorMessage))
{
return modelError.ErrorMessage;
}
// Default in the ValidationMessage case is a fallback error message.
var attemptedValue = containingEntry.AttemptedValue ?? "null";
return modelExplorer.Metadata.ModelBindingMessageProvider.ValueIsInvalidAccessor(attemptedValue);
}
So yes, if you put any text inside the <span> validation tag, the tag helper will choose your text over validation error from model state. Similar behaviour occurs if you put text inside the <label> tag as you did:
<label class="control-label" asp-for="Firstname">Firstname</label>
The tag helper will not overwrite the Firstname value you put inside the tag. It may not seem as bad behaviour, but if you would like to use display name for the Firstname property:
[Display(Name = "Fancy first name")]
public string Firstname { get; set; }
you would not see it work! Because the tag helper would again choose the text you put in-between <label> tags over the display name for Firstname.
What you should do is leave it as simple as i can be:
<label class="control-label" asp-for="Firstname"></label>
Consider putting text inside the tag helpers in scenarios when you really want something
different from the generated value.
At the begging I said that most tag helpers work that way. Most of them do, but not all of them. For example SelectTagHelper allows you to put any custom text inside the tag and if you provide a select list, it will generate the options by appending them to the existing content. It is extremely handy for adding custom <option> tags. For example I can easily add a selected and disabled option, so the dropdown does not have initial value, therefore the user is forced to manually select an option. These lines of code:
<select asp-for="LevelId" asp-items="#Model.Levels" class="custom-select">
<option selected disabled>Select option</option>
</select>
will result in:
<select class="custom-select" data-val="true" data-val-required="'Level Id' must not be empty." id="LevelId" name="LevelId">
<option selected disabled>Select parking level</option>
<option value="9">-2</option>
<option value="8">-1</option>
<option value="7">0</option>
</select>

How to populate dropdown in thymeleaf with hashmap set from controller

I want the dropdown in the view to be get filled with value set from the controller. In my controller using model attribute I have added the list limits
#RequestMapping(value = "/works", method = RequestMethod.GET)
public String searchWorks(Model model){
Map< Integer, String > limits = new HashMap<Integer, String>();
limits.put(20, "20件");
limits.put(25, "25件");
limits.put(30, "30件");
limits.put(35, "35件");
limits.put(40, "40件");
limits.put(45, "45件");
limits.put(50, "50件");
model.addAttribute("limits",limits);
model.addAttribute("limit",limit);
return "workSituation";
}
In view Page in order to populate the dropdown with the value set using model attribute I have used this code
<div class="col-sm-3">
<select class="form-control" name = "limit" id="limit" onchange="getList(this.value)">
<option selected>-- select number of items--</option>
<option data-th-each="limit : ${limits}"
data-th-value="${limt.getKey()}"
data-th-text="${limt.getValue()}" >
</select>
</div>
I am getting the error
caused by: org.attoparser.ParseException: Exception evaluating SpringEL expression: "limt.getKey()"
caused by: org.attoparser.ParseException: Exception evaluating SpringEL expression: "limt.getValue()"
What will be the cause for this. Actually dropdown is iterating 7 times(the number of records in the limits list) but the key and value is not able to fetch with code.What is the correct way to get the dropdown filled with
<option value="20">20件</option>
<option value="25">25件</option>
<option value="30">30件</option>
<option value="35">35件</option>
<option value="40">40件</option>
<option value="45">45件</option>
<option value="50">50件</option>
For thymeleaf try the following
<option th:each="limit : ${limits.entrySet()}" th:value="${limit.key}" th:text="${limit.value}"></option>

Spring MVC - HTTP status code 400 (Bad Request) for missing field when submitting form with dropdown

I'm using spring mvc with thymeleaf to save a form with a dropdown value. The problem is when saving, I get the error 400 bad request only when I append the foreign key field in the form as a dropdown with th:field="studentTypeCode".
<select id="typeSelect" class="text_box" th:field="*{studentTypeCode}">
<div th:each="studentTypeCode : ${studentTypeCodes}" th:remove="tag">
<option th:value="${studentTypeCode.typeId}" th:text="${studentTypeCode.typeName}" />
</div>
</select>
Student.java
public class Student{
private String name;
//... other fields
//..
StudentTypeCode studentTypeCode;
//getters and setters
}
And in the controller I get the Student object using the th:object with the
#ModelAttribute Student student param. Saving the form throws me 400 bad request since the field studentTypeCode is not correctly sent in the request.
I just found that since I'm using the #ModelAttribute I just need to send the proper value to the Student object with the student type code id. Declaring th:field="*{studentTypeCode.studentTypeId}" gives me the exact value(int) selected in the dropdown and I can save this value.
The following code,
<select id="typeSelect" class="text_box" th:field="*{studentTypeCode.studentTypeId}">
<div th:each="studentTypeCode : ${studentTypeCodes}" th:remove="tag">
<option th:value="${studentTypeCode.typeId}" th:text="${studentTypeCode.typeName}" />
</div>
</select>
solved my issue. Also add the BindingResult error after your #ModelAttribute field in the #RequestMapping. Read more this article.

Angularjs 1.2 adds empty option in select

I have a dropdown that gets its items from database. The default value to be selected in this dropdown is retrieved from a cookie.
The problem is,
1. there is an empty option tag as the first option and also,
2. the default value is not selected.
There is no issue at all when I use v1.3.16. This happens when using v1.2.0. (I have to use only v1.2.0)
How to fix this.
ASPX:
<select id="ddlItems" data-ng-model="ItemId">
<option value="-1">-- All Circles --</option>
<option data-ng-repeat="item in Items" value="{{item.AreaCode}}"
data-ng-selected="item.AreaCode==ItemId">{{item.AreaName}}</option>
</select>
Ctrl.js:
var promise = MyFactory.GetItems();
promise.then(function (success) {
if (success.data != null && success.data != '') {
var data = success.data;
$scope.Items = data.lstItems; //bind items to dropdown
$scope.ItemId = itemIdFromCookie; //select default value
alert(itemIdFromCookie); //it pops -1
}
else {
console.log(success.data);
}
}
Rendered HTML:
<select id="ddlItems" data-ng-model="ItemId">
<option value="? string:"-1" ?"></option>
<option value="-1">-- All Circles --</option>
//...other options
</select>
Try to use ngOptions instead
Like this
<select id="ddlItems"
data-ng-model="ItemId"
ng-options="item.AreaCode as item.AreaName for item in Items">
<option value="-1">-- All Circles --</option>
</select>
This happens becausedata-ng-model="ItemId" is empty when your model is App is Loaded.
To Have some initial value. you can put default value in ng-init
For Ex : data-ng-init='data-ng-model="--SELECT--";'
Or per your Array list Item which you are getting from database set one of the values.
Remember init will get triggered b/f you complete the Ajax Call.

Resources