feat(communication): send test emails
This commit is contained in:
parent
e060080261
commit
d90da85df3
@ -23,6 +23,7 @@
|
||||
"no-extra-semi": "off",
|
||||
"semi": ["error", "always"],
|
||||
"comma-dangle": ["error", "always-multiline"],
|
||||
"quotes": ["error", "single"]
|
||||
"quotes": ["error", "single"],
|
||||
"no-var": "error"
|
||||
}
|
||||
}
|
||||
|
||||
88
frontend/src/utils/form/communication-recipients.js
Normal file
88
frontend/src/utils/form/communication-recipients.js
Normal file
@ -0,0 +1,88 @@
|
||||
import { Utility } from '../../core/utility';
|
||||
|
||||
const MASS_INPUT_SELECTOR = '.massinput';
|
||||
const RECIPIENT_CATEGORIES_SELECTOR = '.recipient-categories';
|
||||
const RECIPIENT_CATEGORY_SELECTOR = '.recipient-category';
|
||||
const RECIPIENT_CATEGORY_CHECKBOX_SELECTOR = '.recipient-category__checkbox';
|
||||
const RECIPIENT_CATEGORY_OPTIONS_SELECTOR = '.recipient-category__options';
|
||||
const RECIPIENT_CATEGORY_TOGGLE_ALL_SELECTOR = '.recipient-category__toggle-all [type="checkbox"]';
|
||||
const RECIPIENT_CATEGORY_CHECKED_COUNTER_SELECTOR = '.recipient-category__checked-counter';
|
||||
|
||||
|
||||
@Utility({
|
||||
selector: RECIPIENT_CATEGORIES_SELECTOR,
|
||||
})
|
||||
export class CommunicationRecipients {
|
||||
massInputElement;
|
||||
|
||||
constructor(element) {
|
||||
if (!element) {
|
||||
throw new Error('Communication Recipient utility cannot be setup without an element!');
|
||||
}
|
||||
|
||||
this.massInputElement = element.closest(MASS_INPUT_SELECTOR);
|
||||
|
||||
this.setupRecipientCategories();
|
||||
|
||||
const recipientObserver = new MutationObserver(this.setupRecipientCategories.bind(this));
|
||||
recipientObserver.observe(this.massInputElement, { childList: true });
|
||||
}
|
||||
|
||||
setupRecipientCategories() {
|
||||
Array.from(this.massInputElement.querySelectorAll(RECIPIENT_CATEGORY_SELECTOR)).forEach(setupRecipientCategory);
|
||||
}
|
||||
}
|
||||
|
||||
function setupRecipientCategory(recipientCategoryElement) {
|
||||
const categoryCheckbox = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_CHECKBOX_SELECTOR);
|
||||
const categoryOptions = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_OPTIONS_SELECTOR);
|
||||
|
||||
if (categoryOptions) {
|
||||
const categoryCheckboxes = Array.from(categoryOptions.querySelectorAll('[type="checkbox"]'));
|
||||
const toggleAllCheckbox = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_TOGGLE_ALL_SELECTOR);
|
||||
|
||||
// setup category checkbox to toggle all child checkboxes if changed
|
||||
categoryCheckbox.addEventListener('change', () => {
|
||||
categoryCheckboxes.filter(checkbox => !checkbox.disabled).forEach(checkbox => {
|
||||
checkbox.checked = categoryCheckbox.checked;
|
||||
});
|
||||
updateCheckedCounter(recipientCategoryElement, categoryCheckboxes);
|
||||
updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes);
|
||||
});
|
||||
|
||||
// update counter and toggle checkbox initially
|
||||
updateCheckedCounter(recipientCategoryElement, categoryCheckboxes);
|
||||
updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes);
|
||||
|
||||
// register change listener for individual checkboxes
|
||||
categoryCheckboxes.forEach(checkbox => {
|
||||
checkbox.addEventListener('change', () => {
|
||||
updateCheckedCounter(recipientCategoryElement, categoryCheckboxes);
|
||||
updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes);
|
||||
});
|
||||
});
|
||||
|
||||
// register change listener for toggle all checkbox
|
||||
if (toggleAllCheckbox) {
|
||||
toggleAllCheckbox.addEventListener('change', () => {
|
||||
categoryCheckboxes.filter(checkbox => !checkbox.disabled).forEach(checkbox => {
|
||||
checkbox.checked = toggleAllCheckbox.checked;
|
||||
});
|
||||
updateCheckedCounter(recipientCategoryElement, categoryCheckboxes);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update checked state of toggle all checkbox based on all other checkboxes
|
||||
function updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes) {
|
||||
const allChecked = categoryCheckboxes.reduce((acc, checkbox) => acc && checkbox.checked, true);
|
||||
toggleAllCheckbox.checked = allChecked;
|
||||
}
|
||||
|
||||
// update value of checked counter
|
||||
function updateCheckedCounter(recipientCategoryElement, categoryCheckboxes) {
|
||||
const checkedCounter = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_CHECKED_COUNTER_SELECTOR);
|
||||
const checkedCheckboxes = categoryCheckboxes.reduce((acc, checkbox) => checkbox.checked ? acc + 1 : acc, 0);
|
||||
checkedCounter.innerHTML = checkedCheckboxes + '/' + categoryCheckboxes.length;
|
||||
}
|
||||
@ -5,6 +5,7 @@ import { Datepicker } from './datepicker';
|
||||
import { FormErrorRemover } from './form-error-remover';
|
||||
import { InteractiveFieldset } from './interactive-fieldset';
|
||||
import { NavigateAwayPrompt } from './navigate-away-prompt';
|
||||
import { CommunicationRecipients } from './communication-recipients';
|
||||
|
||||
export const FormUtils = [
|
||||
AutoSubmitButton,
|
||||
@ -13,5 +14,6 @@ export const FormUtils = [
|
||||
FormErrorRemover,
|
||||
InteractiveFieldset,
|
||||
NavigateAwayPrompt,
|
||||
CommunicationRecipients,
|
||||
// ReactiveSubmitButton // not used currently
|
||||
];
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
@use "../../app" as *
|
||||
|
||||
// GENERAL STYLES FOR FORMS
|
||||
|
||||
// FORM GROUPS
|
||||
@ -24,7 +26,7 @@
|
||||
margin-top: 40px
|
||||
|
||||
.form-section-legend
|
||||
color: var(--color-fontsec)
|
||||
@extend .explanation
|
||||
margin: 7px 0
|
||||
|
||||
.form-group__hint, .form-section-title__hint
|
||||
@ -51,6 +53,7 @@
|
||||
content: ' *'
|
||||
color: var(--color-error)
|
||||
font-weight: 600
|
||||
font-style: normal
|
||||
|
||||
.form-group--submit .form-group__input
|
||||
grid-column: 2
|
||||
|
||||
@ -37,6 +37,9 @@ BtnAllocationAccept: Vergabe akzeptieren
|
||||
BtnSystemMessageHide: Verstecken
|
||||
BtnSystemMessageUnhide: Nicht mehr verstecken
|
||||
|
||||
BtnCommunicationSend: Senden
|
||||
BtnCommunicationTest: Test-Nachricht verschicken
|
||||
|
||||
|
||||
Aborted: Abgebrochen
|
||||
Remarks: Hinweise
|
||||
@ -952,6 +955,7 @@ MailEditNotifications: Benachrichtigungen ein-/ausschalten
|
||||
MailSubjectSupport: Supportanfrage
|
||||
MailSubjectSupportCustom customSubject@Text: [Support] #{customSubject}
|
||||
|
||||
CommCourseTestSubject customSubject@Text: [TEST] #{customSubject}
|
||||
CommCourseSubject: Kursmitteilung
|
||||
MailSubjectLecturerInvitation tid@TermId ssh@SchoolId csh@CourseShorthand: [#{tid}-#{ssh}-#{csh}] Einladung zum Kursverwalter
|
||||
InvitationAcceptDecline: Einladung annehmen/ablehnen
|
||||
@ -1415,6 +1419,7 @@ CommRecipientsTip: Sie selbst erhalten immer eine Kopie der Nachricht
|
||||
CommRecipientsList: Die an Sie selbst verschickte Kopie der Nachricht wird, zu Archivierungszwecken, eine vollständige Liste aller Empfänger enthalten. Die Empfängerliste wird im CSV-Format an die E-Mail angehängt. Andere Empfänger erhalten die Liste nicht. Bitte entfernen Sie dementsprechend den Anhang bevor Sie die E-Mail weiterleiten oder anderweitig mit Dritten teilen.
|
||||
CommDuplicateRecipients n@Int: #{n} #{pluralDE n "doppelter" "doppelte"} Empfänger ignoriert
|
||||
CommSuccess n@Int: Nachricht wurde an #{n} Empfänger versandt
|
||||
CommTestSuccess: Nachricht wurde zu Testzwecken nur an Sie selbst versandt
|
||||
CommUndisclosedRecipients: Verborgene Empfänger
|
||||
CommAllRecipients: alle-empfaenger
|
||||
|
||||
|
||||
@ -7,9 +7,6 @@ import Import
|
||||
import Handler.Utils
|
||||
import Handler.Utils.Communication
|
||||
|
||||
import qualified Data.CaseInsensitive as CI
|
||||
|
||||
import qualified Data.Set as Set
|
||||
import qualified Data.Map as Map
|
||||
|
||||
import qualified Database.Esqueleto as E
|
||||
@ -18,23 +15,13 @@ import qualified Database.Esqueleto as E
|
||||
getCCommR, postCCommR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
||||
getCCommR = postCCommR
|
||||
postCCommR tid ssh csh = do
|
||||
jSender <- requireAuthId
|
||||
cid <- runDB . getKeyBy404 $ TermSchoolCourseShort tid ssh csh
|
||||
|
||||
commR CommunicationRoute
|
||||
{ crHeading = SomeMessage . prependCourseTitle tid ssh csh $ SomeMessage MsgCommCourseHeading
|
||||
, crUltDest = SomeRoute $ CourseR tid ssh csh CCommR
|
||||
, crJobs = \Communication{..} -> do
|
||||
let jSubject = cSubject
|
||||
jMailContent = cBody
|
||||
jCourse = cid
|
||||
allRecipients = Set.toList $ Set.insert (Right jSender) cRecipients
|
||||
jMailObjectUUID <- liftIO getRandom
|
||||
jAllRecipientAddresses <- lift . fmap Set.fromList . forM allRecipients $ \case
|
||||
Left email -> return . Address Nothing $ CI.original email
|
||||
Right rid -> userAddress <$> getJust rid
|
||||
forM_ allRecipients $ \jRecipientEmail ->
|
||||
yield JobSendCourseCommunication{..}
|
||||
, crJobs = crJobsCourseCommunication cid
|
||||
, crTestJobs = crTestJobsCourseCommunication cid
|
||||
, crRecipients = Map.fromList
|
||||
[ ( RGCourseParticipants
|
||||
, E.from $ \(user `E.InnerJoin` participant) -> do
|
||||
|
||||
@ -11,31 +11,18 @@ import qualified Database.Esqueleto as E
|
||||
import qualified Database.Esqueleto.Utils as E
|
||||
|
||||
import qualified Data.Map as Map
|
||||
import qualified Data.Set as Set
|
||||
|
||||
import qualified Data.CaseInsensitive as CI
|
||||
|
||||
|
||||
getTCommR, postTCommR :: TermId -> SchoolId -> CourseShorthand -> TutorialName -> Handler Html
|
||||
getTCommR = postTCommR
|
||||
postTCommR tid ssh csh tutn = do
|
||||
jSender <- requireAuthId
|
||||
(cid, tutid) <- runDB $ fetchCourseIdTutorialId tid ssh csh tutn
|
||||
|
||||
commR CommunicationRoute
|
||||
{ crHeading = SomeMessage . prependCourseTitle tid ssh csh $ SomeMessage MsgCommTutorialHeading
|
||||
, crUltDest = SomeRoute $ CTutorialR tid ssh csh tutn TCommR
|
||||
, crJobs = \Communication{..} -> do
|
||||
let jSubject = cSubject
|
||||
jMailContent = cBody
|
||||
jCourse = cid
|
||||
allRecipients = Set.toList $ Set.insert (Right jSender) cRecipients
|
||||
jMailObjectUUID <- liftIO getRandom
|
||||
jAllRecipientAddresses <- lift . fmap Set.fromList . forM allRecipients $ \case
|
||||
Left email -> return . Address Nothing $ CI.original email
|
||||
Right rid -> userAddress <$> getJust rid
|
||||
forM_ allRecipients $ \jRecipientEmail ->
|
||||
yield JobSendCourseCommunication{..}
|
||||
, crJobs = crJobsCourseCommunication cid
|
||||
, crTestJobs = crTestJobsCourseCommunication cid
|
||||
, crRecipients = Map.fromList
|
||||
[ ( RGTutorialParticipants
|
||||
, E.from $ \(user `E.InnerJoin` participant) -> do
|
||||
|
||||
@ -3,6 +3,7 @@ module Handler.Utils.Communication
|
||||
, CommunicationRoute(..)
|
||||
, Communication(..)
|
||||
, commR
|
||||
, crJobsCourseCommunication, crTestJobsCourseCommunication
|
||||
-- * Re-Exports
|
||||
, Job(..)
|
||||
) where
|
||||
@ -18,6 +19,8 @@ import Data.Map ((!), (!?))
|
||||
import qualified Data.Map as Map
|
||||
import qualified Data.Set as Set
|
||||
|
||||
import qualified Data.Conduit.Combinators as C
|
||||
|
||||
|
||||
data RecipientGroup = RGCourseParticipants | RGCourseLecturers | RGCourseCorrectors | RGCourseTutors
|
||||
| RGTutorialParticipants
|
||||
@ -67,11 +70,25 @@ instance RenderMessage UniWorX RecipientCategory where
|
||||
renderMessage' :: forall msg. RenderMessage UniWorX msg => msg -> Text
|
||||
renderMessage' = renderMessage foundation ls
|
||||
|
||||
data CommunicationButton
|
||||
= BtnCommunicationSend
|
||||
| BtnCommunicationTest
|
||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
|
||||
deriving anyclass (Universe, Finite)
|
||||
|
||||
nullaryPathPiece ''CommunicationButton $ camelToPathPiece' 2
|
||||
embedRenderMessage ''UniWorX ''CommunicationButton id
|
||||
makePrisms ''CommunicationButton
|
||||
|
||||
instance Button UniWorX CommunicationButton where
|
||||
btnClasses BtnCommunicationSend = [BCIsButton, BCPrimary]
|
||||
btnClasses BtnCommunicationTest = [BCIsButton]
|
||||
|
||||
|
||||
data CommunicationRoute = CommunicationRoute
|
||||
{ crRecipients :: Map RecipientGroup (E.SqlQuery (E.SqlExpr (Entity User)))
|
||||
, crRecipientAuth :: Maybe (UserId -> DB AuthResult) -- ^ Only resolve userids given as GET-Parameter if they fulfil this criterion
|
||||
, crJobs :: Communication -> ConduitT () Job (YesodDB UniWorX) ()
|
||||
, crJobs, crTestJobs :: Communication -> ConduitT () Job (YesodDB UniWorX) ()
|
||||
, crHeading :: SomeMessage UniWorX
|
||||
, crUltDest :: SomeRoute UniWorX
|
||||
}
|
||||
@ -83,6 +100,26 @@ data Communication = Communication
|
||||
}
|
||||
|
||||
|
||||
crJobsCourseCommunication, crTestJobsCourseCommunication :: CourseId -> Communication -> ConduitT () Job (YesodDB UniWorX) ()
|
||||
crJobsCourseCommunication jCourse Communication{..} = do
|
||||
jSender <- requireAuthId
|
||||
let jSubject = cSubject
|
||||
jMailContent = cBody
|
||||
allRecipients = Set.toList $ Set.insert (Right jSender) cRecipients
|
||||
jMailObjectUUID <- liftIO getRandom
|
||||
jAllRecipientAddresses <- lift . fmap Set.fromList . forM allRecipients $ \case
|
||||
Left email -> return . Address Nothing $ CI.original email
|
||||
Right rid -> userAddress <$> getJust rid
|
||||
forM_ allRecipients $ \jRecipientEmail ->
|
||||
yield JobSendCourseCommunication{..}
|
||||
crTestJobsCourseCommunication jCourse comm = do
|
||||
jSender <- requireAuthId
|
||||
|
||||
MsgRenderer mr <- getMsgRenderer
|
||||
let comm' = comm { cSubject = Just . mr . MsgCommCourseTestSubject . fromMaybe (mr MsgCommCourseSubject) $ cSubject comm }
|
||||
crJobsCourseCommunication jCourse comm' .| C.filter ((== Right jSender) . jRecipientEmail)
|
||||
|
||||
|
||||
commR :: CommunicationRoute -> Handler Html
|
||||
commR CommunicationRoute{..} = do
|
||||
cUser <- maybeAuth
|
||||
@ -129,14 +166,18 @@ commR CommunicationRoute{..} = do
|
||||
miAdd (EnumPosition RecipientCustom, 0) 1 nudge submitView = Just $ \csrf -> do
|
||||
(addRes, addView) <- mpreq (multiUserField True Nothing) (fslpI MsgEMail (mr MsgEMail) & setTooltip MsgMultiEmailFieldTip & addName (nudge "email")) Nothing
|
||||
let
|
||||
addRes' = addRes <&> \(Set.toList -> nEmails) (maybe 0 (succ . snd . fst) . Map.lookupMax . Map.filterWithKey (\(EnumPosition c, _) _ -> c == RecipientCustom) -> kStart) -> FormSuccess . Map.fromList $ zip (map (EnumPosition RecipientCustom, ) [kStart..]) nEmails
|
||||
addRes' = addRes <&> \nEmails ((Map.elems &&& maybe 0 (succ . snd . fst) . Map.lookupMax) . Map.filterWithKey (\(EnumPosition c, _) _ -> c == RecipientCustom) -> (oEmails, kStart)) -> FormSuccess . Map.fromList . zip (map (EnumPosition RecipientCustom, ) [kStart..]) . Set.toList $ nEmails `Set.difference` Set.fromList oEmails
|
||||
return (addRes', $(widgetFile "widgets/communication/recipientAdd"))
|
||||
miAdd _ _ _ _ = Nothing
|
||||
miCell _ (Left (CI.original -> email)) initRes nudge csrf = do
|
||||
(tickRes, tickView) <- mpreq checkBoxField ("" & addName (nudge "tick")) $ initRes <|> Just True
|
||||
return (tickRes, $(widgetFile "widgets/communication/recipientEmail"))
|
||||
miCell _ (Right (lookupUser -> User{..})) initRes nudge csrf = do
|
||||
(tickRes, tickView) <- mpreq checkBoxField ("" & addName (nudge "tick")) $ initRes <|> Just True
|
||||
miCell _ (Right uid@(lookupUser -> User{..})) initRes nudge csrf = do
|
||||
(tickRes, tickView) <- if
|
||||
| fmap entityKey cUser == Just uid
|
||||
-> mforced checkBoxField ("" & addName (nudge "tick")) True
|
||||
| otherwise
|
||||
-> mpreq checkBoxField ("" & addName (nudge "tick")) $ initRes <|> Just True
|
||||
return (tickRes, $(widgetFile "widgets/communication/recipientName"))
|
||||
miAllowAdd (EnumPosition RecipientCustom, 0) 1 _ = True
|
||||
miAllowAdd _ _ _ = False
|
||||
@ -167,22 +208,33 @@ commR CommunicationRoute{..} = do
|
||||
|
||||
recipientsListMsg <- messageI Info MsgCommRecipientsList
|
||||
|
||||
((commRes,commWdgt),commEncoding) <- runFormPost . identifyForm FIDCommunication . renderAForm FormStandard $ Communication
|
||||
((commRes,commWdgt),commEncoding) <- runFormPost . identifyForm FIDCommunication . withButtonForm' universeF . renderAForm FormStandard $ Communication
|
||||
<$> recipientAForm
|
||||
<* aformMessage recipientsListMsg
|
||||
<*> aopt textField (fslI MsgCommSubject) Nothing
|
||||
<*> areq htmlField (fslI MsgCommBody) Nothing
|
||||
formResult commRes $ \comm -> do
|
||||
runDBJobs . runConduit $ transPipe (mapReaderT lift) (crJobs comm) .| sinkDBJobs
|
||||
addMessageI Success . MsgCommSuccess . Set.size $ cRecipients comm
|
||||
redirect crUltDest
|
||||
|
||||
formResult commRes $ \case
|
||||
(comm, BtnCommunicationSend) -> do
|
||||
runDBJobs . runConduit $ transPipe (mapReaderT lift) (crJobs comm) .| sinkDBJobs
|
||||
addMessageI Success . MsgCommSuccess . Set.size $ cRecipients comm
|
||||
redirect crUltDest
|
||||
(comm, BtnCommunicationTest) -> do
|
||||
runDBJobs . runConduit $ transPipe (mapReaderT lift) (crTestJobs comm) .| sinkDBJobs
|
||||
addMessageI Info MsgCommTestSuccess
|
||||
|
||||
let formWdgt = wrapForm commWdgt def
|
||||
{ formMethod = POST
|
||||
, formAction = SomeRoute <$> mbCurrentRoute
|
||||
, formEncoding = commEncoding
|
||||
, formSubmit = FormNoSubmit
|
||||
}
|
||||
siteLayoutMsg crHeading $ do
|
||||
setTitleI crHeading
|
||||
formWdgt
|
||||
let commTestTip = $(i18nWidgetFile "comm-test-tip")
|
||||
[whamlet|
|
||||
$newline never
|
||||
<section>
|
||||
^{formWdgt}
|
||||
<section .explanation>
|
||||
^{commTestTip}
|
||||
|]
|
||||
|
||||
17
templates/i18n/comm-test-tip/de-de-formal.hamlet
Normal file
17
templates/i18n/comm-test-tip/de-de-formal.hamlet
Normal file
@ -0,0 +1,17 @@
|
||||
$newline never
|
||||
|
||||
<p>
|
||||
|
||||
Über den Knopf „_{MsgBtnCommunicationTest}“ haben Sie die #
|
||||
Möglichkeit zunächst eine Kopie der Nachricht nur an sich selbst zu #
|
||||
schicken.
|
||||
|
||||
<br>
|
||||
|
||||
Diese wird den Präfix „[TEST]“ im Betreff enthalten und ansonsten in #
|
||||
allen Aspekten identisch sein zur eigentlichen Nachricht.
|
||||
|
||||
<p>
|
||||
|
||||
Um die Nachricht tatsächlich an alle Empfänger zu verschicken, #
|
||||
verwenden Sie bitte „_{MsgBtnSubmit}“.
|
||||
@ -1,87 +0,0 @@
|
||||
(function() {
|
||||
|
||||
var MASS_INPUT_SELECTOR = '.massinput';
|
||||
var RECIPIENT_CATEGORIES_SELECTOR = '.recipient-categories';
|
||||
var RECIPIENT_CATEGORY_SELECTOR = '.recipient-category';
|
||||
var RECIPIENT_CATEGORY_CHECKBOX_SELECTOR = '.recipient-category__checkbox ';
|
||||
var RECIPIENT_CATEGORY_OPTIONS_SELECTOR = '.recipient-category__options';
|
||||
var RECIPIENT_CATEGORY_TOGGLE_ALL_SELECTOR = '.recipient-category__toggle-all [type="checkbox"]';
|
||||
var RECIPIENT_CATEGORY_CHECKED_COUNTER_SELECTOR = '.recipient-category__checked-counter';
|
||||
|
||||
var massInputElement;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var recipientCategoriesElement = document.querySelector(RECIPIENT_CATEGORIES_SELECTOR);
|
||||
massInputElement = recipientCategoriesElement.closest(MASS_INPUT_SELECTOR);
|
||||
|
||||
setupRecipientCategories();
|
||||
|
||||
var recipientObserver = new MutationObserver(setupRecipientCategories);
|
||||
recipientObserver.observe(massInputElement, { childList: true });
|
||||
});
|
||||
|
||||
function setupRecipientCategories() {
|
||||
var recipientCategoryElements = Array.from(massInputElement.querySelectorAll(RECIPIENT_CATEGORY_SELECTOR));
|
||||
|
||||
recipientCategoryElements.forEach(function(element) {
|
||||
setupRecipientCategory(element);
|
||||
});
|
||||
}
|
||||
|
||||
function setupRecipientCategory(recipientCategoryElement) {
|
||||
var categoryCheckbox = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_CHECKBOX_SELECTOR);
|
||||
var categoryOptions = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_OPTIONS_SELECTOR);
|
||||
if (categoryOptions) {
|
||||
|
||||
var categoryCheckboxes = Array.from(categoryOptions.querySelectorAll('[type="checkbox"]'));
|
||||
var toggleAllCheckbox = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_TOGGLE_ALL_SELECTOR);
|
||||
|
||||
// setup category checkbox to toggle all child checkboxes if changed
|
||||
categoryCheckbox.addEventListener('change', function() {
|
||||
categoryCheckboxes.forEach(function(checkbox) {
|
||||
checkbox.checked = categoryCheckbox.checked;
|
||||
});
|
||||
updateCheckedCounter(recipientCategoryElement, categoryCheckboxes);
|
||||
updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes);
|
||||
});
|
||||
|
||||
// update counter and toggle checkbox initially
|
||||
updateCheckedCounter(recipientCategoryElement, categoryCheckboxes);
|
||||
updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes);
|
||||
|
||||
// register change listener for individual checkboxes
|
||||
categoryCheckboxes.forEach(function(checkbox) {
|
||||
checkbox.addEventListener('change', function() {
|
||||
updateCheckedCounter(recipientCategoryElement, categoryCheckboxes);
|
||||
updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes);
|
||||
});
|
||||
});
|
||||
|
||||
// register change listener for toggle all checkbox
|
||||
if (toggleAllCheckbox) {
|
||||
toggleAllCheckbox.addEventListener('change', function() {
|
||||
categoryCheckboxes.forEach(function(checkbox) {
|
||||
checkbox.checked = toggleAllCheckbox.checked;
|
||||
});
|
||||
updateCheckedCounter(recipientCategoryElement, categoryCheckboxes);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update checked state of toggle all checkbox based on all other checkboxes
|
||||
function updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes) {
|
||||
var allChecked = categoryCheckboxes.reduce(function(acc, checkbox) {
|
||||
return acc && checkbox.checked;
|
||||
}, true);
|
||||
toggleAllCheckbox.checked = allChecked;
|
||||
}
|
||||
|
||||
// update value of checked counter
|
||||
function updateCheckedCounter(recipientCategoryElement, categoryCheckboxes) {
|
||||
var checkedCounter = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_CHECKED_COUNTER_SELECTOR);
|
||||
var checkedCheckboxes = categoryCheckboxes.reduce(function(acc, checkbox) { return checkbox.checked ? acc + 1 : acc; }, 0);
|
||||
checkedCounter.innerHTML = checkedCheckboxes + '/' + categoryCheckboxes.length;
|
||||
}
|
||||
|
||||
})();
|
||||
Loading…
Reference in New Issue
Block a user