Haskell
Yesod

How to define custom Layouts in Yesod

Posted on Sep 25, 2016 by Alexej Bondarenko

According to my experience web projects always grow in terms of different URLs and pages. It's natural to apply one Layout to all pages to fulfill the DRY requirement. This way you don't have to write the same header and footer code for each page of the project.

The most web projects I know don't use only one Layout template. The Yesod framework is (as many web frameworks) prepared for such use cases. The main "template" is already defined for you as defaultLayout when you start up a project by using predefined scaffolds. Templates are located in the "templates" folder of the project and consists of three files: 'default-layout-wrapper.hamlet', 'default-layout.hamlet' and 'default-layout.lucius'. (Hint: The .lucius and .julius files are optional for custom CSS and JavaScript).

Of course we would like to keep the default Layout (maybe customize it to our needs). Personally, I don't like project structure where all files are located on one level (in this case templates). So, let's reorganize our file structure and prepare a second layout. Create a folder 'templates/layout/default' and move the default Layout files to this folder:

mkdir -p templates/layout/default
cp templates/default* templates/layout/default/

Ok, at this point our application should start to complain about missing templates (if you use yesod devel or yesod-fast-devel). Let's fix the defaultLayout function to our new folder structure. In Foundation.hs, locate defaultLayout and add the subfolders:

defaultLayout widget = do
  master <- getYesod
        
  -- Much more code 

  pc <- widgetToPageContent $ do
    addStylesheet $ StaticR css_app_css
    $(widgetFile "layout/default/default-layout")
  withUrlRenderer $(hamletFile "templates/layout/default/default-layout-wrapper.hamlet")

As the next step, let's add two different custom Layouts. Let's pretend we have an admin interface with a sidebar and a special landing page with fancy animations:

mkdir -p templates/layout/admin
mkdir -p templates/layout/landing

cp templates/default/default* templates/layout/admin/
cp templates/default/default* templates/layout/landing/

As you can see, we copied the default layout files. This way we can start using our Layouts right away and customize them to our needs. To do so, we need to write functions for our Layouts and use the new templates there (here we will "override" the defaultLayout). For a better code organization, let's create a new module folder and add the Layout module file:

mkdir -p MyProject

touch MyProject/Layout.hs

The layout functions of this module can be easily copied from defaultLayout implementation (in Foundation.hs). The only parts we have to change are template locations:

module MyProject.Layout where

import Import

landingLayout :: Widget -> Handler Html
landingLayout widget = do
    master <- getYesod
    
    -- much more code here

    pc <- widgetToPageContent $ do
      addStylesheet $ StaticR css_app_css
      -- add custom CSS or JS here
      $(widgetFile "layout/landing/default")
    withUrlRenderer $(hamletFile "templates/layout/landinf/default-layout-wrapper.hamlet")



adminLayout :: Widget -> Handler Html
adminLayout widget = do
    master <- getYesod
    
    -- much more code here

    pc <- widgetToPageContent $ do
      addStylesheet $ StaticR css_app_css
      -- add custom CSS or JS here
      $(widgetFile "layout/landing/default")
    withUrlRenderer $(hamletFile "templates/layout/landinf/default-layout-wrapper.hamlet")

In our handler we can now use our new layout templates by importing our Layout module:

module MyProject.Handler.Landing where

import Import
import MyProject.Layout (landingLayout)


getFancyR :: Handler Html
getFancyR = landingLayout $ do
  setTitle "This is a fancy page"
  $(widgetFile "your-resource-file-here")

This way the FancyR resource will be rendered with the landingLayout instead of the defaultLayout.

By defining own templates and template functions you are now able to customize your Yesod project. If you have any additional information, comments or any problems with the implementation of the above code, just leave a note below.