United Kingdom: +44 (0)208 088 8978

Adding Fable.Form to a SAFE app

Matt shows how to use Fable.Form in a SAFE app.

We're hiring Software Developers

Click here to find out more

In a previous post, I highlighted Fable.Form, a great-looking collection of libraries for defining and rendering forms for use in your Fable apps.

In this post, I'll show how you can make use of Fable.Form in a SAFE app.

Install dependencies

First off, you need to create a SAFE app, install the relevant dependencies, and wire them up to be available for use in your F# Fable code.

  • Create a new SAFE app and restore local tools:
dotnet new SAFE
dotnet tool restore
  • Install Fable.Form.Simple.Bulma via Paket:
dotnet paket add --project src/Client/ Fable.Form.Simple.Bulma
  • Install bulma and fable-form-simple-bulma npm packages:
npm add fable-form-simple-bulma
npm add bulma@0.9.0
  • Add ./src/Client/styles.scss with the following contents:
@import "~bulma";
@import "~fable-form-simple-bulma";
  • Update ./webpack.config.js to use the new stylesheet, per the SAFE Stack recipe. Add a cssEntry property to CONFIG:
cssEntry: './src/Client/style.scss',
  • Also in ./webpack.config.js, update the entry property of the object returned from module.exports:
entry: isProduction ? {
    app: [resolve(CONFIG.fsharpEntry), resolve(CONFIG.cssEntry)]
} : {
    app: resolve(CONFIG.fsharpEntry),
    style: resolve(CONFIG.cssEntry)
},
  • Remove the Bulma stylesheet link from index.html, which is no longer needed:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.0/css/bulma.min.css">

Replace the existing form with a Fable.Form

With the above preparation done, you can use Fable.Form.Simple.Bulma in your ./src/Client/Index.fs file. For example:

  • Open the namespaces:
open Fable.Form.Simple
open Fable.Form.Simple.Bulma
  • Update the Model:
type Values = { Todo: string }
type Form = Form.View.Model<Values>
type Model = { Todos: Todo list; Form: Form }
  • Update the init function:
let model = { Todos = []; Form = Form.View.idle { Todo = "" } }
  • Replace the SetInput Msg case:
| FormChanged of Form
  • Update the AddTodo Msg case to take string data:
| AddTodo of string
  • Update the update function accordingly:
| FormChanged form -> { model with Form = form }, Cmd.none
| AddTodo todo ->
    let todo = Todo.create todo
    model, Cmd.OfAsync.perform todosApi.addTodo todo AddedTodo
| AddedTodo todo ->
    let newModel =
        { model with
            Todos = model.Todos @ [ todo ]
            Form =
                { model.Form with
                    State = Form.View.Success "Todo added"
                    Values = { model.Form.Values with Todo = "" } } }
    newModel, Cmd.none
  • Bind a Fable.Form value to form:
let form : Form.Form<Values, Msg, _> =
    let todoField =
        Form.textField
            {
                Parser = Ok
                Value = fun values -> values.Todo
                Update = fun newValue values -> { values with Todo = newValue }
                Error = fun _ -> None
                Attributes =
                    {
                        Label = "New todo"
                        Placeholder = "What needs to be done?"
                        HtmlAttributes = []
                    }
            }

    Form.succeed AddTodo
    |> Form.append todoField
  • Replace the Bulma TODO field with a Fable.Form html view:
Form.View.asHtml
    {
        Dispatch = dispatch
        OnChange = FormChanged
        Action = Form.View.Action.SubmitOnly "Add"
        Validation = Form.View.Validation.ValidateOnSubmit
    }
    form
    model.Form

With these changes made, the app works as expected:

Todo app with todo text field filled

Todo app with todo added

Check out our safe-fable-form GitHub repo to see the full source code, including commits showing package installation and code changes.

Adding new functionality

With the basic structure in place, it's easy to add functionality to the form. For example, the changes necessary to add a high priority checkbox are pretty small.

Todo app with high-priority todo details filled

Todo app with high-priority todo added

Wrap up

As we've seen, it's easy to install Fable.Form into a SAFE app. Once that's done you can quickly migrate your existing forms and add new functionality.

I really like the interface provided by Fable.Form.Simple, because it asks for all the details it needs to properly configure forms and nothing more. This means that you can focus on behaviour and let Fable.Form.Simple.Bulma take care of turning that into markup with appropriate CSS classes. If you prefer to use something other than Bulma for styling, I can see value in using Fable.Form.Simple to define the interfaces for your Elmish code to use, and writing your own (ideally open-source!) implementation.

Overall, I'm impressed, and will consider using Fable.Form in future.