『Firestoreで読み書きする』をElmで写経にtryしてみるがAddさえまともにできず
Firestoreを用いてElmでチャットアプリを作りたいと思いました。
Firebase Realtime DatabaseとElmのチャットアプリは、前回、ソースコードをダウンロードして実行してみました。
こちらのコードを、
https://github.com/ababup1192/elm-firebase/blob/master/src/index.js
とあわせて、Firestoreで認証付きチャットアプリを作りたいのですが、今の私には無理です。
https://github.com/Goryudyuma/elm-todo
JavaScriptフレームワークである、VueやAngularには、firebaseとの連携のためのツールが用意されていますが、Elmの場合は、フラグやポートを通して、素のJavaScriptでFirebaseとElmの橋渡しをしなければなりません。
ということで素のJavaScriptでFirestoreとの連携の方法を調べるために、以下のサイトを写経してみたいと思います。
しかし、これなら、Elmいらなくて、素のJavaScriptでやればいいのでは???と思ってしまったり、しまわなかったり、、、まあ、趣味ですから、とりあえず、やってみます。
https://blog.katsubemakito.net/firebase/firestore_readwrite_1
開発環境
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
},
create-elm-app 3.0.6
新規Firebaseアプリの作成とFirestoreの初期設定
Googleアカウントが必要です。以下の記載の前半を参考にして、Firebaseアプリを作成し、データベースはFirestoreを設定します。
なお、今回は、Firebaseプロジェクトの名前は"image-upload"とし、セキュリティールールはテストモード(誰でも読み書き可能、、、)とします。
新規create-elm-appアプリの作成
C:/elm/ フォルダをVisualStudioCodeで開き、Ctrl+@でターミナル画面を開いて、以下を入力します。
create-elm-app elm-firestore-crud
cd elm-firestore-crud
elm-app start
参考: https://github.com/halfzebra/create-elm-app
public/index.htmlの編集
Firebaseとbulmaが利用できるように、コードを挿入します。
<!-- Firebase App is always required and must be first -->
<script src="https://www.gstatic.com/firebasejs/5.9.1/firebase-app.js"></script>
参考: https://firebase.google.com/docs/web/setup
src/index.jsの編集
src/index.jsに、firebaseConfigや、elm用のportなどを記載していきます。
const config = {} の中身は、上記のFirebaseの初期設定でのグレーの部分のご自身のアプリのデータをコピペしてください。
import './main.css';
import { Elm } from './Main.elm';
import registerServiceWorker from './registerServiceWorker';
const app = Elm.Main.init({
node: document.getElementById('root')
});
const config = {
apiKey: "<YOUR-API-KEY>",
authDomain: "<YOUR-APP>.firebaseapp.com",
databaseURL: "https://<YOUR-APP>.firebaseio.com",
projectId: "<YOUR-APP-ID>",
storageBucket: "<YOUR-APP>.appspot.com",
messagingSenderId: "<YOUR-APP-SENDERID>"
};
firebase.initializeApp(config);
const DB = firebase.firestore();
registerServiceWorker();
Createの実装
https://blog.katsubemakito.net/firebase/firestore_readwrite_1 によると、以下のように解説されています。
コレクションに対してadd()
メソッドを実行すると、コレクション内にドキュメントが作成されます。
指定したコレクションが存在しない場合は自動的に新規作成されるとのことです。
db.collection("users").add({
name: "マイメロ",
age: 27
})
.then((doc) => {
console.log(`追加に成功しました (${doc.id})`);
})
.catch((error) => {
console.log(`追加に失敗しました (${error})`);
});
これをどうやってElmと関連づけるか、、、
まず、どんなアプリかというと、以下の4つのボタンがあり、TODO(文字列)のリストが表示されたものを作ろうとします。(→写経元サイト)
- 追加
- 表示
- 更新
- 削除
MODELとしては、
https://github.com/adash333/elm-todo-localstorage2/blob/master/src/Main.elm
を参考にすると、newTodoとtodoListでしょうか?
---- MODEL ----
type alias Model =
{ newTodo : String
, todoList : List String
}
emptyModel : Model
emptyModel =
{ newTodo = ""
, todoList = []
}
init : Maybe Model -> ( Model, Cmd Msg )
init flags =
( Maybe.withDefault emptyModel flags
, Cmd.none
)
しかし、これだと、どうしても、index.jsで新規todoの内容を受け取って、firestoreに送ることが難しい。
// index.jsはこうなる?
app.ports.postTodo.subscribe(text => {
//const user = firebase.auth().currentUser;
DB.collection('users').add({
todo: text
});
});
---- Main.elmのUPDATEはこうなる? ----
---- UPDATE ----
type Msg
= Change String
| Add
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Change str ->
( { model | newTodo = str }
, Cmd.none
)
Add ->
( { model
| todoList = model.newTodo :: model.todoList
, newTodo = ""
}
, Cmd.none
)
ということで、MODELからやりなおし。
---- MODEL ----
type alias Todo =
String
type alias Model =
{ todo : Todo, history : List Todo }
emptyModel : Model
emptyModel =
{ todo = "", history = [] }
init : Maybe Model -> ( Model, Cmd Msg )
init flags =
( Maybe.withDefault emptyModel flags
, Cmd.none
)
Elmのエラーの言われるがままに、いろいろ変更すると、なんだか、UPDATEはこんな感じでしょうか。。。
---- UPDATE ----
type Msg
= ChangeTodo Todo
| Submit
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ChangeTodo todo ->
( { model | todo = todo }
, Cmd.none
)
Submit ->
( { model
| todo = ""
, history = model.history
}
, postTodo
model.todo
)
たかがAddするだけなのに、何時間もかかっています。。。とほほ。。。
疲れたのでGistに、、、
しかし、FirebaseにログインしてFirebaseのデータベースの内容を見ても、全く反映されず。。。Elm,,,無理かも。。。
次の日に、firebase realtime databaseを用いたTODOアプリのソースコードを見つけました!感謝!
https://github.com/Goryudyuma/elm-todo
その後、、、特定のデータは追加できましたが、、、
むちゃくちゃですが、とりあえず、firestoreにデータを追加することはできました。まだまだ調整が必要ですが、、、
index.html
<!DOCTYPE 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>
<script src="https://www.gstatic.com/firebasejs/6.0.2/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/6.0.2/firebase-firestore.js"></script>
<script>
var firebaseConfig = {
apiKey: "XXXXXXXXXXX",
authDomain: "image-upload-XXXXXXXXXXX.firebaseapp.com",
databaseURL: "https://image-upload-XXXXXXXXXXX.firebaseio.com",
projectId: "image-upload-XXXXXXXXXXX",
storageBucket: "XXXXXXXXXXX.appspot.com",
messagingSenderId: "1XXXXXXXXXXX",
appId: "1XXXXXXXXXXX"
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const DB = firebase.firestore();
var storedState = localStorage.getItem('elm-todo-save3');
var startingState = storedState ? JSON.parse(storedState) : null;
var app = Elm.Main.init({
node: document.getElementById('elm'),
flags: startingState
});
app.ports.setStorage.subscribe(function(state) {
localStorage.setItem('elm-todo-save3', JSON.stringify(state));
DB.collection("users").add({
name: "ノラクロ",
age: 55
})
.then((doc) => {
console.log(`ノラクロ追加に成功しました (${doc.id})`);
})
.catch((error) => {
console.log(`ノラクロ追加に失敗しました (${error})`);
});
DB.collection("foo").add(
state
)
.then((doc) => {
console.log(`foo追加に成功しました (${doc.id})`);
})
.catch((error) => {
console.log(`foo追加に失敗しました (${error})`);
});
});
/*
app.ports.push.subscribe(text => {
const user = firebase.auth().currentUser;
DB.collection('foo').doc(user.uid).set({input: text});
});
*/
</script>
</body>
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, field, string)
---- MODEL ----
type alias Model =
{ newTodo : String
, todoList : List String
}
emptyModel : Model
emptyModel =
{ newTodo = ""
, todoList = []
}
init : Maybe Model -> ( Model, Cmd Msg )
init flags =
( Maybe.withDefault emptyModel flags
, Cmd.none
)
---- UPDATE ----
type Msg
= Change String
| Add
| Delete Int
| KeyDown Int
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 )
---- 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 localStorage3" ]
]
]
]
, section [ class "section" ]
[ div [ class "container" ]
[ section []
[ figure [ class "image container is-128x128" ]
[ img [ src "./logo.svg" ] []
]
]
]
]
, 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 "https://i-doctor.sakura.ne.jp/font/?p=37627" ] [ 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)
---- PROGRAM ----
main : Program (Maybe Model) Model Msg
main =
Browser.element
{ init = init
, view = view
, update = updateWithStorage
, subscriptions = \_ -> Sub.none
}
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.
-}
updateWithStorage : Msg -> Model -> ( Model, Cmd Msg )
updateWithStorage msg model =
let
( newModel, cmds ) =
update msg model
in
( newModel
, Cmd.batch [ setStorage newModel, cmds ]
)
上記2つのファイル作成後、
elm make src/Main.elm --output=main.js
してから、index.htmlを開くと、なんか文字を入力するたび(ElmのUPDATEが呼ばれるたび)に、Firestoreにデータが追加されまくっていました。
elm以前に、素のJavaScriptの知識が必要ですね。。。
続きはまた今度、、、
ディスカッション
コメント一覧
まだ、コメントがありません