bulmex 2.1.0 → 3.0.0
raw patch · 10 files changed
+186/−47 lines, 10 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Reflex.Bulmex.Form.FormTypes: SpinRest :: SpinState
- Reflex.Bulmex.Form.FormTypes: Spinning :: SpinState
- Reflex.Bulmex.Form.FormTypes: data SpinState
- Reflex.Bulmex.Form.FormTypes: instance GHC.Base.Monoid Reflex.Bulmex.Form.FormTypes.SpinState
- Reflex.Bulmex.Form.FormTypes: instance GHC.Base.Semigroup Reflex.Bulmex.Form.FormTypes.SpinState
- Reflex.Bulmex.Form.FormTypes: instance GHC.Show.Show Reflex.Bulmex.Form.FormTypes.SpinState
+ Reflex.Bulmex.Form.FormTypes: FormStateRest :: FormState
+ Reflex.Bulmex.Form.FormTypes: FormStateSpinning :: FormState
+ Reflex.Bulmex.Form.FormTypes: data FormState
+ Reflex.Bulmex.Form.FormTypes: instance GHC.Base.Monoid Reflex.Bulmex.Form.FormTypes.FormState
+ Reflex.Bulmex.Form.FormTypes: instance GHC.Base.Semigroup Reflex.Bulmex.Form.FormTypes.FormState
+ Reflex.Bulmex.Form.FormTypes: instance GHC.Show.Show Reflex.Bulmex.Form.FormTypes.FormState
+ Reflex.Bulmex.Input.Debounce: InputAborted :: InputStates
+ Reflex.Bulmex.Input.Debounce: InputBuffered :: InputStates
+ Reflex.Bulmex.Input.Debounce: InputInitial :: InputStates
+ Reflex.Bulmex.Input.Debounce: InputProcessed :: InputStates
+ Reflex.Bulmex.Input.Debounce: InputStarted :: InputStates
+ Reflex.Bulmex.Input.Debounce: data InputStates
+ Reflex.Bulmex.Input.Debounce: defStateAttr :: InputStates -> Text
+ Reflex.Bulmex.Input.Debounce: withInput :: (PostBuild t m, MonadFix m, MonadHold t m) => (Event t inputEvt -> m (Event t inputEvt)) -> (result -> Bool) -> (Event t result -> Dynamic t InputStates -> m (actArgs, Event t inputEvt, finalRes)) -> (actArgs -> Event t inputEvt -> m (Event t result)) -> m (Event t result, finalRes)
+ Reflex.Bulmex.Input.Debounce: withInputDebounceEvt :: (PostBuild t m, MonadFix m, MonadHold t m, TriggerEvent t m, MonadIO (Performable m), PerformEvent t m) => NominalDiffTime -> (result -> Bool) -> (Dynamic t InputStates -> m (b, Event t inputEvt)) -> (b -> Event t inputEvt -> m (Event t result)) -> m (Event t result, b)
- Reflex.Bulmex.Form: aSpinButtonClass :: (DomBuilder t m, PostBuild t m) => Text -> Dynamic t SpinState -> m () -> m (Event t ())
+ Reflex.Bulmex.Form: aSpinButtonClass :: (DomBuilder t m, PostBuild t m) => Text -> Dynamic t FormState -> m () -> m (Event t ())
- Reflex.Bulmex.Form: actionForm :: (DomBuilder t m, MonadHold t m, MonadFix m) => (a -> Event t () -> m (Event t b)) -> (Event t b -> Dynamic t SpinState -> m (a, Event t FormAction, c)) -> m c
+ Reflex.Bulmex.Form: actionForm :: (DomBuilder t m, MonadHold t m, MonadFix m) => (a -> Event t () -> m (Event t b)) -> (Event t b -> Dynamic t FormState -> m (a, Event t FormAction, c)) -> m c
- Reflex.Bulmex.Form: loadAttr :: SpinState -> AttrMap
+ Reflex.Bulmex.Form: loadAttr :: FormState -> AttrMap
- Reflex.Bulmex.Form: spinWidget :: (DomBuilder t m, PerformEvent t m, TriggerEvent t m, MonadIO (Performable m), MonadHold t m, MonadFix m) => (Dynamic t SpinState -> m (Event t ())) -> (Event t () -> m (Event t b)) -> m (Event t b)
+ Reflex.Bulmex.Form: spinWidget :: (DomBuilder t m, PerformEvent t m, TriggerEvent t m, MonadIO (Performable m), MonadHold t m, MonadFix m) => (Dynamic t FormState -> m (Event t ())) -> (Event t () -> m (Event t b)) -> m (Event t b)
Files
- bulmex.cabal +5/−3
- src/Reflex/Bulmex/Event.hs +18/−1
- src/Reflex/Bulmex/Form.hs +33/−16
- src/Reflex/Bulmex/Form/FormTypes.hs +9/−9
- src/Reflex/Bulmex/Html.hs +1/−0
- src/Reflex/Bulmex/Input/Basic.hs +10/−4
- src/Reflex/Bulmex/Input/Debounce.hs +88/−0
- src/Reflex/Bulmex/Load.hs +4/−3
- src/Reflex/Bulmex/Modal.hs +17/−11
- src/Reflex/Bulmex/Tag/Hide.hs +1/−0
bulmex.cabal view
@@ -4,12 +4,12 @@ -- -- see: https://github.com/sol/hpack ----- hash: d67294831c6d62f3268fe8a5d7abc24d2271686c6842d096537e2fdda8d7a170+-- hash: 437fddec128f807a107380e8703fad0e32c13b707a2252431b9ac45157b6de50 name: bulmex-version: 2.1.0+version: 3.0.0 synopsis: Reflex infused with bulma (css)-description: Reflex infused with bulma, With a bunch of helper functions for making server side rendering easier. See related [blogpost](https://jappieklooster.nl/reflex-server-side-html-rendering.html).+description: Reflex infused with bulma, [Implements](https://hackage.haskell.org/package/bulmex/docs/Reflex-Bulmex-Modal.html) the [modal](https://bulma.io/documentation/components/modal/) for example. Also has helper functions for common tasks. such as making server side rendering easier. See related [blogpost](https://jappieklooster.nl/reflex-server-side-html-rendering.html). category: Web homepage: https://github.com/jappeace/bulmex#readme bug-reports: https://github.com/jappeace/bulmex/issues@@ -35,6 +35,7 @@ Reflex.Bulmex.Form.FormTypes Reflex.Bulmex.Html Reflex.Bulmex.Input.Basic+ Reflex.Bulmex.Input.Debounce Reflex.Bulmex.Input.Polymorphic Reflex.Bulmex.Load Reflex.Bulmex.Modal@@ -81,6 +82,7 @@ Reflex.Bulmex.Form.FormTypes Reflex.Bulmex.Html Reflex.Bulmex.Input.Basic+ Reflex.Bulmex.Input.Debounce Reflex.Bulmex.Input.Polymorphic Reflex.Bulmex.Load Reflex.Bulmex.Modal
src/Reflex/Bulmex/Event.hs view
@@ -3,7 +3,24 @@ {-# LANGUAGE RankNTypes #-} -- | Helper functions for dealing with events-module Reflex.Bulmex.Event where+module Reflex.Bulmex.Event+ ( eventJoin+ , switchTup+ -- * Hold+ , holdEvent+ , holdEvent_+ , holdAfter+ -- * Flash+ , flash+ , flash'+ -- * Display+ , evtText+ -- * Gate+ , noNothing+ , gatePrism+ , blockFalse+ )+ where import Control.Applicative (empty) import Control.Lens
src/Reflex/Bulmex/Form.hs view
@@ -10,8 +10,8 @@ ( actionForm , form -- * Spin- , withSpinDyn , spinWidget+ , withSpinDyn , aSpinButtonClass , loadAttr -- * Types@@ -33,13 +33,27 @@ import qualified Reflex.Tags as T import qualified Web.KeyCode as Dom --- | This will make send on enter work for a form.--- Gives a spinstate to a form and hooks up onenter press to it--- use actReqButton for sane defaults+-- | This function mimics an arbitrary html form in control flow.+-- The first argument is the action function with arbitrary input a+-- and a trigger event.+-- This returns another event in a monadic context.+--+-- > action="(a -> Event t () -> m (Event t b))"+--+-- This perfectly aligns with servant-reflex.+-- The produced event is given to the form, the second argument.+-- which is the form body:+--+-- > <form>(Event t b -> Dynamic t FormState -> m (a, Event t FormAction, c))</form>+--+-- and also includes the 'FormState',+-- indicating if we're executing the action or not.+-- The form has to return 3 values, the information for the action function,+-- The 'FormAction' event that controls the form, and the third is a return value for the form. actionForm :: (Dom.DomBuilder t m, MonadHold t m, MonadFix m)- => (a -> Event t () -> m (Event t b))- -> (Event t b -> Dynamic t SpinState -> m (a, Event t FormAction, c))+ => (a -> Event t () -> m (Event t b)) -- ^ Action function+ -> (Event t b -> Dynamic t FormState -> m (a, Event t FormAction, c)) -- ^ form body -> m c actionForm actF monM = form $ \onEnter -> do@@ -59,11 +73,14 @@ (Reflex t, MonadHold t m, MonadFix m) => Event t () -> Event t ()- -> m (Dynamic t SpinState)+ -> m (Dynamic t FormState) spinState start stop =- accumDyn (const id) SpinRest $ leftmost [Spinning <$ start, SpinRest <$ stop]+ accumDyn (const id) FormStateRest $ leftmost [FormStateSpinning <$ start, FormStateRest <$ stop] --- | This looks a lot like `withDebounceEvtReq`, maybe a better abstraction is possible?+-- This looks a lot like `withDebounceEvtReq`, maybe a better abstraction is possible?+-- | A more general use of the spinstate.+-- The first argument is the widget that can indicate when to execute+-- the second function. It will be made aware of the 'FormState'. spinWidget :: ( Dom.DomBuilder t m , PerformEvent t m@@ -72,8 +89,8 @@ , MonadHold t m , MonadFix m )- => (Dynamic t SpinState -> m (Event t ()))- -> (Event t () -> m (Event t b))+ => (Dynamic t FormState -> m (Event t ())) -- ^ Widget body+ -> (Event t () -> m (Event t b)) -- ^ Trigger function -> m (Event t b) spinWidget widgetF eventHandlr = do rec onClick <- widgetF dynamicClass@@ -83,9 +100,9 @@ dynamicClass <- spinState onClick $ setClassOnReq <> setClassafterReq pure onRequest -loadAttr :: SpinState -> AttrMap-loadAttr SpinRest = mempty-loadAttr Spinning = Map.fromList [("class", "is-loading"), ("disabled", "1")]+loadAttr :: FormState -> AttrMap+loadAttr FormStateRest = mempty+loadAttr FormStateSpinning = Map.fromList [("class", "is-loading"), ("disabled", "1")] withSpinDyn -- XXX only used once? maybe remove ::@@ -106,7 +123,7 @@ aSpinButtonClass :: (Dom.DomBuilder t m, PostBuild t m) => Text.Text- -> Dynamic t SpinState+ -> Dynamic t FormState -> m () -> m (Event t ()) aSpinButtonClass clazz spinstate =@@ -115,7 +132,7 @@ -- attrmap -- | A form captures enter presses of child componetns and--- sends it to them+-- sends it to them in an event. form :: (Dom.DomBuilder t m, MonadFix m) => (Event t () -> m a) -> m a form monF = do rec val <-
src/Reflex/Bulmex/Form/FormTypes.hs view
@@ -5,7 +5,7 @@ -- | Types for Form. module Reflex.Bulmex.Form.FormTypes ( FormAction(..)- , SpinState(..)+ , FormState(..) -- * Prisms , _PostDefault , _Loading@@ -31,14 +31,14 @@ _FormRest :: Prism' FormAction () _FormRest = _Ctor @"FormRest" -data SpinState -- TODO rename to load state?- = SpinRest- | Spinning+data FormState+ = FormStateRest+ | FormStateSpinning deriving (Show) -instance Semigroup SpinState where- (<>) SpinRest SpinRest = SpinRest- (<>) _ _ = Spinning+instance Semigroup FormState where+ (<>) FormStateRest FormStateRest = FormStateRest+ (<>) _ _ = FormStateSpinning -instance Monoid SpinState where- mempty = SpinRest+instance Monoid FormState where+ mempty = FormStateRest
src/Reflex/Bulmex/Html.hs view
@@ -137,6 +137,7 @@ traversed . to (Text.pack . show) +-- TODO Move to Load -- | Insert an encodable in the document body, -- in case of the server side rendering we encode it as script tag with jsonval, -- in case of ghcjsdom we read the value from that script tag
src/Reflex/Bulmex/Input/Basic.hs view
@@ -5,13 +5,14 @@ -- | Basic inputs with bulma styling module Reflex.Bulmex.Input.Basic- ( abuttonClass+ ( abutton+ , abuttonClass , abuttonLarge- , txtInput- , abutton- , buttonClassAttr , abuttonDynAttr+ , buttonClassAttr+ -- * Text inputs , txtArea+ , txtInput ) where import Control.Lens@@ -56,6 +57,11 @@ -> m (Event t (), a) abuttonClass = abutton . classAttr +-- | A button around an arbitrary dom element:+--+-- > <button> m a </button>+--+-- It's also styled nicely. abutton :: (PostBuild t m, Dom.DomBuilder t m) => AttrMap -> m a -> m (Event t (), a) abutton = abuttonDynAttr . constDyn
+ src/Reflex/Bulmex/Input/Debounce.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecursiveDo #-}+{-# LANGUAGE TypeFamilies #-}++-- | This makes it easy to create auto-save operations+-- not limited to saves.+module Reflex.Bulmex.Input.Debounce+ ( InputStates(..)+ , defStateAttr+ , withInputDebounceEvt+ , withInput+ )+where++import Control.Monad.Fix+import Control.Monad.IO.Class (MonadIO)+import qualified Data.Text as Text+import Data.Time+import Reflex+import Reflex.Bulmex.Event++-- | Allows widgets to react according with what's happening+data InputStates+ = InputStarted+ | InputBuffered+ | InputProcessed+ | InputAborted+ | InputInitial++-- | do a debounced action, create a widget with the dynamic inputstates+-- that indicates the state of doing the action+-- This allows you to automatically request the server once user is+-- finished with typing for example,+-- while not flooding the server with requests trough debouncing+withInputDebounceEvt+ :: ( PostBuild t m+ , MonadFix m+ , MonadHold t m+ , TriggerEvent t m+ , MonadIO (Performable m)+ , PerformEvent t m+ )+ => NominalDiffTime -- ^ Delay before posting the request+ -> (result -> Bool) -- ^ Was the final requess successfull?+ -> (Dynamic t InputStates -> m (b, Event t inputEvt)) -- ^ Widget body reacting to states+ -> (b -> Event t inputEvt -> m (Event t result)) -- ^ Action function+ -> m (Event t result, b)+withInputDebounceEvt debtime succF stateF =+ withInput (debounce debtime) succF+ $ const+ $ fmap (\(one', two) -> (one', two, one'))+ . stateF++-- | Maps input state to bulmex classes: InputStarted = is-warning for example+defStateAttr :: InputStates -> Text.Text+defStateAttr InputStarted = "is-warning"+defStateAttr InputBuffered = "is-warning" -- is-loading+defStateAttr InputProcessed = "is-success"+defStateAttr InputAborted = "is-danger"+defStateAttr InputInitial = ""++-- | A general debounce widget+-- This looks a lot like 'actionForm', but it's not the same because+-- form allows user code to decide what+withInput+ :: (PostBuild t m, MonadFix m, MonadHold t m)+ => (Event t inputEvt -> m (Event t inputEvt)) -- ^ change input timeline, eg pure for no change+ -> (result -> Bool) -- ^ Was the final requess successfull?+ -> ( Event t result+ -> Dynamic t InputStates+ -> m (actArgs, Event t inputEvt, finalRes)+ ) -- ^ Widget body reacting to states+ -> (actArgs -> Event t inputEvt -> m (Event t result)) -- ^ Action function+ -> m (Event t result, finalRes)+withInput timeF isSuccessF createTypeEvt reqFunc = mdo+ (someData, typeEvtImmediate, result) <- createTypeEvt postFinished areaState+ typeEvtDeb <- timeF typeEvtImmediate+ postFinished <- reqFunc someData typeEvtDeb+ areaState <-+ holdDyn InputInitial+ $ leftmost+ $ [ InputStarted <$ typeEvtImmediate+ , InputBuffered <$ typeEvtDeb+ , InputProcessed <$ (blockFalse $ isSuccessF <$> postFinished)+ , InputAborted <$ (blockFalse $ not . isSuccessF <$> postFinished)+ ]+ pure (postFinished, result)
src/Reflex/Bulmex/Load.hs view
@@ -2,8 +2,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecursiveDo #-} --- | Bulma loading screen w/ help of extension:--- https://wikiki.github.io/elements/pageloader/+-- | Load time manipulation module Reflex.Bulmex.Load ( pageLoader , prerenderLoad@@ -24,10 +23,12 @@ import qualified Reflex.Dom.Widget.Basic as Dom import qualified Reflex.Tags as T +-- | Bulma loading screen with help of extension:+-- https://wikiki.github.io/elements/pageloader/ pageLoader :: DomBuilder t m => Text.Text -> m a -> m a pageLoader = partialDiv "pageloader" --- | show a spinning image while loading JS+-- | show input monad while JS is loading, for example a spinning image prerenderLoad :: (Prerender js t m, DomBuilder t m) => m () -> m () prerenderLoad spinner = void $ prerender (T.divClass "prerender-load" $ spinner) Dom.blank
src/Reflex/Bulmex/Modal.hs view
@@ -5,9 +5,9 @@ -- Puts code behinds bulma's modal styling: https://bulma.io/documentation/components/modal/ module Reflex.Bulmex.Modal ( modal- , OnClose , modal' , modalClose+ , OnClose ) where import Control.Lens@@ -18,34 +18,36 @@ import qualified Reflex.Dom.Widget.Basic as Dom import qualified Reflex.Tags as T -data OnClose =- OnClose -- | A modal that opens on event and has a cross to close it.+-- m a dictates what's inside the modal.+-- You probably want to use a 'Reflex.Bulmex.Tag.Bulma.box'. modal :: (PostBuild t m, MonadHold t m, MonadFix m, Dom.DomBuilder t m)- => Event t ()- -> m a+ => Event t () -- ^ open trigger+ -> m a -- ^ modal body -> m (a, Event t OnClose) modal = modal' never -- | A modal that can be opened and closed with events.--- Also has a cross to close it.+-- It also has a cross to close with. modal' :: (PostBuild t m, MonadHold t m, MonadFix m, Dom.DomBuilder t m)- => Event t ()- -> Event t ()- -> m a+ => Event t () -- ^ close trigger+ -> Event t () -- ^ open trigger+ -> m a -- ^ modal body -> m (a, Event t OnClose) modal' closeEvt openEvt monad = modalClose openEvt $ do res <- monad pure (res, closeEvt) +-- | The most generic modal, It receives an open event.+-- And the inner monad can indicate when to close with a closeEvent. modalClose :: (PostBuild t m, MonadHold t m, MonadFix m, Dom.DomBuilder t m)- => Event t ()- -> m (a, Event t ())+ => Event t () -- ^ open trigger+ -> m (a, Event t ()) -- ^ body + close trigger result -> m (a, Event t OnClose) modalClose openEvt monad = do rec stateDyn <-@@ -68,3 +70,7 @@ where closed = classAttr "modal" opened = classAttr "modal is-active"++-- | An incation that an event came from closing a modal+data OnClose =+ OnClose
src/Reflex/Bulmex/Tag/Hide.hs view
@@ -13,6 +13,7 @@ import qualified Reflex.Dom.Builder.Class as Dom import qualified Reflex.Tags as T +-- | When the dynamic is true the first monad is shown, otherwise the second. switchDiv :: (PostBuild t m, Dom.DomBuilder t m) => Dynamic t Bool -> m () -> m a -> m a switchDiv attrDyn true false = do