hyperbole-0.7.1: src/Web/Hyperbole/HyperView/Input.hs
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE UndecidableInstances #-}
module Web.Hyperbole.HyperView.Input where
import Data.Aeson (FromJSON, ToJSON)
import Data.String.Conversions (cs)
import Data.Text (Text)
import GHC.Generics (Generic)
import Web.Atomic.Types
import Web.Hyperbole.Data.Argument (decodeArgument, encodeArgument)
import Web.Hyperbole.HyperView.Event (DelayMs, onChange, onClick, onInput)
import Web.Hyperbole.HyperView.Types (HyperView (..))
import Web.Hyperbole.Route (Route (..), routeUri)
import Web.Hyperbole.View
{- ! \<button\> HTML tag which sends the action when pressed
@
#EMBED Example.Simple messageView
@
-}
{- | \<button\> HTML tag which sends the action when pressed
@
messageView :: Text -> 'View' Message ()
messageView msg = do
'button' (Louder msg) ~ border 1 $ text msg
@
-}
button :: (ViewAction (Action id)) => Action id -> View id () -> View id ()
button action cnt = do
tag "button" cnt @ onClick action
{- ! Type-safe dropdown. Sends the action when selected. The default will be selected on load.
@
#EMBED Example.DataLists.Filter familyDropdown
@
-}
{- | Type-safe dropdown. Sends the action when selected. The default will be selected on load.
@
familyDropdown :: Filters -> 'View' Languages ()
familyDropdown filters =
dropdown SetFamily filters.family ~ border 1 . pad 10 $ do
option Nothing \"Any\"
option (Just ObjectOriented) \"Object Oriented\"
option (Just Functional) \"Functional\"
@
-}
dropdown
:: forall opt id
. (ViewAction (Action id), FromJSON opt)
=> Action id
-> opt -- default option
-> View (Option id opt) ()
-> View id ()
dropdown act defOpt options = do
st :: ViewState id <- viewState
i :: id <- viewId
tag "select" @ onChange act $ do
runViewContext (Option i defOpt) st options
-- | An option for a 'dropdown' or 'select'
option
:: forall opt id
. (ViewAction (Action id), Eq opt, ToJSON opt)
=> opt
-> Text
-> View (Option id opt) ()
option opt cnt = do
os :: Option id opt <- viewId
tag "option" @ att "value" (encodeOption opt) @ selected (os.defaultOption == opt) $ text cnt
encodeOption :: (ToJSON opt) => opt -> Text
encodeOption opt =
case encodeArgument opt of
-- For options, an empty string makes much more sense than null for empty values
"null" -> ""
other -> other
-- | sets selected = true if the 'dropdown' predicate returns True
selected :: (Attributable h) => Bool -> Attributes h -> Attributes h
selected b = if b then att "selected" "true" else id
-- | The view context for an 'option'
data Option id opt = Option
{ id :: id
, defaultOption :: opt
}
deriving (Generic)
instance (ToJSON id, ToJSON opt, FromJSON id, FromJSON opt) => ViewId (Option id opt) where
type ViewState (Option id opt) = ViewState id
{- ! A live search field. Set a DelayMs to avoid hitting the server on every keystroke
@
#EMBED Example.Errors viewSearchUsers
@
-}
{- | A live search field. Set a DelayMs to avoid hitting the server on every keystroke
@
viewSearchUsers :: 'View' Users ()
viewSearchUsers = do
'el' \"Search for a user by id\"
search SearchUser 250 ~ border 1 . pad 10 @ placeholder \"2\"
@
-}
search :: (ViewAction (Action id)) => Action id -> DelayMs -> View id ()
search go delay = do
tag "input" none @ onInput go delay
-- | Set checkbox = checked via the client (VDOM doesn't work)
checked :: (Attributable a) => Bool -> Attributes a -> Attributes a
checked c =
att "data-checked" (cs $ show c)
. if c then att "checked" "" else id
{- | A hyperlink to another route
>>> route (User 100) id "View User"
<a href="/user/100">View User</a>
-}
route :: (Route a) => a -> View c () -> View c ()
route r = link (routeUri r)
-- instance {-# OVERLAPPABLE #-} (FromJSON a) => UserInput (Maybe a) where
-- parseInput "" = pure Nothing
-- parseInput t = pure $ A.decode (cs t)
--
--
-- instance {-# OVERLAPS #-} UserInput (Maybe Text) where
-- parseInput = pure . Just
class InputValue a where
parseInputValue :: Text -> Either String a
default parseInputValue :: (FromJSON a) => Text -> Either String a
parseInputValue = decodeArgument
instance InputValue Text where
parseInputValue = pure
instance InputValue Int where
parseInputValue "" = Right 0
parseInputValue t = decodeArgument t
instance InputValue Float where
parseInputValue "" = Right 0
parseInputValue t = decodeArgument t
instance InputValue Double where
parseInputValue "" = Right 0
parseInputValue t = decodeArgument t
instance InputValue Integer where
parseInputValue "" = Right 0
parseInputValue t = decodeArgument t
instance {-# OVERLAPPABLE #-} (InputValue a) => InputValue (Maybe a) where
parseInputValue "null" = pure Nothing
parseInputValue "" = pure Nothing
parseInputValue t = Just <$> parseInputValue t
instance {-# OVERLAPS #-} InputValue (Maybe Text) where
parseInputValue t = pure $ Just t
instance {-# OVERLAPS #-} InputValue (Maybe String) where
parseInputValue t = pure $ Just (cs t)