3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PureScriptで素朴なDOM操作 (Hello, world!)

Last updated at Posted at 2019-04-26
$ purs --version
0.12.5

前書き

PureScript v0.12の記事がなかなか見つからなかったのでやってみましたが、低水準APIでDOM操作をすると結構大変でした。
このアプローチはほどほどにして、早めに高水準なUIライブラリを使い始めるのが良いと思います。

UIライブラリに関しては @hiruberuto さんの PureScriptのUIライブラリまとめ が参考になります。

今回使うパッケージは低水準APIのpurescript-web-htmlです。

プロジェクトの作成

purescript, pulpはnpm経由でインストールできます。

今回はパッケージマネージャにpsc-packageを使いますが、bowerでも代用可能です。
spagoという選択肢もあるようです。

$ pulp --psc-package init
$ pulp build

ルートにindex.htmlを作成します。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>PureScript</title>
</head>
<body>
  <script type="text/javascript" src="./app.js"></script>
</body>
</html>

お好みでmetaタグを追加したりcssを追加したりしてください。

次にバンドルします。

$ pulp browserify -O --to app.js

ブラウザでindex.htmlを開いてコンソールにHello sailor!と表示されていれば成功です。

必要なパッケージをインストールして、一応ビルドしておきます。
パッケージはweb-htmlだけ追加すれば十分です。

$ psc-package install web-html
$ pulp build

(任意) Parcel

基本はpulp browserify -O --to app.jsで事足りますが、parcel-bundlerを使うのもアリです。

$ npm init
$ npm i -D parcel-bundler

次に、app.jsを一旦削除し、以下のように書き換えます。

app.js
require("./output/Main").main();

後はparcelにバンドルしてもらうだけです。

$ parcel index.html

たったこれだけでホットリロード付きの開発環境が手に入ります。かなりお手軽です。

<p>Hello, world!</p>

PureScriptでElementを作ってbodyにappendChildするサンプルです。

module Main where

import Prelude

import Data.Maybe (Maybe(..))
import Effect (Effect)
import Effect.Console (error) as Console
import Web.DOM.Document (createElement) as DOM
import Web.DOM.Element (toNode) as DOM
import Web.DOM.Node (appendChild, setTextContent) as DOM
import Web.HTML (window) as HTML
import Web.HTML.HTMLDocument (body, toDocument) as HTML
import Web.HTML.HTMLElement (toNode) as HTML
import Web.HTML.Window (document) as HTML

main :: Effect Unit
main = do
  htmlDocument <- HTML.window >>= HTML.document

  maybeBody <- HTML.body htmlDocument
  let document = HTML.toDocument htmlDocument

  case maybeBody of 
    Nothing   -> Console.error "bodyが無い" 
    Just body -> do
      pNode <- DOM.toNode <$> DOM.createElement "p" document
      DOM.appendChild pNode (HTML.toNode body) >>=
      DOM.setTextContent "Hello, world!"

感想

低水準APIでのDOM操作はまあまあ辛かったです。頭の体操にはなりました。
ボタンなどはWeb.DOM.Element.setAttribute Web.Event.EventTarget.addEventListenerを使うと一応作れると思いますが、PureScriptでやる利点をあまり感じません。
FFIを使うというのもアリだと思います。

むしろ、高水準なUIライブラリとpurescript-affjaxpurescript-simple-jsonあたりの使い方の勉強に時間を投資するのが良いと思います。

解説

一応解説です。

bodyを取得するまで

Web.HTMLの話がメインです。

windowの取得

Web.HTML.windowを使います。

window :: Effect Window
window = --
-- const window = () => window;

window.documentの取得

Web.HTML.Window.documentを使います。

document :: Window -> Effect HTMLDocument
document w = --
-- const document = w => () => w.document;

document.bodyの取得

Web.HTML.HTMLDocument.bodyを使います。

body :: HTMLDocument -> Effect (Maybe HTMLElement)
body = map toMaybe <<< _body
-- const _body = d => () => d.body;

bodyをHTMLElementとして取得します。戻り値はMaybe HTMLElementです。

仕様に疎いのでMaybeになっている有難味があまり分かりませんでした。

HTMLElementNodeとして使う

Web.HTML.HTMLElement.toNodeを使うとWeb.DOM.Nodeにキャストできます。
Web.HTMLWeb.DOMの橋渡しです。

toNode :: HTMLElement -> Node
toNode = unsafeCoerce

bodyの取得まとめ

module Main where

import Prelude

import Data.Maybe (Maybe(..))
import Effect (Effect)
import Effect.Console (error) as Console
import Web.DOM (Node) as DOM
import Web.HTML (HTMLElement, window) as HTML
import Web.HTML.HTMLDocument (body) as HTML
import Web.HTML.HTMLElement (toNode) as HTML
import Web.HTML.Window (document) as HTML

main :: Effect Unit
main = do
  maybeBody <- HTML.window >>= HTML.document >>= HTML.body

  case maybeBody of 
    Nothing   -> Console.error "bodyが無い" 
    Just (bodyAsElement :: HTML.HTMLElement) -> do
      let (bodyAsNode :: DOM.Node) = HTML.toNode bodyAsElement
      --

bodyAsElementbodyAsNodeの実体は同じです。

また、maybeBody <- HTML.window >>= HTML.document >>= HTML.bodyと一行で書いてしまっていますが、以下のように3つに分けても同じことです。

main :: Effect Unit
main = do
  window       <- HTML.window
  htmlDocument <- HTML.document window
  maybeBody    <- HTML.body htmlDocument

DOM操作

Web.DOMの話がメインです。

createElement

Web.DOM.Document.createElementを使います。

createElement :: String -> Document -> Effect Element
createElement = --
-- const createElement = name => d => () => d.createElement(name);

補足(document.createElement)

JSでconst elem = document.createElement("div");とやるとします。
これをFFIをせずにやるにはWeb.HTML.Windowdocument :: HTMLDocumentを取得した後、Web.HTML.HTMLDocument.toDocumentでキャストする必要があります。

toDocument :: HTMLDocument -> Document
toDocument = unsafeCoerce

setTextContent

NodeにTextを貼るにはWeb.DOM.Node.setTextContentを使います。

setTextContent :: String -> Node -> Effect Unit
setTextContent = --
-- const setTextContent = val => node => () => {
--   node.textContent = val;
-- };

ElementNodeとして使う

PureScript上でElementNodeにキャストするためのWeb.DOM.Element.toNodeがあります。

toNode :: Element -> Node
toNode = unsafeCoerce

Web.HTML.HTMLElement.toNodeと混同しないように注意です。

appendChild

Web.DOM.Node.appendChildを使います。
ノードは子 → 親の順で指定します。戻り値は子ノードです。

appendChild :: Node -> Node -> Effect Node
appendChild = --
-- const appendChild = node => parent => () => {
--   return parent.appendChild(node);
-- }

(おまけ) Halogen

bodyはAff環境下でHalogen.Aff.awaitBodyすることで取得することができます。

コンポーネントはHalogen.mkComponentで作ることができ、initialStaterenderevalの3つが必要です。

この内のrenderに関してですが、テキストノードはHalogen.HTML.textで作れます。pタグはHalogen.HTML.pなど、全て用意されています。

作ったコンポーネントはHalogen.VDom.Driver.runUIで表示させることができます。

Halogenで<p>Hello, world!</p>

module Main where

import Prelude

import Effect (Effect)
import Halogen as H
import Halogen.Aff as HA
import Halogen.VDom.Driver (runUI)
import Halogen.HTML as HH

main :: Effect Unit
main = HA.runHalogenAff do
  body <- HA.awaitBody
  runUI component unit body
  where
    component = H.mkComponent
      { initialState: const {}
      , render: const $ HH.p_ [HH.text "Hello, world!"]
      , eval: H.mkEval H.defaultEval
      }

StateやActionの管理は公式exampleが参考になります。

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?