packages feed

web-rep-0.13.0.0: src/Web/Rep/Html/Input.hs

{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE OverloadedStrings #-}

-- | Common web page input elements, often with bootstrap scaffolding.
module Web.Rep.Html.Input
  ( Input (..),
    InputType (..),
    markupInput,
    ToByteString (..),
  )
where

import Data.Bool
import Data.ByteString (ByteString)
import Data.ByteString.Char8 qualified as C
import Data.Maybe
import Data.Text (Text)
import Data.Text.Encoding
import GHC.Generics
import MarkupParse

-- | Conversion to a 'ByteString'
class ToByteString a where
  -- | Convert a value to a strict ByteString
  toByteString :: a -> ByteString
  default toByteString :: (Show a) => a -> ByteString
  toByteString = strToUtf8 . show

instance ToByteString ByteString where
  toByteString = id

instance ToByteString Text where
  toByteString = encodeUtf8

instance ToByteString Int

instance ToByteString Integer

instance ToByteString Double

instance ToByteString Float

instance ToByteString Bool

instance (ToByteString a) => ToByteString [a] where
  toByteString xs = "[" <> C.intercalate "," (fmap toByteString xs) <> "]"

-- | something that might exist on a web page and be a front-end input to computations.
data Input a = Input
  { -- | underlying value
    inputVal :: a,
    -- | label suggestion
    inputLabel :: Maybe ByteString,
    -- | name//key//id of the Input
    inputId :: ByteString,
    -- | type of html input
    inputType :: InputType
  }
  deriving (Eq, Show, Generic)

-- | Various types of web page inputs, encapsulating practical bootstrap class functionality
data InputType
  = Slider [Attr]
  | SliderV [Attr]
  | TextBox
  | TextBox'
  | TextArea Int
  | ColorPicker
  | ChooseFile
  | Dropdown [ByteString]
  | DropdownMultiple [ByteString] Char
  | DropdownSum [ByteString]
  | Datalist [ByteString] ByteString
  | Checkbox Bool
  | Toggle Bool
  | Button
  deriving (Eq, Show, Generic)

label :: AttrValue -> Maybe ByteString -> Markup
label i l = maybe mempty (elementc "label" [Attr "for" i, Attr "class" "col-sm col-form-label"]) l

-- | Convert an 'Input' to 'Markup' via a specific printer.
markupInput :: (a -> ByteString) -> Input a -> Markup
markupInput pr (Input v l i (Slider satts)) =
  element
    "div"
    [Attr "class" "row"]
    ( label i l
        <> element_
          "input"
          ( [ Attr "type" "range",
              Attr "class" "col-sm jsbClassEventChange",
              Attr "id" i,
              Attr "value" (pr v)
            ]
              <> satts
          )
    )
markupInput pr (Input v l i (SliderV satts)) =
  element
    "div"
    [Attr "class" "row", Attr "style" "align-items: center;"]
    ( label i l
        <> element_
          "input"
          ( [ Attr "type" "range",
              Attr "class" "col-sm jsbClassEventChange",
              Attr "id" i,
              Attr "value" (pr v),
              Attr "oninput" ("$('#sliderv" <> i <> "').html($(this).val())")
            ]
              <> satts
          )
        <> elementc "span" [Attr "id" ("sliderv" <> i), Attr "class" "col-sm"] (pr v)
    )
markupInput pr (Input v l i TextBox) =
  element
    "div"
    [Attr "class" "form-floating"]
    ( element_
        "input"
        [ Attr "type" "text",
          Attr "class" "form-control jsbClassEventInput",
          Attr "id" i,
          Attr "value" (pr v)
        ]
        <> label i l
    )
markupInput pr (Input v l i TextBox') =
  element
    "div"
    [Attr "class" "form-floating"]
    ( element_
        "input"
        [ Attr "type" "text",
          Attr "class" "form-control jsbClassEventFocusout",
          Attr "id" i,
          Attr "value" (pr v)
        ]
        <> label i l
    )
markupInput pr (Input v l i (TextArea rows)) =
  element
    "div"
    [Attr "class" "form-floating"]
    ( elementc
        "textarea"
        [ Attr "rows" (toByteString rows),
          Attr "class" "form-control jsbClassEventInput",
          Attr "id" i
        ]
        (pr v)
        <> label i l
    )
markupInput pr (Input v l i ColorPicker) =
  element
    "div"
    [Attr "class" "row"]
    ( label i l
        <> element_
          "input"
          [ Attr "type" "color",
            Attr "class" "form-control jsbClassEventInput",
            Attr "id" i,
            Attr "value" (pr v)
          ]
    )
markupInput _ (Input _ l i ChooseFile) =
  element
    "div"
    [Attr "class" "row"]
    ( label i l
        <> element_
          "input"
          [ Attr "type" "file",
            Attr "class" "form-control-file jsbClassEventChooseFile",
            Attr "id" i
          ]
    )
markupInput pr (Input v l i (Dropdown opts)) =
  element
    "div"
    [Attr "class" "row"]
    ( label i l
        <> element
          "select"
          [ Attr "class" "form-control jsbClassEventInput",
            Attr "id" i
          ]
          (mconcat opts')
    )
  where
    opts' =
      ( \o ->
          elementc
            "option"
            ( bool
                []
                [Attr "selected" "selected"]
                (o == pr v)
            )
            o
      )
        <$> opts
markupInput pr (Input vs l i (DropdownMultiple opts sep)) =
  element
    "div"
    [Attr "class" "row"]
    ( label i l
        <> element
          "select"
          [ Attr "class" "form-control jsbClassEventChangeMultiple",
            Attr "multiple" "multiple",
            Attr "id" i
          ]
          (mconcat opts')
    )
  where
    opts' =
      ( \o ->
          elementc
            "option"
            ( bool
                []
                [Attr "selected" "selected"]
                (any (\v -> o == strToUtf8 (show v)) (C.split sep (pr vs)))
            )
            o
      )
        <$> opts
markupInput pr (Input v l i (DropdownSum opts)) =
  element
    "div"
    [Attr "class" "row sumtype-group"]
    ( label i l
        <> element
          "select"
          [ Attr "class" "form-control jsbClassEventInput jsbClassEventShowSum",
            Attr "id" i
          ]
          (mconcat opts')
    )
  where
    opts' =
      ( \o ->
          elementc
            "option"
            (bool [] [Attr "selected" "selected"] (o == pr v))
            o
      )
        <$> opts
markupInput pr (Input v l i (Datalist opts listId)) =
  element
    "div"
    [Attr "class" "row"]
    ( label i l
        <> element_
          "input"
          [ Attr "type" "text",
            Attr "class" "form-control jsbClassEventInput",
            Attr "id" i,
            Attr "list" listId
            -- the datalist concept in html assumes initial state is a null
            -- and doesn't present the list if it has a value already
            -- , value_ (show $ toHtml v)
          ]
        <> element
          "datalist"
          [Attr "id" listId]
          ( mconcat
              ( ( \o ->
                    elementc
                      "option"
                      ( bool
                          []
                          [Attr "selected" "selected"]
                          (o == pr v)
                      )
                      o
                )
                  <$> opts
              )
          )
    )
markupInput _ (Input _ l i (Checkbox checked)) =
  element
    "div"
    [Attr "class" "form-check"]
    ( element
        "input"
        ( [ Attr "type" "checkbox",
            Attr "class" "form-check-input jsbClassEventCheckbox",
            Attr "id" i
          ]
            <> bool [] [Attr "checked" ""] checked
        )
        (maybe mempty (elementc "label" [Attr "for" i, Attr "class" "form-check-label"]) l)
    )
markupInput _ (Input _ l i (Toggle pushed)) =
  element
    "div"
    [Attr "class" "form-group-sm"]
    ( maybe mempty (elementc "label" [Attr "for" i, Attr "class" "mb-0"]) l
        <> element_
          "input"
          ( [ Attr "type" "button",
              Attr "class" "btn btn-primary btn-sm jsbClassEventToggle",
              Attr "data-bs-toggle" "button",
              Attr "id" i,
              Attr "aria-pressed" (bool "false" "true" pushed)
            ]
              <> maybe [] (\l' -> [Attr "value" l']) l
              <> bool [] [Attr "checked" ""] pushed
          )
    )
markupInput _ (Input _ l i Button) =
  element
    "div"
    [Attr "class" "form-group-sm"]
    ( element_
        "input"
        [ Attr "type" "button",
          Attr "id" i,
          Attr "class" "btn btn-primary btn-sm jsbClassEventButton",
          Attr "value" (fromMaybe "button" l)
        ]
    )

{-
markupInput _ (Input _ l i (Toggle pushed)) =
    maybe mempty (elementc "label" [Attr "for" i, Attr "class" "btn btn-primary"]) l
        <> element_
          "input"
          ( [ Attr "type" "checkbox",
              Attr "class" "btn-check jsbClassEventToggle",
              Attr "autocomplete" "off",
              Attr "id" i
            ]
              <> bool [] [Attr "checked" ""] pushed
          )
markupInput _ (Input _ l i Button) =
    elementc
        "button"
        [ Attr "type" "button",
          Attr "id" i,
          Attr "class" "btn btn-primary jsbClassEventButton"
        ]
        (fromMaybe "button" l)
-}