In this lab, we will create a group Management Page where we can add a user to a group and a navigation bar
Group Management
We start by creating a GroupManagementComponent ViewComponent in the de.tschuehly.easy.spring.auth.group.management package:
GroupManagementComponent.java
@ViewComponent
public class GroupManagementComponent {
public static final String MODAL_CONTAINER_ID = "modalContainer";
public static final String CLOSE_MODAL_EVENT = "close-modal";
public record GroupManagementContext() implements ViewContext { }
public ViewContext render() {
return new GroupManagementContext();
}
}
The template is the same as the UserManagementComponent.jte but we added two <a> links to the <nav> element.
Next, we will create a GroupTableComponent in the de.tschuehly.easy.spring.auth.group.management.table package.
We autowire the groupService and create a GROUP_TABLE_ID constant.
GroupTableComponent.java
@ViewComponent
public class GroupTableComponent {
private final GroupService groupService;
public final static String GROUP_TABLE_ID = "groupTable";
public record GroupTableContext() implements ViewContext { }
public ViewContext render() {
return new GroupTableContext();
}
public GroupTableComponent(GroupService groupService) {
this.groupService = groupService;
}
}
We add the corresponding template GroupTableComponent.jte and set the <table> id to ${GROUP_TABLE_ID}
GroupTableComponent.jte
@import static de.tschuehly.easy.spring.auth.group.management.table.GroupTableComponent.*
@param de.tschuehly.easy.spring.auth.group.management.table.GroupTableComponent.GroupTableContext groupTableContext
<table id="${GROUP_TABLE_ID}">
<thead>
<tr>
<th>
Group Name
</th>
<th>
Group Members
</th>
<th>
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
Now back in the GroupTableComponent.java, we retrieve all groups with the groupService.getAll() method and add this List of groups to the ViewContext
GroupTableComponent.java
@ViewComponent
public class GroupTableComponent {
private final GroupService groupService;
public record GroupTableContext(List<EasyGroup> groupList)
implements ViewContext{}
public final static String GROUP_TABLE_ID = "groupTable";
public ViewContext render(){
List<EasyGroup> groupList = groupService.getAll();
return new GroupTableContext(groupList);
}
public GroupTableComponent(GroupService groupService) {
this.groupService = groupService;
}
}
Now we need to replace the <tbody> element of the GroupTableComponent.jte with the following:
(1): We loop over the groupTableContext.groupList() variable with the @for syntax
(2): We show the groupName in a <td>
(3): We loop over the group.memberList
(4): We show the username in a <span>
(5): If the group has no users we show a no member message
Then we render the GroupTableComponent in the GroupManagementComponent. We autowire it and pass it into the ViewContext
GroupManagementComponent.java
@ViewComponent
public class GroupManagementComponent {
private final GroupTableComponent groupTableComponent;
public static final String MODAL_CONTAINER_ID = "modalContainer";
public static final String CLOSE_MODAL_EVENT = "close-modal";
public GroupManagementComponent(GroupTableComponent groupTableComponent) {
this.groupTableComponent = groupTableComponent;
}
public record GroupManagementContext(ViewContext viewContext)
implements ViewContext{}
public ViewContext render(){
return new GroupManagementContext(groupTableComponent.render());
}
}
In the GroupManagementComponent.jte template we render the GroupTableComponent it in the <main> element, by using the viewContext
If you are stuck you can resume at this checkpoint with:
git checkout tags/lab-3-checkpoint-1 -b lab-3-c1
We haven't done anything new yet, now we are going to start with the inline editing feature.
Inline Editing
We now want to add a new User to one of the groups.
We autowire the GroupTableComponent and create an endpoint in the GroupController:
GroupController.java
public final static String POST_ADD_USER = "/group/{groupName}/add-user"; // (1)
public final static String USER_ID_PARAM = "userId"; // (2)
@PostMapping(POST_ADD_USER)
public ViewContext addUser(@PathVariable String groupName, // (3)
@RequestParam(USER_ID_PARAM) UUID userId){ // (4)
groupService.addUserToGroup(groupName,userId); // (5)
return groupTableComponent.render(); // (6)
}
(1): We define a POST_ADD_USER constant
(2): We define a USER_ID_PARAM constant
(3): We capture the groupName via @PathVariable
(4): We capture the userId via @RequestParam
(5): We add the user to the group via the groupService
(6): We return the GroupTableComponent.render which will render the group table with the new user added
Next, we create a AddUserComponent in the de.tschuehly.easy.spring.auth.group.management.table.user package:
AddUserComponent.java
@ViewComponent
public class AddUserComponent {
private final UserService userService;
public AddUserComponent(UserService userService) { // (1)
this.userService = userService;
}
public record AddUserContext(String groupName, List<EasyUser> easyUserList)
implements ViewContext{}
public ViewContext render(String groupName){ // (2)
return new AddUserContext(groupName,userService.findAll()); // (3)
}
}
(1): We autowire the UserService
(2): The render method has a groupName parameter
(3): We pass the groupName and the userService.findAll to the AddUserContext.
Now we create a AddUserComponent.jte template in the same package as the AddUserComponent.java
(1): We create a <form> element add an hx-post attribute that targets the POST_ADD_USER endpoint and inserts the groupName in the ViewContext into the Endpoint URI using the HtmxUtil
(2): We target the GROUP_TABLE_ID and swap the outerHTML of the target element.
(3): We create a <select> and use the @for loop syntax to create an option element for each user.
As you can see in contrast to the rerender method of the UserRowComponent here the htmx logic is in the template.
We now autowire the AddUserComponent and create a GET_SELECT_USER endpoint in the GroupController
GroupController.java
public final static String GET_SELECT_USER = "/group/{groupName}/select-user";
@GetMapping(GET_SELECT_USER)
public ViewContext selectUser(@PathVariable String groupName) {
return addUserComponent.render(groupName);
}
Back to the GroupTableComponent.jte we add a static import to GET_SELECT_USER and HtmxUtiland add a new <td> in the @for loop.
We can click on the plus and see the selector to add a User to the group. When clicking on Add User to group the table is rerendered with the updated value.
Lab-3 Checkpoint 2
If you are stuck you can resume at this checkpoint with: