Cannot fetch roles as an object(Spring boot/JSFetchAPI) - javascript

I'm very new to programming and I have a problem with getting roles from a form through FetchAPI.
This is my rest controller
#PostMapping("admin/users/save")
public ResponseEntity<User> save(#RequestBody User user){
return ResponseEntity.ok(userRepo.save(user));
}
This is JS
<script> const url1='http://localhost:8080/api/admin/users/save'
const form=document.getElementById("newUserForm")
form.addEventListener('submit', async (e)=>{
e.preventDefault()
const formData=new FormData(form);
const formDataSerialized = Object.fromEntries(formData)
const jsonobject = {...formDataSerialized}
console.log(formDataSerialized,"formDataSerialized")
try{
const responce= await fetch(url1, {
method: 'POST',
body: JSON.stringify(jsonobject),
headers: {
'Content-Type': 'application/json',
}
});
const json = await responce.json()
console.log(json)
}catch(e){
console.error(e)
alert('there was an error')
}
})
This is how an object should look in DB
[{"id":1,"name":"john","age":23,"country":"USA","email":"john#mail.com","password":"$2a$12$HbAWvSQU/QQ7HuTxdy862.ifBPSev3NdM5Sa1iGUL1YZdopnwbVZm","roles":[{"id":1,"name":"ADMIN","authority":"ADMIN"},
And this is what i get
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.util.ArrayList<sof.fetchapi314.entity.Role>` from String value (token `JsonToken.VALUE_STRING`); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.util.ArrayList<sof.fetchapi314.entity.Role>` from String value (token `JsonToken.VALUE_STRING`)<EOL> at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 77] (through reference chain: sof.fetchapi314.entity.User["roles"])]
After sending an object that looks like this
age: "22" country: "22" email: "22" name: "22" password: "22" roles: "2"
Form:
<h1>MyForm</h1>
<form id="newUserForm">
<div class="form-group">
<label class="col-form-label" for="name" style="display: block;
text-align: center;"><b>Name</b></label>
<input class="form-control" type="text" name="name" id="name"
placeholder="Enter name"/>
</div>
<div class="form-group">
<label class="col-form-label" for="age" style="display: block;
text-align: center;"><b>Age</b> </label>
<input class="form-control" type="number" name="age" step="1" min="1" max="150" id="age"
placeholder="Enter age"/>
</div>
<div class="form-group">
<label class="col-form-label" for="country" style="display: block;
text-align: center;"><b>Country</b> </label>
<input class="form-control" type="text" name="country" id="country"
placeholder="Enter country"/>
</div>
<div class="form-group">
<label class="col-form-label" for="email" style="display: block;
text-align: center;"><b>Email</b> </label>
<input class="form-control" type="text" name="email" id="email"
placeholder="Enter email"/>
</div>
<div class="form-group">
<label class="col-form-label" for="password" style="display: block;
text-align: center;"><b>Password</b> </label>
<input class="form-control" type="password" name="password" id="password"
placeholder="Enter password"/>
</div>
<div class="form-group">
<label class="col-form-label" for="roles" style="display: block;
text-align: center;"><b>Roles</b> </label>
<select class="form-select form-control" aria-label="multiple select example" id="roles"
name="roles" size="2" multiple="multiple" >
<option value="1">ADMIN</option>
<option value="2">USER</option>
</select>
</div>
<button type="submit" class="btn btn-success btn-lg">Submit</button>
</form>
I believe the problem is that I send roles as a number, not as an array and I have absolutelu no idea how to fix it. I an BAD in java and pre beginner level in JS.
Edit:
Role Class:
#Entity
#Data
#Table(name="roles")
public class Role implements GrantedAuthority, Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
public Role() {
}
private String name;
#Override
public String toString() {
return this.name;
}
#Override
public String getAuthority() {
return name;
}
}
and User class
#Data
#Entity
#Table(name = "users")
public class User implements UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "age")
private int age;
#Column(name = "country")
private String country;
#Column(name = "email")
String email;
#Column(name = "password")
String password;
#ManyToMany(cascade=CascadeType.MERGE)
#JoinTable(
name="user_role",
joinColumns={#JoinColumn(name="USER_ID", referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="ROLE_ID", referencedColumnName="id")})
private List<Role> roles;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList());
}
#Override
public String getUsername() {
return name;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}

Related

How can I add new product using angular and springboot?

I need to add a new product to my table. This is working http://localhost:8080/product/create/ on Postman. When I post and add a new product it returns the id and automatically adds it to my table. My problem is how can I achieve it on angular CLI. I don't how will I do it. Can somebody help me? Here's my code:
ProductsController.java
#PostMapping("/product/create/")
private int saveProduct(#RequestBody Products products)
{
productsService.save(products);
return products.getId();
}
app.component.ts
ngOnInit() {
this.getProduct();
}
public getProduct(): void {
this.productServive.getProduct().subscribe(
(response: Product[]) => {
this.products = response;
},
(error: HttpErrorResponse)=> {
alert(error.message);
}
)
}
public onAddProduct(addForm: NgForm): void {
this.productServive.addProduct(addForm.value).subscribe(
(response: Product) => {
console.log(response);
this.getProduct();
},
(error: HttpErrorResponse) => {
alert(error.message);
}
);
}
product.service.ts
private apiServerUrl = environment.apiBaseUrl;
constructor(private http: HttpClient) { }
public getProduct() : Observable<Product[]> {
return this.http.get<Product[]>(`${this.apiServerUrl}/products/hasstocks`);
}
public addProduct(product: Product) : Observable<Product> {
return this.http.post<Product>(`${this.apiServerUrl}/product/create/`, product);
}
app.component.html
<form #addForm="ngForm" (ngSubmit)="onAddProduct(addForm)">
<div class="mb-3">
<label for="name" class="col-form-label">Name:</label>
<input type="text" class="form-control" id="name" ngForm name="name">
</div>
<div class="mb-3">
<label for="description" class="col-form-label">Description:</label>
<input type="text" class="form-control" id="description" ngModel name="description">
</div>
<div class="mb-3">
<label for="sku" class="col-form-label">SKU:</label>
<input type="text" class="form-control" id="sku" ngModel name="sku">
</div>
<div class="mb-3">
<label for="price" class="col-form-label">Price:</label>
<input type="text" class="form-control" id="price" ngModel name="price">
</div>
<div class="mb-3">
<label for="quantity" class="col-form-label">Quantity:</label>
<input type="text" class="form-control" id="quantity" ngModel name="quantity">
</div>
<div class="float-end">
<button [disabled]="addForm.invalid" type="button" class="btn btn-primary">Add Product</button>
</div>
</form>
It can not be together, you must separete 2 methods as save and getProduct but you can attach getProduct methods and page and when you save products and refresh the page it will seems automatically adds it to your table

How to Interact between a view component's form and a controller in ASP.NET Core?

I'm beginner in web designing with ASP.NET Core. I wrote a view component that has a form with some inputs related to a view model. One of these inputs is a file input (of the IFormFile datatype).
I want to submit this view model to an action of a controller (POST action), check the validity of model, return another view component if the model state is valid, and remain on this view component with this view model if model state is not valid.
This is my View Model: PricingViewModel.cs
public class PricingViewModel
{
[Display(Name = "Select a file")]
public IFormFile formFile { get; set; }
[Display(Name = "ColumnCode")]
[Required(ErrorMessage = "Enter {0} value, please")]
public string colCode { get; set; }
[Display(Name = "ColumnName")]
[Required(ErrorMessage = "Enter {0} value, please")]
public string colName { get; set; }
}
My View Component (controller): PricingComponent.cs
public class PricingComponent : ViewComponent
{
public async Task<IViewComponentResult> InvokeAsync(PricingViewModel pricing)
{
return await Task.FromResult((IViewComponentResult)View("PricingView", pricing));
}
}
My View Component (view): PricingView.cshtml
<form class="text-left" method="post" enctype="multipart/form-data">
<input name="IsValidPricing" type="hidden" value="#ViewBag.isValid" />
<div class="form-group text-left">
<label asp-for="colCode" class="control-label"></label>
<input asp-for="colCode" class="form-control" id="colCodeId"/>
<span asp-validation-for="colCode" class="text-danger"></span>
</div>
<div class="form-group text-left">
<label asp-for="colName" class="control-label"></label>
<input asp-for="colName" class="form-control" id="colNameId"/>
<span asp-validation-for="colName" class="text-danger"></span>
</div>
<div class="form-group text-left">
<label asp-for="formFile " class="control-label"></label>
<input type="file" accept=".xlsx, .csv" asp-for="formFile" id="MyInputFile"/>
</div>
<div class="form-group mt-4">
<input type="submit" asp-action="ShowPricing" asp-controller="Home" value="Show" id="ShowPricingBtn" />
</div>
</form>
My Home Controller: HomeController.cs
[HttpPost]
public IActionResult ShowPricing(PricingViewModel pricing)
{
if (ModelState.IsValid)
{
int temp;
if (!int.TryParse(pricing.colCode, out temp))
{
ViewBag.isValid = 0;
ModelState.AddModelError("colCode", "Invalid Data");
return ViewComponent("PricingComponent", new { pricing = pricing }); // 1
}
else if (!int.TryParse(pricing.colName, out temp))
{
ViewBag.isValid = 0;
ModelState.AddModelError("colName", "Invalid Data");
return ViewComponent("PricingComponent", new { pricing = pricing }); //2
}
else
{
ViewBag.isValid = 1;
// do something ...
return ViewComponent("ShowPricingExcelComponent"); //Call another view component
}
}
else
{
ViewBag.isValid = 0;
return ViewComponent("PricingComponent", new { pricing = pricing }); //3
}
}
Plan A
The above approach is my primary plan.
Problem
If I use options of submit input tag (asp-action, asp-controller) like above, the view model sends correctly, but I don't know how to handle the validity of the model and remain on this view component. In the above code, when the ShowPricing action runs, if the model state is valid, the code works correctly, but when model is invalid (1,2,3), the PricingView doesn't show the validation summery, and just loads with current view model.
Plan B
I used AJAX to send the viewModel to the action and instead of showing the validation summary, I send an alert to the user with AJAX. I changed PricingView as following:
My View Component (view): PricingView.cshtml
<form class="text-left" method="post" enctype="multipart/form-data">
<input name="IsValidPricing" type="hidden" value="#ViewBag.isValid" />
<div class="form-group text-left">
<label asp-for="colCode" class="control-label"></label>
<input asp-for="colCode" class="form-control" id="colCodeId"/>
<span asp-validation-for="colCode" class="text-danger"></span>
</div>
<div class="form-group text-left">
<label asp-for="colName" class="control-label"></label>
<input asp-for="colName" class="form-control" id="colNameId"/>
<span asp-validation-for="colName" class="text-danger"></span>
</div>
<div class="form-group text-left">
<label asp-for="fromFile " class="control-label"></label>
<input type="file" accept=".xlsx, .csv" asp-for="formFile" id="MyInputFile"/>
</div>
<script>
$(document).ready(function () {
$('#ShowPricingBtn').click(function () {
var _url = '#Url.Action("ShowPricing", "Home")';
var input = $("#MyInputFile").get(0).files[0];
$.ajax({
type: "POST",
url: _url,
data: {
formFile: input,
colCode: $("#colCode").val(),
colName: $("#colName").val(),
},
success: function (result)
{
var IsValid = $('body').find('[name="IsValidPricing"]').val();
if (IsValid)
{
$("#ShowExcelTable").html(result);
}
else {
alert("Invalid Data");
}
},
});
});
});
</script>
<div class="form-group mt-4">
<input type="submit" value="Show" id="ShowPricingBtn" />
</div>
</form>
Problem
In this code:
If the model state is not valid, the alert sends correctly, but
If the model state is valid, the formFile input doesn't send correctly to action and it's null in view model.
I don't know whether I should go with the original or the alternate approach these problems. Do you know where I'm going wrong?
Not sure how do you call view components,here are the working demos:
For PlanA
1.Create ViewComponents/PricingComponent.cs and ViewComponents/ShowPricingExcelComponent.cs.
public class PricingComponent : ViewComponent
{
public async Task<IViewComponentResult> InvokeAsync(PricingViewModel pricing)
{
return await Task.FromResult((IViewComponentResult)View("PricingView", pricing));
}
}
public class ShowPricingExcelComponent : ViewComponent
{
public async Task<IViewComponentResult> InvokeAsync(PricingViewModel pricing)
{
return await Task.FromResult((IViewComponentResult)View("ShowPricingExcel", pricing));
}
}
2.Create Views/Shared/Components/PricingComponent/PricingView.cshtml.
#model PricingViewModel
<form class="text-left" method="post" enctype="multipart/form-data">
<input name="IsValidPricing" type="hidden" value="#ViewBag.isValid" />
<div class="form-group text-left">
<label asp-for="colCode" class="control-label"></label>
<input asp-for="colCode" class="form-control" id="colCodeId" />
<span asp-validation-for="colCode" class="text-danger"></span>
</div>
<div class="form-group text-left">
<label asp-for="colName" class="control-label"></label>
<input asp-for="colName" class="form-control" id="colNameId" />
<span asp-validation-for="colName" class="text-danger"></span>
</div>
<div class="form-group text-left">
<label asp-for="formFile " class="control-label"></label>
<input type="file" accept=".xlsx, .csv" asp-for="formFile" id="MyInputFile" />
</div>
<div class="form-group mt-4">
<input type="submit" asp-action="ShowPricing" asp-controller="Home" value="Show" id="ShowPricingBtn" />
</div>
</form>
3.Create Views/Shared/Components/ShowPricingExcelComponent/ShowPricingExcel.cshtml.
<h1>Excel....</h1>
Project Structure:
4.Views/Home/Index.cshtml:
#await Component.InvokeAsync("PricingComponent")
5.HomeController:
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpPost]
public IActionResult ShowPricing(PricingViewModel pricing)
{
if (ModelState.IsValid)
{
int temp;
if (!int.TryParse(pricing.colCode, out temp))
{
ViewBag.isValid = 0;
ModelState.AddModelError("colCode", "Invalid Data");
return View("Index", pricing);
}
if (!int.TryParse(pricing.colName, out temp))
{
ViewBag.isValid = 0;
ModelState.AddModelError("colName", "Invalid Data");
return View("Index", pricing);
}
else
{
ViewBag.isValid = 1;
// do something ...
return ViewComponent("ShowPricingExcelComponent"); //Call another view component
}
}
else
{
ViewBag.isValid = 0;
return View("Index", pricing); //3
}
}
}
Result:
For PlanB
1.Create ViewComponents/PricingComponent.cs and ViewComponents/ShowPricingExcelComponent.cs.
2.Create Views/Shared/Components/PricingComponent/PricingView.cshtml.
Firstly,it should be type="button" otherwise it will call twice to the backend.Secondly,what you did in ajax is not correct,more detailed explation you could refer to this answer.At last,you could not judge the modelstate by get the value of IsValidPricing value in your sucess function.Because the value you get is always be the data you first render the page,you cannot get the changed ViewBag value when ajax post back.
#model PricingViewModel
<form class="text-left" method="post" enctype="multipart/form-data">
<input name="IsValidPricing" type="hidden" value="#ViewBag.isValid" />
<div class="form-group text-left">
<label asp-for="colCode" class="control-label"></label>
<input asp-for="colCode" class="form-control" id="colCodeId" />
<span asp-validation-for="colCode" class="text-danger"></span>
</div>
<div class="form-group text-left">
<label asp-for="colName" class="control-label"></label>
<input asp-for="colName" class="form-control" id="colNameId" />
<span asp-validation-for="colName" class="text-danger"></span>
</div>
<div class="form-group text-left">
<label asp-for="formFile " class="control-label"></label>
<input type="file" accept=".xlsx, .csv" asp-for="formFile" id="MyInputFile" />
</div>
<div class="form-group mt-4">
#*it should be type="button"*#
<input type="button" value="Show" id="ShowPricingBtn" />
</div>
</form>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script>
$(document).ready(function () {
$('#ShowPricingBtn').click(function () {
var _url = '#Url.Action("ShowPricing", "Home")';
var input = $("#MyInputFile").get(0).files[0];
var fdata = new FormData();
fdata.append("formFile", input);
$("form input[type='text']").each(function (x, y) {
fdata.append($(y).attr("name"), $(y).val());
});
$.ajax({
type: "POST",
url: _url,
data: fdata,
contentType: false,
processData: false,
success: function (result)
{
console.log(result);
if (result==false)
{
alert("Invalid Data");
}
else {
$("#ShowExcelTable").html(result);
}
},
});
});
});
</script>
3.Create Views/Shared/Components/ShowPricingExcelComponent/ShowPricingExcel.cshtml.
<h1>Excel....</h1>
4.Views/Home/Index.cshtml:
#await Component.InvokeAsync("PricingComponent")
<div id="ShowExcelTable"></div>
5.HomeController:
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpPost]
public IActionResult ShowPricing(PricingViewModel pricing)
{
if (ModelState.IsValid)
{
int temp;
if (!int.TryParse(pricing.colCode, out temp)|| !int.TryParse(pricing.colName, out temp))
{
ViewBag.isValid = 0;
return Json(false);
}
else
{
ViewBag.isValid = 1;
// do something ...
return ViewComponent("ShowPricingExcelComponent"); //Call another view component
}
}
else
{
ViewBag.isValid = 0;
return Json(false);
}
}
}
Result:
I'm not able to reproduce your error. Your code, as presented, works as expected. A validation message is displayed.
To make it a working example, I've added a GET method first.
[HttpGet]
public IActionResult ShowPricing() => ViewComponent("PricingComponent", new { pricing = new PricingViewModel() });
Open the URL Home/ShowPricing
Fill out the form.
Send the form. And the validation message is displayed.

How to filter the options of a drop down list using another drop down list asp.net core 3.0

I would like to select a product category in one drop-down list and display subcategories for it.
My models:
ProductCategory
public class ProductCategory
{
[Key]
public int Id { get; set; }
[Required(ErrorMessage ="Pole 'Nazwa kategorii' jest wymagane")]
[Display(Name ="Nazwa kategorii")]
public string Name { get; set; }
[Display(Name = "Dodaj zdjęcie")]
public string ImagePath { get; set; }
public virtual ICollection<ProductSubCategory> ProductSubCategory { get; set; }
}
ProductSubCategory
public class ProductSubCategory
{
[Key]
public int Id { get; set; }
[Required(ErrorMessage = "Pole 'Nazwa podkategorii' jest wymagane")]
[Display(Name = "Nazwa kategorii")]
public string Name { get; set; }
[Display(Name = "Wybierz zdjęcie")]
public string ImagePath { get; set; }
public int ProductCategoryId { get; set; }
[ForeignKey("ProductCategoryId")]
[Display(Name = "Kategoria")]
public ProductCategory ProductCategory { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
Create Product Page
public IActionResult OnGet()
{
ViewData["ProductCategoryId"] = new SelectList(_context.ProductCategory, "Id", "Name");
ViewData["ProductSubCategoryId"] = new SelectList(_context.ProductSubCategory, "Id", "Name");
return Page();
}
public JsonResult OnGetSubCategories(int category)
{
var subcategories = _context.ProductSubCategory.Where(c => c.ProductCategoryId == category).ToList();
return new JsonResult(new SelectList(subcategories, "Id", "Name"));
}
CreateProduct html
<form method="post" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Product.Name" class="control-label"></label>
<input asp-for="Product.Name" class="form-control" />
<span asp-validation-for="Product.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Product.Description" class="control-label"></label>
<input asp-for="Product.Description" class="form-control" />
<span asp-validation-for="Product.Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Product.ImagePath" class="control-label"></label>
<input type="file" asp-for="Product.ImagePath" class="form-control" name="image" onchange="readURL(this);"/>
<span asp-validation-for="Product.ImagePath" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Product.DateOfNotification" class="control-label"></label>
<input asp-for="Product.DateOfNotification" class="form-control" />
<span asp-validation-for="Product.DateOfNotification" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Product.ProductCategoryId" class="control-label"></label>
<select id="category" asp-for="Product.ProductCategoryId" class ="form-control" asp-items="ViewBag.ProductCategoryId"></select>
</div>
<div class="form-group">
<label asp-for="Product.ProductSubCategoryId" class="control-label"></label>
<select id="subCategory" asp-for="Product.ProductSubCategoryId" class ="form-control" asp-items="ViewBag.ProductSubCategoryId"></select>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
script
<script>
$("#category").change(function () {
var category = $("#category").val();
$.ajax({
url: "CreteProduct?handler=SubCategories",
method: "GET",
data: { category: category },
success: function (data) {
$("#subCategory option").remove();
$.each(data, function (index, itemData) {
$("#subCategory").append("<option value='" + itemData.Id + "'>" + itemData.Name + "</option>");
});
}
})
});
</script>
Result: The subcategory drop-down list is undefined. After selecting the category the number of items is good but displays 'undefined'.
You need to use itemData.value to replace itemData.Id,itemData.text to replace itemData.Name.Here is a demo worked:
cshtml.cs:
public class CreateProductModel : PageModel
{
[BindProperty]
public Product Product { get; set; }
public static List<ProductCategory> productCategories = new List<ProductCategory> { new ProductCategory { Id = 1, Name = "pc1" }, new ProductCategory { Id = 2, Name = "pc2" } };
public static List<ProductSubCategory> productSubCategories = new List<ProductSubCategory> { new ProductSubCategory { Id = 11, Name = "psc11", ProductCategoryId = 1 }, new ProductSubCategory { Id = 12, Name = "psc12", ProductCategoryId = 1 }, new ProductSubCategory { Id = 21, Name = "psc21", ProductCategoryId = 2 }, new ProductSubCategory { Id = 22, Name = "psc22", ProductCategoryId = 2 } };
public IActionResult OnGet()
{
ViewData["ProductCategoryId"] = new SelectList(productCategories, "Id", "Name");
ViewData["ProductSubCategoryId"] = new SelectList(productSubCategories, "Id", "Name");
return Page();
}
public JsonResult OnGetSubCategories(int category)
{
var subcategories = productSubCategories.Where(p => p.ProductCategoryId == category).ToList();
return new JsonResult(new SelectList(subcategories, "Id", "Name"));
}
}
cshtml:
<form method="post" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Product.Name" class="control-label"></label>
<input asp-for="Product.Name" class="form-control" />
<span asp-validation-for="Product.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Product.Description" class="control-label"></label>
<input asp-for="Product.Description" class="form-control" />
<span asp-validation-for="Product.Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Product.ImagePath" class="control-label"></label>
<input type="file" asp-for="Product.ImagePath" class="form-control" name="image" onchange="readURL(this);" />
<span asp-validation-for="Product.ImagePath" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Product.DateOfNotification" class="control-label"></label>
<input asp-for="Product.DateOfNotification" class="form-control" />
<span asp-validation-for="Product.DateOfNotification" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Product.ProductCategoryId" class="control-label"></label>
<select id="category" asp-for="Product.ProductCategoryId" class="form-control" asp-items="ViewBag.ProductCategoryId"></select>
</div>
<div class="form-group">
<label asp-for="Product.ProductSubCategoryId" class="control-label"></label>
<select id="subCategory" asp-for="Product.ProductSubCategoryId" class="form-control" asp-items="ViewBag.ProductSubCategoryId"></select>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
#section scripts{
<script type="text/javascript">
$("#category").change(function () {
var category = $("#category").val();
$.ajax({
url: "CreateProduct?handler=SubCategories",
method: "GET",
data: {category: category },
success: function (data) {
$("#subCategory option").remove();
$.each(data, function (index, itemData) {
$("#subCategory").append("<option value='" + itemData.value + "'>" + itemData.text + "</option>");
});
}
})
});
</script>
}
result:

Custom Attribute Validation: make field required based on selected option

I'm trying to make a field required, if a specific option is selected from a select.
What I have so far:
ViewModel:
public enum RequestType
{
PaidLeaveOfAbsence = 1,
WorkFromHome = 2,
SickLeave = 3,
BriefLeaveOfAbsence = 4
}
public class RequestFormViewModel
{
public RequestType SelectedRequestType { get; set; }
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
[RequieredIf("SelectedRequestType")]
public string Comment { get; set; }
}
CustomAttribute:
public class RequieredIfAttribute : ValidationAttribute, IClientModelValidator
{
private readonly string _otherProperty;
public RequieredIfAttribute(string otherProperty)
{
_otherProperty = otherProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
string comment = (string)value;
RequestType selectedRequestType = (RequestType)validationContext.ObjectType.GetProperty(_otherProperty).GetValue(validationContext.ObjectInstance, null);
if (string.IsNullOrEmpty(comment) && selectedRequestType == RequestType.BriefLeaveOfAbsence)
{
return new ValidationResult("Comment is requiered.");
}
return ValidationResult.Success;
}
public void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-required-if", "Comment is requiered.");
MergeAttribute(context.Attributes, "data-val-other", "#" + _otherProperty);
}
private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
}
HTML:
<div class="row">
<div class="col-0 col-md-2"></div>
<div class="col-12 col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="SelectedRequestType" class="control-label"></label>
<select asp-for="SelectedRequestType" asp-items="Html.GetEnumSelectList<RequestType>()" class="form-control">
<option selected="selected" value="">Select a request</option>
</select>
<span asp-validation-for="SelectedRequestType" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="FromDate" class="control-label"></label>
<input asp-for="FromDate" class="form-control" type="text" value="" id="fromDate" autocomplete="off" />
<span asp-validation-for="FromDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ToDate" class="control-label"></label>
<input asp-for="ToDate" class="form-control" type="text" value="" id="toDate" autocomplete="off" />
<span asp-validation-for="ToDate" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
<div class="col-12 col-md-4">
<div class="form-group">
<label asp-for="Comment" class="control-label">Comment</label>
<textarea asp-for="Comment" class="form-control" id="comment" rows="3"></textarea>
<span asp-validation-for="Comment" class="text-danger"></span>
</div>
</div>
<div class="col-0 col-md-2"></div>
Generated HTML:
<select class="form-control" data-val="true" id="SelectedRequestType" name="SelectedRequestType">
<option selected="selected" value="">Select a request</option>
<option value="1">PaidLeaveOfAbsence</option>
<option value="2">WorkFromHom</option>
<option value="3">SickLeave</option>
<option value="4">BriefLeaveOfAbsence</option>
</select>
...
<div class="form-group">
<label class="control-label" for="Comment">Comment</label>
<textarea class="form-control" id="comment" rows="3" data-val="true" data-val-other="#SelectedRequestType" data-val-required-if="Comment is required." name="Comment"></textarea>
<span class="text-danger field-validation-valid" data-valmsg-for="Comment" data-valmsg-replace="true"></span>
</div>
The server side validation works fine. I'm stuck on adding client side validation, so far I have this:
validator.js
jQuery.validator.addMethod("required-if",
function (value, element, param) {
var otherProp = $($(element).data('val-other'));
console.log(otherProp);
if (!value.trim() && otherProp.val() == 4) {
return false;
}
return true;
}
)
jQuery.validator.unobtrusive.adapters.add("required-if", ["other"],
function (options) {
console.log(options);
options.rules["required-if"] = "#" + options.params.other;
options.messages["required-if"] = options.message;
});
I've put some console.log()s but they are never executed. (I did preserve the log in chrome).
Most of the google searches are from the ASP.NET MVC that implement IClientValidatable interface and are not very useful. I'm using ASP.NET Core 2.2.0.
I did read the microsoft docs and the link they provided on custom adapters for unusual validators.
Questions:
How can I achieve the expected behavior this way? What am I doing wrong and how can I fix it?
What are my other options? Should I just make a separate client side validation with the jQuery Validation Plugin? I don't like the idea of 2 separate places for validation.
Can someone explain to me why the console.log()s inside the javascript functions are never executed? I have custom validators for FromDate and ToDate and they are executed there. The only difference is that I use
jQuery.validator.unobtrusive.adapters.addBool instead of jQuery.validator.unobtrusive.adapters.add.
You can have your FormViewModel extend IValidatableObject. Once you do that implement Validate method. There you can have custom validation based on values in your Model. Something like:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(SelectedRequestType == RequestType.PaidLeaveOfAbsence)
{
// Check if field is not null and return
yield return new ValidationResult(
"SomeField should be present.",
new[] { "SomeField" });
}
}
You can make above syntax more elegant with use of pattern matching
You can find more about model validation at this link
The comment section was outside the form, so the validation would never happen.
The answer was found in the link from my original post.
Important note: jQuery Validate requires your input elements to be
inside of a <form> element in order to be validated.

AJax call to the controller to pass data from JSP page

i am a beginner in spring mvc and was struck with a error i was not able to pass data to controller using ajax.please find the code and suggest the possible solutions.
<form class="form-horizontal bucket-form" id="myform" method="post" >
<div class="control-label text-center">
<p class="required"><em>required fields</em></p></div>
<div class="form-group">
<label class="col-sm-3 control-label required">First Name</label>
<div class="col-sm-6">
<input type="text" name="firstname" id="firstname" class="form-control">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label required">Last Name</label>
<div class="col-sm-6">
<input type="text" name="lastname" id="lastname" class="form-control">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label required">Gender</label>
<span style="padding-left:15px">
<label class="radio-inline">
<input type="radio" name="gender" id="gender" >
Male
</label>
<label class="radio-inline">
<input type="radio" name="gender" id="gender" >
Female
</label>
</span>
</div>
<div class="form-group">
<label class="col-sm-3 control-label required">Email id</label>
<div class="col-sm-6">
<input type="email" class="form-control" placeholder="" name="mail" id="mail" >
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label required">Profession</label>
<div class="col-sm-6">
<input type="text" class="form-control" placeholder="" name="prof" id="prof" >
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label ">Organisation</label>
<div class="col-sm-6">
<input type="text" name="org" id="org" class="form-control" placeholder="eg:Tech Mahindra">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label ">Experience</label>
<div class="col-sm-6">
<input type="number" class="form-control" min="0" name="exp" id="exp" placeholder="">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label required">Achievements</label>
<div class="col-sm-6">
<textarea class="form-control" rows="5" name="ach" id="ach" placeholder=""></textarea>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label required">Event ID</label>
<div class="col-sm-3">
<input type="number" class="form-control" name="eid" id="eid" >
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Upload image</label>
<div class="col-sm-6">
<input type="file" class="form-control" id="forImage" accept="image/*">
</div>
</div>
<div class="form-group">
<div class="col-lg-offset-3 col-lg-6">
<button class="btn btn-primary" id="speakerSubmit" type="submit">Save</button>
<button type="reset" class="btn btn" onClick="myFunction()">Reset</button>
<script type="text/javascript">
function myFunction() {
document.getElementById("myform").reset();
}
</script>
</div>
</div>
</form>
</div>
</section>
</section>
<div class="footer">
<div class="row">
<div style="float:left" class="col-md-6"><p
style="padding:15px">© 2017.All rights reserved | Powered by TechMahindra </div> </p>
<div class="social col-md-6 " style="float:right">
</div>
</div>
</div>
</section>
<script src="js/jquery2.0.3.min.js"></script>
<script
src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js">
</script>
<script src="js/bootstrap.js"></script><!--bootstrap script-->
<script src="js/jquery.dcjqaccordion.2.7.js"></script>
<script src="js/scripts.js"></script>
<script src="js/jquery.nicescroll.js"></script>
<script>
$( document ).ready(function() {
$("#speakerSubmit").click(function(){
alert("create ready");
alert("data="+window.opener);
var firstname=document.getElementById("firstname").value;
alert("Firstname " +firstname );
var lastname=document.getElementById("lastname").value;
alert("lastname " +lastname);
var mail=document.getElementById("mail").value;
alert("mail " +mail);
var prof=document.getElementById("prof").value;
alert("prof " +prof);
var org=document.getElementById("org").value;
alert("org "+org);
var exp=document.getElementById("exp").value;
alert("exp " +exp);
var ach=document.getElementById("ach").value;
alert("ach "+ach);
var eid=document.getElementById("eid").value;
alert("eid "+eid);
var gender="M";
//var ge=document.getElementsByName("optionsRadios").value; //1 n as _
var details={firstname:firstname,lastname:lastname,gender:gender,mail:mail,prof:prof,org:org,exp:exp,ach:ach,eid:eid};
alert(details.FNAME+" "+details.LNAME+" "+details.GENDER+" "+details.SPKR_MAILID+" "+details.PROFESSION+" "+details.ORGN+" "+details.EXP+" "+details.ACHIEVMNTS+" "+details.SPKR_EVNT_ID);
var res=JSON.stringify(details);
alert("mytxt "+res);
//alert(res.FNAME+" "+res.LNAME+" "+res.GENDER+" "+res.SPKR_MAILID+" "+res.PROFESSION+" "+res.ORGN+" "+res.EXP+" "+res.ACHIEVMNTS+" "+res.SPKR_EVNT_ID);
$.ajax({
url: "http://localhost:8080/EMS_APP/spkr",
type: "POST",
contentType:"application/json",
data : res,
success: function(data, status) {
if(data){
//window.open("CreateEvent.jsp","_self"); //using "_self" or "_parent" will open in same window and same tab
// window.open("CreateEvent.jsp","_self");
alert("inserted");
}else{
//window.open("index.jsp","_self");
alert("not inserted");
}
},
error: function(e) {
console.log("error");
}
});
});
});
</script>
Speaker Class
package com.ems.DO;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
#Entity
#Table(name="ems_spkr_tbl")
public class Speaker {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="SPKR_ID",nullable=false)
private int speakerId;
#Column(name="FNAME")
private String firstname;
#Column(name="LNAME")
private String lastname;
#Column(name="GENDER")
private String gender;
/*private String DOB;*/
#Column(name="SPKR_MAILID")
private String mail;
#Column(name="PROFESSION")
private String prof;
#Column(name="ORGN")
private String org;
#Column(name="EXP")
private String exp;
#Column(name="ACHIEVMNTS")
private String ach;
#Column(name="SPKR_EVNT_ID")
private int eid;
public Speaker(){
}
public Speaker(int speakerId, String firstname, String lastname, String
gender, String mail, String prof,String org, String exp, String ach, int
eid) {
super();
this.speakerId = speakerId;
this.firstname= firstname;
this.lastname = lastname;
this.gender = gender;
this.mail = mail;
this.prof = prof;
this.org = org;
this.exp = exp;
this.ach = ach;
this.eid = eid;
}
/*#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="SPKR_ID",nullable=false)*/
public int getSpeakerId() {
return speakerId;
}
public void setSpeakerId(int speakerId) {
this.speakerId = speakerId;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getMail() {
return mail;
}
public void setMail(String mail) {
this.mail = mail;
}
public String getProf() {
return prof;
}
public void setProf(String prof) {
this.prof = prof;
}
public String getOrg() {
return org;
}
public void setOrg(String org) {
this.org = org;
}
public String getExp() {
return exp;
}
public void setExp(String exp) {
this.exp = exp;
}
public String getAch() {
return ach;
}
public void setAch(String ach) {
this.ach = ach;
}
public int getEid() {
return eid;
}
public void setEid(int eid) {
this.eid = eid;
}
}
SpeakerController
Controller class where i am trying to read the values passed from jsp page using #RequestParam("att-name") annotation and used the same name in ("attribute-name") but was struck with error like 400 Status.
package com.ems.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.ems.DO.Speaker;
import com.ems.service.SpeakerService;
#Controller
public class SpeakerController {
#Autowired
SpeakerService speakerService;
#RequestMapping(value="/spkr",method=RequestMethod.POST)
public #ResponseBody boolean speakerAdd(#RequestParam("firstname") String
FNAME,#RequestParam("lastname") String LNAME,#RequestParam("gender")
String GENDER,#RequestParam("mail") String
SPKR_MAILID,#RequestParam("prof") String PROFESSION,#RequestParam("org")
String ORGN,#RequestParam("exp") String EXP,#RequestParam("ach") String
ACHIEVEMENTS,#RequestParam("eid") String eventid) {
System.out.println("in speakercontrol");
Speaker s=new Speaker();
int i=Integer.parseInt(eventid);
/*s.setfirstname(FNAME);
s.setlastname(LNAME);
s.setgender(GENDER);
s.setspkrmailid(SPKR_MAILID);
s.setprofession(PROFESSION);
s.setorgn(ORGN);
s.setexp(EXP);
s.setachievements(ACHIEVEMENTS);
s.setspkrevntid(eventid);*/
s.setFirstname(FNAME);
s.setLastname(LNAME);
s.setGender(GENDER);
s.setMail(SPKR_MAILID);
s.setProf(PROFESSION);
s.setOrg(ORGN);
s.setExp(EXP);
s.setAch(ACHIEVEMENTS);
s.setSpeakerId(i);
System.out.println(eventid);
System.out.println("Ajax");
System.out.println("----In controller----");
boolean status=speakerService.add(s);
return false;
}
}
SpeakerserviceImpl
package com.ems.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.ems.DO.Speaker;
import com.ems.dao.SpeakerDao;
#Repository
public class SpeakerServiceImpl implements SpeakerService{
#Autowired
SpeakerDao speakerDao;
public SpeakerServiceImpl() {
super();
// TODO Auto-generated constructor stub
}
public boolean add(Speaker s) {
//System.out.println(fname +" "+skill);
System.out.println("----In service----");
boolean status=speakerDao.add(s);
return status;
}}
SpeakerdaoImpl
package com.ems.dao;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.ems.DO.Speaker;
#Repository
public class SpeakerDaoImpl implements SpeakerDao{
#Autowired
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public boolean add(Speaker s) {
boolean status=false;
Session session = sessionFactory.getSessionFactory().openSession();
session.save(s);
System.out.println(s.getAch()+" "+s.getMail()+" "+s.getSpeakerId());
System.out.println("one record inserted");
return true;
}
}
Error Result
The Request sent by client is syntactically incorrect
status-400
Controller
#PostMapping("EMS_APP/spkr")
public ResponseEntity addSpeaker(#RequestBody SpeakerAddRequest request) {
Speaker speaker = new Speaker();
speaker.setFirstName(request.getFirstName());
...
}
What is happening?
#PostMapping is short for #RequestMapping(method = RequestMethod.POST).
#PostMapping("EMS_APP/spkr") is short for #RequestMapping(value = "EMS_APP/spkr", method = RequestMethod.POST).
This specifies the URL and HTTP method on which the method will be called.
#RequestBody is used to map the payload of the POST request to a POJO. In this case, the POJO is SpeakerAddRequest.
You misused #RequestParam. #RequestParam is used to map parameters in the URL. For example:
// GET request with URL ../EMS_APP/spkr?name=foo
#GetMapping("EMS_APP/spkr")
public ResponseEntity<Speaker> getSpeakerByName(#RequestParam("name") String speakerName) {
// speakerName== "foo"
}
Data transfer object (DTO)
public class SpeakerAddRequest {
#JsonProperty("firstname")
private String firstName;
...
public String getFirstName() {
return firstName;
}
}
What is happening?
The mapping of the POST payload to the POJO happens here. For example, property"firstname": "John" will get mapped to private String firstName because the property key is the same as the key specified in #JsonProperty
Form
Replace your ajax call
$.ajax({
...
});
with
$.post("EMS_APP/spkr", $("#myform").serialize());
What is happening?
Instead of manually mapping all the form elements, use this to automatically transform the form into a proper payload and send it as a POST request to the specified URL.

Categories