前回(その3)で作成した画面はちょっと見た目が寂しいのでCSSを作って適用してみます。
VIEWにHTMLの属性(class)を書いていく
まずMain.elmのVIEWにCSSで定義するclassを指定していきます。
view : Model -> Html Msg
view model =
div
[ class "container" ] -- クラス指定を追加
[ div
[ class "input_area" ] -- クラス指定を追加
[ label
[ class "label_zip" ] -- クラス指定を追加
[ text "郵便番号" ]
, input
[ class "input_box_zip1" -- クラス指定を追加
, type_ "text"
, onInput InputZip1
, value model.zip1
, pattern "^[0-9]+$" -- ついでに入力可能な値に数値を指定
, maxlength 3 -- 入力可能な最大長も指定
]
[]
, span
[ class "zip_sep" ] -- クラス指定を追加
[ text "-" ]
, input
[ class "input_box_zip2"
, type_ "text"
, onInput InputZip2
, value model.zip2
, pattern "^[0-9]+$"
, maxlength 4
(以下省略)
CSSを作って、top.htmlと同じディレクトリに保存しておきます。
.container
{ margin-top : 20px
; margin-left : 20px
; padding-top : 20px
; padding-left : 20px
}
.input_area
{ width : 35%
; padding-top : 10px
; padding-bottom : 10px
; margin-bottom : 10px
; background : #f3fafb
}
(以下省略)
これを取り込むためにHTMLの<link>タグに作成したcssを指定します。
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Main</title>
<style>body { padding: 0; margin: 0; }</style>
/* ↓ここにCSSの指定を追加 */
<link rel="stylesheet" href="top.css">
</head>
<body>
(以下省略)
表示してみます。まずはservantサーバを起動して
[user@remote ~/webapp/backend]$ stack exec backend-exe
変わりませんでした。
firefoxの開発ツールを起動(Ctrl + Shift + I)後、ページをリロードしてみます。
top.cssを取得しようとして404になっています。
top.htmlの<link>タグに記載されたCSS(top.css)をサーバに要求したものの、
サーバ側ではこれに対する実装を行っていないためこうなったのだと推測できます。
というわけで、top.cssをきちんと返すようにサーバ側(Main.hs)を修正します。
#Servantでcssを返す
その1で作成したMain.hsをもう一度見てみます。
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE MultiParamTypeClasses #-}
import Servant
import Network.Wai.Handler.Warp
import Network.HTTP.Media ((//), (/:))
import qualified Data.ByteString.Lazy as BS
data HTML
instance Accept HTML where
contentType _ = "text" // "html" /: ("charset", "utf-8")
instance MimeRender HTML BS.ByteString where
mimeRender _ bs = bs
type API = Get '[HTML] BS.ByteString
api :: Proxy API
api = Proxy
server :: BS.ByteString -> Server API
server top = return top
main :: IO ()
main = do
top <- BS.readFile "/home/user/webapp/frontend/top.html"
run 8080 $ serve api (server top)
CSSを返すには/top.css
というパスへのアクセスがあった際、CSSを読み込んで返せばOKなので、
HTMLを返した時と同様に以下のようにすれば返せるのですが、、、。
import Control.Monad.IO.Class (liftIO)
-- CSS用に追加
data CSS
instance Accept CSS where
contentType _ = "text" // "css" /: ("charset", "utf-8")
instance MimeRender CSS BS.ByteString where
mimeRender _ bs = bs
-- APIに/top.cssへのルーティング定義を追加
type API
= Get '[HTML] BS.ByteString
:<|> "top.css" :> Get '[CSS] BS.ByteString -- 追加
-- APIに応じた処理を追加
server :: BS.ByteString -> BS.ByteString -> Server API -- css分を引数に追加
server top css
= (liftIO $ return top) -- 修正
:<|> (liftIO $ return css) -- 追加
main :: IO ()
main = do
top <- BS.readFile "/home/user/webapp/frontend/top.html"
css <- BS.readFile "/home/user/webapp/frontend/top.css" -- 追加
run 8080 $ serve api (server top css) -- serverにcssも渡すよう変更
でも、用意するCSSやjavascript全部に対してこれを書くのはしんどいので何とかしたいです。
Servant.Server.StaticFiles
Servant.Server.StaticFilesのserveDirectoryWebAppを使うとCSSやjavascriptといったファイルを取り扱うことができます。
Servant.Server.StaticFilesを利用したMain.hs
の全量は以下のようになります。
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE MultiParamTypeClasses #-}
import Servant
import Network.Wai.Handler.Warp
import Network.HTTP.Media ((//), (/:))
import qualified Data.ByteString.Lazy as BS
import Control.Monad.IO.Class (liftIO)
data HTML
instance Accept HTML where
contentType _ = "text" // "html" /: ("charset", "utf-8")
instance MimeRender HTML BS.ByteString where
mimeRender _ bs = bs
api :: Proxy API
api = Proxy
type API
= Get '[HTML] BS.ByteString
:<|> "static" :> Raw
server :: BS.ByteString -> Server API
server top
= (liftIO $ return top)
:<|> serveDirectoryWebApp "/home/user/webapp/frontend"
main :: IO ()
main = do
top <- BS.readFile "/home/user/webapp/frontend/top.html"
run 8080 $ serve api (server top)
"static" :> Raw
とすることで、/staticで始まるパスへアクセスがあった際に、これをserveDirectoryWebApp に渡している文字列に変換してくれます。
したがって、上記の例だと/static/top.cssでアクセスされた際に/home/user/webapp/frontend/top.cssを返してくれます。
ということでtop.htmlのほうに記載しているCSSのパスを/static/top.cssに変えます。
<link rel="stylesheet" href="/static/top.css"/>
CSSが無事に取得されて適用されました。
elmコードからjavascriptファイルを出力する
これまではelm make
でHTMLを生成しましたが、javascriptを生成することもできて、CSSと同様にして読み込ませることができます。詳しくはこちらをご参照。
elm make src/Main.elm --output=top.js
これでjavascriptのファイルができあがるので、top.htmlに書かれていたjavascriptのコードを削除して、代わりに出力されたjavascriptファイルを読み込ませるようにしてあげます。
<head>
<link rel="stylesheet" href="/static/top.css"/>
<script src="/static/top.js"></script>
</head>
<body>
<div id="elm"></div>
<script>
var app = Elm.Main.init({
node: document.getElementById('elm')
});
</script>
</body>
サーバを再起動してトップページへ再度アクセスすれば、
きちんと表示され、郵便番号入力からの住所表示も確認できます。
まとめ
-
elm make 入力ファイル --output=出力ファイル
でelmコードからjavascriptファイルを生成することができる。 - CSSやjavascriptなどの静的コンテンツをServantで返す際にはServant.Server.StaticFilesが便利。
次回はフロント側に持たせてしまっていた住所データをDBに移して、郵便番号をキーにしてDBから取得する。ということをやってみようと思います。