From 41acbc78313f23817a371f125e5c552f98142f98 Mon Sep 17 00:00:00 2001 From: Joost Date: Wed, 3 Jan 2024 10:24:09 +0000 Subject: [PATCH 1/6] Direct copy --- docs/recipes/client-server/fable.forms.md | 314 ++++++++++++++++++++++ mkdocs.yml | 2 + 2 files changed, 316 insertions(+) create mode 100644 docs/recipes/client-server/fable.forms.md diff --git a/docs/recipes/client-server/fable.forms.md b/docs/recipes/client-server/fable.forms.md new file mode 100644 index 000000000..156631998 --- /dev/null +++ b/docs/recipes/client-server/fable.forms.md @@ -0,0 +1,314 @@ +## Install dependencies + +First off, you need to create a SAFE app, [install the relevant dependencies](https://mangelmaxime.github.io/Fable.Form/Fable.Form.Simple.Bulma/installation.html), and wire them up to be available for use in your F# Fable code. + +1. Create a new SAFE app and restore local tools: +```sh +dotnet new SAFE +dotnet tool restore +``` + +1. Install Fable.Form.Simple.Bulma using Paket: +```sh +dotnet paket add --project src/Client/ Fable.Form.Simple.Bulma --version 3.0.0 +``` + +1. Install bulma and fable-form-simple-bulma npm packages: +```sh +npm add fable-form-simple-bulma +npm add bulma@0.9.0 +``` + +## Register styles + +1. Create `./src/Client/style.scss` with the following contents: + + === "Code" + ``` { .scss title="style.scss" } + @import "~bulma"; + @import "~fable-form-simple-bulma"; + ``` + + === "Diff" + ``` { .diff title="style.scss" } + +@import "~bulma"; + +@import "~fable-form-simple-bulma"; + ``` + +1. Update webpack config to include the new stylesheet: + + a. Add a `cssEntry` property to the `CONFIG` object: + + === "Code" + ```{ .js title="webpack.config.js" } + cssEntry: './src/Client/style.scss', + ``` + + === "Diff" + ```{ .diff title="webpack.config.js" } + +cssEntry: './src/Client/style.scss', + ``` + + b. Modify the `entry` property of the object returned from `module.exports` to include `cssEntry`: + + === "Code" + ```{ .js title="webpack.config.js" } + entry: isProduction ? { + app: [resolve(config.fsharpEntry), resolve(config.cssEntry)] + } : { + app: resolve(config.fsharpEntry), + style: resolve(config.cssEntry) + }, + ``` + + === "Diff" + ```{ .diff title="webpack.config.js" } + - entry: { + - app: resolve(config.fsharpEntry) + - }, + + entry: isProduction ? { + + app: [resolve(config.fsharpEntry), resolve(config.cssEntry)] + + } : { + + app: resolve(config.fsharpEntry), + + style: resolve(config.cssEntry) + + }, + ``` + +1. Remove the Bulma stylesheet link from `./src/Client/index.html`, as it is no longer needed: + + ``` { .diff title="index.html (diff)" } + + - + + ``` + +## 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. + +1. Open the newly added namespaces: + + === "Code" + ``` { .fsharp title="Index.fs" } + open Fable.Form.Simple + open Fable.Form.Simple.Bulma + ``` + + === "Diff" + ``` { .diff title="Index.fs" } + +open Fable.Form.Simple + +open Fable.Form.Simple.Bulma + ``` + +1. Create type `Values` to represent each input field on the form (a single textbox), and create a type `Form` which is an alias for `Form.View.Model`: + + === "Code" + ``` { .fsharp title="Index.fs" } + type Values = { Todo: string } + type Form = Form.View.Model + ``` + + === "Diff" + ``` { .diff title="Index.fs" } + +type Values = { Todo: string } + +type Form = Form.View.Model + ``` + +1. In the `Model` type definition, replace `Input: string` with `Form: Form` + + === "Code" + ``` { .fsharp title="Index.fs" } + type Model = { Todos: Todo list; Form: Form } + ``` + + === "Diff" + ``` { .diff title="Index.fs" } + -type Model = { Todos: Todo list; Input: string } + +type Model = { Todos: Todo list; Form: Form } + ``` + +1. Update the `init` function to reflect the change in `Model`: + + === "Code" + ``` { .fsharp title="Index.fs" } + let model = { Todos = []; Form = Form.View.idle { Todo = "" } } + ``` + + === "Diff" + ``` { .diff title="Index.fs" } + -let model = { Todos = []; Input = "" } + +let model = { Todos = []; Form = Form.View.idle { Todo = "" } } + ``` + +1. Change `Msg` discriminated union - replace the `SetInput` case with `FormChanged of Form`, and add string data to the `AddTodo` case: + + === "Code" + ``` { .fsharp title="Index.fs" } + type Msg = + | GotTodos of Todo list + | FormChanged of Form + | AddTodo of string + | AddedTodo of Todo + ``` + + === "Diff" + ``` { .diff title="Index.fs" } + type Msg = + | GotTodos of Todo list + - | SetInput of string + - | AddTodo + + | FormChanged of Form + + | AddTodo of string + | AddedTodo of Todo + ``` + +1. Modify the `update` function to handle the changed `Msg` cases: + + === "Code" + ``` { .fsharp title="Index.fs" } + let update (msg: Msg) (model: Model) : Model * Cmd = + match msg with + | GotTodos todos -> { model with Todos = todos }, Cmd.none + | FormChanged form -> { model with Form = form }, Cmd.none + | AddTodo todo -> + let todo = Todo.create todo + let cmd = Cmd.OfAsync.perform todosApi.addTodo todo AddedTodo + model, cmd + | 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 + ``` + + === "Diff" + ``` { .diff title="Index.fs" } + let update (msg: Msg) (model: Model) : Model * Cmd = + match msg with + | GotTodos todos -> { model with Todos = todos }, Cmd.none + - | SetInput value -> { model with Input = value }, Cmd.none + + | FormChanged form -> { model with Form = form }, Cmd.none + - | AddTodo -> + - let todo = Todo.create model.Input + - let cmd = Cmd.OfAsync.perform todosApi.addTodo todo AddedTodo + - { model with Input = "" }, cmd + + | AddTodo todo -> + + let todo = Todo.create todo + + let cmd = Cmd.OfAsync.perform todosApi.addTodo todo AddedTodo + + model, cmd + - | AddedTodo todo -> { model with Todos = model.Todos @ [ todo ] }, Cmd.none + + | 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 + ``` + + +1. Create `form`. This defines the logic of the form, and how it responds to interaction: + + === "Code" + ``` { .fsharp title="Index.fs" } + let form : Form.Form = + 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 + ``` + + === "Diff" + ``` { .diff title="Index.fs" } + +let form : Form.Form = + + 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 + ``` + +1. In the function `containerBox`, remove the existing form view. Then replace it using `Form.View.asHtml` to render the view: + + === "Code" + ``` { .fsharp title="Index.fs" } + let containerBox (model: Model) (dispatch: Msg -> unit) = + Bulma.box [ + Bulma.content [ + Html.ol [ + for todo in model.Todos do + Html.li [ prop.text todo.Description ] + ] + ] + Form.View.asHtml + { + Dispatch = dispatch + OnChange = FormChanged + Action = Form.View.Action.SubmitOnly "Add" + Validation = Form.View.Validation.ValidateOnBlur + } + form + model.Form + ] + ``` + + === "Diff" + ``` { .diff title="Index.fs" } + let containerBox (model: Model) (dispatch: Msg -> unit) = + Bulma.box [ + Bulma.content [ + Html.ol [ + for todo in model.Todos do + Html.li [ prop.text todo.Description ] + ] + ] + - Bulma.field.div [ + - ... removed for brevity ... + - ] + + Form.View.asHtml + + { + + Dispatch = dispatch + + OnChange = FormChanged + + Action = Form.View.Action.SubmitOnly "Add" + + Validation = Form.View.Validation.ValidateOnBlur + + } + + form + + model.Form + ] + ``` + + +## Adding new functionality + +With the basic structure in place, it's easy to add functionality to the form. For example, the [changes](https://github.com/CompositionalIT/safe-fable-form/commit/6342ee8f4abcfeed6dd5066718e6845e6e2174d0) necessary to add a high priority checkbox are pretty small. diff --git a/mkdocs.yml b/mkdocs.yml index 437862b2d..5eb916c81 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -116,6 +116,8 @@ nav: - Get data from the server: "recipes/client-server/messaging.md" - Post data to the server: "recipes/client-server/messaging-post.md" - Share code between the client and the server: "recipes/client-server/share-code.md" + - Add support for Fable.Forms: "recipes/client-server/fable.forms.md" + - FAQs: - Moving from dev to prod: "faq/faq-build.md" - Troubleshooting: "faq/faq-troubleshooting.md" From 8081b626d3f89cd3da57c2fecf0c5df6e85580f4 Mon Sep 17 00:00:00 2001 From: Joost Date: Wed, 3 Jan 2024 13:47:33 +0000 Subject: [PATCH 2/6] make compatible with vite; remove excessive use of diff; fix some code snippets; make coherent with new todoapp structure --- docs/recipes/client-server/fable.forms.md | 228 ++++++++-------------- 1 file changed, 82 insertions(+), 146 deletions(-) diff --git a/docs/recipes/client-server/fable.forms.md b/docs/recipes/client-server/fable.forms.md index 156631998..df413e23a 100644 --- a/docs/recipes/client-server/fable.forms.md +++ b/docs/recipes/client-server/fable.forms.md @@ -7,74 +7,56 @@ First off, you need to create a SAFE app, [install the relevant dependencies](ht dotnet new SAFE dotnet tool restore ``` +1.Add bulma to your project: +follow [this recipe](../ui/add-bulma.md) 1. Install Fable.Form.Simple.Bulma using Paket: ```sh -dotnet paket add --project src/Client/ Fable.Form.Simple.Bulma --version 3.0.0 +dotnet paket add Fable.Form.Simple.Bulma -p Client ``` 1. Install bulma and fable-form-simple-bulma npm packages: ```sh npm add fable-form-simple-bulma -npm add bulma@0.9.0 +npm add bulma ``` ## Register styles -1. Create `./src/Client/style.scss` with the following contents: +1. Rename `src/Client/Index.css` to `Index.scss` - === "Code" - ``` { .scss title="style.scss" } - @import "~bulma"; - @import "~fable-form-simple-bulma"; - ``` - - === "Diff" - ``` { .diff title="style.scss" } - +@import "~bulma"; - +@import "~fable-form-simple-bulma"; - ``` + 2. Update the import in `App.fs` -1. Update webpack config to include the new stylesheet: - - a. Add a `cssEntry` property to the `CONFIG` object: - - === "Code" - ```{ .js title="webpack.config.js" } - cssEntry: './src/Client/style.scss', - ``` - - === "Diff" - ```{ .diff title="webpack.config.js" } - +cssEntry: './src/Client/style.scss', - ``` + === "Code" + ```.fs title="App.fs" + ... + importSideEffects "./index.scss" + ... + ``` + === "Diff" + ```.diff title="App.fs" + ... + - importSideEffects "./index.css" + + importSideEffects "./index.scss" + ... + ``` - b. Modify the `entry` property of the object returned from `module.exports` to include `cssEntry`: +3. Import bulma and fable-form-simple in `Index.scss` === "Code" - ```{ .js title="webpack.config.js" } - entry: isProduction ? { - app: [resolve(config.fsharpEntry), resolve(config.cssEntry)] - } : { - app: resolve(config.fsharpEntry), - style: resolve(config.cssEntry) - }, + ``` .scss title="Index.scss" + @import "~bulma"; + @import "~fable-form-simple-bulma"; + ... ``` - === "Diff" - ```{ .diff title="webpack.config.js" } - - entry: { - - app: resolve(config.fsharpEntry) - - }, - + entry: isProduction ? { - + app: [resolve(config.fsharpEntry), resolve(config.cssEntry)] - + } : { - + app: resolve(config.fsharpEntry), - + style: resolve(config.cssEntry) - + }, + ``` .diff title="Index.scss" + + @import "~bulma"; + + @import "~fable-form-simple-bulma"; + ... ``` -1. Remove the Bulma stylesheet link from `./src/Client/index.html`, as it is no longer needed: +2. Remove the Bulma stylesheet link from `./src/Client/index.html`, as it is no longer needed: ``` { .diff title="index.html (diff)" } @@ -102,17 +84,11 @@ With the above preparation done, you can use Fable.Form.Simple.Bulma in your `./ 1. Create type `Values` to represent each input field on the form (a single textbox), and create a type `Form` which is an alias for `Form.View.Model`: - === "Code" - ``` { .fsharp title="Index.fs" } - type Values = { Todo: string } - type Form = Form.View.Model - ``` - === "Diff" - ``` { .diff title="Index.fs" } - +type Values = { Todo: string } - +type Form = Form.View.Model - ``` + ``` { .fsharp title="Index.fs" } + type Values = { Todo: string } + type Form = Form.View.Model + ``` 1. In the `Model` type definition, replace `Input: string` with `Form: Form` @@ -215,100 +191,60 @@ With the above preparation done, you can use Fable.Form.Simple.Bulma in your `./ 1. Create `form`. This defines the logic of the form, and how it responds to interaction: - === "Code" - ``` { .fsharp title="Index.fs" } - let form : Form.Form = - 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 - ``` - - === "Diff" - ``` { .diff title="Index.fs" } - +let form : Form.Form = - + 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 - ``` + ``` { .fsharp title="Index.fs" } + let form : Form.Form = + 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 + ``` -1. In the function `containerBox`, remove the existing form view. Then replace it using `Form.View.asHtml` to render the view: +1. In the function `todoAction`, remove the existing form view. Then replace it using `Form.View.asHtml` to render the view: === "Code" - ``` { .fsharp title="Index.fs" } - let containerBox (model: Model) (dispatch: Msg -> unit) = - Bulma.box [ - Bulma.content [ - Html.ol [ - for todo in model.Todos do - Html.li [ prop.text todo.Description ] - ] - ] - Form.View.asHtml - { - Dispatch = dispatch - OnChange = FormChanged - Action = Form.View.Action.SubmitOnly "Add" - Validation = Form.View.Validation.ValidateOnBlur - } - form - model.Form - ] - ``` - + ``` { .fsharp title="Index.fs" } + let private todoAction model dispatch = + Form.View.asHtml + { + Dispatch = dispatch + OnChange = FormChanged + Action = Action.SubmitOnly "Add" + Validation = Validation.ValidateOnBlur + } + form + model.Form + ``` === "Diff" - ``` { .diff title="Index.fs" } - let containerBox (model: Model) (dispatch: Msg -> unit) = - Bulma.box [ - Bulma.content [ - Html.ol [ - for todo in model.Todos do - Html.li [ prop.text todo.Description ] - ] - ] - - Bulma.field.div [ - - ... removed for brevity ... - - ] - + Form.View.asHtml - + { - + Dispatch = dispatch - + OnChange = FormChanged - + Action = Form.View.Action.SubmitOnly "Add" - + Validation = Form.View.Validation.ValidateOnBlur - + } - + form - + model.Form - ] - ``` + ``` { .diff title="Index.fs" } + let private todoAction model dispatch = + - Html.div [ + - ... + - ] + + Form.View.asHtml + + { + + Dispatch = dispatch + + OnChange = FormChanged + + Action = Action.SubmitOnly "Add" + + Validation = Validation.ValidateOnBlur + + } + + form + + model.Form + ``` ## Adding new functionality -With the basic structure in place, it's easy to add functionality to the form. For example, the [changes](https://github.com/CompositionalIT/safe-fable-form/commit/6342ee8f4abcfeed6dd5066718e6845e6e2174d0) necessary to add a high priority checkbox are pretty small. +With the basic structure in place, it's easy to add functionality to the form. For example, the [changes](https://github.com/CompositionalIT/safe-fable-form/commit/6342ee8f4abcfeed6dd5066718e6845e6e2174d0) necessary to add a high priority checkbox are pretty small. \ No newline at end of file From 0d6ff4d9632eddfc8993fb3b53b78e74dd9abfa4 Mon Sep 17 00:00:00 2001 From: Joost Date: Wed, 3 Jan 2024 13:51:16 +0000 Subject: [PATCH 3/6] formatting --- docs/recipes/client-server/fable.forms.md | 38 +++++++++++------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/recipes/client-server/fable.forms.md b/docs/recipes/client-server/fable.forms.md index df413e23a..3ae2cb8ac 100644 --- a/docs/recipes/client-server/fable.forms.md +++ b/docs/recipes/client-server/fable.forms.md @@ -7,7 +7,7 @@ First off, you need to create a SAFE app, [install the relevant dependencies](ht dotnet new SAFE dotnet tool restore ``` -1.Add bulma to your project: +1. Add bulma to your project: follow [this recipe](../ui/add-bulma.md) 1. Install Fable.Form.Simple.Bulma using Paket: @@ -25,21 +25,21 @@ npm add bulma 1. Rename `src/Client/Index.css` to `Index.scss` - 2. Update the import in `App.fs` +2. Update the import in `App.fs` - === "Code" - ```.fs title="App.fs" - ... - importSideEffects "./index.scss" - ... - ``` - === "Diff" - ```.diff title="App.fs" - ... - - importSideEffects "./index.css" - + importSideEffects "./index.scss" - ... - ``` + === "Code" + ```.fs title="App.fs" + ... + importSideEffects "./index.scss" + ... + ``` + === "Diff" + ```.diff title="App.fs" + ... + - importSideEffects "./index.css" + + importSideEffects "./index.scss" + ... + ``` 3. Import bulma and fable-form-simple in `Index.scss` @@ -53,12 +53,12 @@ npm add bulma ``` .diff title="Index.scss" + @import "~bulma"; + @import "~fable-form-simple-bulma"; - ... + ... ``` 2. Remove the Bulma stylesheet link from `./src/Client/index.html`, as it is no longer needed: - ``` { .diff title="index.html (diff)" } + ``` { .diff title="index.html" } - @@ -78,8 +78,8 @@ With the above preparation done, you can use Fable.Form.Simple.Bulma in your `./ === "Diff" ``` { .diff title="Index.fs" } - +open Fable.Form.Simple - +open Fable.Form.Simple.Bulma + + open Fable.Form.Simple + + open Fable.Form.Simple.Bulma ``` 1. Create type `Values` to represent each input field on the form (a single textbox), and create a type `Form` which is an alias for `Form.View.Model`: From 978566cdd7e54eeff40b324124472ab105e4658c Mon Sep 17 00:00:00 2001 From: Joost Date: Wed, 3 Jan 2024 16:22:16 +0000 Subject: [PATCH 4/6] Remove some extra diffs --- docs/recipes/client-server/fable.forms.md | 31 +++++++---------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/docs/recipes/client-server/fable.forms.md b/docs/recipes/client-server/fable.forms.md index 3ae2cb8ac..b99fdd59c 100644 --- a/docs/recipes/client-server/fable.forms.md +++ b/docs/recipes/client-server/fable.forms.md @@ -43,18 +43,11 @@ npm add bulma 3. Import bulma and fable-form-simple in `Index.scss` - === "Code" - ``` .scss title="Index.scss" - @import "~bulma"; - @import "~fable-form-simple-bulma"; - ... - ``` - === "Diff" - ``` .diff title="Index.scss" - + @import "~bulma"; - + @import "~fable-form-simple-bulma"; - ... - ``` + ``` .scss title="Index.scss" + @import "~bulma"; + @import "~fable-form-simple-bulma"; + ... + ``` 2. Remove the Bulma stylesheet link from `./src/Client/index.html`, as it is no longer needed: @@ -70,17 +63,11 @@ With the above preparation done, you can use Fable.Form.Simple.Bulma in your `./ 1. Open the newly added namespaces: - === "Code" - ``` { .fsharp title="Index.fs" } - open Fable.Form.Simple - open Fable.Form.Simple.Bulma - ``` + ``` { .fsharp title="Index.fs" } + open Fable.Form.Simple + open Fable.Form.Simple.Bulma + ``` - === "Diff" - ``` { .diff title="Index.fs" } - + open Fable.Form.Simple - + open Fable.Form.Simple.Bulma - ``` 1. Create type `Values` to represent each input field on the form (a single textbox), and create a type `Form` which is an alias for `Form.View.Model`: From 93aaa2b2840dd41374b6e1949f016371dc278c79 Mon Sep 17 00:00:00 2001 From: Joost Date: Wed, 3 Jan 2024 16:24:55 +0000 Subject: [PATCH 5/6] fix .fs instead of .fsharp --- docs/recipes/client-server/fable.forms.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/recipes/client-server/fable.forms.md b/docs/recipes/client-server/fable.forms.md index b99fdd59c..af7bbe88c 100644 --- a/docs/recipes/client-server/fable.forms.md +++ b/docs/recipes/client-server/fable.forms.md @@ -28,7 +28,7 @@ npm add bulma 2. Update the import in `App.fs` === "Code" - ```.fs title="App.fs" + ```.fsharp title="App.fs" ... importSideEffects "./index.scss" ... From e4cb38a6168f363240a305212e2bfa3015c1c640 Mon Sep 17 00:00:00 2001 From: Joost Date: Wed, 3 Jan 2024 16:29:37 +0000 Subject: [PATCH 6/6] fix incorrect imports --- docs/recipes/client-server/fable.forms.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/recipes/client-server/fable.forms.md b/docs/recipes/client-server/fable.forms.md index af7bbe88c..0fa696815 100644 --- a/docs/recipes/client-server/fable.forms.md +++ b/docs/recipes/client-server/fable.forms.md @@ -44,8 +44,8 @@ npm add bulma 3. Import bulma and fable-form-simple in `Index.scss` ``` .scss title="Index.scss" - @import "~bulma"; - @import "~fable-form-simple-bulma"; + @import "bulma/bulma.sass"; + @import "fable-form-simple-bulma/index.scss"; ... ```