Merge branch 'async-form-responses' into 'master'

Async form responses

See merge request !161
This commit is contained in:
Felix Hamann 2019-03-11 20:50:36 +01:00
commit 84237c4484
22 changed files with 243 additions and 96 deletions

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -654,5 +654,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

View File

@ -19,7 +19,7 @@ import Data.Aeson (Value)
import Data.CaseInsensitive (CI) import Data.CaseInsensitive (CI)
import Data.CaseInsensitive.Instances () import Data.CaseInsensitive.Instances ()
import Utils.Message (MessageClass) import Utils.Message (MessageStatus)
import Settings.Cluster (ClusterSettingsKey) import Settings.Cluster (ClusterSettingsKey)
import Data.Binary (Binary) import Data.Binary (Binary)

View File

@ -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)

View File

@ -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))

View File

@ -1,5 +1,74 @@
.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 {

View File

@ -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%;
}

View File

@ -89,6 +89,7 @@
alertElements.forEach(initAlert); alertElements.forEach(initAlert);
return { return {
scope: alertsEl,
destroy: function() {}, destroy: function() {},
}; };
}; };

View File

@ -57,6 +57,7 @@
initAsidenavSubmenus(); initAsidenavSubmenus();
return { return {
scope: asideEl,
destroy: function() {}, destroy: function() {},
}; };
}; };

View File

@ -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,17 +58,28 @@
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 { return {
scope: formElement,
destroy: function() {}, destroy: function() {},
}; };
}; };

View File

@ -225,6 +225,7 @@
init(); init();
return { return {
scope: wrapper,
destroy: destroyUtils, destroy: destroyUtils,
}; };
}; };

View File

@ -159,6 +159,7 @@
setup(); setup();
return { return {
scope: formElement,
destroy: function() {}, destroy: function() {},
}; };
} }

View File

@ -124,6 +124,7 @@
init(); init();
return { return {
scope: wrapper,
destroy: destroy, destroy: destroy,
}; };
}; };

View File

@ -54,6 +54,7 @@
} }
return { return {
scope: form,
destroy: destroyUtils, destroy: destroyUtils,
}; };
}; };
@ -97,6 +98,7 @@
} }
return { return {
scope: form,
destroy: function() {}, destroy: function() {},
}; };
}; };
@ -138,6 +140,7 @@
} }
return { return {
scope: form,
destroy: function() {}, destroy: function() {},
}; };
}; };
@ -149,6 +152,7 @@
} }
return { return {
scope: form,
destroy: function() {}, destroy: function() {},
}; };
}; };

View File

@ -44,6 +44,7 @@
} }
return { return {
scope: wrapper,
destroy: destroyUtils, destroy: destroyUtils,
}; };
}; };
@ -135,6 +136,7 @@
}); });
return { return {
scope: input,
destroy: function() {}, destroy: function() {},
}; };
} }
@ -169,6 +171,7 @@
setup(); setup();
return { return {
scope: input,
destroy: function() {}, destroy: function() {},
}; };
} }
@ -195,6 +198,7 @@
} }
return { return {
scope: input,
destroy: function() {}, destroy: function() {},
}; };
} }
@ -218,6 +222,7 @@
} }
return { return {
scope: input,
destroy: function() {}, destroy: function() {},
}; };
} }

View File

@ -146,6 +146,7 @@
} }
return { return {
scope: modalElement,
destroy: destroyUtils, destroy: destroyUtils,
}; };
}; };

View File

@ -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,57 +14,98 @@
*/ */
window.utils.setup = function(utilName, scope, options) { window.utils.setup = function(utilName, scope, options) {
var utilInstance;
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) {
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);
}
};
window.utils.teardown(utilName);
if (registeredSetupListeners[utilName] && !options.singleton) {
registeredSetupListeners[utilName].push(listener);
} else {
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; 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];
}
} }
})(); })();

View File

@ -70,6 +70,7 @@
}); });
return { return {
scope: wrapper,
destroy: function() {}, destroy: function() {},
}; };
}; };

View File

@ -86,9 +86,5 @@
$(t).tabgroup(); $(t).tabgroup();
}); });
} }
return {
destroy: function() {},
};
}); });
})($); })($);

View File

@ -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);
}); });