123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318 |
- {-# LANGUAGE QuasiQuotes #-}
- {-# LANGUAGE TypeFamilies #-}
- {-# LANGUAGE OverloadedStrings #-}
- -- | Helper functions for creating forms when using <http://getbootstrap.com/ Bootstrap 3>.
- --
- module Yesod.Form.Bootstrap3
- ( -- * Example: Rendering a basic form
- -- $example
- -- * Example: Rendering a horizontal form
- -- $example2
- -- * Rendering forms
- renderBootstrap3
- , BootstrapFormLayout(..)
- , BootstrapGridOptions(..)
- -- * Field settings
- -- $fieldSettings
- , bfs
- , withPlaceholder
- , withAutofocus
- , withLargeInput
- , withSmallInput
- -- * Submit button
- , bootstrapSubmit
- , mbootstrapSubmit
- , BootstrapSubmit(..)
- ) where
- import Control.Arrow (second)
- import Control.Monad (liftM)
- import Data.Text (Text)
- import Data.String (IsString(..))
- import Yesod.Core
- import qualified Data.Text as T
- import Yesod.Form.Types
- import Yesod.Form.Functions
- -- | Create a new 'FieldSettings' with the @form-control@ class that is
- -- required by Bootstrap v3.
- --
- -- Since: yesod-form 1.3.8
- bfs :: RenderMessage site msg => msg -> FieldSettings site
- bfs msg =
- FieldSettings (SomeMessage msg) Nothing Nothing Nothing [("class", "form-control")]
- -- | Add a placeholder attribute to a field. If you need i18n
- -- for the placeholder, currently you\'ll need to do a hack and
- -- use 'getMessageRender' manually.
- --
- -- Since: yesod-form 1.3.8
- withPlaceholder :: Text -> FieldSettings site -> FieldSettings site
- withPlaceholder placeholder fs = fs { fsAttrs = newAttrs }
- where newAttrs = ("placeholder", placeholder) : fsAttrs fs
- -- | Add an autofocus attribute to a field.
- --
- -- Since: yesod-form 1.3.8
- withAutofocus :: FieldSettings site -> FieldSettings site
- withAutofocus fs = fs { fsAttrs = newAttrs }
- where newAttrs = ("autofocus", "autofocus") : fsAttrs fs
- -- | Add the @input-lg@ CSS class to a field.
- --
- -- Since: yesod-form 1.3.8
- withLargeInput :: FieldSettings site -> FieldSettings site
- withLargeInput fs = fs { fsAttrs = newAttrs }
- where newAttrs = addClass "input-lg" (fsAttrs fs)
- -- | Add the @input-sm@ CSS class to a field.
- --
- -- Since: yesod-form 1.3.8
- withSmallInput :: FieldSettings site -> FieldSettings site
- withSmallInput fs = fs { fsAttrs = newAttrs }
- where newAttrs = addClass "input-sm" (fsAttrs fs)
- addClass :: Text -> [(Text, Text)] -> [(Text, Text)]
- addClass klass [] = [("class", klass)]
- addClass klass (("class", old):rest) = ("class", T.concat [old, " ", klass]) : rest
- addClass klass (other :rest) = other : addClass klass rest
- -- | How many bootstrap grid columns should be taken (see
- -- 'BootstrapFormLayout').
- --
- -- Since: yesod-form 1.3.8
- data BootstrapGridOptions =
- ColXs !Int
- | ColSm !Int
- | ColMd !Int
- | ColLg !Int
- deriving (Eq, Ord, Show)
- toColumn :: BootstrapGridOptions -> String
- toColumn (ColXs 0) = ""
- toColumn (ColSm 0) = ""
- toColumn (ColMd 0) = ""
- toColumn (ColLg 0) = ""
- toColumn (ColXs columns) = "col-xs-" ++ show columns
- toColumn (ColSm columns) = "col-sm-" ++ show columns
- toColumn (ColMd columns) = "col-md-" ++ show columns
- toColumn (ColLg columns) = "col-lg-" ++ show columns
- toOffset :: BootstrapGridOptions -> String
- toOffset (ColXs 0) = ""
- toOffset (ColSm 0) = ""
- toOffset (ColMd 0) = ""
- toOffset (ColLg 0) = ""
- toOffset (ColXs columns) = "col-xs-offset-" ++ show columns
- toOffset (ColSm columns) = "col-sm-offset-" ++ show columns
- toOffset (ColMd columns) = "col-md-offset-" ++ show columns
- toOffset (ColLg columns) = "col-lg-offset-" ++ show columns
- addGO :: BootstrapGridOptions -> BootstrapGridOptions -> BootstrapGridOptions
- addGO (ColXs a) (ColXs b) = ColXs (a+b)
- addGO (ColSm a) (ColSm b) = ColSm (a+b)
- addGO (ColMd a) (ColMd b) = ColMd (a+b)
- addGO (ColLg a) (ColLg b) = ColLg (a+b)
- addGO a b | a > b = addGO b a
- addGO (ColXs a) other = addGO (ColSm a) other
- addGO (ColSm a) other = addGO (ColMd a) other
- addGO (ColMd a) other = addGO (ColLg a) other
- addGO (ColLg _) _ = error "Yesod.Form.Bootstrap.addGO: never here"
- -- | The layout used for the bootstrap form.
- --
- -- Since: yesod-form 1.3.8
- data BootstrapFormLayout =
- BootstrapBasicForm -- ^ A form with labels and inputs listed vertically. See <http://getbootstrap.com/css/#forms-example>
- | BootstrapInlineForm -- ^ A form whose @\<inputs>@ are laid out horizontally (displayed as @inline-block@). For this layout, @\<label>@s are still added to the HTML, but are hidden from display. When using this layout, you must add the @form-inline@ class to your form tag. See <http://getbootstrap.com/css/#forms-inline>
- | BootstrapHorizontalForm
- { bflLabelOffset :: !BootstrapGridOptions -- ^ The left <http://getbootstrap.com/css/#grid-offsetting offset> of the @\<label>@.
- , bflLabelSize :: !BootstrapGridOptions -- ^ The number of grid columns the @\<label>@ should use.
- , bflInputOffset :: !BootstrapGridOptions -- ^ The left <http://getbootstrap.com/css/#grid-offsetting offset> of the @\<input>@ from its @\<label>@.
- , bflInputSize :: !BootstrapGridOptions -- ^ The number of grid columns the @\<input>@ should use.
- } -- ^ A form laid out using the Bootstrap grid, with labels in the left column and inputs on the right. When using this layout, you must add the @form-horizontal@ class to your form tag. Bootstrap requires additional markup for the submit button for horizontal forms; you can use 'bootstrapSubmit' in your form or write the markup manually. See <http://getbootstrap.com/css/#forms-horizontal>
- deriving (Show)
- -- | Render the given form using Bootstrap v3 conventions.
- --
- -- Since: yesod-form 1.3.8
- renderBootstrap3 :: Monad m => BootstrapFormLayout -> FormRender m a
- renderBootstrap3 formLayout aform fragment = do
- (res, views') <- aFormToForm aform
- let views = views' []
- has (Just _) = True
- has Nothing = False
- widget = [whamlet|
- $newline never
- #{fragment}
- $forall view <- views
- <div .form-group :fvRequired view:.required :not $ fvRequired view:.optional :has $ fvErrors view:.has-error>
- $case formLayout
- $of BootstrapBasicForm
- $if fvId view /= bootstrapSubmitId
- <label for=#{fvId view}>#{fvLabel view}
- ^{fvInput view}
- ^{helpWidget view}
- $of BootstrapInlineForm
- $if fvId view /= bootstrapSubmitId
- <label .sr-only for=#{fvId view}>#{fvLabel view}
- ^{fvInput view}
- ^{helpWidget view}
- $of BootstrapHorizontalForm labelOffset labelSize inputOffset inputSize
- $if fvId view /= bootstrapSubmitId
- <label .control-label .#{toOffset labelOffset} .#{toColumn labelSize} for=#{fvId view}>#{fvLabel view}
- <div .#{toOffset inputOffset} .#{toColumn inputSize}>
- ^{fvInput view}
- ^{helpWidget view}
- $else
- <div .#{toOffset (addGO inputOffset (addGO labelOffset labelSize))} .#{toColumn inputSize}>
- ^{fvInput view}
- ^{helpWidget view}
- |]
- return (res, widget)
- -- | (Internal) Render a help widget for tooltips and errors.
- helpWidget :: FieldView site -> WidgetT site IO ()
- helpWidget view = [whamlet|
- $maybe tt <- fvTooltip view
- <span .help-block>#{tt}
- $maybe err <- fvErrors view
- <span .help-block .error-block>#{err}
- |]
- -- | How the 'bootstrapSubmit' button should be rendered.
- --
- -- Since: yesod-form 1.3.8
- data BootstrapSubmit msg =
- BootstrapSubmit
- { bsValue :: msg
- -- ^ The text of the submit button.
- , bsClasses :: Text
- -- ^ Classes added to the @\<button>@.
- , bsAttrs :: [(Text, Text)]
- -- ^ Attributes added to the @\<button>@.
- } deriving (Show)
- instance IsString msg => IsString (BootstrapSubmit msg) where
- fromString msg = BootstrapSubmit (fromString msg) " btn-default " []
- -- | A Bootstrap v3 submit button disguised as a field for
- -- convenience. For example, if your form currently is:
- --
- -- > Person <$> areq textField "Name" Nothing
- -- > <*> areq textField "Surname" Nothing
- --
- -- Then just change it to:
- --
- -- > Person <$> areq textField "Name" Nothing
- -- > <*> areq textField "Surname" Nothing
- -- > <* bootstrapSubmit ("Register" :: BootstrapSubmit Text)
- --
- -- (Note that '<*' is not a typo.)
- --
- -- Alternatively, you may also just create the submit button
- -- manually as well in order to have more control over its
- -- layout.
- --
- -- Since: yesod-form 1.3.8
- bootstrapSubmit
- :: (RenderMessage site msg, HandlerSite m ~ site, MonadHandler m)
- => BootstrapSubmit msg -> AForm m ()
- bootstrapSubmit = formToAForm . liftM (second return) . mbootstrapSubmit
- -- | Same as 'bootstrapSubmit' but for monadic forms. This isn't
- -- as useful since you're not going to use 'renderBootstrap3'
- -- anyway.
- --
- -- Since: yesod-form 1.3.8
- mbootstrapSubmit
- :: (RenderMessage site msg, HandlerSite m ~ site, MonadHandler m)
- => BootstrapSubmit msg -> MForm m (FormResult (), FieldView site)
- mbootstrapSubmit (BootstrapSubmit msg classes attrs) =
- let res = FormSuccess ()
- widget = [whamlet|<button class="btn #{classes}" type=submit *{attrs}>_{msg}|]
- fv = FieldView { fvLabel = ""
- , fvTooltip = Nothing
- , fvId = bootstrapSubmitId
- , fvInput = widget
- , fvErrors = Nothing
- , fvRequired = False }
- in return (res, fv)
- -- | A royal hack. Magic id used to identify whether a field
- -- should have no label. A valid HTML4 id which is probably not
- -- going to clash with any other id should someone use
- -- 'bootstrapSubmit' outside 'renderBootstrap3'.
- bootstrapSubmitId :: Text
- bootstrapSubmitId = "b:ootstrap___unique__:::::::::::::::::submit-id"
- -- $example
- -- @\<input\>@ tags in Bootstrap 3 require the @form-control@ class,
- -- and so they need modified 'FieldSettings' to display correctly.
- --
- -- When creating your forms, use the 'bfs' function to add this class:
- --
- -- > personForm :: AForm Handler Person
- -- > personForm = Person
- -- > <$> areq textField (bfs ("Name" :: Text)) Nothing
- -- > <*> areq textField (bfs ("Surname" :: Text)) Nothing
- --
- -- That form can then be rendered into a widget using the 'renderBootstrap3' function. Here, the form is laid out vertically using 'BootstrapBasicForm':
- --
- -- > (formWidget, formEnctype) <- generateFormPost $ renderBootstrap3 BootstrapBasicForm personForm
- --
- -- And then used in Hamlet:
- --
- -- > <form role=form method=post action=@{ActionR} enctype=#{formEnctype}>
- -- > ^{formWidget}
- -- > <button type="submit" .btn .btn-default>Submit
- -- $example2
- -- Yesod.Form.Bootstrap3 also supports <http://getbootstrap.com/css/#forms-horizontal horizontal, grid based forms>.
- -- These forms require additional markup for the submit tag, which is provided by the 'bootstrapSubmit' function:
- --
- -- > personForm :: AForm Handler Person
- -- > personForm = Person
- -- > <$> areq textField MsgName Nothing
- -- > <*> areq textField MsgSurname Nothing
- -- > <* bootstrapSubmit (BootstrapSubmit MsgSubmit "btn-default" [("attribute-name","attribute-value")])
- -- > -- Note: bootstrapSubmit works with all BootstrapFormLayouts, but provides the additional markup required for Bootstrap's horizontal forms.
- --
- -- That form can be rendered with specific grid spacing:
- --
- -- > (formWidget, formEnctype) <- generateFormPost $ renderBootstrap3 (BootstrapHorizontalForm (ColSm 0) (ColSm 4) (ColSm 0) (ColSm 6)) personForm
- --
- -- And then used in Hamlet. Note the additional @form-horizontal@ class on the form, and that a manual submit tag isn't required:
- --
- -- > <form .form-horizontal role=form method=post action=@{ActionR} enctype=#{formEnctype}>
- -- > ^{formWidget}
- -- $fieldSettings
- -- This module comes with several methods to help customize your Bootstrap 3 @\<input\>@s.
- -- These functions can be chained together to apply several properties to an input:
- --
- -- > userForm :: AForm Handler UserForm
- -- > userForm = UserForm
- -- > <$> areq textField nameSettings Nothing
- -- > where nameSettings = withAutofocus $
- -- > withPlaceholder "First name" $
- -- > (bfs ("Name" :: Text))
|