From bd334c73d560da2ae5b8a68759d771eaa9bfeabe Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 3 Mar 2019 21:54:43 +0100 Subject: [PATCH 01/14] expose scope of js util as part of return object --- static/js/utils/alerts.js | 1 + static/js/utils/asidenav.js | 1 + static/js/utils/asyncForm.js | 1 + static/js/utils/asyncTable.js | 1 + static/js/utils/asyncTableFilter.js | 1 + static/js/utils/checkAll.js | 1 + static/js/utils/form.js | 4 ++++ static/js/utils/inputs.js | 5 +++++ static/js/utils/modal.js | 1 + static/js/utils/showHide.js | 1 + static/js/utils/tabber.js | 4 ---- 11 files changed, 17 insertions(+), 4 deletions(-) diff --git a/static/js/utils/alerts.js b/static/js/utils/alerts.js index 03f36b7aa..b854495a0 100644 --- a/static/js/utils/alerts.js +++ b/static/js/utils/alerts.js @@ -89,6 +89,7 @@ alertElements.forEach(initAlert); return { + scope: alertsEl, destroy: function() {}, }; }; diff --git a/static/js/utils/asidenav.js b/static/js/utils/asidenav.js index cbe43fb4f..bb95f6455 100644 --- a/static/js/utils/asidenav.js +++ b/static/js/utils/asidenav.js @@ -57,6 +57,7 @@ initAsidenavSubmenus(); return { + scope: asideEl, destroy: function() {}, }; }; diff --git a/static/js/utils/asyncForm.js b/static/js/utils/asyncForm.js index 0ce2ed986..89ce162a2 100644 --- a/static/js/utils/asyncForm.js +++ b/static/js/utils/asyncForm.js @@ -58,6 +58,7 @@ setup(); return { + scope: formElement, destroy: function() {}, }; }; diff --git a/static/js/utils/asyncTable.js b/static/js/utils/asyncTable.js index 0eeb7528d..ea6458633 100644 --- a/static/js/utils/asyncTable.js +++ b/static/js/utils/asyncTable.js @@ -225,6 +225,7 @@ init(); return { + scope: wrapper, destroy: destroyUtils, }; }; diff --git a/static/js/utils/asyncTableFilter.js b/static/js/utils/asyncTableFilter.js index 478eba9d1..98d9cda75 100644 --- a/static/js/utils/asyncTableFilter.js +++ b/static/js/utils/asyncTableFilter.js @@ -159,6 +159,7 @@ setup(); return { + scope: formElement, destroy: function() {}, }; } diff --git a/static/js/utils/checkAll.js b/static/js/utils/checkAll.js index 8cbeb28b6..b37a89454 100644 --- a/static/js/utils/checkAll.js +++ b/static/js/utils/checkAll.js @@ -124,6 +124,7 @@ init(); return { + scope: wrapper, destroy: destroy, }; }; diff --git a/static/js/utils/form.js b/static/js/utils/form.js index 0c919ee8f..8dc8642a2 100644 --- a/static/js/utils/form.js +++ b/static/js/utils/form.js @@ -54,6 +54,7 @@ } return { + scope: form, destroy: destroyUtils, }; }; @@ -97,6 +98,7 @@ } return { + scope: form, destroy: function() {}, }; }; @@ -138,6 +140,7 @@ } return { + scope: form, destroy: function() {}, }; }; @@ -149,6 +152,7 @@ } return { + scope: form, destroy: function() {}, }; }; diff --git a/static/js/utils/inputs.js b/static/js/utils/inputs.js index 4d8f79946..85229e678 100644 --- a/static/js/utils/inputs.js +++ b/static/js/utils/inputs.js @@ -44,6 +44,7 @@ } return { + scope: wrapper, destroy: destroyUtils, }; }; @@ -135,6 +136,7 @@ }); return { + scope: input, destroy: function() {}, }; } @@ -169,6 +171,7 @@ setup(); return { + scope: input, destroy: function() {}, }; } @@ -195,6 +198,7 @@ } return { + scope: input, destroy: function() {}, }; } @@ -218,6 +222,7 @@ } return { + scope: input, destroy: function() {}, }; } diff --git a/static/js/utils/modal.js b/static/js/utils/modal.js index 4273c5d4d..8bf15bd1a 100644 --- a/static/js/utils/modal.js +++ b/static/js/utils/modal.js @@ -146,6 +146,7 @@ } return { + scope: modalElement, destroy: destroyUtils, }; }; diff --git a/static/js/utils/showHide.js b/static/js/utils/showHide.js index 9f7a4a4df..0441cde4b 100644 --- a/static/js/utils/showHide.js +++ b/static/js/utils/showHide.js @@ -70,6 +70,7 @@ }); return { + scope: wrapper, destroy: function() {}, }; }; diff --git a/static/js/utils/tabber.js b/static/js/utils/tabber.js index e0dd952fe..38fb43578 100644 --- a/static/js/utils/tabber.js +++ b/static/js/utils/tabber.js @@ -86,9 +86,5 @@ $(t).tabgroup(); }); } - - return { - destroy: function() {}, - }; }); })($); From e0c7edc1ca23edf68abbd51c34699584910ebe59 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 3 Mar 2019 22:23:08 +0100 Subject: [PATCH 02/14] pass i18n object to each js util instance --- static/js/utils/setup.js | 5 +++++ templates/default-layout.julius | 16 +++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/static/js/utils/setup.js b/static/js/utils/setup.js index 6ed7c4a35..5a32a6166 100644 --- a/static/js/utils/setup.js +++ b/static/js/utils/setup.js @@ -22,6 +22,11 @@ options = options || {}; + // i18n + if (window.I18N) { + options.i18n = window.I18N; + } + var listener = function(event) { if (event.detail.targetUtil !== utilName) { diff --git a/templates/default-layout.julius b/templates/default-layout.julius index 52f28f0d7..d83daccd0 100644 --- a/templates/default-layout.julius +++ b/templates/default-layout.julius @@ -35,14 +35,16 @@ function setupDatepicker(wrapper) { }); } -document.addEventListener('DOMContentLoaded', function() { - var I18N = { - filesSelected: 'Dateien ausgewählt', // TODO: interpolate these to be translated - selectFile: 'Datei auswählen', - selectFiles: 'Datei(en) auswählen', - }; +// this global I18N object will be picked up automatically by the setup util +window.I18N = { + filesSelected: 'Dateien ausgewählt', // TODO: interpolate these to be translated + selectFile: 'Datei auswählen', + selectFiles: 'Datei(en) auswählen', + asyncFormFailure: 'Da ist etwas schief gelaufen, das tut uns Leid.
Falls das erneut passiert schicke uns gerne eine kurze Beschreibung dieses Ereignisses über das Hilfe-Widget rechts oben.

Vielen Dank für deine Hilfe', +}; +document.addEventListener('DOMContentLoaded', function() { window.utils.setup('flatpickr', document.body, { setupFunction: setupDatepicker }); window.utils.setup('showHide', document.body); - window.utils.setup('inputs', document.body, { i18n: I18N }); + window.utils.setup('inputs', document.body); }); From 010718dfab9a70e6c555500f13be8f806de1c092 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 3 Mar 2019 23:18:26 +0100 Subject: [PATCH 03/14] store active js util instances to be able to easier destroy them --- static/js/utils/setup.js | 58 +++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/static/js/utils/setup.js b/static/js/utils/setup.js index 5a32a6166..2b34a8161 100644 --- a/static/js/utils/setup.js +++ b/static/js/utils/setup.js @@ -4,6 +4,7 @@ window.utils = window.utils || {}; var registeredSetupListeners = {}; + var activeInstances = {}; /** * setup function to initiate a util (utilName) on a scope (sope) with options (options). @@ -13,20 +14,33 @@ */ window.utils.setup = function(utilName, scope, options) { - - var utilInstance; - if (!utilName || !scope) { return; } options = options || {}; + var utilInstance; + // i18n if (window.I18N) { options.i18n = window.I18N; } + if (activeInstances[utilName]) { + var instanceWithSameScope = activeInstances[utilName] + .filter(function(instance) { return !!instance; }) + .find(function(instance) { + return instance.scope === scope; + }); + var isAlreadySetup = !!instanceWithSameScope; + + if (isAlreadySetup) { + console.warn('Trying to setup a JS utility that\'s already been set up', { utility: utilName, scope, options }); + } + } + + function setup() { var listener = function(event) { if (event.detail.targetUtil !== utilName) { @@ -42,15 +56,25 @@ } 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] && !options.singleton) { + } + + if (!registeredSetupListeners[utilName] || Array.isArray(registeredSetupListeners[utilName])) { + registeredSetupListeners[utilName] = []; + } registeredSetupListeners[utilName].push(listener); - } else { - registeredSetupListeners[utilName] = [ listener ]; - } document.addEventListener('setup', listener); @@ -59,16 +83,32 @@ bubbles: true, cancelable: true, })); + } + + setup(); return utilInstance; }; - window.utils.teardown = function(utilName) { + + window.utils.teardown = function(utilName, destroy) { if (registeredSetupListeners[utilName]) { - registeredSetupListeners[utilName].forEach(function(listener) { + registeredSetupListeners[utilName] + .filter(function(listener) { return !!listener }) + .forEach(function(listener) { document.removeEventListener('setup', listener); }); delete registeredSetupListeners[utilName]; } + + if (destroy === true && activeInstances[utilName]) { + activeInstances[utilName] + .filter(function(instance) { return !!instance }) + .forEach(function(instance) { + console.log({ instance }); + instance.destroy(); + }); + delete activeInstances[utilName]; + } } })(); From fb26d70c40216f2d4662fa9ab27bb97cd883606b Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Thu, 7 Mar 2019 19:22:28 +0100 Subject: [PATCH 04/14] show failure message on failed async form requests --- static/js/utils/asyncForm.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/static/js/utils/asyncForm.js b/static/js/utils/asyncForm.js index 89ce162a2..d57199e72 100644 --- a/static/js/utils/asyncForm.js +++ b/static/js/utils/asyncForm.js @@ -6,9 +6,12 @@ var ASYNC_FORM_RESPONSE_CLASS = 'async-form-response'; var ASYNC_FORM_LOADING_CLASS = 'async-form-loading'; 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) { + options = options || {}; + var lastRequestTimestamp = 0; function setup() { @@ -47,11 +50,21 @@ window.utils.httpClient.post(url, headers, body) .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('Response from "' + url + '" has unexpected Content-Type. Expected: "application/json". Received: "' + (response.headers.get("content-type") || '(undefined)') + '"'); + } }).then(function(response) { - processResponse(response[0]) + processResponse(response[0]); }).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); }); } From d730c369b5cc51ffddf6e8fc2e3dce8c042c1f9c Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Thu, 7 Mar 2019 19:28:30 +0100 Subject: [PATCH 05/14] fix js indentation inconsistencies --- static/js/utils/setup.js | 48 +++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/static/js/utils/setup.js b/static/js/utils/setup.js index 2b34a8161..dc2f2c2e3 100644 --- a/static/js/utils/setup.js +++ b/static/js/utils/setup.js @@ -41,21 +41,20 @@ } 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'); + var listener = function(event) { + if (event.detail.targetUtil !== utilName) { + return false; } - utilInstance = util(scope, options); + 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) { @@ -64,11 +63,11 @@ } else { activeInstances[utilName] = [ utilInstance ]; } - } - }; + } + }; if (registeredSetupListeners[utilName] && Array.isArray(registeredSetupListeners[utilName])) { - window.utils.teardown(utilName); + window.utils.teardown(utilName); } if (!registeredSetupListeners[utilName] || Array.isArray(registeredSetupListeners[utilName])) { @@ -76,13 +75,13 @@ } registeredSetupListeners[utilName].push(listener); - document.addEventListener('setup', listener); + document.addEventListener('setup', listener); - document.dispatchEvent(new CustomEvent('setup', { - detail: { targetUtil: utilName, module: 'none' }, - bubbles: true, - cancelable: true, - })); + document.dispatchEvent(new CustomEvent('setup', { + detail: { targetUtil: utilName, module: 'none' }, + bubbles: true, + cancelable: true, + })); } setup(); @@ -90,14 +89,13 @@ return utilInstance; }; - window.utils.teardown = function(utilName, destroy) { if (registeredSetupListeners[utilName]) { registeredSetupListeners[utilName] .filter(function(listener) { return !!listener }) .forEach(function(listener) { - document.removeEventListener('setup', listener); - }); + document.removeEventListener('setup', listener); + }); delete registeredSetupListeners[utilName]; } From b2cb12d02912b25a839e4c554a0f43d30c75d058 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Thu, 7 Mar 2019 19:30:45 +0100 Subject: [PATCH 06/14] remove obsolete js console.log --- static/js/utils/setup.js | 1 - 1 file changed, 1 deletion(-) diff --git a/static/js/utils/setup.js b/static/js/utils/setup.js index dc2f2c2e3..e9afb216b 100644 --- a/static/js/utils/setup.js +++ b/static/js/utils/setup.js @@ -103,7 +103,6 @@ activeInstances[utilName] .filter(function(instance) { return !!instance }) .forEach(function(instance) { - console.log({ instance }); instance.destroy(); }); delete activeInstances[utilName]; From 6939b73802d56d50a156d89580395a196170a594 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Sun, 10 Mar 2019 14:40:44 +0100 Subject: [PATCH 07/14] old-style json-answers in admin email test --- src/Handler/Admin.hs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Handler/Admin.hs b/src/Handler/Admin.hs index 501cc97b9..946310640 100644 --- a/src/Handler/Admin.hs +++ b/src/Handler/Admin.hs @@ -20,6 +20,8 @@ import Database.Persist.Sql (fromSqlKey) -- import qualified Data.UUID.Cryptographic as UUID +import Control.Monad.Trans.Writer (mapWriterT) + -- BEGIN - Buttons needed only here data ButtonCreate = CreateMath | CreateInf -- Dummy for Example deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable) @@ -84,15 +86,12 @@ postAdminTestR = do _other -> addMessage Warning "KEIN Knopf erkannt" ((emailResult, emailWidget), emailEnctype) <- runFormPost . identifyForm "email" $ renderAForm FormStandard emailTestForm - case emailResult of - (FormSuccess (email, ls)) -> do - jId <- runDB $ do - jId <- queueJob $ JobSendTestEmail email ls - addMessage Success [shamlet|Email-test gestartet (Job ##{tshow (fromSqlKey jId)})|] - return jId - writeJobCtl $ JobCtlPerform jId - FormMissing -> return () - (FormFailure errs) -> forM_ errs $ addMessage Error . toHtml + formResultModal emailResult AdminTestR $ \(email, ls) -> do + jId <- mapWriterT runDB $ do + jId <- queueJob $ JobSendTestEmail email ls + tell . pure $ Message Success [shamlet|Email-test gestartet (Job ##{tshow (fromSqlKey jId)})|] + return jId + writeJobCtl $ JobCtlPerform jId let emailWidget' = [whamlet|
From 824a8e24e189fbf9ad8ad2e0b51df95272b42731 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 10 Mar 2019 15:22:29 +0100 Subject: [PATCH 08/14] beautify async form response in modals --- static/css/utils/asyncForm.scss | 21 +++++++++++++++++++++ static/css/utils/modal.scss | 11 ++++++----- static/js/utils/asyncForm.js | 16 ++++++++++++---- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/static/css/utils/asyncForm.scss b/static/css/utils/asyncForm.scss index 4241d8f31..00c721822 100644 --- a/static/css/utils/asyncForm.scss +++ b/static/css/utils/asyncForm.scss @@ -1,5 +1,26 @@ .async-form-response { margin: 20px 0; + position: relative; + width: 100%; + font-size: 18px; + text-align: center; +} + +.async-form-response--success { + padding-top: 60px; +} + +.async-form-response--success::before { + content: ''; + position: absolute; + top: 0px; + left: 50%; + display: block; + width: 17px; + height: 28px; + border: solid #000; + border-width: 0 5px 5px 0; + transform: translateX(-50%) rotate(45deg); } .async-form-loading { diff --git a/static/css/utils/modal.scss b/static/css/utils/modal.scss index 5cac989a3..2f5d0e168 100644 --- a/static/css/utils/modal.scss +++ b/static/css/utils/modal.scss @@ -3,7 +3,7 @@ left: 50%; top: 50%; transform: translate(-50%, -50%) scale(0.8, 0.8); - display: block; + display: flex; background-color: rgba(255, 255, 255, 1); min-width: 60vw; min-height: 100px; @@ -26,10 +26,6 @@ z-index: 200; transform: translate(-50%, -50%) scale(1, 1); } - - .modal__content { - margin: 20px 0; - } } @media (max-width: 1024px) { @@ -96,3 +92,8 @@ color: white; } } + +.modal__content { + margin: 20px 0; + width: 100%; +} diff --git a/static/js/utils/asyncForm.js b/static/js/utils/asyncForm.js index d57199e72..e417b0ceb 100644 --- a/static/js/utils/asyncForm.js +++ b/static/js/utils/asyncForm.js @@ -19,19 +19,27 @@ } function processResponse(response) { - var responseElement = document.createElement('div'); - responseElement.classList.add(ASYNC_FORM_RESPONSE_CLASS); - responseElement.innerHTML = response.content; + var responseElement = makeResponseElement(response.content, response.class); var parentElement = formElement.parentElement; // make sure there is a delay between click and response var delay = Math.max(0, ASYNC_FORM_MIN_DELAY + lastRequestTimestamp - Date.now()); + setTimeout(function() { parentElement.insertBefore(responseElement, formElement); formElement.remove(); }, delay); } + function makeResponseElement(content, type) { + var responseElement = document.createElement('div'); + type = type || 'info'; + responseElement.classList.add(ASYNC_FORM_RESPONSE_CLASS); + responseElement.classList.add(ASYNC_FORM_RESPONSE_CLASS + '--' + type); + responseElement.innerHTML = content; + return responseElement; + } + function submitHandler(event) { event.preventDefault(); @@ -53,7 +61,7 @@ if (response.headers.get("content-type").indexOf("application/json") !== -1) {// checking response header return response.json(); } else { - throw new TypeError('Response from "' + url + '" has unexpected Content-Type. Expected: "application/json". Received: "' + (response.headers.get("content-type") || '(undefined)') + '"'); + throw new TypeError('Unexpected Content-Type. Expected Content-Type: "application/json". Requested URL:' + url + '"'); } }).then(function(response) { processResponse(response[0]); From 101822fd21802e09cd1987d3d923ac6d0b6fe9eb Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Sun, 10 Mar 2019 15:47:33 +0100 Subject: [PATCH 09/14] =?UTF-8?q?`MessageClass`=20=E2=86=92=20`MessageStat?= =?UTF-8?q?us`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/system-messages | 2 +- src/Foundation.hs | 2 +- src/Handler/Utils/Form.hs | 2 +- src/Model.hs | 2 +- src/Settings.hs | 2 +- src/Utils/Message.hs | 50 +++++++++++++++++++-------------------- 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/models/system-messages b/models/system-messages index 0547718ae..0ceec9223 100644 --- a/models/system-messages +++ b/models/system-messages @@ -2,7 +2,7 @@ SystemMessage from UTCTime Maybe to UTCTime Maybe authenticatedOnly Bool - severity MessageClass + severity MessageStatus defaultLanguage Lang content Html summary Html Maybe diff --git a/src/Foundation.hs b/src/Foundation.hs index 047e3f670..70ad9da14 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -220,7 +220,7 @@ instance RenderMessage UniWorX MsgLanguage where instance RenderMessage UniWorX (UnsupportedAuthPredicate (Route UniWorX)) where 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 ''StudyFieldType id embedRenderMessage ''UniWorX ''SheetFileType id diff --git a/src/Handler/Utils/Form.hs b/src/Handler/Utils/Form.hs index af7379f8c..7bf0de1d1 100644 --- a/src/Handler/Utils/Form.hs +++ b/src/Handler/Utils/Form.hs @@ -654,5 +654,5 @@ formResultModal res finalDest handler = maybeT_ $ do if | isModal -> sendResponse $ toJSON messages | otherwise -> do - forM_ messages $ \Message{..} -> addMessage messageClass messageContent + forM_ messages $ \Message{..} -> addMessage messageStatus messageContent redirect finalDest diff --git a/src/Model.hs b/src/Model.hs index 54acc1b28..4a0e3f1c9 100644 --- a/src/Model.hs +++ b/src/Model.hs @@ -19,7 +19,7 @@ import Data.Aeson (Value) import Data.CaseInsensitive (CI) import Data.CaseInsensitive.Instances () -import Utils.Message (MessageClass) +import Utils.Message (MessageStatus) import Settings.Cluster (ClusterSettingsKey) import Data.Binary (Binary) diff --git a/src/Settings.hs b/src/Settings.hs index 81f98eb45..f717ee378 100644 --- a/src/Settings.hs +++ b/src/Settings.hs @@ -39,7 +39,7 @@ import qualified Data.Text.Encoding as Text import qualified Ldap.Client as Ldap -import Utils hiding (MessageClass(..)) +import Utils hiding (MessageStatus(..)) import Control.Lens import Data.Maybe (fromJust) diff --git a/src/Utils/Message.hs b/src/Utils/Message.hs index 7cf7f653f..69ce9e45e 100644 --- a/src/Utils/Message.hs +++ b/src/Utils/Message.hs @@ -1,6 +1,6 @@ module Utils.Message - ( MessageClass(..) - , UnknownMessageClass(..) + ( MessageStatus(..) + , UnknownMessageStatus(..) , addMessage, addMessageI, addMessageIHamlet, addMessageFile, addMessageWidget , Message(..) , messageI, messageIHamlet, messageFile, messageWidget @@ -25,64 +25,64 @@ import Text.Blaze.Html.Renderer.Text (renderHtml) 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) -instance Universe MessageClass -instance Finite MessageClass +instance Universe MessageStatus +instance Finite MessageStatus deriveJSON defaultOptions { constructorTagModifier = camelToPathPiece - } ''MessageClass + } ''MessageStatus -nullaryPathPiece ''MessageClass camelToPathPiece -derivePersistField "MessageClass" +nullaryPathPiece ''MessageStatus camelToPathPiece +derivePersistField "MessageStatus" -newtype UnknownMessageClass = UnknownMessageClass Text +newtype UnknownMessageStatus = UnknownMessageStatus Text deriving (Eq, Ord, Read, Show, Generic, Typeable) -instance Exception UnknownMessageClass +instance Exception UnknownMessageStatus data Message = Message - { messageClass :: MessageClass + { messageStatus :: MessageStatus , messageContent :: Html } 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 - 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 toJSON Message{..} = object - [ "class" .= messageClass + [ "status" .= messageStatus , "content" .= renderHtml messageContent ] instance FromJSON Message where parseJSON = withObject "Message" $ \o -> do - messageClass <- o .: "class" + messageStatus <- o .: "status" messageContent <- preEscapedText . sanitizeBalance <$> o .: "content" return Message{..} -addMessage :: MonadHandler m => MessageClass -> Html -> m () +addMessage :: MonadHandler m => MessageStatus -> Html -> m () 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) -messageI :: (MonadHandler m, RenderMessage (HandlerSite m) msg) => MessageClass -> msg -> m Message -messageI messageClass msg = do +messageI :: (MonadHandler m, RenderMessage (HandlerSite m) msg) => MessageStatus -> msg -> m Message +messageI messageStatus msg = do messageContent <- toHtml . ($ msg) <$> getMessageRender return Message{..} addMessageIHamlet :: ( MonadHandler m , RenderMessage (HandlerSite m) msg , HandlerSite m ~ site - ) => MessageClass -> HtmlUrlI18n msg (Route site) -> m () + ) => MessageStatus -> HtmlUrlI18n msg (Route site) -> m () addMessageIHamlet mc iHamlet = do mr <- getMessageRender ClassyPrelude.Yesod.addMessage (toPathPiece mc) =<< withUrlRenderer (iHamlet $ toHtml . mr) @@ -90,22 +90,22 @@ addMessageIHamlet mc iHamlet = do messageIHamlet :: ( MonadHandler m , RenderMessage (HandlerSite m) msg , HandlerSite m ~ site - ) => MessageClass -> HtmlUrlI18n msg (Route site) -> m Message + ) => MessageStatus -> HtmlUrlI18n msg (Route site) -> m Message messageIHamlet mc iHamlet = do mr <- getMessageRender Message mc <$> withUrlRenderer (iHamlet $ toHtml . mr) -addMessageFile :: MessageClass -> FilePath -> ExpQ +addMessageFile :: MessageStatus -> FilePath -> ExpQ addMessageFile mc tPath = [e|addMessageIHamlet mc $(ihamletFile tPath)|] -messageFile :: MessageClass -> FilePath -> ExpQ +messageFile :: MessageStatus -> FilePath -> ExpQ messageFile mc tPath = [e|messageIHamlet mc $(ihamletFile tPath)|] addMessageWidget :: forall m site. ( MonadHandler m , HandlerSite m ~ site , Yesod site - ) => MessageClass -> WidgetT site IO () -> m () + ) => MessageStatus -> WidgetT site IO () -> m () -- ^ _Note_: `addMessageWidget` ignores `pageTitle` and `pageHead` addMessageWidget mc wgt = do PageContent{pageBody} <- liftHandlerT $ widgetToPageContent wgt @@ -115,7 +115,7 @@ messageWidget :: forall m site. ( MonadHandler m , HandlerSite m ~ site , Yesod site - ) => MessageClass -> WidgetT site IO () -> m Message + ) => MessageStatus -> WidgetT site IO () -> m Message messageWidget mc wgt = do PageContent{pageBody} <- liftHandlerT $ widgetToPageContent wgt messageIHamlet mc (const pageBody :: HtmlUrlI18n (SomeMessage site) (Route site)) From 24df9cb93e0e58cf28d1f65e8925cdb1ca853d6a Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 10 Mar 2019 16:15:34 +0100 Subject: [PATCH 10/14] integrate api changes in FE --- static/css/utils/asyncForm.scss | 60 +++++++++++++++++++++++++++++---- static/js/utils/asyncForm.js | 8 ++--- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/static/css/utils/asyncForm.scss b/static/css/utils/asyncForm.scss index 00c721822..a0f9956dd 100644 --- a/static/css/utils/asyncForm.scss +++ b/static/css/utils/asyncForm.scss @@ -4,25 +4,73 @@ width: 100%; font-size: 18px; text-align: center; -} - -.async-form-response--success { padding-top: 60px; } -.async-form-response--success::before { - content: ''; +.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 #000; + 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 { opacity: 0.1; transition: opacity 800ms ease-out; diff --git a/static/js/utils/asyncForm.js b/static/js/utils/asyncForm.js index e417b0ceb..aa57ed2a0 100644 --- a/static/js/utils/asyncForm.js +++ b/static/js/utils/asyncForm.js @@ -19,7 +19,7 @@ } function processResponse(response) { - var responseElement = makeResponseElement(response.content, response.class); + var responseElement = makeResponseElement(response.content, response.status); var parentElement = formElement.parentElement; // make sure there is a delay between click and response @@ -31,11 +31,11 @@ }, delay); } - function makeResponseElement(content, type) { + function makeResponseElement(content, status) { var responseElement = document.createElement('div'); - type = type || 'info'; + status = status || 'info'; responseElement.classList.add(ASYNC_FORM_RESPONSE_CLASS); - responseElement.classList.add(ASYNC_FORM_RESPONSE_CLASS + '--' + type); + responseElement.classList.add(ASYNC_FORM_RESPONSE_CLASS + '--' + status); responseElement.innerHTML = content; return responseElement; } From 32e9bb63caa0b6cdc49b631ea419d35cb6766f07 Mon Sep 17 00:00:00 2001 From: SJost Date: Tue, 12 Mar 2019 08:39:19 +0100 Subject: [PATCH 11/14] Minor title fix --- src/Handler/Term.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Handler/Term.hs b/src/Handler/Term.hs index d6accb27c..784486f91 100644 --- a/src/Handler/Term.hs +++ b/src/Handler/Term.hs @@ -145,7 +145,7 @@ getTermShowR = do , dbtIdent = "terms" :: Text } defaultLayout $ do - setTitle "Freigeschaltete Semester" + setTitleI MsgTermsHeading $(widgetFile "terms") getTermEditR :: Handler Html From 6e662b9fe97c27f9b0735cd0384020c5e633fe44 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Thu, 14 Mar 2019 17:15:25 +0100 Subject: [PATCH 12/14] fix async table filter trying to submit twice closes #316 --- static/js/utils/asyncTable.js | 4 +++- static/js/utils/form.js | 4 +++- static/js/utils/inputs.js | 4 +++- static/js/utils/modal.js | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/static/js/utils/asyncTable.js b/static/js/utils/asyncTable.js index ea6458633..5e8e371e9 100644 --- a/static/js/utils/asyncTable.js +++ b/static/js/utils/asyncTable.js @@ -217,7 +217,9 @@ } function destroyUtils() { - utilInstances.forEach(function(utilInstance) { + utilInstances.filter(function(utilInstance) { + return !!utilInstance; + }).forEach(function(utilInstance) { utilInstance.destroy(); }); } diff --git a/static/js/utils/form.js b/static/js/utils/form.js index 8dc8642a2..e45fd56c0 100644 --- a/static/js/utils/form.js +++ b/static/js/utils/form.js @@ -48,7 +48,9 @@ form.classList.add(JS_INITIALIZED); function destroyUtils() { - utilInstances.forEach(function(utilInstance) { + utilInstances.filter(function(utilInstance) { + return !!utilInstance; + }).forEach(function(utilInstance) { utilInstance.destroy(); }); } diff --git a/static/js/utils/inputs.js b/static/js/utils/inputs.js index 85229e678..68425b5ba 100644 --- a/static/js/utils/inputs.js +++ b/static/js/utils/inputs.js @@ -38,7 +38,9 @@ }); function destroyUtils() { - utilInstances.forEach(function(utilInstance) { + utilInstances.filter(function(utilInstance) { + return !!utilInstance; + }).forEach(function(utilInstance) { utilInstance.destroy(); }); } diff --git a/static/js/utils/modal.js b/static/js/utils/modal.js index 8bf15bd1a..5c6c1ec43 100644 --- a/static/js/utils/modal.js +++ b/static/js/utils/modal.js @@ -140,7 +140,9 @@ setup(); function destroyUtils() { - utilInstances.forEach(function(utilInstance) { + utilInstances.filter(function(utilInstance) { + return !!utilInstance; + }).forEach(function(utilInstance) { utilInstance.destroy(); }); } From 96303b156af92cd9c73357c188184c82a5698f04 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Tue, 19 Mar 2019 13:19:54 +0100 Subject: [PATCH 13/14] Enforce structured `identifyForm` --- src/Handler/Admin.hs | 6 +-- src/Handler/Corrections.hs | 6 +-- src/Handler/Course.hs | 6 +-- src/Handler/Profile.hs | 2 +- src/Handler/Sheet.hs | 4 +- src/Handler/Submission.hs | 2 +- src/Handler/SystemMessage.hs | 8 +-- src/Handler/Utils/Delete.hs | 2 +- src/Handler/Utils/Form.hs | 36 ------------- src/Handler/Utils/Table/Pagination.hs | 4 +- src/Import/NoFoundation.hs | 2 +- src/Utils.hs | 28 +--------- src/Utils/Form.hs | 72 ++++++++++++++++++++++--- src/Utils/Parameters.hs | 78 +++++++++++++++++++++++++++ 14 files changed, 167 insertions(+), 89 deletions(-) create mode 100644 src/Utils/Parameters.hs diff --git a/src/Handler/Admin.hs b/src/Handler/Admin.hs index 946310640..6edcbf05f 100644 --- a/src/Handler/Admin.hs +++ b/src/Handler/Admin.hs @@ -57,7 +57,7 @@ emailTestForm = (,) SelFormatTime -> t makeDemoForm :: Int -> Form (Int,Bool,Double) -makeDemoForm n = identifyForm "adminTestForm" $ \html -> do -- Important: used identForm instead! +makeDemoForm n = identifyForm ("adminTestForm" :: Text) $ \html -> do (result, widget) <- flip (renderAForm FormStandard) html $ (,,) <$> areq (minIntField n "Zahl") (fromString $ "Ganzzahl > " ++ show n) Nothing <* aformSection MsgFormBehaviour @@ -78,14 +78,14 @@ makeDemoForm n = identifyForm "adminTestForm" $ \html -> do -- Important: used i getAdminTestR, postAdminTestR :: Handler Html -- Demo Page. Referenzimplementierungen sollte hier gezeigt werden! getAdminTestR = postAdminTestR postAdminTestR = do - ((btnResult, btnWdgt), btnEnctype) <- runFormPost $ identifyForm "buttons" (buttonForm :: Form ButtonCreate) + ((btnResult, btnWdgt), btnEnctype) <- runFormPost $ identifyForm ("buttons" :: Text) (buttonForm :: Form ButtonCreate) case btnResult of (FormSuccess CreateInf) -> addMessage Info "Informatik-Knopf gedrückt" (FormSuccess CreateMath) -> addMessage Warning "Knopf Mathematik erkannt" FormMissing -> return () _other -> addMessage Warning "KEIN Knopf erkannt" - ((emailResult, emailWidget), emailEnctype) <- runFormPost . identifyForm "email" $ renderAForm FormStandard emailTestForm + ((emailResult, emailWidget), emailEnctype) <- runFormPost . identifyForm ("email" :: Text) $ renderAForm FormStandard emailTestForm formResultModal emailResult AdminTestR $ \(email, ls) -> do jId <- mapWriterT runDB $ do jId <- queueJob $ JobSendTestEmail email ls diff --git a/src/Handler/Corrections.hs b/src/Handler/Corrections.hs index 14a50dc03..8f802798f 100644 --- a/src/Handler/Corrections.hs +++ b/src/Handler/Corrections.hs @@ -614,13 +614,13 @@ postCorrectionR tid ssh csh shn cid = do (fslpI MsgRatingPoints "Punktezahl") (Just submissionRatingPoints) - ((corrResult, corrForm), corrEncoding) <- runFormPost . identForm FIDcorrection . renderAForm FormStandard $ (,,) + ((corrResult, corrForm), corrEncoding) <- runFormPost . identifyForm FIDcorrection . renderAForm FormStandard $ (,,) <$> areq checkBoxField (fslI MsgRatingDone) (Just $ submissionRatingDone Submission{..}) <*> pointsForm <*> (((\t -> t <$ guard (not $ null t)) =<<) . fmap (Text.strip . unTextarea) <$> aopt textareaField (fslI MsgRatingComment) (Just $ Textarea <$> submissionRatingComment)) <* submitButton - ((uploadResult, uploadForm), uploadEncoding) <- runFormPost . identForm FIDcorrectionUpload . renderAForm FormStandard $ + ((uploadResult, uploadForm), uploadEncoding) <- runFormPost . identifyForm FIDcorrectionUpload . renderAForm FormStandard $ areq (zipFileField True) (fslI MsgRatingFiles) Nothing <* submitButton @@ -693,7 +693,7 @@ getCorrectionUserR tid ssh csh shn cid = do getCorrectionsUploadR, postCorrectionsUploadR :: Handler Html getCorrectionsUploadR = postCorrectionsUploadR postCorrectionsUploadR = do - ((uploadRes, upload), uploadEncoding) <- runFormPost . identForm FIDcorrectionsUpload . renderAForm FormStandard $ + ((uploadRes, upload), uploadEncoding) <- runFormPost . identifyForm FIDcorrectionsUpload . renderAForm FormStandard $ areq (zipFileField True) (fslI MsgCorrUploadField) Nothing <* submitButton diff --git a/src/Handler/Course.hs b/src/Handler/Course.hs index 30399b505..6cc98cc64 100644 --- a/src/Handler/Course.hs +++ b/src/Handler/Course.hs @@ -286,7 +286,7 @@ getCShowR tid ssh csh = do mRegTo <- traverse (formatTime SelFormatDateTime) $ courseRegisterTo course mDereg <- traverse (formatTime SelFormatDateTime) $ courseDeregisterUntil course mRegAt <- traverse (formatTime SelFormatDateTime) registered - (regWidget, regEnctype) <- generateFormPost $ identifyForm "registerBtn" $ registerForm (isJust mRegAt) $ courseRegisterSecret course + (regWidget, regEnctype) <- generateFormPost $ identifyForm FIDCourseRegister $ registerForm (isJust mRegAt) $ courseRegisterSecret course registrationOpen <- (==Authorized) <$> isAuthorized (CourseR tid ssh csh CRegisterR) True siteLayout (toWgt $ courseName course) $ do setTitle [shamlet| #{toPathPiece tid} - #{csh}|] @@ -312,7 +312,7 @@ postCRegisterR tid ssh csh = do (Entity cid course) <- getBy404 $ TermSchoolCourseShort tid ssh csh registered <- isJust <$> getBy (UniqueParticipant aid cid) return (cid, course, registered) - ((regResult,_), _) <- runFormPost $ identifyForm "registerBtn" $ registerForm registered $ courseRegisterSecret course + ((regResult,_), _) <- runFormPost $ identifyForm FIDCourseRegister $ registerForm registered $ courseRegisterSecret course case regResult of (FormSuccess codeOk) | registered -> do @@ -528,7 +528,7 @@ courseToForm (Entity cid Course{..}) = CourseForm } makeCourseForm :: Maybe CourseForm -> Form CourseForm -makeCourseForm template = identForm FIDcourse $ \html -> do +makeCourseForm template = identifyForm FIDcourse $ \html -> do -- TODO: Refactor to avoid the four repeated calls to liftHandlerT and three runDBs -- let editCid = cfCourseId =<< template -- possible start for refactoring diff --git a/src/Handler/Profile.hs b/src/Handler/Profile.hs index a57e1149c..5b767b1fe 100644 --- a/src/Handler/Profile.hs +++ b/src/Handler/Profile.hs @@ -27,7 +27,7 @@ data SettingsForm = SettingsForm } makeSettingForm :: Maybe SettingsForm -> Form SettingsForm -makeSettingForm template = identForm FIDsettings $ \html -> do +makeSettingForm template = identifyForm FIDsettings $ \html -> do (result, widget) <- flip (renderAForm FormStandard) html $ SettingsForm <$ aformSection MsgFormCosmetics <*> areq (natFieldI $ MsgNatField "Favoriten") -- TODO: natFieldI not working here diff --git a/src/Handler/Sheet.hs b/src/Handler/Sheet.hs index f6e4fe51c..c2c8136d1 100644 --- a/src/Handler/Sheet.hs +++ b/src/Handler/Sheet.hs @@ -90,7 +90,7 @@ getFtIdMap sId = do return $ partitionFileType [(t,i)|(E.Value t, E.Value i) <- allfIds] makeSheetForm :: Maybe SheetId -> Maybe SheetForm -> Form SheetForm -makeSheetForm msId template = identForm FIDsheet $ \html -> do +makeSheetForm msId template = identifyForm FIDsheet $ \html -> do oldFileIds <- (return.) <$> case msId of Nothing -> return $ partitionFileType mempty (Just sId) -> liftHandlerT $ runDB $ getFtIdMap sId @@ -780,7 +780,7 @@ postSCorrR = getSCorrR getSCorrR tid ssh csh shn = do Entity shid Sheet{..} <- runDB $ fetchSheet tid ssh csh shn - ((res,formWidget), formEnctype) <- runFormPost . identForm FIDcorrectors . renderAForm FormStandard $ formToAForm (correctorForm shid) <* submitButton + ((res,formWidget), formEnctype) <- runFormPost . identifyForm FIDcorrectors . renderAForm FormStandard $ formToAForm (correctorForm shid) <* submitButton case res of FormFailure errs -> mapM_ (addMessage Error . toHtml) errs diff --git a/src/Handler/Submission.hs b/src/Handler/Submission.hs index feb44cb9b..6ce62d265 100644 --- a/src/Handler/Submission.hs +++ b/src/Handler/Submission.hs @@ -49,7 +49,7 @@ import System.FilePath makeSubmissionForm :: Maybe SubmissionId -> UploadMode -> SheetGroup -> NonEmpty UserEmail -> Form (Maybe (Source Handler File), NonEmpty UserEmail) -makeSubmissionForm msmid uploadMode grouping (self :| buddies) = identForm FIDsubmission $ \html -> do +makeSubmissionForm msmid uploadMode grouping (self :| buddies) = identifyForm FIDsubmission $ \html -> do let fileUploadForm = case uploadMode of NoUpload -> pure Nothing diff --git a/src/Handler/SystemMessage.hs b/src/Handler/SystemMessage.hs index ad791a9e6..c219b394a 100644 --- a/src/Handler/SystemMessage.hs +++ b/src/Handler/SystemMessage.hs @@ -35,7 +35,7 @@ postMessageR cID = do let mkForm = do - ((modifyRes, modifyView), modifyEnctype) <- runFormPost . identForm FIDSystemMessageModify . renderAForm FormStandard + ((modifyRes, modifyView), modifyEnctype) <- runFormPost . identifyForm FIDSystemMessageModify . renderAForm FormStandard $ SystemMessage <$> aopt utcTimeField (fslI MsgSystemMessageFrom) (Just systemMessageFrom) <*> aopt utcTimeField (fslI MsgSystemMessageTo) (Just systemMessageTo) @@ -51,7 +51,7 @@ postMessageR cID = do modifyTranss' <- forM ts' $ \(Entity tId SystemMessageTranslation{..}) -> do cID' <- encrypt tId - runFormPost . identForm (FIDSystemMessageModifyTranslation $ ciphertext cID') . renderAForm FormStandard + runFormPost . identifyForm (FIDSystemMessageModifyTranslation $ ciphertext cID') . renderAForm FormStandard $ (,) <$> fmap (Entity tId) ( SystemMessageTranslation @@ -64,7 +64,7 @@ postMessageR cID = do let modifyTranss = Map.map (view $ _1._1) modifyTranss' - ((addTransRes, addTransView), addTransEnctype) <- runFormPost . identForm FIDSystemMessageAddTranslation . renderAForm FormStandard + ((addTransRes, addTransView), addTransEnctype) <- runFormPost . identifyForm FIDSystemMessageAddTranslation . renderAForm FormStandard $ SystemMessageTranslation <$> pure smId <*> areq (langField False) (fslpI MsgSystemMessageLanguage "RFC1766-Sprachcode") Nothing @@ -246,7 +246,7 @@ postMessageListR = do FormSuccess (_, _selection) -- prop> null _selection -> addMessageI Error MsgSystemMessageEmptySelection - ((addRes, addView), addEncoding) <- runFormPost . identForm FIDSystemMessageAdd . renderAForm FormStandard $ SystemMessage + ((addRes, addView), addEncoding) <- runFormPost . identifyForm FIDSystemMessageAdd . renderAForm FormStandard $ SystemMessage <$> aopt utcTimeField (fslI MsgSystemMessageFrom) Nothing <*> aopt utcTimeField (fslI MsgSystemMessageTo) Nothing <*> areq checkBoxField (fslI MsgSystemMessageAuthenticatedOnly) Nothing diff --git a/src/Handler/Utils/Delete.hs b/src/Handler/Utils/Delete.hs index 400ef2d72..e98d7d98f 100644 --- a/src/Handler/Utils/Delete.hs +++ b/src/Handler/Utils/Delete.hs @@ -51,7 +51,7 @@ confirmForm confirmString = flip traverseAForm aform $ \(inpConfirmStr, BtnDelet multiple = length (filter (not . Text.null . Text.strip) $ Text.lines confirmString) > 1 confirmForm' :: PersistEntity record => Set (Key record) -> Text -> Form Bool -confirmForm' drRecords confirmString = addDeleteTargets . identForm FIDDelete . renderAForm FormStandard $ confirmForm confirmString +confirmForm' drRecords confirmString = addDeleteTargets . identifyForm FIDDelete . renderAForm FormStandard $ confirmForm confirmString where addDeleteTargets :: Form a -> Form a addDeleteTargets form csrf = do diff --git a/src/Handler/Utils/Form.hs b/src/Handler/Utils/Form.hs index a3454fe32..cbaed2eaf 100644 --- a/src/Handler/Utils/Form.hs +++ b/src/Handler/Utils/Form.hs @@ -564,42 +564,6 @@ optionsPersistCryptoId filts ords toDisplay = fmap mkOptionList $ do , optionExternalValue = toPathPiece (cId :: CryptoID UUID (Key a)) }) cPairs -mforced :: (site ~ HandlerSite m, MonadHandler m) - => Field m a -> FieldSettings site -> a -> MForm m (FormResult a, FieldView site) -mforced Field{..} FieldSettings{..} val = do - tell fieldEnctype - name <- maybe newFormIdent return fsName - theId <- lift $ maybe newIdent return fsId - mr <- getMessageRender - let fsAttrs' = fsAttrs <> [("disabled", "")] - return ( FormSuccess val - , FieldView - { fvLabel = toHtml $ mr fsLabel - , fvTooltip = toHtml <$> fmap mr fsTooltip - , fvId = theId - , fvInput = fieldView theId name fsAttrs' (Right val) False - , fvErrors = Nothing - , fvRequired = False - } - ) - -aforced :: (RenderMessage site FormMessage, HandlerSite m ~ site, MonadHandler m) - => Field m a -> FieldSettings site -> a -> AForm m a -aforced field settings val = formToAForm $ second pure <$> mforced field settings val - -apreq :: (RenderMessage site FormMessage, HandlerSite m ~ site, MonadHandler m) - => Field m a -> FieldSettings site -> Maybe a -> AForm m a --- ^ Pseudo required -apreq f fs mx = formToAForm $ do - mr <- getMessageRender - over _1 (maybe (FormFailure [mr MsgValueRequired]) return =<<) . over _2 (pure . (\fv -> fv { fvRequired = True } )) <$> mopt f fs (Just <$> mx) - -wpreq :: (RenderMessage site FormMessage, HandlerSite m ~ site, MonadHandler m) - => Field m a -> FieldSettings site -> Maybe a -> WForm m (FormResult a) -wpreq f fs mx = mFormToWForm $ do - mr <- getMessageRender - over _1 (maybe (FormFailure [mr MsgValueRequired]) return =<<) . over _2 (\fv -> fv { fvRequired = True } ) <$> mopt f fs (Just <$> mx) - multiAction :: (RenderMessage UniWorX action, PathPiece action, Ord action, Eq action) => Map action (AForm (HandlerT UniWorX IO) a) -> Maybe action diff --git a/src/Handler/Utils/Table/Pagination.hs b/src/Handler/Utils/Table/Pagination.hs index 67e5a3f46..ca3408316 100644 --- a/src/Handler/Utils/Table/Pagination.hs +++ b/src/Handler/Utils/Table/Pagination.hs @@ -605,9 +605,9 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db referencePagesize = psLimit . snd . runPSValidator dbtable $ Just prevPi (((filterRes, filterWdgt), filterEnc), ((pagesizeRes, pagesizeWdgt), pagesizeEnc)) <- mdo - (filterRes'@((filterRes, _), _)) <- runFormGet . identForm FIDDBTableFilter . addPIHiddenField dbtable (prevPi & _piFilter .~ Nothing & _piPage .~ Nothing & _piLimit .~ (formResult' pagesizeRes <|> piLimit prevPi)) . renderAForm FormDBTableFilter $ dbtFilterUI (piFilter prevPi) + (filterRes'@((filterRes, _), _)) <- runFormGet . identifyForm FIDDBTableFilter . addPIHiddenField dbtable (prevPi & _piFilter .~ Nothing & _piPage .~ Nothing & _piLimit .~ (formResult' pagesizeRes <|> piLimit prevPi)) . renderAForm FormDBTableFilter $ dbtFilterUI (piFilter prevPi) - (pagesizeRes'@((pagesizeRes, _), _)) <- lift . runFormGet . identForm FIDDBTablePagesize . addPIHiddenField dbtable (prevPi & _piPage .~ Nothing & _piLimit .~ Nothing & _piFilter .~ (formResult' filterRes <|> piFilter prevPi)) . renderAForm FormDBTablePagesize $ + (pagesizeRes'@((pagesizeRes, _), _)) <- lift . runFormGet . identifyForm FIDDBTablePagesize . addPIHiddenField dbtable (prevPi & _piPage .~ Nothing & _piLimit .~ Nothing & _piFilter .~ (formResult' filterRes <|> piFilter prevPi)) . renderAForm FormDBTablePagesize $ areq (pagesizeField referencePagesize) (fslI MsgDBTablePagesize & addAutosubmit & addName (wIdent "pagesize") & addClass "select--pagesize") (Just referencePagesize) <* autosubmitButton return (filterRes', pagesizeRes') diff --git a/src/Import/NoFoundation.hs b/src/Import/NoFoundation.hs index ff88f3065..4a5735725 100644 --- a/src/Import/NoFoundation.hs +++ b/src/Import/NoFoundation.hs @@ -3,7 +3,7 @@ module Import.NoFoundation , MForm ) where -import ClassyPrelude.Yesod as Import hiding (formatTime, derivePersistFieldJSON, addMessage, addMessageI, (.=), MForm, Proxy, foldlM, static, boolField) +import ClassyPrelude.Yesod as Import hiding (formatTime, derivePersistFieldJSON, addMessage, addMessageI, (.=), MForm, Proxy, foldlM, static, boolField, identifyForm) import Model as Import import Model.Types.JSON as Import import Model.Migration as Import diff --git a/src/Utils.hs b/src/Utils.hs index a523c723b..ad4bc5568 100644 --- a/src/Utils.hs +++ b/src/Utils.hs @@ -25,6 +25,7 @@ import Utils.PathPiece as Utils import Utils.Message as Utils import Utils.Lang as Utils import Control.Lens as Utils (none) +import Utils.Parameters as Utils import Text.Blaze (Markup, ToMarkup) @@ -574,32 +575,7 @@ 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) - - -data GlobalPostParam = PostDeleteTarget - deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic) - -instance Universe GlobalPostParam -instance Finite GlobalPostParam -nullaryPathPiece ''GlobalPostParam (camelToPathPiece' 1) - -lookupGlobalPostParam :: (MonadHandler m, PathPiece result) => GlobalPostParam -> m (Maybe result) -lookupGlobalPostParam ident = (>>= fromPathPiece) <$> lookupPostParam (toPathPiece ident) - -hasGlobalPostParam :: MonadHandler m => GlobalPostParam -> m Bool -hasGlobalPostParam ident = isJust <$> lookupPostParam (toPathPiece ident) +-- Moved to Utils.Parameters --------------------------------- -- Custom HTTP Request-Headers -- diff --git a/src/Utils/Form.hs b/src/Utils/Form.hs index 8c53501f8..625d1c570 100644 --- a/src/Utils/Form.hs +++ b/src/Utils/Form.hs @@ -2,9 +2,11 @@ module Utils.Form where -import ClassyPrelude.Yesod hiding (addMessage, cons, Proxy(..)) +import ClassyPrelude.Yesod hiding (addMessage, cons, Proxy(..), identifyForm) import Settings +import Utils.Parameters + -- import Text.Blaze (toMarkup) -- for debugging import qualified Text.Blaze.Internal as Blaze (null) import qualified Data.Text as T @@ -18,6 +20,8 @@ import qualified Data.Map.Lazy as Map import qualified Data.Set as Set import Control.Monad.Trans.Maybe (MaybeT(..)) +import Control.Monad.Reader.Class (MonadReader(..)) +import Control.Monad.Writer.Class (MonadWriter(..)) import Data.List ((!!)) @@ -210,6 +214,7 @@ data FormIdentifier | FIDDBTableFilter | FIDDBTablePagesize | FIDDelete + | FIDCourseRegister deriving (Eq, Ord, Read, Show) instance PathPiece FormIdentifier where @@ -217,11 +222,28 @@ instance PathPiece FormIdentifier where toPathPiece = showToPathPiece -identForm :: (Monad m, PathPiece ident) - => ident -- ^ Form identification - -> (Html -> MForm m (FormResult a, WidgetT (HandlerSite m) IO ())) - -> (Html -> MForm m (FormResult a, WidgetT (HandlerSite m) IO ())) -identForm = identifyForm . toPathPiece +identifyForm :: (Monad m, PathPiece ident, Eq ident) + => ident -- ^ Form identification + -> (Html -> MForm m (FormResult a, widget)) + -> (Html -> MForm m (FormResult a, widget)) +identifyForm identVal form fragment = do + -- Create hidden . + let fragment' = + [shamlet| + + #{fragment} + |] + + -- Check if we got its value back. + hasIdent <- (== Just identVal) <$> lookupGlobalPostParamForm PostFormIdentifier + + -- Run the form proper (with our hidden ). If the + -- data is missing, then do not provide any params to the + -- form, which will turn its result into FormMissing. Also, + -- doing this avoids having lots of fields with red errors. + let eraseParams | not hasIdent = local (\(_, h, l) -> (Nothing, h, l)) + | otherwise = id + fmap (over _1 $ bool (const FormMissing) id hasIdent) . eraseParams $ form fragment' {- Hinweise zur Erinnerung: - identForm primär, wenn es mehr als ein Formular pro Handler gibt @@ -512,4 +534,42 @@ prismAForm p outer form = review p <$> form inner where inner = outer >>= preview p +--------------------------------------------- +-- Special variants of @mopt@, @mreq@, ... -- +--------------------------------------------- +mforced :: (site ~ HandlerSite m, MonadHandler m) + => Field m a -> FieldSettings site -> a -> MForm m (FormResult a, FieldView site) +mforced Field{..} FieldSettings{..} val = do + tell fieldEnctype + name <- maybe newFormIdent return fsName + theId <- lift $ maybe newIdent return fsId + mr <- getMessageRender + let fsAttrs' = fsAttrs <> [("disabled", "")] + return ( FormSuccess val + , FieldView + { fvLabel = toHtml $ mr fsLabel + , fvTooltip = toHtml <$> fmap mr fsTooltip + , fvId = theId + , fvInput = fieldView theId name fsAttrs' (Right val) False + , fvErrors = Nothing + , fvRequired = False + } + ) + +aforced :: (RenderMessage site FormMessage, HandlerSite m ~ site, MonadHandler m) + => Field m a -> FieldSettings site -> a -> AForm m a +aforced field settings val = formToAForm $ second pure <$> mforced field settings val + +apreq :: (RenderMessage site FormMessage, HandlerSite m ~ site, MonadHandler m) + => Field m a -> FieldSettings site -> Maybe a -> AForm m a +-- ^ Pseudo required +apreq f fs mx = formToAForm $ do + mr <- getMessageRender + over _1 (maybe (FormFailure [mr MsgValueRequired]) return =<<) . over _2 (pure . (\fv -> fv { fvRequired = True } )) <$> mopt f fs (Just <$> mx) + +wpreq :: (RenderMessage site FormMessage, HandlerSite m ~ site, MonadHandler m) + => Field m a -> FieldSettings site -> Maybe a -> WForm m (FormResult a) +wpreq f fs mx = mFormToWForm $ do + mr <- getMessageRender + over _1 (maybe (FormFailure [mr MsgValueRequired]) return =<<) . over _2 (\fv -> fv { fvRequired = True } ) <$> mopt f fs (Just <$> mx) diff --git a/src/Utils/Parameters.hs b/src/Utils/Parameters.hs new file mode 100644 index 000000000..81b0c210a --- /dev/null +++ b/src/Utils/Parameters.hs @@ -0,0 +1,78 @@ +module Utils.Parameters + ( GlobalGetParam(..) + , lookupGlobalGetParam, hasGlobalGetParam + , lookupGlobalGetParamForm, hasGlobalGetParamForm + , globalGetParamField + , GlobalPostParam(..) + , lookupGlobalPostParam, hasGlobalPostParam + , lookupGlobalPostParamForm, hasGlobalPostParamForm + , globalPostParamField + ) where + +import ClassyPrelude.Yesod + +import Utils.PathPiece + +import qualified Data.Map as Map + +import Data.Universe + +import Control.Monad.Trans.Maybe (MaybeT(..)) + + +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) + + +lookupGlobalGetParamForm :: (Monad m, PathPiece result) => GlobalGetParam -> MForm m (Maybe result) +lookupGlobalGetParamForm ident = runMaybeT $ do + ps <- MaybeT askParams + MaybeT . return $ Map.lookup (toPathPiece ident) ps >>= listToMaybe >>= fromPathPiece + +hasGlobalGetParamForm :: Monad m => GlobalGetParam -> MForm m Bool +hasGlobalGetParamForm ident = maybe False (Map.member $ toPathPiece ident) <$> askParams + +globalGetParamField :: Monad m => GlobalPostParam -> Field m a -> MForm m (Maybe a) +globalGetParamField ident Field{fieldParse} = runMaybeT $ do + ts <- fromMaybe [] . Map.lookup (toPathPiece ident) <$> MaybeT askParams + fs <- fromMaybe [] . Map.lookup (toPathPiece ident) <$> MaybeT askFiles + MaybeT $ either (const Nothing) id <$> lift (fieldParse ts fs) + +data GlobalPostParam = PostFormIdentifier + | PostDeleteTarget + | PostMassInputShape + deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic) + +instance Universe GlobalPostParam +instance Finite GlobalPostParam +nullaryPathPiece ''GlobalPostParam (camelToPathPiece' 1) + +lookupGlobalPostParam :: (MonadHandler m, PathPiece result) => GlobalPostParam -> m (Maybe result) +lookupGlobalPostParam ident = (>>= fromPathPiece) <$> lookupPostParam (toPathPiece ident) + +hasGlobalPostParam :: MonadHandler m => GlobalPostParam -> m Bool +hasGlobalPostParam ident = isJust <$> lookupPostParam (toPathPiece ident) + +lookupGlobalPostParamForm :: (Monad m, PathPiece result) => GlobalPostParam -> MForm m (Maybe result) +lookupGlobalPostParamForm ident = runMaybeT $ do + ps <- MaybeT askParams + MaybeT . return $ Map.lookup (toPathPiece ident) ps >>= listToMaybe >>= fromPathPiece + +hasGlobalPostParamForm :: Monad m => GlobalPostParam -> MForm m Bool +hasGlobalPostParamForm ident = maybe False (Map.member $ toPathPiece ident) <$> askParams + +globalPostParamField :: Monad m => GlobalPostParam -> Field m a -> MForm m (Maybe a) +globalPostParamField ident Field{fieldParse} = runMaybeT $ do + ts <- fromMaybe [] . Map.lookup (toPathPiece ident) <$> MaybeT askParams + fs <- fromMaybe [] . Map.lookup (toPathPiece ident) <$> MaybeT askFiles + MaybeT $ either (const Nothing) id <$> lift (fieldParse ts fs) From bbeb0e70be2b3db7dea3b6f1f3886bc2c7fde5dd Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Tue, 19 Mar 2019 13:46:11 +0100 Subject: [PATCH 14/14] Identifiers for DBTable-Forms --- src/Handler/Corrections.hs | 1 + src/Handler/SystemMessage.hs | 1 + src/Handler/Utils/Table/Pagination.hs | 25 ++++++++++++++++++++++++- src/Utils/Form.hs | 17 +++++++++++------ 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/Handler/Corrections.hs b/src/Handler/Corrections.hs index 8f802798f..222052f85 100644 --- a/src/Handler/Corrections.hs +++ b/src/Handler/Corrections.hs @@ -353,6 +353,7 @@ correctionsR whereClause displayColumns dbtFilterUI psValidator actions = do return ((, mempty) . Last . Just <$> actionRes, toWidget frag <> action) , dbParamsFormEvaluate = liftHandlerT . runFormPost , dbParamsFormResult = _1 + , dbParamsFormIdent = def } -- -- Similar Query for Statistics over alle possible Table elements (not just the ones shown) -- gradingSummary <- do diff --git a/src/Handler/SystemMessage.hs b/src/Handler/SystemMessage.hs index c219b394a..34ab467ac 100644 --- a/src/Handler/SystemMessage.hs +++ b/src/Handler/SystemMessage.hs @@ -215,6 +215,7 @@ postMessageListR = do return ((, mempty) . Last . Just <$> actionRes, toWidget frag <> action) , dbParamsFormEvaluate = liftHandlerT . runFormPost , dbParamsFormResult = id + , dbParamsFormIdent = def } , dbtIdent = "messages" :: Text } diff --git a/src/Handler/Utils/Table/Pagination.hs b/src/Handler/Utils/Table/Pagination.hs index ca3408316..741117297 100644 --- a/src/Handler/Utils/Table/Pagination.hs +++ b/src/Handler/Utils/Table/Pagination.hs @@ -461,6 +461,19 @@ instance Monoid x => Monoid (DBCell (ReaderT SqlBackend (HandlerT UniWorX IO)) x instance Default (DBParams (ReaderT SqlBackend (HandlerT UniWorX IO)) x) where def = DBParamsDB +data DBParamsFormIdent where + DBParamsFormTableIdent :: DBParamsFormIdent + DBParamsFormOverrideIdent :: forall t. PathPiece t => t -> DBParamsFormIdent + DBParamsFormNoIdent :: DBParamsFormIdent + +instance Default DBParamsFormIdent where + def = DBParamsFormTableIdent + +unDBParamsFormIdent :: DBTable m x -> DBParamsFormIdent -> Maybe Text +unDBParamsFormIdent DBTable{dbtIdent} DBParamsFormTableIdent = Just $ toPathPiece dbtIdent +unDBParamsFormIdent _ (DBParamsFormOverrideIdent x) = Just $ toPathPiece x +unDBParamsFormIdent _ DBParamsFormNoIdent = Nothing + instance Monoid x => IsDBTable (RWST (Maybe (Env, FileEnv), UniWorX, [Lang]) Enctype Ints (HandlerT UniWorX IO)) x where data DBParams (RWST (Maybe (Env, FileEnv), UniWorX, [Lang]) Enctype Ints (HandlerT UniWorX IO)) x = forall a. DBParamsForm { dbParamsFormMethod :: StdMethod @@ -470,6 +483,7 @@ instance Monoid x => IsDBTable (RWST (Maybe (Env, FileEnv), UniWorX, [Lang]) Enc , dbParamsFormAdditional :: Form a , dbParamsFormEvaluate :: forall m' a' x'. (MonadHandler m', HandlerSite m' ~ UniWorX, MonadResource m') => (Html -> MForm (HandlerT UniWorX IO) (FormResult a', x')) -> m' ((FormResult a', x'), Enctype) , dbParamsFormResult :: Lens' x (FormResult a) + , dbParamsFormIdent :: DBParamsFormIdent } type DBResult (RWST (Maybe (Env, FileEnv), UniWorX, [Lang]) Enctype Ints (HandlerT UniWorX IO)) x = (x, Widget) -- type DBResult' (RWST (Maybe (Env, FileEnv), UniWorX, [Lang]) Enctype Ints (HandlerT UniWorX IO)) (FormResult a) = (FormResult a, Enctype) @@ -492,7 +506,15 @@ instance Monoid x => IsDBTable (RWST (Maybe (Env, FileEnv), UniWorX, [Lang]) Enc dbWidget _ _ = return . snd dbHandler _ _ f = return . over _2 f -- runDBTable :: forall m' k'. (MonadHandler m', HandlerSite m' ~ UniWorX, ToJSON k') => DBTable (MForm (HandlerT UniWorX IO)) x -> PaginationInput -> [k'] -> (MForm (HandlerT UniWorX IO)) (x, Widget) -> ReaderT SqlBackend m' (x, Widget) - runDBTable dbtable@(DBTable{ dbtParams = dbtParams@DBParamsForm{..} }) pi pKeys = fmap ((\(res, (wdgt, x)) -> (x & dbParamsFormResult .~ res, wdgt)) . view _1) . dbParamsFormEvaluate . fmap (fmap $ \(x, wdgt) -> (x ^. dbParamsFormResult, (wdgt, x))) . dbParamsFormWrap dbtParams . addPIHiddenField dbtable pi . addPreviousHiddenField dbtable pKeys . withFragment + runDBTable dbtable@(DBTable{ dbtParams = dbtParams@DBParamsForm{..} }) pi pKeys + = fmap ((\(res, (wdgt, x)) -> (x & dbParamsFormResult .~ res, wdgt)) . view _1) + . dbParamsFormEvaluate + . fmap (fmap $ \(x, wdgt) -> (x ^. dbParamsFormResult, (wdgt, x))) + . dbParamsFormWrap dbtParams + . maybe id (identifyForm' dbParamsFormResult) (unDBParamsFormIdent dbtable dbParamsFormIdent) + . addPIHiddenField dbtable pi + . addPreviousHiddenField dbtable pKeys + . withFragment dbInvalidateResult DBParamsForm{..} reason result = do reasonTxt <- getMessageRender <*> pure reason @@ -510,6 +532,7 @@ instance Monoid x => Default (DBParams (RWST (Maybe (Env, FileEnv), UniWorX, [La , dbParamsFormAdditional = \_ -> return (pure (), mempty) , dbParamsFormEvaluate = liftHandlerT . runFormPost , dbParamsFormResult = lens (\_ -> pure ()) (\s _ -> s) + , dbParamsFormIdent = def } dbParamsFormWrap :: Monoid x => DBParams (RWST (Maybe (Env, FileEnv), UniWorX, [Lang]) Enctype Ints (HandlerT UniWorX IO)) x -> (Html -> MForm (HandlerT UniWorX IO) (x, Widget)) -> (Html -> MForm (HandlerT UniWorX IO) (x, Widget)) diff --git a/src/Utils/Form.hs b/src/Utils/Form.hs index 625d1c570..be58ce97b 100644 --- a/src/Utils/Form.hs +++ b/src/Utils/Form.hs @@ -213,6 +213,7 @@ data FormIdentifier | FIDSystemMessageAddTranslation | FIDDBTableFilter | FIDDBTablePagesize + | FIDDBTable | FIDDelete | FIDCourseRegister deriving (Eq, Ord, Read, Show) @@ -222,11 +223,12 @@ instance PathPiece FormIdentifier where toPathPiece = showToPathPiece -identifyForm :: (Monad m, PathPiece ident, Eq ident) - => ident -- ^ Form identification - -> (Html -> MForm m (FormResult a, widget)) - -> (Html -> MForm m (FormResult a, widget)) -identifyForm identVal form fragment = do +identifyForm' :: (Monad m, PathPiece ident, Eq ident) + => Lens' x (FormResult a) + -> ident -- ^ Form identification + -> (Html -> MForm m (x, widget)) + -> (Html -> MForm m (x, widget)) +identifyForm' resLens identVal form fragment = do -- Create hidden . let fragment' = [shamlet| @@ -243,7 +245,10 @@ identifyForm identVal form fragment = do -- doing this avoids having lots of fields with red errors. let eraseParams | not hasIdent = local (\(_, h, l) -> (Nothing, h, l)) | otherwise = id - fmap (over _1 $ bool (const FormMissing) id hasIdent) . eraseParams $ form fragment' + fmap (over (_1 . resLens) $ bool (const FormMissing) id hasIdent) . eraseParams $ form fragment' + +identifyForm :: (Monad m, PathPiece ident, Eq ident) => ident -> (Html -> MForm m (FormResult a, widget)) -> (Html -> MForm m (FormResult a, widget)) +identifyForm = identifyForm' id {- Hinweise zur Erinnerung: - identForm primär, wenn es mehr als ein Formular pro Handler gibt