411 REFERENCE · Wikibooks
Yesod web framework/Controller
Reference material by Wikibooks contributors. This retains its source identity and is not 411 news reporting.
Server interface
Yesod uses a Web application interface API,http://hackage.haskell.org/package/wai The wai package Hackage.haskell.org 2012-10-23 abbreviated WAI, to isolate servlets, aka web apps., from servers, with handlers for the server protocols CGI,http://hackage.haskell.org/package/wai-extra The wai-extra package with CGI WAI handler Hackage.haskell.org 2012-10-23 FastCGI,http://hackage.haskell.org/package/wai-handler-fastcgi The wai-handler-fastcgi package Hackage.haskell.org 2012-10-23 SCGI,http://hackage.haskell.org/package/wai-handler-scgi The wai-handler-scgi package Hackage.haskell.org 2012-10-23 Warp,http://hackage.haskell.org/package/warp The warp package Hackage.haskell.org 2012-10-23 Launch (open as local URL to the default browser, closing the server when the window is closed),http://hackage.haskell.org/package/wai-handler-launch The wai-handler-launch package Hackage.haskell.org 2012-10-23
The foundation type
See ref.http://www.yesodweb.com/book/basics book - Basics Yesodweb.com 2012-10-23 Yesod requires a data type that instantiates the controller classes. This is called the foundation type. In the example below, it is named "MyApp".
The REST model identifies a web resource with a web path. Here REST resources are given names with an R suffix (like "HomeR") and are listed in a parseRoutes site map description template. From this list, route names and dispatch handler names are derived.
Yesod makes use of Template Haskell metaprogramming to generate code from templates at compile time, assuring that the names in the templates match and everything typechecks (e.g. web resource names and handler names).
By inserting a mkYesod call, this will call Template Haskell primitives to generate the codeThe mkYesod code corresponding to the route type members, and the instances of the dispatch controller classes as to dispatch GET calls to route HomeR to a routine named composing them both as "getHomeR", expecting an existing handler that matches the name.
Hello World
"Hello world" example based on a CGI server interface (The actual handler types have changed, but the philosophy remains):
{- file wai-cgi-hello.hs -}
{-# LANGUAGE PackageImports, TypeFamilies, QuasiQuotes, MultiParamTypeClasses,
TemplateHaskell, OverloadedStrings #-}
import "wai" Network.Wai
import "wai-extra" Network.Wai.Handler.CGI (run) -- interchangeable WAI handler
import "yesod" Yesod
import "yesod-core" Yesod.Handler (getRequest)
import "text" Data.Text (Text)
import "shakespeare" Text.Cassius (Color(..), colorBlack)
-- the Foundation type
data MyApp = MyApp
-- sitemap template, listing path, resource name and methods accepted
-- `mkYesod` takes the foundation type name as param. for name composition of dispatch functions
mkYesod "MyApp" [parseRoutes|
/ HomeR GET
|]
instance Yesod MyApp
-- indentation structured CSS template
myStyle :: [Text] → CssUrl url
myStyle paramStyle =
[cassius|
.box
border: 1px solid #{boxColor}
|]
where
boxColor = case paramStyle of
["high-contrast"] → colorBlack
_ → Color 0 0 255
-- indentation structured HTML template
myHtml :: [(Text, Text)] → HtmlUrl url
myHtml params = [hamlet|
<!-- indentation, or lack of it, under starting tags or commands ('$' prefix)
describe the content or sequence tree structure -->
<!-- '.' or '#' prefixes in tags introduce css styled "class" or "id" attribute values -->
<!-- interpolation of haskell expressions follow the "shakespeare templates" #{expr} syntax -->
<p>Hello World! There are <span .box>#{length params} parameters</span>:
$if null params
<p>Nothing to list
$else
<ul>
$forall param <- params
<li>#{fst param}: #{snd param}
|]
getHomeR :: Handler RepHtml
getHomeR = do
req <- getRequest
let params = reqGetParams req
paramStyle <- lookupGetParams "style"
defaultLayout $ do
-- adding widgets to the Widget monad (a ''Writer'' monad)
setTitle "Yesod example"
toWidgetHead $ myStyle paramStyle
toWidgetBody $ myHtml params
-- there are ''run'' function variants for different WAI handlers
main = toWaiApp MyApp >>= run
# cgi test
export REMOTE_ADDR=127.0.0.1
export REQUEST_METHOD=GET
export PATH_INFO=/
export QUERY_STRING='p1=abc;p2=def;style=high-contrast'
./wai-cgi-hello
Resources, routes and HTTP method handlers
See ref.http://www.yesodweb.com/book/routing-and-handlers book - Routing and Handlers Yesodweb.com 2012-10-23http://fpcomplete.com/yesod-tutorial-2-playing-with-routes-and-links/ Playing with Routes and Links FPComplete.com 2012-10-17 2012-10-28
Yesod follows the REpresentational State Transfer model of access to web documents, identifying docs. and directories as resources with a Route constructor, named with an uppercase R suffix (for example, HomeR).
The routes table The parseRoutes template should list the resources specifying route pieces, resource name and dispatch methods to be accepted.
URL segment capture as parameter is possible specifying a '#' prefix for single segment capture or '*' for multisegment capture, followed by the parameter type.
-- given a MyApp foundation type
mkYesod "MyApp" [parseRoutes|
/ HomeR -- no http methods stated: all methods accepted
/blog BlogR GET POST
-- the '#' prefix specify the path segment as a route handler parameter
/article/#ArticleId ArticleR GET PUT
-- the '*' prefix specify the parameter as a sequence of path pieces
/branch/*Texts BranchR GET
-- to simplify the grammar, compound types must use an alias, eg. type Texts for ''[Text]''
|]
Applying the previous template generates the following route constructors:
data Route MyApp =
HomeR -- referenced in templates as: @{HomeR}
| BlogR -- in templates: @{BlogR}
| ArticleR ArticleId -- in templates: @{ArticleR myArticleId}
| BranchR Texts -- in templates: @{BranchR myBranchSegments}
For every supported HTTP method a handler function must be created to match the dispatch names generated by mkYesod from the parseRoutes template, by prefixing the method name (or the prefix "handler" if no method stated) to the resource, as described (actual versions handler types have changed, but the philosophy remains):
-- for "/ HomeR" -- no http methods stated ⇒ only one handler with prefix ''handler''
handlerHomeR :: HasReps t ⇒ Handler t
-- for "/blog BlogR GET POST"
getBlogR :: HasReps t ⇒ Handler t
postBlogR :: HasReps t ⇒ Handler t
-- for "/article/#ArticleId ArticleR GET PUT"
getArticleR :: HasReps t ⇒ ArticleId → Handler t
putArticleR :: HasReps t ⇒ ArticleId → Handler t
Request data, Parameters, Cookies, Languages and other Header info
See ref.
Authentication and authorization
See ref.http://www.yesodweb.com/book/authentication-and-authorization book - Authentication and Authorization Yesodweb.com 2012-10-23 Authentication plugins: OpenId, BrowserId, Email, GoogleEmail, HashDB, RpxNow.http://hackage.haskell.org/package/yesod-auth The yesod-auth package Hackage.haskell.org 2012-10-26
There is an important setting for automatic redirection after authentication.http://www.yesodweb.com/book/sessions book - Sessions - See section "Ultimate Destination" Yesodweb.com 2012-11-17
Sessions
See ref.http://www.yesodweb.com/book/sessions Sessions Yesodweb.com 2012-10-23 Session back-ends: ClientSessionhttp://hackage.haskell.org/packages/archive/clientsession/latest/doc/html/Web-ClientSession.html Web.ClientSession Hackage.haskell.org 2012-10-25 (it stores the session in a cookie), ServerSessionhttp://hackage.haskell.org/package/serversession ServerSession: secure modular server-side sessions Hackage.haskell.org 2018-10-29http://hackage.haskell.org/package/serversession-frontend-yesod Web.ServerSession.Frontend.Yesod Hackage.haskell.org 2018-10-29 (it stores most of the session data at the server)
>> To avoid undue bandwidth overhead, production sites can serve their static content from a separate domain name to avoid the overhead of transmitting the session cookie for each request
Session messages
A success, failure or indicative message can be stored (setMessage) in the Session and will be shown, if it exists, by the default_layout routine through the default_layout.hamlet template, being cleared on consultation.https://www.yesodweb.com/book/sessions#sessions_messages Session Messages Yesodweb.com 2018-10-23
Subsites
Common URL prefix subsites for workflows, file serving or site partitioning. See ref.http://www.yesodweb.com/book/creating-a-subsite Creating a Subsite Yesodweb.com 2012-10-25http://monoid.se/haskell/yesod-and-subsites/ Yesod and subsites: a no-brainer Monoid.se 2012-08-22 2012-10-28[]
Built-in subsites: Static,http://www.yesodweb.com/blog/2010/12/magic-of-yesod-part2 The Magic of Yesod, part 2 - See section "Static Subsite" Yesodweb.com 2010-12-25 2012-10-25http://hackage.haskell.org/packages/archive/yesod-static/latest/doc/html/Yesod-Static.html The package yesod-static - Static Subsite Hackage.haskell.org 2012-10-25 Authhttp://hackage.haskell.org/packages/archive/yesod-auth/latest/doc/html/Yesod-Auth.html The package yesod-auth - Auth Subsite Hackage.haskell.org 2012-10-25
Form processing and layout generation
See ref.http://www.yesodweb.com/book/forms book - Forms Yesodweb.com 2012-10-23
The Form type here is an object that is used in the controller to parse and process the form fields user input and produce a (FormResult, Widget) pair were the widget holds the layout of the next rendering of the form with error messages and marks. It can also be used to generate a new form with blanks or default values.
The form type takes the shape of a function of an html snippet to be embedded in the view, that will hold security purpose hidden fields.
A form object is generated from an Applicative/Monadic composition of fields for a combined/sequential parsing of field inputs.
There are three types of forms:
Applicative (with tabular layout),
Monadic (with free layout style), both in the Yesod.Form.Functions module,
Input (for parsing only, no view generated) in the Yesod.Form.Input module.
The field generators, whose names are composed by the form type initial (a|m|i) followed by (req|opt){- required or optional -}, have a fieldParse component and a fieldView one.http://hackage.haskell.org/packages/archive/yesod-form/latest/doc/html/Yesod-Form-Fields.html Yesod.Form.Fields Hackage.haskell.org 2012-10-23
the function runForm{Post|Get} runs the field parsers against the form field inputs and generates a (FormResult, Widget) pair from the views offering a new form widget with the received form field values as defaults. The function suffix is the http method used in the form submission.
while generateForm{Post|Get} ignores inputs from the client and generates a blank or defaults form widget.http://hackage.haskell.org/packages/archive/yesod-form/latest/doc/html/Yesod-Form-Functions.html#v:runFormPost Yesod.Form.Functions runFormPost Hackage.haskell.org 2012-10-25
The actual function parameters and types have changed through Yesod versions. Check the Yesod book and libraries signatures.
The magic is in the FormResult data type Applicative instance, where (<*>) collects the error messages for the case of FormFailure [textErrMsg] result valueshttp://hackage.haskell.org/packages/archive/yesod-form/latest/doc/html/Yesod-Form-Types.html#t:FormResult Yesod.Form.Types Hackage.haskell.org 2012-10-23
Monadic forms permit free form layout and better treatment of hiddenField members.
A sample of an Applicativehttp://www.haskell.org/haskellwiki/Applicative_functor HaskellWiki - Applicative functor haskell.org 2012-10-24 form:
-- a record for our form fields
data Person = Person {personName :: Text, personAge :: Int, personLikings :: Maybe Text}
-- the Form type has an extra parameter for an html snippet to be embedded, containing a CSRF token hidden field for security
type Form sub master x = Html → MForm sub master (FormResult x, Widget)
{-
-- for messages in validation functions:
@param master: yesod instance to use in renderMessage (return from handler's getYesod)
@param languages: page languages to use in renderMessage
-- optional defaults record:
@param mbPersonDefaults: Just defaults_record, or Nothing for blank form
-}
personForm :: MyFoundationType → [Text] → Maybe Person → Form sub master Person
{- ''aopt'' (optional field AForm component) for "Maybe" fields,
''areq'' (required fld AForm comp.) will insert the "required" attribute
-}
personForm master languages mbPersonDefaults = renderTable $
Person <$> areq textField fldSettingsName mbNameDefault
<*> areq customPersonAgeField fldSettingsAge mbAgeDefault
<*> aopt textareaField fldSettingsLikings mbLikingsDefault
where
mbNameDefault = fmap personName mbPersonDefaults
mbAgeDefault = fmap personAge mbPersonDefaults
mbLikingsDefault = fmap personLikings mbPersonDefaults
-- "fieldSettingsLabel" returns an initial fieldSettings record
-- recently the "FieldSettings" record can be defined from a String label since it implements IsString
fldSettingsName = (fieldSettingsLabel MsgName) {fsAttrs = [("maxlength","20")]}
fldSettingsAge = fieldSettingsLabel MsgAge
fldSettingsLikings = (fieldSettingsLabel MsgLikings) {fsAttrs = [("cols","40"),("rows","10")]}
customPersonAgeField = check validateAge intField
validateAge y
| y < 18 = Left $ renderMessage master languages MsgUnderAge
| otherwise = Right y
References
2
100%
__NOTOC__