Thymeleaf SpringMVC page footer doesn't find Controller - spring-mvc

I have a spring boot project that uses SpringMVC and Thymeleaf. I have a footer defined in a layout.html that is included on every page. Is there anyway to include a a call on that footer that shows "status" for each page. This way when you navigate the site, the page footer will always show desired information?
I have a REST Controller that puts a Status object:
#Controller
public class StatusController {
#RequestMapping(value = "/status", method = RequestMethod.GET)
public String get(Model model) {
Status status = new Stauts;
LocalDate lastUpdate = statusRepository.findByLastUpdate();
...
// do some work to update the status object
model.addAttribute( STATUS, status );
return "status";
}
I have a in my footer.html this defined:
<div th:fragment="foot">
<div id="footer">
<div class="container">
<p class="muted credit">
<form action="refreshData" th:action="#{/status}" method="get"></form>
Last update status: <span th:text="${status.status}" class="text-info"></span>
</p>
</div>
</div>
</div>
And then each page includes the footer as such:
...
<div th:include="layout :: foot"></div>
</body>
I put a breakpoint in my controller and its not called, and I always get this exception on page load:
org.springframework.expression.spel.SpelEvaluationException:
EL1007E:(pos 0): Property or field 'status' cannot be found on null
I wonder if the Controller is never called because I have to use something like jQuery to make Ajax calls, or because I have this in my layout.html and that include doesn't make calls (which seems unlikely)?

In Spring MVC you only have one controller per page/call. You cannot call several controllers in one shot. I believe this is your issue.
You have multiple possibilites:
Either you have to incorporate this status variable in each controller yourself.
create a ControllerAdvice that prepares the model attribute status and is applied to each controller (default)
use a service call in your footer to fetch the status like: th:text="${#statusService.status}"
I for myself like the service approach for these kind of global available information.

CURRENT:
<span th:text="${status.status}" class="text-info">
CHANGE TO
<span th:text="${status}" class="text-info">
this will display null
in the controller you have set Status status = null; and set it in the model map to attribute status".
So ${status} is evaluated as null (this is nothing but model.get("status")). ${status.status} will be null.status .
Resulting in
'status' cannot be found on null

Related

Why won't CSS won't work on specific situation

So I'm working a project of a Website. In it, there's an admin page where I can add or remove images that will be shown on the website. I created a Controller for the Main Admin page alone, and then I created a controller for every type of information that can be changed through that page (Porfolio, banner, etc). All the pages are styled by CSS. So when my AdmController returns the view with the main Admin page, everything works fine
The adress for the main Admin Page results in localhost:8000/adm
But once I try to open a view where I would add an image to be shown on the Main page, CSS simply won't work. To acess, say, the add banner page, I have a BannerController that will return a "form-banner" view, resulting in the adress localhost:8000/adm/banner. In this page CSS does not work.
I wanted to test further so I created a new Route that would take me directly to the create banner page without going through the admin page, and also not using any Controllers at all. I got the adress localhost:8000/banner. Now CSS works.
Any ideas why?
These are the routes including the one I created just to test the view:
Route::resource('adm', 'AdmController');
Route::resource('adm/banner', 'BannerController');
Route::get('banner', function () {
return view('admin/form-banner');
});
This is the AdmController:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class AdmController extends Controller
{
public function index()
{
return view('admin/index');
}
}
This is how the BannerController is returning the mal-functioning View:
public function create()
{
return view('admin/form-banner')
And this is the point in the Main Adm View (the one that works) where It calls the BannerController:
<ul class="nav-second-level" aria-expanded="false">
<li>Create new banner</li>
Add your CSS like below
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
the absolute path after your public folder

Action method not found in controller

I am using 'Pro ASP.NET MVC 5' book by Adam Freeman, working on the Sports Store Web Application that he is using. I tried to redo that example into Computer Store instead and everything was working fine up to the moment when I needed to add extra functionality to the cart ('Summary' partial view).
The 'Summary' PartialView is defined in the Cart Controller:
public PartialViewResult Summary(Cart cart)
{
return PartialView(cart);
}
The Summary View:
#model ComputerStore.Domain.Entities.Cart
#{
ViewBag.Title = "Summary";
}
<div class="navbar-right">
#Html.ActionLink("Checkout", "Index", "Cart",
new { returnUrl = Request.Url.PathAndQuery},
new { #class = "btn btn-default navbar-btn"})
</div>
<div class="navbar-text navbar-right">
<b>Your cart:</b>
#Model.Lines.Sum(x => x.Quantity) item(s),
#Model.CaluclateTotalValue().ToString("c")
</div>
And of course, in the _Layout.cshtml file, I call the Action Method:
#Html.Action("Summary", "Cart")
When I start the Web Application, I get the following error:
-A public action method 'Summary' was not found on controller 'ComputerStore.WebUI.Controllers.CartController'.
Now, before we mention HttpPost and HttpGet, let me inform you that the Sports Store example works fine using the same code as above. I have tried numerous ways to fix this, and I know that this has been answered multiple times here, but I just don't understand why it doesn't work. Moreover, all packages are updates.
You are you requiring a parameter for your Summary.
Try this instead:
[HttpGet]
public PartialViewResult Summary()
{
var cart = new Cart();
//Do something with cart to load the data.
return PartialView(cart);
}
If you want to pass a param to Summary you could do:
#Html.Action("Summary", "Cart", new {cart = someExistingCartObject})

ASP.Net Boilerplate & jTable

I have studied the ASP.Net boilderplate template (http://www.aspnetboilerplate.com/Templates) and made some custom changes.
I have a "GetComponentDataList()" method in my services. I played around with that and rendered it as a list like shown here:
<!-- Component list -->
<ul class="list-group" ng-repeat="component in vm.components">
<div class="list-group-item">
<br />
<span>Entry:</span>
<span>{{component.entry}}</span>
</div>
</ul>
components.js code:
vm.refreshComponents = function () {
abp.ui.setBusy( //Set whole page busy until getComponentDataList complete
null,
componentDataService.getComponentDataList( //Call application service method directly from javascript
).success(function (data) {
vm.components = data.componentData;
})
);
};
Now I would like to render the components via jTable. jTable expects an action to get the list of data:
listAction: '/api/services/app/componentData/GetComponentDataList',
How do I use jTable from boilerplate template?
1. Do I need to add a method in my "HomeController" to use jTable?
2. The result of my "GetComponentDataList" method in my service is of type IOutputDto.
That means the result of my service is not directly a list. There is one indirection
level inbetween. Seems like this does not fit together.
3. Can I provide a function in my JS-ViewModel and use that function instead of an action URL?
Any hint would be awesome.
Thx.
I'm working on a sample project to work ABP with jTable. I did not finish it yet. But you can check it. https://github.com/aspnetboilerplate/aspnetboilerplate-samples/tree/master/AbpWithjTable
Add abp.jtable.js to your file (https://github.com/aspnetboilerplate/aspnetboilerplate-samples/blob/master/AbpWithjTable/AbpjTable.Web/App/Main/libs/abp.jtable.js) after all ABP and jtable scripts.
See example js: https://github.com/aspnetboilerplate/aspnetboilerplate-samples/blob/master/AbpWithjTable/AbpjTable.Web/App/Main/views/people/people.js
See example C# App service: https://github.com/aspnetboilerplate/aspnetboilerplate-samples/blob/master/AbpWithjTable/AbpjTable.Application/People/PersonAppService.cs

Displaying "application-specific" validation errors in web UI using Thymeleaf's "fields.hasErrors"

I use Spring MVC together with Thymeleaf. I have an issue with Thymeleaf fields.hasErrors which is as follows:
When a Bean Validation constraint error is raised (such as a #NotNull constraint), the following use of fields.hasErrors works perfectly:
<span class="help-inline" th:if="${#fields.hasErrors('member.email')}" th:text="#{message_form.validation.email}"></span>
An error message is displayed OK next to the email field.
However, when an "application-specific" error (as opposed to Bean Validation constraint error) is raised by my code (see code from controller below),
// FROM SPRING MVC CONTROLLER
if (!registrationService.isEmailAvailable(registrationInfo.getMember().getEmail())) {
bindingResult.addError(new ObjectError("member.email", "email already used")); // TODO i18n
}
The th:if="${#fields.hasErrors('member.email')}" does not evaluate to true and no error message is displayed....
The only way I have found to display those "application-specific" errors is to include the following underneath the form:
<div id="errors" class="alert alert-error">
<ul th:if="${#fields.hasErrors('*')}">
<li th:each="err : ${#fields.errors('*')}" th:text="${err}"></li>
</ul>
</div>
Then, it will display "email already used" as a list item.
Can anyone please clarify the behavior of th:if="${#fields.hasErrors('member.email')}" and especially tell me why it will evaluate to false in the case of "application specific" errors?
I found the solution to my isue: My problem comes from an incorrect usage of the Spring API.
I should be using a FieldError instead of an ObjectError.
So changing from:
//FROM SPRING MVC CONTROLLER
if (!registrationService.isEmailAvailable(registrationInfo.getMember().getEmail())) {
bindingResult.addError(new ObjectError("member.email", "email already used"));//TODO i18n
}
to
//FROM SPRING MVC CONTROLLER
if (!registrationService.isEmailAvailable(registrationInfo.getMember().getEmail())) {
bindingResult.addError(new FieldError("registrationInfo","member.email", "email already used"));//TODO i18n
}
solved the problem.

Meteor using a local connection results in error: insert failed: 404 -- Method not found

I've got a meteor collection on the client side
Friends = new Meteor.Collection("Friends");
Meteor.subscribe("Friends");
I have a user authenticate with facebook and I want to grab a list of their friends:
FB.api("/me/friends? auth_token="+response.authResponse.accessToken,
function(response){
for (i = 0; i<response.data.length;i++){
Friends.insert(response.data[i]);
}
);
I have a function to get that list:
Template.Friends.all_friends = function(){
return Friends.find();
}
I have a template that would like to display all the friends on the screen:
<template name="Friends">
{{#each all_friends}}
<div id="{{id}}" class="friend">
<img src="http://graph.facebook.com/{{id}}/picture" />
{{name}}
</div>
{{/each}}
</template>
What appears to be happening on the page is that all the friends DO flash up on the screen for a split second then immediately the screen flashes back to blank.
In the javascript console the message appears once per friend I have (yes, it is more than zero, thanks for asking)
insert failed: 404 -- Method not found
So! What have I missed? Anyone?
You need that Collection declaration on both the client and the server.
// common code, do not put under /client or inside Meteor.is_client test
Friends = new Meteor.Collection("Friends");
If you want to use Collection only on Client side and you don't need to save that data to server you can declare your collection in "client" folder or in .isClient() function by passing null to the constructor like this:
if(Meteor.isClient()){
// Some other code
...
onlyClientCollection = new Meteor.Collection(null);
// Some other code
...
}

Resources