Skip to content

Latest commit

 

History

History
514 lines (432 loc) · 12.2 KB

view.md

File metadata and controls

514 lines (432 loc) · 12.2 KB

ビュー

戻る

コンテンツ

イントロダクション

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

XSSを防止するために、 変数に対してget関数を使用してください。 内部でxmlEncodeが適用されます。

API

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の文字列を返す関数のことです。

JavaScript

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名/クラス名を持つことができます。

CSS

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にインスパイアされた、コンポーネントごとのスタイルを作成するのに便利なタイプです。 スタイル化されたクラス名は、コンポーネントごとにランダムなサフィックスを持つので、複数のコンポーネントが同じクラス名を持つことができます。

SCSS

また、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>
"""

API

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

ヘルパー関数

Csrfトークン

formからPOSTリクエストを送信するには、csrf tokenを設定する必要があります。basolato/view` のヘルパー関数を利用することができます。

import basolato/view

proc index*():string =
  tmpli html"""
    <form>
      $(csrfToken())
      <input type="text", name="name">
    </form>
  """

old関数

ユーザーの入力値が不正で、入力ページに戻して以前に入力された値を表示したい場合には、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
  • Nim標準ライブラリ
  • Nimプログラマなら簡単に使える
  • 1つのファイルに複数の関数を定義できる
  • if文やfor文が使えない
  • デザイナーやマークアップエンジニアとの共同作業は難しいかもしれない
SCF
  • Nim標準ライブラリ
  • if文、for文が使える
  • デザイナーやマークアップエンジニアとの共同作業が容易
    • 1つのファイルに複数の関数を定義できない
    Karax
  • 1つのファイルに複数の関数を定義できる
  • if文、for文が使える
    • デザイナーやマークアップエンジニアとの共同作業は難しいかもしれない
    nim-templates
    • 1つのファイルに複数の関数を定義できる
    • if文、for文が使える
    • デザイナーやマークアップエンジニアとの共同作業が容易
    • 個人がメンテナンスしている

    ビューファイルは、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>

    htmlgen

    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

    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))}

    Karax

    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))