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.
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.