Yesod (web framework)
<templatestyles src="Module:Hatnote/styles.css"></templatestyles>
Lua error in package.lua at line 80: module 'strict' not found.
Original author(s) | Michael Snoyman |
---|---|
Developer(s) | Michael Snoyman et al. |
Initial release | 2010 |
Stable release | 1.4.1[1][2] / November 23, 2014 |
Development status | Active |
Written in | Haskell |
Operating system | Cross-platform |
Available in | Haskell |
Type | Web framework |
License | MIT License |
Website | <strong%20class= "error"><span%20class="scribunto-error"%20id="mw-scribunto-error-2">Lua%20error%20in%20Module:Wd%20at%20line%20405:%20invalid%20escape%20sequence%20near%20'"^'. http://<strong%20class="error"><span%20class="scribunto-error"%20id="mw-scribunto-error-2">Lua%20error%20in%20Module:Wd%20at%20line%20405:%20invalid%20escape%20sequence%20near%20'"^'.Lua error in Module:EditAtWikidata at line 29: attempt to index field 'wikibase' (a nil value). |
Yesod (IPA: [je'sod]; Hebrew: <templatestyles src="Script/styles_hebrew.css" />יְסוֺד, "Foundation") is a free and open-source web framework based on Haskell for productive development of type-safe, REST model based (where URLs identify resources, and HTTP methods identify transitions), high performance web applications, developed by Michael Snoyman et al.
Yesod is based on templates, to generate instances for classes, entities, and dynamic content process functions, making use of Haskell compiled templates called QuasiQuotes,[3] that admits code expression interpolations in web-like language snippets, making it fully type-checked at compile-time.[4]
Contents
MVC architecture
<templatestyles src="Module:Hatnote/styles.css"></templatestyles>
Controller
Server interface
Yesod uses a Web application interface API,[5] abbrev. WAI, to isolate servlets, aka web apps., from servers, with handlers for the server protocols CGI,[6] FastCGI,[7] SCGI,[8] Warp,[9] Launch (open as local URL to the default browser, closing the server when the window is closed),[10]
The foundation type
See ref.[11] 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 T.H. primitives to generate the code[12] 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.
Skeleton app. extracted from next section, "Hello world" example:
data MyApp = MyApp
instance Yesod MyApp
-- mkYesod generates instances of ParseRoute, RenderRoute and YesodDispatch classes for MyApp
-- with RenderRoute associated data-type "Route t" (see yesodweb "Basics" ref.)
mkYesod "MyApp" [parseRoutes|
/ HomeR GET
|]
-- handler that matches the dispatch function name and type, generated by mkYesod
-- the handler may return one or more content-type representations,
-- to be chosen (with chooseRep from class HasReps)
-- upon http headers' preferred content-type list
getHomeR :: HasReps t => Handler t
getHomeR = defaultLayout $ setTitle "Yesod example"
-- there are ''run'' function variants for different WAI handlers
main = toWaiApp MyApp >>= run -- WAI CGI-handler ''run''
WAI CGI Hello World
See ref.[11]
{- 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)
data MyApp = MyApp
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|
<!-- only the tag at the beginning of the line will be automatically closed -->
<!-- '.' prefix in tags introduces a class attribute, '#' introduces an id one -->
<!-- interpolation of haskell expressions follow the #{expr} syntax -->
<p>Hello World! There are <span .box>#{length params} parameters</span>:
$if (not . null) params
<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
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.[13][14] 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.
-- 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 parameter
-- the handlers types will have to be parameterized with it
/article/#ArticleId ArticleR GET PUT
|]
-- There are extra route entry syntaxes for subsites and ''multi-piece'' resource paths.
Applying the previous template generates the following route constructors:
-- interpolation of routes in templates follow the @{route_expr} syntax
data Route MyApp =
HomeR -- referenced in templates as: @{HomeR}
| BlogR -- in templates: @{BlogR}
| ArticleR ArticleId -- in templates: @{ArticleR myArticleId}
- Handlers
- For every 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:
-- 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.[13]
Authentication and authorization
See ref.[15] Authentication plugins: OpenId, BrowserId, Email, GoogleEmail, HashDB, RpxNow.[16]
- Redirection after authentication.[17]
Sessions
See ref.[18] Session back-ends: ClientSession.[19]
- >> 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
Subsites
- >> A subsite is a collection of routes and their handlers that can be easily inserted into a master site.[20]
Built-in subsites: Static,[22][23] Auth[24]
Subsite Static
For every file in the "static" folder, a symbol with type (Route Static) is generated for reference, by means of a compile time splice call in the scaffold module StaticFiles.hs, that replaces non-identifier characters "/-." by underscores:[25]
-- referring to the file "static/css/hk-kate.css"
addStylesheet $ StaticR css_hk_kate_css
After adding static files, regenerate (::Route Static) symbols at the next recompilation, just updating the StaticFiles.hs date:
touch Settings/StaticFiles.hs
View
The Handler monad returns content in one or more of several formats as components of types that implement the HasReps class[26] {RepHtml, RepJson, RepXml, RepPlain, the dual RepHtmlJson, a pair or list of pairs [(ContentType, Content)], ..}.[27][28] Json examples:[29][30][31]
The HasReps default implementation of chooseRep chooses the document representation to be returned according to the preferred content-type list of the client accept header.[26]
Widgets[32] are HTML DOM code snippets made by specific commands (e.g. setTitle) or from templates of structure (html) / behaviour (javascript) / style (css), whose types instantiate the classes ToWidget, ToWidgetHead or ToWidgetBody.
A Widget monad,[33] based on a Writer[34] one and argument to defaultLayout, facilitate to piece the widgets together.
Template interpolation - Shakespearean templates
See ref.[35] These are content view templates that follow a common substitution pattern of code expressions within curly brackets with different character prefix to refer to
- template expressions with
^{...}
- other templates of the same type as
^{template params}
, - route expressions with
@{...}
- safe (typed) urls as
@{HomeR}
, - message expressions with
_{...}
- i18n message rendering as
_{MsgMessage params}
- other Haskell expressions with
#{...}
- haskell expression rendering as
#{haskell_expression}
which type must be convertible
-
- in case of hamlet html templates, the expression type must be an instance of Text.Blaze.ToMarkup[36]
- in case of css templates, the expression type must be an instance of Text.Cassius.ToCss[37]
- in case of javascript templates, the expression type must be an instance of Text.Julius.ToJavascript [38]
- in case of i18n localizable msg expressions _{msgExpr} in hamlet templates, the expression type must be an instance of Text.Shakespeare.I18N.ToMessage [39]
- in case of plain text templates, the expression type must be an instance of Text.Shakespeare.Text.ToText [40]
Using non-English text in expressions requires use of the Unicode-aware type Text, since GHC's show for the type String renders non-ASCII characters as escaped numerical codes.
- external file templates: Template content can be loaded from external files using compile time splice calls as $(expr).[41]
- reload mode for external files: See doc.[35]
Localizable (i18n) messages
See ref.[42] For every supported language ISO name there should be a file in the messages subfolder as <iso-language>.msg with entries like
ArticleUnexistant param@Int64: unexistant article #{param}
For each entry in en.msg a message constructor is generated, prefixing the message name by "Msg", so the example msg. can be referred as
-- in code myMsg = MsgArticleUnexistant myArticleId -- in templates _{MsgArticleUnexistant myArticleId}
HTML-like templates
- the hamlet quasiquoter (a parser to compile-time Template Haskell code)[3][43] specified in the T.H. Oxford brackets syntax
[qq| ... |]
introduces an indentation based structured html template with '$' prefixed lines of logic statements (See doc.[35]).[44] Automatic closing tags are generated only for the tag at line start position. - the whamlet quasiquoter returns a Widget expression. (saves toWidget before [hamlet|..|]).
toWidget [hamlet| $doctype 5 <html> <head> <title>#{pageTitle} - My Site <link rel=stylesheet href=@{Stylesheet_route}> <body> <div> ^{headerTemplate} <div> <p><span style="font-weight:bold;">_{MsgArticleListTitle}</span> $if null articles <p>_{MsgEmptyList} $else <ul> $forall art <- articles <li>#{articleNumber art} .- #{articleTitle art} <div> ^{footerHamletTemplate} |]
JavaScript templates
- the julius quasiquoter: introduces a javascript template.[45] Javascript variants CoffeeScript and Roy-language[46] have also specific quasiquoters.[3][45]
toWidgetHead [julius| var myfunc = function(){document.location = "@{SomeRouteR}";} ^{extraJuliusTemplate} |]
CSS-like templates
- the cassius quasiquoter: introduces a css template with indentation based structuring.[47]
toWidget [cassius| .box border: 1px solid #{myColor} background-image: url(@{MyImageR}) ^{extraCassiusTemplate} |]
- the lucius quasiquoter: introduces a css template with standard syntax plus shakespeare-template style substitutions.[47]
toWidgetHead [lucius| .box { border: 1px solid #{myColor} ; background-image: url(@{MyImageR}) ; } ^{extraLuciusTemplate} |]
Plain text templates
- the lt (lazy text, same as stext) and st (strict text) quasiquoters: they introduce Text templates, for e-mail or text/plain http content type.[48]
[lt| Mr./Mrs. #{fullName} ... |]
Specific views
- Search engines XML Sitemaps,[49] where sitemap returns an XML Sitemap as http response, with the routes we want the search engines to crawl, and attributes to instruct the crawler, from a provided list of SitemapUrl records.
- Navigation Breadcrumbs.[50] You have to provide a YesodBreadcrumbs instance for the site where the generator function breadcrumb should return a title and parent route for each one. Then, the query function breadcrumbs will return the present route title and ancestors' (route, title) pairs.
- Web feed views (RSS / Atom).[51] You have handlers that return RepRss, RepAtom, or dual RepAtomRss content (to be selected on accept headers' preferred content-type list) from a given Feed structure.
Model
Using in-memory mutable data (in the foundation datatype)
E.g. a visitor count. See ref.[52]
Persistent
- persistent is the name of the database access layer with templates for generating types for entities and keys as well as schema initialization.[53][54]
There is first class support for PostgreSQL, SQLite, MongoDB, CouchDB and MySQL, with experimental support for Redis.[53]
- automatic table creation, schema update and table migration
- Modifications of the entities template produces an schema update with automatic table creation, and migration for the DBMS's that support "ALTER TABLE" SQL commands in a migrateAll procedure, generated from the template content. See "Migrations" in ref.[53] to look for migration aware DBMS.
share [mkPersist sqlSettings, mkMigrate "migrateAll" -- generates the migration procedure with the specified name ] [persist| User -- table name and entity record type -- implicit autoincrement column "id" as primary key, -- typed UserId (Id suffix to rec. type) ident Text -- refers to db. table column "ident"; -- generates a record field prefixing the table name as "userIdent" password Text Maybe -- Maybe indicates Nullable field UniqueUser ident -- unique constraint with space sep. field sequence Email email Text user UserId -- foreign key verkey Text Maybe -- When adding new columns, in case the table is not empty -- the DB system requires a default new column value -- for existing table rows, for the migration to succeed. -- A "default constraint" (with sql-level type) is required newlyAddedColumn Text "default='sometext'::character varying" UniqueEmail email |]
- Esqueleto: is a haskell combinators layer to generate correct relational queries to persistent.[55]
Example for persistent rawSQL and Esqueleto queries.[56]
- Default column values for added columns in automatic migrations.[57]
Forms
See ref.[58] There are Applicative,[59] Monadic and Input (non rendering, input only) kinds of forms.
Field definitions have a fieldParse component and a fieldView one.[60]
- the function runForm{Post|Get} runs the field parsers against the form field inputs and generates a (FormResult, Widget) pair from the views with param. values as defaults,
- while generateForm{Post|Get} ignores the web inputs and generates a blank form widget.[61]
The magic is in the Applicative and Functor instances of the data type FormResult, where (<*>) collects the error messages for the case of FormFailure [textErrMsg]
result values[62]
Monadic forms permit free form layout and better treatment of hiddenField members.[58]
A sample of an Applicative[59] form:
{- A simple entity
[persist|
Person
name Text
age Int
likings Text Maybe -- Maybe: nullable field
|]
-}
-- ''persist'' equivalent entity record for Person
data Person = Person {personName :: Text, personAge :: Int, personLikings :: Maybe Text}
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: languages to use in renderMessage (return from handler's languages)
-- 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 nullable entity fields (tagged Maybe),
''areq'' (required fld AForm comp.) for non-nullable entity ones
-}
personForm master languages mbPersonDefaults = renderTable $
Person <$> areq textField fldSettingsName mbNameDefault
<*> areq customizedPersonAgeField fldSettingsAge mbAgeDefault
<*> aopt textareaField fldSettingsLikings mbLikingsDefault
where
mbNameDefault = fmap personName mbPersonDefaults
mbAgeDefault = fmap personAge mbPersonDefaults
mbLikingsDefault = fmap personLikings mbPersonDefaults
fldSettingsName = (fieldSettingsLabel MsgName) {fsAttrs = [("maxlength","20")]}
fldSettingsAge = fieldSettingsLabel MsgAge
fldSettingsLikings = (fieldSettingsLabel MsgLikings) {fsAttrs = [("cols","40"),("rows","10")]}
customizedPersonAgeField = check validateAge intField
validateAge y
| y < 18 = Left $ renderMessage master languages MsgUnderAge
| otherwise = Right y
See renderMessage signature.[63]
Other protocols
The following packages are part of the yesod-platform:[2]
- email-validate: Validating an email address.[64]
- mime-mail: Compose and send MIME email messages.[65]
- Useful glue functions between the fb library and Yesod.[66]
Development cycle
Getting started
Create a sandboxed cabal-dev repository for yesod and yesod projects. Yesod 1.2 release notes.[67]
mkdir yesod && cd yesod
cabal-dev install yesod-platform yesod-bin
export PATH=$PWD/cabal-dev/bin:$PATH
|
Scaffolding
The console command yesod init
after asking for details, generates a starting scaffold application in a subfolder with the project name. Then cd to the project folder.
Create a link to the parent dir. cabal-dev folder (if you want to use the same library repository) and build the project
yesod init
...
cd myproject
# next, a link to reuse the cabal-dev library repository
ln -s ../cabal-dev cabal-dev
cabal-dev install
Configuration environments
They are predefined as {Development, Testing, Staging, Production} and each one heads a chapter in the YAML formatted configuration files ("config/" based settings.yml and <yourDBMS>.yml), with configuration attributes for the purpose stated by the environment name.
For each one, several attributes can be specified, e.g.: base url as approot, port, and other configurations, as well as different database names, its connection parameters, and other db parameters adjustable for the purposes of the specific environment.
You have to specify the name of the environment as argument to your project task. Example:
# launch the server with the ''Testing'' configuration environment.
# cabal-dev installs executables at ./cabal-dev/bin
myproject Testing
Developing
The console command yesod --dev devel --port 3000
(the --dev
flag is to look for the cabal-dev library repository) compiles the project in the current folder and starts it as a web server, but also listens for file modifications in the project directory tree, and recompiles and restarts it every time you save a yesod component, whether haskell code or template file.
Adding static files requires to unix touch the Settings/StaticFiles.hs module to generate the correspondent route symbols as explained.
Logging for debug
See ref.[68] The yesod scaffold uses "wai-extra" Network.Wai.Middleware.RequestLogger[69] for request logging, although there are alternatives.[68]
The package "monad-logger"[70] brings T.H. generated logging functions with automatic inclusion of line location, which can be used in the monads Handler, Widget, PersistQuery, PersistStore, .. which are instances of MonadLogger.[71]
The following "monad-logger" calls are also available from "yesod-core":
$(logDebug) "This is a debug log message"
$(logInfo), $(logWarn), $(logError) and $(logOther) are also available
You can set per case log default by overriding the shouldLog method of the Yesod class instance for the site.[72]
Deploying
See ref.[73]
Keter: A web app server monitor and reverse proxy server
Keter is a process as a service that handles deployment and restart of web app servers, and, per web app, database creation for PostgreSQL.
The console command yesod --dev keter
packs the web app. as a keter bundle for uploading to a keter folder named "incoming".
Keter monitors the "incoming" folder and unpacks the app. to a temporary one, then assigns the web app a port to listen to, and starts it.
Initially it worked with Nginx as reverse proxy (keter version 0.1*), adding virtual server entries to its configuration and making Nginx reload it, but now Keter itself provides its own reverse proxy functionality, removing Nginx dependency and acting as the main web server.[76]
Old documentation (Nginx based).[77][78]
Integration with JavaScript generated from functional languages
from true GHC Haskell with hacked compilers having JavaScript backends
Haste[82][83][84] and GhcJs[85] offer alternatives that generate Javascript from the STG[86] output phase of GHC admitting haskell code compilable with GHC. While GhcJs is more feature complete (concurrency, etc.), it requires and generates much more code than Haste.[81][87][88]
Haste sample.[82] Other examples at the ref.[89]
{- file Haste_example.hs -}
import Haste
main = do
Just inp <- elemById "user_input"
Just outp <- elemById "script_output"
onEvent inp OnKeyUp $ \_ -> do
text <- getProp inp "value"
setProp outp "innerHTML" text
Equivalent with reactive data-flow style.
import Haste
import Haste.Reactive
main = do
elemProp "script_output.innerHTML" << result
where
result :: Signal String
result = "user_input" `valueAt` OnKeyUp
Compiling:
hastec Haste_example.hs # generates .js
Yesod widgets code:
addScript $ StaticR js_Haste_example_js -- for static/js/Haste_example.js
toWidget [hamlet|
<section>
<p> Type here:
<input #user_input type="text" maxlength="20">
<p> Script test result:
<span #script_output>
|]
Elm - reactive composition of web user interfaces
Elm is a novel functional reactive programming language (no callbacks) with Haskell like syntax (type annotations included)[90] that compiles to Javascript, but with strict evaluation (top down evaluation, no where clauses), with simplified syntax (single pattern definitions, no guards), propagating events and changes from event sources through the dependency graph.
Elm role is not for adding JavaScript behaviour to existing elements, but to build up a variable html structure that recomposes itself in reaction to event streams.
Elm is more oriented to graphics than to text flows. Since everything can be scaled, moved and rotated,[91] it needs precise knowledge of dimensions, so it doesn't use html lists, paragraphs and tables, whose positioning rely on the browser. With Elm you put text or graphics in rectangular Elements that can be stacked from lists (with Graphics.Element.flow direction elements) along horizontal, vertical or layer dimensions.
It unifies events and behaviours (varying values) in an effect named Signal handled as a Haskell applicative functor.[92] You define value transformation functions and then you lift these functions to apply them to the Signal effect.
Elm | Haskell |
---|---|
(:) | (::) |
(::) | (:) |
Function ops. | |
(<|) | ($) |
(|>) | flip ($) |
always | const |
(.), id, flip, curry, uncurry |
|
Applicative ops. | |
constant | pure |
lift<N> | liftA<N> |
(<~) | (<$>) |
(~) | (<*>) |
Types | |
String (javascript string) (with specific library) |
not a [Char] |
Float (javascript number) |
Double |
Bool, Char, Int | |
Tuples (,) (, ) | |
extensible records[94] type Point = { x:Float, y:Float } type Named a = { a | name:String } |
remembers Hugs#Extensible records |
Algebraic Data Types (data ..) | |
type aliases | |
Containers | |
Dict | Map |
Maybe, Either, List, Set |
Elm calculator as example:
-- elm v. 0.12 -- THIS IS NOT HASKELL BUT ELM
-- the dot close after lowercase lead names is parsed as ''record.field'' syntax
import Graphics.Input as I
data Keys = Digit Int | Plus | Total | ClearDigit | ClearAcc
-- multi-control input controller
keysController : I.Input Keys
keysController = I.input ClearAcc
calcInterface : Element
calcInterface =
flow down [
flow right [ I.button keysController.handle (Digit 1) "1"
, I.button keysController.handle (Digit 2) "2"
, I.button keysController.handle (Digit 3) "3"
, I.button keysController.handle (Digit 0) "0"
],
flow right [ I.button keysController.handle Plus "+"
, I.button keysController.handle Total "="
, I.button keysController.handle ClearDigit "C"
, I.button keysController.handle ClearAcc "AC"
]
]
calc : Keys -> (Int,Int) -> (Int,Int)
calc k state =
let (disp, acc) = state -- (display, accumulator)
in case k of
ClearAcc -> (0,0)
ClearDigit -> (disp `div` 10, acc)
Digit n -> (disp * 10 + n, acc)
Plus -> (0, acc + disp) -- post-input addition
Total -> (acc, acc) -- retrieve accumulator
-- using past-dependent fold function Signal.foldp
-- Signal.foldp : (a -> b -> b) -> b -> Signal a -> Signal b
sigState = foldp calc (0,0) keysController.signal
sigDisplay = lift (asText . fst) sigState
main = lift (below calcInterface) sigDisplay
Instead of mangling the DOM tree nodes, Elm builds Html structure by composing Elements (html block elements) (with flow, container, layers or empty from Graphics.Element),[95] from
- appendable Text (html inline elements) (library Text),[95]
- stackable graphics (type named Form) as a collage (html canvas) (library Graphics.Collage),[95]
- composable input elements (html form elements) (library Graphics.Input),[95] image elements (library Graphics.Element) and container elements
Conditional structures may switch to Element.empty.
Styling. Elm needs to know precisely the width and height of elements, so styling is done through Elm styling functions instead of leaving it to external CSS. It lacks margins and paddings for now. You make element (html block) styles as a composition of (Element -> Element) styling functions,[96] and text (html inline) styles in a similar way (Text -> Text).[97]
Instead of web forms, you wrap the status signals of the input elements in a Request signal as input to Elm's Ajax Http.send[95]
The main function effects-sequencing pattern is Applicative (there are no do blocks).[90] You can add state effects through past-dependent folds (Signal.foldp, Signal.count)[95] or state Automatons (an arrow instance)[98] There is a third-party library repository[99]
Elm requires
- an empty div html element (with an ID), where to hook the varying content result,
- and a client JavaScript snippet to start the compiled script as shown in Elm#Parameterizing an Elm script
Styling is not as good as CSS, url encoding requires a third party library, but the language works fairly well. It seems more geared to build game UI's but with some extra work can do ajax web forms as well.[100]
Disadvantages:
- No Html lists, so no list items, you have to paint the bullets yourself.
- Styling is rudimentary. No margins, no paddings, no paragraphs, no CSS.
- interfacing with JavaScript functions is not allowed (blamed impure).[101] Only values or signal streams are admitted.[102]
Advantages:
- Html built by composition in reaction to Signals.
- Good to make an animated collage, as this is the name of its graphics library.
Using Elm with Yesod in a remote echo example
- XmlHttpRequest (in Elm's Http.send) requires server side authorization headers
"Access-Control-Allow-Origin: *"
for Http GET and extra"Access-Control-Allow-Methods: GET, POST"
for Http POST. Url encoding of requests is implemented in a third-party library named elm_CodecURI.[99]
-- Elm v.0.12, strict evaluation, then reactive propagation of events and value signals
-- no "where" clauses, only "let" .. "in" local declarations
-- single pattern function definitions, no guards
module MyElm where
import Graphics.Input as I -- qualified import
import Graphics.Input.Field as F -- qualified import
import Http (..)
import String as S
inpFld1 : I.Input F.Content
inpFld1 = I.input F.noContent
sigElemFld1 = F.field F.defaultStyle inpFld1.handle id "enter numeric" <~ inpFld1.signal
url = "http://localhost:3000/echo-status?status="
-- varying url signal by prepending previous url prefix to inpFld1 content signal
urlSignal : Signal String
urlSignal = lift (\content -> url ++ content.string) inpFld1.signal
-- local echo in plainText element
sigElemFld2 = lift plainText urlSignal
myGet : String -> Request String
myGet url = let body = ""
headers = []
in request "GET" url body headers
remoteSignal = send <| lift myGet urlSignal
showResult : Response String -> String
showResult result = case result of
Waiting -> "waiting"
Success str -> "success: " ++ S.trim str
Failure iStatus msg -> "failure! " ++ S.show iStatus ++ ": " ++ msg
--remote echo from localhost server
sigElemFld3 = lift (plainText . showResult) remoteSignal
-- combine : [Signal a] -> Signal [a]
main = lift (flow down) <| combine [sigElemFld1, sigElemFld2, sigElemFld3]
Install Elm and compile:
cabal install elm elm-get elm-repl
# elm compiles by default to folder "build"
elm --make MyElm.elm
browser build/MyElm.html
# for inclusion with yesod
cd myproject
# copy elm-runtime.js
cp ~/.cabal/share/i386-linux-ghc-N.N.N/Elm-N.N.N/elm-runtime.js static/js/
elm --make --build-dir=static/js --only-js MyElm.elm
Yesod handler to return echo of "status" parameter.
-- Yesod handler
{-# LANGUAGE OverloadedStrings #-}
module Handler.EchoStatus where
import Import
import Text.Shakespeare.Text (lt)
import Data.Maybe
getEchoStatusR :: Handler RepPlain
getEchoStatusR = do
mbStatus ← lookupGetParam "status"
let clientStatus = fromMaybe "" mbStatus
addHeader "Access-Control-Allow-Origin" "*" -- XmlHttpRequest authorization
return $ (RepPlain . toContent) [lt|#{clientStatus}|]
Templates for inclusion of Elm functionality
addScript $ StaticR js_elm_runtime_js -- for static/js/elm-runtime.js
addScript $ StaticR js_MyElm_js -- for static/js/MyElm.js
toWidget [hamlet|
<section>
<!-- div for elm container must be empty -->
<div #myElmContainerId>
|]
toWidgetBody [julius|
// elm script arguments, corresponding elm source production ::= "port" identifier ":" type
var myPorts = {arg1: "@{specialRoute}", // for port arg1 : String
arg2: #{doubleExpression}, // for port arg2 : Float // ''Double'' not in Elm
arg3: #{intListExpression} // for port arg3 : [Int]
} ;
var myContainer = document.getElementById('myElmContainerId') ;
var myModule = Elm.MyElm ;
Elm.embed(myModule, myContainer, myPorts) ;
|]
References
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ 2.0 2.1 Lua error in package.lua at line 80: module 'strict' not found.
- ↑ 3.0 3.1 3.2 Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ 11.0 11.1 Lua error in package.lua at line 80: module 'strict' not found.
- ↑ The mkYesod code
- ↑ 13.0 13.1 Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ 20.0 20.1 Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.[]
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ 26.0 26.1 Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ 35.0 35.1 35.2 Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ 45.0 45.1 Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ 47.0 47.1 Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ 53.0 53.1 53.2 Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ 58.0 58.1 Lua error in package.lua at line 80: module 'strict' not found.
- ↑ 59.0 59.1 Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Yesod 1.2 released!
- ↑ 68.0 68.1 Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ 81.0 81.1 Lua error in package.lua at line 80: module 'strict' not found.
- ↑ 82.0 82.1 Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ 90.0 90.1 Elm Syntax
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Elm predefined functions
- ↑ Elm Records
- ↑ 95.0 95.1 95.2 95.3 95.4 95.5 Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Automaton library
- ↑ 99.0 99.1 Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
- ↑ Lua error in package.lua at line 80: module 'strict' not found.
External links
- No URL found. Please specify a URL here or add one to Wikidata.
- Presentations: InfoQ, Haskell eXchange 2012
- Slides: A.C.M. at Johns Hopkins Univ. - ReST-ful Websites with Yesod
- ScreenCast: Yesod 1.0 at Vimeo
- O'Reilly ebook - Developing Web Applications with Haskell and Yesod - Safety-Driven Web Development
- Q&A: StackOverflow.com - Yesod tagged Q&A
Blog tutorials
- FPComplete.com - My First Web Site, Playing with Routes and Links
- Yesod for newbies
- hamberg.no - handlerToIO: use forkIO in Yesod handlers
Comparisons
- HaskellWiki - Haskell web frameworks
- A Hopefully Fair and Useful Comparison of Haskell Web Frameworks
- Univ. of Kent - Comparing Dynamic and Static Language Approaches to Web Frameworks - Yesod vs Ruby on Rails
Other languages
- Haskell Biblio. - Yesod (Spanish) Univ. of Cadiz