HaskellのYesodでWebアプリ開発入門 (4)

はじめに

HaskellでYesodを使ってWebアプリケーションを作ってみている際のメモ記事です。

前回はビューを構成するためのウィジェットについて学び、CSSライブラリのバージョンアップを行いました。

Hamletを用いたレイアウト

今回はいよいよ、Hamletファイルを編集していきます。 なお、templates/layout/wrapper.hamletのほとんどを占めているIE対策コードやGoogle Analyticsのコードについてはここでは触れません。 サイトの用途に依っては重要ではありますが、それぞれの環境によって自分の思うように整形したりIE対策をどこまでやるか考えればいいかと思います(ここのサンプルコードではIE9以降のブラウザのみへの対応を考えています)。

さっそく、templates/layout/application.hamletからはサンプルコードのために書かれていたコードを取り除いて標準的なHTML5のテンプレート的な内容に書き換え、templates/layout/wrapper.hamletからは不要なコードを削除して単純化を行いました。

なお、ナビゲーションバーの内容がHomeとContactだけでは寂しいので、2回目で行なったのと同じ手順でAboutとHelpのハンドラとビューも追加しました。 この追加の仕方は、完全にrailstutorial.orgのパクリですね。

templates/layout/application.hamlet

$newline never
<div #main>
    <header>
        <h1>
            <a href=@{HomeR}>Sample App
        <nav>
            <ul>
                <li>
                    <a href=@{AboutR}>About
                <li>
                    <a href=@{HelpR}>Help
                <li>
                    <a href=@{ContactR}>Contact

    ^{widget}

    <footer>
        <p>&copy; 2014 demmy_s

templates/layout/wrapper.hamlet

$newline never
$doctype 5
<html lang="en">
    <head>
        <meta charset="UTF-8">

        <title>#{pageTitle pc} | Yesod Sample
        <meta name="description" content="Sample Application of Yesod">

        <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">

        ^{pageHead pc}
        $maybe analytics <- extraAnalytics $ appExtra $ settings master
            <script>
              if(!window.location.href.match(/localhost/)){
                window._gaq = [['_setAccount','#{analytics}'],['_trackPageview'],['_trackPageLoadTime']];
                (function() {
                \  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
                \  ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
                \  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
                })();
              }

        \<!--[if lt IE 9]>
        \<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
        \<![endif]-->
    <body>
        ^{pageBody pc}

とりあえず編集してページを見ると、嬉しい事にしっかりとルーティングがなされた汚い(CSSのない)ページを楽しむことができます。

なお、FOOTERタグ内のコピーライトについて、Scaffoldで生成されたものではconfig/settings.ymlで記述したものを表示するようになっていたのですが、通常それほど書き換える需要もないかと思いますので、単にベタ書きに変更しました。

不要になったextraCopyrightなどが気になる場合はSettings.hsの68行目以降を以下のように編集し、config/settings.ymlからcopyrightの行を取り除いてしまえばいいでしょう。

data Extra = Extra
    { extraAnalytics :: Maybe Text -- ^ Google Analytics
    } deriving Show

parseExtra :: DefaultEnv -> Object -> Parser Extra
parseExtra _ o = Extra
    <$> o .:? "analytics"

また、Foundation.hsdefaultLayout内にあったmmsg <- getMessageという記述もサンプルアプリケーション用の記述を消したので必要無くなり、コンパイル時にWarningとして出てしまうので、消しておくといいでしょう。

サンプルの方では前回Hamletの構文として紹介しなかったものが続々と出てきましたので、順に説明しておこうかと思います。

糖衣構文

まず、それぞれの1行目とwrappter.hamletの2行目はただの糖衣構文です。 $newline neverとすると、最終的に生成するHTMLから改行とインデントを除き、ファイルサイズを小さくしてくれるので、基本的には書いておくといいかと思います。 $doctype 5は容易に想像がつく通り、HTML5のDOCTYPE宣言を挿入してくれるだけの構文です。

なお、DOCTYPE宣言は古いYesodのバージョンでは!!!と書くだけで挿入されていましたが、わかりづらいために現在では推奨されていません。

また、BODYタグ直下のDIVタグで用いている#mainという表記はid="main"の糖衣構文です。Hamletでは同じようにクラスの指定も.mainのように行うことができます。

エスケープ記号

Hamletテンプレートファイル内でスペース以外を含まない行頭に\を記述した場合にはその行はエスケープされ、\の後からスペースを含まない行末までに記述した内容がそのまま最終的なHTMLに含められます。

また、上記のサンプルでは使用されていませんが、行末に#を記述することで、その#以前に記述されたスペースをエスケープされたとしてそのまま最終的なHTMLに含ませることができます。

これらの含まれたコードは少し複雑にも思えますが、コメントや他のサービスから提供される埋め込み用コードをシンプルな記述で含ませられるのは選択肢として悪くない気がします。

内挿

続いて、全体に散りばめられている#^@で始まり{ }が続いている構文は内挿(Interpolation)です。これまでにも少し触れましたが、Haskellのコードを型安全に埋め込むための構文であり、{ }の前の記号は埋め込まれる値の型を特定しています。

以下に記号と内挿できる値の対応関係を書いておきます。

記号 内挿できる型
# ToHtmlクラスのインスタンスとなっている型
^ Hamletテンプレートを表す型
@ config/routesなどで作成されたURLの型

なお、@{ }で内挿されるURLの文字列はドメイン名までを含む完全なURLとなります。

ひとまずHamletの説明は以上です。 次は、CSSを出力するテンプレートを用いてナビゲーションバーとフッターのデザインをしてみます。

Luciusを用いたデザイン

LuciusはCSSの拡張としてYesodで用意されているCSSテンプレートエンジンの1つですが、構文はほとんどRoRでのSassやLessと同じようなものとなっていますので、特にとっつきにくさもなく導入できるかと思います。

今回もHamletの時と同様にまずサンプルプログラムのために書いたLuciusファイルを紹介したいと思います。

Foundation.hsの中でのtemplates/layout/application.hamletの展開は2回目の記事で紹介したwidgetFile関数を使用して行われているので、これを利用するために、編集するLuciusファイルはtemplates/layout/application.luciusという名前で保存します。

/*
 * Global
 */
@defaultColor: #222;
@inverseColor: #fff;

html {
    color: #{defaultColor};
    font-size: 100%;
    height: 100%;
}

body {
    height: 100%;
}

h1,p,ul {
    margin: 0;
}

h1 {
    font-weight: normal;
}

a {
    color: #{defaultColor};
    text-decoration: none;
}

ul {
    list-style: none;
    padding-left: 0;
}



/*
 * Layout
 */
@sidePadding: 3%;

@headerBackColor: #222;

@buttonColor: #bbb;
@buttonActiveColor: #fff;

@footerLineColor: #ccc;

#main {
    width: 100%;
    margin: 0 auto;
    padding-bottom: 3em;
}

#main>header {
    display: table;
    width: 100%;
    padding: 0.5em 0;
    background-color: #{headerBackColor};

    >* {
        display: table-cell;
        vertical-align: middle;
    }

    >h1 {
        font-size: 1.3em;
        width: 10em;
        padding-left: #{sidePadding};

        >a {
            color: #{buttonActiveColor};
        }
    }

    >nav {
        padding-right: #{sidePadding};

        >ul {
            display: table;
            table-layout: fixed;
            float: right;

            >li {
                display: table-cell;
                font-size: 1em;
                vertical-align: middle;
                text-align: center;
                width: 5em;

                >a {
                    color: #{buttonColor};
                }
                >a:hover {
                    color: #{buttonActiveColor};
                }
            }

            >li.active>a {
                color: #{buttonActiveColor};
            }
        }
    }
}

#main>section {
    margin: #{sidePadding};
}

#main>footer {
    text-align: right;
    padding: 0.7em 1.5% 0 1.5%;
    margin: 3em 1.5% 0 1.5%;
    border-top: 1px solid #{footerLineColor};
}



/*
 * Components
 */
@normalBackColor: #ccc;
@redBackColor: #c0392b;
@greenBackColor: #27ae60;
@blueBackColor: #2980b9;

.red {
    color: #{inverseColor};
    background-color: #{redBackColor} !important;
}
.green {
    color: #{inverseColor};
    background-color: #{greenBackColor} !important;
}
.blue {
    color: #{inverseColor};
    background-color: #{blueBackColor} !important;
}

.button {
    display: inline-block;
    padding: 0.7em 1em;
    border-radius: 5px;
    background-color: #{normalBackColor};
}

Lucius内ではHamletと同様に#{valiable}で変数を展開できるだけでなく、@valiable: value;という構文によって変数を定義することができます。もちろん、url( )による指定では@{ }という形の内挿を記述することで型安全なURLを埋め込むことができます。

また、SassやLessと同じように{ }を入れ子にすることで、子要素へのスタイルの適用を記述することができます。

なお、LuciusMixinテンプレートを利用することでミックスインを簡単に記述することもできるのですが、それについてはまた出てきた際に説明します。

このLuciusファイルの適用でナビゲーションバーとフッターがいい感じにデザインされましたが、まだトップページがHello, Wold!だけの悲しい状態ですので、またまたrailstutorial.orgのデザインを真似てtemplates/home/index.hamlettemplates/home/index.luciusも編集してみました。

<section>
    <h1>Welcome to the Yesod Sample
    <p>This is the home page for the Yesod sample application.
    <a href="#" .button .blue>Sign up now!
@topBackColor: #eee;

#main>section {
    background-color: #{topBackColor};
    text-align: center;
    padding: 3em;
    border-radius: 10px;

    >h1 {
        font-weight: bold;
        font-size: 2.5em;
        margin-bottom: 0.2em;
    }

    >p {
        font-size: 1.2em;
    }

    >a {
        margin-top: 1em;
    }
}

おもいっきりパクリではありますが、なかなか良い感じになりました。

f:id:demmy_s:20140522113858p:plain

それでは、デザインでモチベーションを保ちつつ、残りの構文についても説明してしまいたいと思います。

Cassiusの構文

CassiusはLuciusの構文をよりHaskellらしくしたもので、スコープを表現するために{ }ではなくインデントを用い、行の終りを表現するために;ではなく改行を用います。 他の部分はLuciusと全く同様になっており、実際内部的にもLuciusと同じパーサが用いられているだけです。

原理主義的な発想ではCassiusを使用するべきなのかもしれませんが、Luciusの方が既存のCSSもそのまま一緒に記述できるため、ここでは一貫してLuciusのみを使用していきます。

Juliusの構文

Juliusは単純に、Hamletと同様の内挿を行えるようにしたJavaScriptの拡張です。 URLの記述や変数によるハンドラからの値の受け渡しができるというだけですが、本格的なアプリケーションを構築していく上では相当助けとなる機能でしょう。

おわりに

今回はYesodのフロントエンドを支えるShakespearen TemplateのDSL構文を順に見ていきました。

これで、とりあえず静的なWebサイトであれば自由に作れるくらいにはYesodがわかってきたかと思います。

次の内容についてはまだ考えていませんが、そろそろ本格的にYesodアプリケーションで遊ぶための環境をつくりたいので、本番環境の用意と結合テストの書き方などを見ていこうかと考えています。