はじめに
前の記事でElmでFirebase Authする方法を書いたので、その流れでFirestoreにデータを追加する方法を書きます。
基本的にやってること同じなので前と似たようなことしか書いてないです(なんで書いた?)
間違ったこと書いてたら罵詈雑言でも構いませんので教えてください。
やりたいこと
Google SignIn後に、フォームにTitleとContentsを入力して、Saveボタンを押すと、Firestoreに文字列を追加します。
以下がFirestoreの画面です。ちゃっかり追加した時刻も入れています。
開発環境
Mac
create-elm-app使用
コード全体
こちら参照ください
index.jsには各自のFirebaseのapiKey等を記載してください。
大まかな流れ
- Firebase FireStoreの初期設定
- Elm側でコンテンツ入力フォームを作る
- フォームに入力されたコンテンツ情報をJavaScriptに送る
- JavaScriptでコンテンツ情報を受け取り、Firestoreに追加する
- 追加結果をElmで受け取る
1. Firebase FireStoreの初期設定
ここでは特に説明しません。ググればすぐに出てきます。この記事とか良さそうです(適当)
2. Elm側でコンテンツ入力フォームを作る
入力フォームの書き方は前の記事の「2. Elm側でemail、パスワード入力フォームを作る」で書いたので省略します
←めんどくさいだけ
3. フォームに入力されたコンテンツ情報をJavaScriptに送る
PORT
フォームに入力したコンテンツ情報を、portを使ってJavaScriptに送り、JavaScriptでfirestoreに追加します。
portに関しての詳しい解説は公式ドキュメントや他の方の記事を読むと良いと思います。
port saveContents : Encode.Value -> Cmd msg
UPDATE
例によってコンテンツ情報をJavaScriptに送るためには、"エンコード"する必要があります。
エンコードに関しての詳しい解説は公式ドキュメントや他の方の記事を読むと良いと思います。
update : Msg -> Model -> ( Model, Cmd Msg )
update msg =
case msg of
...
SaveMsg saveMsg ->
updateSaveContent saveMsg
...
-- エンコードした文字列をportに引数として渡す
updateSaveContent : SaveMsg -> Model -> ( Model, Cmd Msg )
updateSaveContent msg model =
case msg of
SaveContent ->
( model, saveContents <| contentsInfoEncoder model )
-- titleとcontentのエンコード
contentsInfoEncoder : Model -> Encode.Value
contentsInfoEncoder model =
Encode.object
[ ( "title", Encode.string model.contents.title )
, ( "content", Encode.string model.contents.content )
]
VIEW
コンテンツをFirestoreに追加するSaveボタンも作っておきます。
view : Model -> Html Msg
view model =
div []
[ ...
, Html.map SaveMsg viewSaveButton
...
]
viewSaveButton : Html SaveMsg
viewSaveButton =
div []
[ button [ onClick SaveContent ] [ text "Save" ] ]
4. JavaScriptでコンテンツ情報を受け取り、Firestoreに追加する
初期設定
JavaScript側の初期設定です。
自分はcreat-elm-appを使っているので、appの定義とかは皆さんと異なるかもしれません。
とりあえずappの定義は忘れずにやってください。
firebase関係のコードは基本的に同じになると思います。
...
import firebase from "firebase/app";
import "firebase/analytics";
import "firebase/auth";
import "firebase/firestore";
// ここが異なるかも
const app = Elm.Main.init({
node: document.getElementById("root"),
});
const firebaseConfig = {
// 各自のapi key等を記載してください
};
// firebaseの初期化
firebase.initializeApp(firebaseConfig);
const Googleprovider = new firebase.auth.GoogleAuthProvider();
const DB = firebase.firestore();
コンテンツ情報を受け取り、Firestoreに追加する
こちらの公式ドキュメントに従って、Firestoreにデータを追加します。
portを使用してJavaScriptとデータのやり取りをしたい場合、以下のように、app.ports.(Elm側で記載したポート名).subscribe...というように記載します。
app.ports.saveContents.subscribe((Contents) => {
//現在ログインしているユーザー
const user = firebase.auth().currentUser;
//サーバーの時間
const time = firebase.firestore.FieldValue.serverTimestamp();
//コンテンツを追加する参照先(.doc()でidを自動生成)
const ref = DB.collection("Test").doc(user.uid).collection("Contents").doc();
ref
//コンテンツの追加を行う
.set(
{
//Elm側でエンコードしたtitle, content
Title: Contents.title,
Content: Contents.content,
//データ追加時のサーバーの時間
timestamp: time,
},
//コンテンツを上書きするかしないかの指定。Trueにすると上書きせず、ドキュメントにコンテンツが新規追加されていく。
{ merge: true }
)
//追加に成功したらPass, 失敗したらFailをElmに返す
.then(() => {
app.ports.validateFirestore.send("Pass");
})
.catch(() => {
app.ports.validateFirestore.send("Fail");
});
});
5. 追加結果をElmで受け取る
PORT
JavaScriptから認証状態の文字列を受け取る用のportsを用意します。
文字列を受け取るので(String -> msg)にしています。
port validateFirestore : (String -> msg) -> Sub msg
MODEL
追加の状態を表すカスタム型を定義します。
type FirestoreState
= Pass
| Fail
type alias Model =
{ ...
, firestoreState : FirestoreState
...
}
SUBSCRIPTIONS
portで受け取ったデータをElmのUPDATEで使用するために、SUBSCRIPTIONに以下のように記載します。
カスタム型を使用しているので、Sub.mapを使用しています。
また以下のように複数のSbuscriptionを書きたいときは、Sub.batchを使用します。
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ validateAuthState ValidateAuthState
|> Sub.map ValidateAuthStateMsg
, validateFirestore ValidateFirestore
|> Sub.map ValidateFirestoreMsg
]
UPDATE
JavaScriptから受け取った文字列に応じて、firebaseStateを更新します。
type ValidateFirestoreMsg
= ValidateFirestore String
type Msg
= ...
| ValidateFirestoreMsg ValidateFirestoreMsg
update : Msg -> Model -> ( Model, Cmd Msg )
update msg =
case msg of
...
ValidateFirestoreMsg validateSaveContentsMsg ->
updateValidateFirestoreState validateSaveContentsMsg
--受け取った文字列に応じてfirestoreStateを更新する
updateValidateFirestoreState : ValidateFirestoreMsg -> Model -> ( Model, Cmd Msg )
updateValidateFirestoreState msg model =
case msg of
ValidateFirestore contentsmsg ->
case contentsmsg of
"Fail" ->
( { model | firestoreState = Fail }
, Cmd.none
)
"Pass" ->
( { model | firestoreState = Pass }
, Cmd.none
)
_ ->
( model, Cmd.none )
VIEW
データ追加の結果を表示します。
firebaseStateに応じて表示する文字列を変更しています。
view : Model -> Html Msg
view model =
div []
[ ...
, viewValidateFirestore model
]
viewValidateFirestore : Model -> Html Msg
viewValidateFirestore model =
case model.firestoreState of
Pass ->
div []
[ div [] [ text "Firestore Status: Pass" ]
]
Fail ->
div []
[ div [] [ text "Firestore Status: Fail" ] ]
以上でできました!
##参考文献
・Qiitaに投稿してくださった記事(参考になります本当にありがとうございます!!)
Elm portsでFirebase Firestoreを触ろう!
・Firebase系
[Firebase Firestoreの初期設定]
(https://www.techpit.jp/courses/13/curriculums/14/sections/133/parts/515)
Firestoreへのデータの追加
・Elm公式ドキュメント
型エイリアス
カスタム型
ポート
Json.Encode
Platform.Sub
・関連する自分の記事
【Elm】Firebase Authenticationでパスワード認証をする