I'm trying to add a search form in the navbar on every page in my spring mvc web app, just like the one here on stackoverflow, and I'm having issues. Right now I have a working search functions on a couple of my pages, using the typical mvc forms. I take the inputted string and store a variable called "searchString" in an object I created called "searchForm.java". Then I try to query that inputted string in the database using spring data's findbycontaing method, and then put that result on the model, and then represent that on the view, using thymeleaf. However I think that the navbar should be done using ajax, since it's on every page and pages with other forms.
So I think I'm sending the string that was submitted to the search form in the navbar to the controller where I queried it in the repository to bring back search results, then I tried to put the search results on the model, but I get nothing, all it does is redirect me to the search page. I may not be making very much sense, but I'll show my code, and if anyone could let me know if I'm going about my problem in the right way or not, and if you guys see any errors in my code. Thanks in advance.
So here's my ajax and jquery to submit the form.
<script th:inline="javascript">
/*<![CDATA[*/
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(header, token);
});
$(document).ready(function(){
$("#searchButton").on("click", function(ev) {
$.ajax({
url : "navSearch",
type : "post",
data : {
"newSearch" : $("#newSearch").val()
},
success : function(data) {
console.log(data);
},
error : function() {
console.log("There was an error");
}
});
});
});
/*]]>*/
</script>
There may be an issue here, because in the console in the chrome developer tools, before it redirects, a message pops up very quickly that says uncaught TypeError: Cannot read property 'toLowerCase' of undefined, and it's coming from jquery.min.js:5 so that could be my issue, but I have no idea how to go about fixing this, and I've searched for answers so far with no luck.
Here's my html form, I think this shouldn't be a problem, but who knows, so I'll put it up anyways. And I'm using thymeleaf for this view.
<form action = "setSearch" class="navbar-form navbar-right">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search" id="newSearch"></input>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
</div>
<button type="submit" class="btn btn-default" id="searchButton">Search</button>
</form>
Here's my searchForm.java class, where I temporarily store the string to be queried in the database.
public class SearchForm {
private String searchString;
public String getSearchString()
{
return searchString;
}
public void setSearchString(String searchString)
{
this.searchString = searchString;
}
}
And Here's my controller, where I'm trying to handle the ajax submission and return it as search results on the setSearch.html page. What I'm thinking here is that the string "newSearch" from the form could be matched using the Spring Data query methods, and then be able to return it and add it to the model, but it's not working, it's just redirecting me to the /searchSet page with no data, because that's where the form action goes and that's what I tell it to return. So honestly I'm no sure if any data is even getting to this point.
#RequestMapping(value="setSearch/navSearch", method=RequestMethod.POST)
public #ResponseBody String navSearch (#RequestParam String newSearch, ModelMap model)
{
List<QuestionAnswerSet> questionAnswerSetByQuestion = (List<QuestionAnswerSet>) questionAnswerSetRepo.findByQuestionContaining(newSearch);
model.put("searchResult", questionAnswerSetByQuestion);
return "setSearch";
}
And here's an example of a working search method that I have in my controller that I use on a regular form, with no ajax, on the /searchSet page.
#RequestMapping(value="/setSearch", method=RequestMethod.GET)
public String searchGet(ModelMap model) {
SearchForm searchForm = new SearchForm();
model.put("searchForm", searchForm);
return "setSearch";
}
#RequestMapping(value="/setSearch", method=RequestMethod.POST)
public String searchPost(#ModelAttribute SearchForm searchForm, ModelMap model) {
List<QuestionAnswerSet> questionAnswerSetByQuestion = (List<QuestionAnswerSet>) questionAnswerSetRepo.findByQuestionContaining(searchForm.getSearchString());
model.put("searchResult", questionAnswerSetByQuestion);
return "setSearch";
}
UPDATE
I've changed my code in the form from <button type="submit" class="btn btn-default" id="searchButton">Search</button> to <button type="button" class="btn btn-default" id="searchButton">Search</button> and now I get the Uncaught TypeError: Cannot read property 'toLowerCase' of undefined from earlier and nothing happens with the page.
UPDATE
I can now submit the ajax form without a problem, I was missing meta tags in the header, so the csrf wasn't submitting correctly, so now I get this error in the chrome developer tools console XHR Loaded (navSearch - 405 Method Not Allowed - 7.265999971423298ms - 634B)
UPDATE
Now everything works on the Ajax side, I needed to adjust my url to match the url I had in the request mapping on the controller and it runs through all the code fine. However the overall search function still doesn't work, here's my updated controller.
I know my issue here is that I'm returning a string and not an object, but I'm not sure how to return the object and then redirect the url to the /setSearch page. It's running through the code and returning a string "setSearch" in the console, because I told it to at the end of the controller.
#RequestMapping(value="/setSearch/search", method=RequestMethod.POST)
public #ResponseBody String search (#RequestParam String newSearch, ModelMap model)
{
List<QuestionAnswerSet> questionAnswerSetByQuestion = (List<QuestionAnswerSet>) questionAnswerSetRepo.findByQuestionContaining(newSearch);
model.put("searchResult", questionAnswerSetByQuestion);
return "setSearch";
}
Here's my working ajax
<script th:inline="javascript">
/*<![CDATA[*/
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(header, token);
});
$(document).ready(function(){
$("#searchButton").on("click", function(ev) {
$.ajax({
url : "/setSearch/search",
type : "post",
data : {
"newSearch" : $("#newSearch").val()
},
success : function(data) {
console.log(data);
},
error : function() {
console.log("There was an error");
}
});
});
});
/*]]>*/
</script>
but it's not working, it's just redirecting me to the /searchSet page
with no data, because that's where the form action goes and that's
what I tell it to return
You are right, it is because you are submitting the form and in the action you specify it to submit to setSearch, that is why the page is getting redirected to the same page. Just replace button type="submit" with button type="button" so that the form will not be submitted when searchButton is clicked.
Related
I want to implement a send mail form using Thymeleaf.
I have a page called start_page.html that contains this form :
<div class="w3-container w3-padding-64" id="contact">
<h1>Contact</h1><br>
<p>We offer full-service catering for any event, large or small. We understand your needs and we will cater the food to satisfy the biggerst criteria of them all, both look and taste. Do not hesitate to contact us.</p>
<p class="w3-text-blue-grey w3-large"><b>Catering Service, 42nd Living St, 43043 New York, NY</b></p>
<p>You can also contact us by phone 00553123-2323 or email catering#catering.com, or you can send us a message here:</p>
<form th:action="#{~/homePage/contact}" th:object="${contactMail}" method="post" target="_blank">
<p><input class="w3-input w3-padding-16" type="text" th:field="*{nom}" th:placeholder="#{homePage.nom}" required name="nom"></p>
<p><input class="w3-input w3-padding-16" type="text" th:field="*{prenom}" th:placeholder="#{homePage.prenom}" required name="prenom"></p>
<p><textarea class="w3-input w3-padding-16" type="text" th:field="*{message}" style="height: 250px;" th:placeholder="#{homePage.message}" required name="message"></textarea>
<p><button class="w3-button w3-light-grey w3-section" type="submit">[[#{homePage.envoyer}]]</button></p>
</form>
</div>
I have already implemented a controller for this form action
#Controller
#PropertySource(ignoreResourceNotFound = true , value = "classpath:messages.properties")
public class HomePageController {
#Autowired
private MailContactService mailService;
#RequestMapping(value = "/homePage/contact", method = RequestMethod.POST)
public String sendMessage(ContactMail contactMail){
mailService.sendContactMail(contactMail);
System.out.println("done");
return "/home/start_page";
}
}
I'm not getting the desired behavior: I though that my page will stay the same but my page is reloading.
I want to order the controller to do something without getting out of my page.
I googled and I found that I can send a service object to my page but I want to avoid this option if there is other solutions .
Thank you.
You'll need to use an AJAX call if you don't want to refresh your page.
What this means is that you want to intercept the default HTTP form post behavior (that will do a full page refresh) using javascript.
For this you need to :
Remove the action tag on your form (let javascript handle it when clicking the button to submit the form)
Add this to your page (will be executed when the form is submitted :
$(document).ready(function () {
$("#contact-form").submit(function (event) {
// do not post the form and trigger full page refresh
event.preventDefault();
var formData = .. // construct some formData
$.ajax({
type: "POST",
contentType: "application/json",
url: "/homePage/contact",
data: JSON.stringify(formData),
dataType: "json",
success: function (data) {
console.log("SUCCESS : ", data);
},
error: function (e) {
console.log("ERROR : ", e);
}
});
});
});
For a full example, as always, mkyong.com has got you covered :)
Ok basically I have a column, and when you click the header a couple of input tags appear dynamically, to change column name and submit it. I want to change it and show it right away using ajax. The code to do this is below, and sorry but the code is in coffee script. Also I am using ASPNET5 RC1 with MVC6.
SumbitColumnForm = () ->
$('.panel-heading').on 'click', 'input.ColumnTitleSumbit', (event) ->
event.preventDefault();
columnName = $('.NewColumnName').val().trim()
columnNumber = $(this).parent().parent().attr 'id'
newColumnData = "{
'ColumnName': 'columnName'
'ColumnNumber': 'columnNumber'
}"
$.ajax
url: '/Board/ChangeColumnName',
type: 'POST'
dataType: 'json',
data: newColumnData,
success: (data) ->
#res = $.parseJSON(data);
alert "Hit the Success part";
error: (xhr, err) ->
alert("readyState: " + xhr.readyState + "\nstatus: " + xhr.status);
alert("responseText: " + xhr.responseText);
The controller actions are
[Route("{p_BoardName}")]
public IActionResult Show(string p_BoardName)
{
m_Board.BoardName = p_BoardName;
ViewData["BoardName"] = m_Board.BoardName;
return View(m_Board);
}
[HttpPost]
public IActionResult ChangeColumnName(ColumnModel newColumnData)
{
string name = newColumnData.ColumnName;
m_Board.ColumnList[newColumnData.ColumnNumber].ColumnName = name;
return View("Show", m_Board);
}
Also the modelS if needed
public class ColumnModel
{
private string m_name;
private int m_columnNumber;
public int ColumnNumber { get { return m_columnNumber; } }
public string ColumnName { get { return m_name;} set { m_name = value; } }
public ColumnModel(string p_Name, int p_ColumnNumber)
{
m_name = p_Name;
m_columnNumber = p_ColumnNumber;
}
}
public class BoardModel
{
private string m_BoardName;
private List<ColumnModel> m_ColumnList;
[Required]
public string BoardName { get { return m_BoardName; } set { m_BoardName = value; } }
public List<ColumnModel> ColumnList
{
get { return m_ColumnList; }
set { m_ColumnList = value; }
}
}
So I have debugged both the Javascript and the controller action ChangeColumnName, and it is not hitting it. The javascript also does not hit the success part of the ajax call also. I have tried everything that comes to mind and done a lot of googling but it just won't work. I feel like it is because of the way I am sending the data for the $.ajax call but really I just don't know why this doesn't work.
Also if i want to update the column name, by removing the input and adding the normal HTML to display the name, am I right to return a view in the action method ChangeColumnName? I mean will it reload the page or add it dynamically. Will I have to add JS code in the success attribute of the $.ajax call to change it dynamically?
Any help will be greatly appreciated. Thank you.
Edit: Added the board model
Edit2: Updating showing the html code as a request of Stephen.
Using bootstrap column/grid design, and also bootstrap panels. I basically have column shown by html
<div id="1" class="panel panel-default BoardColumn">
<div class="panel-heading">
<h3 class="panel-title">Something1</h3>
</div>
</div>
I want to change the header class 'panel-title' and update it dynamically without reloading the page. I have set up another ajax call, so when the header is clicked a few input tags are added and and the html is changed to the following. In minimal explaining I have done this using jquery append and remove functions.
<div id="1" class="panel panel-default BoardColumn">
<div class="panel-heading">
<input class="PreviousColumnName" type="hidden" style="display: none;" value="Something1">
<input class="NewColumnName" name="ColumnName">
<input class="ColumnTitleSumbit" type="submit" value="Continue">
</div>
</div>
Now I want to get the value of the input with class name 'NewcolumnName', update the current column name in the model and place its new name. When the submit button is clicked, I want to remove the input tags and go back to the original html before that, showings it new column name without reloading.
So I'm trying to create a search form in the navbar of my site, and I'm using ajax to submit the form. I have other working search forms in my web app so I know how to do that. And I have the ajax submitting properly. I just don't know how to get the data from the form and use it in the controller to get the result that I want.
The way I'm doing the search function is by creating a searchForm.java object that has a string variable called searchString and I populate that and then query it against the database in the controller using spring data methods in my repository class.
So here's what my jquery ajax form looks like, and in the console on the chrome developer tools it returns "setSearch" like I tell it to in the controller, and I know that's an issue, I just don't really know how to fix it.
<script th:inline="javascript">
/*<![CDATA[*/
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(header, token);
});
$(document).ready(function(){
$("#searchButton").on("click", function(ev) {
$.ajax({
url : "/setSearch/search",
type : "post",
data : {
"newSearch" : $("#newSearch").val()
},
success : function(data) {
console.log(data);
},
error : function() {
console.log("There was an error");
}
});
});
});
/*]]>*/
</script>
Here's my thymeleaf html page
<form action = "setSearch" class="navbar-form navbar-right">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search" id="newSearch"></input>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
</div>
<button type="button" class="btn btn-default" id="searchButton">Search</button>
</form>
This is my searchForm.java object
public class SearchForm {
private String searchString;
public String getSearchString()
{
return searchString;
}
public void setSearchString(String searchString)
{
this.searchString = searchString;
}
}
Here's my controller, and I know this won't work, because I'm returning a string and not a json object(I think that's right). But I tried to change it and I get a lot of errors, and I'm not sure how I should go about this.
#RequestMapping(value="/setSearch/search", method=RequestMethod.POST)
public #ResponseBody String search (#RequestParam String newSearch, ModelMap model)
{
List<QuestionAnswerSet> questionAnswerSetByQuestion = (List<QuestionAnswerSet>) questionAnswerSetRepo.findByQuestionContaining(newSearch);
model.put("searchResult", questionAnswerSetByQuestion);
return "setSearch";
}
Here's a working example of a non ajax search function in my controller, so you guys can see what I'm trying to do.
#RequestMapping(value="/setSearch", method=RequestMethod.GET)
public String searchGet(ModelMap model) {
SearchForm searchForm = new SearchForm();
model.put("searchForm", searchForm);
return "setSearch";
}
#RequestMapping(value="/setSearch", method=RequestMethod.POST)
public String searchPost(#ModelAttribute SearchForm searchForm, ModelMap model) {
List<QuestionAnswerSet> questionAnswerSetByQuestion = (List<QuestionAnswerSet>) questionAnswerSetRepo.findByQuestionContaining(searchForm.getSearchString());
model.put("searchResult", questionAnswerSetByQuestion);
return "setSearch";
}
Let me know if I left anything out or if you would need to see any more code to see my issue. Thanks in advance.
If you are only submitting one parameter and it will be restfull there is no need for form or POST
Here is a simple example of how I would do a search that returns a array of objects from database. I hope you can use it to implement what you need.
HTML
<form>
<label for="search_input">Search:</label>
<input type="text" id="search_input">
</form>
Javascript
<script>
$.get("/search", {term: $('#search_input').val()}, function(data) {
// do your data manipulation and transformation here
});
</script>
Controller
RequestMapping("/search")
public #ResponseBody List searchPost(#RequestParameter("term") String query) {
List<Object> retVal = getListOfObjectFromDbBasedOnQuery(query);
return retVal;
}
Lot simpler (from a logical perspective), remember in RESTfull terms post is used to create objects. Retrieving data use GET only.
I have a loaded jsp page with products and addtocart link against it. I am trying to show the cart in a div in the same page. I want to send html as response. This is what i have done. It just returns the string <div>output</div>. Can someone show me how i should do it.
Controller
#RequestMapping(value="/addtocart{id}", produces = "text/plain;charset=UTF-8")
#ResponseBody
public String addToCart(#PathVariable("id") int id, #ModelAttribute("cart") Cart cart,Model model)
{
Product product = productService.getProductById(id);
if (product != null) {
CartLine line = new CartLine();
line.setProduct(product);
line.setQuantity(1);
productService.updateProduct(product);
}
return "<div>output</div>";
}
JSP
<td><a id="demo4" href="addtocart${product.id}">Add To Cart</a> </td>
$('#demo4').click(function() {
$.ajax({
url : '/addtocart{id}',
dataType: 'json',
contentType: "text/html",
type : 'GET',
data :{id:id},
success : function(response) {
$('#output').html(response);
}
});
});
<div id="output" style="display:none">
<h2>Cart Content(s):</h2>
</div>
I also favor the approach with using the view, and separate page even on ajax call. Nevertheless what you're asking is possible, simply change your produces = "text/plain;charset=UTF-8" to produces
produces = "text/html;charset=UTF-8"
There are many other aspects that appear wrong, not related to Spring MVC, so even with the produces corrected you still have to do few corrections to get what you're expecting.
I think that you're not sending an ajax call at all. You're most likely doing a complete browser redirect. When first read, I was confused that "text/plain" vs "text/html" makes a difference in an ajax response, but now I believe that you're actually redirecting via browser. Change this <a id="demo4" href="addtocart${product.id}">Add To Cart</a> into something like this <a id="demo4" href="#">Add To Cart</a> and add return false to the end of your function. This will execute the function, and the return will make sure that the link is not followed
When you do this you'll notice a few issues with your ajax call as well; firstly, url : '/addtocart{id}' should be url : '/addtocart${product.id}
Capture your response in complete function not in success, and get the output as response.responseText, the response will return fine, but the browser will attempt to parse it as json and fail.
Your div will remain invisible, you should add some js to toggle that
One Spring MVC gotcha, your Cart bean seems to have a property called id as well. Because of this, your id path variable should be renamed, otherwise it'll be ignored
This would be something that, if not completly, than much closer to working
<a id="demo4" href="#">Add To Cart</a>
<div id="output"></div>
<script>
$('#demo4').click(function() {
$.ajax({
url : '/addtocart${product.id}',
dataType: 'json',
contentType: "text/html",
type : 'GET',
data :{id:4},
complete: function(response) {
$('#output').html(response.responseText);
}
});
return false;
});
</script>
Renaming PathVariable
#RequestMapping(value="/addtocart{productId}", produces = "text/plain;charset=UTF-8")
public String addToCart(#PathVariable("productId") int productId, #ModelAttribute("cart") Cart cart,Model model)
You can do it with AJAX and still return a separate page for your Card, as Slava suggested.
JSP page cart.jsp:
<%# page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!-- custom html for your cart representation, just an example -->
<div>
<h1>${cart.headline}</h1>
<p>${cart.otherProperty}</p>
</div>
Controller:
#RequestMapping(value="/addtocart{id}")
public String addToCart(#PathVariable("id") int id, #ModelAttribute("cart") Cart cart, Model model) {
doSomethingWithCart(cart);
model.addAttribute("cart", cart); // add cart to model after doing some custom operations
return "cart"; // resolved to cart.jsp by your view resolver
}
This way you are using AJAX but still you are returning dynamic html content (
adjusted to one specific cart).
I've got a page that contains multiple links. These links should do an ajax post and callback. However, the link is doing a Get instead of a Post. This causes a 404 error since I do not have an action method to handle a get at the requested URL.
If I remove the HTTPPost attribute from my Action method, the link works, but the call back fails and the Json I return is rendered in a new page.
Here is the code I'm using in my view.
<td id="action-#item.ItemID">#Ajax.ActionLink("Add", "AddToOrder", new { itemID = item.ItemID }, new AjaxOptions { HttpMethod = "POST", OnSuccess = "actionCompleted" }, new { id = "add-" + item.ItemID })</td>
This ends up adding this HTML:
<td id="action-012679"><a data-ajax="true" data-ajax-method="POST" data-ajax-success="actionCompleted" href="/mysite/neworder/AddToOrder?itemID=012679" id="add-012679">Add to Order</a></td>
My Controller has the following Action Method.
[HttpPost]
public JsonResult AddToOrder(string itemID) {
return Json(new { id = itemID, Action = "Added", "Just getting this working"});
}
My callback method that is called on Success looks like this:
<script language="javascript" type="text/javascript">
function actionCompleted(response, status, data) {
alert("We have returned");
}
</script>
If I change the [HTTPPost] attribute on my action method to [HTTPGet] I get an Json error. I can fix this by adding the JsonRequestBehavior.AllowGet to my return value, but this doesn't use the call back function defined on the page and fails.
Any help would be appreciated.
Probably you don't have jquery.unobtrusive-ajax.js script attached to page and link is gracefully degraded to regular anchor.
In order to have this working properly. You need to add references to these scripts on your page:
MicrosoftAjax.js
MicrosoftMvcAjax.js