From e39b5599e3b9c6a0e459762b548e1761e16020b8 Mon Sep 17 00:00:00 2001 From: Jannis Jorre Date: Tue, 5 Oct 2021 10:17:45 +0200 Subject: [PATCH 01/32] actually link typed out localhost links --- Guide/your-first-project.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Guide/your-first-project.markdown b/Guide/your-first-project.markdown index 3d08cd74c..456882fc0 100644 --- a/Guide/your-first-project.markdown +++ b/Guide/your-first-project.markdown @@ -72,9 +72,9 @@ Start the development server by running the following in the `blog` directory: Your application is starting now. The development server will automatically launch the built-in IDE. The server can be stopped by pressing CTRL+C. -By default, your app is available at `http://localhost:8000` and your development tooling is at `http://localhost:8001`. +By default, your app is available at [`http://localhost:8000`](http://localhost:8000) and your development tooling is at [`http://localhost:8001`](http://localhost:8001). -The development server automatically picks other ports when they are already in use by some other server. For example, it would pick `http://localhost:8001` and `http://localhost:8002` if port 8000 is used. +The development server automatically picks other ports when they are already in use by some other server. For example, it would pick [`http://localhost:8001`](http://localhost:8001) and [`http://localhost:8002`](http://localhost:8002) if port 8000 is used. In the background, the built-in development server starts a PostgreSQL database connected to your application. Don't worry about manually setting up the database. It also runs a WebSocket server to power live reloads on file saves inside your app. @@ -83,7 +83,7 @@ The very first time you start this might take a while, and in rare cases may eve ### Hello Haskell World -Open http://localhost:8000 and you will see this: +Open [`http://localhost:8000`](http://localhost:8000) and you will see this: ![It's working screen](images/first-project/its-working.png) From d2c5cd7dca1b89052154d935806ffcc5bea4af45 Mon Sep 17 00:00:00 2001 From: Jannis Jorre Date: Tue, 5 Oct 2021 10:18:14 +0200 Subject: [PATCH 02/32] fix type: Created -> Create (in CRUD) --- Guide/your-first-project.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Guide/your-first-project.markdown b/Guide/your-first-project.markdown index 456882fc0..802db832d 100644 --- a/Guide/your-first-project.markdown +++ b/Guide/your-first-project.markdown @@ -220,7 +220,7 @@ The preview will show you all the files which are going to be created or modifie ![](images/first-project/code_gen_3_posts.png) -After the files have been created as in the preview, your controller is ready to be used. Open your browser at [http://localhost:8000/Posts](http://localhost:8000/Posts) to try out the new controller. The generator did all the initial work we need to get our usual [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) (Created, Read, Update, Delete) actions going. +After the files have been created as in the preview, your controller is ready to be used. Open your browser at [http://localhost:8000/Posts](http://localhost:8000/Posts) to try out the new controller. The generator did all the initial work we need to get our usual [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) (Create, Read, Update, Delete) actions going. Here's how the new `/Posts` page looks like: From ad1296a6cc3214883bcc8b37759d9ca6dd944654 Mon Sep 17 00:00:00 2001 From: Jannis Jorre Date: Tue, 5 Oct 2021 10:47:37 +0200 Subject: [PATCH 03/32] link functions to api-docs in guide (your-first-project.markdown) --- Guide/your-first-project.markdown | 46 +++++++++++++++---------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Guide/your-first-project.markdown b/Guide/your-first-project.markdown index 802db832d..2709f35d9 100644 --- a/Guide/your-first-project.markdown +++ b/Guide/your-first-project.markdown @@ -282,11 +282,11 @@ In the header we just see some imports. Controllers always import a special `Web instance Controller PostsController where ``` -The controller logic is specified by implementing an instance of the `Controller` type-class. +The controller logic is specified by implementing an instance of the [`Controller`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#t:Controller) type-class. #### Index Action -This is where the interesting part begins. As we will see below, the controller implementation is just an `action` function, pattern matching over our `data PostsController` structure we defined in `Web/Types.hs`. +This is where the interesting part begins. As we will see below, the controller implementation is just an [`action`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#v:action) function, pattern matching over our `data PostsController` structure we defined in `Web/Types.hs`. ```haskell action PostsAction = do @@ -304,7 +304,7 @@ This is the index action. It's called when opening `/Posts`. First, it fetches a render NewView { .. } ``` -This is our endpoint for `/NewPost`. It just creates an empty new post and then passes it to the `NewView`. The `newRecord` is giving us an empty `Post` model. It's equivalent to manually writing `Post { id = Default, title = "", body = "" }`. +This is our endpoint for `/NewPost`. It just creates an empty new post and then passes it to the `NewView`. The [`newRecord`](https://ihp.digitallyinduced.com/api-docs/IHP-ModelSupport.html#v:newRecord) is giving us an empty `Post` model. It's equivalent to manually writing `Post { id = Default, title = "", body = "" }`. #### Show Action @@ -314,7 +314,7 @@ This is our endpoint for `/NewPost`. It just creates an empty new post and then render ShowView { .. } ``` -This is our show action at `/ShowPost?postId=postId`. Here we pattern match on the `postId` field of `ShowPostAction` to get the post id of the given request. Then we just call `fetch` on that `postId` which gives us the specific `Post` record. Finally, we just pass that post to the view. +This is our show action at `/ShowPost?postId=postId`. Here we pattern match on the `postId` field of `ShowPostAction` to get the post id of the given request. Then we just call [`fetch`](https://ihp.digitallyinduced.com/api-docs/IHP-Fetch.html#v:fetch) on that `postId` which gives us the specific `Post` record. Finally, we just pass that post to the view. #### Edit Action @@ -343,13 +343,13 @@ Our `/EditPost?postId=postId` action. It's pretty much the same as in the `actio This action deals with update requests for a specific post. As usual, we pattern match on the `postId` and fetch it. -The interesting part is `buildPost`. It is a helper function defined later in the controller: `buildPost = fill @["title", "body"]`. The `fill` call inside `buildPost` reads the `title` and `body` attributes from the browser request and fills them into the `post` record. The `buildPost` is also the place for validation logic. +The interesting part is `buildPost`. It is a helper function defined later in the controller: `buildPost = fill @["title", "body"]`. The [`fill`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Param.html#v:fill) call inside `buildPost` reads the `title` and `body` attributes from the browser request and fills them into the `post` record. The `buildPost` is also the place for validation logic. `ifValid` returns `Either Post Post`. `Left post` means that e.g. the `title` or `body` did not pass validation. `Right post` means that all parameters could be set on `post` without any errors. In the error case (`Left post ->`) we just re-render the `EditView`. The `EditView` then tells the user about validation errors. -In the success case (`Right post ->`) we save the updated post to the database (with `updateRecord`). Then we set a success message and redirect the user back to the edit view. +In the success case (`Right post ->`) we save the updated post to the database (with [`updateRecord`](https://ihp.digitallyinduced.com/api-docs/IHP-ModelSupport.html#v:updateRecord)). Then we set a success message and redirect the user back to the edit view. #### Create Action @@ -368,7 +368,7 @@ In the success case (`Right post ->`) we save the updated post to the database ( Our create action, dealing with `POST /CreatePost` requests. -It's pretty much like the update action. When the validation succeeded, it saves the record to the database using `createRecord`. +It's pretty much like the update action. When the validation succeeded, it saves the record to the database using [`createRecord`](https://ihp.digitallyinduced.com/api-docs/IHP-ModelSupport.html#v:createRecord). #### Delete Action @@ -380,7 +380,7 @@ It's pretty much like the update action. When the validation succeeded, it saves redirectTo PostsAction ``` -The last action is dealing with `DELETE /DeletePost?postId=postId` requests. It's pretty much like the other actions, we just call `deleteRecord` here. +The last action is dealing with `DELETE /DeletePost?postId=postId` requests. It's pretty much like the other actions, we just call [`deleteRecord`](https://ihp.digitallyinduced.com/api-docs/IHP-ModelSupport.html#v:deleteRecord) here. #### Routes @@ -418,7 +418,7 @@ instance View ShowView where |] ``` -We can see that `ShowView` is just a data definition. There is also a `View ShowView` instance. The HTML-like syntax inside the `html` function is `hsx` code. It's similar to React's [JSX](https://reactjs.org/docs/introducing-jsx.html). You can write HTML code as usual there. Everything inside the `[hsx|...|]` block is also type-checked and converted to Haskell code at compile-time. +We can see that `ShowView` is just a data definition. There is also a `View ShowView` instance. The HTML-like syntax inside the [`html`](https://ihp.digitallyinduced.com/api-docs/IHP-ViewSupport.html#v:html) function is [`hsx`](https://ihp.digitallyinduced.com/api-docs/IHP-ViewPrelude.html#v:hsx) code. It's similar to React's [JSX](https://reactjs.org/docs/introducing-jsx.html). You can write HTML code as usual there. Everything inside the [`[hsx|...|]`](https://ihp.digitallyinduced.com/api-docs/IHP-ViewPrelude.html#v:hsx) block is also type-checked and converted to Haskell code at compile-time. Now that we have a rough overview of all the parts belonging to our `Post`, it's time to do some coding ourselves. @@ -548,7 +548,7 @@ In general, the workflow for making database schema changes locally is: Make cha You can open [http://localhost:8000/Posts](http://localhost:8000/Posts) again. The error is gone now. -Now we can order the posts by our new `created_at` field. Open `Web/Controller/Posts.hs` and add `orderByDesc #createdAt` like this inside the `action PostsAction`: +Now we can order the posts by our new `created_at` field. Open `Web/Controller/Posts.hs` and add [`orderByDesc #createdAt`](https://ihp.digitallyinduced.com/api-docs/IHP-QueryBuilder.html#v:orderByDesc) like this inside the `action PostsAction`: ```haskell action PostsAction = do @@ -572,7 +572,7 @@ Let's also show the creation time in the `ShowView` in `Web/View/Posts/Show.hs`.
{get #body post}
``` -Open the view to check that it's working. If everything is fine, you will see something like `5 minutes ago` below the title. The `timeAgo` helper uses a bit of JavaScript to automatically display the given timestamp in the current time zone and in a relative format. In case you want to show the absolute time (like `10.6.2019, 15:58`), just use `dateTime` instead of `timeAgo`. +Open the view to check that it's working. If everything is fine, you will see something like `5 minutes ago` below the title. The [`timeAgo`](https://ihp.digitallyinduced.com/api-docs/IHP-View-TimeAgo.html#v:timeAgo) helper uses a bit of JavaScript to automatically display the given timestamp in the current time zone and in a relative format. In case you want to show the absolute time (like `10.6.2019, 15:58`), just use [`dateTime`](https://ihp.digitallyinduced.com/api-docs/IHP-View-TimeAgo.html#v:dateTime) instead of [`timeAgo`](https://ihp.digitallyinduced.com/api-docs/IHP-View-TimeAgo.html#v:timeAgod). ![Schema Designer created at view](images/first-project/created_at_view.png) @@ -582,7 +582,7 @@ Right now our posts can only be plain text. Let's make it more powerful by addin #### Adding a Markdown Library -To deal with Markdown, instead of implementing a custom Markdown parser, let's just use an existing package. There's the excellent `mmark` package we can use. +To deal with Markdown, instead of implementing a custom Markdown parser, let's just use an existing package. There's the excellent [`mmark`](https://hackage.haskell.org/package/mmark) package we can use. To install this package, open the `default.nix` file and append `mmark` to the `haskellDeps` list. The file will now look like this: @@ -616,7 +616,7 @@ Stop the development server by pressing CTRL+C. Then update the local developmen #### Markdown Rendering -Now that we have `mmark` installed, we need to integrate it into our `ShowView`. First, we need to import it: add the following line to the top of `Web/View/Posts/Show.hs`: +Now that we have [`mmark`](https://hackage.haskell.org/package/mmark) installed, we need to integrate it into our `ShowView`. First, we need to import it: add the following line to the top of `Web/View/Posts/Show.hs`: ```haskell import qualified Text.MMark as MMark @@ -630,13 +630,13 @@ Add the following to the bottom of the show view: renderMarkdown text = text ``` -This function now does nothing except return its input text. Our Markdown package provides two functions, `MMark.parse` and `MMark.render` to deal with the Markdown. Let's first deal with parsing: +This function now does nothing except return its input text. Our Markdown package provides two functions, [`MMark.parse`](https://hackage.haskell.org/package/mmark-0.0.7.3/docs/Text-MMark.html#v:parse) and [`MMark.render`](https://hackage.haskell.org/package/mmark-0.0.7.3/docs/Text-MMark.html#v:render) to deal with the Markdown. Let's first deal with parsing: ```haskell renderMarkdown text = text |> MMark.parse "" ``` -The empty string we pass to `MMark.parse` is usually the file name of the `.markdown` file. As we don't have any Markdown file, we just pass an empty string. +The empty string we pass to [`MMark.parse`](https://hackage.haskell.org/package/mmark-0.0.7.3/docs/Text-MMark.html#v:parse) is usually the file name of the `.markdown` file. As we don't have any Markdown file, we just pass an empty string. Now open the web app and take a look at a blog post. You will see something like this: @@ -644,7 +644,7 @@ Now open the web app and take a look at a blog post. You will see something like Right MMark {..} ``` -This is the parsed representation of the Markdown. Of course, that's not very helpful. We also have to connect it with `MMark.render` to get HTML code for our Markdown. Replace the `renderMarkdown` with the following code: +This is the parsed representation of the Markdown. Of course, that's not very helpful. We also have to connect it with [`MMark.render`](https://hackage.haskell.org/package/mmark-0.0.7.3/docs/Text-MMark.html#v:render) to get HTML code for our Markdown. Replace the `renderMarkdown` with the following code: ```haskell renderMarkdown text = @@ -659,7 +659,7 @@ The `show` view will now show real formatted text, as we would have expected. Let's also quickly update our form. Right now we have a one-line text field there. We can replace it with a text area to support multi-line text. -Open `Web/View/Posts/Edit.hs` and change `{textField #body}` to `{textareaField #body}`. We can also add a short hint that the text area supports Markdown: Replace `{textareaField #body}` with `{(textareaField #body) { helpText = "You can use Markdown here"} }`. +Open `Web/View/Posts/Edit.hs` and change [`{textField #body}`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:textField) to [`{textareaField #body}`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:textareaField). We can also add a short hint that the text area supports Markdown: Replace [`{textareaField #body}`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:textareaField) with [`{(textareaField #body) { helpText = "You can use Markdown here"} }`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Types.html#t:FormField). ```haskell renderForm :: Post -> Html @@ -690,7 +690,7 @@ isMarkdown text = Right _ -> Success ``` -We can use the validator by adding a new `validateField #body isMarkdown` line to the `buildPost` function: +We can use the validator by adding a new [`validateField #body isMarkdown`](https://ihp.digitallyinduced.com/api-docs/IHP-ValidationSupport-ValidateField.html#v:validateField) line to the `buildPost` function: ```haskell buildPost post = post @@ -946,7 +946,7 @@ Inside the `Show.hs` we need to update the type signature to tell our action wha data ShowView = ShowView { post :: Post } ``` -Add an `Include "comments"` like this: +Add an [`Include "comments"`](https://ihp.digitallyinduced.com/api-docs/IHP-MailPrelude.html#t:Include) like this: ```haskell data ShowView = ShowView { post :: Include "comments" Post } @@ -954,20 +954,20 @@ data ShowView = ShowView { post :: Include "comments" Post } This specifies that our view requires a post and should also include its comments. This will trigger a type error to be shown in the browser because our `ShowPostAction` is not passing the comments yet. -To fix this, open `Web/Controller/Posts.hs` and take a look at the `ShowPostAction`. Right now we have a `fetch` call: +To fix this, open `Web/Controller/Posts.hs` and take a look at the `ShowPostAction`. Right now we have a [`fetch`](https://ihp.digitallyinduced.com/api-docs/IHP-Fetch.html#v:fetch) call: ```haskell post <- fetch postId ``` -We need to extend our fetch to also include comments. We can use `fetchRelated` for this: +We need to extend our fetch to also include comments. We can use [`fetchRelated`](https://ihp.digitallyinduced.com/api-docs/IHP-FetchRelated.html#v:fetchRelated) for this: ```haskell post <- fetch postId >>= fetchRelated #comments ``` -The type of `post` has changed from `Post` to `Include "comments" Post`. In general, when you're dealing with has-many relationships, use `Include "relatedRecords"` and `fetchRelated` to specify and fetch data according to your needs. +The type of `post` has changed from `Post` to [`Include "comments" Post`](https://ihp.digitallyinduced.com/api-docs/IHP-MailPrelude.html#t:Include). In general, when you're dealing with has-many relationships, use [`Include "relatedRecords"`](https://ihp.digitallyinduced.com/api-docs/IHP-MailPrelude.html#t:Include) and [`fetchRelated`](https://ihp.digitallyinduced.com/api-docs/IHP-FetchRelated.html#v:fetchRelated) to specify and fetch data according to your needs. The type error is fixed now. When opening the Show View of a post, you will see that the comments are displayed. When you take a look at the [`Logs` in the Dev tools](http://localhost:8001/AppLogs) you can see, that when opening a Post, two SQL queries will be fired: @@ -1032,7 +1032,7 @@ To the following: render ShowView { .. } ``` -The `modify #comments (orderByDesc #createdAt)` basically just does a `|> orderByDesc #createdAt` to the query builder inside the `#comments` field. Then it just writes it back to the field. The `fetchRelated #comments` will then use the query builder stored inside `#comments` to fetch the comments, thus using the `ORDER BY` we added to the query. +The [`modify #comments (orderByDesc #createdAt)`](https://ihp.digitallyinduced.com/api-docs/IHP-HaskellSupport.html#v:modify) basically just does a [`|> orderByDesc #createdAt`](https://ihp.digitallyinduced.com/api-docs/IHP-QueryBuilder.html#v:orderByDesc) to the query builder inside the `#comments` field. Then it just writes it back to the field. The [`fetchRelated #comments`](https://ihp.digitallyinduced.com/api-docs/IHP-FetchRelated.html#v:fetchRelated) will then use the query builder stored inside `#comments` to fetch the comments, thus using the `ORDER BY` we added to the query. That's it already. Taking a look at our post, we can see that the newest comment is shown first now. From 0f666c8c198423e96bd43e80dbafbd68a17ccce1 Mon Sep 17 00:00:00 2001 From: Jannis Jorre Date: Tue, 5 Oct 2021 10:49:54 +0200 Subject: [PATCH 04/32] add clarifying comma --- Guide/routing.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Guide/routing.markdown b/Guide/routing.markdown index bd1b755e9..e0692eec4 100644 --- a/Guide/routing.markdown +++ b/Guide/routing.markdown @@ -6,7 +6,7 @@ ## Routing Basics -In your project routes are defined in the `Web/Routes.hs`. In addition to defining that route, it also has to be added in `Web/FrontController.hs` to be picked up by the routing system. +In your project, routes are defined in the `Web/Routes.hs`. In addition to defining that route, it also has to be added in `Web/FrontController.hs` to be picked up by the routing system. The simplest way to define a route is by using `AutoRoute`, which automatically maps each controller action to an URL. For a `PostsController`, the definition in `Web/Routes.hs` will look like this: From 009a2b5c01600613bfa9248fab4bb5eacc872b23 Mon Sep 17 00:00:00 2001 From: Jannis Jorre Date: Tue, 5 Oct 2021 10:55:45 +0200 Subject: [PATCH 05/32] link functions to api-docs in guide (routing.markdown) --- Guide/routing.markdown | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Guide/routing.markdown b/Guide/routing.markdown index e0692eec4..63b591c4e 100644 --- a/Guide/routing.markdown +++ b/Guide/routing.markdown @@ -8,7 +8,7 @@ In your project, routes are defined in the `Web/Routes.hs`. In addition to defining that route, it also has to be added in `Web/FrontController.hs` to be picked up by the routing system. -The simplest way to define a route is by using `AutoRoute`, which automatically maps each controller action to an URL. For a `PostsController`, the definition in `Web/Routes.hs` will look like this: +The simplest way to define a route is by using [`AutoRoute`](https://ihp.digitallyinduced.com/api-docs/IHP-RouterSupport.html#t:AutoRoute), which automatically maps each controller action to an URL. For a `PostsController`, the definition in `Web/Routes.hs` will look like this: ```haskell instance AutoRoute PostsController @@ -28,7 +28,7 @@ Now you can open e.g. `/Posts` to access the `PostsAction`. ## Changing the Start Page / Home Page -You can define a custom start page action using the `startPage` function like this: +You can define a custom start page action using the [`startPage`](https://ihp.digitallyinduced.com/api-docs/IHP-RouterSupport.html#v:startPage) function like this: ```haskell instance FrontController WebApplication where @@ -38,18 +38,18 @@ instance FrontController WebApplication where ] ``` -In a new IHP project, you usually have a `startPage WelcomeAction` defined. Make sure to remove this line. Otherwise, you will still see the default IHP welcome page. +In a new IHP project, you usually have a [`startPage WelcomeAction`](https://ihp.digitallyinduced.com/api-docs/IHP-RouterSupport.html#v:startPage) defined. Make sure to remove this line. Otherwise, you will still see the default IHP welcome page. ## URL Generation -Use `pathTo` to generate a path to a given action: +Use [`pathTo`](https://ihp.digitallyinduced.com/api-docs/IHP-ViewPrelude.html#v:pathTo) to generate a path to a given action: ```haskell pathTo ShowPostAction { postId = "adddfb12-da34-44ef-a743-797e54ce3786" } -- /ShowPost?postId=adddfb12-da34-44ef-a743-797e54ce3786 ``` -To generate a full URL, use `urlTo`: +To generate a full URL, use [`urlTo`](https://ihp.digitallyinduced.com/api-docs/IHP-ViewPrelude.html#v:urlTo): ```haskell urlTo NewUserAction @@ -138,7 +138,7 @@ pathTo (MyController [1,2,3]) ==> "/Default?listParam=1,2,3" ## For Integer ID types AutoRoute needs some help if your model does not use UUID as the id type and uses an integer based type instead. To get this to work, add the following to the -`AutoRoute` instance declarations for each controller that needs to parse an integer ID type as an argument: +[`AutoRoute`](https://ihp.digitallyinduced.com/api-docs/IHP-RouterSupport.html#t:AutoRoute) instance declarations for each controller that needs to parse an integer ID type as an argument: ```haskell instance AutoRoute TestController where @@ -157,7 +157,7 @@ Show_Action => GET, HEAD otherwise => GET, POST, HEAD ``` -If you need more strong rules, consider using the other routing APIs available or overriding the `allowedMethodsForAction` like this: +If you need more strong rules, consider using the other routing APIs available or overriding the [`allowedMethodsForAction`](https://ihp.digitallyinduced.com/api-docs/IHP-RouterSupport.html#v:allowedMethodsForAction) like this: ```haskell instance AutoRoute HelloWorldController where @@ -172,7 +172,7 @@ This prefixing has special handling for the `Web` module so that all controllers ## Custom Routing -Sometimes you have special needs for your routing. For this case, IHP provides a lower-level routing API on which `AutoRoute` is built. +Sometimes you have special needs for your routing. For this case, IHP provides a lower-level routing API on which [`AutoRoute`](https://ihp.digitallyinduced.com/api-docs/IHP-RouterSupport.html#t:AutoRoute) is built. Let's say we have a controller like this: @@ -180,14 +180,14 @@ Let's say we have a controller like this: data PostsController = ShowAllMyPostsAction ``` -We want requests to `/posts` to map to `ShowAllMyPostsAction`. For that we need to add a `CanRoute` instance: +We want requests to `/posts` to map to `ShowAllMyPostsAction`. For that we need to add a [`CanRoute`](https://ihp.digitallyinduced.com/api-docs/IHP-RouterSupport.html#t:CanRoute) instance: ```haskell instance CanRoute PostsController where parseRoute' = string "/posts" <* endOfInput >> pure ShowAllMyPostsAction ``` -The `parseRoute'` function is a parser that reads an URL and returns an action of type `PostsController`. The router uses [attoparsec](https://hackage.haskell.org/package/attoparsec). See below for examples on how to use this for building beautiful URLs. +The [`parseRoute'`](https://ihp.digitallyinduced.com/api-docs/IHP-RouterSupport.html#v:parseRoute-39-) function is a parser that reads an URL and returns an action of type `PostsController`. The router uses [attoparsec](https://hackage.haskell.org/package/attoparsec). See below for examples on how to use this for building beautiful URLs. Next to the routing itself, we also need to implement the URL generation: @@ -236,7 +236,7 @@ action ShowPostAction { postId, slug } = do This expects the `posts` table to have a field `slug :: Text`. -Now we define our `CanRoute` instance like this: +Now we define our [`CanRoute`](https://ihp.digitallyinduced.com/api-docs/IHP-RouterSupport.html#t:CanRoute) instance like this: ```haskell instance CanRoute PostsController where @@ -247,7 +247,7 @@ instance CanRoute PostsController where postById <|> postBySlug ``` -Additionally we also have to implement the `HasPath` instance: +Additionally we also have to implement the [`HasPath`](https://ihp.digitallyinduced.com/api-docs/IHP-RouterSupport.html#t:HasPath) instance: ```haskell instance HasPath PostsController where From 2b2cbc15d1735e493a08b40a92be8397a1fc78fe Mon Sep 17 00:00:00 2001 From: Jannis Jorre Date: Tue, 5 Oct 2021 11:21:17 +0200 Subject: [PATCH 06/32] link functions to api-docs in guide (controller.markdown) --- Guide/controller.markdown | 70 +++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/Guide/controller.markdown b/Guide/controller.markdown index 1bbc84fef..56b078653 100644 --- a/Guide/controller.markdown +++ b/Guide/controller.markdown @@ -26,9 +26,9 @@ data PostsController deriving (Eq, Show, Data) ``` -This defines a type `PostsController` with a data constructor `ShowPostAction { postId :: !(Id Post) }`. The argument `postId` will later be filled with the `postId` parameter of the request URL. This is done automatically by the IHP router. IHP also requires the controller to have `Eq`, `Show` and `Data` instances. Therefore we derive them here. +This defines a type `PostsController` with a data constructor `ShowPostAction { postId :: !(Id Post) }`. The argument `postId` will later be filled with the `postId` parameter of the request URL. This is done automatically by the IHP router. IHP also requires the controller to have [`Eq`](https://ihp.digitallyinduced.com/api-docs/IHP-Prelude.html#t:Eq), [`Show`](https://ihp.digitallyinduced.com/api-docs/IHP-Prelude.html#t:Show) and [`Data`](https://ihp.digitallyinduced.com/api-docs/IHP-Prelude.html#t:Data) instances. Therefore we derive them here. -After we have defined the "interface" for our controller, we need to implement the actual request handling logic. IHP expects to find this inside the `action` function of the [`Controller`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#t:Controller) instance. We can define this instance in `Web/Controller/Posts.hs`: +After we have defined the "interface" for our controller, we need to implement the actual request handling logic. IHP expects to find this inside the [`action`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#v:action) function of the [`Controller`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#t:Controller) instance. We can define this instance in `Web/Controller/Posts.hs`: ```haskell module Web.Controller.Posts where @@ -39,11 +39,11 @@ instance Controller PostsController where action ShowPostAction { postId } = renderPlain "Hello World" ``` -This implementation for `ShowPostAction` responds with a simple plain text message. The `action` implementation is usually a big pattern match over all possible actions of a controller. +This implementation for `ShowPostAction` responds with a simple plain text message. The [`action`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#v:action) implementation is usually a big pattern match over all possible actions of a controller. ## Reading Query and Body Parameters -Inside the action, you can access request parameters using the `param` function. A parameter can either be a URL parameter like `?paramName=paramValue` (_this is also called a query parameter_), or given as a form field like `
` (_in that case we're talking about a body parameter_). The `param` function will work with query and body parameters, so you don't have to worry about that (in case a query and body parameter is set with the same name, the body parameter will take priority). +Inside the action, you can access request parameters using the [`param`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Param.html#v:param) function. A parameter can either be a URL parameter like `?paramName=paramValue` (_this is also called a query parameter_), or given as a form field like `
` (_in that case we're talking about a body parameter_). The [`param`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Param.html#v:param) function will work with query and body parameters, so you don't have to worry about that (in case a query and body parameter is set with the same name, the body parameter will take priority). Given a request like `GET /UsersAction?maxItems=50`, you can access the `maxItems` like this: @@ -61,7 +61,7 @@ An alternative request to that action can use a form for passing the `maxItems`: ``` -The value is automatically transformed to an `Int`. This parsing works out of the box for Ids, UUID, Bools, Timestamps, etc. Here are some more examples: +The value is automatically transformed to an [`Int`](https://ihp.digitallyinduced.com/api-docs/IHP-MailPrelude.html#t:Int). This parsing works out of the box for [Ids](https://ihp.digitallyinduced.com/api-docs/IHP-Prelude.html#t:Id), [UUID](https://ihp.digitallyinduced.com/api-docs/IHP-MailPrelude.html#t:UUID), [Bools](https://ihp.digitallyinduced.com/api-docs/IHP-MailPrelude.html#t:Bool), [Timestamps](https://ihp.digitallyinduced.com/api-docs/IHP-RouterPrelude.html#t:UTCTime), etc. Here are some more examples: ```haskell action ExampleAction = do @@ -74,7 +74,7 @@ action ExampleAction = do In case there is a problem parsing the request parameter, an error will be triggered. -When the parameter is optional, use `paramOrDefault`: +When the parameter is optional, use [`paramOrDefault`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Param.html#v:paramOrDefault): ```haskell action UsersAction = do @@ -83,7 +83,7 @@ action UsersAction = do When this action is called without the `maxItems` parameter being set (or when invalid), it will fall back to the default value `50`. -There is also `paramOrNothing` which will return `Nothing` when the parameter is missing and `Just theValue` otherwise. +There is also [`paramOrNothing`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Param.html#v:paramOrNothing) which will return [`Nothing`](https://ihp.digitallyinduced.com/api-docs/IHP-Prelude.html#t:Maybe) when the parameter is missing and [`Just theValue`](https://ihp.digitallyinduced.com/api-docs/IHP-Prelude.html#t:Maybe) otherwise. ### Multiple Params With Same Name (Checkboxes) @@ -97,7 +97,7 @@ When working with checkboxes sometimes there are multiple values for a given par Egg ``` -When both checkboxes for Milk and Egg are checked, the usual way of calling `param @Text "ingredients"` will only return the first ingredient `"Milk"`. To access all the checked `ingredients` use `paramList`: +When both checkboxes for Milk and Egg are checked, the usual way of calling [`param @Text "ingredients"`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Param.html#v:param) will only return the first ingredient `"Milk"`. To access all the checked `ingredients` use [`paramList`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Param.html#v:paramList): ```haskell action BuildFood = do @@ -106,7 +106,7 @@ action BuildFood = do When this action is called with both checkboxes checked `ingredients` will be set to `["milk", "egg"]`. When no checkbox is checked it will return an empty list. -Similar to `param` this works out of the box for Ids, UUID, Bools, Timestamps, etc. +Similar to [`param`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Param.html#v:param) this works out of the box for [Ids](https://ihp.digitallyinduced.com/api-docs/IHP-Prelude.html#t:Id), [UUID](https://ihp.digitallyinduced.com/api-docs/IHP-Prelude.html#t:UUID), [Bools](https://ihp.digitallyinduced.com/api-docs/IHP-Prelude.html#t:Bool), [Timestamps](https://ihp.digitallyinduced.com/api-docs/IHP-RouterPrelude.html#t:UTCTime), etc. ### Passing Data from the Action to the View @@ -159,26 +159,26 @@ This will render `Hello World, Unnamed!` when the `ExampleAction` is called with ### Accessing the FrameworkConfig inside Controllers and Views. -The config defined in `Config/Config.hs` is available through the implicit parameter `context`, a `ConfigProvider` that is available in controllers. +The config defined in `Config/Config.hs` is available through the implicit parameter `context`, a [`ConfigProvider`](https://ihp.digitallyinduced.com/api-docs/IHP-FrameworkConfig.html#t:ConfigProvider) that is available in controllers. -There are helpers that use this implicit parameter, e.g. `isDevelopment/isProduction`: +There are helpers that use this implicit parameter, e.g. [`isDevelopment`](https://ihp.digitallyinduced.com/api-docs/IHP-FrameworkConfig.html#v:isDevelopment)/[`isProduction`](https://ihp.digitallyinduced.com/api-docs/IHP-FrameworkConfig.html#v:isProduction): ```haskell action MyAction = do when isDevelopment (putStrLn "Running in dev mode") ``` -or you can use the function `getFrameworkConfig` if you need to access the config yourself. +or you can use the function [`getFrameworkConfig`](https://ihp.digitallyinduced.com/api-docs/IHP-FrameworkConfig.html#v:getFrameworkConfig) if you need to access the config yourself. ### Advanced: Working with Custom Types -Rarely you might want to work with a custom scalar value which is not yet supported with `param`. Define a custom `ParamReader` instance to be able to use the `param` functions with your custom value type. [For that, take a look at the existing instances of `ParamReader`.](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Param.html#t:ParamReader) +Rarely you might want to work with a custom scalar value which is not yet supported with [`param`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Param.html#v:param). Define a custom [`ParamReader`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Param.html#t:ParamReader) instance to be able to use the [`param`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Param.html#v:param) functions with your custom value type. [For that, take a look at the existing instances of `ParamReader`.](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Param.html#t:ParamReader) ### Records -When working with records, use `fill` instead of `param`. Fill automatically deals with validation failure when e.g. a field value needs to be an integer, but the submitted value is not numeric. +When working with records, use [`fill`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Param.html#v:fill) instead of [`param`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Param.html#v:param). [`fill`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Param.html#v:fill) automatically deals with validation failure when e.g. a field value needs to be an integer, but the submitted value is not numeric. -Here is an example of using `fill`: +Here is an example of using [`fill`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Param.html#v:fill): ```haskell action CreatePostAction = do @@ -195,7 +195,7 @@ action CreatePostAction = do ## Lifecycle -The Controller instance provides a `beforeAction` function, which is called before the `action` function is called. Common request handling logic like authentication is often placed inside `beforeAction` to protect all actions of the controller against unprotected access. +The Controller instance provides a [`beforeAction`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#v:beforeAction) function, which is called before the [`action`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#v:action) function is called. Common request handling logic like authentication is often placed inside [`beforeAction`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#v:beforeAction) to protect all actions of the controller against unprotected access. Here is an example to illustrate the lifecycle: @@ -221,13 +221,13 @@ instance Controller PostsController where ## Accessing the Current Action -Inside the `beforeAction` and `action` you can access the current action using the special `?theAction` variable. That is useful when writing controller helpers, because the variable is passed implicitly. +Inside the [`beforeAction`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#v:beforeAction) and [`action`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#v:action) you can access the current action using the special `?theAction` variable. That is useful when writing controller helpers, because the variable is passed implicitly. ## Accessing the Current Request -IHP uses the Haskell WAI standard for dealing with HTTP requests and responses. You can get access to the Wai Request data structure by using `request`: +IHP uses the Haskell WAI standard for dealing with HTTP requests and responses. You can get access to the Wai Request data structure by using [`request`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#v:request): -Take a look at [the Wai documentation](https://hackage.haskell.org/package/wai-3.2.2.1/docs/Network-Wai.html) to see what you can do with the Wai `Request`: +Take a look at [the Wai documentation](https://hackage.haskell.org/package/wai-3.2.2.1/docs/Network-Wai.html) to see what you can do with the Wai [`Request`](https://hackage.haskell.org/package/wai-3.2.2.1/docs/Network-Wai.html#t:Request): ```haskell action ExampleAction = do @@ -253,7 +253,7 @@ action ExampleAction = do ### Request Headers -Use `getHeader` to access a request header: +Use [`getHeader`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#v:getHeader) to access a request header: ```haskell action ExampleAction = do @@ -266,7 +266,7 @@ to `Just "the user agent value"`. The lookup for the header in the request is case insensitive. -Use `setHeader` to set a request header: +Use [`setHeader`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#v:setHeader) to set a request header: ```haskell action ExampleAction = do @@ -277,17 +277,17 @@ action ExampleAction = do ### Rendering Views -Inside a controller, you have several ways of sending a response. The most common way is to use the `render` function with a `View` data structure, like this: +Inside a controller, you have several ways of sending a response. The most common way is to use the [`render`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Render.html#v:render) function with a [`View`](https://ihp.digitallyinduced.com/api-docs/IHP-ViewSupport.html#t:View) data structure, like this: ``` render ShowPostView { .. } ``` -The `render` function automatically picks the right response format based on the `Accept` header of the browser. It will try to send an HTML response when HTML is requested, and will also try to send a JSON response when a JSON response is expected. A [`406 Not Acceptable`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/406) will be send when the `render` function cannot fulfill the requested `Accept` formats. +The [`render`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Render.html#v:render) function automatically picks the right response format based on the `Accept` header of the browser. It will try to send an HTML response when HTML is requested, and will also try to send a JSON response when a JSON response is expected. A [`406 Not Acceptable`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/406) will be send when the [`render`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Render.html#v:render) function cannot fulfill the requested `Accept` formats. ### Rendering Plain Text -Call `renderPlain` to send a simple plain text response: +Call [`renderPlain`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Render.html#v:renderPlain) to send a simple plain text response: ```haskell action ExampleAction = do @@ -298,18 +298,18 @@ action ExampleAction = do Usually, you want to render your HTML using a view. See `Rendering Views` for details. -Sometimes you want to render HTML without using views, e.g. doing it inline in the action. Call `respondHtml` for that: +Sometimes you want to render HTML without using views, e.g. doing it inline in the action. Call [`respondHtml`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Render.html#v:respondHtml) for that: ```haskell action ExampleAction = do respondHtml [hsx|
Hello World
|] ``` -You will need to import `hsx` into your controller: `import IHP.ViewPrelude (hsx)`. +You will need to import [`hsx`](https://ihp.digitallyinduced.com/api-docs/IHP-ViewPrelude.html#v:hsx) into your controller: [`import IHP.ViewPrelude (hsx)`](https://ihp.digitallyinduced.com/api-docs/IHP-ViewPrelude.html#v:hsx). ### Rendering a Static File -Use `renderFile path contentType` to respond with a static file: +Use [`renderFile path contentType`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Render.html#v:renderFile) to respond with a static file: ```haskell action ExampleAction = do @@ -318,7 +318,7 @@ action ExampleAction = do ### Rendering a Not Found Message -Use `renderNotFound` to render a generic not found message, e.g. when an entity cannot be found: +Use [`renderNotFound`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Render.html#v:renderNotFound) to render a generic not found message, e.g. when an entity cannot be found: ```haskell action ExampleAction = do @@ -329,20 +329,20 @@ action ExampleAction = do ### Redirect to an Action -Use `redirectTo` to redirect to an action: +Use [`redirectTo`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Redirect.html#v:redirectTo) to redirect to an action: ```haskell action ExampleAction = do redirectTo ShowPostAction { postId = ... } ``` -When you need to pass a custom query parameter, you cannot use the `redirectTo` function. See `Redirect to a Path` for that. +When you need to pass a custom query parameter, you cannot use the [`redirectTo`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Redirect.html#v:redirectTo) function. See `Redirect to a Path` for that. -The redirect will use HTTP status code `302`. The `baseUrl` in `Config/Config.hs` will be used. In development mode, the `baseUrl` might not be specified in `Config/Config.hs`. Then it will be set to localhost by default. +The redirect will use HTTP status code `302`. The [`baseUrl`](https://ihp.digitallyinduced.com/api-docs/IHP-FrameworkConfig.html#t:FrameworkConfig) in `Config/Config.hs` will be used. In development mode, the [`baseUrl`](https://ihp.digitallyinduced.com/api-docs/IHP-FrameworkConfig.html#t:FrameworkConfig) might not be specified in `Config/Config.hs`. Then it will be set to localhost by default. ### Redirect to a Path -Use `redirectToPath` when you want to redirect to a path on the same domain: +Use [`redirectToPath`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Redirect.html#v:redirectToPath) when you want to redirect to a path on the same domain: ```haskell action ExampleAction = do @@ -358,7 +358,7 @@ action ExampleAction = do ### Redirect to a URL -Use `redirectToUrl` to redirect to some external URL: +Use [`redirectToUrl`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Redirect.html#v:redirectToUrl) to redirect to some external URL: ```haskell action ExampleAction = do @@ -377,7 +377,7 @@ action ExampleAction = do putStrLn "This line here is not reachable" ``` -The `putStrLn` will never be called because the `redirectTo` already stops execution. +The [`putStrLn`](https://ihp.digitallyinduced.com/api-docs/IHP-Prelude.html#v:putStrLn) will never be called because the [`redirectTo`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Redirect.html#v:redirectTo) already stops execution. When you have created a [`Response`](https://hackage.haskell.org/package/wai-3.2.2.1/docs/Network-Wai.html#t:Response) manually, you can use [`respondAndExit`](https://ihp.digitallyinduced.com/api-docs/src/IHP.ControllerSupport.html#respondAndExit) to send your response and stop action execution. @@ -393,7 +393,7 @@ Actions have access to the Request Context via the controller context: let requestContext = get #requestContext ?context ``` -The Request Context provides access to the Wai request as well as information like the request query and post parameters and the uploaded files. It's usually used by other functions to provide high-level functionality. E.g. the `getHeader` function uses the Request Context to access the request headers. +The Request Context provides access to the Wai request as well as information like the request query and post parameters and the uploaded files. It's usually used by other functions to provide high-level functionality. E.g. the [`getHeader`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#v:getHeader) function uses the Request Context to access the request headers. ## File Uploads From 117716c1d8a02fae3b7be07a393580b05da8712b Mon Sep 17 00:00:00 2001 From: Jannis Jorre Date: Tue, 5 Oct 2021 11:35:30 +0200 Subject: [PATCH 07/32] link functions to api-docs in guide (view.markdown) --- Guide/view.markdown | 46 ++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Guide/view.markdown b/Guide/view.markdown index c8fe610f2..1464bc747 100644 --- a/Guide/view.markdown +++ b/Guide/view.markdown @@ -10,9 +10,9 @@ IHP views are usually represented as HTML, but can also be represented as JSON o The HTML templating is implemented on top of the well-known blaze-html Haskell library. To quickly build HTML views, IHP supports a JSX-like syntax called HSX. HSX is type-checked and compiled to Haskell code at compile-time. -The controller provides the view with a key-value map called `ControllerContext`. The `ControllerContext` provides the view information it might need to render, without always explicitly passing it. This is usually used to pass e.g. the current HTTP request, logged-in user, flash messages, the layout, etc.. +The controller provides the view with a key-value map called [`ControllerContext`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#t:ControllerContext). The [`ControllerContext`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#t:ControllerContext) provides the view information it might need to render, without always explicitly passing it. This is usually used to pass e.g. the current HTTP request, logged-in user, flash messages, the layout, etc.. -Usually, a view consists of a data structure and a `View` instance. E.g. like this: +Usually, a view consists of a data structure and a [`View`](https://ihp.digitallyinduced.com/api-docs/IHP-ViewSupport.html#t:View) instance. E.g. like this: ```haskell data ExampleView = ExampleView { optionA :: Text, optionB :: Bool } @@ -56,7 +56,7 @@ module Web.View.Layout (defaultLayout, appLayout) where ### Using a layout inside a single view -To use the layout inside a view, call `setLayout` from the `beforeRender`: +To use the layout inside a view, call [`setLayout`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Layout.html#v:setLayout) from the [`beforeRender`](https://ihp.digitallyinduced.com/api-docs/IHP-ViewSupport.html#v:beforeRender): ```haskell instance View MyView where @@ -66,7 +66,7 @@ instance View MyView where ### Using a layout for a complete controller -When all views of a controller use a custom layout place the `setLayout` call in the `beforeAction` of the controller: +When all views of a controller use a custom layout place the [`setLayout`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Layout.html#v:setLayout) call in the [`beforeAction`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#v:beforeAction) of the controller: ```haskell instance Controller MyController where @@ -79,7 +79,7 @@ instance Controller MyController where ### Changing the default layout -You can change the default layout of your application by updating `initContext` in `Web.FrontController`. +You can change the default layout of your application by updating [`initContext`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#v:initContext) in `Web.FrontController`. ```haskell instance InitControllerContext WebApplication where @@ -89,7 +89,7 @@ instance InitControllerContext WebApplication where ### Disabling the Layout for a View -You can disable the layout for a specific view by overriding the `beforeRender` function like this: +You can disable the layout for a specific view by overriding the [`beforeRender`](https://ihp.digitallyinduced.com/api-docs/IHP-ViewSupport.html#v:beforeRender) function like this: ```haskell instance View MyView where @@ -139,7 +139,7 @@ initCompanyContext = Nothing -> pure () ``` -The `initContext` is called on every request, just before the action is executed. The `initCompanyContext` fetches the current user's company and then calls `putContext company` to store it inside the controller context. +The [`initContext`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#v:initContext) is called on every request, just before the action is executed. The `initCompanyContext` fetches the current user's company and then calls [`putContext company`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Context.html#v:putContext) to store it inside the controller context. Next we'll read the company from the `Layout.hs` @@ -166,9 +166,9 @@ company :: (?context :: ControllerContext) => Company company = fromFrozenContext ``` -Here the company is read by using the `fromFrozenContext` function. +Here the company is read by using the [`fromFrozenContext`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Context.html#v:fromFrozenContext) function. -You might wonder: How does `fromFrozenContext` know that I want the company? The context is a key-value map, where the key's are the type of the object. Using the `company :: Company` type annotation the `fromFrozenContext` knows we want to read the value with the key `Company`. +You might wonder: How does [`fromFrozenContext`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Context.html#v:fromFrozenContext) know that I want the company? The context is a key-value map, where the key's are the type of the object. Using the `company :: Company` type annotation the [`fromFrozenContext`](https://ihp.digitallyinduced.com/api-docs/IHP-Controller-Context.html#v:fromFrozenContext) knows we want to read the value with the key `Company`. Now the `company` variable can be used to read the current user's company across the layout and also in all views (you need to add `company` to the export list of the Layout module for that). If the `company` value is used somewhere during rendering while the user is not logged it will raise a runtime error. @@ -176,7 +176,7 @@ Now the `company` variable can be used to read the current user's company across ### Accessing the Request -Use `theRequest` to access the current [WAI request](https://hackage.haskell.org/package/wai-3.2.2.1/docs/Network-Wai.html). +Use [`theRequest`](https://ihp.digitallyinduced.com/api-docs/IHP-ViewSupport.html#v:theRequest) to access the current [WAI request](https://hackage.haskell.org/package/wai-3.2.2.1/docs/Network-Wai.html). ### Highlighting the current active link @@ -222,7 +222,7 @@ By default, a message `Are you sure you want to delete this?` is shown as a simp #### Setting the Page Title -You can override the default page title by calling `setTitle` inside the `beforeRender` function of your view: +You can override the default page title by calling [`setTitle`](https://ihp.digitallyinduced.com/api-docs/IHP-PageHead-ControllerFunctions.html#v:setTitle) inside the [`beforeRender`](https://ihp.digitallyinduced.com/api-docs/IHP-ViewSupport.html#v:beforeRender) function of your view: ```haskell instance View MyView where @@ -232,7 +232,7 @@ instance View MyView where -- ... ``` -You can also call `setTitle` from the controller action if needed: +You can also call [`setTitle`](https://ihp.digitallyinduced.com/api-docs/IHP-PageHead-ControllerFunctions.html#v:setTitle) from the controller action if needed: ```haskell module Web.Controller.Posts where @@ -247,7 +247,7 @@ instance Controller PostsController where render ShowView { .. } ``` -If the page title is not changed as expected, make sure that your `Layout.hs` is using `pageTitleDefault`: +If the page title is not changed as expected, make sure that your `Layout.hs` is using [`pageTitleDefault`](https://ihp.digitallyinduced.com/api-docs/IHP-PageHead-ViewFunctions.html#v:pageTitleOrDefault): ```html WRONG: @@ -280,7 +280,7 @@ To dynamically manage meta tags like `{markdownHtml |> preEscapedToHtml} @@ -294,9 +293,10 @@ In these cases you can use `preEscapedToHtml` to mark a text as already escaped:

hello

``` -Be careful when using `preEscapedToHtml` as it can easily introduce security issues. +Be careful when using [`preEscapedToHtml`](https://ihp.digitallyinduced.com/api-docs/IHP-ViewPrelude.html#v:preEscapedToHtml) as it can easily introduce security issues. The `preEscapedToHtml` function can also be used to output HTML code that is not supported by HSX: +The [`preEscapedToHtml`]() function can also be used to output HTML code that is not supported by HSX: ```html {"" |> preEscapedToHtml} From 034593ba959aa3468faaf399e2554f775b252e4d Mon Sep 17 00:00:00 2001 From: Jannis Jorre Date: Tue, 5 Oct 2021 12:01:36 +0200 Subject: [PATCH 09/32] link functions to api-docs in guide (form.markdown) --- Guide/form.markdown | 94 ++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/Guide/form.markdown b/Guide/form.markdown index 952b6d4a7..e69931983 100644 --- a/Guide/form.markdown +++ b/Guide/form.markdown @@ -14,7 +14,7 @@ Unless javascript helpers have been deactivated, your form will be submitted usi ## Simple Forms -Forms usually begin with a `formFor` expression. This is how a simple form can look like: +Forms usually begin with a [`formFor`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:formFor) expression. This is how a simple form can look like: ```haskell renderForm :: Post -> Html @@ -51,21 +51,19 @@ All inputs have auto-generated class names and ids for styling. Also, all `name` IHP has the most commonly-used form controls built in. In general the form control helpers just need to be passed the field name. Here is a list of all built-in form control helpers: -```haskell -{textField #title} -{textareaField #body} -{colorField #brandColor} -{emailField #email} -{dateField #dueAt} -{passwordField #password} -{dateTimeField #createdAt} -{numberField #quantity} -{hiddenField #projectId} -{checkboxField #termsAccepted} -{selectField #projectId allProjects} -{fileFile #profilePicture} -{submitButton} -``` +- [`{textField #title}`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:textField) +- [`{textareaField #body}`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:textareaField) +- [`{colorField #brandColor}`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:colorField) +- [`{emailField #email}`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:emailField) +- [`{dateField #dueAt}`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:dateField) +- [`{passwordField #password}`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:passwordField) +- [`{dateTimeField #createdAt}`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:dateTimeField) +- [`{numberField #quantity}`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:numberField) +- [`{hiddenField #projectId}`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:hiddenField) +- [`{checkboxField #termsAccepted}`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:checkboxField) +- [`{selectField #projectId allProjects}`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:selectField) +- [`{fileFile #profilePicture}`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:fileField) +- [`{submitButton}`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:submitButton) A form control is always filled with the value of the given field when rendering. For example, given a post @@ -73,7 +71,7 @@ A form control is always filled with the value of the given field when rendering let post = Post { ..., title = "Hello World" } ``` -Rendering `{textField #title}`, the input value will be set like +Rendering [`{textField #title}`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:textField), the input value will be set like ```html @@ -90,7 +88,7 @@ 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: +Rendering [`{textField #title}`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:textField), the input will have the css class `is-invalid` and an element with the error message will be rendered below the input: ```html
@@ -108,7 +106,7 @@ Rendering `{textField #title}`, the input will have the css class `is-invalid` a ## Forms Are Also HSX -It's important to understand that while the form helpers like `{textField #title}` are called by `formFor`, you can still use HSX there. So you can just add any kind of HSX code inside your form: +It's important to understand that while the form helpers like [`{textField #title}`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:textField) are called by [`formFor`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#v:formFor), you can still use HSX there. So you can just add any kind of HSX code inside your form: ```haskell renderForm :: Post -> Html @@ -136,7 +134,7 @@ Inside the HSX block of a form, you have access to the special `?formContext` va ## Customizing Inputs -The return values of the form control helpers are usually a value of type [FormField](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#t:FormField). The `FormField` value is automatically rendered as HTML when used inside an HSX expression. Before this rendering happens, you can specify options to customize the rendering. +The return values of the form control helpers are usually a value of type [FormField](https://ihp.digitallyinduced.com/api-docs/IHP-View-Form.html#t:FormField). The [`FormField`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Types.html#t:FormField) value is automatically rendered as HTML when used inside an HSX expression. Before this rendering happens, you can specify options to customize the rendering. ### Help Texts @@ -159,7 +157,7 @@ This will render like: ### Custom Field Label Text -By default, the field name will be used as a label text. The camel case field name will be made more human-readable of course, so `contactName` will turn to `Contact Name`, etc. Sometimes you want to change this auto-generated input label to something custom. Use `fieldLabel` for that, like this: +By default, the field name will be used as a label text. The camel case field name will be made more human-readable of course, so `contactName` will turn to `Contact Name`, etc. Sometimes you want to change this auto-generated input label to something custom. Use [`fieldLabel`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Types.html#t:FormField) for that, like this: ```haskell {(textField #title) { fieldLabel = "Post Title"} } @@ -176,7 +174,7 @@ This will render like: ### Custom CSS Classes -You can add custom CSS classes to the input and label for better styling. Set `fieldClass` for adding a class to the input element and `labelClass` for the label element: +You can add custom CSS classes to the input and label for better styling. Set [`fieldClass`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Types.html#t:FormField) for adding a class to the input element and [`labelClass`](https://ihp.digitallyinduced.com/api-docs/IHP-View-Types.html#t:FormField) for the label element: ```haskell {(textField #title) { fieldClass="title-input", labelClass = "title-label" } } @@ -398,7 +396,7 @@ This will render like: #### Don't render `