React + Spring #GetMapping with #RequestParam simple app - java

I am trying to make a simple web app, that will have 3 forms for data input, Submit button and Results form.
Requirements: When I enter a data in forms and push Submit button, these parameters should go to a controller and after that React should also parse data from json response that comes from the controller and show parsed data in the Results form.
I have the Spring Controller that takes 3 parameters and return json file as a response.
But I am new with React. I've tried to use different approaches, but stuck with what way exactly I need to do it in this case. So need a help to create a simple React part.
Controller part:
#GetMapping("/current/city")
public JSONObject getCurrentPollutionDataByCityStateCountry(
#RequestParam(value = "city") String city,
#RequestParam(value = "state") String state,
#RequestParam(value = "country") String country
) {
try {
return pollutionService.getCurrentPollutionDataByCityStateCountry(city, state, country);
} catch (Exception e) {
e.printStackTrace();
}
return new JSONObject();
}
Response example:
{"date":"Tue Dec 06 22:13:32 CET 2022","no2":"48.67","pm10":"9.51","pm2_5":"5.85"}
UPDATE
Here is my App.js part:
import React, {Component} from 'react'
import './App.css'
import axios from 'axios'
class App extends Component {
constructor(props) {
super(props);
this.state = {
city: 'New York',
province: 'New York',
country: 'US',
responseData: ''
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value
});
}
handleSubmit(event) {
axios.get('http://localhost:8080/pollution/current/city?' +
'city=' + this.state.city +
'&state='+ this.state.province +
'&country=' + this.state.country)
//not sure about setState here and what is going after that
.then(response => this.setState({responseData: response.data.date}))
//need to take all fields from response
//just alert message with returned response for now
alert('Response: ' + this.state.responseData);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
City:
<input name="city" type="text" value={this.state.city} onChange={this.handleInputChange}/>
</label>
<br/>
<label>
State:
<input name="state" type="text" value={this.state.province} onChange={this.handleInputChange}/>
</label>
<br/>
<label>
Country:
<input name="country" type="text" value={this.state.country} onChange={this.handleInputChange} />
</label>
<br/>
<input type="submit" value="Submit"/>
</form>
);
}
}
export default App
But I am not sure about my approach in general and particularly about parts where I left comments in App.js.
To be more precise with questions:
Should this approach work for my case or I need to implement some different logic?
If yes, how I can get all fields from response? I mean not only the first one (date) but no2, pm10, pm2_5 also. For now this logic can return only a first value from json.
How I can set these fields to show them in pop-up window (alert) or if question 2 will be solved, this thing also will be good in current form?

The only problem I think you need to solve is to set the data not data.date
use this:
this.setState({responseData: response.data}))
then access your data fields using
responseData.date
you can replace date with any other field.
do the same for alerts.
your code is working fine, furthermore, you can learn to use functional component instead of class, its much easier.

Related

Spring MVC - How do I call the same URL with different mapping annotations

I have set up a Controller with two methods. The URL mappings are exactly the same, and the only difference is the mapping annotation method. One is #PostMapping and the other #DeleteMapping written in that order. However, when I try to call the #DeleteMapping method, the #PostMapping method is called.
Note that even though variables in the two URL forms are different, they have the same values. They are just from different HTML pages.
The question is how to call the desired method each time.
#PostMapping method
#Secured({"ROLE_ADMIN", "ROLE_STUDENT"})
#PostMapping("students/{username}/internships/{id}")
public String addInternship(Model model, #PathVariable("username") String username, #PathVariable("id") int id) {
/* DOES SOME STUFF
Student student = userService.getUser(username).getStudent();
Internship internship = userService.getInternship(id);
StudentInternship studentInternship = new StudentInternship(internship, student, "Sent");
internship.setNumberOfPositions(internship.getNumberOfPositions() - 1);
userService.updateInternship(internship);
student.setApplicationNumber(student.getApplicationNumber() + 1);
userService.updateStudent(student);
userService.addStudentInternship(studentInternship);
*/
return "redirect:/internships";
}
#DeleteMapping method
#Secured({"ROLE_ADMIN", "ROLE_STUDENT"})
#DeleteMapping("students/{username}/internships/{id}")
public String removeInternship(Model model, #PathVariable("username") String username, #PathVariable("id") int id) {
/* DOES SOME STUFF
Student student = userService.getUser(username).getStudent();
Internship internship = userService.getInternship(id);
int studentInternshipID = userService.getStudentInternshipByParams(student, internship).getId();
internship.setNumberOfPositions(internship.getNumberOfPositions() + 1);
userService.updateInternship(internship);
student.setApplicationNumber(student.getApplicationNumber() - 1);
userService.updateStudent(student);
userService.removeStudentInternship(studentInternshipID);
*/
return "redirect:/students/" + username + "/internships";
}
HTML
<form:form action="${pageContext.request.contextPath}/students/${username}/internships/${tempInternship.id}" method="POST">
<input type="submit" value="Request" ${disabled}/>
</form:form>
<form:form action="${pageContext.request.contextPath}/students/${tempStudentInternship.student.username}/internships/${tempStudentInternship.internship.id}" method="DELETE">
<input type="submit" value="Dismiss" />
</form:form>
Browsers only support GET and POST as http request methods. The solution is to send your form with the POST method and inject a hidden field inside the same html form called _method with your desired method as a value, in your case here, it is just DELETE. For the case of POST, just write your form as usual.
Example :
<form:form action="${pageContext.request.contextPath}/students/${tempStudentInternship.student.username}/internships/${tempStudentInternship.internship.id}" method="POST">
<input type="hidden" name="_method" value="DELETE"/>
<input type="submit" value="Dismiss" />
</form:form>
Please, have a look at this answer for creating the spring bean and then applying the mentioned form attribute inside spring:form html forms.
Only GET and POST are allowed from a FORM. You need to use AJAX to specify additional types of the request.
Hi you need to use javascript and XMLHttpRequest in order to specify the type of the request.
Here is one example I took randomly from internet:
// Delete a user
var url = "http://localhost:8080/api/v1/users";
var xhr = new XMLHttpRequest();
xhr.open("DELETE", url+'/12', true);
xhr.onload = function () {
var users = JSON.parse(xhr.responseText);
if (xhr.readyState == 4 && xhr.status == "200") {
console.table(users);
} else {
console.error(users);
}
}
xhr.send(null);

Thymeleaf dynamically create forms using th:each

I would like to know how to create forms that uses th:object for each object looped in a th:each. For example, I have the following code.
HTML
<th:block th:each="store: ${stores}">
<form th:object="${store}" th:action="#{/modify-store}">
<input th:field="*{idStorePk}"/>
<input th:field="*{name}"/>
<input th:field="*{phoneNumber}"/>
<button type="submit">Modify</button>
</form>
</th:block>
Controller
#RequestMapping(value = "/stores")
public String getIndex(Model model) {
model.addAttribute("stores", storeService.getAllStores());
return "store";
}
So, I would like to add a form for each object, but it seems that it is not possible and I get the following error.
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'store' available as request attribute
So, I decided to add a #ModelAttribute in my controller, but can't get to return the actual store.
#ModelAttribute("store")
public Store getStore(Store store) {
return store;
}
With this approach all my forms have null values. I also tried to add a #PathVariable, but can't see to bind it using th:object. Is there a solution for this?
So for anyone stuck at a similar problem. I find out a work around that might help you out. First, you can't use th:object, it simply won't cut it. Instead, do the following.
<th:block th:each="store: ${stores}">
<form class="store-form" th:action="#{/modify-store}">
<input th:name="idStorePk" th:value="${store.idStorePk}"/>
<input th:name="name" th:value="${store.name}"/>
<input th:name="phoneNumber" th:value="${store.phoneNumber}"/>
<button class="submit-button" type="submit">Modify</button>
</form>
</th:block>
Then just add something similar to the controller.
#PostMapping(value = "/modify-store")
#ResponseBody
public boolean deleteEntry(#ModelAttribute Store store) throws Exception {
// Your code here...
return true;
}
If you want to send it asynchronously then you will need to add some JS code in order for it to work. It should look something like the code below.
const forms = document.querySelectorAll('.store-form');
forms.forEach(form => {
form.addEventListener('submit', event => {
// Stop the normal form submit triggered by the submit button
event.preventDefault();
const formInputs = form.getElementsByTagName("input");
let formData = new FormData();
for (let input of formInputs) {
formData.append(input.name, input.value);
}
fetch(form.action,
{
method: form.method,
body: formData
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.log(error.message))
.finally(() => console.log("Done"));
});
You're sending stores in your controller in model-attribute and on your second controller where you're submitting your form you're using store that's the reason you're getting this error. So correct the spelling error on any one of your controller. Like this :-
#RequestMapping(value = "/stores")
public String getIndex(Model model) {
model.addAttribute("stores", storeService.getAllStores());
return "store";
}
And Your second controller where you're submitting your form will be like this -
#ModelAttribute("stores")
public Store getStore(Store store) {
return store;
}

Pass value from Angular 5 to Java REST API using http put, line 0:-1 no viable alternative at input '<EOF>

I'm facing an issue to try learn the full cycle of web following of data, i passed value from java jersay REST-API to angular 5, but i wanna pass text from angular to backend to execute some query and return the result again, i got confused between #FormParam and many things,
bellow my code,
File-list.service.ts
import { Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '#angular/common/http';
import { FileList } from './file-list';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map'
save(fileList: FileList): Observable<Response> {
console.log('Saving person ' + JSON.stringify(fileList));
return this.http.put('http://localhost:8080/SWBackend/jaxrs/Person/Save', JSON.stringify(fileList), { headers: this.getHeaders()});}
List.component.html
<form (click)="save()" method="POST" action="http://localhost:8080/SWBackend/jaxrs/Person/Save">
<p>
Path : <input type="text" name="pathUrl" />
</p>
Press to show the files
<input type="submit" value="Add User" />
<input type="hidden" name="_method" value="PUT">
</form>
Java file.java
#PUT
#Path("Save")
#Consumes({ "application/xml", "application/json" })
#Produces("application/json")
public Response saveEmp(FilesList pathUrl) {
System.out.println("Reach Save Emp");
return Response.ok().header("Access-Control-Allow-Origin", "*").build();
}
i'm getting this error:
line 0:-1 no viable alternative at input ''
i don't know if my way is right or no, but i was trying since week ago,
thank you all.
Try this instead,
in your form remove this:
<form (click)="save()" method="POST" action="http://localhost:8080/SWBackend/jaxrs/Person/Save">
Use ReactiveFormsModule for your forms, using FormGroup and FormControl.
Then in your component, when you submit the form, it will invoke the function to call your service which in return will communicate with your java controller.
The Component should have the function that is associated with your formGroup in your template(html).
The function should then call your service such as: this.service.save(fileList);
https://angular.io/guide/reactive-forms
ex:
<form [formGroup]="fileListForm" (ngSubmit)="save(fileListForm.value)">
<input type="text" formControlName="fileListNameControl">
</form>
Component:
private fileListForm: FormGroup;
private fileListNameControl: FormControl;
ngOninit(){
this.fileListForm = new FormGroup({
fileListNameControl: new FormControl("", Validators.required)
});
}
save(form: any){
// get your form values here and then call service
this.fileLIstService.save('your value');
}
** Make sure to import ReactiveFormsModule via #angular/forms in your module.

Can we serialize and send a form with a list of objects inside the main object - Ajax, Spring MVC

I am sorry that I cannot share my code. I will try to give an appropriate example for my situation.
Class Person:
public class Community {
private int personCount;
private List<Person> persons;
//getters and setters for all
}
Call Person:
public class Person {
private int name;
private int age;
//getters and setters
}
Now, I want to serialize and send a form with an AJAX call from my JSP.
In JSP, I used
<div th:each="parameter,iterStat : ${persons}" class="personsContainer">
<div th:text="'Name: ' + ${parameter.name}"></div>
...
to traverse through the list.
This is the AJAX call
function updateCommunityPeople(initial) {
var getValuesUrl = /*[[#{/getPeoplesJson}]]*/ "";
var data = $(".personsContainer").parents('form:first').serialize();
data += "&initial=" + initial;
alert("date: "+data);
$.ajax({
'type': "POST",
'url': getValuesUrl,
'dataType': "json",
'data': data,
})
.done(function (data, textStatus, jqXHR) {
alert("Updated successfully");
});
}
My controller method:
#RequestMapping(value = "/getPeoplesJson", method = RequestMethod.POST)
public #ResponseBody String getCommunityPeopleValuesJson(HttpServletRequest request, HttpSession session,
Locale locale, Model model, Device device, Principal principal, #Valid Community post,
BindingResult result)
{
int count = post.getPersonCount();
if (post != null && post.getPersons() != null)
{
//calls to service and so on...
return "true";
}
return false;
}
Here, I am able to retrieve the person count correctly but the whole issue is with the list of Persons. It is null always...
Your list of persons is always null because the jQuery's serialize() function takes into account only form elements. From the official documentation:
Description: Encode a set of form elements as a string for submission.
The .serialize() method creates a text string in standard URL-encoded notation. It can act on a jQuery object that has selected individual form controls, such as <input>, <textarea>, and <select>: $( "input, textarea, select" ).serialize();
It is typically easier, however, to select the <form> itself for serialization
For example, if you had a <select multiple> element and listed the names as options, the serialize function would correctly serialize all selected <option> elements:
<form>
<select multiple th:each="parameter,iterStat : ${persons}" class="personsContainer">
<option selected th:text="'Name: ' + ${parameter.name}"></option>
</select>
</form>
Here is a working example.
Edit
To automatically fill in the persons list from the form parameters or the query string it will need to be in the following format:
personCount=2&persons[0].name=joe&persons[0].age=20&persons[1].name=john&persons[1].age=25`
This means your form should look something like this:
<form>
<div th:each="parameter,iterStat : ${persons}" class="personsContainer">
<input type="text" th:value="${parameter.name}" th:name="'people[' + ${iterStat.index} + '].name'">
<input type="text" th:value="${parameter.age}" th:name="'age[' + ${iterStat.index} + '].age'">
</div>
</form>
Also, the ajax's dataType should be set to application/x-www-form-urlencoded if you're going to use serialize().

Play 2 - templates without helpers

#inputText(
signupForm("email"), '_label -> "Email",
'_help -> "Enter a valid email address."
)
How would I write this in pure html?
I have no idea how i add the value to the signupForm, so that I can use it in my controller with bindfromRequest() (in html)
Edit:
I normally used this approach
final static Form<User> signupForm = form(User.class);
and the the binding process
Form<User> filledForm = signupForm.bindFromRequest();
and my rendered form looks like this:
<div class="control-group ">
<label class="control-label" for="email">Email</label>
<div class="controls">
<input type="text" id="email" name="email" value="" >
<span class="help-inline"></span>
</div>
</div>
And this worked for me I was just curious how to use pure html, so I could create my own little helpers.
Edit2:
public static Result blank() {
return ok(form.render(signupForm));
}
and in the template itself
#(signupForm: Form[User])
Edit 3:
I don't know if this helps but the helper looks like this. (for the inputtext)
I just have no idea what this means, scala looks really cryptic to me.
#(field: play.api.data.Field, args: (Symbol,Any)*)(implicit handler: FieldConstructor, lang: play.api.i18n.Lang)
#input(field, args:_*) { (id, name, value, htmlArgs) =>
<input type="text" id="#id" name="#name" value="#value" #toHtmlArgs(htmlArgs)>
}
Use your browser to check the source coderendered by Play and copy/paste the HTML into your template.
In general the most interesting for you is proper inserting value to the manually created field:
<input type="text" name="email" value='#signupForm.field("email").value' />
It's also important to set the proper name attribut otherwise filling Form in controller will fail.
Edit:
Of course in blank action your signupForm is empty so that's normal that there's no value, in next action let's name it saveBlank, you need to fill the form with request's data, validate and save. If there are errors in form you need to render form.scala.view again with data binded to the form from previous request:
public static Result saveBlank(){
signupForm = signupForm.bindFromRequest();
if (signupForm.hasErrors()){
return badRequest(form.render(signupForm));
}
User user = new User();
user = signupForm.get();
user.save();
return ok("user saved);
}
of course if you'll want to edit existing user, you have to prefill it with data from DB ie:
public static Result edit(Long id){
signupform = signupForm.fill(User.find.byId(id));
return ok(form.render(signupForm));
}
(note: I wrote it just from top of my head, so check pls if there are no typos or errors)
Finally you don't need to use Form object in every case, you can also use DynamicForm:
public static Result saySomething(){
DynamicForm df = form().bindFromRequest();
return("you entered :" + df.get("email"));
}
or even in one line:
public static Result sayShorter(){
return("you entered :" + form().bindFromRequest().get("email"));
}

Categories