コンテンツ
Basolatoでは、デフォルトのテンプレートエンジンとして、nim-templates
を使用しています。これは basolato/view
をインポートすることで利用できます。
import basolato/view
proc baseImpl(content:string): string =
tmpli html"""
<html>
<heade>
<title>Basolato</title>
</head>
<body>
$(content.get)
</body>
</html>
"""
proc indexImpl(message:string): string =
tmpli html"""
<p>$(message.get)</p>
"""
proc indexView*(message:string): string =
baseImpl(indexImpl(message))
XSSを防止するために、 変数に対してget
関数を使用してください。 内部でxmlEncodeが適用されます。
proc get*(val:JsonNode):string =
proc get*(val:string):string =
title = "This is title<script>alert("aaa")</script>"
params = @["<script>alert("aaa")</script>", "b"].parseJson()
import basolato/view
proc impl(title:string, params:JsonNode):Future[string] {.async.} =
tmpli html"""
<h1>$(title.get)</h1>
<ul>
$for param in params {
<li>$(param.get)</li>
}
</ul>
"""
Basolato viewは、ReactやVueのようなコンポーネント指向のデザインを採用しています。 コンポーネントとは、htmlとJavaScriptとCSSの単一の塊であり、htmlの文字列を返す関数のことです。
controller
import basolato/controller
proc withSscriptPage*(request:Request, params:Params):Future[Response] {.async.} =
return render(withScriptView())
view
import basolato/view
import ../layouts/application_view
proc impl():string =
script ["toggle"], script:"""
<script>
window.addEventListener('load', ()=>{
let el = document.getElementById('toggle')
el.style.display = 'none'
})
const toggleOpen = () =>{
let el = document.getElementById('toggle')
if(el.style.display == 'none'){
el.style.display = ''
}else{
el.style.display = 'none'
}
}
</script>
"""
tmpli html"""
$(script)
<div>
<button onclick="toggleOpen()">toggle</button>
<div id="$(script.element("toggle"))">...content</div>
</div>
"""
proc withScriptView*():string =
let title = "Title"
return applicationView(title, impl())
これをhtmlにコンパイルすると以下のようになります。
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
window.addEventListener('load', ()=>{
let el = document.getElementById('toggle_akvcgccoeg')
el.style.display = 'none'
})
const toggleOpen = () =>{
let el = document.getElementById('toggle_akvcgccoeg')
if(el.style.display == 'none'){
el.style.display = ''
}else{
el.style.display = 'none'
}
}
</script>
<div>
<button onclick="toggleOpen()">toggle</button>
<div id="toggle_akvcgccoeg">...content</div>
</div>
</body>
</html>
script
テンプレートの第一引数に渡されたセレクタはコンポーネントごとにランダムなサフィックスを持つので、複数のコンポーネントが同じID名/クラス名を持つことができます。
controller
import basolato/controller
proc withStylePage*(request:Request, params:Params):Future[Response] {.async.} =
return render(withStyleView())
view
import basolato/view
import ../layouts/application_view
proc impl():string =
style "css", style:"""
<style>
.background {
height: 200px;
width: 200px;
background-color: blue;
}
.background:hover {
background-color: green;
}
</style>
"""
tmpli html"""
$(style)
<div class="$(style.element("background"))"></div>
"""
proc withStyleView*():string =
let title = "Title"
return applicationView(title, impl())
これをhtmlにコンパイルすると以下のようになります。
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<style type="text/css">
.background_jtshlgnucx {
height: 200px;
width: 200px;
background-color: blue;
}
.background_jtshlgnucx:hover {
background-color: green;
}
</style>
<div class="background_jtshlgnucx"></div>
</body>
</html>
style
テンプレートは、CSS-in-JS
にインスパイアされた、コンポーネントごとのスタイルを作成するのに便利なタイプです。
スタイル化されたクラス名は、コンポーネントごとにランダムなサフィックスを持つので、複数のコンポーネントが同じクラス名を持つことができます。
また、libsassをインストールすれば、SCSSを使うことができます。
apt install libsass-dev
# or
apk add --no-cache libsass-dev
そして、次のようにスタイルブロックを書きます。
style "scss", style:"""
<style>
.background {
height: 200px;
width: 200px;
background-color: blue;
&:hover {
background-color: green;
}
}
</style>
"""
script
テンプレートは Script
型のインスタンスを name
の 引数に格納します。
style
テンプレートは Css
型のインスタンスを name
の 引数に格納します。
# for JavaScript
template script*(selectors:openArray[string], name, body:untyped):untyped
template script*(name, body:untyped):untyped
proc `$`*(self:Script):string
proc element*(self:Script, name:string):string
# for CSS
template style*(typ:string, name, body: untyped):untyped
proc `$`*(self:Css):string
proc element*(self:Css, name:string):string
form
からPOSTリクエストを送信するには、csrf token
を設定する必要があります。basolato/view` のヘルパー関数を利用することができます。
import basolato/view
proc index*():string =
tmpli html"""
<form>
$(csrfToken())
<input type="text", name="name">
</form>
"""
ユーザーの入力値が不正で、入力ページに戻して以前に入力された値を表示したい場合には、old
ヘルパー関数を使います。
API
proc old*(params:JsonNode, key:string):string =
proc old*(params:TableRef, key:string):string =
controller
# getアクセス
proc signinPage*(request:Request, params:Params):Future[Response] {.async.} =
return render(signinView())
# postアクセス
proc signin*(request:Request, params:Params):Future[Response] {.async.} =
let email = params.getStr("email")
try
...
except:
return render(Http422, signinView(%params))
view
proc impl(params=newJObject()):string =
tmpli html"""
<input type="text" name="email" value="$(old(params, "email"))">
<input type="text" name="password">
"""
proc signinView*(params=newJObject()):string =
let title = "SignIn"
return self.applicationView(title, impl(params))
params
にキー email
があれば値を表示し、なければ空の文字列を表示します。
HTMLを生成する他のライブラリもBasolatoに選択することができます。しかし、それぞれのライブラリには、それぞれの利点と欠点があります。
ライブラリ | メリット | デメリット |
---|---|---|
htmlgen |
|
|
SCF |
|
|
Karax |
|
|
nim-templates |
|
|
ビューファイルは、app/http/views
ディレクトリにある必要があります。
コントローラと出力結果はそれぞれの例で同じです。
controller
proc index*(): Response =
let message = "Basolato"
return render(indexView(message))
出力結果
<html>
<head>
<title>Basolato</title>
</head>
<body>
<p>Basolato</p>
</body>
</html>
import htmlgen
proc baseImpl(content:string): string =
html(
head(
title("Basolato")
),
body(content)
)
proc indexImpl(message:string): string =
p(message)
proc indexView*(message:string): string =
baseImpl(indexImpl(message))
SCFでは関数毎にファイルを分ける必要があります。
baseImpl.nim
#? stdtmpl | standard
#proc baseImpl*(content:string): string =
<html>
<heade>
<title>Basolato</title>
</head>
<body>
$content
</body>
</html>
indexImpl.nim
#? stdtmpl | standard
#proc indexImpl*(message:string): string =
<p>$message</p>
index_view.nim
#? stdtmpl | standard
#import baseImpl
#import indexImpl
#proc indexView*(message:string): string =
${baseImpl(indexImpl(message))}
Server Side HTML Rendering の使い方をしています。
import karax / [karasdsl, vdom]
proc baseImpl(content:string): string =
var vnode = buildView(html):
head:
title: text("Basolato")
body: text(content)
return $vnode
proc indexImpl(message:string): string =
var vnode = buildView(p):
text(message)
return $vnode
proc indexView*(message:string): string =
baseImpl(indexImpl(message))