diff --git a/ccm-core/src/main/java/org/libreccm/ui/admin/usersgroupsroles/UserDetailsModel.java b/ccm-core/src/main/java/org/libreccm/ui/admin/usersgroupsroles/UserDetailsModel.java index 744eea92b..2dcd27e1b 100644 --- a/ccm-core/src/main/java/org/libreccm/ui/admin/usersgroupsroles/UserDetailsModel.java +++ b/ccm-core/src/main/java/org/libreccm/ui/admin/usersgroupsroles/UserDetailsModel.java @@ -19,7 +19,9 @@ package org.libreccm.ui.admin.usersgroupsroles; import org.libreccm.core.EmailAddress; +import org.libreccm.security.Group; import org.libreccm.security.GroupMembership; +import org.libreccm.security.GroupRepository; import org.libreccm.security.RoleMembership; import org.libreccm.security.User; import org.libreccm.ui.Message; @@ -31,6 +33,7 @@ import java.util.Objects; import java.util.stream.Collectors; import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; import javax.inject.Named; import javax.transaction.Transactional; @@ -41,6 +44,9 @@ import javax.transaction.Transactional; @RequestScoped @Named("UserDetailsModel") public class UserDetailsModel { + + @Inject + private GroupRepository groupRepository; private long userId; @@ -148,6 +154,14 @@ public class UserDetailsModel { public List getGroupMemberships() { return Collections.unmodifiableList(groupMemberships); } + + public List getUserGroupsFormEntries() { + return groupRepository + .findAll() + .stream() + .map(this::buildUserGroupsFormEntry) + .collect(Collectors.toList()); + } public List getRoles() { return Collections.unmodifiableList(roles); @@ -156,4 +170,19 @@ public class UserDetailsModel { public boolean isNewUser() { 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; + } } diff --git a/ccm-core/src/main/java/org/libreccm/ui/admin/usersgroupsroles/UserFormController.java b/ccm-core/src/main/java/org/libreccm/ui/admin/usersgroupsroles/UserFormController.java index 1cda75d47..2c713a3ed 100644 --- a/ccm-core/src/main/java/org/libreccm/ui/admin/usersgroupsroles/UserFormController.java +++ b/ccm-core/src/main/java/org/libreccm/ui/admin/usersgroupsroles/UserFormController.java @@ -36,6 +36,7 @@ import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.mvc.Controller; import javax.mvc.Models; +import javax.mvc.MvcContext; import javax.mvc.binding.BindingResult; import javax.mvc.binding.MvcBinding; import javax.transaction.Transactional; @@ -66,6 +67,9 @@ public class UserFormController { @Inject private Models models; + + @Inject + private MvcContext mvc; @Inject private UserManager userManager; @@ -201,7 +205,28 @@ public class UserFormController { )); 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 + ) + ) + ); } } diff --git a/ccm-core/src/main/java/org/libreccm/ui/admin/usersgroupsroles/UserGroupsFormEntry.java b/ccm-core/src/main/java/org/libreccm/ui/admin/usersgroupsroles/UserGroupsFormEntry.java new file mode 100644 index 000000000..06eb026f5 --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/ui/admin/usersgroupsroles/UserGroupsFormEntry.java @@ -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 Jens Pelzetter + */ +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; + } + + +} diff --git a/ccm-core/src/main/resources/WEB-INF/views/org/libreccm/ui/admin/users-groups-roles/user-details.xhtml b/ccm-core/src/main/resources/WEB-INF/views/org/libreccm/ui/admin/users-groups-roles/user-details.xhtml index a130e9b64..d42af0124 100644 --- a/ccm-core/src/main/resources/WEB-INF/views/org/libreccm/ui/admin/users-groups-roles/user-details.xhtml +++ b/ccm-core/src/main/resources/WEB-INF/views/org/libreccm/ui/admin/users-groups-roles/user-details.xhtml @@ -1,4 +1,4 @@ - +]> - + - - -

+
+

#{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.heading']}

- - - - - #{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.add']} - - - - - - - - - - + + + + +
- #{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.cols.address']} - - #{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.cols.boucing']} - - #{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.cols.verified']} - - #{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.cols.actions']} -
+ - - - - - + + + + - - -
- #{address.address} - - - - #{AdminMessages['usergroupsroles.users.user_details.email_address.bouncing.yes']} - - - #{AdminMessages['usergroupsroles.users.user_details.email_address.bouncing.no']} - - - - - - #{AdminMessages['usergroupsroles.users.user_details.email_address.verified.yes']} - - - #{AdminMessages['usergroupsroles.users.user_details.email_address.verified.no']} - - - - - - - - - #{AdminMessages['usergroupsroles.users.user_details.email_addresses.edit']} - - - - - - + #{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.cols.address']} + + #{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.cols.boucing']} + + #{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.cols.verified']} + + #{AdminMessages['usergroupsroles.users.user_details.additional_email_addresses.cols.actions']} +
- + + + + #{address.address} + + + + + #{AdminMessages['usergroupsroles.users.user_details.email_address.bouncing.yes']} + + + #{AdminMessages['usergroupsroles.users.user_details.email_address.bouncing.no']} + + + + + + + #{AdminMessages['usergroupsroles.users.user_details.email_address.verified.yes']} + + + #{AdminMessages['usergroupsroles.users.user_details.email_address.verified.no']} + + + + + + + + + + #{AdminMessages['usergroupsroles.users.user_details.email_addresses.edit']} + + + + + + + + + + + + + + + + - -

+
+

#{AdminMessages['usergroupsroles.users.user_details.groups.heading']}

- + + +
+ + + + + + + + - -

+
+

#{AdminMessages['usergroupsroles.users.user_details.roles.heading']}

- + +
+ + + + + + + + + + - diff --git a/ccm-core/src/main/resources/WEB-INF/views/org/libreccm/ui/admin/users-groups-roles/users.xhtml b/ccm-core/src/main/resources/WEB-INF/views/org/libreccm/ui/admin/users-groups-roles/users.xhtml index 8b124acd1..c23f0e7b5 100644 --- a/ccm-core/src/main/resources/WEB-INF/views/org/libreccm/ui/admin/users-groups-roles/users.xhtml +++ b/ccm-core/src/main/resources/WEB-INF/views/org/libreccm/ui/admin/users-groups-roles/users.xhtml @@ -1,4 +1,4 @@ - +]> initFilterables()); \ No newline at end of file diff --git a/ccm-core/src/main/typescript/ccm-admin/filterable-list.ts b/ccm-core/src/main/typescript/ccm-admin/filterable-list.ts new file mode 100644 index 000000000..08af45ca1 --- /dev/null +++ b/ccm-core/src/main/typescript/ccm-admin/filterable-list.ts @@ -0,0 +1,135 @@ +export function initFilterables(): void { + document + .querySelectorAll("*[data-filter]") + .forEach(filterable => initFilterable(filterable)); +} + +function buildList( + filterable: Element, + options: Record[], + 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, + 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[], + 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[] { + 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[] = 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) { + 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; + } +}