Spring & Thymeleaf: Refresh single row in table - spring

I refresh content of a table with:
<tr id="rows" th:each="entity: ${entities}">
<!-- ... -->
</tr>
and on server side:
#RequestMapping(value = "/replace", method = RequestMethod.GET)
public String replace(Model model) {
model.addAttribute("entities", entities);
return "/ :: #rows";
}
calling the method with:
$.get('/replace', function(fragment) {
// fragment gives me all rows of the table
}
which works as expected. However, I do not want to update all rows every time, but only a single one, so my idea was to use a dynamic id as follows:
<tr th:id="'row' + ${entity.id}" th:each="entity, iterStat: ${entities}">
<!-- ... -->
</tr>
and on server side:
#RequestMapping(value = "/replace", method = RequestMethod.GET)
public String replace(#RequestParam("id") int id, Model model) {
model.addAttribute("entities", entities);
return "/ :: #row" + id;
}
calling the method with:
$.get('/replace', {id: id} function(fragment) {
// fragment is empty
}
but that doesn't work. Looking a the html code and server side, the id is correct. Is it not possible to use a th:id? What would be a workaround if I only want to update one row in a table and not all rows? Thanks.
I also tried the same with th:fragment - it works without ${entity.id} and does not with.

Using th:fragment should do the trick. I normally use them to accomplish what you are trying to do. This is an example of how to implement it.
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/xhtml">
<head></head>
<body>
<th:block th:fragment="entity-row">
<tr th:id="'row'+${entity.id}">
<td th:text=${entity.id}></td>
<!-- Whatever other element you need. -->
</tr>
</th:block>
</body>
Add the code above in a whole a new html file. Remember, we are expecting one single entity, so we don't need the th:each. Now in order to fetch the fragment, you would need to the following in your Controller.
#RequestMapping(value = "/replace/fragment", method = RequestMethod.GET)
#ResponseBody
public String replaceSingleRow(#RequestParam("id") int id, Model model) {
Entity entity = entityService.findById(id);
model.addAttribute("entity", entity);
return "file :: entity-row";
}
Where file would be the name of the html file that contains the fragment. Of course, I am assuming you are passing only the desired id to our controller.

Very late response, but I think this is the ultimate solution.
Thymeleaf fragment
<tr th:fragment="entity-row" th:id="'row' + ${entity.id}" th:each="entity, iterStat: ${entities}">
<!-- ... -->
</tr>
Controller code
#RequestMapping(value = "/replace", method = RequestMethod.GET)
public String replace(#RequestParam("id") int id, Model model) {
Entity entity = entityService.findById(id);
model.addAttribute("entities", Arrays.asList(entity)); // sending the row as list, so that the loop can iterate and will produce a single row
return "file :: entity-row";
}
You have the desired single row html in your ajax response, now you can replace the existing row
$.get('/replace', {id: id} function(fragment) {
$("#row" + id).replaceWith(fragment);
}

I was able to send only one entity to the view with:
Entity entity = entityService.findById(id);
model.addAttribute("entities", entity);
It is for sure not the prettiest way, but it gives the desired result.

Related

How can I get data from from table2 by sending corresponding ID from table1 using thymleaf

getFunction() in database class:
public Review getReviews(Long id) {
MapSqlParameterSource namedParameters = new MapSqlParameterSource();
String query = "SELECT * FROM reviews WHERE id = :id";
namedParameters.addValue("id", id);
BeanPropertyRowMapper<Review> reviewMapper = new BeanPropertyRowMapper<Review>(Review.class);
List<Review> reviews = jdbc.query(query, namedParameters, reviewMapper);
if (reviews.isEmpty()) {
return null;
} else {
return reviews.get(0);
}
}
Controller class:
#GetMapping("/viewReview")
public String viewReviews(Model model,Long id) {
List<Review> review = da.getReviews(id);
model.addAttribute("reviewList", review);
return "view-book";
}
HTML:
<tr th:each="book : ${bookList}">
<td th:text="${book.title}"></td>
<td th:text="${book.author}"></td>
<td> View</td>
</tr>
I have two tables 'books' and 'reviews'. When I click the first book it should display the corresponding review of the book on the next page. But instead shows 404 error. Im guessing Im missing something in my database class but not sure what.
I'm not very familiar with Thymeleaf, but looking at your Controller (and the error message), the problem could be with your Controller than Database class.
Your viewReviews method is mapped for /viewReview, but you are trying to navigate to /viewReview/BOOK_ID, which is why you are getting the 404 error message.
Try updating your Controller as:
#GetMapping("/viewReview/{id}")
public String viewReviews(Model model, #PathVariable("id") Long id) {
List<Review> review = da.getReviews(id);
model.addAttribute("reviewList", review);
return "view-book";
}

RequestMapping isn't reaching in the controller and thus list isn't showing on the JSP page

I'm developing a simple application of tender management in which I'm using J2EE, Spring-mvc and CRUD operations on Eclipse IDE
I've to retrieve the list of tenders from the database and show in the JSP using controller. The JSP page is mapped in the #RequestMapping but the mapping isn't executing and hence the code following it isn't reaching.
I've tried non- crud operations before and have used different methods including ModelAndView and String but the issue is with mapping which isn't able to run when loading the required JSP. No error is being shown.
Here is the mapping call in the Controller
#RequestMapping("/viewTendersByUser")
public String viewTender(Model m) {
System.out.println("in edit tender user controller");
List<tender> tender = userDao.getTenderByCreator(name);
m.addAttribute("tender", tender);
System.out.println("in ctender after view tender");
System.out.println(""+tender);
return "viewTendersByUser";
}
This is the method in DAO which I'm calling but isn't actually reached
#Override
public List<tender> getTenderByCreator(String name) {
String sql = "select * from tenderdb where publisher =
'"+name+"'";
return jdbcTemplate.query(sql,new RowMapper<tender>(){
public tender mapRow(ResultSet rs, int row) throws SQLException {
tender t = new tender();
t.settName(rs.getString(1));
t.setOpening(rs.getTimestamp(2));
t.setClosing(rs.getTimestamp(3));
t.setMinBid(rs.getDouble(4));
t.settDesc(rs.getString(5));
return t;
}
});
}
And this is the JSP I'm trying to show data in:
<h3>List of Tenders</h3>
<table border="2" width="70%" cellpadding="2">
<tr><th>Tender Name</th><th>Opening Date</th><th>Closing Date</th><th>Minimum Bid</th><th>Tender Description</th><th>Edit</th><th>Delete</th></tr>
<c:forEach var="row" items="${tender}">
<tr>
<td>${row.tName}</td>
<td>${row.opening}</td>
<td>${row.closing}</td>
<td>${row.minBid}</td>
<td>${row.tDesc}</td>
<td>Edit</td>
<td>Delete</td>
</tr>
</c:forEach>
</table>
Expected results should be the list of tenders from the database onto the viewTendersByUser.jsp.
Actual result is the JSP page being loaded but the contents to be received from controller aren't showing which traces to mapping not being called and actually nothing is happening.
Even an error or exception would be easy to debug but there's no output.

How to pass a more dynamic structure to jsp Spring Forms?

I have a structure:
public class Tabl {
String tableName;
List<String> tableColumns;
public Tabl() {
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public List<String> getTableColumns() {
return tableColumns;
}
public void setTableColumns(List<String> tableColumns) {
this.tableColumns = tableColumns;
}
}
This form is emulating a table dynamically so to say. I have lots of simple tables with 1-2 columns except the id. I don't have an explicit assignment of fields as I have only one dynamic entity to hold them. The point of my question here is that I want to pass this tabl object and prepare a Spring form in jsp like this (I pass tableName from another JSP and it's ok:
#RequestMapping(value = "/addnew/{tableName}")
public String insertForm(#PathVariable("tableName") String tableName, Model m) {
Tabl tabl = new Tabl();
tabl.setTableName(tableName);
List<String> cols = spravochnikService.selectCols(tableName); //prepares structure by getting column names of the needed table (except the id): for example tableName=Employee, cols are firstname and lastname
tabl.setTableColumns(cols);
m.addAttribute("command", tabl);
return "insert_form"
}
insert_form.jsp:
<f:form action="${url_csave}">
<table border="1">
<c:forEach var="col" items="${command.tableColumns}">
<tr>
<td>
<c:out value="col" />
<f:input path="col" />
</td>
</tr>
</c:forEach>
<tr>
<td colspan="2" align="right">
<button>Save</button>
</td>
</tr>
</table>
</f:form>
When I try to do it it says invalid property "firstname" and invalid getter method. I can't have an explicit getter for it of course as all my tables are all in one dynamic entity. How to make Spring Forms understand the structure?

MVC3 error when passing a model from controller to view while using a viewModel

I'm sorry, for I'm sure there's a way to do this with a viewModel, however I'm very inexperienced with this and don't even know if I'm doing it correctly.
What I'm trying to do is pass multiple blogs and the profile info of the user who posted each blog to a view.
I'm getting the following error.
The model item passed into the dictionary is of type
'ACapture.Models.ViewModels.BlogViewModel', but this dictionary
requires a model item of type
'System.Collections.Generic.IEnumerable`1[ACapture.Models.ViewModels.BlogViewModel]'.
I'm trying to pass the following query results to the view.
var results = (from r in db.Blog.AsEnumerable()
join a in db.Profile on r.AccountID equals a.AccountID
select new { r, a });
return View(new BlogViewModel(results.ToList()));
}
This is my viewModel
public class BlogViewModel
{
private object p;
public BlogViewModel(object p)
{
this.p = p;
}
}
And my view
#model IEnumerable<ACapture.Models.ViewModels.BlogViewModel>
#{
ViewBag.Title = "Home Page";
}
<div class="Forum">
<p>The Forum</p>
#foreach (var item in Model)
{
<div class="ForumChild">
<img src="#item.image.img_path" alt="Not Found" />
<br />
<table>
#foreach (var comment in item.comment)
{
<tr><td></td><td>#comment.Commentation</td></tr>
}
</table>
</div>
}
</div>
Thanks in advance.
I guess you need to change your view model a little to:
public class BlogViewModel
{
public Blog Blog { get; set; }
public Profile Profile{ get; set; }
}
and then return it as follow:
var results = (from r in db.Blog.AsEnumerable()
join a in db.Profile on r.AccountID equals a.AccountID
select new new BlogViewModel { Blog = r, Profile = a });
return View(results.ToList());
Then in your foreach loop inside of view, you will get an objects that will contain both - profile and blog info, so you can use it like f.e. #item.Profile.Username
I'm not entirely sure what you're trying to accomplish with the ViewModel in this case, but it seems like you are expecting for the page to represent a single blog with a collection of comments. In this case you should replace
IEnumerable<ACapture.Models.ViewModels.BlogViewModel>
With
ACapture.Models.ViewModels.BlogViewModel
Then Model represents a single BlogViewModel, that you can iterate over the comments by using Model.comments and access the image using Model.image.img_path.
If this not the case, and you intend to have multiple BlogViewModels per page, then you will have to actually construct a collection of BlogViewModels and pass that to the view instead.

MVC3 - parameter value always null

I'm trying to make an "advanced search" view, using two partial views and Ajax. I defined a "SearchFilter" entity having as properties all the available search criteria. On submit in the "_Filter" partial view (the OnSuccess AjaxOption), I need to pass it to the "ListResults" action which updates the "_Results" partial view.
The problem is that I always get a null entity as incoming parameter of the ListResults action.
The code is as follows:
AdvancedSearchView.cshtml
#model MyApp.ViewModels.SearchFormViewModel
#{
ViewBag.Title = "Advanced search";
}
<div id="divFilter">
#Html.Partial("_Filter", Model)
</div>
<div id="divResults">
#Html.Partial("_Results", Model.ResultsList)
</div>
_Filter.cshtml
#model MyApp.ViewModels.SearchFormViewModel
<script type="text/javascript">
function getForm(url, divName) {
var obj = new Date();
url = (url.indexOf('?', 0) != -1) ? url + '&uid=' + obj.getTime() : url + '?uid=' + obj.getTime();
$.get(url, function (data) {
$("#" + divName).html(data);
});
}
</script>
#using (Ajax.BeginForm("Search", null, new AjaxOptions
{
UpdateTargetId = "divFilter",
InsertionMode = InsertionMode.Replace,
OnSuccess="getForm('"+Url.Action("ListResults", "Products", new { myFilter = Model.CurrentFilter}) + "','divResults')"
}, new { id = "idSearchForm" }))
{
<fieldset style="width: 800px; line-height: 1.4em;">
<legend>Configure your search filters</legend>
...
</fieldset>
<input type="submit" value="Rechercher" class="submit" style="width: 280px" />
}
Controller
public ActionResult Search()
{
SearchFilter currentFilter = new SearchFilter();
List<Product> filteredProductsList = repository.FindProducts_Filtered(currentFilter);
return View("AdvancedSearchView", new AdvancedSearchFormViewModel(currentFilter, filteredProductsList/* , necessary select lists */));
}
[HttpPost]
public ActionResult Search(AdvancedSearchFormViewModel model)
{
SearchFilter currentFilter = model.CurrentFilter;
// set the necessary select lists
List<Product> filteredProductsList = repository.FindProducts_Filtered(currentFilter);
return PartialView("_Filter", AdvancedSearchFormViewModel(currentFilter, filteredProductsList/* , necessary select lists */));
}
public ActionResult ListResults(SearchFilter myFilter)
{
List<Product> filteredProductsList = repository.FindProducts_Filtered(currentFilter);
return PartialView("_Results", filteredProductsList);
}
The view model
public class AdvancedSearchFormViewModel
{
// Properties
public SearchFilter CurrentFilter { get; set; }
public List<Product> ResultsList { get; set; }
// some SelectLists
// Constructor
public AdvancedSearchFormViewModel()
{}
public AdvancedSearchFormViewModel(SearchFilter pCurrentFilter, List<Product> pResultsList, /* necessary select lists*/)
{
CurrentFilter = pCurrentFilter;
ResultsList = pResultsList;
// the SelectLists
}
}
I have no doubt I do something wrong, but I cannot see what it is.
The generated HTML markup of the BeginForm is like this:
<form action="/Products/Search" data-ajax="true" data-ajax-mode="replace" data-ajax-success="getForm('/Products/ListResults?myFilter=MyApp.Models.SearchFilter&uid=2622ea0e-d7dc-48fa-b65d-519978ee40b3','divResults')" data-ajax-update="#divFilter" id="idSearchForm" method="post">
The reason you are getting a null value for the myFilter argument of the ListResults action is because this is what you are submitting to the server:
/Products/ListResults?myFilter=MyApp.Models.SearchFilter&uid=2622ea0e-d7...
The default modelbinder is trying to turn the string "MyApp.Models.SearchFilter" into an instance of MyApp.Models.SearchFilter, which it cannot do.
I can't see the code for this model object, but you should try sending each of the parameters individually so that the modelbinder is able to construct an instance of the SearchFilter out of its properties:
OnSuccess="getForm('"+Url.Action("ListResults", "Products", new {
Prop1 = Model.CurrentFilter.Prop1,
Prop2 = Model.CurrentFilter.Prop2,
etc...
})...
Update after comment 1
To answer your question about showing parameter values in the URL, this is how HTTP works. If you do not want to show any parameters sent to the server in the URL, then you will need to do an HTTP POST instead of an HTTP GET.
However, any time your operation is idempotent, you should use an HTTP GET. HTTP POST should be reserved for when the input submission should somehow alter the state of the application. In actuality, I disagree with your using [HttpPost] for your public ActionResult Search(AdvancedSearchFormViewModel model) action method. Since all it is doing is returning data to be viewed, it is an idempotent action.
That said, there is nothing preventing you from breaking this guideline and doing an HTTP POST instead of a GET. Personally, I don't see a problem with the URL parameters. You see them all the time on the web. For example, see the URL for this link.
HTTP does not understand complex objects, only text. To send data for complex objects over HTTP, they need to be broken down into their text parts. To send data like this over an HTTP GET, they need to be in the URL.

Resources