Introduction
In IHP an action is a place for request handling logic. A controller is just a group of related actions. You can think about actions as messages sent to your application, e.g. aShowPostAction { postId :: Id Post } is the message “Show me the post with id $postId”.
Each action needs to be defined as a data structure inside Web/Types.hs. Therefore you can see an overview of all the messages which can be sent to your application just by looking at Web/Types.hs.
Creating a new Controller
We recommend using the code generators for adding a new controller. Using the web GUI, you can open http://localhost:8001/NewController. Using the CLI, runnew-controller CONTROLLER_NAME.
The following section will guide you through the manual process of creating a new controller.
Define the controller type
Let’s say we want to create a new controller with a single action This defines a type
ShowPostAction. First we need to define our types in Web/Types.hs: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.Implement the action logic
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 This implementation for
action function of the Controller instance. We can define this instance in Web/Controller/Posts.hs:ShowPostAction responds with a simple plain text message. The 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 theparam 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 <form><input name="paramName" value="paramValue"/></form> (in that case we’re talking about a body parameter).
Given a request like GET /UsersAction?maxItems=50, you can access the maxItems like this:
Int. This parsing works out of the box for Ids, UUID, Bools, Timestamps, etc. Here are some more examples:
Missing parameters
In case there is a problem parsing the request parameter, an error will be triggered. When the parameter is optional, useparamOrDefault:
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.
Multiple Params With Same Name (Checkboxes)
When working with checkboxes sometimes there are multiple values for a given parameter name. Given a form like this:param @Text "ingredients" will only return the first ingredient "Milk". To access all the checked ingredients use paramList:
ingredients will be set to ["milk", "egg"]. When no checkbox is checked it will return an empty list.
Passing Data from the Action to the View
A common task is to pass data from the action to the view for rendering. You can do this by extending the view data structure by the required data. Given an action like this:ExampleView like this:
firstname :: Text field to the ExampleView data structure and then adding a {firstname} to the HSX:
firstname field inside the action. So we also need to update the action:
Populating Records from From Data with fill
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.
Here is an example of using fill:
NewView again, so the user can fix the errors. If the request contains valid data, we are creating the post record and redirecting to the PostsAction.
Lifecycle
The Controller instance provides abeforeAction 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.
Here is an example to illustrate the lifecycle:
ShowPostAction will cause the following output to be logged to the server console:
Rendering Responses
Rendering Views
Inside a controller, you have several ways of sending a response. The most common way is to use therender function with a View data structure, like this:
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 will be send when the render function cannot fulfill the requested Accept formats.
Rendering Plain Text
CallrenderPlain to send a simple plain text response:
Rendering HTML
Usually, you want to render your HTML using a view. SeeRendering Views for details.
Sometimes you want to render HTML without using views, e.g. doing it inline in the action. Call respondHtml for that:
hsx into your controller: import IHP.ViewPrelude (hsx).
Rendering a Not Found Message
UserenderNotFound to render a generic not found message, e.g. when an entity cannot be found:
Redirects
Redirect to an Action
UseredirectTo to redirect to an action:
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.
If you need to force the follow-up request to be a GET (e.g. after a POST or DELETE), use redirectToSeeOther to send a 303 See Other:
Redirect to a Path
UseredirectToPath when you want to redirect to a path on the same domain:
Redirect to a URL
UseredirectToUrl to redirect to some external URL:
Action Execution
When calling a function to send the response, IHP will stop executing the action. Internally this is implemented by throwing and catching aResponseException. Any code after e.g. a render SomeView { .. } call will not be called. This also applies to all redirect helpers.
Here is an example of this behavior:
putStrLn will never be called because the redirectTo already stops execution.