シングルページでなく複数ページでそれぞれ別のElmアプリが動作していて、小さなデータを共有したいケースを考えましょう。例えばトップページのApp.elmでログインしてサーバからJWTトークンを取得している場合、それをページ2のApp2.elmで使いたい場合はどうすればいいでしょうか。多分JavaScript(Html5)のLocal storageを使うのが一番簡単でしょう。
Local storageには、保存したデータに関して、特に有効期限はなく、ブラウザを閉じても喪失することはなく、永続的に利用できる利点があります。もちろんJWT tokenを保存するときは、token自体の有効期限があるので、それに応じて明示的に更新(削除)する必要があるでしょう。またLocal storageの保存データサイズが5MBと比較的大きいのももう一つの利点でしょう。
以下、今回はJWTトークンでなく、単にuser_id文字列を渡すサンプルコードを考えます。
#1.ページ設計
"/" -- トップページ (App.elm, index.html)
"/page2" -- ページ2(App2.elm, index2.html)
以下がApp.elmで取得したデータを、Local storageを通して、App2.elmに渡す流れです。
port port
App.elm --> JavaScript(Local storage) --> App2.elm
userId userId
#2.App.elm - Local storageに書く
App.elmでは入力フォームから入力されたuserId文字列をLocalStorageに保存します。そのためにportでJavaScriptのコードにアクセスします。
#2-1.ports設計
(key,value)のタプルを渡して、JavaScriptにLocalStorageに保存するよう依頼します。
#
-- OUTGOING PORT
port portSetLocalStorage : (String, String) -> Cmd msg
#
#2-2.App.elm全コード
以下がApp.elmの全コードです。
port module App exposing (..)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput)
-- OUTGOING PORT
port portSetLocalStorage : (String, String) -> Cmd msg
init : ( Model, Cmd Msg )
init =
(Model "1111") ! []
main : Program Never Model Msg
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}
type Msg
= CacheUserId
| ChangeUserId String
type alias Model =
{ userId : String
}
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
CacheUserId ->
( model
, portSetLocalStorage ("userId", model.userId)
)
ChangeUserId userId ->
{ model | userId=userId } ! []
view : Model -> Html Msg
view model =
div []
[ input [ value model.userId, onInput ChangeUserId ] []
, button [ onClick CacheUserId ] [ text "Cache userId!" ]
, div [] [ text <| "current user id = " ++ model.userId ]
]
#2-3.index.html
以下がindex.htmlです。タプル(key,value)で渡された引数は、req[]配列で受け取れます。
<!doctype html>
<html>
<head>
</head>
<body>
<a href="index2.html">index2</a>
<div id="elm-area"></div>
<script src="app.js"></script>
<script>
const app = Elm.App.embed(document.getElementById("elm-area"));
app.ports.portSetLocalStorage.subscribe( (req) => {
localStorage.setItem(req[0],req[1]);
} )
</script>
</body>
</html>
#3.App2.elm - Local storageを読む
App2.elmではLocal storageの値を読み取ります。
#3-1.ports設計
portGetLocalStorageにkey文字列を渡して、valueを取得してくれるように依頼します。返り値はportResLocalStorageを通して行われます。(String, String)で受け取ることを宣言しているので、JavaScript側では配列で渡しますが、タプルに自動変換されます。
-- OUTGOING PORT
port portGetLocalStorage : String -> Cmd msg
-- INCOMING PORT
port portResLocalStorage : ((String, String) -> msg) -> Sub msg
#3-3.App2.elm全コード
以下がApp2.elmの全コードです。
port module App2 exposing (..)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput)
-- OUTGOING PORT
port portGetLocalStorage : String -> Cmd msg
-- INCOMING PORT
port portResLocalStorage : ((String, String) -> msg) -> Sub msg
init : ( Model, Cmd Msg )
init =
(Model "2222") ! []
main : Program Never Model Msg
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
type Msg
= GetCacheUserId
| Receive (String, String)
type alias Model =
{ userId : String
}
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
GetCacheUserId ->
( model
, portGetLocalStorage "userId"
)
Receive (_, val) ->
{model | userId=val} ! []
view : Model -> Html Msg
view model =
div []
[ button [ onClick GetCacheUserId ] [ text "Get Cache userId!" ]
, div [] [ text <| "current user id = " ++ model.userId ]
]
subscriptions : Model -> Sub Msg
subscriptions model =
portResLocalStorage Receive
#3-4.index2.html
app2.ports.portResLocalStorage.send(res)のresは配列であることに注意してください。
<!doctype html>
<html>
<head>
</head>
<body>
<a href="index.html">top</a>
<div id="elm-area"></div>
<script src="app2.js"></script>
<script>
const app2 = Elm.App2.embed(document.getElementById("elm-area"));
app2.ports.portGetLocalStorage.subscribe( (key) => {
const val = localStorage.getItem(key);
res = [key, val];
app2.ports.portResLocalStorage.send(res);
} )
</script>
</body>
</html>
#4.プログラムの実行
適当なディレクトリを作成します。以下のようにして必要なパッケージをインストールします。
elm-package install elm-lang/html
コンパイルとサーバの実行は以下の通りです。
elm-make App.elm --output app.js
elm-make App2.elm --output app2.js
elm-reactor -a=www.mypress.jp -p=3030
ブラウザでindex.htmlを開いてください。以下のようになっています。「13579」と入力してボタンを押します。入力欄の数字をLocalStorageに書き込みます。
次にindex2.htmlを開いてボタンを押してください。LocalStorageの数字を読み込み表示します。
これでApp.elmとApp2.elmでuserIdを共有させることができました。さて、これからですね。
(補足)まさに今回のテーマの仕事を行うパッケージがいくつかありました。今回のように自分で書くよりはコード量を少なくできるようですが、パッケージを使う環境を整えるのにそれ以上の労力が必要に思えました。自分でportを使って書いた方が、トータル的に考えてよいと思われます。