Sortablejs integration for sorting contacts of a sci project

pull/1/head
Jens Pelzetter 2022-05-10 20:28:40 +02:00
parent c198617f0d
commit 2943c5be22
11 changed files with 220 additions and 249 deletions

View File

@ -8,11 +8,12 @@
"name": "sci-types-project",
"version": "7.0.0",
"license": "LGPL-3.0-or-later",
"dependencies": {
"sortablejs": "^1.14.0"
},
"devDependencies": {
"@types/jquery": "^3.5.6",
"@types/sortablejs": "^1.10.7",
"npm-run-all": "^4.1.5",
"sass": "^1.42.1",
"ts-loader": "^9.2.6",
"typescript": "^4.4.3",
"webpack": "^5.55.1",
@ -50,14 +51,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/jquery": {
"version": "3.5.14",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/sizzle": "*"
}
},
"node_modules/@types/json-schema": {
"version": "7.0.11",
"dev": true,
@ -68,11 +61,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/sizzle": {
"version": "2.3.3",
"dev": true,
"license": "MIT"
},
"node_modules/@types/sortablejs": {
"version": "1.10.7",
"dev": true,
@ -305,31 +293,11 @@
"node": ">=4"
}
},
"node_modules/anymatch": {
"version": "3.1.2",
"dev": true,
"license": "ISC",
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"dev": true,
"license": "MIT"
},
"node_modules/binary-extensions": {
"version": "2.2.0",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"dev": true,
@ -423,32 +391,6 @@
"node": ">=4"
}
},
"node_modules/chokidar": {
"version": "3.5.3",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/chrome-trace-event": {
"version": "1.0.3",
"dev": true,
@ -839,17 +781,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/glob-parent": {
"version": "5.1.2",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/glob-to-regexp": {
"version": "0.4.1",
"dev": true,
@ -936,11 +867,6 @@
"node": ">=10.17.0"
}
},
"node_modules/immutable": {
"version": "4.0.0",
"dev": true,
"license": "MIT"
},
"node_modules/import-local": {
"version": "3.1.0",
"dev": true,
@ -996,17 +922,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"dev": true,
"license": "MIT",
"dependencies": {
"binary-extensions": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/is-boolean-object": {
"version": "1.1.2",
"dev": true,
@ -1058,25 +973,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-negative-zero": {
"version": "2.0.2",
"dev": true,
@ -1400,14 +1296,6 @@
"validate-npm-package-license": "^3.0.1"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/npm-run-all": {
"version": "4.1.5",
"dev": true,
@ -1650,17 +1538,6 @@
"node": ">=4"
}
},
"node_modules/readdirp": {
"version": "3.6.0",
"dev": true,
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/rechoir": {
"version": "0.7.1",
"dev": true,
@ -1726,22 +1603,6 @@
],
"license": "MIT"
},
"node_modules/sass": {
"version": "1.51.0",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/schema-utils": {
"version": "3.1.1",
"dev": true,
@ -1828,6 +1689,11 @@
"dev": true,
"license": "ISC"
},
"node_modules/sortablejs": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz",
"integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w=="
},
"node_modules/source-map": {
"version": "0.6.1",
"dev": true,
@ -1836,14 +1702,6 @@
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-support": {
"version": "0.5.21",
"dev": true,
@ -2391,13 +2249,6 @@
"version": "0.0.51",
"dev": true
},
"@types/jquery": {
"version": "3.5.14",
"dev": true,
"requires": {
"@types/sizzle": "*"
}
},
"@types/json-schema": {
"version": "7.0.11",
"dev": true
@ -2406,10 +2257,6 @@
"version": "17.0.27",
"dev": true
},
"@types/sizzle": {
"version": "2.3.3",
"dev": true
},
"@types/sortablejs": {
"version": "1.10.7",
"dev": true
@ -2586,22 +2433,10 @@
"color-convert": "^1.9.0"
}
},
"anymatch": {
"version": "3.1.2",
"dev": true,
"requires": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
}
},
"balanced-match": {
"version": "1.0.2",
"dev": true
},
"binary-extensions": {
"version": "2.2.0",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"dev": true,
@ -2653,20 +2488,6 @@
"supports-color": "^5.3.0"
}
},
"chokidar": {
"version": "3.5.3",
"dev": true,
"requires": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"fsevents": "~2.3.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
}
},
"chrome-trace-event": {
"version": "1.0.3",
"dev": true
@ -2921,13 +2742,6 @@
"get-intrinsic": "^1.1.1"
}
},
"glob-parent": {
"version": "5.1.2",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
}
},
"glob-to-regexp": {
"version": "0.4.1",
"dev": true
@ -2977,10 +2791,6 @@
"version": "2.1.0",
"dev": true
},
"immutable": {
"version": "4.0.0",
"dev": true
},
"import-local": {
"version": "3.1.0",
"dev": true,
@ -3013,13 +2823,6 @@
"has-bigints": "^1.0.1"
}
},
"is-binary-path": {
"version": "2.1.0",
"dev": true,
"requires": {
"binary-extensions": "^2.0.0"
}
},
"is-boolean-object": {
"version": "1.1.2",
"dev": true,
@ -3046,17 +2849,6 @@
"has-tostringtag": "^1.0.0"
}
},
"is-extglob": {
"version": "2.1.1",
"dev": true
},
"is-glob": {
"version": "4.0.3",
"dev": true,
"requires": {
"is-extglob": "^2.1.1"
}
},
"is-negative-zero": {
"version": "2.0.2",
"dev": true
@ -3253,10 +3045,6 @@
"validate-npm-package-license": "^3.0.1"
}
},
"normalize-path": {
"version": "3.0.0",
"dev": true
},
"npm-run-all": {
"version": "4.1.5",
"dev": true,
@ -3398,13 +3186,6 @@
"path-type": "^3.0.0"
}
},
"readdirp": {
"version": "3.6.0",
"dev": true,
"requires": {
"picomatch": "^2.2.1"
}
},
"rechoir": {
"version": "0.7.1",
"dev": true,
@ -3436,15 +3217,6 @@
"version": "5.2.1",
"dev": true
},
"sass": {
"version": "1.51.0",
"dev": true,
"requires": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
}
},
"schema-utils": {
"version": "3.1.1",
"dev": true,
@ -3500,14 +3272,15 @@
"version": "3.0.7",
"dev": true
},
"sortablejs": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz",
"integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w=="
},
"source-map": {
"version": "0.6.1",
"dev": true
},
"source-map-js": {
"version": "1.0.2",
"dev": true
},
"source-map-support": {
"version": "0.5.21",
"dev": true,

View File

@ -10,17 +10,15 @@
"author": "Jens Pelzetter",
"license": "LGPL-3.0-or-later",
"devDependencies": {
"@types/jquery": "^3.5.6",
"@types/sortablejs": "^1.10.7",
"npm-run-all": "^4.1.5",
"sass": "^1.42.1",
"ts-loader": "^9.2.6",
"typescript": "^4.4.3",
"webpack": "^5.55.1",
"webpack-cli": "^4.8.0"
},
"dependencies": {
"sortablejs": "^1.14.0"
},
"targets": {
"main": false

View File

@ -19,6 +19,19 @@
</div>
</c:if>
<template id="sciproject-contacts-sort-error-general">
<div class="alert alert-danger mt-3" role="alert">
#{SciProjectMessageBundle['contacts.sort.errors.general']}
</div>
</template>
<template id="sciproject-contacts-sort-error-save">
<div class="alert alert-danger mt-3" role="alert">
#{SciProjectMessageBundle['contacts.sort.errors.save']}
</div>
</template>
<div id="messages"></div>
<librecms:cmsEditorVariants
addButtonLabel="#{SciProjectMessageBundle['description.editor.add_variant']}"
addDialogLocaleSelectHelp="#{SciProjectMessageBundle['description.editor.add.locale.help']}"
@ -61,12 +74,27 @@
name="type"
/>
</librecms:assetPicker>
<button class="btn btn-secondary contacts-save-order-button"
disabled="disable"
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 class="sr-only">#{SciProjectMessageBundle['contacts.order.save.inprogress']}</span>
</span>
<span>#{SciProjectMessageBundle['contacts.order.save']}</span>
</button>
<c:choose>
<c:when test="#{SciProjectDescriptionContacts.contacts.isEmpty()}">
<p>#{SciProjectMessageBundle['contacts.none']}</p>
</c:when>
<c:otherwise>
<table>
<table id="sciproject-contacts-table"
data-saveUrl="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@sciproject-description/contacts/save-order">
<thead>
<tr>
<th scope="col">#{SciProjectMessageBundle['contacts.cols.contactable']}</th>
@ -79,8 +107,19 @@
<tbody>
<c:forEach items="#{SciProjectDescriptionContacts.contacts}"
var="contact">
<tr id="#{contact.contactId}">
<td>#{contact.contactable}</td>
<tr class="sciproject-contact"
id="#{contact.contactId}"
data-id="#{contact.contactId}">
<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['contacts.move.button']}</span>
</button>
</c:if>
#{contact.contactable}
</td>
<td>#{contact.contactType}</td>
<td>
<button class="btn btn-secondary"
@ -294,8 +333,8 @@
</ui:define>
<ui:define name="scripts">
<script src="#{request.contextPath}/assets/@content-sections/sciproject-contacts.js" />
<script src="#{request.contextPath}/assets/@content-sections/sciproject-members.js" />
<script src="#{request.contextPath}/assets/@sciproject/sciproject-contacts.js" />
<!--<script src="#{request.contextPath}/assets/@content-sections/sciproject-members.js" />-->
</ui:define>
</ui:composition>

View File

@ -172,3 +172,8 @@ authoringsteps.projectdescription.label=Description
memberships.none=No memberships assigned yet.
contacts.none=No contacts assigned yet.
description_step.errors.contactable_not_found=Selected contactable entity {0} not found.
contacts.move.button=Sort contacts
contacts.order.save.inprogress=Saving...
contacts.order.save=Save order
contacts.sort.errors.general=Error sorting contacts.
contacts.sort.errors.save=Failed to save contacts order.

View File

@ -172,3 +172,8 @@ authoringsteps.projectdescription.label=Beschreibung
memberships.none=Es wurden noch keine Mitglieder hinzugef\u00fcgt.
contacts.none=Es wurden noch keine Kontakte hinzugef\u00fcgt.
description_step.errors.contactable_not_found=Die als Kontakt ausgew\u00e4hlte Entity {0} wurde nicht gefunden.
contacts.move.button=Kontakte sortieren
contacts.order.save.inprogress=Speichern...
contacts.order.save=Sortierung speichern
contacts.sort.errors.general=Fehler f\u00fcr Sortierung der Kontakte.
contacts.sort.errors.save=Fehler beim Speichern der Sortierung der Kontakte.

View File

@ -0,0 +1,151 @@
import Sortable, { SortableEvent } from "sortablejs";
let contactsSortable: Sortable;
document.addEventListener("DOMContentLoaded", function (event) {
const contactsTable = document.querySelector(
"#sciproject-contacts-table tbody"
);
if (contactsTable) {
contactsSortable = initContactsTable(contactsTable as HTMLElement);
}
const saveOrderButtons = document.querySelectorAll(
".contacts-save-order-button"
);
for (let i = 0; i < saveOrderButtons.length; i++) {
saveOrderButtons[i].addEventListener("click", saveOrder);
}
});
function initContactsTable(contactsTable: HTMLElement): Sortable {
return new Sortable(contactsTable, {
animation: 150,
group: "sciproject-contact",
handle: ".cms-sort-handle",
onEnd: enableSaveButton,
});
}
function enableSaveButton(event: SortableEvent) {
const saveOrderButtons = document.querySelectorAll(
".contacts-save-order-button"
);
for (let i = 0; i < saveOrderButtons.length; i++) {
const saveOrderButton: HTMLButtonElement = saveOrderButtons[
i
] as HTMLButtonElement;
saveOrderButton.disabled = false;
}
}
function saveOrder() {
const contactsTable = document.querySelector("#sciproject-contacts-table");
if (!contactsTable) {
showGeneralError();
throw Error("sciproject-contacts-table not found");
}
const saveUrl = contactsTable.getAttribute("data-saveUrl");
if (!saveUrl) {
showGeneralError();
throw Error(
"data-saveUrl on sciproject-contacts-table container is missing or empty"
);
}
const saveOrderButtons = document.querySelectorAll(
".contacts-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(contactsSortable.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 attachments 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 attachments order: ${error}`);
});
}
function showGeneralError(): void {
const alertTemplate = document.querySelector(
"#sciproject-contacts-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-contacts-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

@ -6,7 +6,7 @@ module.exports = {
},
entry: {
// "sciproject-description": "./src/main/typescript/sciproject-description.ts",
//"sciproject-contacts": "./src/main/typescript/sciproject-contacts.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"