2011-12-29 16:05:05 +00:00
<?xml version="1.0" encoding="utf-8"?>
< !DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
< html xmlns = "http://www.w3.org/1999/xhtml" lang = "fr" xml:lang = "fr" >
< head >
< meta http-equiv = "Content-Type" content = "text/html; charset=UTF-8" / >
< meta name = "keywords" content = "yesod, haskell, programming, web" >
< link rel = "shortcut icon" type = "image/x-icon" href = "/Scratch/img/favicon.ico" / >
< link rel = "stylesheet" type = "text/css" href = "/Scratch/assets/css/main.css" / >
< link rel = "stylesheet" type = "text/css" href = "/Scratch/css/twilight.css" / >
< link rel = "stylesheet" type = "text/css" href = "/Scratch/css/idc.css" / >
< link rel = "alternate" type = "application/rss+xml" title = "RSS" href = "http://feeds.feedburner.com/yannespositocomen" / >
2012-01-11 20:40:22 +00:00
< link rel = "alternate" lang = "fr" xml:lang = "fr" title = "Site en Haskell" type = "text/html" hreflang = "fr" href = "/Scratch/fr/blog/Yesod-tutorial-for-newbies/" / >
< link rel = "alternate" lang = "en" xml:lang = "en" title = "Haskell web programming" type = "text/html" hreflang = "en" href = "/Scratch/en/blog/Yesod-tutorial-for-newbies/" / >
2011-12-29 16:05:05 +00:00
< script type = "text/javascript" src = "/Scratch/js/jquery-1.3.1.min.js" > < / script >
< script type = "text/javascript" src = "/Scratch/js/jquery.cookie.js" > < / script >
< script type = "text/javascript" src = "/Scratch/js/index.js" > < / script >
<!-- [if lt IE 9]>
< script src = "http://ie7-js.googlecode.com/svn/version/2.1(beta4)/IE9.js" > < / script >
<![endif]-->
2012-01-11 20:40:22 +00:00
< title > Haskell web programming< / title >
2011-12-29 16:05:05 +00:00
< / head >
< body lang = "en" class = "article" >
< script type = "text/javascript" > / / < ! [ C D A T A [
document.write('< div id = "blackpage" > < img src = "/Scratch/img/loading.gif" alt = "loading..." / > < / div > ');
// ]]>
< / script >
< div id = "content" >
< div id = "choix" >
< div class = "return" > < a href = "#entete" > ↓ Menu ↓ < / a > < / div >
< div id = "choixlang" >
< a href = "/Scratch/fr/blog/Yesod-tutorial-for-newbies/" onclick = "setLanguage('fr')" > en Français< / a >
< / div >
< div class = "flush" > < / div >
< / div >
< div id = "titre" >
< h1 >
2012-01-11 20:40:22 +00:00
Haskell web programming
2011-12-29 16:05:05 +00:00
< / h1 >
2012-01-11 20:40:22 +00:00
< h2 >
A Yesod tutorial
< / h2 >
2011-12-29 16:05:05 +00:00
< / div >
< div class = "flush" > < / div >
< div class = "flush" > < / div >
< div id = "afterheader" >
< div class = "corps" >
2012-01-11 20:40:22 +00:00
< p > < img alt = "Neo Flying at warp speed" src = "/Scratch/img/blog/Yesod-tutorial-for-newbies/flying_neo.jpg" / > < / p >
2011-12-29 16:05:05 +00:00
< div class = "intro" >
2012-01-18 13:28:01 +00:00
< p > < span class = "sc" > < abbr title = "Too long; didn't read" > tl;dr< / abbr > : < / span > A simple yesod tutorial.
Yesod is an Haskell web framework.
2012-01-20 21:36:36 +00:00
You shouldn’ t need to know Haskell. < / p >
2011-12-29 16:05:05 +00:00
< blockquote >
2012-01-11 20:40:22 +00:00
< center > < span class = "sc" > < b > Table of content< / b > < / span > < / center >
2011-12-29 16:05:05 +00:00
< ul id = "markdown-toc" >
2012-01-11 20:40:22 +00:00
< li > < a href = "#before-the-real-start" > Before the real start< / a > < ul >
< li > < a href = "#install" > Install< / a > < / li >
< li > < a href = "#initialize" > Initialize< / a > < / li >
< li > < a href = "#configure-git" > Configure git< / a > < / li >
2012-01-18 13:28:01 +00:00
< li > < a href = "#some-last-minute-words" > Some last minute words< / a > < / li >
2012-01-02 14:39:00 +00:00
< / ul >
< / li >
2012-01-11 20:40:22 +00:00
< li > < a href = "#echo" > Echo< / a > < ul >
< li > < a href = "#bulletproof" > Bulletproof?< / a > < / li >
2012-01-18 13:28:01 +00:00
< li > < a href = "#cleaning-up" > Cleaning up< / a > < ul >
< li > < a href = "#span-classschtml5span-boilerplate-and-a-better-span-classsccssspan" > < span class = "sc" > html5< / span > boilerplate and a better < span class = "sc" > css< / span > < / a > < / li >
< li > < a href = "#separate-handlers" > Separate Handlers< / a > < / li >
< li > < a href = "#datatext" > < code > Data.Text< / code > < / a > < / li >
< li > < a href = "#use-templates" > Use templates< / a > < / li >
< / ul >
< / li >
2011-12-30 16:14:57 +00:00
< / ul >
< / li >
2012-01-18 13:28:01 +00:00
< li > < a href = "#mirror" > Mirror< / a > < / li >
< li > < a href = "#a-blog" > A Blog< / a > < / li >
2012-01-18 14:51:20 +00:00
< li > < a href = "#conclusion" > Conclusion< / a > < / li >
2011-12-29 16:05:05 +00:00
< / ul >
< / blockquote >
< / div >
2012-01-18 13:28:01 +00:00
< p > Why Haskell?< / p >
2012-01-11 20:40:22 +00:00
< p > < img alt = "Impressive Haskell Benchmark" src = "/Scratch/img/blog/Yesod-tutorial-for-newbies/haskell-benchmark.png" / > < / p >
2012-01-18 13:28:01 +00:00
< p > Its efficiency (see < a href = "http://snapframework.com/blog/2010/11/17/snap-0.3-benchmarks" > Snap Benchmark< / a > < em class = "pala" > & < / em >
< a href = "http://www.yesodweb.com/blog/2011/03/preliminary-warp-cross-language-benchmarks" > Warp Benchmark< / a > < sup id = "fnref:benchmarkdigression" > < a href = "#fn:benchmarkdigression" rel = "footnote" > 1< / a > < / sup > ).
Haskell is an order of magnitude faster than interpreted languages
like < a href = "http://shootout.alioth.debian.org/u64q/benchmark.php?test=all&lang=ghc&lang2=yarv" > Ruby< / a > and < a href = "http://shootout.alioth.debian.org/u64q/benchmark.php?test=all&lang=ghc&lang2=python3" > Python< / a > < sup id = "fnref:speeddigression" > < a href = "#fn:speeddigression" rel = "footnote" > 2< / a > < / sup > .< / p >
2012-01-11 20:40:22 +00:00
2012-01-18 13:28:01 +00:00
< p > Haskell is a high level language and make it harder to shoot you in the foot
2012-01-18 14:51:20 +00:00
than < code > C< / code > , < code > C++< / code > or < code > Java< / code > for example.
2012-01-18 13:28:01 +00:00
One of the best property of Haskell being:< / p >
2012-01-11 20:40:22 +00:00
< blockquote >
2012-01-20 21:36:36 +00:00
< p > “ If your program compile it will be
very close to what the programmer intended” .< / p >
2012-01-11 20:40:22 +00:00
< / blockquote >
2012-01-18 13:28:01 +00:00
< p > Haskell web frameworks handle parallel tasks perfectly.
For example even better than node.js< sup id = "fnref:nodejstroll" > < a href = "#fn:nodejstroll" rel = "footnote" > 3< / a > < / sup > .< / p >
2012-01-11 20:40:22 +00:00
2012-01-18 14:51:20 +00:00
< p > < img alt = "Thousands of Agent Smith" src = "/Scratch/img/blog/Yesod-tutorial-for-newbies/thousands_smiths.jpg" class = "left" / > < / p >
2012-01-18 13:28:01 +00:00
2012-01-20 11:20:07 +00:00
< p > From the pure technical point of view,
2012-01-18 14:51:20 +00:00
Haskell seems to be the perfect web development tool.
2012-01-20 21:36:36 +00:00
Weaknesses of Haskell certainly won’ t be technical:< / p >
2011-12-29 16:05:05 +00:00
2012-01-02 14:39:00 +00:00
< ul >
2012-01-11 20:40:22 +00:00
< li > Hard to grasp Haskell< / li >
< li > Hard to find a Haskell programmer< / li >
2012-01-18 14:51:20 +00:00
< li > The Haskell community is smaller than the community for < code > /.*/< / code > < / li >
2012-01-11 20:40:22 +00:00
< li > There is no < a href = "http://heroku.com" > heroku< / a > for Haskell (even if < a href = "http://www.yesodweb.com/blog/2011/07/haskell-on-heroku" > Greg Weber did it< / a > , it was more a workaround).< / li >
2012-01-02 14:39:00 +00:00
< / ul >
2011-12-29 16:05:05 +00:00
2012-01-20 21:36:36 +00:00
< p > I won’ t say these are not important drawbacks.
2012-01-18 14:51:20 +00:00
But, with Haskell your web application will have both properties
to absorb an impressive number of parallel request securely
and to adapt to change.< / p >
2012-01-02 14:39:00 +00:00
2012-01-18 14:51:20 +00:00
< p > Actually there are three main Haskell web frameworks:< / p >
2011-12-29 16:05:05 +00:00
< ol >
< li > < a href = "http://happstack.com" > Happstack< / a > < / li >
< li > < a href = "http://snapframework.com" > Snap< / a > < / li >
< li > < a href = "http://yesodweb.com" > Yesod< / a > < / li >
< / ol >
2012-01-20 21:36:36 +00:00
< p > I don’ t think there is a real winner between these three framework.
2012-01-02 14:39:00 +00:00
The choice I made for yesod is highly subjective.
2012-01-18 13:28:01 +00:00
I just lurked a bit and tried some tutorials.
2012-01-11 20:40:22 +00:00
I had the feeling yesod make a better job at helping newcomers.
2012-01-18 13:28:01 +00:00
Furthermore, apparently the yesod team seems the most active.
Of course I might be wrong since it is a matter of feeling.< / p >
2011-12-29 16:05:05 +00:00
2012-01-11 20:40:22 +00:00
< p > < img alt = "1. Draw some circles. 2. Draw the rest of the fucking owl" src = "/Scratch/img/blog/Yesod-tutorial-for-newbies/owl_draw.png" / > < / p >
2011-12-29 16:05:05 +00:00
2012-01-11 20:40:22 +00:00
< p > Why did I write this article?
2012-01-18 13:28:01 +00:00
The yesod documentation and particularly the book are excellent.
But I missed an intermediate tutorial.
2012-01-20 21:36:36 +00:00
This tutorial won’ t explain all details.
2012-01-18 13:28:01 +00:00
I tried to give a step by step of how to start from a five minute tutorial
to an almost production ready architecture.
Furthermore explaining something to others is a great way to learn.
2012-01-20 21:36:36 +00:00
If you are used to Haskell and Yesod, this tutorial won’ t learn you much.
2012-01-20 11:20:07 +00:00
If you are completely new to Haskell and Yesod it might hopefully helps you.
Also if you find yourself too confused by the syntax, it might helps to read this
< a href = "http://blog.ezyang.com/2011/11/how-to-read-haskell/" > article< / a > < / p >
2012-01-18 13:28:01 +00:00
2012-01-20 21:36:36 +00:00
< p > During this tutorial you’ ll install, initialize and configure your first yesod project.
2012-01-18 13:28:01 +00:00
Then there is a very minimal 5 minutes yesod tutorial to heat up and verify the awesomeness of yesod.
2012-01-20 21:36:36 +00:00
Then we will clean up the 5 minutes tutorial to use some “ best practices” .
2012-01-18 13:28:01 +00:00
Finally there will be a more standard real world example; a minimal blog system.< / p >
2012-01-11 20:40:22 +00:00
< h2 id = "before-the-real-start" > Before the real start< / h2 >
2011-12-29 16:05:05 +00:00
2012-01-11 20:40:22 +00:00
< h3 id = "install" > Install< / h3 >
2011-12-29 16:05:05 +00:00
2012-01-18 13:28:01 +00:00
< p > The recommended way to install < a href = "http://www.haskell.org" > Haskell< / a >
is to download the < a href = "http://www.haskell.org/platform" > Haskell Platform< / a > .< / p >
2011-12-29 16:05:05 +00:00
2012-01-18 13:28:01 +00:00
< p > Once done, you need to install yesod.
Open a terminal session and do:< / p >
2011-12-29 16:05:05 +00:00
< pre class = "twilight" >
2012-01-11 20:40:22 +00:00
< span class = "Keyword" > ~< / span > cabal update
< span class = "Keyword" > ~< / span > cabal install yesod cabal-dev
2011-12-29 16:05:05 +00:00
< / pre >
2012-01-18 13:28:01 +00:00
< p > There are few steps but it should take some time to finish.< / p >
2011-12-29 16:05:05 +00:00
2012-01-11 20:40:22 +00:00
< h3 id = "initialize" > Initialize< / h3 >
2011-12-29 16:05:05 +00:00
2012-01-18 13:28:01 +00:00
< p > You are now ready to initialize your first yesod project.
Open a terminal and type:< / p >
2011-12-29 16:05:05 +00:00
< pre class = "twilight" >
2012-01-11 20:40:22 +00:00
< span class = "Keyword" > ~< / span > yesod init
2011-12-29 16:05:05 +00:00
< / pre >
2012-01-18 13:28:01 +00:00
< p > Enter your name, choose < code > yosog< / code > for the project name and enter < code > Yosog< / code > for the name of the Foundation.
Finally choose < code > sqlite< / code > .
Now, start the development cycle:< / p >
2011-12-29 16:05:05 +00:00
< pre class = "twilight" >
2012-01-11 20:40:22 +00:00
< span class = "Keyword" > ~< / span > cd yosog
2012-01-18 13:28:01 +00:00
< span class = "Keyword" > ~< / span > cabal-dev install < span class = "Keyword" > & & < / span > yesod --dev devel
2011-12-29 16:05:05 +00:00
< / pre >
2012-01-18 13:28:01 +00:00
< p > This will compile the entire project. Be patient it could take a while the first time.
2012-01-02 14:39:00 +00:00
Once finished a server is launched and you could visit it by clicking this link:< / p >
2011-12-29 16:05:05 +00:00
< p > < a href = "http://localhost:3000" > < code > http://localhost:3000< / code > < / a > < / p >
2012-01-11 20:40:22 +00:00
< p > Congratulation! Yesod works!< / p >
2012-01-02 14:39:00 +00:00
< blockquote >
2012-01-11 20:40:22 +00:00
Note: if something is messed up use the following command line inside the project directory.
< pre class = "twilight" >
< span class = "Constant" > \r< / span > m -rf dist/* < span class = "Keyword" > ;< / span > cabal-dev install < span class = "Keyword" > & & < / span > yesod --dev devel
< / pre >
2012-01-02 14:39:00 +00:00
< / blockquote >
2012-01-18 13:28:01 +00:00
< p > Until the end of the tutorial, use another terminal and let this one open
in a corner to see what occurs.< / p >
2011-12-29 16:05:05 +00:00
2012-01-11 20:40:22 +00:00
< h3 id = "configure-git" > Configure git< / h3 >
2011-12-29 16:05:05 +00:00
2012-01-18 13:28:01 +00:00
< blockquote >
< p > Of course this step is not mandatory for the tutorial
but it is a good practice.< / p >
< / blockquote >
2011-12-29 16:05:05 +00:00
2012-01-02 14:39:00 +00:00
< p > Copy this < code > .gitignore< / code > file into the < code > yosog< / code > folder.< / p >
2011-12-29 16:05:05 +00:00
< div class = "code" > < div class = "file" > < a href = "/Scratch/en/blog/Yesod-tutorial-for-newbies/code/.gitignore" > ➥ .gitignore < / a > < / div > < div class = "withfile" >
< pre class = "twilight" >
cabal-dev
dist
.static-cache
static/tmp
*.sqlite3
< / pre >
< / div > < / div >
< p > Then initialize your git repository:< / p >
< pre class = "twilight" >
2012-01-11 20:40:22 +00:00
< span class = "Keyword" > ~< / span > git init .
< span class = "Keyword" > ~< / span > git add .
< span class = "Keyword" > ~< / span > git commit -a -m < span class = "String" > < span class = "String" > " < / span > Initial yesod commit< span class = "String" > " < / span > < / span >
2011-12-29 16:05:05 +00:00
< / pre >
2012-01-18 13:28:01 +00:00
< p > We are almost ready to start.< / p >
2011-12-29 16:05:05 +00:00
2012-01-18 13:28:01 +00:00
< h3 id = "some-last-minute-words" > Some last minute words< / h3 >
2011-12-29 16:05:05 +00:00
2012-01-18 13:28:01 +00:00
< p > Up until here, we have a directory containing a bunch of files
and a local web server listening the port 3000.
If we modify a file inside this directory, yesod should try
to recompile as fast as possible the site.
Instead of explaining the role of every file,
2012-01-20 21:36:36 +00:00
let’ s focus only on the important files/directories for this tutorial:< / p >
2011-12-29 16:05:05 +00:00
< ol >
< li > < code > config/routes< / code > < / li >
< li > < code > Handler/< / code > < / li >
< li > < code > templates/< / code > < / li >
< li > < code > config/models< / code > < / li >
< / ol >
< p > Obviously:< / p >
2012-01-11 20:40:22 +00:00
< table >
< tbody >
< tr >
< td > < code > config/routes< / code > < / td >
2012-01-20 21:36:36 +00:00
< td > is where you’ ll configure the map < span class = "sc" > url< / span > → Code.< / td >
2012-01-11 20:40:22 +00:00
< / tr >
< tr >
< td > < code > Handler/< / code > < / td >
2012-01-18 13:28:01 +00:00
< td > contains the files that will contain the code called when a < span class = "sc" > url< / span > is accessed.< / td >
2012-01-11 20:40:22 +00:00
< / tr >
< tr >
< td > < code > templates/< / code > < / td >
2012-01-18 13:28:01 +00:00
< td > contains < span class = "sc" > html< / span > , js and < span class = "sc" > css< / span > templates.< / td >
2012-01-11 20:40:22 +00:00
< / tr >
< tr >
< td > < code > config/models< / code > < / td >
2012-01-20 21:36:36 +00:00
< td > is where you’ ll configure the persistent objects (database tables).< / td >
2012-01-11 20:40:22 +00:00
< / tr >
< / tbody >
< / table >
2012-01-20 21:36:36 +00:00
< p > During this tutorial we’ ll modify other files as well,
but we won’ t explore them in detail.< / p >
2012-01-11 20:40:22 +00:00
2012-01-20 11:20:07 +00:00
< p > Also note, shell commands are executed in the root directory of your project instead specified otherwise.< / p >
2012-01-11 20:40:22 +00:00
2012-01-18 13:28:01 +00:00
< p > We are now ready to start!< / p >
2011-12-29 16:05:05 +00:00
2012-01-18 13:28:01 +00:00
< h2 id = "echo" > Echo< / h2 >
2011-12-29 16:05:05 +00:00
2012-01-18 13:28:01 +00:00
< p > To verify the quality of the security of the yesod framework,
2012-01-20 21:36:36 +00:00
let’ s make a minimal echo application.< / p >
2012-01-02 14:39:00 +00:00
2012-01-18 13:28:01 +00:00
< blockquote >
< p > Goal:< / p >
2011-12-29 16:05:05 +00:00
2012-01-20 21:36:36 +00:00
< p > Make a server that when accessed < code > /echo/[some text]< / code > should return a web page containing “ some text” inside an < code > h1< / code > bloc.< / p >
2012-01-18 13:28:01 +00:00
< / blockquote >
2011-12-29 16:05:05 +00:00
2012-01-18 13:28:01 +00:00
< p > In a first time, we must declare the < span class = "sc" > url< / span > of the form < code > /echo/...< / code > are meaningful.
2012-01-20 21:36:36 +00:00
Let’ s take a look at the file < code > config/routes< / code > :< / p >
2012-01-18 13:28:01 +00:00
< pre >
2011-12-29 16:05:05 +00:00
/static StaticR Static getStatic
/auth AuthR Auth getAuth
/favicon.ico FaviconR GET
/robots.txt RobotsR GET
/ RootR GET
< / pre >
< p > We want to add a route of the form < code > /echo/[anything]< / code > somehow and do some action with this.
2012-01-18 13:28:01 +00:00
Add the following:< / p >
2011-12-29 16:05:05 +00:00
< pre >
/echo/#String EchoR GET
< / pre >
2012-01-18 13:28:01 +00:00
< p > This line contains three elements: the < span class = "sc" > url< / span > pattern, a handler name, an < span class = "sc" > http< / span > method.
I am not particularly fan of the big R notation but this is the standard convention.< / p >
2011-12-29 16:05:05 +00:00
2012-01-02 14:39:00 +00:00
< p > If you save < code > config/routes< / code > , you should see your terminal in which you launched < code > yesod devel< / code > activate and certainly displaying an error message.< / p >
2011-12-29 16:05:05 +00:00
< pre >
Application.hs:31:1: Not in scope: `getEchoR'
< / pre >
2012-01-20 21:36:36 +00:00
< p > Why? Simply because we didn’ t written the code for the handler < code > EchoR< / code > .
2012-01-18 13:28:01 +00:00
Edit the file < code > Handler/Root.hs< / code > and append this:< / p >
2011-12-29 16:05:05 +00:00
< pre class = "twilight" >
2012-01-11 20:40:22 +00:00
< span class = "Entity" > getEchoR< / span > :: < span class = "Constant" > String< / span > -> < span class = "Constant" > Handler< / span > < span class = "Constant" > RepHtml< / span >
2011-12-29 16:05:05 +00:00
getEchoR theText = < span class = "Keyword" > do< / span >
defaultLayout $ < span class = "Keyword" > do< / span >
[whamlet|< h1> #{theText}|]
< / pre >
2012-01-20 21:36:36 +00:00
< p > Don’ t worry if you find all of this a bit cryptic.
2012-01-18 13:28:01 +00:00
In short it just declare a function named < code > getEchoR< / code > with one argument (< code > theText< / code > ) of type String.
When this function is called, it return a < code > Handler RepHtml< / code > whatever it is.
But mainly this will encapsulate our expected result inside an < span class = "sc" > html< / span > text.< / p >
2012-01-02 14:39:00 +00:00
2011-12-29 16:05:05 +00:00
< p > After saving the file, you should see yesod recompile the application.
2012-01-20 21:36:36 +00:00
When the compilation is finished you’ ll see the message: < code > Starting devel application< / code > .< / p >
2012-01-02 14:39:00 +00:00
< p > Now you can visit: < a href = "http://localhost:3000/echo/Yesod%20rocks!" > < code > http://localhost:3000/echo/Yesod%20rocks!< / code > < / a > < / p >
2011-12-29 16:05:05 +00:00
2012-01-18 13:28:01 +00:00
< p > TADA! It works! < / p >
2011-12-29 16:05:05 +00:00
2012-01-11 20:40:22 +00:00
< h3 id = "bulletproof" > Bulletproof?< / h3 >
2012-01-02 14:39:00 +00:00
2012-01-11 20:40:22 +00:00
< p > < img alt = "Neo stops a myriad of bullets" src = "/Scratch/img/blog/Yesod-tutorial-for-newbies/neo_bullet_proof.jpg" / > < / p >
2012-01-18 13:28:01 +00:00
< p > Even this extremely minimal web application has some impressive properties.
For exemple, imagine an attacker entering this < span class = "sc" > url< / span > :< / p >
2011-12-29 16:05:05 +00:00
2011-12-30 16:14:57 +00:00
< p > < a href = "http://localhost:3000/echo/<a>I'm <script>alert("Bad!");" > < code > http://localhost:3000/echo/< a> I'm < script> alert("Bad!");< / code > < / a > < / p >
2011-12-29 16:05:05 +00:00
< p > The special characters are protected for us.
2012-01-18 13:28:01 +00:00
A malicious user could not hide some bad script inside.< / p >
2011-12-29 16:05:05 +00:00
2012-01-18 13:28:01 +00:00
< p > This behavior is a direct consequence of < em > type safety< / em > .
The < span class = "sc" > url< / span > string is put inside a < span class = "sc" > url< / span > type.
Then the interesting part in the < span class = "sc" > url< / span > is put inside a String type.
To pass from < span class = "sc" > url< / span > type to String type some transformation are made.
2012-01-20 21:36:36 +00:00
For example, replace all “ < code > %20< / code > ” by space characters.
2012-01-18 13:28:01 +00:00
Then to show the String inside an < span class = "sc" > html< / span > document, the string is put inside an < span class = "sc" > html< / span > type.
2012-01-20 21:36:36 +00:00
Some transformations occurs like replace “ < code > < < / code > ” by “ < code > & lt;< / code > ” .
2012-01-18 13:28:01 +00:00
Thanks to yesod, this tedious job is done for us.< / p >
2011-12-29 16:05:05 +00:00
< pre class = "twilight" >
2012-01-11 20:40:22 +00:00
< span class = "String" > < span class = "String" > " < / span > http://localhost:3000/echo/some%20text< a> < span class = "String" > " < / span > < / span > :: URL
2011-12-29 16:05:05 +00:00
↓
2012-01-11 20:40:22 +00:00
< span class = "String" > < span class = "String" > " < / span > some text< a> < span class = "String" > " < / span > < / span > :: String
2011-12-29 16:05:05 +00:00
↓
2012-01-18 13:28:01 +00:00
< span class = "String" > < span class = "String" > " < / span > some text & lt;a& gt;< span class = "String" > " < / span > < / span > :: Html
2011-12-29 16:05:05 +00:00
< / pre >
2012-01-18 13:28:01 +00:00
< p > Yesod is not only fast, it helps us to remain secure.
It protects us from many common errors in other paradigms.
Yes, I am looking at you PHP!< / p >
2012-01-02 14:39:00 +00:00
2012-01-11 20:40:22 +00:00
< h3 id = "cleaning-up" > Cleaning up< / h3 >
2011-12-29 16:05:05 +00:00
2012-01-18 13:28:01 +00:00
< p > Even this very minimal example should be enhanced.
We will clean up many details:< / p >
< ul >
< li > Use < span class = "sc" > html5< / span > boilerplate< / li >
< li > Use a general < span class = "sc" > css< / span > (cleaner than the empty by default)< / li >
< li > Dispatch handler code into different files< / li >
< li > Use < code > Data.Text< / code > instead of < code > String< / code > < / li >
2012-01-20 21:36:36 +00:00
< li > Put our “ views” < sup id = "fnref:explainviewwidget" > < a href = "#fn:explainviewwidget" rel = "footnote" > 4< / a > < / sup > inside the < code > template< / code > directory< / li >
2012-01-18 13:28:01 +00:00
< / ul >
< h4 id = "span-classschtml5span-boilerplate-and-a-better-span-classsccssspan" > < span class = "sc" > html5< / span > boilerplate and a better < span class = "sc" > css< / span > < / h4 >
2011-12-29 16:05:05 +00:00
2012-01-18 13:28:01 +00:00
< p > Copy the boilerplate template to the default template.
If you take a look at them, the format is not < span class = "sc" > html< / span > but hamlet.< / p >
2011-12-29 16:05:05 +00:00
2012-01-18 13:28:01 +00:00
< pre class = "twilight" >
< span class = "Keyword" > ~< / span > cp templates/boilerplate-wrapper.hamlet templates/default-layout-wrapper.hamlet
< / pre >
2011-12-29 16:05:05 +00:00
2012-01-18 13:28:01 +00:00
< p > It is time to change the default < span class = "sc" > css< / span > .
2012-01-20 15:35:15 +00:00
Add a file named < code > default-layout.lucius< / code > inside the < code > templates/< / code > directory containing:< / p >
2012-01-18 13:28:01 +00:00
< div class = "code" > < div class = "file" > < a href = "/Scratch/en/blog/Yesod-tutorial-for-newbies/code/default-layout.lucius" > ➥ default-layout.lucius < / a > < / div > < div class = "withfile" >
< pre class = "twilight" >
< span class = "CssTagName" > body< / span > {
< span class = "CssPropertyName" > font-family< / span > : < span class = "SupportConstant" > Helvetica< / span > , < span class = "SupportConstant" > sans-serif< / span > ;
< span class = "CssPropertyName" > font-size< / span > : < span class = "CssAdditionalConstants" > 18< / span > < span class = "Keyword" > px< / span > ; }
< span class = "CssId" > < span class = "CssId" > #< / span > content< / span > {
< span class = "CssPropertyName" > padding< / span > : < span class = "CssAdditionalConstants" > 1< / span > < span class = "Keyword" > em< / span > ;
< span class = "CssPropertyName" > border< / span > : < span class = "CssAdditionalConstants" > < span class = "CssAdditionalConstants" > #< / span > CCC< / span > < span class = "CssPropertyValue" > solid< / span > < span class = "CssAdditionalConstants" > 2< / span > < span class = "Keyword" > px< / span > ;
< span class = "CssPropertyName" > border< / span > -radius: < span class = "CssAdditionalConstants" > 5< / span > < span class = "Keyword" > px< / span > ;
< span class = "CssPropertyName" > margin< / span > : < span class = "CssAdditionalConstants" > 1< / span > < span class = "Keyword" > em< / span > ;
< span class = "CssPropertyName" > width< / span > : < span class = "CssAdditionalConstants" > 37< / span > < span class = "Keyword" > em< / span > ;
< span class = "CssPropertyName" > margin< / span > : < span class = "CssAdditionalConstants" > 1< / span > < span class = "Keyword" > em< / span > < span class = "CssPropertyValue" > auto< / span > ;
< span class = "CssPropertyName" > background< / span > : < span class = "CssAdditionalConstants" > < span class = "CssAdditionalConstants" > #< / span > F2F2F2< / span > ;
< span class = "CssPropertyName" > line-height< / span > : < span class = "CssAdditionalConstants" > 1.5< / span > < span class = "Keyword" > em< / span > ;
< span class = "CssPropertyName" > color< / span > : < span class = "CssAdditionalConstants" > < span class = "CssAdditionalConstants" > #< / span > 333< / span > ; }
< span class = "CssClass" > < span class = "CssClass" > .< / span > required< / span > { < span class = "CssPropertyName" > margin< / span > : < span class = "CssAdditionalConstants" > 1< / span > < span class = "Keyword" > em< / span > < span class = "CssAdditionalConstants" > 0< / span > ; }
< span class = "CssClass" > < span class = "CssClass" > .< / span > optional< / span > { < span class = "CssPropertyName" > margin< / span > : < span class = "CssAdditionalConstants" > 1< / span > < span class = "Keyword" > em< / span > < span class = "CssAdditionalConstants" > 0< / span > ; }
< span class = "CssTagName" > label< / span > { < span class = "CssPropertyName" > width< / span > : < span class = "CssAdditionalConstants" > 8< / span > < span class = "Keyword" > em< / span > ; < span class = "CssPropertyName" > display< / span > : < span class = "CssPropertyValue" > inline-block< / span > ; }
< span class = "CssTagName" > input< / span > , < span class = "CssTagName" > textarea< / span > { < span class = "CssPropertyName" > background< / span > : < span class = "CssAdditionalConstants" > < span class = "CssAdditionalConstants" > #< / span > FAFAFA< / span > }
< span class = "CssTagName" > textarea< / span > { < span class = "CssPropertyName" > width< / span > : < span class = "CssAdditionalConstants" > 27< / span > < span class = "Keyword" > em< / span > ; < span class = "CssPropertyName" > height< / span > : < span class = "CssAdditionalConstants" > 9< / span > < span class = "Keyword" > em< / span > ;}
< span class = "CssTagName" > ul< / span > { < span class = "CssPropertyName" > list-style< / span > : < span class = "CssPropertyValue" > square< / span > ; }
< span class = "CssTagName" > a< / span > { < span class = "CssPropertyName" > color< / span > : < span class = "CssAdditionalConstants" > < span class = "CssAdditionalConstants" > #< / span > A56< / span > ; }
< span class = "CssTagName" > a< / span > < span class = "MetaTagInline" > < span class = "MetaTagInline" > :< / span > hover< / span > { < span class = "CssPropertyName" > color< / span > : < span class = "CssAdditionalConstants" > < span class = "CssAdditionalConstants" > #< / span > C58< / span > ; }
< span class = "CssTagName" > a< / span > < span class = "MetaTagInline" > < span class = "MetaTagInline" > :< / span > active< / span > { < span class = "CssPropertyName" > color< / span > : < span class = "CssAdditionalConstants" > < span class = "CssAdditionalConstants" > #< / span > C58< / span > ; }
< span class = "CssTagName" > a< / span > < span class = "MetaTagInline" > < span class = "MetaTagInline" > :< / span > visited< / span > { < span class = "CssPropertyName" > color< / span > : < span class = "CssAdditionalConstants" > < span class = "CssAdditionalConstants" > #< / span > 943< / span > ; }
< / pre >
< / div > < / div >
< p > Personally I would prefer if such a minimal < span class = "sc" > css< / span > was put with the scaffolding tool.
I am sure somebody already made such a minimal < span class = "sc" > css< / span > which give the impression
the browser handle correctly < span class = "sc" > html< / span > without any style applied to it.
But I digress.< / p >
< h4 id = "separate-handlers" > Separate Handlers< / h4 >
2012-01-20 21:36:36 +00:00
< p > Generally you don’ t want to have all your code inside a unique file.
2012-01-18 13:28:01 +00:00
This is why we will separate our handlers.
In a first time create a new file < code > Handler/Echo.hs< / code > containing:< / p >
2011-12-30 16:14:57 +00:00
< pre class = "twilight" >
< span class = "Keyword" > module< / span > < span class = "Constant" > Handler< / span > .< span class = "Constant" > Echo< / span > < span class = "Keyword" > where< / span >
< span class = "Keyword" > import< / span > < span class = "Constant" > Import< / span >
2012-01-11 20:40:22 +00:00
< span class = "Entity" > getEchoR< / span > :: < span class = "Constant" > String< / span > -> < span class = "Constant" > Handler< / span > < span class = "Constant" > RepHtml< / span >
2011-12-30 16:14:57 +00:00
getEchoR theText = < span class = "Keyword" > do< / span >
defaultLayout $ < span class = "Keyword" > do< / span >
[whamlet|< h1> #{theText}|]
< / pre >
2012-01-18 13:28:01 +00:00
< p > Do not forget to remove the getEchoR function inside < code > Handler/Root.hs< / code > .< / p >
2011-12-30 16:14:57 +00:00
2012-01-18 13:28:01 +00:00
< p > We must declare this new file into< code > yosog.cabal< / code > .
Just after < code > Handler.Root< / code > , add:< / p >
2011-12-30 16:14:57 +00:00
< pre >
Handler.Echo
< / pre >
2012-01-18 13:28:01 +00:00
< p > We must also declare this new Handler module inside < code > Application.hs< / code > .
2012-01-20 21:36:36 +00:00
Just after the “ < code > import Handler.Root< / code > ” , add:< / p >
2011-12-30 16:14:57 +00:00
< pre class = "twilight" >
< span class = "Keyword" > import< / span > < span class = "Constant" > Handler< / span > .< span class = "Constant" > Echo< / span >
< / pre >
2012-01-18 13:28:01 +00:00
< p > This is it. < / p >
< p > < small > < em > ps:< / em > I am sure not so far in the future we could simply write
< code > yesod add-handler Echo< / code > to declare it and create a new handler file.< / small > < / p >
2011-12-30 16:14:57 +00:00
2012-01-18 13:28:01 +00:00
< h4 id = "datatext" > < code > Data.Text< / code > < / h4 >
2011-12-30 16:14:57 +00:00
2012-01-11 20:40:22 +00:00
< p > It is a good practice to use < code > Data.Text< / code > instead of < code > String< / code > .< / p >
2011-12-30 16:14:57 +00:00
2012-01-18 13:28:01 +00:00
< p > To declare it, add this import directive to < code > Foundation.hs< / code > (just after the last one):< / p >
2011-12-30 16:14:57 +00:00
< pre class = "twilight" >
import Data.Text
< / pre >
2012-01-18 13:28:01 +00:00
< p > We have to modify < code > config/routes< / code > and our handler accordingly.
Replace < code > #String< / code > by < code > #Text< / code > in < code > config/routes< / code > :< / p >
2011-12-30 16:14:57 +00:00
< pre >
/echo/#Text EchoR GET
< / pre >
< p > And do the same in < code > Handler/Echo.hs< / code > :< / p >
< div class = "code" > < div class = "file" > < a href = "/Scratch/en/blog/Yesod-tutorial-for-newbies/code/Echo.hs" > ➥ Echo.hs < / a > < / div > < div class = "withfile" >
< pre class = "twilight" >
< span class = "Keyword" > module< / span > < span class = "Constant" > Handler< / span > .< span class = "Constant" > Echo< / span > < span class = "Keyword" > where< / span >
< span class = "Keyword" > import< / span > < span class = "Constant" > Import< / span >
2012-01-11 20:40:22 +00:00
< span class = "Entity" > getEchoR< / span > :: < span class = "Constant" > Text< / span > -> < span class = "Constant" > Handler< / span > < span class = "Constant" > RepHtml< / span >
2011-12-30 16:14:57 +00:00
getEchoR theText = < span class = "Keyword" > do< / span >
defaultLayout $ < span class = "Keyword" > do< / span >
[whamlet|< h1> #{theText}|]
< / pre >
< / div > < / div >
2012-01-18 13:28:01 +00:00
< h4 id = "use-templates" > Use templates< / h4 >
2011-12-30 16:14:57 +00:00
2012-01-18 13:28:01 +00:00
< p > Some < span class = "sc" > html< / span > (more precisely hamlet) is written directly inside our handler.
We should put this part inside another file.
2012-01-20 15:35:15 +00:00
Create the new file < code > templates/echo.hamlet< / code > containing:< / p >
2011-12-30 16:14:57 +00:00
< div class = "code" > < div class = "file" > < a href = "/Scratch/en/blog/Yesod-tutorial-for-newbies/code/echo.hamlet" > ➥ echo.hamlet < / a > < / div > < div class = "withfile" >
< pre class = "twilight" >
< h1> #{theText}
< / pre >
< / div > < / div >
< p > and modify the handler < code > Handler/Echo.hs< / code > :< / p >
< pre class = "twilight" >
2012-01-11 20:40:22 +00:00
< span class = "Entity" > getEchoR< / span > :: < span class = "Constant" > Text< / span > -> < span class = "Constant" > Handler< / span > < span class = "Constant" > RepHtml< / span >
2011-12-30 16:14:57 +00:00
getEchoR theText = < span class = "Keyword" > do< / span >
defaultLayout $ < span class = "Keyword" > do< / span >
$(widgetFile < span class = "String" > < span class = "String" > " < / span > echo< span class = "String" > " < / span > < / span > )
< / pre >
2011-12-29 16:05:05 +00:00
2012-01-18 13:28:01 +00:00
< p > At this point, our web application is structured between different files.
2012-01-02 14:39:00 +00:00
Handler are grouped, we use < code > Data.Text< / code > and our views are in templates.
2012-01-18 13:28:01 +00:00
It is the time to make a slightly more complex example.< / p >
< h2 id = "mirror" > Mirror< / h2 >
2012-01-02 14:39:00 +00:00
2012-01-18 13:28:01 +00:00
< p > < img alt = "Neo touching a mirror" src = "/Scratch/img/blog/Yesod-tutorial-for-newbies/mirror.jpg" class = "left" / > < / p >
2011-12-29 16:05:05 +00:00
2012-01-20 21:36:36 +00:00
< p > Let’ s make another minimal application.
2012-01-11 20:40:22 +00:00
You should see a form containing a text field and a validation button.
2012-01-20 21:36:36 +00:00
When you enter some text (for example “ Jormungad” ) and validate,
2012-01-18 13:28:01 +00:00
the next page present you the content and its reverse appended to it.
2012-01-20 21:36:36 +00:00
In our example it should return “ JormungaddagnumroJ” . < / p >
2012-01-02 14:39:00 +00:00
< p > First, add a new route:< / p >
2012-01-18 13:28:01 +00:00
< pre >
/mirror MirrorR GET POST
2012-01-02 14:39:00 +00:00
< / pre >
2012-01-18 13:28:01 +00:00
< p > This time the path < code > /mirror< / code > will accept GET and POST requests.
Add the corresponding new Handler file:< / p >
2012-01-02 14:39:00 +00:00
2012-01-18 13:28:01 +00:00
< div class = "code" > < div class = "file" > < a href = "/Scratch/en/blog/Yesod-tutorial-for-newbies/code/Mirror.hs" > ➥ Mirror.hs < / a > < / div > < div class = "withfile" >
2012-01-02 14:39:00 +00:00
< pre class = "twilight" >
2012-01-18 13:28:01 +00:00
< span class = "Keyword" > module< / span > < span class = "Constant" > Handler< / span > .< span class = "Constant" > Mirror< / span > < span class = "Keyword" > where< / span >
2012-01-02 14:39:00 +00:00
< span class = "Keyword" > import< / span > < span class = "Constant" > Import< / span >
2012-01-18 13:28:01 +00:00
< span class = "Keyword" > import< / span > qualified < span class = "Constant" > Data< / span > .< span class = "Constant" > Text< / span > < span class = "Keyword" > as< / span > < span class = "Constant" > T< / span >
2012-01-02 14:39:00 +00:00
2012-01-18 13:28:01 +00:00
< span class = "Entity" > getMirrorR< / span > :: < span class = "Constant" > Handler< / span > < span class = "Constant" > RepHtml< / span >
getMirrorR = < span class = "Keyword" > do< / span >
2012-01-02 14:39:00 +00:00
defaultLayout $ < span class = "Keyword" > do< / span >
2012-01-18 13:28:01 +00:00
$(widgetFile < span class = "String" > < span class = "String" > " < / span > mirror< span class = "String" > " < / span > < / span > )
2012-01-02 14:39:00 +00:00
2012-01-18 13:28:01 +00:00
< span class = "Entity" > postMirrorR< / span > :: < span class = "Constant" > Handler< / span > < span class = "Constant" > RepHtml< / span >
postMirrorR = < span class = "Keyword" > do< / span >
postedText < - runInputPost $ ireq textField < span class = "String" > < span class = "String" > " < / span > content< span class = "String" > " < / span > < / span >
defaultLayout $ < span class = "Keyword" > do< / span >
$(widgetFile < span class = "String" > < span class = "String" > " < / span > posted< span class = "String" > " < / span > < / span > )
2012-01-02 14:39:00 +00:00
< / pre >
< / div > < / div >
2012-01-20 21:36:36 +00:00
< p > Don’ t forget to declare it inside < code > yosog.cabal< / code > and < code > Application.hs< / code > .< / p >
2012-01-18 13:28:01 +00:00
< p > We will need to use the < code > reverse< / code > function provided by < code > Data.Text< / code >
which explain the additional import.< / p >
2012-01-02 14:39:00 +00:00
2012-01-20 21:36:36 +00:00
< p > The only new thing here is the line that get the POST parameter named “ content” .
2012-01-18 13:28:01 +00:00
If you want to know more detail about it and form in general you can take
look at < a href = "http://www.yesodweb.com/book/forms" > the yesod book< / a > .< / p >
2012-01-02 14:39:00 +00:00
< p > Create the two corresponding templates:< / p >
2012-01-18 13:28:01 +00:00
< div class = "code" > < div class = "file" > < a href = "/Scratch/en/blog/Yesod-tutorial-for-newbies/code/mirror.hamlet" > ➥ mirror.hamlet < / a > < / div > < div class = "withfile" >
2012-01-02 14:39:00 +00:00
< pre class = "twilight" >
< span class = "MetaTagAll" > < span class = "MetaTagAll" > < < / span > < span class = "MetaTagAll" > h1< / span > < span class = "MetaTagAll" > > < / span > < / span > Enter your text
2012-01-18 13:28:01 +00:00
< span class = "MetaTagAll" > < span class = "MetaTagAll" > < < / span > < span class = "MetaTagAll" > form< / span > < span class = "MetaTagAll" > method< / span > =< span class = "MetaTagAll" > post< / span > < span class = "MetaTagAll" > action< / span > =@< span class = "EmbeddedSource" > {MirrorR}< / span > < span class = "MetaTagAll" > > < / span > < / span >
2012-01-02 14:39:00 +00:00
< span class = "MetaTagInline" > < span class = "MetaTagInline" > < < / span > < span class = "MetaTagInline" > input< / span > < span class = "MetaTagInline" > type< / span > =< span class = "MetaTagInline" > text< / span > < span class = "MetaTagInline" > name< / span > =< span class = "MetaTagInline" > content< / span > < span class = "MetaTagInline" > > < / span > < / span >
< span class = "MetaTagInline" > < span class = "MetaTagInline" > < < / span > < span class = "MetaTagInline" > input< / span > < span class = "MetaTagInline" > type< / span > =< span class = "MetaTagInline" > submit< / span > < span class = "MetaTagInline" > > < / span > < / span >
< / pre >
< / div > < / div >
< div class = "code" > < div class = "file" > < a href = "/Scratch/en/blog/Yesod-tutorial-for-newbies/code/posted.hamlet" > ➥ posted.hamlet < / a > < / div > < div class = "withfile" >
< pre class = "twilight" >
< span class = "MetaTagAll" > < span class = "MetaTagAll" > < < / span > < span class = "MetaTagAll" > h1< / span > < span class = "MetaTagAll" > > < / span > < / span > You've just posted
2012-01-18 13:28:01 +00:00
< span class = "MetaTagAll" > < span class = "MetaTagAll" > < < / span > < span class = "MetaTagAll" > p< / span > < span class = "MetaTagAll" > > < / span > < / span > #< span class = "EmbeddedSource" > {postedText}< / span > #< span class = "EmbeddedSource" > {T.reverse postedText}< / span >
2012-01-02 14:39:00 +00:00
< span class = "MetaTagAll" > < span class = "MetaTagAll" > < < / span > < span class = "MetaTagAll" > hr< / span > < span class = "MetaTagAll" > > < / span > < / span >
2012-01-18 13:28:01 +00:00
< span class = "MetaTagAll" > < span class = "MetaTagAll" > < < / span > < span class = "MetaTagAll" > p< / span > < span class = "MetaTagAll" > > < / span > < / span > < span class = "MetaTagInline" > < span class = "MetaTagInline" > < < / span > < span class = "MetaTagInline" > a< / span > < span class = "MetaTagInline" > href< / span > =@< span class = "EmbeddedSource" > {MirrorR}< / span > < span class = "MetaTagInline" > > < / span > < / span > Get back
2012-01-02 14:39:00 +00:00
< / pre >
< / div > < / div >
2012-01-11 20:40:22 +00:00
< p > And that is all.
2012-01-20 21:36:36 +00:00
This time, we won’ t need to clean up.
2012-01-11 20:40:22 +00:00
We may have used another way to generate the form
2012-01-20 21:36:36 +00:00
but we’ ll see this in the next section.< / p >
2012-01-11 20:40:22 +00:00
2012-01-18 13:28:01 +00:00
< p > Just try it by < a href = "http://localhost:3000/mirror" > clicking here< / a > .< / p >
2012-01-02 14:39:00 +00:00
2012-01-18 13:28:01 +00:00
< p > Also you can try to enter strange values.
Like before, your application is quite secure.< / p >
2012-01-11 20:40:22 +00:00
2012-01-18 13:28:01 +00:00
< h2 id = "a-blog" > A Blog< / h2 >
2012-01-11 20:40:22 +00:00
2012-01-18 13:28:01 +00:00
< p > We saw how to retrieve < span class = "sc" > http< / span > parameters.
It is the time to save things into a database.< / p >
2012-01-11 20:40:22 +00:00
2012-01-18 13:28:01 +00:00
< p > As before add some routes inside < code > config/routes< / code > :< / p >
2012-01-11 20:40:22 +00:00
2012-01-18 13:28:01 +00:00
< pre >
/blog BlogR GET POST
/blog/#ArticleId ArticleR GET
< / pre >
2012-01-11 20:40:22 +00:00
2012-01-18 13:28:01 +00:00
< p > This example will be very minimal:< / p >
2011-12-29 16:05:05 +00:00
< ul >
2012-01-18 13:28:01 +00:00
< li > < code > GET< / code > on < code > /blog< / code > should display the list of articles.< / li >
< li > < code > POST< / code > on < code > /blog< / code > should create a new article< / li >
< li > < code > GET< / code > on < code > /blog/< article id> < / code > should display the content of the article.< / li >
2011-12-29 16:05:05 +00:00
< / ul >
2012-01-18 13:28:01 +00:00
< p > First we declare another model object.
Append the following content to < code > config/models< / code > :< / p >
2012-01-11 20:40:22 +00:00
2012-01-18 13:28:01 +00:00
< pre >
Article
title Text
content Html
deriving
< / pre >
< p > As < code > Html< / code > is not an instance of < code > Read< / code > , < code > Show< / code > and < code > Eq< / code > ,
we had to add the < code > deriving< / code > line.
If you forget it, there will be an error.< / p >
< p > After the route and the model, we write the handler.
First, declare a new Handler module.
Add < code > import Handler.Blog< / code > inside < code > Application.hs< / code > and add it into < code > yosog.cabal< / code > .
2012-01-20 21:36:36 +00:00
Let’ s write the content of < code > Handler/Blog.hs< / code > .
2012-01-18 13:28:01 +00:00
We start by declaring the module and by importing some block necessary to
handle Html in forms.< / p >
< pre class = "twilight" >
< span class = "Keyword" > module< / span > < span class = "Constant" > Handler< / span > .< span class = "Constant" > Blog< / span >
( getBlogR
, postBlogR
, getArticleR
)
< span class = "Keyword" > where< / span >
< span class = "Keyword" > import< / span > < span class = "Constant" > Import< / span >
< span class = "Comment" > < span class = "Comment" > --< / span > to use Html into forms< / span >
< span class = "Keyword" > import< / span > < span class = "Constant" > Yesod< / span > .< span class = "Constant" > Form< / span > .< span class = "Constant" > Nic< / span > (< span class = "Constant" > YesodNic< / span > , nicHtmlField)
< span class = "Keyword" > instance< / span > < span class = "Constant" > YesodNic< / span > < span class = "Constant" > Yosog< / span >
< / pre >
2012-01-19 15:02:53 +00:00
< p > < small > Remark: it is a best practice to add the YesodNic instance inside < code > Foundation.hs< / code > .
I put this definition here to make things easier but you should see a warning about this orphan instance.
To put the include inside Foundation.hs is left as an exercice to the reader.< / small > < / p >
< p > < small > < em > Hint: Do not forget to put < code > YesodNic< / code > and < code > nicHtmlField< / code > inside the exported objects of the module.< / em >
< / small > < / p >
2012-01-18 13:28:01 +00:00
< pre class = "twilight" >
< span class = "Entity" > entryForm< / span > :: < span class = "Constant" > Form< / span > < span class = "Constant" > Article< / span >
entryForm = renderDivs $ < span class = "Constant" > Article< / span >
< $> areq textField < span class = "String" > < span class = "String" > " < / span > Title< span class = "String" > " < / span > < / span > < span class = "Constant" > Nothing< / span >
< *> areq nicHtmlField < span class = "String" > < span class = "String" > " < / span > Content< span class = "String" > " < / span > < / span > < span class = "Constant" > Nothing< / span >
< / pre >
< p > This function defines a form for adding a new article.
2012-01-20 21:36:36 +00:00
Don’ t pay attention to all the syntax.
2012-01-18 13:28:01 +00:00
If you are curious you can take a look at Applicative Functor.
You just have to remember < code > areq< / code > is for required form input.
Its arguments being: < code > areq type label default_value< / code > .< / p >
< pre class = "twilight" >
< span class = "Comment" > < span class = "Comment" > --< / span > The view showing the list of articles< / span >
< span class = "Entity" > getBlogR< / span > :: < span class = "Constant" > Handler< / span > < span class = "Constant" > RepHtml< / span >
getBlogR = < span class = "Keyword" > do< / span >
< span class = "Comment" > < span class = "Comment" > --< / span > Get the list of articles inside the database.< / span >
articles < - runDB $ selectList [] [< span class = "Constant" > Desc< / span > < span class = "Constant" > ArticleTitle< / span > ]
< span class = "Comment" > < span class = "Comment" > --< / span > We'll need the two " objects" : articleWidget and enctype< / span >
2012-01-20 15:35:15 +00:00
< span class = "Comment" > < span class = "Comment" > --< / span > to construct the form (see templates/articles.hamlet).< / span >
2012-01-18 13:28:01 +00:00
((_,articleWidget), enctype) < - generateFormPost entryForm
defaultLayout $ < span class = "Keyword" > do< / span >
$(widgetFile < span class = "String" > < span class = "String" > " < / span > articles< span class = "String" > " < / span > < / span > )
< / pre >
< p > This handler should display a list of articles.
We get the list from the DB and we construct the form.
Just take a look at the corresponding template:< / p >
< div class = "code" > < div class = "file" > < a href = "/Scratch/en/blog/Yesod-tutorial-for-newbies/code/articles.hamlet" > ➥ articles.hamlet < / a > < / div > < div class = "withfile" >
< pre class = "twilight" >
< span class = "MetaTagAll" > < span class = "MetaTagAll" > < < / span > < span class = "MetaTagAll" > h1< / span > < span class = "MetaTagAll" > > < / span > < / span > Articles
$if null articles
-- Show a standard message if there is no article
< span class = "MetaTagAll" > < span class = "MetaTagAll" > < < / span > < span class = "MetaTagAll" > p< / span > < span class = "MetaTagAll" > > < / span > < / span > _< span class = "EmbeddedSource" > {MsgNoEntries}< / span >
$else
-- Show the list of articles
< span class = "MetaTagAll" > < span class = "MetaTagAll" > < < / span > < span class = "MetaTagAll" > ul< / span > < span class = "MetaTagAll" > > < / span > < / span >
$forall article < span class = "InvalidIllegal" > < < / span > - articles
< span class = "MetaTagInline" > < span class = "MetaTagInline" > < < / span > < span class = "MetaTagInline" > li< / span > < span class = "MetaTagInline" > > < / span > < / span >
< span class = "MetaTagInline" > < span class = "MetaTagInline" > < < / span > < span class = "MetaTagInline" > a< / span > < span class = "MetaTagInline" > href< / span > =@< span class = "EmbeddedSource" > {ArticleR (fst article)}< / span > < span class = "MetaTagInline" > > < / span > < / span > #< span class = "EmbeddedSource" > {articleTitle (snd article)}< / span >
< span class = "MetaTagAll" > < span class = "MetaTagAll" > < < / span > < span class = "MetaTagAll" > hr< / span > /< span class = "MetaTagAll" > > < / span > < / span >
< span class = "MetaTagAll" > < span class = "MetaTagAll" > < < / span > < span class = "MetaTagAll" > form< / span > < span class = "MetaTagAll" > method< / span > =< span class = "MetaTagAll" > post< / span > < span class = "MetaTagAll" > enctype< / span > =#< span class = "EmbeddedSource" > {enctype}< / span > < span class = "MetaTagAll" > > < / span > < / span >
^< span class = "EmbeddedSource" > {articleWidget}< / span >
< span class = "MetaTagAll" > < span class = "MetaTagAll" > < < / span > < span class = "MetaTagAll" > div< / span > < span class = "MetaTagAll" > > < / span > < / span >
< span class = "MetaTagInline" > < span class = "MetaTagInline" > < < / span > < span class = "MetaTagInline" > input< / span > < span class = "MetaTagInline" > type< / span > =< span class = "MetaTagInline" > submit< / span > < span class = "MetaTagInline" > value< / span > =< span class = "String" > < span class = "String" > " < / span > Post New Article< span class = "String" > " < / span > < / span > < span class = "MetaTagInline" > > < / span > < / span >
< / pre >
< / div > < / div >
< p > You should remark we added some logic inside the template.
2012-01-20 21:36:36 +00:00
There is a test and a “ loop” .< / p >
2012-01-18 13:28:01 +00:00
< p > Another very interesting part is the creation of the form.
The < code > articleWidget< / code > was created by yesod.
We have given him the right parameters
(input required or optional, labels, default values).
And now we have a protected form made for us.
But we have to create the submit button.< / p >
< p > Get back to < code > Handler/Blog.hs< / code > .< / p >
< pre class = "twilight" >
< span class = "Comment" > < span class = "Comment" > --< / span > we continue Handler/Blog.hs< / span >
< span class = "Entity" > postBlogR< / span > :: < span class = "Constant" > Handler< / span > < span class = "Constant" > RepHtml< / span >
postBlogR = < span class = "Keyword" > do< / span >
((res,articleWidget),enctype) < - runFormPost entryForm
< span class = "Keyword" > case< / span > res < span class = "Keyword" > of< / span >
< span class = "Constant" > FormSuccess< / span > article -> < span class = "Keyword" > do< / span >
articleId < - runDB $ insert article
2012-01-19 15:01:15 +00:00
setMessage $ toHtml $ (articleTitle article) < > < span class = "String" > < span class = "String" > " < / span > created< span class = "String" > " < / span > < / span >
2012-01-18 13:28:01 +00:00
redirect < span class = "Constant" > RedirectPermanent< / span > $ < span class = "Constant" > ArticleR< / span > articleId
_ -> defaultLayout $ < span class = "Keyword" > do< / span >
setTitle < span class = "String" > < span class = "String" > " < / span > Please correct your entry form< span class = "String" > " < / span > < / span >
$(widgetFile < span class = "String" > < span class = "String" > " < / span > articleAddError< span class = "String" > " < / span > < / span > )
< / pre >
< p > This function should be used to create a new article.
We handle the form response.
If there is an error we display an error page.
For example if we left some required value blank.
If things goes right: < / p >
< ul >
< li > we add the new article inside the DB (< code > runDB $ insert article< / code > )< / li >
< li > we add a message to be displayed (< code > setMessage $ ...< / code > )< / li >
< li > we are redirected to the article web page.< / li >
< / ul >
< p > Here is the content of the error Page:< / p >
< pre class = "twilight" >
< form method=post enctype=#{enctype}>
^{articleWidget}
< < span class = "Entity" > div< / span > >
< input < span class = "Keyword" > type< / span > =submit value=< span class = "String" > < span class = "String" > " < / span > Post New Article< span class = "String" > " < / span > < / span > >
< / pre >
< p > Finally we need to display an article:< / p >
< pre class = "twilight" >
< span class = "Entity" > getArticleR< / span > :: < span class = "Constant" > ArticleId< / span > -> < span class = "Constant" > Handler< / span > < span class = "Constant" > RepHtml< / span >
getArticleR articleId = < span class = "Keyword" > do< / span >
article < - runDB $ get404 articleId
defaultLayout $ < span class = "Keyword" > do< / span >
setTitle $ toHtml $ articleTitle article
$(widgetFile < span class = "String" > < span class = "String" > " < / span > article< span class = "String" > " < / span > < / span > )
< / pre >
< p > The < code > get404< / code > function try to do a get on the DB.
If it fails it return a 404 page.
The rest should be clear.
2012-01-20 15:35:15 +00:00
Here is the content of < code > templates/article.hamlet< / code > :< / p >
2012-01-18 13:28:01 +00:00
< div class = "code" > < div class = "file" > < a href = "/Scratch/en/blog/Yesod-tutorial-for-newbies/code/article.hamlet" > ➥ article.hamlet < / a > < / div > < div class = "withfile" >
< pre class = "twilight" >
< span class = "MetaTagAll" > < span class = "MetaTagAll" > < < / span > < span class = "MetaTagAll" > h1< / span > < span class = "MetaTagAll" > > < / span > < / span > #< span class = "EmbeddedSource" > {articleTitle article}< / span >
< span class = "MetaTagAll" > < span class = "MetaTagAll" > < < / span > < span class = "MetaTagAll" > article< / span > < span class = "MetaTagAll" > > < / span > < / span > #< span class = "EmbeddedSource" > {articleContent article}< / span >
< / pre >
< / div > < / div >
2012-01-18 14:51:20 +00:00
< p > The blog system is finished.
Just for fun, you can try to create an article with the following content:< / p >
2012-01-18 13:28:01 +00:00
< pre class = "twilight" >
< span class = "MetaTagAll" > < span class = "MetaTagAll" > < < / span > < span class = "MetaTagAll" > p< / span > < span class = "MetaTagAll" > > < / span > < / span > A last try to < span class = "MetaTagInline" > < span class = "MetaTagInline" > < < / span > < span class = "MetaTagInline" > em< / span > < span class = "MetaTagInline" > > < / span > < / span > cross script< span class = "MetaTagInline" > < span class = "MetaTagInline" > < /< / span > < span class = "MetaTagInline" > em< / span > < span class = "MetaTagInline" > > < / span > < / span >
and < span class = "MetaTagInline" > < span class = "MetaTagInline" > < < / span > < span class = "MetaTagInline" > em< / span > < span class = "MetaTagInline" > > < / span > < / span > SQL injection< span class = "MetaTagInline" > < span class = "MetaTagInline" > < /< / span > < span class = "MetaTagInline" > em< / span > < span class = "MetaTagInline" > > < / span > < / span > < span class = "MetaTagAll" > < span class = "MetaTagAll" > < /< / span > < span class = "MetaTagAll" > p< / span > < span class = "MetaTagAll" > > < / span > < / span >
< span class = "MetaTagAll" > < span class = "MetaTagAll" > < < / span > < span class = "MetaTagAll" > p< / span > < span class = "MetaTagAll" > > < / span > < / span > Here is the first try:
< span class = "EmbeddedSource" > < span class = "EmbeddedSource" > < < / span > < span class = "MetaTagInline" > script< / span > < span class = "EmbeddedSource" > > < / span > < span class = "SupportFunction" > alert< / span > < span class = "EmbeddedSource" > (< / span > < span class = "String" > < span class = "String" > " < / span > You loose< span class = "String" > " < / span > < / span > < span class = "EmbeddedSource" > )< / span > < span class = "EmbeddedSource" > ;< / span > < span class = "EmbeddedSource" > < /< / span > < span class = "MetaTagInline" > script< / span > < span class = "EmbeddedSource" > > < / span > < / span > < span class = "MetaTagAll" > < span class = "MetaTagAll" > < /< / span > < span class = "MetaTagAll" > p< / span > < span class = "MetaTagAll" > > < / span > < / span >
< span class = "MetaTagAll" > < span class = "MetaTagAll" > < < / span > < span class = "MetaTagAll" > p< / span > < span class = "MetaTagAll" > > < / span > < / span > And Here is the last < span class = "MetaTagAll" > < span class = "MetaTagAll" > < /< / span > < span class = "MetaTagAll" > p< / span > < span class = "MetaTagAll" > > < / span > < / span >
" ); DROP TABLE ARTICLE;;
< / pre >
2012-01-18 14:51:20 +00:00
< h2 id = "conclusion" > Conclusion< / h2 >
< p > This is the end of this tutorial.
2012-01-20 21:36:36 +00:00
I made it very minimal.< / p >
< p > If you already know Haskell and you want to go further,
you should take a look at the
2012-01-18 14:51:20 +00:00
recent < a href = "http://yesodweb.com/blog/2012/01/blog-example" > i18n blog tutorial< / a > .
It will be obvious I inspired my own tutorial on it.
2012-01-20 21:36:36 +00:00
You’ ll learn in a very straightforward way how easy it is to use authorizations,
2012-01-18 14:51:20 +00:00
Time and internationalization.
The example on also add a comment system.< / p >
2012-01-19 15:11:43 +00:00
2012-01-20 21:36:36 +00:00
< p > If, on the other hand you don’ t know Haskell.
Then you shouldn’ t jump directly to web programming.
Haskell is a very complex and unusual language.
My advice to go as fast as possible in using Haskell for web programming is:< / p >
< ol >
< li > Start by < a href = "http://tryhaskell.org" > try Haskell in your browser< / a > < / li >
< li > Then read the excellent < a href = "http://learnyouahaskell.com" > Learn you a Haskell for Great Good< / a > < / li >
< li > If you have difficulties in understanding concepts like monads, you should really read < a href = "http://homepages.inf.ed.ac.uk/wadler/topics/monads.html" > these articles< / a > . For me they were enlightening.< / li >
< li > If you feel confident, you should be able to follows the < a href = "http://yesodweb.com/book" > yesod book< / a > and if you find difficult to follows the yesod book, you should read < a href = "book.realworldhaskell.org" > real world Haskell< / a > first (it is a must read).< / li >
< / ol >
< p > Also, note that:< / p >
< ul >
< li > < a href = "http://haskell.org" > haskell.org< / a > is full of excellent resources.< / li >
< li > < a href = "http://www.haskell.org/hoogle/" > hoogle< / a > will be very useful< / li >
< li > Use < a href = "http://community.haskell.org/~ndm/hlint/" > hlint< / a > as soon as possible to get good habits.< / li >
< / ul >
< p > As you should see, if you don’ t already know Haskell,
the path is long but I guaranty you it will be very rewarding!< / p >
< p > < em > ps:< / em > You can download the source of this yesod blog tutorial at
2012-01-20 11:20:07 +00:00
< a href = "http://github.com/yogsototh/yosog" > github.com/yogsototh/yosog< / a > .< / p >
2012-01-11 20:40:22 +00:00
< hr / > < div class = "footnotes" >
< ol >
< li id = "fn:benchmarkdigression" >
2012-01-20 21:36:36 +00:00
< p > One can argue these benchmark contains many problems. But the benchmarks are just here to give an order of idea. Mainly Haskell is very fast.< a href = "#fnref:benchmarkdigression" rel = "reference" > ↩ < / a > < / p >
2012-01-11 20:40:22 +00:00
< / li >
< li id = "fn:speeddigression" >
2012-01-20 21:36:36 +00:00
< p > Generally < em > high level< / em > Haskell is slower than C, but < em > low level< / em > Haskell is equivalent to C speed. It means that even if you can easily link C code with Haskell, this is not needed to reach the same speed. Furthermore writing a web service in C/C++ seems to be a very bad idea. You can take a look at a < a href = "http://news.ycombinator.com/item?id=3449388" > discussion on HN about this< / a > . < a href = "#fnref:speeddigression" rel = "reference" > ↩ < / a > < / p >
2012-01-11 20:40:22 +00:00
< / li >
< li id = "fn:nodejstroll" >
2012-01-20 21:36:36 +00:00
< p > If you are curious, you can search about < a href = "http://www.unlimitednovelty.com/2011/10/nodejs-has-jumped-shark.html" > the Fibonacci node.js troll< / a > . Without any tweaking, < a href = "http://mathias-biilmann.net/posts/2011/10/is-haskell-the-cure" > Haskell handled this problem perfectly< / a > . I tested it myself using yesod instead of Snap.< a href = "#fnref:nodejstroll" rel = "reference" > ↩ < / a > < / p >
2012-01-18 13:28:01 +00:00
< / li >
< li id = "fn:explainviewwidget" >
2012-01-20 21:36:36 +00:00
< p > By view I mean yesod widget’ s hamlet, lucius and julius files. < a href = "#fnref:explainviewwidget" rel = "reference" > ↩ < / a > < / p >
2012-01-11 20:40:22 +00:00
< / li >
< / ol >
2011-12-29 16:05:05 +00:00
< / div >
< / div >
< div id = "choixrss" >
< a id = "rss" href = "http://feeds.feedburner.com/yannespositocomen" >
Subscribe
< / a >
< / div >
< script type = "text/javascript" >
$(document).ready(function(){
$('#comment').hide();
$('#clickcomment').click(showComments);
});
function showComments() {
$('#comment').show();
$('#clickcomment').fadeOut();
}
document.write('< div id = "clickcomment" > Comments< / div > ');
< / script >
< div class = "flush" > < / div >
< div class = "corps" id = "comment" >
< h2 class = "first" > comments< / h2 >
< noscript >
You must enable javascript to comment.
< / noscript >
< script type = "text/javascript" >
var idcomments_acct = 'a307f0044511ff1b5cfca573fc0a52e7';
var idcomments_post_id = '/Scratch/en/blog/Yesod-tutorial-for-newbies/';
var idcomments_post_url = 'http://yannesposito.com/Scratch/en/blog/Yesod-tutorial-for-newbies/';
< / script >
< span id = "IDCommentsPostTitle" style = "display:none" > < / span >
< script type = 'text/javascript' src = '/Scratch/js/genericCommentWrapperV2.js' > < / script >
< / div >
< div id = "entete" class = "corps_spaced" >
< div id = "liens" >
< ul > < li > < a href = "/Scratch/en/" > Home< / a > < / li >
< li > < a href = "/Scratch/en/blog/" > Blog< / a > < / li >
< li > < a href = "/Scratch/en/softwares/" > Softwares< / a > < / li >
< li > < a href = "/Scratch/en/about/" > About< / a > < / li > < / ul >
< / div >
< div class = "flush" > < / div >
< hr / >
< div id = "next_before_articles" >
< div id = "previous_articles" >
previous entries
< div class = "previous_article" >
< a href = "/Scratch/en/blog/SVG-and-m4-fractals/" > < span class = "nicer" > «< / span > Increase the power of deficient languages.< / a >
< / div >
< div class = "previous_article" >
< a href = "/Scratch/en/blog/Yesod-excellent-ideas/" > < span class = "nicer" > «< / span > Yesod excellent ideas< / a >
< / div >
< div class = "previous_article" >
2012-01-11 20:40:22 +00:00
< a href = "/Scratch/en/blog/Higher-order-function-in-zsh/" > < span class = "nicer" > «< / span > Higher order function in zsh< / a >
2011-12-29 16:05:05 +00:00
< / div >
< / div >
< div id = "next_articles" >
next entries
< / div >
< div class = "flush" > < / div >
< / div >
< / div >
< div id = "bottom" >
< div >
< a rel = "license" href = "http://creativecommons.org/licenses/by-sa/3.0/" > Copyright ©, Yann Esposito< / a >
< / div >
< div id = "lastmod" >
2012-01-18 14:51:20 +00:00
Created: 01/15/2012
2012-01-20 11:21:04 +00:00
Modified: 01/20/2012
2011-12-29 16:05:05 +00:00
< / div >
< div >
Entirely done with
< a href = "http://www.vim.org" > Vim< / a >
and
< a href = "http://nanoc.stoneship.org" > nanoc< / a >
< / div >
< div >
< a href = "/Scratch/en/validation/" > Validation< / a >
< a href = "http://validator.w3.org/check?uri=referer" > [xhtml] < / a >
.
< a href = "http://jigsaw.w3.org/css-validator/check/referer?profile=css3" > [css] < / a >
.
< a href = "http://validator.w3.org/feed/check.cgi?url=http%3A//yannesposito.com/Scratch/en/blog/feed/feed.xml" > [rss]< / a >
< / div >
< / div >
< div class = "clear" > < / div >
< / div >
< / body >
< / html >