~alcinnz/bureaucromancy

d6349351d838372d9199a690c5997382d13269c7 — Adrian Cochrane 1 year, 28 days ago b48eccb
Validate form before submitting, ensure submit & reset buttons are present!
M app/Main.hs => app/Main.hs +2 -1
@@ 25,7 25,8 @@ main = do
        [n] -> (n, "0")
        [] -> ("form.html", "0")
  doc <- HTML.readFile filename
  runEnv 2018 $ servePage $ fromJust $ parseDocument doc $ Txt.pack ident
  let form = ensureButtons $ fromJust $ parseDocument doc $ Txt.pack ident
  runEnv 2018 $ servePage form

servePage :: Form -> Application
servePage form req respond = do

M src/Text/HTML/Form.hs => src/Text/HTML/Form.hs +40 -1
@@ 1,7 1,7 @@
{-# LANGUAGE OverloadedStrings, TypeSynonymInstances, FlexibleInstances #-}
module Text.HTML.Form (Form(..), Input(..), OptionGroup(..), Option(..),
    FileSelector(..), defaultFileData, ImageData(..), defaultImageData,
    TextArea(..), defaultTextArea, parseElement, parseDocument) where
    TextArea(..), defaultTextArea, parseElement, parseDocument, ensureButtons) where

import Data.Text (Text)
import qualified Data.Text as Txt


@@ 373,3 373,42 @@ text :: Cursor -> Text
text = Txt.concat . (descendant >=> content)
mkEl :: Text -> Node
mkEl = NodeContent

ensureButtons :: Form -> Form
ensureButtons = ensureButton "submit" "Submit" . ensureButton "reset" "Reset"
  where
    ensureButton typ label' form
        | any (\x -> inputType x == typ) $ inputs form = form
        | otherwise = form { inputs = inputs form ++ [button typ label'] }
    button typ label' = Input {
        label = label',
        description = mkEl "",
        autofocus = False,
        disabled = False,
        formAction = Nothing,
        formMethod = Nothing,
        formEnctype = Nothing,
        formValidate = True,
        formTarget = Nothing,
        inputName = "",
        inputType = typ,
        value = "",
        title = "",
        placeholder = "",
        dirname = "",
        inputAutocomplete = "",
        checked = False, -- Switch to true for the activated button!
        readonly = False,
        multiple = False,
        inputMode = "",
        list = [],
        range = (Nothing, Nothing),
        step = Nothing,
        lengthRange = (Nothing, Nothing),
        pattern = Nothing,
        required = False,
        size = Nothing,
        fileData = defaultFileData,
        imageData = defaultImageData,
        textArea = defaultTextArea
      }

M src/Text/HTML/Form/Query.hs => src/Text/HTML/Form/Query.hs +4 -2
@@ 1,6 1,6 @@
{-# LANGUAGE OverloadedStrings #-}
module Text.HTML.Form.Query(
    renderQueryString, renderQueryString', renderQuery', applyQuery) where
module Text.HTML.Form.Query(renderQueryString, renderQueryString', renderQuery',
        applyQuery, applyQuery') where

import Text.HTML.Form (Form(..), Input(..), OptionGroup(..), Option(..))
import Network.URI (escapeURIString, isUnescapedInURIComponent)


@@ 41,3 41,5 @@ applyQuery input@Input { inputName = n } qs
    | inputType input `notElem` ["submit", "reset", "button", "checkbox", "radio"],
        Just val' <- unpack n `lookup` qs = input { value = Txt.pack val' }
    | otherwise = input
applyQuery' :: Form -> [(String, String)] -> Form
applyQuery' form qs = form { inputs = flip applyQuery qs `map` inputs form }

M src/Text/HTML/Form/WebApp.hs => src/Text/HTML/Form/WebApp.hs +10 -2
@@ 2,9 2,11 @@
module Text.HTML.Form.WebApp (renderPage, Form(..)) where

import Data.ByteString as BS
import Data.ByteString.Char8 as B8
import Data.Text as Txt
import Data.Text.Encoding as Txt
import Data.List as L
import Data.Maybe (fromMaybe)
import Text.Read (readMaybe)
import Network.URI (unEscapeString)
import System.IO (readFile')


@@ 14,7 16,8 @@ import System.Directory (XdgDirectory(..), getXdgDirectory, doesFileExist,

import Text.HTML.Form (Form(..), Input(..))
import Text.HTML.Form.WebApp.Ginger (template, template', resolveSource, list')
import Text.HTML.Form.Query (renderQueryString, renderQuery')
import Text.HTML.Form.Query (renderQueryString, renderQuery', applyQuery')
import Text.HTML.Form.Validate (isFormValid')
import Text.HTML.Form.WebApp.Ginger.Hourglass (timeData, modifyTime', timeParseOrNow,
        gSeqTo, gPad2)
import Text.HTML.Form.WebApp.Ginger.TZ (tzdata, continents)


@@ 107,7 110,10 @@ renderInput form ix input@Input {inputType="radio", inputName=k', value=v'} [] q
renderInput form ix input@Input { inputType="<select>" } [] qs =
    template "select.html" form ix input qs
renderInput form ix input@Input { inputType="submit" } [] qs =
    template "submit.html" form ix input qs
    template' "submit.html" form ix input qs $ \x -> case x of
        "isFormValid" -> toGVal $ formValidate input &&
                isFormValid' (applyQuery' form $ strQuery qs)
        _ -> toGVal ()
renderInput _ _ input@Input { inputType="submit" } ["_"] qs =
    return $ Just $ Left $ set (inputName input) (value input) qs
renderInput form ix input@Input { inputType="image" } [] qs =


@@ 231,6 237,8 @@ get k' qs
    | Just (Just ret) <- utf8 k' `lookup` qs =
        Txt.unpack $ Txt.decodeUtf8 ret
    | otherwise = ""
strQuery :: [(ByteString, Maybe ByteString)] -> [(String, String)]
strQuery qs = [(B8.unpack k, B8.unpack $ fromMaybe "" v) | (k, v) <- qs]

partitionM :: Monad f => (a -> f Bool) -> [a] -> f ([a], [a])
partitionM _ [] = pure ([], [])

M tpl/submit.html => tpl/submit.html +4 -0
@@ 3,8 3,12 @@
{%- block main -%}<section>
  <div>{{ input.description }}</div>
  <hr />
  {% if isFormValid %}
  <!-- TODO: Internationalize! -->
  <p><a href="_{{Q}}">Upload</a> to
    <code>{{ input.form.action|default(form.action) }}
        ({{ input.form.method|default(form.method) }})</code></p>
  {% else %}
  <p>Please correct errors listed in sidebar before submitting this form!</p>
  {% endif %}
</section>{%- endblock -%}