Using Spring Boot, Thymeleaf and Oracle: How to dynamically populate the submenus in a web site menu defined in a fragment? - spring-boot

I have a web site that consists of approximately 30 html thymeleaf templates. I have a home_navigation.html fragment that gets included in all templates as the header. This header defines a main navigational menu with popup submenus. The popup submenus under one of my main menus needs to be generated from my oracle database table.
Normally when passing data from a database into a Thymeleaf template I would put the code in the controller to call the java DAO and return a list of Link objects and then add that list in the controller to the model using .setAttribute. Then in the Thymeleaf template I would iterate through the "${List}" in a "th:each" outputting the "<a href..." for each Link object in the list. That is all fine and dandy.
I also can pass parameters into the fragment. So that isn't the problem BUT...
Since the main navigational menu is added as a header fragment into the beginning of every template then I would have to go into every defined controller and add code that would pull the list of Links and pass it into the template and then pass the list into the fragment from every page. that would be approximately 30 times!!!
How...using Spring Boot and Thymeleaf, does someone feed data into a fragment to populate a menu dynamically from a database that is then added as a header fragment into every page/template on the site?
Is there a way to create a controller for the fragment and somehow have every page call the controller for the fragment before the fragment contents are put into every page?
Thank you.

There are a number of ways to solve this problem depending on whether your menu can change while the user is logged in. If it can change then you can create a base controller class that has a method annotated with #ModelAttribute. All of your controllers would inherit from this base class. This method would obtain the menu items and add them to the model each time a page is requested. A simplistic example of such a class is:
#Component
public abstract class BaseController {
#Autowired
private AlphabetService alphabetService;
#ModelAttribute(name = "alphabet")
public List<String> getAlphabet() {
return alphabetService.getCharacters();
}
}
Your page would access them as normal
th:each="character : ${alphabet}"
You could also access the service directly in the page by doing something like
th:each="character: ${#alphabetService.getCharacters()}"
If the menu items do not change while the user is logged in then you could add #SessionAttributes("alphabet") at the class level to the above example and the service method would be called only once when the first page is displayed and cached in the session for future access.

Related

How to set global viewmodels in ASP .NET Core 3.1?

I am new to ASP.NET Core and have some trouble with binding global models and viewmodels to razor views. My application is a mixture of Web API and MVC with razor views.
My goal
When I open an ASP.NET MVC page, I need to instantiate a model by loading it from the database (DbContext service) based on an id received in a cookie. I want to use this model object globally in every view or partial view.
Current Implementation
I can access the cookies in action methods of page controllers, so that I have to load the model from the DbContext in every action method and bind it as viewmodel to target view. This is not practical, because I have to do this in every page controller, because I need that model object on all pages in my navigation pane.
Idea
I think it should be possible to access to Cookies and dbcontext within Startup.cs and bind the model object to _ViewStart.cshtml, so that it is accessible globally in every view or partial view. Even this approach were correct, I do not have any idea how the code would look like. Because I am learning Web-Apps with .NET Core by learning by doing and try and error at the moment. :(
UPDATE
I have a layout page _Layout.cshtml, which includes partial views like the _NavPane.cshtml. My goal is to pass a Model object to the _Layout, which is instantiated via loading from the database (I have a service IMandantRepository for this purpose) and dependent on a cookie.
That model object is needed on every page request. That's why it would be a better practice to load the model object outside the MVC page controllers and pass it to them (what I can not implement technically).
I tried to find a solution by myself and ended up in following interim ugly solution. Following is the content of the _ViewStart file. On the bottom I assign the needed global variables, which I can use in every view or partial view.
This solution has at least two disadvantages:
The model object is possibly loaded redundantly.
Too many program logic in a view file.
#inject MyProject.Data.IMandantRepository mandantRepo
#{
// Main layout template
Layout = "_Layout";
// Define default values
bool showAdminSection = false;
string src = "/images/logos/nologo.png";
// Read cookie value
string currentMandantUid;
Context.Request.Cookies.TryGetValue("currentMandant", out currentMandantUid);
// Load mandant from the database
var thisMandant = mandantRepo.GetMandantByUid(currentMandantUid);
if(thisMandant is Mandant){
src = "data:image/*;base64," + thisMandant.GetBase64Logo();
showAdminSection = thisMandant.Abbr == "AdminMandant";
}
// Assing global variables to ViewData
ViewData["CurrentMandant"] = thisMandant;
ViewData["logoSrc"] = src;
ViewData["showAdminSection"] = showAdminSection;
}
This is an example code in ConfigureService() of Startup.cs. You can register your dbContext class in this way.
services.AddDbContext<BookStoreContext>( options =>
options.UseSqlServer(_configuration.GetConnectionString("DefaultConnection")));

ASP.NET Core populate model only once for all views

I'm new with ASP.Net Core (3.0 in this case) and I´m trying to create a menu that is visible on all views of a WebApplication, is created dynamically and must be populated only once. Below i explain the steps and try outs i did to reach the goal needed (if required i can share the code I'm using).
This is what i did:
In a simple way, using the "_Layout.cshtml" page, i created a static HTML menu and made all other views simply inherit that layout. So far, so good;
Next challenge comes from the fact that the menu items are dynamically created after a User has logged-in, which i managed to overcome by setting a ModelView inside a controller (HomeController.cs with Index action in this case), and then delivering it to the view. For this case works OK, because the default page is ~\Home\Index\, problem is when i change to a different view with a different controller, the menu has to be rendered again, and so i have to replicate the code (a problem dealt create a BaseController and BaseModel based on this post along side the OnActionExecuted to host the menu generating code)
Now, the biggest problem is the fact that i can only populate the menu once, after the user logs-in. Each time there is a redirect between different controllers/views (post-back of same controller/view works fine), the model is null inside the OnActionExecuted, I tried using ViewData, ViewBag, TemData, but all are null.
So, my question is, how to keep that specific data alive and shared, basically across all the views, and only gets populated once (after each user login) between redirects from different views?
I have been reading around and found several solutions besides the one i did, but i did not found any that could keep data alive throughout the user session the way I need:
ViewBag, ViewData and TempData
Can the shared layout view have a controller in ASP.NET MVC?
Pass data to layout that are common to all pages
To sum up, my flow at this moment, is like this:
User Logged-in
Redirect to default: ~\Home\Index
MenuModelView.cs for the menu gets built and HomeController.cs returns to Index.cshtml with the model attached to it.
Index.cshtml receives the populated ModelView and it uses _Layout.cshtml
The _Layout.cshtml builds the HTML tags for the menu based on the MenuModelView.cs data
User navigates to a different view and steps 3 to 5 are repeated from a specific controller/view
If you want to create a control that can be accessible in all pages without changing every controller, I strongly suggest creating a view component. For a view component has no relationship with your controller, but can access dependencies like database and full HTTP context.
For example, you want to build a custom nav menu, you can just create a view component named NavHeader
using Microsoft.AspNetCore.Mvc;
namespace YourProject.Views.Shared.Components.NavHeader
{
public class NavHeader : ViewComponent
{
public NavHeader(YourDbContext context)
{
// you can access your dependencies, like database.
}
public IViewComponentResult Invoke()
{
// your own logic. You can access HTTPContext here.
var model = new YourOwnModel();
return View(model);
}
}
}
And just call it in any view or layout.
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
</head>
<body>
#*Render your component like this*#
<vc:nav-header></vc:nav-header>
</body>
For more details about view component, please reference:
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-3.1
https://anduin.aiursoft.com/post/2020/1/4/share-view-component-between-different-aspnet-core-web-project

Sharing controller JavaFX [duplicate]

I have created a root FXML which is a BorderPane and it has his own root controller.
I want to dynamicly add FXML's to the center of this borderpane.
Each of these fxml's share the same controller, root controller. I have done this in netbeans by choosing an exsisting controller when creating an empty FXML file.
I also have gave the nodes different id names, but the root controller does not recognize the nodes in these fxml's.
Is it possible to share the same controller for different fxml's?
Thanks in advance
Background
I don't know that sharing a controller instance is really recommended, at least I've never seen it done before.
Even if you set the controller class in each of the fxml's you are loading to the same value, it isn't going to share the same controller instance, because every time you load a controller, it will create a new instance (object) of the controller class (which doesn't seem to be what you want).
Potential Solutions
I haven't tried either of these solutions, but believe they will work.
The initialize method will probably be called each time you load a new fxml file. So you will want to account for that in your logic by making initialize idempotent.
A. Manually set the controller instance.
Remove all of the references to your controller class from your fxml files.
Manually create an instance of your controller class.
MyController controller = new MyController();
Set the controller to your controller instance before you load each fxml.
FXMLLoader loader = new FXMLLoader();
loader.setController(controller);
Panel panel = (Panel) loader.load("myfxml.fxml");
Repeat step 3 for each of your fxml files, using the same controller reference each time.
B. Use a controller factory.
You can set a controller factory on your fxml loaders and have the controller factory always return the same controller instance.

How to make a laravel 5 view composer

I'm still learning Laravel and I'm working on a small project to help me understand better. In the project, I am in need of a global array, so that I may display it or its attributes on every view rendered. sort of on a notification bar, so that each page the user visits, he/she can see the number of notifications (which have been fetched in the background and are stored in the array).
I have done some research, and realized that I have to fetch and compile the array in a view composer I think. But everywhere I go, I cant seem to understand how to make a view composer.
I need to fetch the relevant rows from the database table, and make the resulting array available to each view rendered (I'm thinking attaching it somehow to my layouts/default.blade.php file.). Please help, any and all advice is greatly appreciated:)
You can now inject services on your view
More info here: https://laracasts.com/series/whats-new-in-laravel-5-1/episodes/2
You have to use Sub-Views of laravel blade. I guess your functionality is like a sidebar or like a top bar which will be rendered at every page.
//Your Controller pass data
class YOUR_CONTROLLER extends Controller {
public function index()
{
$data = YOUR_DATA;
return view('YOUR_VIEW_FILE', get_defined_vars());
}
}
//In Your View File
#extends('LAYOUTS_FILE')
#section('YOUR_SECTION')
#include('YOUR_SUB_VIEW_FOR_NOTIFICATION')//You need not pass any data passed all data will be available to this sub view.
#endsection
In your sub view
//Do what ever you want looping logic rendering HTML etc.
//In your layout file just yield or render the section that's it
#yield('YOUR_SECTION')
More explanation can be found Including Sub-Views

Custom MVC Framework: How do I propogate data to a view?

I am creating a custom MVC framework.
I verrrrry loosely modeled it after the codeIgniter framework, but it's ground-up custom for the most part.
I'm at the point where I have URL's routing to the appropriate controller actions, but I'm stuck at the point where I generate a view that can utilize data generated by the controller.
I have views defined (static HTML with inline php ready to populate dynamic data), and I have the destructor of my base controller require()'ing the view in order to populate the browser with the view... here's the code:
public function __destruct()
{
if ($this->bSuppressView === false)
{
require(APP_PATH.'views/layout/header.php');
require(APP_PATH.'views/'.$this->sController.'/view.'.$this->sController.'.'.$this->sAction.'.php');
require(APP_PATH.'views/layout/footer.php');
}
}
Basically, when the controller is done executing, the teardown process of the base controller will then include the global header view, the controller's action's view, and then the global footer view, which should populate the webpage with everything for the URL that was requested...
HOWEVER, I cannot access any globally defined variables from the embedded php in the view code. In my bootstrap class, I define a bunch of local variables such as my config variable, etc., but the view seems to consider those variables undefined. Additionally, i'm unsure how to allow the view to access data that the controller may have generated. Where do I "stick" it to make it available to the view?
Let me know if this isn't clear, and i'll update.
Thanks!
UPDATE: I've discovered that while doing it this way, the "environment" of the views is within the controller object, which, as far as I can tell is a great thing! I don't have to propogate anything anywhere but in the controller, and I can use "$this->" in the views to get access to anything public or private from within the controller class!!!
That leaves the question: is this "normally" how it's done in MVC? What's the BEST way to propogate a view? I think this will suit my purposes, and I will post back if I discover a limitation to just treating the embedded view php as "within the scope of the calling controller"...
The way this is generally done, is that the view is actually an object. You pass that object you're variables, and that view object takes the template you gave it, includes it so that it's in the current scope, and grab the output into a variable using output buffering.
To give you a basic idea:
// controller object
$this->set('key','val');
$this->render('mytemplate');
// controller base class
$view = new View;
$view->setData($this->getData());
// view class
class View {
....
function render() {
ob_start();
include $this->resolveTemplate();
$out = ob_get_contents();
ob_end_clean();
return $out;
}

Resources