ihp-1.4.0: IHP/View/Types.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE InstanceSigs, UndecidableInstances, AllowAmbiguousTypes, ScopedTypeVariables, IncoherentInstances #-}
module IHP.View.Types
( FormField (..)
, SubmitButton (..)
, FormContext (..)
, InputType (..)
, CSSFramework (..)
, BreadcrumbsView(..)
, PaginationView(..)
, HtmlWithContext
, Layout
)
where
import IHP.Prelude hiding (div)
import qualified Text.Blaze.Html5 as Blaze
import IHP.FlashMessages.Types
import IHP.ModelSupport (Violation)
import IHP.Breadcrumb.Types
import IHP.Pagination.Types
type HtmlWithContext context = (?context :: context) => Blaze.Html
-- | A layout is just a function taking a view and returning a new view.
--
-- __Example:__ A very basic html layout.
--
-- > myLayout :: Layout
-- > myLayout view = [hsx|
-- > <html>
-- > <body>
-- > {view}
-- > </body>
-- > </html>
-- > |]
type Layout = Blaze.Html -> Blaze.Html
data FormField = FormField
{ fieldType :: !InputType
, fieldName :: !Text
, fieldLabel :: !Text
, fieldValue :: !Text
, fieldInputId :: !Text
, validatorResult :: !(Maybe Violation)
, additionalAttributes :: [(Text, Text)]
, fieldClass :: !Text
, labelClass :: !Text
, disabled :: !Bool
, disableLabel :: !Bool
, disableGroup :: !Bool
, disableValidationResult :: !Bool
, cssFramework :: CSSFramework
, helpText :: !Text
, placeholder :: !Text
, required :: Bool
, autofocus :: Bool
}
data SubmitButton = SubmitButton
{ label :: Blaze.Html
, buttonClass :: Text
, buttonDisabled :: Bool
, cssFramework :: CSSFramework
}
data FormContext model = FormContext
{ model :: model -- ^ The record this form is based on
, formAction :: !Text -- ^ Url where the form is submitted to
, formMethod :: !Text -- ^ Usually "POST", sometimes set to "GET"
, cssFramework :: !CSSFramework
, formClass :: !Text -- ^ In the generated HTML, the @class@ attribute will be set to this value
, formId :: !Text -- ^ In the generated HTML, the @id@ attribute will be set to this value
, disableJavascriptSubmission :: !Bool -- ^ When set to True, the IHP helpers.js will not submit the form using ajax
, customFormAttributes :: ![(Text, Text)] -- ^ Attach custom HTML attributes here
, fieldNamePrefix :: !Text -- ^ Used by nested forms to preprend the nested field name to the field name
}
instance SetField "model" (FormContext record) record where setField value record = record { model = value }
instance SetField "formAction" (FormContext record) Text where setField value record = record { formAction = value }
instance SetField "formMethod" (FormContext record) Text where setField value record = record { formMethod = value }
instance SetField "cssFramework" (FormContext record) CSSFramework where setField value record = record { cssFramework = value }
instance SetField "formClass" (FormContext record) Text where setField value record = record { formClass = value }
instance SetField "formId" (FormContext record) Text where setField value record = record { formId = value }
instance SetField "disableJavascriptSubmission" (FormContext record) Bool where setField value record = record { disableJavascriptSubmission = value }
instance SetField "customFormAttributes" (FormContext record) [(Text, Text)] where setField value record = record { customFormAttributes = value }
data InputType
= TextInput
| NumberInput
| UrlInput
| CheckboxInput
| ColorInput
| EmailInput
| HiddenInput
| TextareaInput
| DateInput
| DateTimeInput
| PasswordInput
| SelectInput { options :: ![(Text, Text)] }
| RadioInput { options :: ![(Text, Text)] }
| FileInput
data BreadcrumbsView = BreadcrumbsView { breadcrumbItems :: !Blaze.Html }
data PaginationView =
PaginationView
{ cssFramework :: !CSSFramework
, pagination :: !Pagination
-- Function used to get the page URL.
, pageUrl :: Int -> ByteString
-- Previous page link.
, linkPrevious :: !Blaze.Html
-- Next page link.
, linkNext :: !Blaze.Html
-- The page and dot dot as rendered by `styledPaginationPageLink` and `styledPaginationDotDot`.
, pageDotDotItems :: !Blaze.Html
-- Selector changing the number of allowed items per page.
, itemsPerPageSelector :: !Blaze.Html
}
-- | Render functions to render with Bootstrap, Tailwind CSS etc.
-- We call this functions with the cssFramework passed to have late binding (like from OOP languages).
-- Here's an explanation breaking it down, step by step
--
-- > renderedHtml = styledPagination theCSSFramework theCSSFramework paginationView
--
-- Can also be written using get:
--
-- > renderedHtml = (theCSSFramework.styledPagination) theCSSFramework paginationView
--
-- That's important to understand here. We get a 'styledPagination' function using 'theCSSFramework.styledPagination'.
-- Next, we apply 'theCSSFramework' to that function. We do that so because the 'styledPagination' function internally
-- might want to call other functions of the CSSFramework type. But we might want to override some functions of the CSSFramework.
--
-- Here's an example of how it would look if we don't pass this a second time, and it's shortcomings.
-- Let's assume 'styledPagination' is calling a 'styledButton':
--
-- > data CSSFramework = CSSFramework { styledPagination :: Html, styledButton :: Html }
-- >
-- > bootstrapCSS = CSSFramework { styledPagination, styledButton }
-- > where
-- > styledPagination = [hsx|<div>{styledButton}</div>|]
-- > styledButton = [hsx|<button style="color: red">button</button>|]]
-- >
-- > myPage = [hsx|{styledPagination bootstrapCSS}|]
--
-- So far all seems fine. But now let's say we would like to override the button styling, and change the button to green instead of red:
--
-- > customCSS = bootstrapCSS { styledButton = [hsx|<button style="color: green">button</button>|]] }
-- >
-- > myPage = [hsx|{styledPagination customCSS}|]
--
-- Now, when we render the 'myPage' we will get '<div><button style="color: red">button</button></div>' (a red button, while our customCSS specified it should be green).
--
-- Our way to fix this is to "late-bind" the calls, by manually passing around a CSSFramework. Here we added that second 'CSSFramework' to all functions.
-- Notice how 'styledPagination' is getting the correct 'styledButton' by calling 'cssFramework.styledButton':
--
-- > data CSSFramework = CSSFramework { styledPagination :: CSSFramework -> Html, styledButton :: CSSFramework -> Html }
-- >
-- > bootstrapCSS = CSSFramework { styledPagination, styledButton }
-- > where
-- > styledPagination cssFramework = [hsx|<div>{cssFramework.styledButton}</div>|]
-- > styledButton cssFramework = [hsx|<button style="color: red">button</button>|]]
-- >
-- > myPage = [hsx|{styledPagination bootstrapCSS bootstrapCSS}|]
--
-- Now, with this second 'CSSFramework' in place we can customize it again:
--
-- > customCSS = bootstrapCSS { styledButton = \cssFramework -> [hsx|<button style="color: green">button</button>|]] }
-- >
-- > myPage = [hsx|{styledPagination customCSS customCSS}|]
data CSSFramework = CSSFramework
{ styledFlashMessage :: CSSFramework -> FlashMessage -> Blaze.Html
, styledFlashMessages :: CSSFramework -> [FlashMessage] -> Blaze.Html
-- | Renders the full form field calling other functions below
, styledFormField :: CSSFramework -> FormField -> Blaze.Html
, styledTextFormField :: CSSFramework -> Text -> FormField -> Blaze.Html -> Blaze.Html
, styledTextareaFormField :: CSSFramework -> FormField -> Blaze.Html -> Blaze.Html
, styledCheckboxFormField :: CSSFramework -> FormField -> Blaze.Html -> Blaze.Html
, styledSelectFormField :: CSSFramework -> FormField -> Blaze.Html -> Blaze.Html
, styledRadioFormField :: CSSFramework -> FormField -> Blaze.Html -> Blaze.Html
, styledFormGroup :: CSSFramework -> Text -> Blaze.Html -> Blaze.Html
-- | The primary form submit button
, styledSubmitButton :: CSSFramework -> SubmitButton -> Blaze.Html
-- | Class for the primary form submit button
, styledSubmitButtonClass :: Text
-- | Renders the help text below an input field. Used with @[hsx|{(textField #firstname) { helpText = "Your first name" } }|]@
, styledFormFieldHelp :: CSSFramework -> FormField -> Blaze.Html
-- | First class attached to @<input/>@ elements, e.g. @<input class="form-control"/>@
, styledInputClass :: CSSFramework -> FormField -> Text
-- | When the form validation failed, invalid inputs will have this class
, styledInputInvalidClass :: CSSFramework -> FormField -> Text
-- | Class applied to the div wrapping the label and input, e.g. @"form-group"@
, styledFormGroupClass :: Text
-- | Elements that containers the validation error message for a invalid form field
, styledValidationResult :: CSSFramework -> FormField -> Blaze.Html
-- | Class name for container of validation error message
, styledValidationResultClass :: Text
-- | Renders a the entire pager, with all its elements.
, styledPagination :: CSSFramework -> PaginationView -> Blaze.Html
-- | The pagination's previous link
, styledPaginationLinkPrevious :: CSSFramework -> Pagination -> ByteString -> Blaze.Html
-- | The pagination's next link
, styledPaginationLinkNext :: CSSFramework -> Pagination -> ByteString -> Blaze.Html
-- | Render the pagination links
, styledPaginationPageLink :: CSSFramework -> Pagination -> ByteString -> Int -> Blaze.Html
-- | Render the dots between pagination numbers (e.g. 5 6 ... 7 8)
, styledPaginationDotDot :: CSSFramework -> Pagination -> Blaze.Html
-- | Render the items per page selector for pagination.
-- Note the (Int -> ByteString), we are passing the pageUrl function, so anyone that would like to override
-- it the selector with different items per page could still use the pageUrl function to get the correct URL.
, styledPaginationItemsPerPageSelector :: CSSFramework -> Pagination -> (Int -> ByteString) -> Blaze.Html
-- | Renders an entire breadcrumbs element.
, styledBreadcrumb :: CSSFramework -> [BreadcrumbItem] -> BreadcrumbsView -> Blaze.Html
-- | Render a single breadcrumb item. We pass the entire list of breadcrumbs, in case an item may change based on that list.
-- The 'Bool' indicates if item is the last one.
, styledBreadcrumbItem :: CSSFramework -> [BreadcrumbItem]-> BreadcrumbItem -> Bool -> Blaze.Html
}