Spring MVC 3.2 Thymeleaf Ajax Fragments - ajax

I'm building application with Spring MVC 3.2 and Thymeleaf templating engine. I'm a beginner in Thymeleaf.
I have everything working, including Thymeleaf but I was wondering if anyone knows of a simple and clear toturial on how to do simple Ajax request to controller and in result rendering only a part of a template (fragment).
My app has everything configured (Spring 3.2, spring-security, thymeleaf, ...) and works as expected. Now I would like to do Ajax request (pretty simple with jQuery but I don't wan't to use is since Thymeleaf in its tutorial, chapter 11: Rendering Template Fragments (link) mentiones it can be done with fragments.
Currently I have in my Controller
#RequestMapping("/dimensionMenuList")
public String showDimensionMenuList(Model model) {
Collection<ArticleDimensionVO> articleDimensions;
try {
articleDimensions = articleService.getArticleDimension(ArticleTypeVO.ARTICLE_TYPE);
} catch (DataAccessException e) {
// TODO: return ERROR
throw new RuntimeException();
}
model.addAttribute("dimensions", articleDimensions);
return "/admin/index :: dimensionMenuList";
}
the part of the view where I would like to replace <ul></ul> menu items:
<ul th:fragment="dimensionMenuList" class="dropdown-menu">
<li th:unless="${#lists.isEmpty(dimensions)}" th:each="dimension : ${dimensions}">
</li>
</ul>
Any clue is greatly appreciated. Especially if I don't have to include any more frameworks. It's already too much for java web app as it is.

Here is an approach I came across in a blog post:
I didn't want to use those frameworks so in this section I'm using jQuery to send an AJAX request to the server, wait for the response and partially update the view (fragment rendering).
The Form
<form>
<span class="subtitle">Guest list form</span>
<div class="listBlock">
<div class="search-block">
<input type="text" id="searchSurname" name="searchSurname"/>
<br />
<label for="searchSurname" th:text="#{search.label}">Search label:</label>
<button id="searchButton" name="searchButton" onclick="retrieveGuests()" type="button"
th:text="#{search.button}">Search button</button>
</div>
<!-- Results block -->
<div id="resultsBlock">
</div>
</div>
</form>
This form contains an input text with a search string (searchSurname) that will be sent to the server. There's also a region (resultsBlock div) which will be updated with the response received from the server.
When the user clicks the button, the retrieveGuests() function will be invoked.
function retrieveGuests() {
var url = '/th-spring-integration/spring/guests';
if ($('#searchSurname').val() != '') {
url = url + '/' + $('#searchSurname').val();
}
$("#resultsBlock").load(url);
}
The jQuery load function makes a request to the server at the specified url and places the returned HTML into the specified element (resultsBlock div).
If the user enters a search string, it will search for all guests with the specified surname. Otherwise, it will return the complete guest list. These two requests will reach the following controller request mappings:
#RequestMapping(value = "/guests/{surname}", method = RequestMethod.GET)
public String showGuestList(Model model, #PathVariable("surname") String surname) {
model.addAttribute("guests", hotelService.getGuestsList(surname));
return "results :: resultsList";
}
#RequestMapping(value = "/guests", method = RequestMethod.GET)
public String showGuestList(Model model) {
model.addAttribute("guests", hotelService.getGuestsList());
return "results :: resultsList";
}
Since Spring is integrated with Thymeleaf, it will now be able to return fragments of HTML. In the above example, the return string "results :: resultsList" is referring to a fragment named resultsList which is located in the results page. Let's take a look at this results page:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
</head>
<body>
<div th:fragment="resultsList" th:unless="${#lists.isEmpty(guests)}" id="results-block">
<table>
<thead>
<tr>
<th th:text="#{results.guest.id}">Id</th>
<th th:text="#{results.guest.surname}">Surname</th>
<th th:text="#{results.guest.name}">Name</th>
<th th:text="#{results.guest.country}">Country</th>
</tr>
</thead>
<tbody>
<tr th:each="guest : ${guests}">
<td th:text="${guest.id}">id</td>
<td th:text="${guest.surname}">surname</td>
<td th:text="${guest.name}">name</td>
<td th:text="${guest.country}">country</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
The fragment, which is a table with registered guests, will be inserted in the results block.

Rendering only Thymeleaf fragments also works well with ModelAndView.
Your controller
#RequestMapping(value = "/feeds", method = RequestMethod.GET)
public ModelAndView getFeeds() {
LOGGER.debug("Feeds method called..");
return new ModelAndView("feeds :: resultsList");
}
Your view
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head></head>
<body>
<div th:fragment="resultsList" id="results-block">
<div>A test fragment</div>
<div>A test fragment</div>
</div>
</body>
</html>
What's actually rendered
<div id="results-block">
<div>A test fragment</div>
<div>A test fragment
</div>
</div>

As an alternate version to Sohail's great answer, I want to give a version that using javascript can send the whole th:object to the controller, integrating Thymeleaf in your forms, not having to use #PathVariable which becomes messy or not usable at all when you've forms with many fields.
For the form (using an example which returns an object which has an id and a name Strings, and feeds a combobox with a Map that has some of those objects as values) we have:
<form method="post" th:action="#{/yourMapping}" th:object="${object}" id="yourFormId">
<select th:field="*{mapOfObjects}">
<option
th:each="entry: ${mapOfObjects}"
th:value="${entry.value.id}"
th:text="${entry.value.name}" >
</option>
</select>
<p>Name:
<input type="text" th:field="*{name}" />
</p>
</form>
When this form is submited (using a button with type submit for example) the whole document will be replaced. However we can intercept this submit with javascript and do it the ajax-way. To achieve this, we will add an interceptor to our form using a function. First call the function that adds the interceptor right after the form:
<script>formInterceptor("yourFormId");</script>
And the function looks like this (place it in the head of the document or wherever suits your needs):
<script>
function formInterceptor(formName) {
var $form = $("#" + formName);
$form.on('submit', function(e) {
e.preventDefault();
$.ajax({
url : $form.attr('action'),
type : 'post',
data : $form.serialize(),
success : function(response) {
if ($(response).find('.has-error').length) {
$form.replaceWith(response);
}
else{
$("#ajaxLoadedContent").replaceWith(response);
}
}
});
});
};
</script>
Now whenever the form is submited, this function will trigger, and it will:
Prevent the original form submit
Make an ajax call using the url defined in the form's th:action
Serialize the form data. Your controller will be able to recieve this in an object
Replace the part of your html code with the returned fragment
The replaced part should look like this
<div id="ajaxLoadedContent"></div>
And the controller can recieve the th:object in the form, with it's values filled, like this (Replace Object with your object's type and "object" with a proper name):
#PostMapping(value = /yourMapping)
public String ajaxCtrlExample(#ModelAttribute("object") Object object, Model model) {
return yourFragmentPath;
}
And that's everything. Call the function that adds the interceptor after every form you need in ajax-version.

Related

How to add an element id to the url of the Thymeleaf template in the controller in Spring

I have a Spring Application and Server Side Rendering with Thymeleaf as Templating language.
A button sends a get or post request to the controller in Spring, which puts some message to the view, which is rendered into the HTML file and send back to the client. The message should be optional. Thats why the template must also be able to be called without the message.
Next i want the client browser to scroll down to the part of the page where this message is rendered into, which is normally very easy. You would just have to append the id of the element to the url like following example.
https://stackoverflow.com/#footer
In this example the browser scrolls down to the footer of the page.
Below is what i tried. Unfortunately it doesnt't work like that. Spring/Thymeleaf tries to find a index#messagebox template which can't be found. Hence a Whitelabel Error Page error is thrown/shown.
Page.html
<section>
<h2>Form to send request</h2>
<form action="showmessage" method="get">
<input type="submit" value="Click for message">
</form>
</section>
Controller.java
#GetMapping("showmessage")
public ModelAndView showMessage(){
return new ModelAndView("index#messagebox",Map.of("optionalmessage","Some message that is optioal"));
}
src/main/resources/templates/index.html
<body>
<h1>Index Page</h1>
<div id="messagebox" th:fragment="message" th:with="optionalmessage=${optionalmessage}">
<p th:if="${optionalmessage!=null}">[[${optionalmessage}]]</p>
</div>
</body>
The problem can be solved with Flashmessages and Redirects. The html basically keeps the same. If the message attribute is set, you render it.
src/main/resources/templates/index.html
<div th:if="${msg}">
<div class="message" >
<p>[[${msg}]]</p>
</div>
</div>
The most important changes had to be made in the controller. First a parameter of type RedirectAttributes is added to the Controller that handles the request.
If wanted the message is added with the RedirectAttributes.addFlashAttribute function as shown below. Finally a redirect is returned, which contains the needed tag. A second controller is also needed that handles the Get Request of the Redirect with a Model as input parameter and returns the needed Template. The #tag is simply passed throuhg to the client browser.
Controller.java
#GetMapping("showMessage")
public String postNotification(RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("optinalMessage", "Hello I am an optional message");
return "redirect:/index#footer";
}
#GetMapping("index")
public String getIndex(Model model) {
return "index";
}
You add id in URL by using ?id=value.
And in controller #RequestMapping("/path/{id}")
to access that variable #PathVariable

ASP .NET MVC Ajax insert view instead instead replace data only first time

I'm new to AJAX, and I have a very simple example, but has a problem; first call the data is duplicated at the View and in subsequent calls work correctly. What am I doing wrong?
The ~/Views/Shared/_Layout.cshtml file had all scripts necesary:
<script src="~/Scripts/jquery-3.1.0.min.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
First time, the initial view:
Second time, first time button click inserts a number instead of replace number:
Third time, second time button click works fine but in the second line number... an so on:
This is my code:
Model
namespace MQWebSt.Models
{
public class AjaxTest
{
public int Number { get; set; }
}
}
Controller:
using System.Web;
using System.Web.Mvc;
using MQWebSt.Models;
namespace MQWebSt.Controllers
{
public class AjaxTestController : Controller
{
// GET: AjaxTest
public ActionResult Vista()
{
AjaxTest at = new AjaxTest { Number = 1 };
return View(at);
}
[HttpPost]
public ActionResult Vista( AjaxTest model)
{
Random rnd = new Random();
model.Number = rnd.Next(1, 100);
return PartialView("AjaxTestPartial", model);
}
}
}
View Vista.chtml:
#model MQWebSt.Models.AjaxTest
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Ajax.BeginForm("Vista", new AjaxOptions { UpdateTargetId = "divEmp", InsertionMode = InsertionMode.Replace }))
{
<button class="btn btn-primary btn-md glyphicon glyphicon-menu-right" name="Contestar" type="submit" value="+1"></button>
<div class="panel panel-footer">
<table id="divEmp">
#Model.Number.ToString()
</table>
</div>
}
AjaxTestPartial.chtml:
#model MQWebSt.Models.AjaxTest
#Model.Number.ToString()
The issue is you're using InsertionMode = InsertionMode.Replace, it's going to replace the data in the UpdateTargetId. You could use InsertionMode.InsertBefore instead and it would build the values out in the parent of #divEmp. However, if you need the values to be placed in #divEmp, you'll need to write a JavaScript handler for the OnSuccess option of your Ajax form, that way you can dictate if you're doing insert vs. pre-pend vs. replace.
The issue is, your HTML markup code in your razor view is not valid. With the code you have, razor will generate the below markup (Check the view source)
<div class="panel panel-footer">
1
<table id="divEmp"></table>
</div>
You can see that 1 is not inside the table. It is outside. So when your ajax form submit happens, it will update the tables content with the new value. that is the reason you are still seeing the initial number.
The solution is to change the table to a div/span
<div id="divEmp"> #Model.Number.ToString() </div>
Or if you absolutely need to have a table for any reason, have the value inside a td and use "divEmp" as the id attribute value of that.
<table >
<tr>
<td id="divEmp">1</td>
</tr>
</table>

Simple AJAX example with Grails

I'm pretty new to Grails and I'm trying to learn how AJAX works in Grails. For this I'm trying to modify the multiply AJAX example from here so that my application displays the input string with AJAX on the page. (To clarify this: The user enters for example "foo" and the page should display "foo" under the search field)
This is my code so far:
My index.gsp template which contains an input form and shall display the string which is typed in the form:
<!doctype html>
<html>
<head>
</head>
<body>
<div id="search">
<g:render template="searchForm"/>
</div>
<div id="results">
<g:render template="searchResultForm"/>
</div>
</body>
</html>
My _searchForm.gsp template which includes the search form:
<g:form>
<label for="suchen"></label><g:textField name="suchen"/>
<g:submitToRemote url="[controller:'search', action:'search']" update="results" value="Suchen"/>
</g:form>
My _searchResultForm.gsp template which shall display all results (in my case just the search string)
${results}
My SearchController which shall get the search string and return the same string:
class SearchController {
...
def search(String s) {
return s
}
}
My problem is that after I write something into the input form and press the send button, nothing happens. No error but it also don't displays the input string under the input form.
You may wonder why I'm trying to do this with AJAX: My purpose is to realize an AJAX search. When the AJAX part works it shouldn't be a problem to add the search logic to the controller.
You should show your searchResultForm template. But I guess your problem is you don't send the model correctly to the gsp, and you haven't set the gsp in the response also
If this is your template gsp:
<div>${s}</div>
You action have to be:
def search(String s) {
render template: 'searchResultForm', model:[s:s]
}
By this way, the action generates the html to be sent with the given model, which is rendered by jquery into the div.

Validation of dynamic created form (AngularJS)

I try to made nested form with validation. All works fine, but when I remove one of nested form, validation continue to use removed form. I made jsfiddle example http://jsfiddle.net/sokolov_stas/VAyXu/
When example runs, form are valid. If click "+" button, nested form will be added and valid will be false. Then click "-" button, and valid will be false all the same.
The question is: How to remove dynamic created form from validation processing.
Well, for one thing, a <form> inside of a <form> is not valid HTML.
Second, you're not supposed to be doing DOM manipulation from inside the controller. The controller is for "business" logic. See the section on controllers here
For what you're doing, you'd probably be better off using one form, with an ng-repeat inside of it, and adding additional elements to an array:
<form name="myForm" ng-controller="FormCtrl" ng-submit="doSomething()">
<div ng-repeat="item in items">
<input ng-model="item" type="text" required/>
</div>
<a ng-click="addItem()">+</a>
<a ng-click="removeItem()">-</a>
<button type="submit">Submit</button>
<div>Form valid: {{myForm.$valid}}</div>
</form>
and the controller:
function FormCtrl($scope) {
$scope.items = [];
$scope.addItem = function() {
$scope.items.push(null);
};
$scope.removeItem = function() {
$scope.items.pop();
};
$scope.doSomething = function () {
//your submission stuff goes here.
};
}

Partial View - >> How to REFRESH the HTML content without having to redirect to the view

My site has a concept like Skype that allow users to go "Online" and "Offline". I created a partial view that allows the user to switch mode:
#if (Convert.ToBoolean(ViewData["IsLogged"].ToString()))
{
<div id="onlineStatus">
You are currently <strong>ONLINE</strong> >>
#Html.ActionLink("Go OFFLINE", "GoOffline", "Account")
</div>
}
else
{
<div id="offlineStatus">
Ready for business >>
#Html.ActionLink("Go ONLINE", "GoOnline", "Account")
</div>
}
This is how we load the Partial View:
public ActionResult OnlineStatusCtrl()
{
if (SiteUser.IsAuthenticated)
ViewData["IsLogged"] = SiteUser.IsOnline.ToString();
return PartialView("OnlineStatusCtrl");
}
When a user clicks on the link "Go ONLINE" or "Go OFFLINE", the Controller respond as:
public ActionResult GoOnline()
{
if (SiteUser.IsAuthenticated)
SiteUser.GoOnline();
ViewData["IsLogged"] = "True";
return RedirectToAction("Index", "Home");
//return PartialView("OnlineStatusCtrl");
//return EmptyResult();
}
public ActionResult GoOffline()
{
if (SiteUser.IsAuthenticated)
SiteUser.GoOffline(true);
ViewData["IsLogged"] = "False";
return RedirectToAction("Index", "Home");
}
This works well ...but the ONLY problem is that if I am on View XXXX, and I click on "Go Online", the controller redirects me to the Index View.
I tried "return EmptyResult()" or "return PartialView("OnlineStatusCtrl") but it does just not work.
From the code, you can see that the only thing the PartialView cares about is the "ViewData['IsLogged'] value"
QUESTION:
What is the way to REFRESH a partial view without having to refresh the entire page or redirect to the main page?
Is it maybe a matter of putting an Html.Beginform() ?
Is it maybe a matter of the Controller returning something that just refresh the content of the PartialView independently from what view is holding the PV itself?
Aiaiaiaia
I still can't figure out how MVC works with PartialViews/
UPDATE
I have updated the code as x suggested and the HTML output is as follow:
<div id="divStatus">
<form action="/" data-ajax="true" data-ajax-mode="replace" data-ajax-update="#divStatus" id="form0" method="post">
<div id="offlineStatus">
Ready for business >>
Go Online
</div>
</form>
</div>
When I click on the LINK, the Controller return PartialView("_OnlineStatusCtrl"); which is hte name of the calling PV ...and the ENTIRE PAGE gets replaced.
You're going to have to use an ajax call of some sort (Microsoft ajax helpers built into MVC, or jquery/javascript ajax call.
To use Microsoft's Ajax, you can use Ajax.BeginForm or Ajax.ActionLink. Both take an AjaxOptions parameter that will allow you to set javascript functions for OnSuccess, and an UpdateTargetId to display the returned data (usually a partial view). Using this will call your action which should return a partial view. Your partial view then replaces the html element (usually a div) identified by the UpdateTargetId parameter. If you decide to go this route, make sure you reference all the proper Microsoft ajax/mvc scripts. You'll pretty much need each script with any combinations of Microsoft, Ajax, Mvc, and even unobtrusive in the name.
Here's an example of one of my ajax forms (modified slightly for simplicity)
<% using(Ajax.BeginForm("addAttribute", new { id = Model.PersonId, attributeId = item.AttributeId }, new AjaxOptions { UpdateTargetId = "myTargetId", OnSuccess = "initForm" })) { %>
<input type="submit" value="Ok" class="editMode okButton" disabled="disabled" />
<input type="button" class="editMode cancelButton" value="Cancel" />
<br />
<input type="button" value="Add" class="addButton" />
<% } %>
I have a div with an id of "myTargetId" (for this example) that will be updated with the returned partial view.

Resources