Firebase Realtime Databaseで遊んでみた件

2019/03/28Database

ヒロシです。
FirebaseとはGoogleが提供しているmBaasです。mBaasって?という方はググってください。
今回、その機能の中に、「Realtime Database」なるものが存在するということをお客様よりお聞きし、
さっそく遊んでみました。というお話です。

Realtime Databaseとはなんぞや?

https://firebase.google.com/

「アプリデータを瞬時に保存および同期」

うん、なんとなくわかった。
サーバとクライアント間で同期できるNoSQLを使った仕組みで、チャットみたいなのも簡単に?作れるんだよね、きっと。
(という、ふんわりとした理解)

そんなこんなで、mBaasだもんで、UnityやらAndroidやら、そんなとこで使うもんなのかな?と
Firebaseのドキュメントを読み進めてたところ、
C#やAndroid、Unityというワードに、ごくごく当たり前のように潜んでいる
『ウェブ』なんていう、何とも抽象的な「そこにシビれる!あこがれるゥ!」な、
技術ドキュメントあるあるワードを発見。

サンプルコードもあったので見てみると、シンプルなjavascriptですね、これ簡単そう。

ということで、最近モノ忘れの激しい社内メンバーのために、リアルタイム共有TODOツールを作ってみたYO!

今回作りたいもの

  • TODOを登録できる
  • リアルタイムに共有できる
  • 完了したら削除できる

至ってシンプルですが、まずはこんなところでしょうか。

まずは初期設定

https://qiita.com/H_Crane/items/adf88cc01eabce2b9a5f

この辺りを参考にさせてもらって、まずは登録して、プロジェクト作って、
ウェブ用のconfigをコピー(下で使ってます)、と。

そして、今回作成するのは「todo.html」1枚だけ!(たぶん)

登録するよ

まずはこんな感じなのかね?
(Firebaseのプロジェクトの設定からコピーできるやつです)

<script src="https://www.gstatic.com/firebasejs/5.9.1/firebase.js"></script>
<script> // Initialize Firebase var config = { apiKey: hoge, authDomain: hoge, databaseURL: hoge, projectId: hoge, storageBucket: hoge, messagingSenderId: hoge }; firebase.initializeApp(config); </script>

でもって、データベースを参照する場合はこんな感じらしい

const database = firebase.database();

そんで、これでノードを指定するのね

const ref = database.ref('todo');

とりあえず、ここまでで準備は完了!まずは登録を。
今回、登録したい項目として、

  • タイトル
  • 内容
  • 期限
  • 担当者

が入力できる適当なフォームを作りました。
こんな。

<div>
    <div>
        <input type="text" id="title" placeholder="タイトル(必須)">
    </div>
    <div>
        <textarea id="content" placeholder="内容(任意)"></textarea>
    </div>
    <div>
        <input type="text" id="end_date" placeholder="タスクの期限を設定">
    </div>
    <div>
        <select id="person">
            <option value="未指定">担当者を選んでください</option>
            <option value="おれ">おれ</option>
            <option value="あいつ">あいつ</option>
        </select>
    </div>
    <div>
        <button id="post">登録する</button>
    </div>
</div>

divdivしちゃってるのは、あとで何かスタイルつけようかと思ってるだけ。
ブラウザでアクセスしてみましょう。

で、登録アクションを作ります。
内部ツールなのでタイトルだけ処理側で必須にして、弾かれてもエラーメッセージもなんもなし!
そう、これを漢(をとこ)ツールと勝手に呼ぶ。

// 登録処理
const postAction = () => {
    const title = $("#title").val();
    const content = $("#content").val();
    const end_date = $("#end_date").val();
    const person = $("#person").val();
    if(title && title !== "") {
        ref.push({
            title: title,
            content: content,
            person: person,
            end_date: end_date,
            date: new Date().getTime()
        });
    }
    // とりあえず登録終わったら空にしとく
    $("#title").val("");
    $("#content").val("");
    $("#end_date").val("");
    $("#person").val("未指定");
};

// 登録時のアクション
$('#post').click(() => postAction());

そう、気づいた人もいるだろう。
漢ツールはエスケープなんてものもしないのです。

NOエスケープ NOバリデーション!

これぞ漢(をとこ)の浪漫!
(お仕事ではちゃんとやりますので安心してくださいね)

で、指定したノードにpushってやるだけで子ノードが追加できるんだと。
ほんとかね? 実際に登録してみる。

こんな感じで、適当に入力して「ポチっ」と。

Firebaseの管理画面で確認すると、、
おおおお!!いけてますわー。(ちょっと感動)
どんどんいきましょ、次へ。

一覧表示してみる

// 初期表示と登録後のコールバック
ref.on("child_added", (snapshot) => {
    dispTodo({
        id: snapshot.key,
        value: snapshot.val()
    });
});

onの第一引数にリスナーっての指定してあげると、
リスナーに対応したイベントがトリガーとなって、この部分がコールバックされて動くみたい。
何言ってるかわからんって?
とりあえず、公式を以下に引用しておくので各自読み解いてくださいな。

value 特定のデータベース パスにあるコンテンツの静的なスナップショットを、読み取りイベントのときに存在していたとおりに読み取るために使用します。これは、初期データで 1 回トリガーされます。さらに、データが変更されると、そのたびに再びトリガーされます。イベントのコールバックには、その場所にあるすべてのデータ(子のデータも含む)を含んでいるスナップショットが渡されます。上記のコード例で、value はアプリ内のすべてのブログ投稿を返しています。新しいブログ投稿が追加されるたびに、コールバック関数がすべての投稿を返します。
child_added 通常、データベースからアイテムのリストを取得するために使用します。その場所にあるコンテンツ全体を返す value とは異なり、child_added は既存の子ごとに 1 回トリガーされます。さらに、指定されたパスに新しい子が追加されると、そのたびに再びトリガーされます。イベント コールバックには、新しい子のデータを含んでいるスナップショットが渡されます。並べ替え目的のため、前の子のキーを含んでいる 2 番目の引数も渡されます。
child_changed 子ノードが修正されると、そのたびに child_changed イベントがトリガーされます。この修正には、子ノードの子孫に対する修正も含まれます。これはアイテムのリストに対する変更に応答するために通常、child_added や child_removed と組み合わせて使用されます。イベント コールバックに渡されるスナップショットには、子の更新済みデータが含まれています。
child_removed 直接の子が削除されるとトリガーされます。これは通常、child_added や child_changed と組み合わせて使用されます。イベントのコールバックに渡されるスナップショットには、削除された子のデータが含まれています。

ひとまず、今回はchild_addedとchild_removedを使えばよさそう。
ってことでまずは初期表示と登録を行うためにchild_addedをチョイス。
dispTodoという実際にTODO表示を行う関数(後述)を呼び出す想定でこんな感じに。
さて、snapshotはなんぞや?という話になりますが、
これは上のコールバックが呼ばれた際に受信する静的スナップショットなんだそう。
なんかわからんけど、key:valueで来るのね。

じゃ、実際の表示処理を作ってみよう。

// TODOを表示する
const dispTodo = (todo) => {
    let end_date = "";
    // 期日があれば「xxまでに」という文字列を作る
    if(todo.value.end_date) {
        end_date = todo.value.end_date + "までに";
    }
    // TODO内容をリストの一番上に挿入
    const todo_html = todo.value.title + "<br />" + todo.value.content + "<br />" + todo.value.person + "が" + end_date  + "やる";
    $("#todo_list").prepend(`<div id="${todo.id}">${todo_html}</div>`);
}

とりあえず、下のような空のdivを作っておいて、そこに渡されたノードの情報をどんどん出していく作戦。

<div id="todo_list"></div>

リロード。でた。(ちょっと感動)

削除する

片付いたタスクは消すことができないと困りますね。
ということで、削除機能を作ります。
まずはボタン。『削除』ってのもなんかアレなんで『DONE』というボタンを作ってみました。

// TODOを表示する
const dispTodo = (todo) => {
    let end_date = "";
    // 期日があれば「xxまでに」という文字列を作る
    if(todo.value.end_date) {
        end_date = todo.value.end_date + "までに";
    }
    // TODO内容をリストの一番上に挿入
    const todo_html = todo.value.title + "<br />" + todo.value.content + "<br />" + todo.value.person + "が" + end_date  + "やる";
    $("#todo_list").prepend(`<div id="${todo.id}">${todo_html}<button class="done">DONE</button></div>`);
}

こんな感じで、しょーもないボタンを。

次に削除処理も作りましょ。
ボタンもどんどん動的に追加されていくので、documentのonでセレクタを指定してやる。
イベントの犯人を捕まえて、その親divのid(※)を取ってきて、削除リクエストを投げる、というプレイ。
※各TODOを挟んでいる<div id="${todo.id}">

// 削除処理
$(document).on('click', '.done', (event) => {
    const id = $(event.target).closest('div').attr('id');
    firebase.database().ref('todo/' + id).remove();
});

/keyで目的のノードを操れるようだ。
※登録時にkeyは指定していないが、Firebase側でユニークIDが勝手に付与される、というのはどこかで知りました。という前提ですw

次に削除時のコールバック

/
// 削除
ref.on("child_removed", (snapshot) => {
    $("#"+snapshot.key).remove();
});

出ました、リスナー。
迷わず「child_removed」でコールバックされることを期待しつつ、受け取ったkeyに対応するTODOのdivごと葬る。

よし、削除してみましょ!
ポチっとな!

できた。(あっさり)

さてさて、ほんとに同期とれてるの?
同期されてなければただのしょーもないHelloWorldに毛の生えたWebシステムもどきだし。

これ、複数ブラウザ開いて試してみてください。
どっちかで登録すると、もう一方に出現。
削除すると、もう一方からも消滅。
たいしたことしてないからこその、大感動がここにあるっ!

あとはちょいちょいとスタイルなんかつけちゃって、こんな感じの仕上がり♪

それにしてもHTML一枚、サーバレスでこんなことできる時代になったんですね。
おじさん、困っちゃう。

じゃ、またね!