ihp-1.5.0: Haskell Web Framework
Copyright(c) digitally induced GmbH 2020
Safe HaskellNone
LanguageGHC2021

IHP.View.Form.FormFor

Description

 
Synopsis

Documentation

formFor :: (?context :: ControllerContext, ?request :: Request, ModelFormAction record, HasField "meta" record MetaBag) => record -> ((?context :: ControllerContext, ?formContext :: FormContext record) => Markup) -> Markup Source #

Forms usually begin with a formFor expression.

This is how a simple form can look like:

renderForm :: Post -> Html
renderForm post = formFor post [hsx|
    {textField #title}
    {textareaField #body}
    {submitButton}
|]

Calling this form from inside your HSX code will lead to the following HTML being generated:

<form method="POST" action="/CreatePost" id="" class="new-form">
    <div class="form-group" id="form-group-post_title">
        <label for="post_title">Title</label>
        <input type="text" name="title" id="post_title" class="form-control" />
    </div>

    <div class="form-group" id="form-group-post_body">
        <label for="post_body">Body</label>
        <textarea name="body" id="post_body" class="form-control"></textarea>
    </div>

    <button class="btn btn-primary">Create Post</button>
</form>

You can see that the form is submitted via POST. The form action has also been set by default to /CreatePost.

All inputs have auto-generated class names and ids for styling. Also, all name attributes are set as expected.

Field Values:

A form control is always filled with the value of the given field when rendering. For example, given a post

let post = Post { ..., title = "Hello World" }

Rendering this, the input value will be set like:

>>> {textField #title}
<input ... value="Hello World" />

Validation:

When rendering a record that has failed validation, the validation error message will be rendered automatically.

Given a post like this:

let post = Post { ..., title = "" }
    |> validateField #title nonEmpty

Rendering {textField #title}, the input will have the css class is-invalid and an element with the error message will be rendered below the input:

<div class="form-group" id="form-group-post_title">
    <label for="post_title">Title</label>
    <input
        type="text"
        name="title"
        placeholder=""
        id="post_title"
        class="form-control is-invalid "
    />
    <div class="invalid-feedback">This field cannot be empty</div>
</div>

formForWithOptions :: (?context :: ControllerContext, ?request :: Request, ModelFormAction record, HasField "meta" record MetaBag) => record -> (FormContext record -> FormContext record) -> ((?context :: ControllerContext, ?formContext :: FormContext record) => Markup) -> Markup Source #

Like formFor but allows changing the underlying FormContext

This is how you can render a form with a id="post-form" id attribute and a custom data-post-id attribute:

renderForm :: Post -> Html
renderForm post = formForWithOptions formOptions post [hsx|
    {textField #title}
    {textareaField #body}
    {submitButton}
|]

formOptions :: FormContext Post -> FormContext Post
formOptions formContext = formContext
    |> set #formId "post-form"
    |> set #customFormAttributes [("data-post-id", show formContext.model.id)]

formForWithoutJavascript :: (?context :: ControllerContext, ?request :: Request, ModelFormAction record, HasField "meta" record MetaBag) => record -> ((?context :: ControllerContext, ?formContext :: FormContext record) => Markup) -> Markup Source #

Like formFor but disables the IHP javascript helpers.

Use it like this:

renderForm :: Post -> Html
renderForm post = formForWithoutJavascript post [hsx|
    {textField #title}
    {textareaField #body}
    {submitButton}
|]

If you want to use this with e.g. a custom form action, remember that formForWithoutJavascript is just a shortcut for formForWithOptions:

renderForm :: Post -> Html
renderForm post = formForWithOptions formOptions post [hsx|
    {textField #title}
    {textareaField #body}
    {submitButton}
|]

formOptions :: FormContext Post -> FormContext Post
formOptions formContext = formContext
    |> set #formAction (pathTo BespokeNewPostAction)
    |> set #disableJavascriptSubmission True

formFor' :: (?context :: ControllerContext, ?request :: Request, HasField "meta" record MetaBag) => record -> Text -> ((?context :: ControllerContext, ?formContext :: FormContext record) => Markup) -> Markup Source #

Allows a custom form action (form submission url) to be set

The URL where the form is going to be submitted to is specified in HTML using the form's action attribute. When using formFor the action attribute is automatically set to the expected path.

E.g. given the below formFor code, the action is set to /CreatePost or /UpdatePost:

renderForm :: Post -> Html
renderForm post = formFor post [hsx|
    {textField #title}
    {textareaField #body}
    {submitButton}
|]

To override the auto-generated action attribute use the 'formFor'' function:

renderForm :: Post -> Html
renderForm post = formFor' post "/my-custom-endpoint" [hsx||]

If you pass an action to that, you need to wrap it with pathTo:

renderForm :: Post -> Html
renderForm post = formFor' post (pathTo CreateDraftAction) [hsx||]

createFormContext :: (?request :: Request, HasField "meta" record MetaBag) => record -> FormContext record Source #

Used by formFor to make a new form context

buildForm :: (?context :: ControllerContext) => FormContext model -> ((?context :: ControllerContext, ?formContext :: FormContext model) => Markup) -> Markup Source #

Used by formFor to render the form

nestedFormFor :: forall (fieldName :: Symbol) childRecord parentRecord idType. (?context :: ControllerContext, ?formContext :: FormContext parentRecord, HasField fieldName parentRecord [childRecord], KnownSymbol fieldName, KnownSymbol (GetModelName childRecord), HasField "id" childRecord idType, InputValue idType, HasField "meta" childRecord MetaBag) => Proxy fieldName -> ((?context :: ControllerContext, ?formContext :: FormContext childRecord) => Markup) -> Markup Source #

submitButton :: (?formContext :: FormContext model, HasField "meta" model MetaBag, KnownSymbol (GetModelName model)) => SubmitButton Source #

Renders a submit button

<button class="btn btn-primary">Create Post</button>

Example:

renderForm :: Post -> Html
renderForm post = formFor post [hsx|
    {submitButton}
|]

This will generate code like this:

<form method="POST" action="/CreatePost" id="" class="new-form">
    <button class="btn btn-primary">Create Post</button>
</form>

Custom Text

renderForm :: Post -> Html
renderForm post = formFor post [hsx|
    {submitButton { label = "Create it!" } }
|]

This will generate code like this:

<form method="POST" action="/CreatePost" id="" class="new-form">
    <button class="btn btn-primary">Create it!</button>
</form>

Custom Class

renderForm :: Post -> Html
renderForm post = formFor post [hsx|
    {submitButton { buttonClass = "create-button" } }
|]

This will generate code like this:

<form method="POST" action="/CreatePost" id="" class="new-form">
    <button class="btn btn-primary create-button">Create Post</button>
</form>

Disabled button

renderForm :: Post -> Html
renderForm post = formFor post [hsx|
    {submitButton { buttonDisabled = True } }
|]

This will generate code like this:

<form method="POST" action="/CreatePost" id="" class="new-form">
    <button class="btn btn-primary create-button" disabled="disabled">Create Post</button>
</form>

class ModelFormAction record where Source #

Returns the form's action attribute for a given record.

Methods

modelFormAction :: record -> Text Source #

Instances

Instances details
(HasField "id" record (Id' (GetTableName record)), HasField "meta" record MetaBag, KnownSymbol (GetModelName record), Show (Id' (GetTableName record))) => ModelFormAction record Source # 
Instance details

Defined in IHP.View.Form.FormFor

Methods

modelFormAction :: record -> Text Source #