sci-types-project: Managing sponsors.

pull/1/head
Jens Pelzetter 2022-05-14 16:59:56 +02:00
parent aaaa9d8277
commit 236866d93e
9 changed files with 383 additions and 48 deletions

View File

@ -29,7 +29,8 @@ public class SciProjectAuthoringSteps implements MvcAuthoringSteps {
SciProjectDescriptionStepResources.class,
SciProjectDescriptionStepService.class,
SciProjectFundingStepResources.class,
SciProjectFundingStepService.class
SciProjectFundingStepService.class,
SciProjectFundingSponsors.class
);
}

View File

@ -0,0 +1,102 @@
package org.scientificcms.contenttypes.sciproject.ui;
import org.librecms.contentsection.ContentItem;
import org.librecms.contentsection.ContentItemRepository;
import org.librecms.contentsection.ContentSection;
import org.librecms.ui.contentsections.ContentSectionsUi;
import org.librecms.ui.contentsections.documents.MvcAuthoringSteps;
import org.scientificcms.contenttypes.sciproject.SciProject;
import org.scientificcms.contenttypes.sciproject.Sponsoring;
import org.scientificcms.contenttypes.sciproject.SponsoringRepository;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.transaction.Transactional;
import javax.ws.rs.Consumes;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Path(MvcAuthoringSteps.PATH_PREFIX + "sciproject-funding-sponsors")
public class SciProjectFundingSponsors {
@Inject
private SponsoringRepository sponsoringRepo;
@Inject
private ContentItemRepository itemRepo;
@Inject
private ContentSectionsUi sectionsUi;
@POST
@Path("/save-order")
@Consumes(MediaType.APPLICATION_JSON)
@Transactional(Transactional.TxType.REQUIRED)
public Response saveOrder(
@PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME)
final String documentPath,
final List<String> order
) {
final ContentSection contentSection = sectionsUi
.findContentSection(sectionIdentifier)
.orElseThrow(
() -> new NotFoundException(
String.format(
"No content identifed by %s found.",
sectionIdentifier
)
)
);
final ContentItem document = itemRepo
.findByPath(contentSection, documentPath)
.orElseThrow(
() -> new NotFoundException(
String.format(
"No document for path %s in section %s.",
documentPath,
contentSection.getLabel()
)
)
);
if (!(document instanceof SciProject)) {
throw new NotFoundException(
String.format(
"No SciProject for path %s in section %s.",
documentPath,
contentSection.getLabel()
)
);
}
final Map<Long, Long> orderMap = new HashMap<>();
for (int i = 0; i < order.size(); i++) {
orderMap.put(Long.parseLong(order.get(i)), (long) i);
}
final SciProject project = (SciProject) document;
for (final Sponsoring sponsoring : project.getSponsoring()) {
sponsoring.setOrder(orderMap.get(sponsoring.getSponsoringId()));
sponsoringRepo.save(sponsoring);
}
return Response.ok().build();
}
}

View File

@ -1,5 +1,7 @@
package org.scientificcms.contenttypes.sciproject.ui;
import org.libreccm.api.Identifier;
import org.libreccm.api.IdentifierParser;
import org.libreccm.l10n.GlobalizationHelper;
import org.libreccm.security.AuthorizationRequired;
import org.libreccm.ui.BaseUrl;
@ -74,6 +76,9 @@ public class SciProjectFundingStep extends AbstractMvcAuthoringStep {
@Context
private HttpServletRequest request;
@Inject
private IdentifierParser identifierParser;
@Inject
private ItemPermissionChecker itemPermissionChecker;
@ -496,7 +501,7 @@ public class SciProjectFundingStep extends AbstractMvcAuthoringStep {
@PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME)
final String documentPath,
@FormParam("sponsorUuid")
final String sponsorUuid,
final String sponsorUuidParam,
@FormParam("fundingCode")
final String fundingCode
) {
@ -509,11 +514,29 @@ public class SciProjectFundingStep extends AbstractMvcAuthoringStep {
}
if (itemPermissionChecker.canEditItem(getProject())) {
final Optional<Organization> result = assetRepo
.findByUuidAndType(sponsorUuid, Organization.class);
final Optional<Organization> result;
final Identifier identifier = identifierParser.parseIdentifier(
sponsorUuidParam
);
switch (identifier.getType()) {
case UUID:
result = assetRepo.findByUuidAndType(
identifier.getIdentifier(), Organization.class
);
break;
case ID:
result = assetRepo.findById(
Long.parseLong(identifier.getIdentifier()),
Organization.class
);
break;
default:
models.put("sponsorNotFound", sponsorUuidParam);
return showStep(sectionIdentifier, documentPath);
}
if (!result.isPresent()) {
models.put("sponsorNotFound", sponsorUuid);
models.put("sponsorNotFound", sponsorUuidParam);
return showStep(sectionIdentifier, documentPath);
}
@ -582,7 +605,7 @@ public class SciProjectFundingStep extends AbstractMvcAuthoringStep {
}
@POST
@Path("/sponsoring/remove")
@Path("/sponsoring/{sponsoringId}/remove")
@Transactional(Transactional.TxType.REQUIRED)
@AuthorizationRequired
public String removeSponsoring(
@ -590,7 +613,7 @@ public class SciProjectFundingStep extends AbstractMvcAuthoringStep {
final String sectionIdentifier,
@PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME)
final String documentPath,
@FormParam("sponsoringId")
@PathParam("sponsoringId")
final String sponsoringId
) {
try {

View File

@ -13,6 +13,25 @@
<ui:define name="authoringStep">
<h2>#{SciProjectMessageBundle['funding_step.header']}</h2>
<c:if test="#{sponsorNotFound != null}">
<div class="alert alert-warning">
#{SciProjectMessageBundle.getMessage('funding.errors.sponsor_not_found', [sponsorNotFound])}
</div>
</c:if>
<template id="sciproject-sponsoring-sort-error-general">
<div class="alert alert-danger mt-3" role="alert">
#{SciProjectMessageBundle['sponsors.sort.errors.general']}
</div>
</template>
<template id="sciproject-sponsoring-sort-error-save">
<div class="alert alert-danger mt-3" role="alert">
#{SciProjectMessageBundle['sponsors.sort.errors.save']}
</div>
</template>
<div id="messages"></div>
<librecms:cmsEditorVariants
addButtonLabel="#{SciProjectMessageBundle['funding.editor.add_variant']}"
addDialogLocaleSelectHelp="#{SciProjectMessageBundle['funding.editor.add.locale.help']}"
@ -72,7 +91,21 @@
name="fundingCode"
/>
</librecms:assetPicker>
<table>
<button class="btn btn-secondary sponsoring-save-order-button"
disabled="disabled"
type="button">
<span class="save-icon">
<bootstrap:svgIcon icon="save" />
</span>
<span class="save-spinner d-none">
<span aria-hidden="true"
class="spinner-border spinner-border-sm"
role="status"></span>
</span>
<span>#{SciProjectMessageBundle['sponsoring.order.save']}</span>
</button>
<table id="sciproject-sponsoring-table"
data-saveUrl="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@sciproject-funding-sponsors/save-order">
<thead>
<tr>
<th scope="col">#{SciProjectMessageBundle['sponsoring.cols.sponsor']}</th>
@ -84,27 +117,40 @@
<tbody>
<c:forEach items="#{SciProjectFundingSponsoring.sponsoring}"
var="sponsoring">
<tr id="#{sponsoring.sponsoringId}">
<td>#{sponsoring.sponsor}</td>
<tr class="sciproject-sponsor"
id="#{sponsoring.sponsoringId}"
data-id="#{sponsoring.sponsoringId}">
<td>
<c:if test="#{CmsSelectedDocumentModel.canEdit}">
<button class="btn btn-secondary cms-sort-handle mr-2"
type="button">
<bootstrap:svgIcon icon="arrows-move" />
<span class="sr-only">#{SciProjectMessageBundle['sponsoring.move.button']}</span>
</button>
</c:if>
#{sponsoring.sponsor}
</td>
<td>#{sponsoring.fundingCode}</td>
<td>
<button class="btn btn-secondary"
data-toggle="modal"
data-target="#sponsoring-edit-dialog"
data-target="#sponsoring-#{sponsoring.sponsoringId}-edit-dialog"
type="button">
<bootstrap:svgIcon icon="pen" />
<span>#{SciProjectMessageBundle['sponsoring.edit.label']}</span>
</button>
<div aria-hidden="true"
aria-labelledby="sponsoring-edit-dialog-title"
aria-labelledby="sponsoring-#{sponsoring.sponsoringId}-edit-dialog-title"
class="modal fade"
id="spoonsoring-edit-dialog"
id="sponsoring-#{sponsoring.sponsoringId}-edit-dialog"
tabindex="-1">
<div class="modal-dialog">
<form class="modal-content">
<form action="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@sciproject-funding/sponsoring/edit/#{sponsoring.sponsoringId}"
class="modal-content"
method="post">
<div class="modal-header">
<h4 class="modal-title"
id="sponsoring-edit-dialog-title">
id="sponsoring-#{sponsoring.sponsoringId}-edit-dialog-title">
#{SciProjectMessageBundle['sponsoring.edit.title']}
</h4>
<button
@ -115,14 +161,13 @@
<bootstrap:svgIcon icon="x"/>
</button>
</div>
</form>
</div>
<div class="modal-body">
<bootstrap:formGroupText
help="#{SciProjectMessageBundle['sponsoring.fundingcode.help']}"
inputId="fundingcode"
label="#{SciProjectMessageBundle['sponsoring.fundingcode.label']}"
name="fundingCode"
value="#{sponsoring.fundingCode}"
/>
</div>
<div class="modal-footer">
@ -136,15 +181,17 @@
#{SciProjectMessageBundle['sponsoring.edit.submit']}
</button>
</div>
</form>
</div>
</div>
</td>
<td>
<libreccm:deleteDialog
actionTarget="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@sciproject-funding/sponsoring/remove"
actionTarget="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@sciproject-funding/sponsoring/#{sponsoring.sponsoringId}/remove"
buttonText="#{SciProjectMessageBundle['sponsoring.remove.label']}"
cancelLabel="#{SciProjectMessageBundle['sponsoring.remove.cancel']}"
confirmLabel="#{SciProjectMessageBundle['sponsoring.remove.confirm']}"
dialogId="sponsoring-remove-dialog"
dialogId="sponsoring-#{sponsoring.sponsoringId}-remove-dialog"
dialogTitle="#{SciProjectMessageBundle['sponsoring.remove.title']}"
message="#{SciProjectMessageBundle.getMessage('sponsoring.remove.message', [sponsoring.sponsor])}"
/>
@ -154,10 +201,10 @@
</tbody>
</table>
<ui:define name="scripts">
<script src="#{request.contextPath}/assets/@content-sections/sciproject-sponsoring.js"></script>
</ui:define>
<ui:define name="scripts">
<script src="#{request.contextPath}/assets/@sciproject/sciproject-sponsoring.js"></script>
</ui:define>
</ui:composition>

View File

@ -181,3 +181,8 @@ contact.add.title=Add contact
memberships.add.title=Add member
description_step.errors.person_not_found=Selected person {0} not found.
description_step.errors.illegal_member_status_value=The status value {0} is not valid.
sponsoring.order.save=Save order
sponsoring.move.button=Drag to order sponsors
funding.errors.sponsor_not_found=The selected sponsor {0} was not found.
sponsors.sort.errors.general=Error sorting sponsors
sponsors.sort.errors.save=Failed to save order of sponsors.

View File

@ -181,3 +181,8 @@ contact.add.title=Kontakt hinzuf\u00fcgen
memberships.add.title=Mitglied hinzuf\u00fcgen
description_step.errors.person_not_found=Die ausgew\u00e4hlte Person {0} wurde nicht gefunden.
description_step.errors.illegal_member_status_value=Der Status {0} wird nicht unterst\u00fctzt.
sponsoring.order.save=Sortierung speichern
sponsoring.move.button=Zum sortieren Sponsoren ziehen
funding.errors.sponsor_not_found=Der ausgew\u00e4hlte Sponsor {0} wurde nicht gefunden.
sponsors.sort.errors.general=Fehler beim Sortieren der Sponsoren
sponsors.sort.errors.save=Speichern der Sortierung der Sponsoren fehlgeschlagen.

View File

@ -106,7 +106,7 @@ function saveOrder() {
spinner?.classList.toggle("d-none");
}
throw Error(
`Failed to save attachments order. Response status: ${response.status}, statusText: ${response.statusText}`
`Failed to save contacts order. Response status: ${response.status}, statusText: ${response.statusText}`
);
}
})
@ -122,7 +122,7 @@ function saveOrder() {
saveIcon?.classList.toggle("d-none");
spinner?.classList.toggle("d-none");
}
throw new Error(`Failed to save attachments order: ${error}`);
throw new Error(`Failed to save contacts order: ${error}`);
});
}

View File

@ -0,0 +1,155 @@
import Sortable, { SortableEvent } from "sortablejs";
let sponsoringSortable: Sortable;
document.addEventListener("DOMContentLoaded", function (event) {
const sponsoringTable = document.querySelector(
"#sciproject-sponsoring-table tbody"
);
if (sponsoringTable) {
sponsoringSortable = initSponsoringTable(
sponsoringTable as HTMLElement
);
}
const saveOrderButtons = document.querySelectorAll(
".sponsoring-save-order-button"
);
for (let i = 0; i < saveOrderButtons.length; i++) {
saveOrderButtons[i].addEventListener("click", saveOrder);
}
});
function initSponsoringTable(sponsoringTable: HTMLElement): Sortable {
return new Sortable(sponsoringTable, {
animation: 150,
group: "sciproject-sponsor",
handle: ".cms-sort-handle",
onEnd: enableSaveButtons,
});
}
function enableSaveButtons(event: SortableEvent) {
const saveOrderButtons = document.querySelectorAll(
".sponsoring-save-order-button"
);
for (let i = 0; i < saveOrderButtons.length; i++) {
const saveOrderButton: HTMLButtonElement = saveOrderButtons[
i
] as HTMLButtonElement;
saveOrderButton.disabled = false;
}
}
function saveOrder() {
const sponsoringTable = document.querySelector(
"#sciproject-sponsoring-table"
);
if (!sponsoringTable) {
showGeneralError();
throw Error("sciproject-sponsoring-table not found.");
}
const saveUrl = sponsoringTable.getAttribute("data-saveUrl");
if (!saveUrl) {
showGeneralError();
throw Error(
"data-saveUrl on sciproject-sponsoring-table is missing or empty"
);
}
const saveOrderButtons = document.querySelectorAll(
".sponsoring-save-order-button"
);
for (let i = 0; i < saveOrderButtons.length; i++) {
const saveOrderButton: HTMLButtonElement = saveOrderButtons[
i
] as HTMLButtonElement;
saveOrderButton.disabled = true;
const saveIcon = saveOrderButton.querySelector("save-icon");
const spinner = saveOrderButton.querySelector(".save-spinner");
saveIcon?.classList.toggle("d-none");
spinner?.classList.toggle("d-none");
}
const headers = new Headers();
headers.append("Content-Type", "application/json");
fetch(saveUrl, {
credentials: "include",
body: JSON.stringify(sponsoringSortable.toArray()),
headers,
method: "POST",
})
.then((response) => {
if (response.ok) {
for (let i = 0; i < saveOrderButtons.length; i++) {
const saveOrderButton: HTMLButtonElement = saveOrderButtons[
i
] as HTMLButtonElement;
const saveIcon =
saveOrderButton.querySelector(".save-icon");
const spinner =
saveOrderButton.querySelector(".save-spinner");
saveIcon?.classList.toggle("d-none");
spinner?.classList.toggle("d-none");
}
} else {
showSaveError();
for (let i = 0; i < saveOrderButtons.length; i++) {
const saveOrderButton: HTMLButtonElement = saveOrderButtons[
i
] as HTMLButtonElement;
saveOrderButton.disabled = false;
const saveIcon =
saveOrderButton.querySelector(".save-icon");
const spinner =
saveOrderButton.querySelector(".save-spinner");
saveIcon?.classList.toggle("d-none");
spinner?.classList.toggle("d-none");
}
throw Error(
`Failed to save sponsor order. Response status: ${response.status}, statusText: ${response.statusText}`
);
}
})
.catch((error) => {
showSaveError();
for (let i = 0; i < saveOrderButtons.length; i++) {
const saveOrderButton: HTMLButtonElement = saveOrderButtons[
i
] as HTMLButtonElement;
saveOrderButton.disabled = false;
const saveIcon = saveOrderButton.querySelector(".save-icon");
const spinner = saveOrderButton.querySelector(".save-spinner");
saveIcon?.classList.toggle("d-none");
spinner?.classList.toggle("d-none");
}
throw new Error(`Failed to save sponsor order: ${error}`);
});
}
function showGeneralError(): void {
const alertTemplate = document.querySelector(
"#sciproject-sponsoring-sort-error-general"
) as HTMLTemplateElement;
const alert = alertTemplate.content.cloneNode(true) as Element;
const container = document.querySelector("#messages");
if (container) {
container.appendChild(alert);
}
}
function showSaveError(): void {
const alertTemplate = document.querySelector(
"#sciproject-sponsoring-sort-error-save"
) as HTMLTemplateElement;
const alert = alertTemplate.content.cloneNode(true) as Element;
const container = document.querySelector("#messages");
if (container) {
container.appendChild(alert);
}
}

View File

@ -5,11 +5,8 @@ module.exports = {
chunkIds: false
},
entry: {
// "sciproject-description": "./src/main/typescript/sciproject-description.ts",
"sciproject-contacts": "./src/main/typescript/sciproject-contacts.ts",
// "sciproject-fundingtext": "./src/main/typescript/sciproject-fundingtext.ts",
// "sciproject-fundingvolume": "./src/main/typescript/sciproject-fundingvolume.ts",
//"sciproject-sponsoring": "./src/main/typescript/sciproject-sponsoring.ts"
"sciproject-sponsoring": "./src/main/typescript/sciproject-sponsoring.ts"
},
output: {
filename: "[name].js",