Merge and bugfix sorting by participants registration date
This commit is contained in:
commit
889c3ebb35
@ -89,6 +89,7 @@ CourseRegisterToTip: Anmeldung darf auch unbegrenzt offen bleiben
|
|||||||
CourseDeregisterUntilTip: Abmeldung darf auch unbegrenzt erlaubt bleiben
|
CourseDeregisterUntilTip: Abmeldung darf auch unbegrenzt erlaubt bleiben
|
||||||
CourseFilterSearch: Volltext-Suche
|
CourseFilterSearch: Volltext-Suche
|
||||||
CourseFilterRegistered: Registriert
|
CourseFilterRegistered: Registriert
|
||||||
|
CourseFilterNone: Egal
|
||||||
CourseDeleteQuestion: Wollen Sie den unten aufgeführten Kurs wirklich löschen?
|
CourseDeleteQuestion: Wollen Sie den unten aufgeführten Kurs wirklich löschen?
|
||||||
CourseDeleted: Kurs gelöscht
|
CourseDeleted: Kurs gelöscht
|
||||||
CourseUserNote: Notiz
|
CourseUserNote: Notiz
|
||||||
|
|||||||
@ -2,7 +2,7 @@ SystemMessage
|
|||||||
from UTCTime Maybe
|
from UTCTime Maybe
|
||||||
to UTCTime Maybe
|
to UTCTime Maybe
|
||||||
authenticatedOnly Bool
|
authenticatedOnly Bool
|
||||||
severity MessageClass
|
severity MessageStatus
|
||||||
defaultLanguage Lang
|
defaultLanguage Lang
|
||||||
content Html
|
content Html
|
||||||
summary Html Maybe
|
summary Html Maybe
|
||||||
|
|||||||
43
shell.nix
43
shell.nix
@ -1,11 +1,8 @@
|
|||||||
{ nixpkgs ? import <nixpkgs> {}, compiler ? null }:
|
{ nixpkgs ? import <nixpkgs>, compiler ? null }:
|
||||||
|
|
||||||
let
|
let
|
||||||
inherit (nixpkgs) pkgs;
|
inherit (nixpkgs {}) pkgs;
|
||||||
|
haskellPackages = if isNull compiler then pkgs.haskellPackages else pkgs.haskell.packages."${compiler}";
|
||||||
haskellPackages = if isNull compiler
|
|
||||||
then pkgs.haskellPackages
|
|
||||||
else pkgs.haskell.packages."${compiler}";
|
|
||||||
|
|
||||||
drv = haskellPackages.callPackage ./uniworx.nix {};
|
drv = haskellPackages.callPackage ./uniworx.nix {};
|
||||||
|
|
||||||
@ -26,21 +23,29 @@ let
|
|||||||
shellHook = ''
|
shellHook = ''
|
||||||
export PROMPT_INFO="${oldAttrs.name}"
|
export PROMPT_INFO="${oldAttrs.name}"
|
||||||
|
|
||||||
pgDir=$(mktemp -d)
|
if [[ -z "$PGHOST" ]]; then
|
||||||
pgSockDir=$(mktemp -d)
|
set -xe
|
||||||
pgLogFile=$(mktemp)
|
|
||||||
initdb --no-locale -D ''${pgDir}
|
|
||||||
pg_ctl start -D ''${pgDir} -l ''${pgLogFile} -w -o "-k ''${pgSockDir} -c listen_addresses=''' -c hba_file='${postgresHba}' -c unix_socket_permissions=0700"
|
|
||||||
export PGHOST=''${pgSockDir} PGLOG=''${pgLogFile}
|
|
||||||
psql -f ${postgresSchema} postgres
|
|
||||||
printf "Postgres logfile is %s\nPostgres socket directory is %s\n" ''${pgLogFile} ''${pgSockDir}
|
|
||||||
|
|
||||||
cleanup() {
|
pgDir=$(mktemp -d)
|
||||||
pg_ctl stop -D ''${pgDir}
|
pgSockDir=$(mktemp -d)
|
||||||
rm -rvf ''${pgDir} ''${pgSockDir} ''${pgLogFile}
|
pgLogFile=$(mktemp)
|
||||||
}
|
initdb --no-locale -D ''${pgDir}
|
||||||
|
pg_ctl start -D ''${pgDir} -l ''${pgLogFile} -w -o "-k ''${pgSockDir} -c listen_addresses=''' -c hba_file='${postgresHba}' -c unix_socket_permissions=0700"
|
||||||
|
export PGHOST=''${pgSockDir} PGLOG=''${pgLogFile}
|
||||||
|
psql -f ${postgresSchema} postgres
|
||||||
|
printf "Postgres logfile is %s\nPostgres socket directory is %s\n" ''${pgLogFile} ''${pgSockDir}
|
||||||
|
|
||||||
trap cleanup EXIT
|
cleanup() {
|
||||||
|
set +e -x
|
||||||
|
pg_ctl stop -D ''${pgDir}
|
||||||
|
rm -rvf ''${pgDir} ''${pgSockDir} ''${pgLogFile}
|
||||||
|
set +x
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
set +xe
|
||||||
|
fi
|
||||||
|
|
||||||
${oldAttrs.shellHook}
|
${oldAttrs.shellHook}
|
||||||
'';
|
'';
|
||||||
|
|||||||
@ -220,7 +220,7 @@ instance RenderMessage UniWorX MsgLanguage where
|
|||||||
instance RenderMessage UniWorX (UnsupportedAuthPredicate (Route UniWorX)) where
|
instance RenderMessage UniWorX (UnsupportedAuthPredicate (Route UniWorX)) where
|
||||||
renderMessage f ls (UnsupportedAuthPredicate tag route) = renderMessage f ls $ MsgUnsupportedAuthPredicate tag (show route)
|
renderMessage f ls (UnsupportedAuthPredicate tag route) = renderMessage f ls $ MsgUnsupportedAuthPredicate tag (show route)
|
||||||
|
|
||||||
embedRenderMessage ''UniWorX ''MessageClass ("Message" <>)
|
embedRenderMessage ''UniWorX ''MessageStatus ("Message" <>)
|
||||||
embedRenderMessage ''UniWorX ''NotificationTrigger $ ("NotificationTrigger" <>) . concat . drop 1 . splitCamel
|
embedRenderMessage ''UniWorX ''NotificationTrigger $ ("NotificationTrigger" <>) . concat . drop 1 . splitCamel
|
||||||
embedRenderMessage ''UniWorX ''StudyFieldType id
|
embedRenderMessage ''UniWorX ''StudyFieldType id
|
||||||
embedRenderMessage ''UniWorX ''SheetFileType id
|
embedRenderMessage ''UniWorX ''SheetFileType id
|
||||||
@ -1021,6 +1021,7 @@ siteLayout' headingOverride widget = do
|
|||||||
addScript $ StaticR js_utils_asidenav_js
|
addScript $ StaticR js_utils_asidenav_js
|
||||||
addScript $ StaticR js_utils_asyncForm_js
|
addScript $ StaticR js_utils_asyncForm_js
|
||||||
addScript $ StaticR js_utils_asyncTable_js
|
addScript $ StaticR js_utils_asyncTable_js
|
||||||
|
addScript $ StaticR js_utils_asyncTableFilter_js
|
||||||
addScript $ StaticR js_utils_checkAll_js
|
addScript $ StaticR js_utils_checkAll_js
|
||||||
addScript $ StaticR js_utils_httpClient_js
|
addScript $ StaticR js_utils_httpClient_js
|
||||||
addScript $ StaticR js_utils_form_js
|
addScript $ StaticR js_utils_form_js
|
||||||
@ -1032,9 +1033,13 @@ siteLayout' headingOverride widget = do
|
|||||||
addStylesheet $ StaticR css_utils_alerts_scss
|
addStylesheet $ StaticR css_utils_alerts_scss
|
||||||
addStylesheet $ StaticR css_utils_asidenav_scss
|
addStylesheet $ StaticR css_utils_asidenav_scss
|
||||||
addStylesheet $ StaticR css_utils_asyncForm_scss
|
addStylesheet $ StaticR css_utils_asyncForm_scss
|
||||||
|
addStylesheet $ StaticR css_utils_asyncTable_scss
|
||||||
|
addStylesheet $ StaticR css_utils_asyncTableFilter_scss
|
||||||
|
addStylesheet $ StaticR css_utils_checkbox_scss
|
||||||
addStylesheet $ StaticR css_utils_form_scss
|
addStylesheet $ StaticR css_utils_form_scss
|
||||||
addStylesheet $ StaticR css_utils_inputs_scss
|
addStylesheet $ StaticR css_utils_inputs_scss
|
||||||
addStylesheet $ StaticR css_utils_modal_scss
|
addStylesheet $ StaticR css_utils_modal_scss
|
||||||
|
addStylesheet $ StaticR css_utils_radio_scss
|
||||||
addStylesheet $ StaticR css_utils_showHide_scss
|
addStylesheet $ StaticR css_utils_showHide_scss
|
||||||
addStylesheet $ StaticR css_utils_tabber_scss
|
addStylesheet $ StaticR css_utils_tabber_scss
|
||||||
addStylesheet $ StaticR css_utils_tooltip_scss
|
addStylesheet $ StaticR css_utils_tooltip_scss
|
||||||
|
|||||||
@ -20,6 +20,8 @@ import Database.Persist.Sql (fromSqlKey)
|
|||||||
|
|
||||||
-- import qualified Data.UUID.Cryptographic as UUID
|
-- import qualified Data.UUID.Cryptographic as UUID
|
||||||
|
|
||||||
|
import Control.Monad.Trans.Writer (mapWriterT)
|
||||||
|
|
||||||
-- BEGIN - Buttons needed only here
|
-- BEGIN - Buttons needed only here
|
||||||
data ButtonCreate = CreateMath | CreateInf -- Dummy for Example
|
data ButtonCreate = CreateMath | CreateInf -- Dummy for Example
|
||||||
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
|
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
|
||||||
@ -84,15 +86,12 @@ postAdminTestR = do
|
|||||||
_other -> addMessage Warning "KEIN Knopf erkannt"
|
_other -> addMessage Warning "KEIN Knopf erkannt"
|
||||||
|
|
||||||
((emailResult, emailWidget), emailEnctype) <- runFormPost . identifyForm "email" $ renderAForm FormStandard emailTestForm
|
((emailResult, emailWidget), emailEnctype) <- runFormPost . identifyForm "email" $ renderAForm FormStandard emailTestForm
|
||||||
case emailResult of
|
formResultModal emailResult AdminTestR $ \(email, ls) -> do
|
||||||
(FormSuccess (email, ls)) -> do
|
jId <- mapWriterT runDB $ do
|
||||||
jId <- runDB $ do
|
jId <- queueJob $ JobSendTestEmail email ls
|
||||||
jId <- queueJob $ JobSendTestEmail email ls
|
tell . pure $ Message Success [shamlet|Email-test gestartet (Job ##{tshow (fromSqlKey jId)})|]
|
||||||
addMessage Success [shamlet|Email-test gestartet (Job ##{tshow (fromSqlKey jId)})|]
|
return jId
|
||||||
return jId
|
writeJobCtl $ JobCtlPerform jId
|
||||||
writeJobCtl $ JobCtlPerform jId
|
|
||||||
FormMissing -> return ()
|
|
||||||
(FormFailure errs) -> forM_ errs $ addMessage Error . toHtml
|
|
||||||
|
|
||||||
let emailWidget' = [whamlet|
|
let emailWidget' = [whamlet|
|
||||||
<form method=post action=@{AdminTestR} enctype=#{emailEnctype} data-ajax-submit>
|
<form method=post action=@{AdminTestR} enctype=#{emailEnctype} data-ajax-submit>
|
||||||
|
|||||||
@ -85,22 +85,22 @@ submissionModeIs sMode ((_course `E.InnerJoin` sheet `E.InnerJoin` _submission)
|
|||||||
|
|
||||||
|
|
||||||
-- Columns
|
-- Columns
|
||||||
colTerm :: IsDBTable m a => Colonnade _ CorrectionTableData (DBCell m a)
|
colTerm :: IsDBTable m a => Colonnade Sortable CorrectionTableData (DBCell m a)
|
||||||
colTerm = sortable (Just "term") (i18nCell MsgTerm)
|
colTerm = sortable (Just "term") (i18nCell MsgTerm)
|
||||||
$ \DBRow{ dbrOutput=(_, _, course, _, _) } ->
|
$ \DBRow{ dbrOutput=(_, _, course, _, _) } ->
|
||||||
-- cell [whamlet| _{untermKey $ course ^. _3}|] -- lange, internationale Semester
|
-- cell [whamlet| _{untermKey $ course ^. _3}|] -- lange, internationale Semester
|
||||||
textCell $ termToText $ unTermKey $ course ^. _3 -- kurze Semsterkürzel
|
textCell $ termToText $ unTermKey $ course ^. _3 -- kurze Semsterkürzel
|
||||||
|
|
||||||
colSchool :: IsDBTable m a => Colonnade _ CorrectionTableData (DBCell m a)
|
colSchool :: IsDBTable m a => Colonnade Sortable CorrectionTableData (DBCell m a)
|
||||||
colSchool = sortable (Just "school") (i18nCell MsgCourseSchool)
|
colSchool = sortable (Just "school") (i18nCell MsgCourseSchool)
|
||||||
$ \DBRow{ dbrOutput=(_, _, course, _, _) } ->
|
$ \DBRow{ dbrOutput=(_, _, course, _, _) } ->
|
||||||
anchorCell (TermSchoolCourseListR (course ^. _3) (course ^. _4)) [whamlet|#{unSchoolKey (course ^. _4)}|]
|
anchorCell (TermSchoolCourseListR (course ^. _3) (course ^. _4)) [whamlet|#{unSchoolKey (course ^. _4)}|]
|
||||||
|
|
||||||
colCourse :: IsDBTable m a => Colonnade _ CorrectionTableData (DBCell m a)
|
colCourse :: IsDBTable m a => Colonnade Sortable CorrectionTableData (DBCell m a)
|
||||||
colCourse = sortable (Just "course") (i18nCell MsgCourse)
|
colCourse = sortable (Just "course") (i18nCell MsgCourse)
|
||||||
$ \DBRow{ dbrOutput=(_, _, (_,csh,tid,sid), _, _) } -> courseCellCL (tid,sid,csh)
|
$ \DBRow{ dbrOutput=(_, _, (_,csh,tid,sid), _, _) } -> courseCellCL (tid,sid,csh)
|
||||||
|
|
||||||
colSheet :: IsDBTable m a => Colonnade _ CorrectionTableData (DBCell m a)
|
colSheet :: IsDBTable m a => Colonnade Sortable CorrectionTableData (DBCell m a)
|
||||||
colSheet = sortable (Just "sheet") (i18nCell MsgSheet)
|
colSheet = sortable (Just "sheet") (i18nCell MsgSheet)
|
||||||
$ \DBRow{ dbrOutput=(_, sheet, course, _, _) } ->
|
$ \DBRow{ dbrOutput=(_, sheet, course, _, _) } ->
|
||||||
let tid = course ^. _3
|
let tid = course ^. _3
|
||||||
@ -109,16 +109,16 @@ colSheet = sortable (Just "sheet") (i18nCell MsgSheet)
|
|||||||
shn = sheetName $ entityVal sheet
|
shn = sheetName $ entityVal sheet
|
||||||
in anchorCell (CSheetR tid ssh csh shn SShowR) [whamlet|#{display shn}|]
|
in anchorCell (CSheetR tid ssh csh shn SShowR) [whamlet|#{display shn}|]
|
||||||
|
|
||||||
colSheetType :: IsDBTable m a => Colonnade _ CorrectionTableData (DBCell m a)
|
colSheetType :: IsDBTable m a => Colonnade Sortable CorrectionTableData (DBCell m a)
|
||||||
colSheetType = sortable (toNothing "sheetType") (i18nCell MsgSheetType)
|
colSheetType = sortable (toNothing "sheetType") (i18nCell MsgSheetType)
|
||||||
$ \DBRow{ dbrOutput=(_, sheet, _, _, _) } -> i18nCell . sheetType $ entityVal sheet
|
$ \DBRow{ dbrOutput=(_, sheet, _, _, _) } -> i18nCell . sheetType $ entityVal sheet
|
||||||
|
|
||||||
colCorrector :: IsDBTable m a => Colonnade _ CorrectionTableData (DBCell m a)
|
colCorrector :: IsDBTable m a => Colonnade Sortable CorrectionTableData (DBCell m a)
|
||||||
colCorrector = sortable (Just "corrector") (i18nCell MsgCorrector) $ \case
|
colCorrector = sortable (Just "corrector") (i18nCell MsgCorrector) $ \case
|
||||||
DBRow{ dbrOutput = (_, _, _, Nothing , _) } -> cell mempty
|
DBRow{ dbrOutput = (_, _, _, Nothing , _) } -> cell mempty
|
||||||
DBRow{ dbrOutput = (_, _, _, Just (Entity _ User{..}), _) } -> userCell userDisplayName userSurname
|
DBRow{ dbrOutput = (_, _, _, Just (Entity _ User{..}), _) } -> userCell userDisplayName userSurname
|
||||||
|
|
||||||
colSubmissionLink :: IsDBTable m a => Colonnade _ CorrectionTableData (DBCell m a)
|
colSubmissionLink :: IsDBTable m a => Colonnade Sortable CorrectionTableData (DBCell m a)
|
||||||
colSubmissionLink = sortable Nothing (i18nCell MsgSubmission)
|
colSubmissionLink = sortable Nothing (i18nCell MsgSubmission)
|
||||||
$ \DBRow{ dbrOutput=(submission, sheet, course, _, _) } ->
|
$ \DBRow{ dbrOutput=(submission, sheet, course, _, _) } ->
|
||||||
let csh = course ^. _2
|
let csh = course ^. _2
|
||||||
@ -134,7 +134,7 @@ colSubmissionLink = sortable Nothing (i18nCell MsgSubmission)
|
|||||||
colSelect :: forall act h. (Monoid act, Headedness h) => Colonnade h CorrectionTableData (DBCell _ (FormResult (act, DBFormResult CryptoFileNameSubmission Bool CorrectionTableData), SheetTypeSummary))
|
colSelect :: forall act h. (Monoid act, Headedness h) => Colonnade h CorrectionTableData (DBCell _ (FormResult (act, DBFormResult CryptoFileNameSubmission Bool CorrectionTableData), SheetTypeSummary))
|
||||||
colSelect = dbSelect (_1 . applying _2) id $ \DBRow{ dbrOutput=(Entity subId _, _, _, _, _) } -> encrypt subId
|
colSelect = dbSelect (_1 . applying _2) id $ \DBRow{ dbrOutput=(Entity subId _, _, _, _, _) } -> encrypt subId
|
||||||
|
|
||||||
colSubmittors :: IsDBTable m a => Colonnade _ CorrectionTableData (DBCell m a)
|
colSubmittors :: IsDBTable m a => Colonnade Sortable CorrectionTableData (DBCell m a)
|
||||||
colSubmittors = sortable (Just "submittors") (i18nCell MsgSubmissionUsers) $ \DBRow{ dbrOutput=(_, _, course, _, users) } -> let
|
colSubmittors = sortable (Just "submittors") (i18nCell MsgSubmissionUsers) $ \DBRow{ dbrOutput=(_, _, course, _, users) } -> let
|
||||||
csh = course ^. _2
|
csh = course ^. _2
|
||||||
tid = course ^. _3
|
tid = course ^. _3
|
||||||
@ -146,7 +146,7 @@ colSubmittors = sortable (Just "submittors") (i18nCell MsgSubmissionUsers) $ \DB
|
|||||||
Just p -> [whamlet|^{nameWidget userDisplayName userSurname} (#{review _PseudonymText p})|]
|
Just p -> [whamlet|^{nameWidget userDisplayName userSurname} (#{review _PseudonymText p})|]
|
||||||
in protoCell & cellAttrs <>~ [("class", "list--inline list--comma-separated")]
|
in protoCell & cellAttrs <>~ [("class", "list--inline list--comma-separated")]
|
||||||
|
|
||||||
colSMatrikel :: IsDBTable m a => Colonnade _ CorrectionTableData (DBCell m a)
|
colSMatrikel :: IsDBTable m a => Colonnade Sortable CorrectionTableData (DBCell m a)
|
||||||
colSMatrikel = sortable Nothing (i18nCell MsgMatrikelNr) $ \DBRow{ dbrOutput=(_, _, _, _, users) } -> let
|
colSMatrikel = sortable Nothing (i18nCell MsgMatrikelNr) $ \DBRow{ dbrOutput=(_, _, _, _, users) } -> let
|
||||||
protoCell = listCell (Map.toList users) $ \(userId, (User{..}, _)) -> anchorCellM (AdminUserR <$> encrypt userId) (maybe mempty toWidget userMatrikelnummer)
|
protoCell = listCell (Map.toList users) $ \(userId, (User{..}, _)) -> anchorCellM (AdminUserR <$> encrypt userId) (maybe mempty toWidget userMatrikelnummer)
|
||||||
in protoCell & cellAttrs <>~ [("class", "list--inline list--comma-separated")]
|
in protoCell & cellAttrs <>~ [("class", "list--inline list--comma-separated")]
|
||||||
@ -170,26 +170,26 @@ colRating = sortable (Just "rating") (i18nCell MsgRating) $ \DBRow{ dbrOutput=(E
|
|||||||
scribe (_2 :: Lens' (a, SheetTypeSummary) SheetTypeSummary) summary
|
scribe (_2 :: Lens' (a, SheetTypeSummary) SheetTypeSummary) summary
|
||||||
]
|
]
|
||||||
|
|
||||||
colAssigned :: IsDBTable m a => Colonnade _ CorrectionTableData (DBCell m a)
|
colAssigned :: IsDBTable m a => Colonnade Sortable CorrectionTableData (DBCell m a)
|
||||||
colAssigned = sortable (Just "assignedtime") (i18nCell MsgAssignedTime) $ \DBRow{ dbrOutput=(Entity _subId Submission{..}, _sheet, _course, _, _) } ->
|
colAssigned = sortable (Just "assignedtime") (i18nCell MsgAssignedTime) $ \DBRow{ dbrOutput=(Entity _subId Submission{..}, _sheet, _course, _, _) } ->
|
||||||
maybe mempty dateTimeCell submissionRatingAssigned
|
maybe mempty dateTimeCell submissionRatingAssigned
|
||||||
|
|
||||||
colRated :: IsDBTable m a => Colonnade _ CorrectionTableData (DBCell m a)
|
colRated :: IsDBTable m a => Colonnade Sortable CorrectionTableData (DBCell m a)
|
||||||
colRated = sortable (Just "ratingtime") (i18nCell MsgRatingTime) $ \DBRow{ dbrOutput=(Entity _subId Submission{..}, _sheet, _course, _, _) } ->
|
colRated = sortable (Just "ratingtime") (i18nCell MsgRatingTime) $ \DBRow{ dbrOutput=(Entity _subId Submission{..}, _sheet, _course, _, _) } ->
|
||||||
maybe mempty dateTimeCell submissionRatingTime
|
maybe mempty dateTimeCell submissionRatingTime
|
||||||
|
|
||||||
colPseudonyms :: IsDBTable m a => Colonnade _ CorrectionTableData (DBCell m a)
|
colPseudonyms :: IsDBTable m a => Colonnade Sortable CorrectionTableData (DBCell m a)
|
||||||
colPseudonyms = sortable Nothing (i18nCell MsgPseudonyms) $ \DBRow{ dbrOutput=(_, _, _, _, users) } -> let
|
colPseudonyms = sortable Nothing (i18nCell MsgPseudonyms) $ \DBRow{ dbrOutput=(_, _, _, _, users) } -> let
|
||||||
lCell = listCell (catMaybes $ snd . snd <$> Map.toList users) $ \pseudo ->
|
lCell = listCell (catMaybes $ snd . snd <$> Map.toList users) $ \pseudo ->
|
||||||
cell [whamlet|#{review _PseudonymText pseudo}|]
|
cell [whamlet|#{review _PseudonymText pseudo}|]
|
||||||
in lCell & cellAttrs <>~ [("class", "list--inline list--comma-separated")]
|
in lCell & cellAttrs <>~ [("class", "list--inline list--comma-separated")]
|
||||||
|
|
||||||
colRatedField :: Colonnade _ CorrectionTableData (DBCell _ (FormResult (DBFormResult SubmissionId (Bool, a, b) CorrectionTableData)))
|
colRatedField :: Colonnade Sortable CorrectionTableData (DBCell _ (FormResult (DBFormResult SubmissionId (Bool, a, b) CorrectionTableData)))
|
||||||
colRatedField = sortable Nothing (i18nCell MsgRatingDone) $ formCell id
|
colRatedField = sortable Nothing (i18nCell MsgRatingDone) $ formCell id
|
||||||
(\DBRow{ dbrOutput=(Entity subId _, _, _, _, _) } -> return subId)
|
(\DBRow{ dbrOutput=(Entity subId _, _, _, _, _) } -> return subId)
|
||||||
(\DBRow{ dbrOutput=(Entity _ (submissionRatingDone -> done), _, _, _, _) } mkUnique -> over (_1.mapped) (_1 .~) . over _2 fvInput <$> mreq checkBoxField (fsUniq mkUnique "rated") (Just done))
|
(\DBRow{ dbrOutput=(Entity _ (submissionRatingDone -> done), _, _, _, _) } mkUnique -> over (_1.mapped) (_1 .~) . over _2 fvInput <$> mreq checkBoxField (fsUniq mkUnique "rated") (Just done))
|
||||||
|
|
||||||
colPointsField :: Colonnade _ CorrectionTableData (DBCell _ (FormResult (DBFormResult SubmissionId (a, Maybe Points, b) CorrectionTableData)))
|
colPointsField :: Colonnade Sortable CorrectionTableData (DBCell _ (FormResult (DBFormResult SubmissionId (a, Maybe Points, b) CorrectionTableData)))
|
||||||
colPointsField = sortable (Just "rating") (i18nCell MsgColumnRatingPoints) $ formCell id
|
colPointsField = sortable (Just "rating") (i18nCell MsgColumnRatingPoints) $ formCell id
|
||||||
(\DBRow{ dbrOutput=(Entity subId _, _, _, _, _) } -> return subId)
|
(\DBRow{ dbrOutput=(Entity subId _, _, _, _, _) } -> return subId)
|
||||||
(\DBRow{ dbrOutput=(Entity _ Submission{..}, Entity _ Sheet{..}, _, _, _) } mkUnique -> case sheetType of
|
(\DBRow{ dbrOutput=(Entity _ Submission{..}, Entity _ Sheet{..}, _, _, _) } mkUnique -> case sheetType of
|
||||||
@ -197,7 +197,7 @@ colPointsField = sortable (Just "rating") (i18nCell MsgColumnRatingPoints) $ for
|
|||||||
_other -> over (_1.mapped) (_2 .~) . over _2 fvInput <$> mopt pointsField (fsUniq mkUnique "points") (Just submissionRatingPoints)
|
_other -> over (_1.mapped) (_2 .~) . over _2 fvInput <$> mopt pointsField (fsUniq mkUnique "points") (Just submissionRatingPoints)
|
||||||
)
|
)
|
||||||
|
|
||||||
colCommentField :: Colonnade _ CorrectionTableData (DBCell _ (FormResult (DBFormResult SubmissionId (a, b, Maybe Text) CorrectionTableData)))
|
colCommentField :: Colonnade Sortable CorrectionTableData (DBCell _ (FormResult (DBFormResult SubmissionId (a, b, Maybe Text) CorrectionTableData)))
|
||||||
colCommentField = sortable Nothing (i18nCell MsgRatingComment) $ formCell id
|
colCommentField = sortable Nothing (i18nCell MsgRatingComment) $ formCell id
|
||||||
(\DBRow{ dbrOutput=(Entity subId _, _, _, _, _) } -> return subId)
|
(\DBRow{ dbrOutput=(Entity subId _, _, _, _, _) } -> return subId)
|
||||||
(\DBRow{ dbrOutput=(Entity _ Submission{..}, _, _, _, _) } mkUnique -> over (_1.mapped) ((_3 .~) . assertM (not . null) . fmap (Text.strip . unTextarea)) . over _2 fvInput <$> mopt textareaField (fsUniq mkUnique "comment") (Just $ Textarea <$> submissionRatingComment))
|
(\DBRow{ dbrOutput=(Entity _ Submission{..}, _, _, _, _) } mkUnique -> over (_1.mapped) ((_3 .~) . assertM (not . null) . fmap (Text.strip . unTextarea)) . over _2 fvInput <$> mopt textareaField (fsUniq mkUnique "comment") (Just $ Textarea <$> submissionRatingComment))
|
||||||
@ -237,6 +237,9 @@ makeCorrectionsTable whereClause dbtColonnade dbtFilterUI psValidator dbtProj' d
|
|||||||
[ ( "term"
|
[ ( "term"
|
||||||
, SortColumn $ \((course `E.InnerJoin` _ `E.InnerJoin` _) `E.LeftOuterJoin` _) -> course E.^. CourseTerm
|
, SortColumn $ \((course `E.InnerJoin` _ `E.InnerJoin` _) `E.LeftOuterJoin` _) -> course E.^. CourseTerm
|
||||||
)
|
)
|
||||||
|
, ( "school"
|
||||||
|
, SortColumn $ \((course `E.InnerJoin` _ `E.InnerJoin` _) `E.LeftOuterJoin` _) -> course E.^. CourseSchool
|
||||||
|
)
|
||||||
, ( "course"
|
, ( "course"
|
||||||
, SortColumn $ \((course `E.InnerJoin` _ `E.InnerJoin` _) `E.LeftOuterJoin` _) -> course E.^. CourseShorthand
|
, SortColumn $ \((course `E.InnerJoin` _ `E.InnerJoin` _) `E.LeftOuterJoin` _) -> course E.^. CourseShorthand
|
||||||
)
|
)
|
||||||
|
|||||||
@ -33,7 +33,7 @@ import qualified Database.Esqueleto as E
|
|||||||
-- NOTE: Outdated way to use dbTable; see ProfileDataR Handler for a more recent method.
|
-- NOTE: Outdated way to use dbTable; see ProfileDataR Handler for a more recent method.
|
||||||
type CourseTableData = DBRow (Entity Course, Int64, Bool, Entity School)
|
type CourseTableData = DBRow (Entity Course, Int64, Bool, Entity School)
|
||||||
|
|
||||||
colCourse :: IsDBTable m a => Colonnade _ CourseTableData (DBCell m a)
|
colCourse :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
||||||
colCourse = sortable (Just "course") (i18nCell MsgCourse)
|
colCourse = sortable (Just "course") (i18nCell MsgCourse)
|
||||||
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _) } ->
|
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _) } ->
|
||||||
anchorCell (CourseR courseTerm courseSchool courseShorthand CShowR)
|
anchorCell (CourseR courseTerm courseSchool courseShorthand CShowR)
|
||||||
@ -51,12 +51,12 @@ colDescription = sortable Nothing mempty
|
|||||||
Nothing -> mempty
|
Nothing -> mempty
|
||||||
(Just descr) -> cell $ modal (toWidget $ hasComment True) (Right $ toWidget descr)
|
(Just descr) -> cell $ modal (toWidget $ hasComment True) (Right $ toWidget descr)
|
||||||
|
|
||||||
colCShort :: IsDBTable m a => Colonnade _ CourseTableData (DBCell m a)
|
colCShort :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
||||||
colCShort = sortable (Just "cshort") (i18nCell MsgCourseShort)
|
colCShort = sortable (Just "cshort") (i18nCell MsgCourseShort)
|
||||||
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _) } ->
|
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _) } ->
|
||||||
anchorCell (CourseR courseTerm courseSchool courseShorthand CShowR) [whamlet|#{display courseShorthand}|]
|
anchorCell (CourseR courseTerm courseSchool courseShorthand CShowR) [whamlet|#{display courseShorthand}|]
|
||||||
|
|
||||||
-- colCShortDescr :: IsDBTable m a => Colonnade _ CourseTableData (DBCell m a)
|
-- colCShortDescr :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
||||||
-- colCShortDescr = sortable (Just "cshort") (i18nCell MsgCourseShort)
|
-- colCShortDescr = sortable (Just "cshort") (i18nCell MsgCourseShort)
|
||||||
-- $ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _) } -> mappend
|
-- $ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _) } -> mappend
|
||||||
-- ( anchorCell (CourseR courseTerm courseSchool courseShorthand CShowR) [whamlet|#{display courseShorthand}|] )
|
-- ( anchorCell (CourseR courseTerm courseSchool courseShorthand CShowR) [whamlet|#{display courseShorthand}|] )
|
||||||
@ -70,39 +70,39 @@ colCShort = sortable (Just "cshort") (i18nCell MsgCourseShort)
|
|||||||
-- |]
|
-- |]
|
||||||
-- )
|
-- )
|
||||||
|
|
||||||
colTerm :: IsDBTable m a => Colonnade _ CourseTableData (DBCell m a)
|
colTerm :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
||||||
colTerm = sortable (Just "term") (i18nCell MsgTerm)
|
colTerm = sortable (Just "term") (i18nCell MsgTerm)
|
||||||
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _) } ->
|
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _) } ->
|
||||||
anchorCell (TermCourseListR courseTerm) [whamlet|#{display courseTerm}|]
|
anchorCell (TermCourseListR courseTerm) [whamlet|#{display courseTerm}|]
|
||||||
|
|
||||||
colSchool :: IsDBTable m a => Colonnade _ CourseTableData (DBCell m a)
|
colSchool :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
||||||
colSchool = sortable (Just "school") (i18nCell MsgCourseSchool)
|
colSchool = sortable (Just "school") (i18nCell MsgCourseSchool)
|
||||||
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, Entity _ School{..}) } ->
|
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, Entity _ School{..}) } ->
|
||||||
anchorCell (TermSchoolCourseListR courseTerm courseSchool) [whamlet|#{display schoolName}|]
|
anchorCell (TermSchoolCourseListR courseTerm courseSchool) [whamlet|#{display schoolName}|]
|
||||||
|
|
||||||
colSchoolShort :: IsDBTable m a => Colonnade _ CourseTableData (DBCell m a)
|
colSchoolShort :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
||||||
colSchoolShort = sortable (Just "schoolshort") (i18nCell MsgCourseSchoolShort)
|
colSchoolShort = sortable (Just "schoolshort") (i18nCell MsgCourseSchoolShort)
|
||||||
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, Entity _ School{..}) } ->
|
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, Entity _ School{..}) } ->
|
||||||
anchorCell (TermSchoolCourseListR courseTerm courseSchool) [whamlet|#{display schoolShorthand}|]
|
anchorCell (TermSchoolCourseListR courseTerm courseSchool) [whamlet|#{display schoolShorthand}|]
|
||||||
|
|
||||||
colRegFrom :: IsDBTable m a => Colonnade _ CourseTableData (DBCell m a)
|
colRegFrom :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
||||||
colRegFrom = sortable (Just "register-from") (i18nCell MsgRegisterFrom)
|
colRegFrom = sortable (Just "register-from") (i18nCell MsgRegisterFrom)
|
||||||
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _) } ->
|
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _) } ->
|
||||||
maybe mempty dateTimeCell courseRegisterFrom
|
maybe mempty dateTimeCell courseRegisterFrom
|
||||||
-- cell $ traverse (formatTime SelFormatDateTime) courseRegisterFrom >>= maybe mempty toWidget
|
-- cell $ traverse (formatTime SelFormatDateTime) courseRegisterFrom >>= maybe mempty toWidget
|
||||||
|
|
||||||
colRegTo :: IsDBTable m a => Colonnade _ CourseTableData (DBCell m a)
|
colRegTo :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
||||||
colRegTo = sortable (Just "register-to") (i18nCell MsgRegisterTo)
|
colRegTo = sortable (Just "register-to") (i18nCell MsgRegisterTo)
|
||||||
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _) } ->
|
$ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _) } ->
|
||||||
maybe mempty dateTimeCell courseRegisterTo
|
maybe mempty dateTimeCell courseRegisterTo
|
||||||
|
|
||||||
colMembers :: IsDBTable m a => Colonnade _ CourseTableData (DBCell m a)
|
colMembers :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
||||||
colMembers = sortable (Just "members") (i18nCell MsgCourseMembers)
|
colMembers = sortable (Just "members") (i18nCell MsgCourseMembers)
|
||||||
$ \DBRow{ dbrOutput=(Entity _ Course{..}, currentParticipants, _, _) } -> i18nCell $ case courseCapacity of
|
$ \DBRow{ dbrOutput=(Entity _ Course{..}, currentParticipants, _, _) } -> i18nCell $ case courseCapacity of
|
||||||
Nothing -> MsgCourseMembersCount currentParticipants
|
Nothing -> MsgCourseMembersCount currentParticipants
|
||||||
Just limit -> MsgCourseMembersCountLimited currentParticipants limit
|
Just limit -> MsgCourseMembersCountLimited currentParticipants limit
|
||||||
|
|
||||||
colRegistered :: IsDBTable m a => Colonnade _ CourseTableData (DBCell m a)
|
colRegistered :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a)
|
||||||
colRegistered = sortable (Just "registered") (i18nCell MsgRegistered)
|
colRegistered = sortable (Just "registered") (i18nCell MsgRegistered)
|
||||||
$ \DBRow{ dbrOutput=(_, _, registered, _) } -> tickmarkCell registered
|
$ \DBRow{ dbrOutput=(_, _, registered, _) } -> tickmarkCell registered
|
||||||
|
|
||||||
|
|||||||
@ -145,7 +145,7 @@ getTermShowR = do
|
|||||||
, dbtIdent = "terms" :: Text
|
, dbtIdent = "terms" :: Text
|
||||||
}
|
}
|
||||||
defaultLayout $ do
|
defaultLayout $ do
|
||||||
setTitle "Freigeschaltete Semester"
|
setTitleI MsgTermsHeading
|
||||||
$(widgetFile "terms")
|
$(widgetFile "terms")
|
||||||
|
|
||||||
getTermEditR :: Handler Html
|
getTermEditR :: Handler Html
|
||||||
|
|||||||
@ -522,6 +522,30 @@ secretJsonField = Field{..}
|
|||||||
|]
|
|]
|
||||||
fieldEnctype = UrlEncoded
|
fieldEnctype = UrlEncoded
|
||||||
|
|
||||||
|
boolField :: ( MonadHandler m
|
||||||
|
, HandlerSite m ~ UniWorX
|
||||||
|
)
|
||||||
|
=> Field m Bool
|
||||||
|
boolField = Field
|
||||||
|
{ fieldParse = \e _ -> return $ boolParser e
|
||||||
|
, fieldView = \theId name attrs val isReq -> $(widgetFile "widgets/fields/bool")
|
||||||
|
, fieldEnctype = UrlEncoded
|
||||||
|
}
|
||||||
|
where
|
||||||
|
boolParser [] = Right Nothing
|
||||||
|
boolParser (x:_) = case x of
|
||||||
|
"" -> Right Nothing
|
||||||
|
"none" -> Right Nothing
|
||||||
|
"yes" -> Right $ Just True
|
||||||
|
"on" -> Right $ Just True
|
||||||
|
"no" -> Right $ Just False
|
||||||
|
"true" -> Right $ Just True
|
||||||
|
"false" -> Right $ Just False
|
||||||
|
t -> Left $ SomeMessage $ MsgInvalidBool t
|
||||||
|
showVal = either $ const False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
funcForm :: forall k v m.
|
funcForm :: forall k v m.
|
||||||
( Finite k, Ord k
|
( Finite k, Ord k
|
||||||
@ -671,5 +695,5 @@ formResultModal res finalDest handler = maybeT_ $ do
|
|||||||
if
|
if
|
||||||
| isModal -> sendResponse $ toJSON messages
|
| isModal -> sendResponse $ toJSON messages
|
||||||
| otherwise -> do
|
| otherwise -> do
|
||||||
forM_ messages $ \Message{..} -> addMessage messageClass messageContent
|
forM_ messages $ \Message{..} -> addMessage messageStatus messageContent
|
||||||
redirect finalDest
|
redirect finalDest
|
||||||
|
|||||||
@ -3,7 +3,7 @@ module Import.NoFoundation
|
|||||||
, MForm
|
, MForm
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import ClassyPrelude.Yesod as Import hiding (formatTime, derivePersistFieldJSON, addMessage, addMessageI, (.=), MForm, Proxy, foldlM, static)
|
import ClassyPrelude.Yesod as Import hiding (formatTime, derivePersistFieldJSON, addMessage, addMessageI, (.=), MForm, Proxy, foldlM, static, boolField)
|
||||||
import Model as Import
|
import Model as Import
|
||||||
import Model.Types.JSON as Import
|
import Model.Types.JSON as Import
|
||||||
import Model.Migration as Import
|
import Model.Migration as Import
|
||||||
|
|||||||
@ -20,8 +20,8 @@ import Data.CaseInsensitive (CI)
|
|||||||
import Data.CaseInsensitive.Instances ()
|
import Data.CaseInsensitive.Instances ()
|
||||||
|
|
||||||
import Text.Blaze (ToMarkup, toMarkup, Markup)
|
import Text.Blaze (ToMarkup, toMarkup, Markup)
|
||||||
|
import Utils.Message (MessageStatus)
|
||||||
|
|
||||||
import Utils.Message (MessageClass)
|
|
||||||
import Settings.Cluster (ClusterSettingsKey)
|
import Settings.Cluster (ClusterSettingsKey)
|
||||||
|
|
||||||
import Data.Binary (Binary)
|
import Data.Binary (Binary)
|
||||||
|
|||||||
@ -39,7 +39,7 @@ import qualified Data.Text.Encoding as Text
|
|||||||
|
|
||||||
import qualified Ldap.Client as Ldap
|
import qualified Ldap.Client as Ldap
|
||||||
|
|
||||||
import Utils hiding (MessageClass(..))
|
import Utils hiding (MessageStatus(..))
|
||||||
import Control.Lens
|
import Control.Lens
|
||||||
|
|
||||||
import Data.Maybe (fromJust)
|
import Data.Maybe (fromJust)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
module Utils.Message
|
module Utils.Message
|
||||||
( MessageClass(..)
|
( MessageStatus(..)
|
||||||
, UnknownMessageClass(..)
|
, UnknownMessageStatus(..)
|
||||||
, addMessage, addMessageI, addMessageIHamlet, addMessageFile, addMessageWidget
|
, addMessage, addMessageI, addMessageIHamlet, addMessageFile, addMessageWidget
|
||||||
, Message(..)
|
, Message(..)
|
||||||
, messageI, messageIHamlet, messageFile, messageWidget
|
, messageI, messageIHamlet, messageFile, messageWidget
|
||||||
@ -25,64 +25,64 @@ import Text.Blaze.Html.Renderer.Text (renderHtml)
|
|||||||
import Text.HTML.SanitizeXSS (sanitizeBalance)
|
import Text.HTML.SanitizeXSS (sanitizeBalance)
|
||||||
|
|
||||||
|
|
||||||
data MessageClass = Error | Warning | Info | Success
|
data MessageStatus = Error | Warning | Info | Success
|
||||||
deriving (Eq, Ord, Enum, Bounded, Show, Read, Lift)
|
deriving (Eq, Ord, Enum, Bounded, Show, Read, Lift)
|
||||||
|
|
||||||
instance Universe MessageClass
|
instance Universe MessageStatus
|
||||||
instance Finite MessageClass
|
instance Finite MessageStatus
|
||||||
|
|
||||||
deriveJSON defaultOptions
|
deriveJSON defaultOptions
|
||||||
{ constructorTagModifier = camelToPathPiece
|
{ constructorTagModifier = camelToPathPiece
|
||||||
} ''MessageClass
|
} ''MessageStatus
|
||||||
|
|
||||||
nullaryPathPiece ''MessageClass camelToPathPiece
|
nullaryPathPiece ''MessageStatus camelToPathPiece
|
||||||
derivePersistField "MessageClass"
|
derivePersistField "MessageStatus"
|
||||||
|
|
||||||
newtype UnknownMessageClass = UnknownMessageClass Text
|
newtype UnknownMessageStatus = UnknownMessageStatus Text
|
||||||
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
|
|
||||||
instance Exception UnknownMessageClass
|
instance Exception UnknownMessageStatus
|
||||||
|
|
||||||
|
|
||||||
data Message = Message
|
data Message = Message
|
||||||
{ messageClass :: MessageClass
|
{ messageStatus :: MessageStatus
|
||||||
, messageContent :: Html
|
, messageContent :: Html
|
||||||
}
|
}
|
||||||
|
|
||||||
instance Eq Message where
|
instance Eq Message where
|
||||||
a == b = ((==) `on` messageClass) a b && ((==) `on` renderHtml . messageContent) a b
|
a == b = ((==) `on` messageStatus) a b && ((==) `on` renderHtml . messageContent) a b
|
||||||
|
|
||||||
instance Ord Message where
|
instance Ord Message where
|
||||||
a `compare` b = (compare `on` messageClass) a b `mappend` (compare `on` renderHtml . messageContent) a b
|
a `compare` b = (compare `on` messageStatus) a b `mappend` (compare `on` renderHtml . messageContent) a b
|
||||||
|
|
||||||
instance ToJSON Message where
|
instance ToJSON Message where
|
||||||
toJSON Message{..} = object
|
toJSON Message{..} = object
|
||||||
[ "class" .= messageClass
|
[ "status" .= messageStatus
|
||||||
, "content" .= renderHtml messageContent
|
, "content" .= renderHtml messageContent
|
||||||
]
|
]
|
||||||
|
|
||||||
instance FromJSON Message where
|
instance FromJSON Message where
|
||||||
parseJSON = withObject "Message" $ \o -> do
|
parseJSON = withObject "Message" $ \o -> do
|
||||||
messageClass <- o .: "class"
|
messageStatus <- o .: "status"
|
||||||
messageContent <- preEscapedText . sanitizeBalance <$> o .: "content"
|
messageContent <- preEscapedText . sanitizeBalance <$> o .: "content"
|
||||||
return Message{..}
|
return Message{..}
|
||||||
|
|
||||||
|
|
||||||
addMessage :: MonadHandler m => MessageClass -> Html -> m ()
|
addMessage :: MonadHandler m => MessageStatus -> Html -> m ()
|
||||||
addMessage mc = ClassyPrelude.Yesod.addMessage (toPathPiece mc)
|
addMessage mc = ClassyPrelude.Yesod.addMessage (toPathPiece mc)
|
||||||
|
|
||||||
addMessageI :: (MonadHandler m, RenderMessage (HandlerSite m) msg) => MessageClass -> msg -> m ()
|
addMessageI :: (MonadHandler m, RenderMessage (HandlerSite m) msg) => MessageStatus -> msg -> m ()
|
||||||
addMessageI mc = ClassyPrelude.Yesod.addMessageI (toPathPiece mc)
|
addMessageI mc = ClassyPrelude.Yesod.addMessageI (toPathPiece mc)
|
||||||
|
|
||||||
messageI :: (MonadHandler m, RenderMessage (HandlerSite m) msg) => MessageClass -> msg -> m Message
|
messageI :: (MonadHandler m, RenderMessage (HandlerSite m) msg) => MessageStatus -> msg -> m Message
|
||||||
messageI messageClass msg = do
|
messageI messageStatus msg = do
|
||||||
messageContent <- toHtml . ($ msg) <$> getMessageRender
|
messageContent <- toHtml . ($ msg) <$> getMessageRender
|
||||||
return Message{..}
|
return Message{..}
|
||||||
|
|
||||||
addMessageIHamlet :: ( MonadHandler m
|
addMessageIHamlet :: ( MonadHandler m
|
||||||
, RenderMessage (HandlerSite m) msg
|
, RenderMessage (HandlerSite m) msg
|
||||||
, HandlerSite m ~ site
|
, HandlerSite m ~ site
|
||||||
) => MessageClass -> HtmlUrlI18n msg (Route site) -> m ()
|
) => MessageStatus -> HtmlUrlI18n msg (Route site) -> m ()
|
||||||
addMessageIHamlet mc iHamlet = do
|
addMessageIHamlet mc iHamlet = do
|
||||||
mr <- getMessageRender
|
mr <- getMessageRender
|
||||||
ClassyPrelude.Yesod.addMessage (toPathPiece mc) =<< withUrlRenderer (iHamlet $ toHtml . mr)
|
ClassyPrelude.Yesod.addMessage (toPathPiece mc) =<< withUrlRenderer (iHamlet $ toHtml . mr)
|
||||||
@ -90,22 +90,22 @@ addMessageIHamlet mc iHamlet = do
|
|||||||
messageIHamlet :: ( MonadHandler m
|
messageIHamlet :: ( MonadHandler m
|
||||||
, RenderMessage (HandlerSite m) msg
|
, RenderMessage (HandlerSite m) msg
|
||||||
, HandlerSite m ~ site
|
, HandlerSite m ~ site
|
||||||
) => MessageClass -> HtmlUrlI18n msg (Route site) -> m Message
|
) => MessageStatus -> HtmlUrlI18n msg (Route site) -> m Message
|
||||||
messageIHamlet mc iHamlet = do
|
messageIHamlet mc iHamlet = do
|
||||||
mr <- getMessageRender
|
mr <- getMessageRender
|
||||||
Message mc <$> withUrlRenderer (iHamlet $ toHtml . mr)
|
Message mc <$> withUrlRenderer (iHamlet $ toHtml . mr)
|
||||||
|
|
||||||
addMessageFile :: MessageClass -> FilePath -> ExpQ
|
addMessageFile :: MessageStatus -> FilePath -> ExpQ
|
||||||
addMessageFile mc tPath = [e|addMessageIHamlet mc $(ihamletFile tPath)|]
|
addMessageFile mc tPath = [e|addMessageIHamlet mc $(ihamletFile tPath)|]
|
||||||
|
|
||||||
messageFile :: MessageClass -> FilePath -> ExpQ
|
messageFile :: MessageStatus -> FilePath -> ExpQ
|
||||||
messageFile mc tPath = [e|messageIHamlet mc $(ihamletFile tPath)|]
|
messageFile mc tPath = [e|messageIHamlet mc $(ihamletFile tPath)|]
|
||||||
|
|
||||||
addMessageWidget :: forall m site.
|
addMessageWidget :: forall m site.
|
||||||
( MonadHandler m
|
( MonadHandler m
|
||||||
, HandlerSite m ~ site
|
, HandlerSite m ~ site
|
||||||
, Yesod site
|
, Yesod site
|
||||||
) => MessageClass -> WidgetT site IO () -> m ()
|
) => MessageStatus -> WidgetT site IO () -> m ()
|
||||||
-- ^ _Note_: `addMessageWidget` ignores `pageTitle` and `pageHead`
|
-- ^ _Note_: `addMessageWidget` ignores `pageTitle` and `pageHead`
|
||||||
addMessageWidget mc wgt = do
|
addMessageWidget mc wgt = do
|
||||||
PageContent{pageBody} <- liftHandlerT $ widgetToPageContent wgt
|
PageContent{pageBody} <- liftHandlerT $ widgetToPageContent wgt
|
||||||
@ -115,7 +115,7 @@ messageWidget :: forall m site.
|
|||||||
( MonadHandler m
|
( MonadHandler m
|
||||||
, HandlerSite m ~ site
|
, HandlerSite m ~ site
|
||||||
, Yesod site
|
, Yesod site
|
||||||
) => MessageClass -> WidgetT site IO () -> m Message
|
) => MessageStatus -> WidgetT site IO () -> m Message
|
||||||
messageWidget mc wgt = do
|
messageWidget mc wgt = do
|
||||||
PageContent{pageBody} <- liftHandlerT $ widgetToPageContent wgt
|
PageContent{pageBody} <- liftHandlerT $ widgetToPageContent wgt
|
||||||
messageIHamlet mc (const pageBody :: HtmlUrlI18n (SomeMessage site) (Route site))
|
messageIHamlet mc (const pageBody :: HtmlUrlI18n (SomeMessage site) (Route site))
|
||||||
|
|||||||
12
stack.nix
12
stack.nix
@ -1,8 +1,14 @@
|
|||||||
{ ghc, nixpkgs ? (import <nixpkgs> {}) }:
|
{ ghc, nixpkgs ? import <nixpkgs> }:
|
||||||
|
|
||||||
let
|
let
|
||||||
inherit (nixpkgs) haskell pkgs;
|
snapshot = "lts-10.5";
|
||||||
haskellPackages = if ghc.version == pkgs.haskellPackages.ghc.version then pkgs.haskellPackages else pkgs.haskell.packages."ghc${builtins.replaceStrings ["."] [""] ghc.version}";
|
stackage = import (fetchTarball {
|
||||||
|
url = "https://stackage.serokell.io/drczwlyf6mi0ilh3kgv01wxwjfgvq14b-stackage/default.nix.tar.gz";
|
||||||
|
sha256 = "1bwlbxx6np0jfl6z9gkmmcq22crm0pa07a8zrwhz5gkal64y6jpz";
|
||||||
|
});
|
||||||
|
inherit (nixpkgs { overlays = [ stackage."${snapshot}" ]; }) haskell pkgs;
|
||||||
|
|
||||||
|
haskellPackages = pkgs.haskell.packages."${snapshot}";
|
||||||
in haskell.lib.buildStackProject {
|
in haskell.lib.buildStackProject {
|
||||||
inherit ghc;
|
inherit ghc;
|
||||||
name = "stackenv";
|
name = "stackenv";
|
||||||
|
|||||||
@ -17,6 +17,10 @@ packages:
|
|||||||
git: https://github.com/pngwjpgh/encoding.git
|
git: https://github.com/pngwjpgh/encoding.git
|
||||||
commit: 67bb87ceff53f0178c988dd4e15eeb2daee92b84
|
commit: 67bb87ceff53f0178c988dd4e15eeb2daee92b84
|
||||||
extra-dep: true
|
extra-dep: true
|
||||||
|
- location:
|
||||||
|
git: https://github.com/pngwjpgh/memcached-binary.git
|
||||||
|
commit: b5461747e7be226d3b67daebc3c9aefe8a4490ad
|
||||||
|
extra-dep: true
|
||||||
|
|
||||||
extra-deps:
|
extra-deps:
|
||||||
- colonnade-1.2.0
|
- colonnade-1.2.0
|
||||||
@ -45,8 +49,4 @@ extra-deps:
|
|||||||
- quickcheck-classes-0.4.14
|
- quickcheck-classes-0.4.14
|
||||||
- semirings-0.2.1.1
|
- semirings-0.2.1.1
|
||||||
|
|
||||||
- memcached-binary-0.2.0
|
|
||||||
|
|
||||||
allow-newer: true
|
|
||||||
|
|
||||||
resolver: lts-10.5
|
resolver: lts-10.5
|
||||||
|
|||||||
@ -57,7 +57,7 @@
|
|||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
|
|
||||||
&.js-show-hide__toggle::before {
|
&.js-show-hide__toggle::before {
|
||||||
top: 25px;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,7 +103,6 @@
|
|||||||
&::before {
|
&::before {
|
||||||
left: auto;
|
left: auto;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
top: 30px;
|
|
||||||
color: var(--color-font);
|
color: var(--color-font);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -314,6 +313,10 @@
|
|||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
background-color: var(--color-dark);
|
background-color: var(--color-dark);
|
||||||
color: var(--color-lightwhite);
|
color: var(--color-lightwhite);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-dark);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.asidenav__link-shorthand {
|
.asidenav__link-shorthand {
|
||||||
|
|||||||
@ -1,9 +1,78 @@
|
|||||||
.async-form-response {
|
.async-form-response {
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 18px;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.async-form-response::before,
|
||||||
|
.async-form-response::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 50%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.async-form-response--success::before {
|
||||||
|
content: '';
|
||||||
|
width: 17px;
|
||||||
|
height: 28px;
|
||||||
|
border: solid #069e04;
|
||||||
|
border-width: 0 5px 5px 0;
|
||||||
|
transform: translateX(-50%) rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.async-form-response--info::before {
|
||||||
|
content: '';
|
||||||
|
width: 5px;
|
||||||
|
height: 30px;
|
||||||
|
top: 10px;
|
||||||
|
background-color: #777;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
.async-form-response--info::after {
|
||||||
|
content: '';
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
background-color: #777;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.async-form-response--warning::before {
|
||||||
|
content: '';
|
||||||
|
width: 5px;
|
||||||
|
height: 30px;
|
||||||
|
background-color: rgb(255, 187, 0);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
.async-form-response--warning::after {
|
||||||
|
content: '';
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
top: 35px;
|
||||||
|
background-color: rgb(255, 187, 0);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.async-form-response--error::before {
|
||||||
|
content: '';
|
||||||
|
width: 5px;
|
||||||
|
height: 40px;
|
||||||
|
background-color: #940d0d;
|
||||||
|
transform: translateX(-50%) rotate(-45deg);
|
||||||
|
}
|
||||||
|
.async-form-response--error::after {
|
||||||
|
content: '';
|
||||||
|
width: 5px;
|
||||||
|
height: 40px;
|
||||||
|
background-color: #940d0d;
|
||||||
|
transform: translateX(-50%) rotate(45deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.async-form-loading {
|
.async-form-loading {
|
||||||
opacity: 0.1;
|
opacity: 0.1;
|
||||||
transition: opacity 800ms ease-in-out;
|
transition: opacity 800ms ease-out;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|||||||
5
static/css/utils/asyncTable.scss
Normal file
5
static/css/utils/asyncTable.scss
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.async-table--loading {
|
||||||
|
opacity: 0.7;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 400ms ease-out;
|
||||||
|
}
|
||||||
5
static/css/utils/asyncTableFilter.scss
Normal file
5
static/css/utils/asyncTableFilter.scss
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.async-table-filter--loading {
|
||||||
|
opacity: 0.7;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 400ms ease-out;
|
||||||
|
}
|
||||||
74
static/css/utils/checkbox.scss
Normal file
74
static/css/utils/checkbox.scss
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
|
||||||
|
/* CUSTOM CHECKBOXES */
|
||||||
|
/* Completely replaces legacy checkbox */
|
||||||
|
.checkbox {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
[type="checkbox"] {
|
||||||
|
position: fixed;
|
||||||
|
top: -1px;
|
||||||
|
left: -1px;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
box-shadow: inset 0 1px 2px 1px rgba(50, 50, 50, 0.05);
|
||||||
|
border: 2px solid var(--color-primary);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
label::before,
|
||||||
|
label::after {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
top: 12px;
|
||||||
|
left: 8px;
|
||||||
|
height: 2px;
|
||||||
|
width: 8px;
|
||||||
|
background-color: var(--color-font);
|
||||||
|
}
|
||||||
|
|
||||||
|
:checked + label {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[type="checkbox"]:focus + label {
|
||||||
|
border-color: #3273dc;
|
||||||
|
box-shadow: 0 0 0 0.125em rgba(50,115,220,.25);
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:checked + label::before,
|
||||||
|
:checked + label::after {
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
:checked + label::before {
|
||||||
|
background-color: white;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:checked + label::after {
|
||||||
|
background-color: white;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
top: 11px;
|
||||||
|
width: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[disabled] + label {
|
||||||
|
pointer-events: none;
|
||||||
|
border: none;
|
||||||
|
opacity: 0.6;
|
||||||
|
filter: grayscale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -95,7 +95,6 @@ select {
|
|||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
-webkit-appearance: none;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@ -162,6 +161,11 @@ textarea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* OPTIONS */
|
/* OPTIONS */
|
||||||
|
|
||||||
|
select {
|
||||||
|
-webkit-appearance: menulist;
|
||||||
|
}
|
||||||
|
|
||||||
select,
|
select,
|
||||||
option {
|
option {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@ -171,7 +175,8 @@ option {
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
color: #363636;
|
color: #363636;
|
||||||
min-width: 200px;
|
min-width: 250px;
|
||||||
|
width: auto;
|
||||||
background-color: #f3f3f3;
|
background-color: #f3f3f3;
|
||||||
box-shadow: inset 0 1px 2px 1px rgba(50,50,50,.05);
|
box-shadow: inset 0 1px 2px 1px rgba(50,50,50,.05);
|
||||||
}
|
}
|
||||||
@ -183,130 +188,6 @@ option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CUSTOM LEGACY CHECKBOX AND RADIO BOXES */
|
|
||||||
input[type="checkbox"] {
|
|
||||||
position: relative;
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
input[type="checkbox"]::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
background-color: var(--color-lighter);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
input[type="checkbox"]:checked::before {
|
|
||||||
background-color: var(--color-light);
|
|
||||||
}
|
|
||||||
input[type="checkbox"]:checked::after {
|
|
||||||
content: '✓';
|
|
||||||
position: absolute;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: white;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* CUSTOM CHECKBOXES AND RADIO BOXES */
|
|
||||||
/* Completely replaces legacy checkbox and radiobox */
|
|
||||||
.checkbox,
|
|
||||||
.radio {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
[type="checkbox"],
|
|
||||||
[type="radio"] {
|
|
||||||
position: fixed;
|
|
||||||
top: -1px;
|
|
||||||
left: -1px;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
background-color: #f3f3f3;
|
|
||||||
box-shadow: inset 0 1px 2px 1px rgba(50, 50, 50, 0.05);
|
|
||||||
border: 2px solid var(--color-primary);
|
|
||||||
border-radius: 4px;
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
label::before,
|
|
||||||
label::after {
|
|
||||||
position: absolute;
|
|
||||||
display: block;
|
|
||||||
top: 12px;
|
|
||||||
left: 8px;
|
|
||||||
height: 2px;
|
|
||||||
width: 8px;
|
|
||||||
background-color: var(--color-font);
|
|
||||||
}
|
|
||||||
|
|
||||||
:checked + label {
|
|
||||||
background-color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
[type="checkbox"]:focus + label,
|
|
||||||
[type="radio"]:focus + label {
|
|
||||||
border-color: #3273dc;
|
|
||||||
box-shadow: 0 0 0 0.125em rgba(50,115,220,.25);
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:checked + label::before,
|
|
||||||
:checked + label::after {
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
:checked + label::before {
|
|
||||||
background-color: white;
|
|
||||||
transform: rotate(45deg);
|
|
||||||
left: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:checked + label::after {
|
|
||||||
background-color: white;
|
|
||||||
transform: rotate(-45deg);
|
|
||||||
top: 11px;
|
|
||||||
width: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[disabled] + label {
|
|
||||||
pointer-events: none;
|
|
||||||
border: none;
|
|
||||||
opacity: 0.6;
|
|
||||||
filter: grayscale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 2px;
|
|
||||||
left: 2px;
|
|
||||||
right: 2px;
|
|
||||||
bottom: 2px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 2px solid white;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* CUSTOM FILE INPUT */
|
/* CUSTOM FILE INPUT */
|
||||||
.file-input__label {
|
.file-input__label {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translate(-50%, -50%) scale(0.8, 0.8);
|
transform: translate(-50%, -50%) scale(0.8, 0.8);
|
||||||
display: block;
|
display: flex;
|
||||||
background-color: rgba(255, 255, 255, 1);
|
background-color: rgba(255, 255, 255, 1);
|
||||||
min-width: 60vw;
|
min-width: 60vw;
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
@ -26,10 +26,6 @@
|
|||||||
z-index: 200;
|
z-index: 200;
|
||||||
transform: translate(-50%, -50%) scale(1, 1);
|
transform: translate(-50%, -50%) scale(1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal__content {
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
@ -96,3 +92,8 @@
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal__content {
|
||||||
|
margin: 20px 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|||||||
76
static/css/utils/radio.scss
Normal file
76
static/css/utils/radio.scss
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/* CUSTOM RADIO BOXES */
|
||||||
|
/* Completely replaces native radiobox */
|
||||||
|
|
||||||
|
.radio-group {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-group__option {
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
[type="radio"] {
|
||||||
|
position: fixed;
|
||||||
|
top: -1px;
|
||||||
|
left: -1px;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
height: 34px;
|
||||||
|
min-width: 42px;
|
||||||
|
line-height: 34px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0 13px;
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
box-shadow: inset 2px 1px 2px 1px rgba(50, 50, 50, 0.05);
|
||||||
|
color: var(--color-font);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
:checked + label {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: var(--color-lightwhite);
|
||||||
|
box-shadow: inset -2px -1px 2px 1px rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
:focus + label {
|
||||||
|
border-color: #3273dc;
|
||||||
|
box-shadow: 0 0 0.125em 0 rgba(50,115,220,0.8);
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[disabled] + label {
|
||||||
|
pointer-events: none;
|
||||||
|
border: none;
|
||||||
|
opacity: 0.6;
|
||||||
|
filter: grayscale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio:first-child {
|
||||||
|
label {
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio:last-child {
|
||||||
|
label {
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.radio + .radio {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,22 +1,30 @@
|
|||||||
|
$show-hide-toggle-size: 6px;
|
||||||
|
|
||||||
.js-show-hide__toggle {
|
.js-show-hide__toggle {
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
padding: 3px 7px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-grey-lighter);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.js-show-hide__toggle::before {
|
.js-show-hide__toggle::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 0;
|
width: $show-hide-toggle-size;
|
||||||
height: 0;
|
height: $show-hide-toggle-size;
|
||||||
left: -28px;
|
left: -15px;
|
||||||
top: 6px;
|
top: 12px - $show-hide-toggle-size / 2;
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
border-right: 8px solid transparent;
|
border-right: 2px solid currentColor;
|
||||||
border-top: 8px solid transparent;
|
border-top: 2px solid currentColor;
|
||||||
border-left: 8px solid transparent;
|
|
||||||
border-bottom: 8px solid currentColor;
|
|
||||||
transition: transform .2s ease;
|
transition: transform .2s ease;
|
||||||
transform-origin: 8px 12px;
|
transform-origin: ($show-hide-toggle-size / 2);
|
||||||
|
transform: translateY($show-hide-toggle-size) rotate(-45deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.js-show-hide__target {
|
.js-show-hide__target {
|
||||||
@ -26,7 +34,7 @@
|
|||||||
.js-show-hide--collapsed {
|
.js-show-hide--collapsed {
|
||||||
|
|
||||||
.js-show-hide__toggle::before {
|
.js-show-hide__toggle::before {
|
||||||
transform: rotate(180deg);
|
transform: translateY($show-hide-toggle-size / 3) rotate(135deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.js-show-hide__target {
|
.js-show-hide__target {
|
||||||
|
|||||||
@ -87,5 +87,10 @@
|
|||||||
|
|
||||||
initToggler();
|
initToggler();
|
||||||
alertElements.forEach(initAlert);
|
alertElements.forEach(initAlert);
|
||||||
|
|
||||||
|
return {
|
||||||
|
scope: alertsEl,
|
||||||
|
destroy: function() {},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -55,5 +55,10 @@
|
|||||||
|
|
||||||
initFavoritesButton();
|
initFavoritesButton();
|
||||||
initAsidenavSubmenus();
|
initAsidenavSubmenus();
|
||||||
|
|
||||||
|
return {
|
||||||
|
scope: asideEl,
|
||||||
|
destroy: function() {},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -6,9 +6,12 @@
|
|||||||
var ASYNC_FORM_RESPONSE_CLASS = 'async-form-response';
|
var ASYNC_FORM_RESPONSE_CLASS = 'async-form-response';
|
||||||
var ASYNC_FORM_LOADING_CLASS = 'async-form-loading';
|
var ASYNC_FORM_LOADING_CLASS = 'async-form-loading';
|
||||||
var ASYNC_FORM_MIN_DELAY = 600;
|
var ASYNC_FORM_MIN_DELAY = 600;
|
||||||
|
var DEFAULT_FAILURE_MESSAGE = 'The response we received from the server did not match what we expected. Please let us know this happened via the help widget in the top navigation.';
|
||||||
|
|
||||||
window.utils.asyncForm = function(formElement, options) {
|
window.utils.asyncForm = function(formElement, options) {
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
var lastRequestTimestamp = 0;
|
var lastRequestTimestamp = 0;
|
||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
@ -16,19 +19,27 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function processResponse(response) {
|
function processResponse(response) {
|
||||||
var responseElement = document.createElement('div');
|
var responseElement = makeResponseElement(response.content, response.status);
|
||||||
responseElement.classList.add(ASYNC_FORM_RESPONSE_CLASS);
|
|
||||||
responseElement.innerHTML = response.content;
|
|
||||||
var parentElement = formElement.parentElement;
|
var parentElement = formElement.parentElement;
|
||||||
|
|
||||||
// make sure there is a delay between click and response
|
// make sure there is a delay between click and response
|
||||||
var delay = Math.max(0, ASYNC_FORM_MIN_DELAY + lastRequestTimestamp - Date.now());
|
var delay = Math.max(0, ASYNC_FORM_MIN_DELAY + lastRequestTimestamp - Date.now());
|
||||||
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
parentElement.insertBefore(responseElement, formElement);
|
parentElement.insertBefore(responseElement, formElement);
|
||||||
formElement.remove();
|
formElement.remove();
|
||||||
}, delay);
|
}, delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeResponseElement(content, status) {
|
||||||
|
var responseElement = document.createElement('div');
|
||||||
|
status = status || 'info';
|
||||||
|
responseElement.classList.add(ASYNC_FORM_RESPONSE_CLASS);
|
||||||
|
responseElement.classList.add(ASYNC_FORM_RESPONSE_CLASS + '--' + status);
|
||||||
|
responseElement.innerHTML = content;
|
||||||
|
return responseElement;
|
||||||
|
}
|
||||||
|
|
||||||
function submitHandler(event) {
|
function submitHandler(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
@ -47,14 +58,29 @@
|
|||||||
|
|
||||||
window.utils.httpClient.post(url, headers, body)
|
window.utils.httpClient.post(url, headers, body)
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
return response.json();
|
if (response.headers.get("content-type").indexOf("application/json") !== -1) {// checking response header
|
||||||
|
return response.json();
|
||||||
|
} else {
|
||||||
|
throw new TypeError('Unexpected Content-Type. Expected Content-Type: "application/json". Requested URL:' + url + '"');
|
||||||
|
}
|
||||||
}).then(function(response) {
|
}).then(function(response) {
|
||||||
processResponse(response[0])
|
processResponse(response[0]);
|
||||||
}).catch(function(error) {
|
}).catch(function(error) {
|
||||||
console.error('could not fetch or process response from ' + url, { error });
|
var failureMessage = DEFAULT_FAILURE_MESSAGE;
|
||||||
|
if (options.i18n && options.i18n.asyncFormFailure) {
|
||||||
|
failureMessage = options.i18n.asyncFormFailure;
|
||||||
|
}
|
||||||
|
processResponse({ content: failureMessage });
|
||||||
|
|
||||||
|
formElement.classList.remove(ASYNC_FORM_LOADING_CLASS);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setup();
|
setup();
|
||||||
|
|
||||||
|
return {
|
||||||
|
scope: formElement,
|
||||||
|
destroy: function() {},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -5,6 +5,10 @@
|
|||||||
|
|
||||||
var HEADER_HEIGHT = 80;
|
var HEADER_HEIGHT = 80;
|
||||||
var RESET_OPTIONS = [ 'scrollTo' ];
|
var RESET_OPTIONS = [ 'scrollTo' ];
|
||||||
|
var TABLE_FILTER_FORM_CLASS = 'table-filter-form';
|
||||||
|
var ASYNC_TABLE_CONTENT_CHANGED_CLASS = 'async-table--changed';
|
||||||
|
var ASYNC_TABLE_LOADING_CLASS = 'async-table--loading';
|
||||||
|
var JS_INITIALIZED_CLASS = 'js-async-table-initialized';
|
||||||
|
|
||||||
window.utils.asyncTable = function(wrapper, options) {
|
window.utils.asyncTable = function(wrapper, options) {
|
||||||
|
|
||||||
@ -17,6 +21,8 @@
|
|||||||
var pagesizeForm;
|
var pagesizeForm;
|
||||||
var scrollTable;
|
var scrollTable;
|
||||||
|
|
||||||
|
var utilInstances = [];
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
var table = wrapper.querySelector('#' + tableIdent);
|
var table = wrapper.querySelector('#' + tableIdent);
|
||||||
|
|
||||||
@ -42,17 +48,30 @@
|
|||||||
// pagesize form
|
// pagesize form
|
||||||
pagesizeForm = wrapper.querySelector('#' + tableIdent + '-pagesize-form');
|
pagesizeForm = wrapper.querySelector('#' + tableIdent + '-pagesize-form');
|
||||||
|
|
||||||
|
// check all
|
||||||
|
utilInstances.push(window.utils.setup('checkAll', wrapper));
|
||||||
|
|
||||||
|
// showhide
|
||||||
|
utilInstances.push(window.utils.setup('showHide', wrapper));
|
||||||
|
|
||||||
|
// filter
|
||||||
|
var filterForm = wrapper.querySelector('.' + TABLE_FILTER_FORM_CLASS);
|
||||||
|
if (filterForm) {
|
||||||
|
options.updateTableFrom = updateTableFrom;
|
||||||
|
utilInstances.push(window.utils.setup('asyncTableFilter', filterForm, options));
|
||||||
|
}
|
||||||
|
|
||||||
// take options into account
|
// take options into account
|
||||||
if (options && options.scrollTo) {
|
if (options.scrollTo) {
|
||||||
window.scrollTo(options.scrollTo);
|
window.scrollTo(options.scrollTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options && options.horizPos && scrollTable) {
|
if (options.horizPos && scrollTable) {
|
||||||
scrollTable.scrollLeft = options.horizPos;
|
scrollTable.scrollLeft = options.horizPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
setupListeners();
|
setupListeners();
|
||||||
wrapper.classList.add('js-initialized');
|
wrapper.classList.add(JS_INITIALIZED_CLASS);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupListeners() {
|
function setupListeners() {
|
||||||
@ -117,32 +136,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function changePagesizeHandler(event) {
|
function changePagesizeHandler(event) {
|
||||||
var currentTableUrl = options.currentUrl || window.location.href;
|
var pagesizeParamKey = tableIdent + '-pagesize';
|
||||||
var url = getUrlWithUpdatedPagesize(currentTableUrl, event.target.value);
|
var pageParamKey = tableIdent + '-page';
|
||||||
url = new URL(getUrlWithResetPagenumber(url));
|
var url = new URL(options.currentUrl || window.location.href);
|
||||||
|
url.searchParams.set(pagesizeParamKey, event.target.value);
|
||||||
|
url.searchParams.set(pageParamKey, 0);
|
||||||
updateTableFrom(url);
|
updateTableFrom(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUrlWithUpdatedPagesize(url, pagesize) {
|
|
||||||
if (url.indexOf('pagesize') >= 0) {
|
|
||||||
return url.replace(/pagesize=(\d+|all)/, 'pagesize=' + pagesize);
|
|
||||||
} else if (url.indexOf('?') >= 0) {
|
|
||||||
return url += '&' + tableIdent + '-pagesize=' + pagesize;
|
|
||||||
}
|
|
||||||
|
|
||||||
return url += '?' + tableIdent + '-pagesize=' + pagesize;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getUrlWithResetPagenumber(url) {
|
|
||||||
return url.replace(/-page=\d+/, '-page=0');
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetches new sorted table from url with params and replaces contents of current table
|
// fetches new sorted table from url with params and replaces contents of current table
|
||||||
function updateTableFrom(url, tableOptions) {
|
function updateTableFrom(url, tableOptions, callback) {
|
||||||
if (!window.utils.httpClient) {
|
if (!window.utils.httpClient) {
|
||||||
throw new Error('httpClient not found!');
|
throw new Error('httpClient not found!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wrapper.classList.add(ASYNC_TABLE_LOADING_CLASS);
|
||||||
|
|
||||||
tableOptions = tableOptions || {};
|
tableOptions = tableOptions || {};
|
||||||
var headers = {
|
var headers = {
|
||||||
'Accept': 'text/html',
|
'Accept': 'text/html',
|
||||||
@ -157,6 +166,11 @@
|
|||||||
tableOptions.currentUrl = url.href;
|
tableOptions.currentUrl = url.href;
|
||||||
removeListeners();
|
removeListeners();
|
||||||
updateWrapperContents(data, tableOptions);
|
updateWrapperContents(data, tableOptions);
|
||||||
|
if (callback && typeof callback === 'function') {
|
||||||
|
callback(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper.classList.remove(ASYNC_TABLE_LOADING_CLASS);
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
@ -165,11 +179,11 @@
|
|||||||
function updateWrapperContents(newHtml, tableOptions) {
|
function updateWrapperContents(newHtml, tableOptions) {
|
||||||
tableOptions = tableOptions || {};
|
tableOptions = tableOptions || {};
|
||||||
wrapper.innerHTML = newHtml;
|
wrapper.innerHTML = newHtml;
|
||||||
wrapper.classList.remove("js-initialized");
|
wrapper.classList.remove(JS_INITIALIZED_CLASS);
|
||||||
|
wrapper.classList.add(ASYNC_TABLE_CONTENT_CHANGED_CLASS);
|
||||||
|
|
||||||
|
destroyUtils();
|
||||||
|
|
||||||
// setup the wrapper and its components to behave async again
|
|
||||||
window.utils.teardown('asyncTable');
|
|
||||||
window.utils.teardown('form');
|
|
||||||
// merge global options and table specific options
|
// merge global options and table specific options
|
||||||
var resetOptions = {};
|
var resetOptions = {};
|
||||||
Object.keys(options)
|
Object.keys(options)
|
||||||
@ -195,13 +209,24 @@
|
|||||||
window.utils.setup('asyncTable', wrapper, combinedOptions);
|
window.utils.setup('asyncTable', wrapper, combinedOptions);
|
||||||
|
|
||||||
Array.from(wrapper.querySelectorAll('form')).forEach(function(form) {
|
Array.from(wrapper.querySelectorAll('form')).forEach(function(form) {
|
||||||
window.utils.setup('form', form);
|
utilInstances.push(window.utils.setup('form', form));
|
||||||
});
|
});
|
||||||
Array.from(wrapper.querySelectorAll('.modal')).forEach(function(modal) {
|
Array.from(wrapper.querySelectorAll('.modal')).forEach(function(modal) {
|
||||||
window.utils.setup('modal', modal);
|
utilInstances.push(window.utils.setup('modal', modal));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroyUtils() {
|
||||||
|
utilInstances.forEach(function(utilInstance) {
|
||||||
|
utilInstance.destroy();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
|
return {
|
||||||
|
scope: wrapper,
|
||||||
|
destroy: destroyUtils,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
166
static/js/utils/asyncTableFilter.js
Normal file
166
static/js/utils/asyncTableFilter.js
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
window.utils = window.utils || {};
|
||||||
|
|
||||||
|
var ASYNC_TABLE_FILTER_LOADING_CLASS = 'async-table-filter--loading';
|
||||||
|
var JS_INITIALIZED_CLASS = 'js-async-table-filter-initialized';
|
||||||
|
var INPUT_DEBOUNCE = 600;
|
||||||
|
|
||||||
|
// debounce function, taken from Underscore.js
|
||||||
|
function debounce(func, wait, immediate) {
|
||||||
|
var timeout;
|
||||||
|
return function() {
|
||||||
|
var context = this, args = arguments;
|
||||||
|
var later = function() {
|
||||||
|
timeout = null;
|
||||||
|
if (!immediate) func.apply(context, args);
|
||||||
|
};
|
||||||
|
var callNow = immediate && !timeout;
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(later, wait);
|
||||||
|
if (callNow) func.apply(context, args);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
window.utils.asyncTableFilter = function(formElement, options) {
|
||||||
|
if (!options || !options.updateTableFrom) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formElement.matches('.' + JS_INITIALIZED_CLASS)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
options = options || {};
|
||||||
|
var tableIdent = options.dbtIdent;
|
||||||
|
|
||||||
|
var formId = formElement.querySelector('[name="_formid"]').value;
|
||||||
|
var inputs = {
|
||||||
|
search: [],
|
||||||
|
input: [],
|
||||||
|
change: [],
|
||||||
|
select: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
function setup() {
|
||||||
|
gatherInputs();
|
||||||
|
addEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
function gatherInputs() {
|
||||||
|
Array.from(formElement.querySelectorAll('input[type="search"]')).forEach(function(input) {
|
||||||
|
inputs.search.push(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
Array.from(formElement.querySelectorAll('input[type="text"]')).forEach(function(input) {
|
||||||
|
inputs.input.push(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
Array.from(formElement.querySelectorAll('input:not([type="text"]):not([type="search"])')).forEach(function(input) {
|
||||||
|
inputs.change.push(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
Array.from(formElement.querySelectorAll('select')).forEach(function(input) {
|
||||||
|
inputs.select.push(input);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addEventListeners() {
|
||||||
|
inputs.search.forEach(function(input) {
|
||||||
|
var debouncedInput = debounce(function() {
|
||||||
|
if (input.value.length === 0 || input.value.length > 2) {
|
||||||
|
updateTable();
|
||||||
|
}
|
||||||
|
}, INPUT_DEBOUNCE);
|
||||||
|
input.addEventListener('input', debouncedInput);
|
||||||
|
});
|
||||||
|
|
||||||
|
inputs.input.forEach(function(input) {
|
||||||
|
var debouncedInput = debounce(function() {
|
||||||
|
if (input.value.length === 0 || input.value.length > 2) {
|
||||||
|
updateTable();
|
||||||
|
}
|
||||||
|
}, INPUT_DEBOUNCE);
|
||||||
|
input.addEventListener('input', debouncedInput);
|
||||||
|
});
|
||||||
|
|
||||||
|
inputs.change.forEach(function(input) {
|
||||||
|
input.addEventListener('change', function() {
|
||||||
|
updateTable();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
inputs.select.forEach(function(input) {
|
||||||
|
input.addEventListener('change', function() {
|
||||||
|
updateTable();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
formElement.addEventListener('submit', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
updateTable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTable() {
|
||||||
|
var url = serializeFormToURL();
|
||||||
|
var callback = null;
|
||||||
|
|
||||||
|
formElement.classList.add(ASYNC_TABLE_FILTER_LOADING_CLASS);
|
||||||
|
|
||||||
|
var focusedSearch = inputs.search.reduce(function(acc, input) {
|
||||||
|
return acc || (input.matches(':focus') && input);
|
||||||
|
}, null);
|
||||||
|
// focus search input
|
||||||
|
if (focusedSearch) {
|
||||||
|
var selectionStart = focusedSearch.selectionStart;
|
||||||
|
callback = function(wrapper) {
|
||||||
|
var search = wrapper.querySelector('input[type="search"]');
|
||||||
|
if (search) {
|
||||||
|
search.focus();
|
||||||
|
search.selectionStart = selectionStart;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
options.updateTableFrom(url, options, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeFormToURL() {
|
||||||
|
var url = new URL(options.currentUrl || window.location.href);
|
||||||
|
url.searchParams.set('_formid', formId);
|
||||||
|
url.searchParams.set('_hasdata', 'true');
|
||||||
|
url.searchParams.set(tableIdent + '-page', '0');
|
||||||
|
|
||||||
|
inputs.search.forEach(function(input) {
|
||||||
|
url.searchParams.set(input.name, input.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
inputs.input.forEach(function(input) {
|
||||||
|
url.searchParams.set(input.name, input.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
inputs.change.forEach(function(input) {
|
||||||
|
if (input.checked) {
|
||||||
|
url.searchParams.set(input.name, input.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
inputs.select.forEach(function(select) {
|
||||||
|
var options = Array.from(select.querySelectorAll('option'));
|
||||||
|
var selected = options.find(function(option) { return option.selected });
|
||||||
|
if (selected) {
|
||||||
|
url.searchParams.set(select.name, selected.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
setup();
|
||||||
|
|
||||||
|
return {
|
||||||
|
scope: formElement,
|
||||||
|
destroy: function() {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
||||||
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
window.utils = window.utils || {};
|
window.utils = window.utils || {};
|
||||||
|
|
||||||
|
var ASYNC_TABLE_CONTENT_CHANGED_CLASS = 'async-table--changed';
|
||||||
var JS_INITIALIZED_CLASS = 'js-check-all-initialized';
|
var JS_INITIALIZED_CLASS = 'js-check-all-initialized';
|
||||||
var CHECKBOX_SELECTOR = '[type="checkbox"]';
|
var CHECKBOX_SELECTOR = '[type="checkbox"]';
|
||||||
|
|
||||||
@ -12,7 +13,7 @@
|
|||||||
|
|
||||||
window.utils.checkAll = function(wrapper, options) {
|
window.utils.checkAll = function(wrapper, options) {
|
||||||
|
|
||||||
if (!wrapper || wrapper.classList.contains(JS_INITIALIZED_CLASS)) {
|
if ((!wrapper || wrapper.classList.contains(JS_INITIALIZED_CLASS)) && !wrapper.classList.contains(ASYNC_TABLE_CONTENT_CHANGED_CLASS)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
options = options || {};
|
options = options || {};
|
||||||
@ -21,6 +22,8 @@
|
|||||||
var checkboxColumn = [];
|
var checkboxColumn = [];
|
||||||
var checkAllCheckbox = null;
|
var checkAllCheckbox = null;
|
||||||
|
|
||||||
|
var utilInstances = [];
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
|
|
||||||
columns = gatherColumns(wrapper);
|
columns = gatherColumns(wrapper);
|
||||||
@ -79,7 +82,7 @@
|
|||||||
checkAllCheckbox.setAttribute('id', getCheckboxId());
|
checkAllCheckbox.setAttribute('id', getCheckboxId());
|
||||||
th.innerHTML = '';
|
th.innerHTML = '';
|
||||||
th.insertBefore(checkAllCheckbox, null);
|
th.insertBefore(checkAllCheckbox, null);
|
||||||
window.utils.setup('checkboxRadio', checkAllCheckbox);
|
utilInstances.push(window.utils.setup('checkbox', checkAllCheckbox));
|
||||||
|
|
||||||
checkAllCheckbox.addEventListener('input', onCheckAllCheckboxInput);
|
checkAllCheckbox.addEventListener('input', onCheckAllCheckboxInput);
|
||||||
setupCheckboxListeners();
|
setupCheckboxListeners();
|
||||||
@ -112,6 +115,17 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function destroy() {
|
||||||
|
utilInstances.forEach(function(util) {
|
||||||
|
util.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
|
return {
|
||||||
|
scope: wrapper,
|
||||||
|
destroy: destroy,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -25,36 +25,48 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var utilInstances = [];
|
||||||
|
|
||||||
// reactive buttons
|
// reactive buttons
|
||||||
var submitBtn = form.querySelector(SUBMIT_BUTTON_SELECTOR);
|
utilInstances.push(window.utils.setup('reactiveButton', form));
|
||||||
if (submitBtn) {
|
|
||||||
window.utils.setup('reactiveButton', form, { button: submitBtn });
|
|
||||||
}
|
|
||||||
|
|
||||||
// conditonal fieldsets
|
// conditonal fieldsets
|
||||||
var fieldSets = Array.from(form.querySelectorAll('fieldset[data-conditional-id][data-conditional-value]'));
|
var fieldSets = Array.from(form.querySelectorAll('fieldset[data-conditional-id][data-conditional-value]'));
|
||||||
window.utils.setup('interactiveFieldset', form, { fieldSets });
|
utilInstances.push(window.utils.setup('interactiveFieldset', form, { fieldSets }));
|
||||||
|
|
||||||
// hide autoSubmit submit button
|
// hide autoSubmit submit button
|
||||||
window.utils.setup('autoSubmit', form, options);
|
utilInstances.push(window.utils.setup('autoSubmit', form, options));
|
||||||
|
|
||||||
// async form
|
// async form
|
||||||
if (AJAX_SUBMIT_FLAG in form.dataset) {
|
if (AJAX_SUBMIT_FLAG in form.dataset) {
|
||||||
window.utils.setup('asyncForm', form, options);
|
utilInstances.push(window.utils.setup('asyncForm', form, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inputs
|
||||||
|
utilInstances.push(window.utils.setup('inputs', form, options));
|
||||||
|
|
||||||
form.classList.add(JS_INITIALIZED);
|
form.classList.add(JS_INITIALIZED);
|
||||||
|
|
||||||
|
function destroyUtils() {
|
||||||
|
utilInstances.forEach(function(utilInstance) {
|
||||||
|
utilInstance.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
scope: form,
|
||||||
|
destroy: destroyUtils,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// registers input-listener for each element in <inputs> (array) and
|
// registers input-listener for each element in <inputs> (array) and
|
||||||
// enables <button> if <formValidator> for these inputs returns true
|
// enables <button> if <formValidator> for these inputs returns true
|
||||||
window.utils.reactiveButton = function(form, options) {
|
window.utils.reactiveButton = function(form, options) {
|
||||||
options = options || {};
|
var button = form.querySelector(SUBMIT_BUTTON_SELECTOR);
|
||||||
var button = options.button;
|
|
||||||
var requireds = Array.from(form.querySelectorAll('[required]'));
|
var requireds = Array.from(form.querySelectorAll('[required]'));
|
||||||
|
|
||||||
if (!button) {
|
if (!button || button.matches(AUTOSUBMIT_BUTTON_SELECTOR)) {
|
||||||
throw new Error('Please provide both a button to reactiveButton');
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requireds.length == 0) {
|
if (requireds.length == 0) {
|
||||||
@ -84,6 +96,11 @@
|
|||||||
button.setAttribute('disabled', 'true');
|
button.setAttribute('disabled', 'true');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
scope: form,
|
||||||
|
destroy: function() {},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
window.utils.interactiveFieldset = function(form, options) {
|
window.utils.interactiveFieldset = function(form, options) {
|
||||||
@ -121,6 +138,11 @@
|
|||||||
addEventListeners();
|
addEventListeners();
|
||||||
updateFields();
|
updateFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
scope: form,
|
||||||
|
destroy: function() {},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
window.utils.autoSubmit = function(form, options) {
|
window.utils.autoSubmit = function(form, options) {
|
||||||
@ -128,5 +150,10 @@
|
|||||||
if (button) {
|
if (button) {
|
||||||
button.classList.add('hidden');
|
button.classList.add('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
scope: form,
|
||||||
|
destroy: function() {},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -10,22 +10,43 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.utils.inputs = function(wrapper, options) {
|
window.utils.inputs = function(wrapper, options) {
|
||||||
// checkboxes / radios
|
|
||||||
var checkboxes = Array.from(wrapper.querySelectorAll('input[type="checkbox"], input[type="radio"]'));
|
var utilInstances = [];
|
||||||
checkboxes.filter(isNotInitialized).forEach(window.utils.checkboxRadio);
|
|
||||||
|
// checkboxes
|
||||||
|
var checkboxes = Array.from(wrapper.querySelectorAll('input[type="checkbox"]'));
|
||||||
|
checkboxes.filter(isNotInitialized).forEach(function(checkbox) {
|
||||||
|
utilInstances.push(window.utils.setup('checkbox', checkbox));
|
||||||
|
});
|
||||||
|
|
||||||
|
// radios
|
||||||
|
var radios = Array.from(wrapper.querySelectorAll('input[type="radio"]'));
|
||||||
|
radios.filter(isNotInitialized).forEach(function(radio) {
|
||||||
|
utilInstances.push(window.utils.setup('radio', radio));
|
||||||
|
});
|
||||||
|
|
||||||
// file-uploads
|
// file-uploads
|
||||||
var fileUploads = Array.from(wrapper.querySelectorAll('input[type="file"]'));
|
var fileUploads = Array.from(wrapper.querySelectorAll('input[type="file"]'));
|
||||||
fileUploads.filter(isNotInitialized).forEach(function(input) {
|
fileUploads.filter(isNotInitialized).forEach(function(input) {
|
||||||
window.utils.fileUpload(input, options);
|
utilInstances.push(window.utils.setup('fileUpload', input, options));
|
||||||
});
|
});
|
||||||
|
|
||||||
// file-checkboxes
|
// file-checkboxes
|
||||||
var fileCheckboxes = Array.from(wrapper.querySelectorAll('.file-checkbox'));
|
var fileCheckboxes = Array.from(wrapper.querySelectorAll('.file-checkbox'));
|
||||||
fileCheckboxes.filter(isNotInitialized).forEach(function(inp) {
|
fileCheckboxes.filter(isNotInitialized).forEach(function(input) {
|
||||||
window.utils.fileCheckbox(inp);
|
utilInstances.push(window.utils.setup('fileCheckbox', input, options));
|
||||||
inp.classList.add(JS_INITIALIZED_CLASS);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function destroyUtils() {
|
||||||
|
utilInstances.forEach(function(utilInstance) {
|
||||||
|
utilInstance.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
scope: wrapper,
|
||||||
|
destroy: destroyUtils,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// (multiple) dynamic file uploads
|
// (multiple) dynamic file uploads
|
||||||
@ -113,6 +134,11 @@
|
|||||||
|
|
||||||
updateLabel(input.files);
|
updateLabel(input.files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
scope: input,
|
||||||
|
destroy: function() {},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// to remove previously uploaded files
|
// to remove previously uploaded files
|
||||||
@ -141,20 +167,24 @@
|
|||||||
input.classList.add(JS_INITIALIZED_CLASS);
|
input.classList.add(JS_INITIALIZED_CLASS);
|
||||||
cont.classList.add(JS_INITIALIZED_CLASS);
|
cont.classList.add(JS_INITIALIZED_CLASS);
|
||||||
}
|
}
|
||||||
|
|
||||||
setup();
|
setup();
|
||||||
|
|
||||||
|
return {
|
||||||
|
scope: input,
|
||||||
|
destroy: function() {},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// turns native checkboxes and radio buttons into custom ones
|
// turns native checkboxes into custom ones
|
||||||
window.utils.checkboxRadio = function(input) {
|
window.utils.checkbox = function(input) {
|
||||||
|
|
||||||
var type = input.getAttribute('type');
|
if (!input.parentElement.classList.contains('checkbox')) {
|
||||||
|
|
||||||
if (!input.parentElement.classList.contains(type)) {
|
|
||||||
var parentEl = input.parentElement;
|
var parentEl = input.parentElement;
|
||||||
var siblingEl = input.nextElementSibling;
|
var siblingEl = input.nextElementSibling;
|
||||||
var wrapperEl = document.createElement('div');
|
var wrapperEl = document.createElement('div');
|
||||||
var labelEl = document.createElement('label');
|
var labelEl = document.createElement('label');
|
||||||
wrapperEl.classList.add(type);
|
wrapperEl.classList.add('checkbox');
|
||||||
labelEl.setAttribute('for', input.id);
|
labelEl.setAttribute('for', input.id);
|
||||||
wrapperEl.appendChild(input);
|
wrapperEl.appendChild(input);
|
||||||
wrapperEl.appendChild(labelEl);
|
wrapperEl.appendChild(labelEl);
|
||||||
@ -166,6 +196,35 @@
|
|||||||
parentEl.appendChild(wrapperEl);
|
parentEl.appendChild(wrapperEl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
scope: input,
|
||||||
|
destroy: function() {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// turns native radio buttons into custom ones
|
||||||
|
window.utils.radio = function(input) {
|
||||||
|
|
||||||
|
if (!input.parentElement.classList.contains('radio')) {
|
||||||
|
var parentEl = input.parentElement;
|
||||||
|
var siblingEl = input.nextElementSibling;
|
||||||
|
var wrapperEl = document.createElement('div');
|
||||||
|
wrapperEl.classList.add('radio');
|
||||||
|
wrapperEl.appendChild(input);
|
||||||
|
|
||||||
|
if (siblingEl && siblingEl.matches('label')) {
|
||||||
|
wrapperEl.appendChild(siblingEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
input.classList.add(JS_INITIALIZED_CLASS);
|
||||||
|
parentEl.appendChild(wrapperEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
scope: input,
|
||||||
|
destroy: function() {},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -23,6 +23,8 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var utilInstances = [];
|
||||||
|
|
||||||
var overlayElement = document.createElement('div');
|
var overlayElement = document.createElement('div');
|
||||||
var closerElement = document.createElement('div');
|
var closerElement = document.createElement('div');
|
||||||
var triggerElement = document.querySelector('#' + modalElement.dataset.trigger);
|
var triggerElement = document.querySelector('#' + modalElement.dataset.trigger);
|
||||||
@ -73,7 +75,7 @@
|
|||||||
function setupForm() {
|
function setupForm() {
|
||||||
var form = modalElement.querySelector('form');
|
var form = modalElement.querySelector('form');
|
||||||
if (form) {
|
if (form) {
|
||||||
window.utils.setup('form', form, { headers: MODAL_HEADERS });
|
utilInstances.push(window.utils.setup('form', form, { headers: MODAL_HEADERS }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,10 +114,23 @@
|
|||||||
if (previousModalContent) {
|
if (previousModalContent) {
|
||||||
previousModalContent.remove();
|
previousModalContent.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modalContent = withPrefixedInputIDs(modalContent);
|
||||||
modalElement.insertBefore(modalContent, null);
|
modalElement.insertBefore(modalContent, null);
|
||||||
setupForm();
|
setupForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function withPrefixedInputIDs(modalContent) {
|
||||||
|
var idAttrs = ['id', 'for', 'data-conditional-id'];
|
||||||
|
idAttrs.forEach(function(attr) {
|
||||||
|
modalContent.querySelectorAll('[' + attr + ']').forEach(function(input) {
|
||||||
|
var value = modalElement.id + '__' + input.getAttribute(attr);
|
||||||
|
input.setAttribute(attr, value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return modalContent;
|
||||||
|
}
|
||||||
|
|
||||||
function keyupHandler(event) {
|
function keyupHandler(event) {
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
close();
|
close();
|
||||||
@ -123,5 +138,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
setup();
|
setup();
|
||||||
|
|
||||||
|
function destroyUtils() {
|
||||||
|
utilInstances.forEach(function(utilInstance) {
|
||||||
|
utilInstance.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
scope: modalElement,
|
||||||
|
destroy: destroyUtils,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
window.utils = window.utils || {};
|
window.utils = window.utils || {};
|
||||||
|
|
||||||
var registeredSetupListeners = {};
|
var registeredSetupListeners = {};
|
||||||
|
var activeInstances = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* setup function to initiate a util (utilName) on a scope (sope) with options (options).
|
* setup function to initiate a util (utilName) on a scope (sope) with options (options).
|
||||||
@ -13,53 +14,98 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
window.utils.setup = function(utilName, scope, options) {
|
window.utils.setup = function(utilName, scope, options) {
|
||||||
|
|
||||||
if (!utilName || !scope) {
|
if (!utilName || !scope) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
||||||
var listener = function(event) {
|
var utilInstance;
|
||||||
|
|
||||||
if (event.detail.targetUtil !== utilName) {
|
// i18n
|
||||||
return false;
|
if (window.I18N) {
|
||||||
}
|
options.i18n = window.I18N;
|
||||||
|
|
||||||
if (options.setupFunction) {
|
|
||||||
options.setupFunction(scope, options);
|
|
||||||
} else {
|
|
||||||
var util = window.utils[utilName];
|
|
||||||
if (!util) {
|
|
||||||
throw new Error('"' + utilName + '" is not a known js util');
|
|
||||||
}
|
|
||||||
|
|
||||||
util(scope, options);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (registeredSetupListeners[utilName] && !options.singleton) {
|
|
||||||
registeredSetupListeners[utilName].push(listener);
|
|
||||||
} else {
|
|
||||||
window.utils.teardown(utilName);
|
|
||||||
registeredSetupListeners[utilName] = [ listener ];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('setup', listener);
|
if (activeInstances[utilName]) {
|
||||||
|
var instanceWithSameScope = activeInstances[utilName]
|
||||||
|
.filter(function(instance) { return !!instance; })
|
||||||
|
.find(function(instance) {
|
||||||
|
return instance.scope === scope;
|
||||||
|
});
|
||||||
|
var isAlreadySetup = !!instanceWithSameScope;
|
||||||
|
|
||||||
document.dispatchEvent(new CustomEvent('setup', {
|
if (isAlreadySetup) {
|
||||||
detail: { targetUtil: utilName, module: 'none' },
|
console.warn('Trying to setup a JS utility that\'s already been set up', { utility: utilName, scope, options });
|
||||||
bubbles: true,
|
}
|
||||||
cancelable: true,
|
}
|
||||||
}));
|
|
||||||
|
function setup() {
|
||||||
|
var listener = function(event) {
|
||||||
|
if (event.detail.targetUtil !== utilName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.setupFunction) {
|
||||||
|
utilInstance = options.setupFunction(scope, options);
|
||||||
|
} else {
|
||||||
|
var util = window.utils[utilName];
|
||||||
|
if (!util) {
|
||||||
|
throw new Error('"' + utilName + '" is not a known js util');
|
||||||
|
}
|
||||||
|
|
||||||
|
utilInstance = util(scope, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (utilInstance) {
|
||||||
|
if (activeInstances[utilName] && Array.isArray(activeInstances[utilName])) {
|
||||||
|
activeInstances[utilName].push(utilInstance);
|
||||||
|
} else {
|
||||||
|
activeInstances[utilName] = [ utilInstance ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (registeredSetupListeners[utilName] && Array.isArray(registeredSetupListeners[utilName])) {
|
||||||
|
window.utils.teardown(utilName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!registeredSetupListeners[utilName] || Array.isArray(registeredSetupListeners[utilName])) {
|
||||||
|
registeredSetupListeners[utilName] = [];
|
||||||
|
}
|
||||||
|
registeredSetupListeners[utilName].push(listener);
|
||||||
|
|
||||||
|
document.addEventListener('setup', listener);
|
||||||
|
|
||||||
|
document.dispatchEvent(new CustomEvent('setup', {
|
||||||
|
detail: { targetUtil: utilName, module: 'none' },
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
setup();
|
||||||
|
|
||||||
|
return utilInstance;
|
||||||
};
|
};
|
||||||
|
|
||||||
window.utils.teardown = function(utilName) {
|
window.utils.teardown = function(utilName, destroy) {
|
||||||
if (registeredSetupListeners[utilName]) {
|
if (registeredSetupListeners[utilName]) {
|
||||||
registeredSetupListeners[utilName].forEach(function(listener) {
|
registeredSetupListeners[utilName]
|
||||||
document.removeEventListener('setup', listener);
|
.filter(function(listener) { return !!listener })
|
||||||
});
|
.forEach(function(listener) {
|
||||||
|
document.removeEventListener('setup', listener);
|
||||||
|
});
|
||||||
delete registeredSetupListeners[utilName];
|
delete registeredSetupListeners[utilName];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (destroy === true && activeInstances[utilName]) {
|
||||||
|
activeInstances[utilName]
|
||||||
|
.filter(function(instance) { return !!instance })
|
||||||
|
.forEach(function(instance) {
|
||||||
|
instance.destroy();
|
||||||
|
});
|
||||||
|
delete activeInstances[utilName];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -3,7 +3,12 @@
|
|||||||
|
|
||||||
window.utils = window.utils || {};
|
window.utils = window.utils || {};
|
||||||
|
|
||||||
|
var JS_INITIALIZED_CLASS = 'js-show-hide-initialized';
|
||||||
var LOCAL_STORAGE_SHOW_HIDE = 'SHOW_HIDE';
|
var LOCAL_STORAGE_SHOW_HIDE = 'SHOW_HIDE';
|
||||||
|
var SHOW_HIDE_TOGGLE_CLASS = 'js-show-hide__toggle';
|
||||||
|
var SHOW_HIDE_COLLAPSED_CLASS = 'js-show-hide--collapsed';
|
||||||
|
var SHOW_HIDE_TARGET_CLASS = 'js-show-hide__target';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* div
|
* div
|
||||||
* div.js-show-hide__toggle
|
* div.js-show-hide__toggle
|
||||||
@ -12,9 +17,12 @@
|
|||||||
* content here
|
* content here
|
||||||
*/
|
*/
|
||||||
window.utils.showHide = function(wrapper, options) {
|
window.utils.showHide = function(wrapper, options) {
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
function addEventHandler(el) {
|
function addEventHandler(el) {
|
||||||
el.addEventListener('click', function elClickListener() {
|
el.addEventListener('click', function elClickListener() {
|
||||||
var newState = el.parentElement.classList.toggle('js-show-hide--collapsed');
|
var newState = el.parentElement.classList.toggle(SHOW_HIDE_COLLAPSED_CLASS);
|
||||||
updateLSState(el.dataset.shIndex || null, newState);
|
updateLSState(el.dataset.shIndex || null, newState);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -23,33 +31,47 @@
|
|||||||
if (!index) {
|
if (!index) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var lsData = fromLocalStorage();
|
var lsData = getLocalStorageData();
|
||||||
lsData[index] = state;
|
lsData[index] = state;
|
||||||
window.localStorage.setItem(LOCAL_STORAGE_SHOW_HIDE, JSON.stringify(lsData));
|
window.localStorage.setItem(LOCAL_STORAGE_SHOW_HIDE, JSON.stringify(lsData));
|
||||||
}
|
}
|
||||||
|
|
||||||
function collapsedStateInLocalStorage(index) {
|
function collapsedStateInLocalStorage(index) {
|
||||||
return fromLocalStorage()[index] || null;
|
var lsState = getLocalStorageData();
|
||||||
|
return lsState[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
function fromLocalStorage() {
|
function getLocalStorageData() {
|
||||||
return JSON.parse(window.localStorage.getItem(LOCAL_STORAGE_SHOW_HIDE)) || {};
|
return JSON.parse(window.localStorage.getItem(LOCAL_STORAGE_SHOW_HIDE)) || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
Array
|
Array
|
||||||
.from(wrapper.querySelectorAll('.js-show-hide__toggle'))
|
.from(wrapper.querySelectorAll('.' + SHOW_HIDE_TOGGLE_CLASS))
|
||||||
.forEach(function(el) {
|
.forEach(function(el) {
|
||||||
|
if (el.classList.contains(JS_INITIALIZED_CLASS)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var index = el.dataset.shIndex || null;
|
var index = el.dataset.shIndex || null;
|
||||||
el.parentElement.classList.toggle(
|
var isCollapsed = el.dataset.collapsed === 'true';
|
||||||
'js-show-hide--collapsed',
|
var lsCollapsedState = collapsedStateInLocalStorage(index);
|
||||||
collapsedStateInLocalStorage(index) || el.dataset.collapsed === 'true'
|
if (typeof lsCollapsedState !== 'undefined') {
|
||||||
);
|
isCollapsed = lsCollapsedState;
|
||||||
|
}
|
||||||
|
el.parentElement.classList.toggle(SHOW_HIDE_COLLAPSED_CLASS, isCollapsed);
|
||||||
|
|
||||||
Array.from(el.parentElement.children).forEach(function(el) {
|
Array.from(el.parentElement.children).forEach(function(el) {
|
||||||
if (!el.classList.contains('js-show-hide__toggle')) {
|
if (!el.classList.contains('' + SHOW_HIDE_TOGGLE_CLASS)) {
|
||||||
el.classList.add('js-show-hide__target');
|
el.classList.add(SHOW_HIDE_TARGET_CLASS);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
el.classList.add(JS_INITIALIZED_CLASS);
|
||||||
addEventHandler(el);
|
addEventHandler(el);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
scope: wrapper,
|
||||||
|
destroy: function() {},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -35,14 +35,16 @@ function setupDatepicker(wrapper) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
// this global I18N object will be picked up automatically by the setup util
|
||||||
var I18N = {
|
window.I18N = {
|
||||||
filesSelected: 'Dateien ausgewählt', // TODO: interpolate these to be translated
|
filesSelected: 'Dateien ausgewählt', // TODO: interpolate these to be translated
|
||||||
selectFile: 'Datei auswählen',
|
selectFile: 'Datei auswählen',
|
||||||
selectFiles: 'Datei(en) auswählen',
|
selectFiles: 'Datei(en) auswählen',
|
||||||
};
|
asyncFormFailure: 'Da ist etwas schief gelaufen, das tut uns Leid.<br>Falls das erneut passiert schicke uns gerne eine kurze Beschreibung dieses Ereignisses über das Hilfe-Widget rechts oben.<br><br>Vielen Dank für deine Hilfe',
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
window.utils.setup('flatpickr', document.body, { setupFunction: setupDatepicker });
|
window.utils.setup('flatpickr', document.body, { setupFunction: setupDatepicker });
|
||||||
window.utils.setup('showHide', document.body);
|
window.utils.setup('showHide', document.body);
|
||||||
window.utils.setup('inputs', document.body, { i18n: I18N });
|
window.utils.setup('inputs', document.body);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
--color-lightwhite: #fcfffa;
|
--color-lightwhite: #fcfffa;
|
||||||
--color-grey: #B1B5C0;
|
--color-grey: #B1B5C0;
|
||||||
--color-grey-light: #efefef;
|
--color-grey-light: #efefef;
|
||||||
|
--color-grey-lighter: #f5f5f5;
|
||||||
--color-grey-medium: #9A989E;
|
--color-grey-medium: #9A989E;
|
||||||
--color-font: #34303a;
|
--color-font: #34303a;
|
||||||
--color-fontsec: #5b5861;
|
--color-fontsec: #5b5861;
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
$newline never
|
$newline never
|
||||||
<section>
|
<div .table-filter>
|
||||||
<form method=GET action=#{filterAction} enctype=#{filterEnctype}>
|
<h3 .js-show-hide__toggle data-sh-index=table-filter data-collapsed=true>Filter
|
||||||
^{filterWgdt}
|
<div>
|
||||||
<button>
|
<form .table-filter-form method=GET action=#{filterAction} enctype=#{filterEnctype}>
|
||||||
^{btnLabel BtnSubmit}
|
^{filterWgdt}
|
||||||
<section>
|
<button type=submit data-autosubmit>
|
||||||
^{scrolltable}
|
^{btnLabel BtnSubmit}
|
||||||
|
^{scrolltable}
|
||||||
|
|||||||
4
templates/table/layout-filter-default.lucius
Normal file
4
templates/table/layout-filter-default.lucius
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.table-filter {
|
||||||
|
border-bottom: 1px solid #d3d3d3;
|
||||||
|
margin-bottom: 13px;
|
||||||
|
}
|
||||||
@ -1,11 +1,10 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
var dbtIdent = #{String $ dbtIdent};
|
var dbtIdent = #{String dbtIdent};
|
||||||
var headerDBTableShortcircuit = #{String (toPathPiece HeaderDBTableShortcircuit)};
|
var headerDBTableShortcircuit = #{String (toPathPiece HeaderDBTableShortcircuit)};
|
||||||
var selector = '#' + dbtIdent + '-table-wrapper';
|
var selector = '#' + dbtIdent + '-table-wrapper';
|
||||||
var wrapper = document.querySelector(selector);
|
var wrapper = document.querySelector(selector);
|
||||||
|
|
||||||
if (wrapper) {
|
if (wrapper) {
|
||||||
window.utils.setup('asyncTable', wrapper, { headerDBTableShortcircuit, dbtIdent });
|
window.utils.setup('asyncTable', wrapper, { headerDBTableShortcircuit, dbtIdent });
|
||||||
window.utils.setup('checkAll', wrapper);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
14
templates/widgets/fields/bool.hamlet
Normal file
14
templates/widgets/fields/bool.hamlet
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
$newline never
|
||||||
|
<div .radio-group>
|
||||||
|
$if not isReq
|
||||||
|
<div .radio>
|
||||||
|
<input id=#{theId}-none *{attrs} type=radio name=#{name} value=none checked>
|
||||||
|
<label for=#{theId}-none>_{MsgCourseFilterNone}
|
||||||
|
|
||||||
|
<div .radio>
|
||||||
|
<input id=#{theId}-yes *{attrs} type=radio name=#{name} value=yes :showVal id val:checked>
|
||||||
|
<label for=#{theId}-yes>_{MsgBoolYes}
|
||||||
|
|
||||||
|
<div .radio>
|
||||||
|
<input id=#{theId}-no *{attrs} type=radio name=#{name} value=no :showVal not val:checked>
|
||||||
|
<label for=#{theId}-no>_{MsgBoolNo}
|
||||||
@ -1,7 +1,9 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// TODO: replace for loop with one precise query for this specific modal instance
|
var modalIdent = #{String modalId};
|
||||||
var modalElements = Array.from(document.querySelectorAll('.modal'));
|
var selector = '#modal-' + modalIdent;
|
||||||
modalElements.forEach(function(modal) {
|
var modal = document.querySelector(selector);
|
||||||
|
|
||||||
|
if (modal) {
|
||||||
window.utils.setup('modal', modal);
|
window.utils.setup('modal', modal);
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user