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 toCONFIG
:
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 takestring
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:
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.
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.