From 9da6407d23772a1096cc40872ab2ca48bf269919 Mon Sep 17 00:00:00 2001 From: Joost Date: Wed, 20 Dec 2023 12:44:19 +0000 Subject: [PATCH 1/3] update "add routing" recipe for v5 --- docs/recipes/ui/add-routing.md | 202 +++++++++++++++++++++++++++++++++ mkdocs.yml | 3 +- 2 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 docs/recipes/ui/add-routing.md diff --git a/docs/recipes/ui/add-routing.md b/docs/recipes/ui/add-routing.md new file mode 100644 index 00000000..f448043a --- /dev/null +++ b/docs/recipes/ui/add-routing.md @@ -0,0 +1,202 @@ +# How do I add routing to a SAFE app with a shared model for all pages? + + +When building larger apps, you probably want different pages to be accessible through different URLs. In this recipe, we show you how to add routes to different pages to an application, including adding a "page not found" page that is displayed when an unknown URL is entered. + +In this recipe we use the simplest approach to storing states for multiple pages, by creating a single state for the full app. A potential benefit of this approach is that the state of a page is not lost when navigating away from it. You will see how that works at the end of the recipe. + +## 1. Adding the Feliz router + +Install Feliz.Router in the client project + +```bash +dotnet paket add Feliz.Router -p Client +``` + + +To include the router in the Client, open `Feliz.Router` at the top of Index.fs + +```fsharp +open Feliz.Router +``` + +## 2. Adding the URL object + +Add the current page to the model of the client, using a new `Page` type + +=== "Code" + ```fsharp + type Page = + | TodoList + | NotFound + + type Model = + { CurrentPage: Page + Todos: Todo list + Input: string } + ``` +=== "Diff" + ```.diff + + type Page = + + | TodoList + + | NotFound + + + - type Model = { Todos: Todo list; Input: string } + + type Model = + + { CurrentPage: Page + + Todos: Todo list + + Input: string } + ``` + +## 3. Parsing URLs + +Create a function to parse a URL to a page, including a wildcard for unmapped pages + +```fsharp +let parseUrl url = + match url with + | ["todo"] -> Page.TodoList + | _ -> Page.NotFound +``` + +## 4. Initialization when using a URL + +On initialization, set the current page + +=== "Code" + ```fsharp + let init () : Model * Cmd = + let page = Router.currentUrl () |> parseUrl + + let model = + { CurrentPage = page + Todos = [] + Input = "" } + ... + model, cmd + ``` +=== "Diff" + ```diff + let init () : Model * Cmd = + + let page = Router.currentUrl () |> parseUrl + + + - let model = { Todos = []; Input = "" } + + let model = + + { CurrentPage = page + + Todos = [] + + Input = "" } + ... + model, cmd + ``` +## 5. Updating the URL + +Add an action to handle navigation. + +To the `Msg` type, add a `PageChanged` case of `Page` + +=== "Code" + ```fsharp + type Msg = + ... + | PageChanged of Page + ``` +=== "Diff" + ```.diff + type Msg = + ... + + | PageChanged of Page + ``` + +Add the `PageChanged` update action + +=== "Code" + ```fsharp + let update (msg: Msg) (model: Model) : Model * Cmd = + match msg with + ... + | PageChanged page -> { model with CurrentPage = page }, Cmd.none + ``` +=== "Diff" + ```.diff + let update (msg: Msg) (model: Model) : Model * Cmd = + match msg with + ... + + | PageChanged page -> { model with CurrentPage = page }, Cmd.none + ``` + +## 6. Displaying the correct content + +Rename the `view` function to `todoView` + +=== "Code" + ```fsharp + let todoView model dispatch = + Html.section [ + ... + ] + ``` +=== "Diff" + ```.diff + - let view model dispatch = + + let todoView model dispatch = + Html.section [ + ... + ] + ``` + +Add a new view function, that returns the appropriate page + +```fsharp +let view model dispatch = + match model.CurrentPage with + | TodoList -> todoView model dispatch + | NotFound -> + Html.div [ + prop.className "flex flex-col items-center justify-center h-full" + prop.text "Page not found" + ] +``` + +!!! info "Adding UI elements to every page of the website" + In this recipe, we moved all the page content to the `todoView`, but you don't have to. You can add UI you want to display on every page of the application to the `view` function. + +## 7. Adding the React router to the view + +Add the `React.Router` element as the outermost element of the view. Dispatch the PageChanged event on `onUrlChanged` + +=== "Code" + ```fsharp + let view (model: Model) (dispatch: Msg -> unit) = + React.router [ + router.onUrlChanged (parseUrl >> PageChanged >> dispatch) + router.children [ + match model.CurrentPage with + ... + ] + ] + ``` +=== "Diff" + ```.diff + let view (model: Model) (dispatch: Msg -> unit) = + + React.router [ + + router.onUrlChanged (parseUrl >> PageChanged >> dispatch) + router.children [ + match model.CurrentPage with + ... + ] + ] + ``` + +## 9. Try it out + +The routing should work now. Try navigating to [localhost:8080](http://localhost:8080/); you should see a page with "Page not Found". If you go to [localhost:8080/#/todo](http://localhost:8080/#/todo), you should see the todo app. + +To see how the state is maintained even when navigating away from the page, type something in the text box and move away from the page by entering another path in the address bar. Then go back to the todo page. The entered text is still there. + +!!! info "# sign" + You might be surprised to see the hash sign as part of the URL. It enables React to react to URL changes without a full page refresh. + There are ways to omit this, but getting this to work properly is outside of the scope of this recipe. + +## 10. Adding more pages + +Now that you have set up the routing, adding more pages is simple: add a new case to the `Page` type; add a route for this page in the `parseUrl` function; add a function that takes a model and dispatcher to generate your new page, and add a new case to the pattern match inside the `view` function to display the new case. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index ea346f7a..fee42abe 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -81,6 +81,7 @@ nav: - Add Feliz support: "recipes/ui/add-feliz.md" - Add FontAwesome support: "recipes/ui/add-fontawesome.md" - Migrate from a CDN stylesheet to an NPM package: "recipes/ui/cdn-to-npm.md" + - Add routing with state shared between pages: "recipes/ui/add-routing.md" - Storage: - Quickly add a database: "recipes/storage/use-litedb.md" - JavaScript: @@ -156,4 +157,4 @@ nav: - Add a NuGet package to the Server: "v4-recipes/package-management/add-nuget-package-to-server.md" - Migrate to Paket from NuGet: "v4-recipes/package-management/migrate-to-paket.md" - Migrate to NuGet from Paket: "v4-recipes/package-management/migrate-to-nuget.md" - - Sync NuGet and NPM Packages: "v4-recipes/package-management/sync-nuget-and-npm-packages.md" + - Sync NuGet and NPM Packages: "v4-recipes/package-management/sync-nuget-and-npm-packages.md" \ No newline at end of file From 451b81f19a8311844ec9a8e41c7ce02a47bf42d1 Mon Sep 17 00:00:00 2001 From: Joost Date: Wed, 20 Dec 2023 12:48:24 +0000 Subject: [PATCH 2/3] remove extra whitespace --- docs/recipes/ui/add-routing.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/recipes/ui/add-routing.md b/docs/recipes/ui/add-routing.md index f448043a..693e91f0 100644 --- a/docs/recipes/ui/add-routing.md +++ b/docs/recipes/ui/add-routing.md @@ -1,6 +1,5 @@ # How do I add routing to a SAFE app with a shared model for all pages? - When building larger apps, you probably want different pages to be accessible through different URLs. In this recipe, we show you how to add routes to different pages to an application, including adding a "page not found" page that is displayed when an unknown URL is entered. In this recipe we use the simplest approach to storing states for multiple pages, by creating a single state for the full app. A potential benefit of this approach is that the state of a page is not lost when navigating away from it. You will see how that works at the end of the recipe. From aa97eaf3f5bdfe891f5ac0a9c4d895ce5fc9bd08 Mon Sep 17 00:00:00 2001 From: Joost Date: Wed, 20 Dec 2023 12:55:47 +0000 Subject: [PATCH 3/3] Fix incorrect path/title in mkdocs.yml --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 64e16f4a..191eaed7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -82,7 +82,7 @@ nav: - Add Feliz support: "recipes/ui/add-feliz.md" - Add FontAwesome support: "recipes/ui/add-fontawesome.md" - Migrate from a CDN stylesheet to an NPM package: "recipes/ui/cdn-to-npm.md" - - Add Routing with UseElmish: "recipes/ui/routing-with-elmish.md" + - Add routing with state shared between pages: "recipes/ui/add-routing.md" - Storage: - Quickly add a database: "recipes/storage/use-litedb.md" - JavaScript: