Sorry for the delay of this page – last week was quite a hard time and this post took much longer than I thought. To make up for it, this week will get two posts – first today and second one tomorrow.

As you probably remember, last time we managed to set up database connection and fetch projects from it. Still, we didn’t have an option to insert data – all our data was preset from a hardcoded file. Well, good news – today we’re going to remove this obstacle.

Right now we’re going to do: add a new route – project/[id]/edit for project data edition – we’ll create a formular and a handling routine.

First of all, let’s start with the route. That’s pretty straightforward and you probably know the right syntax:

/project/#Int/edit ProjectEditR GET POST

in config/routes. A new thing here is that we handle POST as well as GET (no DELETE yet though!), but it doesn’t make a big difference. We just have to provide one more function – postProjectEditR in our handler. So let’s do it now – create a Handler/ProjectEdit.hs and include it in Application.hs and .cabal file.

Now we’re going to write the handler – that’s the core of our today’s concept, so let’s start with a little introduction.

There are three types of forms in Yesod – applicative forms, monadic forms and input forms. Applicative ones are the most common, and ones that we’re going to deal with today. Monadic ones are used in case of non-standard view, and input ones are not generated, but only received (e.g. because of dynamic fields). Applicative forms are generally created by composing preconfigured fields. Let’s take a look on a more concrete example – our Project with four fields – identifier, name, shortName and deadline. While changing identifier might seem a bit weird (it’s in URL after all!), we’re not going to care about it – it’s the forms that are interesting today after all, not the semantics of project addition, right?

We’ll start with creating the form. Type signature will be a little weird – Int -> Maybe Project -> AForm Handler Project. That’s a little unintuitive, but our route allows for accessing UIDs without projects, and if the project already exists, we want to fill in rest of the fields.

We’re gonna start with the easy part – the Hamlet template for the form. W’ve already created quite a bunch of Hamlet templates, so it shouldn’t be a big deal. Here’s the code for templates/project-edition.hamlet (by the way, if you’re wondering how to change templates directory in Yesod – you can’t, it’s hardcoded):


<div .jumbotron>

<h2> Project edition

<form method=post action=@{ProjectEditR projectId} enctype=#{enctype}>
    ^{formWidget}

pretty simple, eh? We just add a master block, a heading and form declaration (with encoding and target route – be careful with the indentation – it’s important that formWidget is under form) and that’s all – well, maybe except the formWidget, which we still have to create. We should do it now, but, unfortunately, it’s not that simple. Yesod uses runFormPost function to both generate a form and evaluate input data against it. It has quite an impressive type signature, but the important thing is the result – it’s m ((FormResult a, xml), Enctype). FormResult a is the parsed result, information that no data was provided or a list of error messages, while xml is the formWidget we’re looking for. Enctype is not really interesting – it is the encoding required by form (UrlEncoded or Multipart). It’s argument is a function, that tranforms Markup(roughly Html) to MForm (a formular).

Before diving into code, let’s recap what we’re going to do:

  1. create a POST/GET handler (one for both methods)
  2. fetch project from database, if it already exists
  3. generate form with prefilled fields (in case of existing project)
  4. after submission, save the form to database

Quite a lot of work for a single post, right? We’d better get started straight ahead!
Let’s start with the POST handler:

postProjectEditR :: Int -> Handler Html
postProjectEditR projectId =  do
  project <- runDB $ getBy $ UID projectId renderForm $ (\(Entity _ proj) -> proj) `fmap` project

That’s fairly straightforward and similar to what we’ve done in Handler/Project.hs. Perhaps the only interesting part is using the fact that Maybe is a Functor, so we can use fmap on it (it’ll be executed on Just value, Nothing will be ignored). Well, since the first part is already done, let’s go to the rendering part!

renderForm :: Maybe Project -> Handler Html
renderForm projectId projectEntry = do
  ((result, formWidget), enctype) <- runFormPost (projectForm projectId projectEntry) case result of FormSuccess project -> redirect $ ProjectR (projectIdentifier project)
    _ -> defaultLayout $ do
      app <- getYesod
      setTitle $ (toHtml $ (appName app)) ++ ": editing projects"
      $(widgetFile "project-edition")
      $(widgetFile "back-to-projects")

Now, this piece of code is much more interesting – first of all, it uses the actual form, generated by projectForm projectId projectEntry. We didn’t write this function yet, so it’s more of a stub for the future. Next interesting part is handling of successfully created Project – here we simply redirect to view page. This means that we don’t actually insert anything to database – we’re gonna deal with the inserting later on. And the rest is displaying the form (we’ve created the Hamlet template in the beginning).

Now, before we get to the actual form, there is one more level of indirection that I’d like to introduce:

projectForm :: Int -> Maybe Project -> Html -> MForm Handler (FormResult Project, Widget)
projectForm projectId project = renderBootstrap3 BootstrapBasicForm $ projectAForm projectId project

I want to introduce it, since it’s quite independent from the form creation, but is the point at which the layout is decided (the form’s form). There are a few interesting things here – first of all, it returns a MForm (monadic form version), and creates it from applicative version of form (AForm). Next, it performs the form conversion to Bootstrap mechanisms – namely BootstrapBasicForm. There are three types of Bootstrap form layouts, and this one is simplest – label above input field. Looks good enough for our purposes, so let’s finally get to creating forms!

Here’s the code:

projectAForm :: Int -> Maybe Project -> AForm Handler Project
projectAForm projectId project = Project
    <$> areq hiddenField identifierConfig (pure projectId)
    <*> areq textField nameConfig (projectName <$> project)
    <*> areq textField shortNameConfig (projectShortName <$> project)
    <*> areq utcDayField timeFieldConfig (projectDeadline <$> project)
    <* bootstrapSubmit ("Modify" :: BootstrapSubmit Text)
    where
      identifierConfig = bfs ("" :: Text)
      nameConfig = withPlaceholder "Project name" $ bfs ("Project name" :: Text)
      shortNameConfig = withPlaceholder "Short project name" $ bfs ("Short project name" :: Text)
      timeFieldConfig = bfs ("Project deadline" :: Text)

Lots of code, eh? But it’s mostly simple, so it shouldn’t be a big deal. First we declare that we’re gonna create a Project, and then list it’s fields. In our case all the fields are required (areq – optional are added by using aopt). Fields are added by using <*> operator. First areq argument is type of field (e.g., textField), second – field configuration (in our case labels, but we can also set tooltips or ids) and third – default value. We take most default values out of database project, except identifier – we know it for sure, so it’s taken from URL (as an argument). bfs call in all settings is used to underline the fact that we use Bootstrap styles, and it’s argument will be used as a label for field. There is one little quirk here – currently Bootstrap forms in Yesod don’t handle hidden fields very well, and leave some space for it (namely, space for the label). This might be fixed when a GitHub issue is resolved, but it’s quite old already, and it doesn’t work yet. A possible workaround is to add this field to form after its creation, but it’s not a nice solution, and we’re just gonna live with the current state for now.

We’re almost done – there is just one thing left to display the form. Namely – utcDayField does not exist in Yesod. But fret not! Yesod has a built-in Day field, which is used for dates. We just need to transform it so that using the same field will yield a different data type as output. Luckily, Yesod provides a function to transform one field to another, and it has (quite unintuitive) name checkMMap. Our defintion of utcDayField looks like this:

utcDayField = checkMMap (return . Right . toUTCTime :: (Day -> Handler (Either Text UTCTime))) utctDay dayField
  where
  toUTCTime x = UTCTime x (secondsToDiffTime 0)

Looks a bit magical, I agree. Basically we need to provide two functions: one to transform Day to UTCTime (to inject data to our code), and second, to transform UTCTime to Day (to display default value properly). This is done be toUTCTime (defined) and utctDay (built-in). Rest of the definition (return . Right . and type signature) serves mostly to feed the type system.

And we’re done! The form is generated and user can display it easily. A nice bonus is that Yesod-generated forms automatically provide tokens that protect us from CSRF attacks.

Remember to add also handler for GET requests (getProjectEditR = postProjectEditR is a sufficient implementation) and required imports: Yesod.Form.Bootstrap3 and Data.Time.Clock).

That’s the end of today’s post. We did a lot of things – accessed database to fetch a – possibly existing – project, generated a form and filled it with default values. We didn’t manage to insert the data to database yet – we’re going to do this in the next post, tomorrow.

Stay tuned!