IMprovements for user management UI
parent
1ecaac58a5
commit
444f59f760
|
|
@ -19,7 +19,9 @@
|
||||||
package org.libreccm.ui.admin.usersgroupsroles;
|
package org.libreccm.ui.admin.usersgroupsroles;
|
||||||
|
|
||||||
import org.libreccm.core.EmailAddress;
|
import org.libreccm.core.EmailAddress;
|
||||||
|
import org.libreccm.security.Group;
|
||||||
import org.libreccm.security.GroupMembership;
|
import org.libreccm.security.GroupMembership;
|
||||||
|
import org.libreccm.security.GroupRepository;
|
||||||
import org.libreccm.security.RoleMembership;
|
import org.libreccm.security.RoleMembership;
|
||||||
import org.libreccm.security.User;
|
import org.libreccm.security.User;
|
||||||
import org.libreccm.ui.Message;
|
import org.libreccm.ui.Message;
|
||||||
|
|
@ -31,6 +33,7 @@ import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.enterprise.context.RequestScoped;
|
import javax.enterprise.context.RequestScoped;
|
||||||
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.transaction.Transactional;
|
import javax.transaction.Transactional;
|
||||||
|
|
||||||
|
|
@ -42,6 +45,9 @@ import javax.transaction.Transactional;
|
||||||
@Named("UserDetailsModel")
|
@Named("UserDetailsModel")
|
||||||
public class UserDetailsModel {
|
public class UserDetailsModel {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private GroupRepository groupRepository;
|
||||||
|
|
||||||
private long userId;
|
private long userId;
|
||||||
|
|
||||||
private String uuid;
|
private String uuid;
|
||||||
|
|
@ -149,6 +155,14 @@ public class UserDetailsModel {
|
||||||
return Collections.unmodifiableList(groupMemberships);
|
return Collections.unmodifiableList(groupMemberships);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<UserGroupsFormEntry> getUserGroupsFormEntries() {
|
||||||
|
return groupRepository
|
||||||
|
.findAll()
|
||||||
|
.stream()
|
||||||
|
.map(this::buildUserGroupsFormEntry)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
public List<PartyRoleMembership> getRoles() {
|
public List<PartyRoleMembership> getRoles() {
|
||||||
return Collections.unmodifiableList(roles);
|
return Collections.unmodifiableList(roles);
|
||||||
}
|
}
|
||||||
|
|
@ -156,4 +170,19 @@ public class UserDetailsModel {
|
||||||
public boolean isNewUser() {
|
public boolean isNewUser() {
|
||||||
return userId == 0;
|
return userId == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private UserGroupsFormEntry buildUserGroupsFormEntry(final Group group) {
|
||||||
|
final UserGroupsFormEntry entry = new UserGroupsFormEntry();
|
||||||
|
entry.setGroupId(group.getPartyId());
|
||||||
|
entry.setGroupName(group.getName());
|
||||||
|
entry.setGroupUuid(group.getUuid());
|
||||||
|
entry.setMember(
|
||||||
|
groupMemberships
|
||||||
|
.stream()
|
||||||
|
.anyMatch(
|
||||||
|
membership -> membership.getGroupUuid().equals(group.getUuid())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import javax.enterprise.context.RequestScoped;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.mvc.Controller;
|
import javax.mvc.Controller;
|
||||||
import javax.mvc.Models;
|
import javax.mvc.Models;
|
||||||
|
import javax.mvc.MvcContext;
|
||||||
import javax.mvc.binding.BindingResult;
|
import javax.mvc.binding.BindingResult;
|
||||||
import javax.mvc.binding.MvcBinding;
|
import javax.mvc.binding.MvcBinding;
|
||||||
import javax.transaction.Transactional;
|
import javax.transaction.Transactional;
|
||||||
|
|
@ -67,6 +68,9 @@ public class UserFormController {
|
||||||
@Inject
|
@Inject
|
||||||
private Models models;
|
private Models models;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private MvcContext mvc;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private UserManager userManager;
|
private UserManager userManager;
|
||||||
|
|
||||||
|
|
@ -201,7 +205,28 @@ public class UserFormController {
|
||||||
));
|
));
|
||||||
return "org/libreccm/ui/admin/users-groups-roles/user-form.xhtml";
|
return "org/libreccm/ui/admin/users-groups-roles/user-form.xhtml";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("{userIdentifier}/groups")
|
||||||
|
@AuthorizationRequired
|
||||||
|
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
|
||||||
|
@Transactional(Transactional.TxType.REQUIRED)
|
||||||
|
public String updateGroupMemberships(
|
||||||
|
@PathParam("userIdentifier") final String userIdentifierParam,
|
||||||
|
@FormParam("userGroups") final String[] userGroups
|
||||||
|
) {
|
||||||
|
// ToDo
|
||||||
|
return String.format(
|
||||||
|
"redirect:%s",
|
||||||
|
mvc.uri(
|
||||||
|
String.format(
|
||||||
|
"UsersController#getUserDetails",
|
||||||
|
"{userIdentifier: %s}",
|
||||||
|
userIdentifierParam
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 LibreCCM Foundation.
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
* MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.libreccm.ui.admin.usersgroupsroles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
|
||||||
|
*/
|
||||||
|
public class UserGroupsFormEntry {
|
||||||
|
|
||||||
|
private long groupId;
|
||||||
|
|
||||||
|
private String groupUuid;
|
||||||
|
|
||||||
|
private String groupName;
|
||||||
|
|
||||||
|
private boolean member;
|
||||||
|
|
||||||
|
public long getGroupId() {
|
||||||
|
return groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupId(final long groupId) {
|
||||||
|
this.groupId = groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupUuid() {
|
||||||
|
return groupUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupUuid(final String groupUuid) {
|
||||||
|
this.groupUuid = groupUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupName() {
|
||||||
|
return groupName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupName(final String groupName) {
|
||||||
|
this.groupName = groupName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMember() {
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMember(final boolean member) {
|
||||||
|
this.member = member;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html [<!ENTITY times '×'>]>
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
|
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
|
||||||
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||||
|
|
@ -118,7 +118,7 @@
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
<a class="btn btn-info" href="#">
|
<a class="btn btn-primary" href="#">
|
||||||
<svg class="bi"
|
<svg class="bi"
|
||||||
width="1em"
|
width="1em"
|
||||||
height="1em"
|
height="1em"
|
||||||
|
|
@ -130,211 +130,264 @@
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<div class="d-flex">
|
||||||
<c:if test="#{UserDetailsModel.emailAddresses.size() > 0}">
|
<h2 class="mr-2">
|
||||||
<h2>
|
|
||||||
#{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.heading']}
|
#{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.heading']}
|
||||||
</h2>
|
</h2>
|
||||||
<a class="btn btn-secondary" href="#">
|
<div>
|
||||||
<svg class="bi"
|
<a class="btn btn-primary" href="#">
|
||||||
width="1em"
|
<svg class="bi"
|
||||||
height="1em"
|
width="1em"
|
||||||
fill="currentColor">
|
height="1em"
|
||||||
<use xlink:href="#{request.contextPath}/assets/bootstrap/bootstrap-icons.svg#plus-circle" />
|
fill="currentColor">
|
||||||
</svg>
|
<use xlink:href="#{request.contextPath}/assets/bootstrap/bootstrap-icons.svg#plus-circle" />
|
||||||
<span>#{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.add']}</span>
|
</svg>
|
||||||
</a>
|
<span>#{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.add']}</span>
|
||||||
<table class="table table-hover">
|
</a>
|
||||||
<thead class="thead-light">
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<th>
|
<c:choose>
|
||||||
#{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.cols.address']}
|
<c:when test="#{UserDetailsModel.emailAddresses.size() > 0}">
|
||||||
</th>
|
<table class="table table-hover">
|
||||||
<th class="text-center">
|
<thead class="thead-light">
|
||||||
#{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.cols.boucing']}
|
|
||||||
</th>
|
|
||||||
<th class="text-center">
|
|
||||||
#{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.cols.verified']}
|
|
||||||
</th>
|
|
||||||
<th class="text-center" colspan="2">
|
|
||||||
#{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.cols.actions']}
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
<c:forEach items="#{UserDetailsModel.emailAddresses}"
|
|
||||||
var="address"
|
|
||||||
varStatus="status">
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<th>
|
||||||
#{address.address}
|
#{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.cols.address']}
|
||||||
</td>
|
</th>
|
||||||
<td>
|
<th class="text-center">
|
||||||
<c:choose>
|
#{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.cols.boucing']}
|
||||||
<c:when test="#{address.bouncing}">
|
</th>
|
||||||
#{AdminMessages['usergroupsroles.users.user_details.email_address.bouncing.yes']}
|
<th class="text-center">
|
||||||
</c:when>
|
#{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.cols.verified']}
|
||||||
<c:otherwise>
|
</th>
|
||||||
#{AdminMessages['usergroupsroles.users.user_details.email_address.bouncing.no']}
|
<th class="text-center" colspan="2">
|
||||||
</c:otherwise>
|
#{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.cols.actions']}
|
||||||
</c:choose>
|
</th>
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<c:choose>
|
|
||||||
<c:when test="#{address.verified}">
|
|
||||||
#{AdminMessages['usergroupsroles.users.user_details.email_address.verified.yes']}
|
|
||||||
</c:when>
|
|
||||||
<c:otherwise>
|
|
||||||
#{AdminMessages['usergroupsroles.users.user_details.email_address.verified.no']}
|
|
||||||
</c:otherwise>
|
|
||||||
</c:choose>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a class="btn btn-info" href="#">
|
|
||||||
<svg class="bi"
|
|
||||||
width="1em"
|
|
||||||
height="1em"
|
|
||||||
fill="currentColor">
|
|
||||||
<use xlink:href="#{request.contextPath}/assets/bootstrap/bootstrap-icons.svg#pen" />
|
|
||||||
</svg>
|
|
||||||
<span>
|
|
||||||
#{AdminMessages['usergroupsroles.users.user_details.email_addresses.edit']}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button class="btn btn-danger"
|
|
||||||
data-toggle="modal"
|
|
||||||
data-target="#confirm-remove-#{status.index}">
|
|
||||||
<svg class="bi"
|
|
||||||
width="1em"
|
|
||||||
height="1em"
|
|
||||||
fill="currentColor">
|
|
||||||
<use xlink:href="#{request.contextPath}/assets/bootstrap/bootstrap-icons.svg#x-circle" />
|
|
||||||
</svg>
|
|
||||||
<span>
|
|
||||||
#{AdminMessages['usergroupsroles.users.user_details.email_addresses.remove']}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<div class="modal"
|
|
||||||
id="confirm-remove-#{status.index}"
|
|
||||||
tabindex="-1">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<form action="#{mvc.uri('UsersController#removeEmailAddress', { 'userIdentifier': user.name, 'emailId': status.index })}"
|
|
||||||
class="modal-content"
|
|
||||||
method="post">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h3 class="modal-title">
|
|
||||||
#{AdminMessages['usergroupsroles.users.user_details.email_addresses.remove.confirm.title']}
|
|
||||||
</h3>
|
|
||||||
<button aria-label="#{AdminMessages['usergroupsroles.users.user_details.email_addresses.remove.confirm.cancel']}"
|
|
||||||
class="close"
|
|
||||||
data-dismiss="modal"
|
|
||||||
type="button">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
#{AdminMessages.getMessage('usergroupsroles.users.user_details.email_addresses.remove.confirm.message', [address.address])}
|
|
||||||
<input name="confirmed"
|
|
||||||
type="hidden"
|
|
||||||
value="true" />
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="btn btn-secondary"
|
|
||||||
data-dismiss="modal"
|
|
||||||
type="button">
|
|
||||||
#{'usergroupsroles.users.user_details.email_addresses.remove.confirm.cancel'}
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-danger"
|
|
||||||
data-dismiss="modal"
|
|
||||||
type="submit">
|
|
||||||
#{'usergroupsroles.users.user_details.email_addresses.remove.confirm.yes'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</c:forEach>
|
<c:forEach items="#{UserDetailsModel.emailAddresses}"
|
||||||
</thead>
|
var="address"
|
||||||
</table>
|
varStatus="status">
|
||||||
</c:if>
|
<tr>
|
||||||
|
<td>
|
||||||
|
#{address.address}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<c:choose>
|
||||||
|
<c:when test="#{address.bouncing}">
|
||||||
|
#{AdminMessages['usergroupsroles.users.user_details.email_address.bouncing.yes']}
|
||||||
|
</c:when>
|
||||||
|
<c:otherwise>
|
||||||
|
#{AdminMessages['usergroupsroles.users.user_details.email_address.bouncing.no']}
|
||||||
|
</c:otherwise>
|
||||||
|
</c:choose>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<c:choose>
|
||||||
|
<c:when test="#{address.verified}">
|
||||||
|
#{AdminMessages['usergroupsroles.users.user_details.email_address.verified.yes']}
|
||||||
|
</c:when>
|
||||||
|
<c:otherwise>
|
||||||
|
#{AdminMessages['usergroupsroles.users.user_details.email_address.verified.no']}
|
||||||
|
</c:otherwise>
|
||||||
|
</c:choose>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a class="btn btn-info" href="#">
|
||||||
|
<svg class="bi"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
fill="currentColor">
|
||||||
|
<use xlink:href="#{request.contextPath}/assets/bootstrap/bootstrap-icons.svg#pen" />
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
|
#{AdminMessages['usergroupsroles.users.user_details.email_addresses.edit']}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-danger"
|
||||||
|
data-toggle="modal"
|
||||||
|
data-target="#confirm-remove-#{status.index}">
|
||||||
|
<svg class="bi"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
fill="currentColor">
|
||||||
|
<use xlink:href="#{request.contextPath}/assets/bootstrap/bootstrap-icons.svg#x-circle" />
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
|
#{AdminMessages['usergroupsroles.users.user_details.email_addresses.remove']}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<div class="modal"
|
||||||
|
id="confirm-remove-#{status.index}"
|
||||||
|
tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form action="#{mvc.uri('UsersController#removeEmailAddress', { 'userIdentifier': user.name, 'emailId': status.index })}"
|
||||||
|
class="modal-content"
|
||||||
|
method="post">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3 class="modal-title">
|
||||||
|
#{AdminMessages['usergroupsroles.users.user_details.email_addresses.remove.confirm.title']}
|
||||||
|
</h3>
|
||||||
|
<button aria-label="#{AdminMessages['usergroupsroles.users.user_details.email_addresses.remove.confirm.cancel']}"
|
||||||
|
class="close"
|
||||||
|
data-dismiss="modal"
|
||||||
|
type="button">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
#{AdminMessages.getMessage('usergroupsroles.users.user_details.email_addresses.remove.confirm.message', [address.address])}
|
||||||
|
<input name="confirmed"
|
||||||
|
type="hidden"
|
||||||
|
value="true" />
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-secondary"
|
||||||
|
data-dismiss="modal"
|
||||||
|
type="button">
|
||||||
|
#{'usergroupsroles.users.user_details.email_addresses.remove.confirm.cancel'}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-danger"
|
||||||
|
data-dismiss="modal"
|
||||||
|
type="submit">
|
||||||
|
#{'usergroupsroles.users.user_details.email_addresses.remove.confirm.yes'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</c:forEach>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
</c:when>
|
||||||
|
<c:otherwise>
|
||||||
|
<div class="alert alert-info" role="alert">
|
||||||
|
#{AdminMessages['usergroupsroles.users.user_details.email_addresses.none']}
|
||||||
|
</div>
|
||||||
|
</c:otherwise>
|
||||||
|
</c:choose>
|
||||||
|
|
||||||
<c:if test="#{UserDetailsModel.groupMemberships.size() > 0}">
|
<div class="d-flex mb-1">
|
||||||
<h2>
|
<h2 class="mr-2">
|
||||||
#{AdminMessages['usergroupsroles.users.user_details.groups.heading']}
|
#{AdminMessages['usergroupsroles.users.user_details.groups.heading']}
|
||||||
</h2>
|
</h2>
|
||||||
<a class="btn btn-secondary" href="#">
|
<button class="btn btn-primary"
|
||||||
|
data-toggle="modal"
|
||||||
|
data-target="#user-groups-dialog"
|
||||||
|
type="button">
|
||||||
<svg class="bi"
|
<svg class="bi"
|
||||||
width="1em"
|
width="1em"
|
||||||
height="1em"
|
height="1em"
|
||||||
fill="currentColor">
|
fill="currentColor">
|
||||||
<use xlink:href="#{request.contextPath}/assets/bootstrap/bootstrap-icons.svg#plus-circle" />
|
<use xlink:href="#{request.contextPath}/assets/bootstrap/bootstrap-icons.svg#pen" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>#{AdminMessages['usergroupsroles.users.user_details.groups.add']}</span>
|
<span>#{AdminMessages['usergroupsroles.users.user_details.groups.edit']}</span>
|
||||||
</a>
|
</button>
|
||||||
<ul class="list-group mt-1">
|
<div aria-labelledby="user-groups-dialog-title"
|
||||||
<c:forEach items="#{UserDetailsModel.groupMemberships}"
|
aria-hidden="true"
|
||||||
var="group">
|
class="modal fade"
|
||||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
data-backdrop="static"
|
||||||
<a href="#">
|
id="user-groups-dialog"
|
||||||
#{group.groupName}
|
tabindex="-1">
|
||||||
</a>
|
<div class="modal-dialog">
|
||||||
<a class="btn btn-danger" href="#">
|
<form action=""
|
||||||
<svg class="bi"
|
class="modal-content"
|
||||||
width="1em"
|
method="post">
|
||||||
height="1em"
|
<div class="modal-header">
|
||||||
fill="currentColor">
|
<h3 class="modal-title"
|
||||||
<use xlink:href="#{request.contextPath}/assets/bootstrap/bootstrap-icons.svg#x-circle" />
|
id="user-groups-dialog-title">
|
||||||
</svg>
|
#{AdminMessages['usergroupsroles.users.user_details.groups.dialog.title']}
|
||||||
<span>
|
</h3>
|
||||||
#{AdminMessages['usergroupsroles.users.user_details.groups.remove']}
|
<button aria-label="#{AdminMessages['usergroupsroles.users.user_details.groups.dialog.close']}"
|
||||||
</span>
|
class="close"
|
||||||
</a>
|
data-dismiss="modal"
|
||||||
</li>
|
type="button">
|
||||||
</c:forEach>
|
<span aria-hidden="true">×</span>
|
||||||
</ul>
|
</button>
|
||||||
</c:if>
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<c:forEach items="#{UserDetailsModel.userGroupsFormEntries}"
|
||||||
|
var="entry">
|
||||||
|
<input checked="#{entry.member ? 'checked' : 'false'}"
|
||||||
|
id="group-#{entry.groupName}"
|
||||||
|
name="userGroups[]"
|
||||||
|
value="#{entry.groupName}"
|
||||||
|
type="checkbox" />
|
||||||
|
</c:forEach>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-secondary"
|
||||||
|
data-dismiss="modal"
|
||||||
|
type="button" >
|
||||||
|
#{AdminMessages['usergroupsroles.users.user_details.groups.dialog.close']}
|
||||||
|
</button>
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
#{AdminMessages['usergroupsroles.users.user_details.groups.dialog.save']}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<c:choose>
|
||||||
|
<c:when test="#{UserDetailsModel.groupMemberships.size() > 0}">
|
||||||
|
<ul class="list-group mt-1">
|
||||||
|
<c:forEach items="#{UserDetailsModel.groupMemberships}">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<a href="#">
|
||||||
|
#{group.groupName}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</c:forEach>
|
||||||
|
</ul>
|
||||||
|
</c:when>
|
||||||
|
<c:otherwise>
|
||||||
|
<div class="alert alert-info" role="alert">
|
||||||
|
#{AdminMessages['usergroupsroles.users.user_details.groups.none']}
|
||||||
|
</div>
|
||||||
|
</c:otherwise>
|
||||||
|
</c:choose>
|
||||||
|
|
||||||
<c:if test="#{UserDetailsModel.roles.size() > 0}">
|
<div class="d-flex mb-1">
|
||||||
<h2>
|
<h2 class="mr-2">
|
||||||
#{AdminMessages['usergroupsroles.users.user_details.roles.heading']}
|
#{AdminMessages['usergroupsroles.users.user_details.roles.heading']}
|
||||||
</h2>
|
</h2>
|
||||||
<a class="btn btn-secondary" href="#">
|
<button class="btn btn-primary" type="button">
|
||||||
<svg class="bi"
|
<svg class="bi"
|
||||||
width="1em"
|
width="1em"
|
||||||
height="1em"
|
height="1em"
|
||||||
fill="currentColor">
|
fill="currentColor">
|
||||||
<use xlink:href="#{request.contextPath}/assets/bootstrap/bootstrap-icons.svg#plus-circle" />
|
<use xlink:href="#{request.contextPath}/assets/bootstrap/bootstrap-icons.svg#pen" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>#{AdminMessages['usergroupsroles.users.user_details.roles.add']}</span>
|
<span>#{AdminMessages['usergroupsroles.users.user_details.roles.edit']}</span>
|
||||||
</a>
|
</button>
|
||||||
<ul class="list-group mt-1">
|
</div>
|
||||||
<c:forEach items="#{UserDetailsModel.roles}"
|
|
||||||
var="role">
|
<c:choose>
|
||||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
<c:when test="#{UserDetailsModel.roles.size() > 0}">
|
||||||
<a href="#">
|
<ul class="list-group mt-1 mb-4">
|
||||||
#{role.roleName}
|
<c:forEach items="#{UserDetailsModel.roles}" var="role">
|
||||||
</a>
|
<li class="list-group-item">
|
||||||
<a class="btn btn-danger" href="#">
|
<a href="#">
|
||||||
<svg class="bi"
|
#{role.roleName}
|
||||||
width="1em"
|
</a>
|
||||||
height="1em"
|
</li>
|
||||||
fill="currentColor">
|
</c:forEach>
|
||||||
<use xlink:href="#{request.contextPath}/assets/bootstrap/bootstrap-icons.svg#x-circle" />
|
</ul>
|
||||||
</svg>
|
</c:when>
|
||||||
<span>
|
<c:otherwise>
|
||||||
#{AdminMessages['usergroupsroles.users.user_details.roles.remove']}
|
<div class="alert alert-info" role="alert">
|
||||||
</span>
|
#{AdminMessages['usergroupsroles.users.user_details.roles.none']}
|
||||||
</a>
|
</div>
|
||||||
</li>
|
</c:otherwise>
|
||||||
</c:forEach>
|
</c:choose>
|
||||||
</ul>
|
|
||||||
</c:if>
|
|
||||||
</ui:define>
|
</ui:define>
|
||||||
|
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html [<!ENTITY times '×'>]>
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
|
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
|
||||||
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||||
|
|
|
||||||
|
|
@ -115,3 +115,12 @@ usergroupsroles.users.user_details.email_addresses.remove.confirm.title=Confirm
|
||||||
usergroupsroles.users.user_details.email_addresses.remove.confirm.cancel=Cancel
|
usergroupsroles.users.user_details.email_addresses.remove.confirm.cancel=Cancel
|
||||||
usergroupsroles.users.user_details.email_addresses.remove.confirm.message=Are you sure to remove email address {0}?
|
usergroupsroles.users.user_details.email_addresses.remove.confirm.message=Are you sure to remove email address {0}?
|
||||||
usergroupsroles.users.user_details.email_addresses.remove.confirm.yes=Remove Email Address
|
usergroupsroles.users.user_details.email_addresses.remove.confirm.yes=Remove Email Address
|
||||||
|
usergroupsroles.users.user_details.groups.none=This user is not a member of any group
|
||||||
|
usergroupsroles.users.user_details.groups.edit=Edit
|
||||||
|
usergroupsroles.users.user_details.roles.edit=Edit
|
||||||
|
usergroupsroles.users.user_details.roles.none=No roles assigned to this user
|
||||||
|
usergroupsroles.users.user_details.email_addresses.none=This user has no additional email addresses
|
||||||
|
usergroupsroles.users.user_details.additional_email_addresses.add=Add email address
|
||||||
|
usergroupsroles.users.user_details.groups.dialog.title=Edit groupmemberships
|
||||||
|
usergroupsroles.users.user_details.groups.dialog.close=Cancel
|
||||||
|
usergroupsroles.users.user_details.groups.dialog.save=Save
|
||||||
|
|
|
||||||
|
|
@ -115,3 +115,12 @@ usergroupsroles.users.user_details.email_addresses.remove.confirm.title=Entferne
|
||||||
usergroupsroles.users.user_details.email_addresses.remove.confirm.cancel=Abbrechen
|
usergroupsroles.users.user_details.email_addresses.remove.confirm.cancel=Abbrechen
|
||||||
usergroupsroles.users.user_details.email_addresses.remove.confirm.message=Sind Sie sicher, dass Sie die E-Mail-Addresse {0} entfernen wollen?
|
usergroupsroles.users.user_details.email_addresses.remove.confirm.message=Sind Sie sicher, dass Sie die E-Mail-Addresse {0} entfernen wollen?
|
||||||
usergroupsroles.users.user_details.email_addresses.remove.confirm.yes=E-Mail-Addresse entfernen
|
usergroupsroles.users.user_details.email_addresses.remove.confirm.yes=E-Mail-Addresse entfernen
|
||||||
|
usergroupsroles.users.user_details.groups.none=Diese(r) Benutzer*in ist nicht Mitglied einer Gruppe
|
||||||
|
usergroupsroles.users.user_details.groups.edit=Bearbeiten
|
||||||
|
usergroupsroles.users.user_details.roles.edit=Bearbeiten
|
||||||
|
usergroupsroles.users.user_details.roles.none=Dieser(m) Benutzer*in sind keine Rollen zugeordnet
|
||||||
|
usergroupsroles.users.user_details.email_addresses.none=Diese(r) Benutzer*in hat keine weiteren E-Mail-Addressen
|
||||||
|
usergroupsroles.users.user_details.additional_email_addresses.add=E-Mail-Addresse hinzuf\u00fcgen
|
||||||
|
usergroupsroles.users.user_details.groups.dialog.title=Gruppenmitgliedschaften bearbeiten
|
||||||
|
usergroupsroles.users.user_details.groups.dialog.close=Abbrechen
|
||||||
|
usergroupsroles.users.user_details.groups.dialog.save=Anwenden
|
||||||
|
|
|
||||||
|
|
@ -1 +1,5 @@
|
||||||
import "bootstrap";
|
import "bootstrap";
|
||||||
|
|
||||||
|
import { initFilterables } from "./filterable-list";
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentReady", event => initFilterables());
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
export function initFilterables(): void {
|
||||||
|
document
|
||||||
|
.querySelectorAll("*[data-filter]")
|
||||||
|
.forEach(filterable => initFilterable(filterable));
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildList(
|
||||||
|
filterable: Element,
|
||||||
|
options: Record<string, string>[],
|
||||||
|
template: HTMLTemplateElement,
|
||||||
|
filterInput: HTMLInputElement,
|
||||||
|
filterBy: string[]
|
||||||
|
) {
|
||||||
|
console.log("(Re-)Building list...");
|
||||||
|
console.dir(filterOptions);
|
||||||
|
filterable.innerHTML = "";
|
||||||
|
|
||||||
|
const filteredOptions = filterInput.value
|
||||||
|
? filterOptions(options, filterInput.value, filterBy)
|
||||||
|
: options;
|
||||||
|
|
||||||
|
for (const option of filteredOptions) {
|
||||||
|
const item = template.content.cloneNode(true);
|
||||||
|
replacePlaceholders(item, option);
|
||||||
|
filterable.appendChild(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterOption(
|
||||||
|
option: Record<string, string>,
|
||||||
|
filter: string,
|
||||||
|
filterBy: string[]
|
||||||
|
) {
|
||||||
|
let result: boolean = false;
|
||||||
|
|
||||||
|
for (const filterByProp of filterBy) {
|
||||||
|
result = result || option[filterByProp].indexOf(filter) !== -1;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterOptions(
|
||||||
|
options: Record<string, string>[],
|
||||||
|
filterValue: string,
|
||||||
|
filterBy: string[]
|
||||||
|
) {
|
||||||
|
return options.filter(option =>
|
||||||
|
filterOption(option, filterValue, filterBy)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilterBy(filterable): string[] {
|
||||||
|
const filterByValue: string = filterable.getAttribute("data-filter-by");
|
||||||
|
if (filterByValue) {
|
||||||
|
return filterByValue.split(",");
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilterInput(filterable): HTMLInputElement {
|
||||||
|
const filterInputId: string = filterable.getAttribute("data-filter");
|
||||||
|
return document.querySelector(`input${filterInputId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOptions(filterable: Element): Record<string, string>[] {
|
||||||
|
const attrValue: string = filterable.getAttribute("data-options");
|
||||||
|
|
||||||
|
if (attrValue.startsWith("#")) {
|
||||||
|
const dataScript: Element = document.querySelector(
|
||||||
|
`script${attrValue}`
|
||||||
|
);
|
||||||
|
return JSON.parse(dataScript.textContent);
|
||||||
|
} else {
|
||||||
|
return JSON.parse(attrValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTemplate(filterable: Element): HTMLTemplateElement {
|
||||||
|
const templateId: string = filterable.getAttribute("data-template");
|
||||||
|
return document.querySelector(templateId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initFilterable(filterable: Element): void {
|
||||||
|
const options: Record<string, string>[] = getOptions(filterable);
|
||||||
|
const template = getTemplate(filterable);
|
||||||
|
const filterInput = getFilterInput(filterable);
|
||||||
|
const filterBy = getFilterBy(filterable);
|
||||||
|
|
||||||
|
filterInput.addEventListener("keyup", event =>
|
||||||
|
buildList(filterable, options, template, filterInput, filterBy)
|
||||||
|
);
|
||||||
|
|
||||||
|
buildList(filterable, options, template, filterInput, filterBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
function replacePlaceholders(node: Node, data: Record<string, string>) {
|
||||||
|
switch (node.nodeType) {
|
||||||
|
case Node.ELEMENT_NODE: {
|
||||||
|
const childNodes = node.childNodes;
|
||||||
|
for (let i = 0; i < childNodes.length; i++) {
|
||||||
|
replacePlaceholders(childNodes[i], data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Node.TEXT_NODE: {
|
||||||
|
for (const key in data) {
|
||||||
|
console.log(`replacing ${key} with ${data[key]}`);
|
||||||
|
node.textContent = node.textContent.replace(
|
||||||
|
`{{${key}}}`,
|
||||||
|
data[key]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Node.CDATA_SECTION_NODE:
|
||||||
|
return;
|
||||||
|
case Node.DOCUMENT_NODE: {
|
||||||
|
const childNodes = node.childNodes;
|
||||||
|
for (let i = 0; i < childNodes.length; i++) {
|
||||||
|
replacePlaceholders(childNodes[i], data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Node.DOCUMENT_FRAGMENT_NODE: {
|
||||||
|
const childNodes = node.childNodes;
|
||||||
|
for (let i = 0; i < childNodes.length; i++) {
|
||||||
|
replacePlaceholders(childNodes[i], data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue