« Previous 1 2 3 4 Next »
Haskell framework for the web
Speaking in Tongues
Yesod Ecosystem
The listings that follow are excerpts from the project folder, which Stack generates as soon as you call:
stack new <My project>
The original project essentially comprises the pages shown in a login and link to Yesod documents (Figure 1), the possibility to upload files (Figure 4), and built-in support (Figure 5). Also, the home page includes a brief introduction to the framework.
The login overrides the sample project with the option to register with Google and Yahoo (Figure 3) and sets the maximum number of uploadable files to one (Figure 4). For a quick start in Yesod, you still need to know which information belongs where. Table 1 provides an overview of the most important files.
Table 1
<My project> Folder
Folder/File Name | Content |
---|---|
Application.hs
|
Initialize application, register handler |
Foundation.hs
|
Configure plugins |
<Project name>.cabal
|
Register extensions, packages, and handler |
stack.yaml
|
Packages, GHC |
config/settings.yml
|
Global variables |
config/favicon.ico
|
Icon |
config/models
|
Database schema |
config/routes
|
Routes |
Handler/handler.hs
|
Handler |
static/
|
CSS, JavaScript, images, fonts |
templates/
|
Templates in Hamlet, Julius, Cassius, or Lucius |
Now you can edit the files and define an individual icon for the URL bar; you can replace the config/favicon.ico
file with an original composition of the same name. You can modify the database schema in the config/model
file, as necessary. Changes affect the current system once you save them – you do not need to restart the web application – and applies to changes to other files in the project.
In the config/routes
file, you determine the routes (i.e., the URLs) that users use to reach the individual websites. To do so, you can store POST, GET, and other methods for each route in the same file.
Yesod also requires one handler per route. The handler determines what will happen as soon as you call a route. It is best to create a new handler in the handler
folder, where you should make sure to use the typical Haskell .hs
file suffix. Last but not least, you need to define the name of the domain in which the Yesod framework runs in the config/settings.yml
file below the AppRoot
keyword.
The static
subfolder includes JavaScript files, style sheets, fonts, and images. It is worth mentioning in this context that the project includes the widespread Bootstrap CSS framework [15]. The static
folder only contains pure CSS or JavaScript files here, because Yesod has its own template language, and the files created with it are usually in the templates
directory.
The <My project>/test
subfolder contains sample code that you use to put the web application through its paces. Additionally, several interesting files reside in the main folder of the project. Yesod initializes global variables or data types in the Application.hs
(Listing 5) file, and the handler uses the getYesod
function to access their values (Listing 6).
Listing 5
<My project>/Application.hs
01 [...] 02 import Model.MySettings 03 [...] 04 makeFoundation :: AppSettings -> IO App 05 makeFoundation appSettings = do 06 [...] 07 - maximum number of uploadable files 08 let maxFiles = 5 09 mysettings' <- newSettings maxFiles 10 -- make "myMax" version available globally 11 let mkFoundation appConnPool = App mysettings' 12 [...]
Listing 6
<My project>/handler/Home.hs (Extract)
01 [...] 02 module Handler.Home where 03 [...] 04 -- handler that created the www.domainname page 05 getHomeR :: Handler Html 06 getHomeR = do 07 [...] 08 - access to global variables with getYesod 09 (App{mySettings=s}) <- getYesod 10 let (M.MySettings {M.maxFiles=m }) = s 11 [...]
For example, if a website user can upload a maximum of five images per ticket, you would import a separate file (Listing 7), in which you have set a data type and a function that initializes the variable.
Listing 7
<My project>/Model/MySettings.hs
01 module Model.MySettings 02 ( 03 MySettings(..) 04 , newSettings 05 )where 06 import [...] 07 -- type synonym 08 type Max = Int 09 --Datentyp MySettings 10 data MySettings = MySettings 11 { myMax :: Max } 12 --this function determines the maximum number of uploadable files 13 newSettings num = do 14 return MySettings { myMax = num }
You then declare a local variable in the makeFoundation
function in Application.hs
(Listing 5): The variable contains the maximum number of uploadable files and passes this variable in to the imported function, which initializes the variable. Additionally, you need to define which data types belong to the web application named App
(Listing 8).
Listing 8
<My project>/Foundation.hs (Extract)
01 [...] 02 import Model.MySettings 03 [...] 04 --Name the of the web application = App 05 data App = App 06 - In the app, only the variables of the data type MySettings appear. 07 - Under Java developers could compare MySettings with a class, 08 --which has the myApp variable. This is from MySettings type. 09 {mySettings :: MySettings } 10 [...]
To Be or Not To Be
Shakespeare provides the name of the template language that Yesod uses to generate HTML files from a template: Hamlet. If you know HTML, you will have no trouble familiarizing yourself with Hamlet, which supports if
-then
queries, case distinctions (using case
and Maybe
), and loops.
If necessary, you can overwrite the settings in the global style sheet with the use of two style sheet languages: Cassius and Lucius. Whereas Lucius, like CSS, requires brackets when defining individual selectors and a semicolon at the end of a statement, Cassius gets by without this overhead. However, with Cassius, you do need to indent the definition of a selector.
Unlike CSS, Lucius and Cassius can define variables, saving lines of code. Using Julius, you can create JavaScript files that access the existing variables in the project, such as route names. You can statically integrate existing CSS style sheets or JavaScript files without having to convert them to another format. In Listing 9, Yesod loads the static/css/styling.css
style sheet when the user calls a route of their choice.
Listing 9
<My project>/Foundation.hs (Extract)
01 [...] 02 - defines the default layout, which applies to each page of a given domain 03 pc <- widgetToPageContent $ do 04 addStylesheet $ StaticR css_styling_css 05 - the standard layout tempate is in the "default-layout.hamlet" file 06 $(widgetFile "default-layout") 07 withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet") 08 [...]
However, it makes sense to use the template languages for new files. For example, if you want the handler that implements the homepage to integrate both HTML code in a Hamlet file and an appropriate style sheet and Julius file on top, you should ensure that the names of these files are all the same (e.g., homepage.hamlet
, homepage.julius
, and homepage.lucius
). In this way, you only need to include a file in the handler once and then reference homepage
(Listing 10) in the handler. Yesod then automatically loads the templates.
Listing 10
<My project>/handler/Home.hs (Extract)
01 [...] 02 module Handler.Home where 03 [...] 04 - handler that generates the home page of a domain 05 - In the homepage.hamlet file you will find the template for the home page 06 getHomeR :: Handler Html 07 getHomeR = defaultLayout $(widgetFile "homepage") 08 [...]
Guaranteeing Security
If a user contacts the webmaster via a contact form, you can generate the necessary forms under Yesod without having to write multiple lines of code. Two different methods give you the same results. If you just want to get started, type A forms are suitable. You don't need an additional Hamlet file for them; you just need to specify which HTML fields the form will comprise, as in the contactAForm
function (Listing 11).
Listing 11
contact-v2.hs
01 [...] 02 data ContactForm = ContactForm { 03 contactName :: Text 04 ,contactEmail :: Text 05 ,contactText :: Textarea 06 } deriving Show 07 08 - generates a type A form 09 contactAForm :: AForm Handler ContactForm 10 contactAForm = ContactForm 11 <$> areq textField "Name" Nothing 12 <*> areq textField "Email" (Just "info@example.com") 13 <*> areq textareaField "Text" Nothing 14 15 contactForm :: Html -> MForm Handler (FormResult ContactForm, Widget) 16 contactForm = renderBootstrap2 contactAForm 17 18 getContactR = do 19 (widget, enctype) <- generateFormPost $ contactForm 20 page enctype widget 21 [...]
You can define the appearance of monadic M forms, mentioned at the top of the article, yourself (Figure 2), but this does mean you add the previously defined HTML input elements to the Hamlet section. You also add classes or IDs to the Hamlet files that you previously defined in Lucius or Cassius (Listing 4).
Yesod guarantees security with the use of security tokens, which are stored in a hidden HTML input field. These tokens prevent cross-site request forgery attacks. You only need to enter the #{extra}
variable in the Hamlet section for M forms. Yesod automatically adds the extra variable for A forms.
« Previous 1 2 3 4 Next »
Buy this article as PDF
(incl. VAT)