Elm0.19でFirestore利用のTODOアプリ(2)ElmからFirestoreへデータの追加(Create)
ElmでFirestore利用のTodoアプリを作りたいシリーズの第1回では、portsを用いて、Firebase Firestoreに保存した
{ “newTodo" : “", “todoList" :"[“abc", “111", “3333333"]" }
のような文字列を、index.html(JavaScript)からmain.js(src/Main.elmをコンパイルしたもの)に送り、Elm側でレコードrecordに変換(デコード)して表示してみました。
第2回の今回は、Elm側でnewTodoを入力し、Firestoreにデータを追加してみたいと思います。
以下の本のp171-p178を参考に、いろいろ写経してみたいと思います。
Elm0.19でFirestore利用のTODOアプリ目次
- Elm0.19でFirestore利用のTODOアプリ(1)ElmでJSONを扱う(FirestoreからElmへデータを送る)
- Elm0.19でFirestore利用のTODOアプリ(2)ElmからFirestoreへデータの追加(Create)
開発環境
Windows 10 Pro
Chrome
VisualStudioCode 1.32.3
git version 2.20.1.windows.1
nvm 1.1.7
node 10.2.0
npm 6.4.1
elm 0.19.0-bugfix6
elm-format 0.8.1
VisualStudioCodeの拡張機能でelmをインストールして、settings.jsonに以下のようにelmを設定。
(『Alt + Shift + F』と『Ctrl + S』を使用。)
"[elm]": {
"editor.formatOnSave": true
},
Elmからindex.html(JavaScript)にデータを送る
https://github.com/evancz/elm-todomvc のコードを見てみると、以下のように書くのがよさそうです。
main : Program () Model Msg
main =
Browser.element
{ init = init
, view = view
, update = updateWithStorage
, subscriptions = subscriptions
}
{- subscriptionsのところの記載は今回は省略 -}
{- portを介して、Elmからindex.html(JavaScript)にデータを送る -}
port setStorage : Model -> Cmd msg
{-| We want to `setStorage` on every update. This function adds the setStorage
command for every step of the update function.2
-}
updateWithStorage : Msg -> Model -> ( Model, Cmd Msg )
updateWithStorage msg model =
let
( newModel, cmds ) =
update msg model
in
( newModel
, Cmd.batch [ setStorage newModel, cmds ]
)
setStorageという名前のportを通して、Elmからindex.html(JavaScript)にnewModelを送ります。
index.html(JavaScript)でデータを受け取り、Firestoreに書き込む
全く同じデータをlocalStorageに書き込むときは、以下のようなコードでした。
const DB = firebase.firestore();
var docRef = DB.collection('foo').doc('1111111');
app.ports.setStorage.subscribe(function(state) {
localStorage.setItem('elm-todo-save', JSON.stringify(state));
});
今回は、この中身で、Firestoreに書き込むようにコードを変更します。
const DB = firebase.firestore();
var docRef = DB.collection('foo').doc('1111111');
app.ports.setStorage.subscribe(function(state) {
docRef.update(state);
});
一文字書き込むたびに、すべてのnewTodoとtodoListをFirestoreに書き込むというトラフィックを使いまくりな仕様ですが、とりあえず、これでよしとします。
削除機能は自動的に実装されることになります
Elmの方でtodoListの一部を削除すると、それと同時に、その時点でのすべてのデータをFirestoreに送る仕様になっているので、削除機能も実装済みとなります。
以上で、create, read, deleteのみで、通信料も膨大となりますが、Firestore利用のTODOアプリが完成しました。。。
ソースコード
Firebase configの部分は、ご自身のものを入力してください。
また、最初、index.htmlの階層で、コマンドプロンプトで以下のように入力します。
elm init
elm install elm/json
さらに、以下の2つのファイルを作成後に、また、index.htmlの階層で、コマンドプロンプトで以下のように入力し、src/Main.elmをmain.jsにコンパイルし、最後にindex.htmlをChromeで開きます。
elm make src/Main.elm --output=main.js
index.html
<html>
<head>
<meta charset="UTF-8" />
<title>Main</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css"
/>
<script
defer
src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"
></script>
<script src="main.js"></script>
</head>
<body>
<div id="elm"></div>
<!-- Firebase App is always required and must be first -->
<script src="https://www.gstatic.com/firebasejs/5.9.1/firebase-app.js"></script>
<!-- Add additional services that you want to use -->
<script src="https://www.gstatic.com/firebasejs/5.9.1/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.9.1/firebase-database.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.9.1/firebase-firestore.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.9.1/firebase-messaging.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.9.1/firebase-functions.js"></script>
<script>
// Your web app's Firebase configuration
// こちらは、ご自身のものに書き換えてください。
var firebaseConfig = {
apiKey: "<API_KEY>",
authDomain: "<PROJECT_ID>.firebaseapp.com",
databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
projectId: "<PROJECT_ID>",
storageBucket: "<BUCKET>.appspot.com",
messagingSenderId: "<SENDER_ID>"
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const DB = firebase.firestore();
var docRef = DB.collection('foo').doc('1111111');
docRef.onSnapshot(function(doc) {
console.log(doc.data());
console.log(typeof doc.data());
console.log(typeof JSON.stringify(doc.data()));
app.ports.read.send(JSON.stringify(doc.data()));
});
var app = Elm.Main.init({
node: document.getElementById('elm')
});
app.ports.setStorage.subscribe(function(state) {
docRef.update(state);
});
</script>
</body>
</html>
src/Main.elm
port module Main exposing (Model, Msg(..), emptyModel, init, main, setStorage, update, view)
import Browser
import Html exposing (Attribute, Html, a, div, figure, footer, form, h1, img, input, li, p, section, text, ul)
import Html.Attributes exposing (autofocus, class, href, placeholder, src, type_, value)
import Html.Events exposing (keyCode, on, onClick, onInput)
import Json.Decode as Json exposing (Decoder, decodeString, field, list, map2, string)
---- MODEL ----
type alias Model =
{ newTodo : String
, todoList : List String
}
fromJsonModel : Decoder Model
fromJsonModel =
map2 Model
(field "newTodo" string)
(field "todoList" (list string))
emptyModel : Model
emptyModel =
{ newTodo = ""
, todoList = []
}
init : () -> ( Model, Cmd Msg )
init _ =
( emptyModel
, Cmd.none
)
---- UPDATE ----
type Msg
= Change String
| Add
| Delete Int
| KeyDown Int
| Read String
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
let
isSpace =
String.trim >> String.isEmpty
in
case msg of
Change str ->
( { model | newTodo = str }
, Cmd.none
)
Add ->
if isSpace model.newTodo then
( model, Cmd.none )
else
( { model
| todoList = model.newTodo :: model.todoList
, newTodo = ""
}
, Cmd.none
)
Delete n ->
let
t =
model.todoList
in
( { model
| todoList = List.take n t ++ List.drop (n + 1) t
}
, Cmd.none
)
KeyDown key ->
if key == 13 then
if isSpace model.newTodo then
( model, Cmd.none )
else
( { model
| todoList = model.newTodo :: model.todoList
, newTodo = ""
}
, Cmd.none
)
else
( model, Cmd.none )
Read s ->
( case decodeString fromJsonModel s of
Err e ->
model
Ok newModel ->
{ newModel
| newTodo = model.newTodo
}
, Cmd.none
)
---- VIEW ----
view : Model -> Html Msg
view model =
div []
[ section [ class "hero is-primary" ]
[ div [ class "hero-body" ]
[ div [ class "container" ]
[ h1 [ class "title" ]
[ text "Elm Todo localStorage" ]
]
]
]
, section [ class "section" ]
[ div [ class "container" ]
[ form [ class "field has-addons" ]
[ div [ class "control" ]
[ input [ class "input", type_ "text", placeholder "input your todo", onKeyDown KeyDown, autofocus True, onInput Change, value model.newTodo ] []
]
, div [ class "control" ]
[ a [ class "button is-info", onClick Add ] [ text "add todo" ]
]
]
, ul [ class "list is-hoverable" ]
(showList model.todoList)
]
]
, footer [ class "footer" ]
[ div [ class "content has-text-centered" ]
[ p []
[ a [ href "http://i-doctor.sakura.ne.jp/font/?p=38214" ] [ text "WordPressでフリーオリジナルフォント2" ]
]
]
]
]
showList : List String -> List (Html Msg)
showList =
let
todos =
List.indexedMap Tuple.pair
column ( n, s ) =
li [ class "list-item has-text-left" ]
[ div []
[ text s
, a [ class "button is-danger", onClick (Delete n) ] [ text "delete" ]
]
]
in
todos >> List.map column
onKeyDown : (Int -> msg) -> Attribute msg
onKeyDown tagger =
on "keydown" (Json.map tagger keyCode)
---- MAIN ----
main : Program () Model Msg
main =
Browser.element
{ init = init
, view = view
, update = updateWithStorage
, subscriptions = subscriptions
}
subscriptions : Model -> Sub Msg
subscriptions model =
read Read
port read : (String -> msg) -> Sub msg
port setStorage : Model -> Cmd msg
{-| We want to `setStorage` on every update. This function adds the setStorage
command for every step of the update function.2
-}
updateWithStorage : Msg -> Model -> ( Model, Cmd Msg )
updateWithStorage msg model =
let
( newModel, cmds ) =
update msg model
in
( newModel
, Cmd.batch [ setStorage newModel, cmds ]
)
Netlifyにデプロイ
デプロイというより、そのままindex.htmlとmain.jsをアップロードすれば終了です。
https://modest-kirch-c3a6d2.netlify.com/
セキュリティ的にかなりよろしくないので、できれば、いつか、Google認証を追加したいと思います。
今回参考にした本とサイト
https://github.com/evancz/elm-todomvc
https://github.com/qnoyxu/chat-room
https://qiita.com/qnoyxu/items/ee479b2b96831907e024
https://github.com/ababup1192/elm-firebase
https://qiita.com/ababup1192/items/f27f9af282d9fa642eb5
ディスカッション
コメント一覧
まだ、コメントがありません