From a627b7be72f650bd7e977d5556d0ca0efacb5d9e Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Thu, 22 Nov 2018 14:55:27 +0100 Subject: [PATCH 1/5] Globally defined get parameters & headers --- package.yaml | 1 + src/Foundation.hs | 6 ++---- src/Handler/Home.hs | 2 +- src/Handler/Utils/Form.hs | 8 +++++-- src/Handler/Utils/Templates.hs | 6 ------ src/Utils.hs | 36 +++++++++++++++++++++++++++++++ src/Utils/Message.hs | 35 ++++++++++++++++++++++++++++++ templates/standalone/modal.julius | 9 +++----- 8 files changed, 84 insertions(+), 19 deletions(-) diff --git a/package.yaml b/package.yaml index 20a50b6c5..6c3077b78 100644 --- a/package.yaml +++ b/package.yaml @@ -108,6 +108,7 @@ dependencies: - mmorph - clientsession - monad-memo + - xss-sanitize other-extensions: - GeneralizedNewtypeDeriving diff --git a/src/Foundation.hs b/src/Foundation.hs index 71893e2b3..301ca8939 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -16,8 +16,6 @@ import Auth.PWHash import Auth.Dummy import Jobs.Types -import Handler.Utils.Templates (siteModalId, modalParameter) - import qualified Network.Wai as W (pathInfo) import Yesod.Default.Util (addStaticContentExternal) @@ -768,7 +766,7 @@ siteLayout headingOverride widget = do master <- getYesod let AppSettings { appUserDefaults = UserDefaultConf{..}, .. } = appSettings master - isModal <- isJust <$> siteModalId + isModal <- hasCustomHeader HeaderIsModal mcurrentRoute <- getCurrentRoute @@ -987,7 +985,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the { menuItemType = NavbarRight , menuItemLabel = MsgMenuHelp , menuItemIcon = Just "question" - , menuItemRoute = SomeRoute (HelpR, catMaybes [("site", ) . toPathPiece <$> mCurrentRoute]) + , menuItemRoute = SomeRoute (HelpR, catMaybes [(toPathPiece GetReferer, ) . toPathPiece <$> mCurrentRoute]) , menuItemModal = True , menuItemAccessCallback' = return True } diff --git a/src/Handler/Home.hs b/src/Handler/Home.hs index a301af506..c90238b0e 100644 --- a/src/Handler/Home.hs +++ b/src/Handler/Home.hs @@ -257,7 +257,7 @@ getHelpR, postHelpR :: Handler Html getHelpR = postHelpR postHelpR = do mUid <- maybeAuthId - mReferer <- flip formResultMaybe return <=< runInputGetResult $ iopt routeField "site" + mReferer <- flip formResultMaybe return <=< runInputGetResult $ iopt routeField (toPathPiece GetReferer) ((res,formWidget),formEnctype) <- runFormPost $ renderAForm FormStandard $ helpForm mReferer mUid diff --git a/src/Handler/Utils/Form.hs b/src/Handler/Utils/Form.hs index e78d86214..77065cfaf 100644 --- a/src/Handler/Utils/Form.hs +++ b/src/Handler/Utils/Form.hs @@ -587,5 +587,9 @@ multiActionA FieldSettings{..} acts defAction = formToAForm $ do } ]) - - +formResultModal :: MonadHandler m => FormResult a -> (a -> m ()) -> m () +formResultModal res handler = do + formResult res handler + whenM (hasCustomHeader HeaderIsModal) $ do + messages <- mapM (\(cl, co) -> maybe (throwM $ UnknownMessageClass cl) (return . flip Message co) $ fromPathPiece cl) =<< getMessages + sendResponse $ toJSON messages diff --git a/src/Handler/Utils/Templates.hs b/src/Handler/Utils/Templates.hs index ed4f0111a..14f8ce38c 100644 --- a/src/Handler/Utils/Templates.hs +++ b/src/Handler/Utils/Templates.hs @@ -7,12 +7,6 @@ import Import.NoFoundation lipsum :: WidgetT site IO () lipsum = $(widgetFile "widgets/lipsum") -modalParameter :: Text -modalParameter = "_modal" - -siteModalId :: MonadHandler m => m (Maybe Text) -siteModalId = lookupGetParam modalParameter - modal :: WidgetT site IO () -> Either (Route site) (WidgetT site IO ()) -> WidgetT site IO () modal modalTrigger modalContent = do let modalDynamic = isLeft modalContent diff --git a/src/Utils.hs b/src/Utils.hs index f51c03c23..a451eb70b 100644 --- a/src/Utils.hs +++ b/src/Utils.hs @@ -52,6 +52,8 @@ import Text.Shakespeare.Text (st) import qualified Data.Aeson as Aeson +import Data.Universe + ----------- @@ -476,3 +478,37 @@ tellSessionJson key val = modifySessionJson key $ Just . (`mappend` val) . fromM getSessionJson :: (PathPiece k, FromJSON v, MonadHandler m) => k -> m (Maybe v) -- ^ `lookupSessionJson` followed by `deleteSession` getSessionJson key = lookupSessionJson key <* deleteSession (toPathPiece key) + +-------------------- +-- GET Parameters -- +-------------------- + +data GlobalGetParam = GetReferer + deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic) + +instance Universe GlobalGetParam +instance Finite GlobalGetParam +nullaryPathPiece ''GlobalGetParam (camelToPathPiece' 1) + +lookupGlobalGetParam :: (MonadHandler m, PathPiece result) => GlobalGetParam -> m (Maybe result) +lookupGlobalGetParam ident = (>>= fromPathPiece) <$> lookupGetParam (toPathPiece ident) + +hasGlobalGetParam :: MonadHandler m => GlobalGetParam -> m Bool +hasGlobalGetParam ident = isJust <$> lookupGetParam (toPathPiece ident) + +--------------------------------- +-- Custom HTTP Request-Headers -- +--------------------------------- + +data CustomHeader = HeaderIsModal + deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic) + +instance Universe CustomHeader +instance Finite CustomHeader +nullaryPathPiece ''CustomHeader (intercalate "-" . drop 1 . splitCamel) + +lookupCustomHeader :: (MonadHandler m, PathPiece result) => CustomHeader -> m (Maybe result) +lookupCustomHeader ident = (>>= fromPathPiece . decodeUtf8) <$> lookupHeader (CI.mk . encodeUtf8 $ toPathPiece ident) + +hasCustomHeader :: MonadHandler m => CustomHeader -> m Bool +hasCustomHeader ident = isJust <$> lookupHeader (CI.mk . encodeUtf8 $ toPathPiece ident) diff --git a/src/Utils/Message.hs b/src/Utils/Message.hs index 7c806d996..724850167 100644 --- a/src/Utils/Message.hs +++ b/src/Utils/Message.hs @@ -1,5 +1,7 @@ module Utils.Message ( MessageClass(..) + , UnknownMessageClass(..) + , Message(..) , addMessage, addMessageI, addMessageIHamlet, addMessageFile, addMessageWidget ) where @@ -17,6 +19,10 @@ import Text.Hamlet import Language.Haskell.TH import Language.Haskell.TH.Syntax (Lift) +import Text.Blaze (preEscapedText) +import Text.Blaze.Html.Renderer.Text (renderHtml) +import Text.HTML.SanitizeXSS (sanitizeBalance) + data MessageClass = Error | Warning | Info | Success deriving (Eq, Ord, Enum, Bounded, Show, Read, Lift) @@ -31,6 +37,35 @@ deriveJSON defaultOptions nullaryPathPiece ''MessageClass camelToPathPiece derivePersistField "MessageClass" +data UnknownMessageClass = UnknownMessageClass !Text + deriving (Eq, Ord, Read, Show, Generic, Typeable) + +instance Exception UnknownMessageClass + + +data Message = Message + { messageClass :: MessageClass + , messageContent :: Html + } + +instance Eq Message where + a == b = ((==) `on` messageClass) a b && ((==) `on` renderHtml . messageContent) a b + +instance Ord Message where + a `compare` b = (compare `on` messageClass) a b `mappend` (compare `on` renderHtml . messageContent) a b + +instance ToJSON Message where + toJSON Message{..} = object + [ "class" .= messageClass + , "content" .= renderHtml messageContent + ] + +instance FromJSON Message where + parseJSON = withObject "Message" $ \o -> do + messageClass <- o .: "class" + messageContent <- preEscapedText . sanitizeBalance <$> o .: "content" + return Message{..} + addMessage :: MonadHandler m => MessageClass-> Html -> m () addMessage mc = ClassyPrelude.Yesod.addMessage (toPathPiece mc) diff --git a/templates/standalone/modal.julius b/templates/standalone/modal.julius index 61760f730..5ca78ae33 100644 --- a/templates/standalone/modal.julius +++ b/templates/standalone/modal.julius @@ -59,17 +59,14 @@ if ('dynamic' in modal.dataset) { var dynamicContentURL = trigger.getAttribute('href'); - var i = dynamicContentURL.indexOf('?'); - if (i === -1) { - dynamicContentURL = dynamicContentURL + "?" + #{String modalParameter}; - } else { - dynamicContentURL = dynamicContentURL.slice(0,i) + "?" + #{String modalParameter} + "&" + dynamicContentURL.slice(i + 1); - } console.log(dynamicContentURL); if (dynamicContentURL.length > 0) { fetch(dynamicContentURL, { credentials: 'same-origin', + headers: { + #{String (toPathPiece HeaderIsModal)}: 'True' + } }).then(function(response) { return response.text(); }).then(function(body) { From 213f3a39cc13fb2892ff00b989081516dabd4e8a Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Thu, 22 Nov 2018 17:25:56 +0100 Subject: [PATCH 2/5] Fully ajaxified modals --- src/Handler/Home.hs | 29 +++--- src/Handler/Utils/Form.hs | 21 +++-- src/Utils/Message.hs | 30 ++++++- templates/default-layout.hamlet | 2 +- templates/help.hamlet | 2 +- templates/standalone/alerts.julius | 33 ++++--- templates/standalone/modal.julius | 140 +++++++++++++++++++++++++---- templates/table/layout.julius | 3 + templates/widgets/modal.hamlet | 3 +- 9 files changed, 202 insertions(+), 61 deletions(-) diff --git a/src/Handler/Home.hs b/src/Handler/Home.hs index c90238b0e..430583bf8 100644 --- a/src/Handler/Home.hs +++ b/src/Handler/Home.hs @@ -261,27 +261,20 @@ postHelpR = do ((res,formWidget),formEnctype) <- runFormPost $ renderAForm FormStandard $ helpForm mReferer mUid - case res of - FormSuccess HelpForm{..} -> do - now <- liftIO getCurrentTime - hfReferer' <- traverse toTextUrl hfReferer - queueJob' JobHelpRequest - { jSender = hfUserId - , jHelpRequest = hfRequest - , jRequestTime = now - , jReferer = hfReferer' - } - -- redirect $ HelpR - addMessageI Success MsgHelpSent - return () - {-selectRep $ do - provideJson () - provideRep (redirect $ HelpR :: Handler Html) -} - FormMissing -> return () - FormFailure errs -> mapM_ (addMessage Error . toHtml) errs + formResultModal res HelpR $ \HelpForm{..} -> do + now <- liftIO getCurrentTime + hfReferer' <- traverse toTextUrl hfReferer + queueJob' JobHelpRequest + { jSender = hfUserId + , jHelpRequest = hfRequest + , jRequestTime = now + , jReferer = hfReferer' + } + tell . pure =<< messageI Success MsgHelpSent defaultLayout $ do setTitle "Hilfe" -- TODO: International + isModal <- hasCustomHeader HeaderIsModal $(widgetFile "help") diff --git a/src/Handler/Utils/Form.hs b/src/Handler/Utils/Form.hs index 77065cfaf..32a1d28d9 100644 --- a/src/Handler/Utils/Form.hs +++ b/src/Handler/Utils/Form.hs @@ -1,6 +1,7 @@ module Handler.Utils.Form ( module Handler.Utils.Form , module Utils.Form + , MonadWriter(..) ) where import Utils.Form @@ -35,6 +36,7 @@ import qualified Data.Set as Set import Data.Map (Map, (!)) import qualified Data.Map as Map +import Control.Monad.Trans.Writer (execWriterT, WriterT) import Control.Monad.Writer.Class import Data.Scientific (Scientific) @@ -587,9 +589,16 @@ multiActionA FieldSettings{..} acts defAction = formToAForm $ do } ]) -formResultModal :: MonadHandler m => FormResult a -> (a -> m ()) -> m () -formResultModal res handler = do - formResult res handler - whenM (hasCustomHeader HeaderIsModal) $ do - messages <- mapM (\(cl, co) -> maybe (throwM $ UnknownMessageClass cl) (return . flip Message co) $ fromPathPiece cl) =<< getMessages - sendResponse $ toJSON messages +formResultModal :: (MonadHandler m, RedirectUrl (HandlerSite m) route) => FormResult a -> route -> (a -> WriterT [Message] m ()) -> m () +formResultModal res finalDest handler = maybeT_ $ do + messages <- case res of + FormMissing -> mzero + FormFailure errs -> return $ map (Message Error . toHtml) errs + FormSuccess val -> lift . execWriterT $ handler val + + isModal <- hasCustomHeader HeaderIsModal + if + | isModal -> sendResponse $ toJSON messages + | otherwise -> do + forM_ messages $ \Message{..} -> addMessage messageClass messageContent + redirect finalDest diff --git a/src/Utils/Message.hs b/src/Utils/Message.hs index 724850167..2d819bc32 100644 --- a/src/Utils/Message.hs +++ b/src/Utils/Message.hs @@ -1,8 +1,9 @@ module Utils.Message ( MessageClass(..) , UnknownMessageClass(..) - , Message(..) , addMessage, addMessageI, addMessageIHamlet, addMessageFile, addMessageWidget + , Message(..) + , messageI, messageIHamlet, messageFile, messageWidget ) where @@ -67,12 +68,17 @@ instance FromJSON Message where return Message{..} -addMessage :: MonadHandler m => MessageClass-> Html -> m () +addMessage :: MonadHandler m => MessageClass -> Html -> m () addMessage mc = ClassyPrelude.Yesod.addMessage (toPathPiece mc) addMessageI :: (MonadHandler m, RenderMessage (HandlerSite m) msg) => MessageClass -> msg -> m () addMessageI mc = ClassyPrelude.Yesod.addMessageI (toPathPiece mc) +messageI :: (MonadHandler m, RenderMessage (HandlerSite m) msg) => MessageClass -> msg -> m Message +messageI messageClass msg = do + messageContent <- toHtml . ($ msg) <$> getMessageRender + return Message{..} + addMessageIHamlet :: ( MonadHandler m , RenderMessage (HandlerSite m) msg , HandlerSite m ~ site @@ -81,9 +87,20 @@ addMessageIHamlet mc iHamlet = do mr <- getMessageRender ClassyPrelude.Yesod.addMessage (toPathPiece mc) =<< withUrlRenderer (iHamlet $ toHtml . mr) +messageIHamlet :: ( MonadHandler m + , RenderMessage (HandlerSite m) msg + , HandlerSite m ~ site + ) => MessageClass -> HtmlUrlI18n msg (Route site) -> m Message +messageIHamlet mc iHamlet = do + mr <- getMessageRender + Message mc <$> withUrlRenderer (iHamlet $ toHtml . mr) + addMessageFile :: MessageClass -> FilePath -> ExpQ addMessageFile mc tPath = [e|addMessageIHamlet mc $(ihamletFile tPath)|] +messageFile :: MessageClass -> FilePath -> ExpQ +messageFile mc tPath = [e|messageIHamlet mc $(ihamletFile tPath)|] + addMessageWidget :: forall m site. ( MonadHandler m , HandlerSite m ~ site @@ -93,3 +110,12 @@ addMessageWidget :: forall m site. addMessageWidget mc wgt = do PageContent{pageBody} <- liftHandlerT $ widgetToPageContent wgt addMessageIHamlet mc (const pageBody :: HtmlUrlI18n (SomeMessage site) (Route site)) + +messageWidget :: forall m site. + ( MonadHandler m + , HandlerSite m ~ site + , Yesod site + ) => MessageClass -> WidgetT site IO () -> m Message +messageWidget mc wgt = do + PageContent{pageBody} <- liftHandlerT $ widgetToPageContent wgt + messageIHamlet mc (const pageBody :: HtmlUrlI18n (SomeMessage site) (Route site)) diff --git a/templates/default-layout.hamlet b/templates/default-layout.hamlet index 9cae5ae39..c889c20b8 100644 --- a/templates/default-layout.hamlet +++ b/templates/default-layout.hamlet @@ -28,7 +28,7 @@ $if not isModal ^{pageactionprime} -
+
$forall (status, msg) <- mmsgs $with status2 <- bool status "info" (status == "")
diff --git a/templates/help.hamlet b/templates/help.hamlet index 6d4b32bca..3fe70ebe6 100644 --- a/templates/help.hamlet +++ b/templates/help.hamlet @@ -1,4 +1,4 @@ _{MsgHelpIntroduction} -
+ ^{formWidget} diff --git a/templates/standalone/alerts.julius b/templates/standalone/alerts.julius index 242ca3362..e4a69d07b 100644 --- a/templates/standalone/alerts.julius +++ b/templates/standalone/alerts.julius @@ -5,24 +5,24 @@ var ALERT_INVISIBLE_CLASS = 'alert--invisible'; var TOGGLER_INVISIBLE_CLASS = 'alerts__toggler--visible'; + var alertsShowingToggler = false; window.utils.alerts = function(alertsEl) { - var alerts = Array.from(alertsEl.querySelectorAll('.alert')); - var toggler; - var showingToggler = false; + var toggler = alertsEl.querySelector('.alerts__toggler'); function makeToggler() { toggler = document.createElement('DIV'); toggler.classList.add('alerts__toggler'); toggler.addEventListener('click', function() { - alerts.forEach(function(alert) { + Array.from(alertsEl.querySelectorAll('.alert')).forEach(function(alert) { alert.classList.remove(ALERT_INVISIBLE_CLASS); toggler.classList.remove(TOGGLER_INVISIBLE_CLASS); }); checkToggler(); }); alertsEl.appendChild(toggler); + alertsEl.classList.add('js-initialized'); } function makeAlert(alertEl) { @@ -47,6 +47,8 @@ closeAlert(alertEl); }, autoDecay * 1000); } + + alertEl.classList.add('js-initialized'); } function closeAlert(alertEl) { @@ -56,22 +58,23 @@ function checkToggler() { var hidden = true; - alerts.forEach(function(alert) { + Array.from(alertsEl.querySelectorAll('.alert')).forEach(function(alert) { if (hidden && !alert.classList.contains(ALERT_INVISIBLE_CLASS)) { hidden = false; } }); - if (!showingToggler) { - showingToggler = true; + if (!alertsShowingToggler) { + alertsShowingToggler = true; window.setTimeout(function() { toggler.classList.toggle(TOGGLER_INVISIBLE_CLASS, hidden); - showingToggler = false; + alertsShowingToggler = false; }, 120); } } - makeToggler(); - alerts.map(makeAlert); + if (!alertsEl.classList.contains('js-initialized') || !toggler) + makeToggler(); + Array.from(alertsEl.querySelectorAll('.alert:not(.js-initialized)')).map(makeAlert); } })(); @@ -79,7 +82,11 @@ document.addEventListener('setup', function(e) { // setup alerts - var alertsEl = e.detail.scope.querySelector('.alerts'); - if (alertsEl) - window.utils.alerts(alertsEl); + if (e.detail.scope.classList.contains('alerts')) { + window.utils.alerts(e.detail.scope); + } else { + var alertsEl = e.detail.scope.querySelector('.alerts'); + if (alertsEl) + window.utils.alerts(alertsEl); + } }); diff --git a/templates/standalone/modal.julius b/templates/standalone/modal.julius index 5ca78ae33..81fdc3a46 100644 --- a/templates/standalone/modal.julius +++ b/templates/standalone/modal.julius @@ -10,6 +10,10 @@ // var origParent = modal.parentNode; function open(event) { + if (!modal.classList.contains('js-modal-initialized')) { + return; + } + // disable modals for narrow screens if (event) { event.preventDefault(); @@ -17,7 +21,7 @@ modal.classList.add('modal--open'); overlay.classList.add('modal__overlay'); // document.body.insertBefore(modal, null); - document.body.insertBefore(overlay, modal); + document.body.insertBefore(overlay, modal); overlay.classList.add('modal__overlay--open'); if ('closeable' in modal.dataset) { @@ -37,7 +41,7 @@ } function close(event) { - if (typeof event === 'undefined' || event.target === closer || event.target === overlay) { + if (typeof event === 'undefined' || event.target === closer || event.target === overlay || event.target === modal) { overlay.remove(); // origParent.insertBefore(modal, null); modal.classList.remove('modal--open'); @@ -46,7 +50,7 @@ }; function setup() { - document.body.insertBefore(modal, null); + document.body.insertBefore(modal, null); // every modal can be openend via document-wide event, see openOnEvent document.addEventListener('modal-open', openOnEvent, false); @@ -57,21 +61,9 @@ } if ('dynamic' in modal.dataset) { - var dynamicContentURL = trigger.getAttribute('href'); - - console.log(dynamicContentURL); - - if (dynamicContentURL.length > 0) { - fetch(dynamicContentURL, { - credentials: 'same-origin', - headers: { - #{String (toPathPiece HeaderIsModal)}: 'True' - } - }).then(function(response) { - return response.text(); - }).then(function(body) { + function fetchModal(url, init) { + function responseHtml(body) { var modalContent = document.createElement('div'); - modalContent.classList.add('modal__content'); modalContent.innerHTML = body; var contentBody = modalContent.querySelector('.main__content-body'); @@ -122,6 +114,7 @@ modalContent = contentBody; } + modalContent.classList.add('modal__content'); var nudgeAttr = function(attr, x) { var oldVal = x.getAttribute(attr); @@ -136,11 +129,12 @@ modalContent.querySelectorAll('[' + attr + ']').forEach(function(x) { nudgeAttr(attr, x); }); }); + modal.querySelectorAll('.modal__content').forEach(function(prev) { modal.removeChild(prev); }); modal.appendChild(modalContent); var triggerContentLoad = function() { document.dispatchEvent(new CustomEvent('setup', { - detail: { scope: modalContent }, + detail: { scope: modal }, bubbles: true, cancelable: true })); @@ -151,14 +145,110 @@ } triggerContentLoad(); + + return 'html'; + } + + function responseJson(data) { + var alertsEl = document.querySelector('#alerts'); + if (!alertsEl) + return null; + + for (var i = 0; i < data.length; i++) { + var alert = document.createElement('div'); + alert.classList.add('alert', 'alert-' + data[i].class); + var alertContent = document.createElement('div'); + alertContent.classList.add('alert__content'); + alertContent.innerHTML = data[i].content; + alert.appendChild(alertContent); + + alertsEl.insertBefore(alert, alertsEl.querySelector('.alerts__toggler')); + } + + document.dispatchEvent(new CustomEvent('setup', { detail: { scope: alertsEl }, bubbles: true, cancelable: true })); + + return 'json'; + } + + + return fetch(url, init).then(function(response) { + var contentType = response.headers.get('Content-Type') + if (contentType && contentType.includes('text/html')) { + return response.text().then(responseHtml); + } else if (contentType && contentType.includes('application/json')) { + return response.json().then(responseJson); + } else { + console.log(response); + return null; + } }); - } + }; + + modal.addEventListener('modal-fetch', function(event) { + var dynamicContentURL = (event.detail && event.detail.url) || trigger.getAttribute('href'); + console.log(dynamicContentURL); + + var fetchInit = (event.detail && event.detail.init) || { + credentials: 'same-origin', + headers: { + #{String (toPathPiece HeaderIsModal)}: 'True' + } + }; + + if (dynamicContentURL.length > 0) { + fetchModal(dynamicContentURL, fetchInit).then((event.detail && event.detail.then) || (function(){})); + } + }); + modal.dispatchEvent(new CustomEvent('modal-fetch')); } + // tell further modals, that this one already got initialized modal.classList.add('js-modal-initialized'); + + modal.addEventListener('modal-close', close); } setup(); }; + + window.utils.ajaxSubmit = function(modal, form) { + function doSubmit(event) { + event.preventDefault(); + + console.log('ajaxSubmit', modal, form); + + var modalContent = modal.querySelector('.modal__content'); + modalContent.style.pointerEvents = 'none'; + modalContent.style.opacity = 0.5; + + modal.dispatchEvent(new CustomEvent('modal-fetch', { + detail: { + url: form.target, + init: { + credentials: 'same-origin', + headers: { + #{String (toPathPiece HeaderIsModal)}: 'True' + }, + method: form.method, + body: new FormData(form) + }, + then: (function(typ) { + modalContent.style.pointerEvents = 'auto'; + modalContent.style.opacity = 1; + + if (typ === 'json') { + modal.dispatchEvent(new CustomEvent('modal-close')); + modal.dispatchEvent(new CustomEvent('modal-fetch')); + } + }) + }, + bubbles: true, + cancelable: true + })); + }; + + form.addEventListener('submit', doSubmit); + form.classList.add('js-ajax-initialized'); + }; })(); document.addEventListener('setup', function(e) { @@ -167,5 +257,17 @@ document.addEventListener('setup', function(e) { new utils.modal(modal); }); + if (e.detail.scope.classList.contains('js-modal')) { + Array.from(e.detail.scope.querySelectorAll('form[data-ajax-submit]:not(.js-ajax-initialized)')).map(function(form) { + window.utils.ajaxSubmit(e.detail.scope, form); + }); + } else { + Array.from(e.detail.scope.querySelectorAll('.js-modal')).map(function(modal) { + Array.from(modal.querySelectorAll('form[data-ajax-submit]:not(.js-ajax-initialized)')).map(function(form) { + window.utils.ajaxSubmit(modal, form); + }); + }); + }; + }, false); diff --git a/templates/table/layout.julius b/templates/table/layout.julius index 1ed9c1f88..25ded585f 100644 --- a/templates/table/layout.julius +++ b/templates/table/layout.julius @@ -6,6 +6,9 @@ function setupAsync(wrapper) { var table = wrapper.querySelector('#' + #{String $ dbtIdent}); + if (!table) + return; + var ths = Array.from(table.querySelectorAll('th.sortable')); var pagination = wrapper.querySelector('#' + #{String $ dbtIdent} + '-pagination'); diff --git a/templates/widgets/modal.hamlet b/templates/widgets/modal.hamlet index 8ffb8a53a..767fde13b 100644 --- a/templates/widgets/modal.hamlet +++ b/templates/widgets/modal.hamlet @@ -1,5 +1,6 @@
$case modalContent $of Right content - ^{content} +
+ ^{content} $of Left _ From 67f6103f87238b34f0b14bd3482547e87a281bac Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Thu, 22 Nov 2018 20:56:49 +0100 Subject: [PATCH 3/5] Debug javascript setup --- src/Foundation.hs | 2 +- src/Handler/Utils/Form.hs | 2 +- templates/default-layout-wrapper.hamlet | 6 -- templates/default-layout.hamlet | 14 ++-- templates/standalone/alerts.julius | 7 ++ templates/standalone/datepicker.julius | 7 ++ templates/standalone/inputs.julius | 6 ++ templates/standalone/modal.julius | 89 +++++++++++++++--------- templates/standalone/modal.lucius | 9 ++- templates/standalone/showHide.julius | 6 ++ templates/standalone/tooltip.julius | 1 - templates/widgets/asidenav.julius | 6 ++ templates/widgets/form.julius | 7 +- templates/widgets/navbar.hamlet | 6 +- templates/widgets/pageactionprime.hamlet | 4 +- 15 files changed, 116 insertions(+), 56 deletions(-) diff --git a/src/Foundation.hs b/src/Foundation.hs index 301ca8939..da72da1af 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -804,7 +804,7 @@ siteLayout headingOverride widget = do return (c, courseRoute, items') mmsgs <- if - | isModal -> return [] + | isModal -> getMessages | otherwise -> do applySystemMessages authTagPivots <- fromMaybe Set.empty <$> getSessionJson SessionInactiveAuthTags diff --git a/src/Handler/Utils/Form.hs b/src/Handler/Utils/Form.hs index 32a1d28d9..d1c9d3e4b 100644 --- a/src/Handler/Utils/Form.hs +++ b/src/Handler/Utils/Form.hs @@ -593,7 +593,7 @@ formResultModal :: (MonadHandler m, RedirectUrl (HandlerSite m) route) => FormRe formResultModal res finalDest handler = maybeT_ $ do messages <- case res of FormMissing -> mzero - FormFailure errs -> return $ map (Message Error . toHtml) errs + FormFailure errs -> mapM_ (addMessage Error . toHtml) errs >> mzero FormSuccess val -> lift . execWriterT $ handler val isModal <- hasCustomHeader HeaderIsModal diff --git a/templates/default-layout-wrapper.hamlet b/templates/default-layout-wrapper.hamlet index b5b94d5d1..c293b607a 100644 --- a/templates/default-layout-wrapper.hamlet +++ b/templates/default-layout-wrapper.hamlet @@ -17,10 +17,4 @@ $newline never