【Firebase】Realtime Database（version 9）データ操作あれこれ

# 前提

- Firebase JavaScript SDK version9を使用
- chrome（バージョン: 101.0.4951.54）

# 準備

## SDK をインポート、初期化

- SDK（Software Development Kit）とは
ソフトウェアを開発するためのあれこれが詰め込まれたキット（一式）

> [SDKとは](https://wa3.i-3-i.info/word13150.html)
> 

```jsx
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.8.1/firebase-app.js";
//使用するメソッドをインポート
import { getDatabase, ref, push, set, onChildAdded, remove, onChildRemoved, onValue } from "https://www.gstatic.com/firebasejs/9.8.1/firebase-database.js";

//Realtime Database管理画面に表示された値を入力
const firebaseConfig = {
  apiKey: "XXXXXXXXXXXXXXXXXX",
  authDomain: "XXXXXXXXXXXXXXXXXX",
  projectId: "XXXXXXXXXXXXXXXXXX",
  storageBucket: "XXXXXXXXXXXXXXXXXX",
  messagingSenderId: "XXXXXXXXXXXXXXXXXX",
  appId: "XXXXXXXXXXXXXXXXXX",
};

//初期化
const app = initializeApp(firebaseConfig);
//データベースサービスへの参照を取得
const db = getDatabase(app);
```

> [【バージョン9】利用可能なFirebaseJSSDK（公式）](https://firebase.google.com/docs/web/learn-more?hl=ja#available-libraries)


# データの保存

## set

指定したパスに書き込みを行う。
既存データで`username` と `email` の2つのkeyがあった場合、再度 `username` に新たな値を保存しようとすると `email` が消えてしまう。
特定のkeyのみ任意のデータを保存したい場合は `update` を使用する。

```jsx
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.8.1/firebase-app.js";
import { getDatabase, ref, set } from "https://www.gstatic.com/firebasejs/9.8.1/firebase-database.js";

const app = initializeApp(firebaseConfig);
const db = getDatabase(app);

const auth = getAuth();
set(ref(db, 'users/' + "ユーザーID"), {
  username: "任意の値",
  email: "任意の値",
});
```

# データの更新

## update

特定のkeyのみ任意のデータを保存を保存する。

```jsx
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.8.1/firebase-app.js";
import { getDatabase, ref, set } from "https://www.gstatic.com/firebasejs/9.8.1/firebase-database.js";

const app = initializeApp(firebaseConfig);
const db = getDatabase(app);

const auth = getAuth();

// chat/users/ユーザーIDのnameを更新する
update(ref(db, "chat/users/" + auth.currentUser.uid), {
			name: "任意の値",
});
```
# データの削除

## remove

- 特定のディレクトリごと削除する

```jsx
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.8.1/firebase-app.js";
import { getDatabase, ref, push, set, onChildAdded, remove, onChildRemoved, onValue } from "https://www.gstatic.com/firebasejs/9.8.1/firebase-database.js";

const firebaseConfig = {}
const app = initializeApp(firebaseConfig);
const db = getDatabase(app);
const dbref = ref(db, "chat/www"); //削除したいデータのパスを指定

document.getElementById("delete").addEventListener("click", function () {
		remove(dbref);
});
```

- 特定のデータのみ削除したい場合
削除したいデータのユニークキーを末尾に入れる。

```jsx
//削除したいデータのユニークキーを指定
const dbref = ref(db, "chat/www/-N1tBd7XQJeUgymBbrbq");
```

# データリストの操作

データベースの階層は下記のディレクトリとする。

```bash
chat
   └ www
       ┝ -N1tBd7XQJeUgymBbrbq
       │ └ text:"AAA"
       ┝ -N1wM58eG8pztMDv7gFJ
       │ └ text:"BBB"
       └ -N1tBd7XQJeUgymBbrbq
         └ text:"CCC"
```

> [ウェブ上でデータリストを操作する（公式）](https://firebase.google.com/docs/database/web/lists-of-data?hl=ja)

## 書き込み

```jsx
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.8.1/firebase-app.js";
import { getDatabase, ref, push, set, onChildAdded, remove, onChildRemoved, onValue } from "https://www.gstatic.com/firebasejs/9.8.1/firebase-database.js";

const app = initializeApp(firebaseConfig);
const db = getDatabase(app);
const dbref = ref(db, "chat/www"); //データを書き込むパスを指定

document.getElementById("submit").addEventListener("click", function () {
	//登録するデータを定義
  const data = {
    text: document.getElementById("text").value
  };
  const newPostRef = push(dbref); //ユニークキーを生成
  set(newPostRef, data); //書き込み
});
```

【結果】
`key` が `text` 、 `value` は `"取得した値"` で保存される。

```bash
chat
   └ www
       └ -N1tBd7XQJeUgymBbrbq
         └ text:"取得した値"
```

## データ操作を監視する

### onChildAdded

指定されたパスに保存しているデータの数の分だけトリガーされる。
さらに、指定されたパスに新しいデータが追加されると、そのたびに再度トリガーされ、引数のsnapshotに取り出したデータの個数分のオブジェクトが入る。
データの個数分イベントが発生しているのでループ処理をしなくても1個ずつconsoleに出力できる。

```jsx
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.8.1/firebase-app.js";
import { getDatabase, ref, push, set, onChildAdded, remove, onChildRemoved, onValue } from "https://www.gstatic.com/firebasejs/9.8.1/firebase-database.js";

const firebaseConfig = {}
const app = initializeApp(firebaseConfig);
const db = getDatabase(app);
const dbref = ref(db, "chat/www"); //取得したいデータのパスを指定

onChildAdded(dbref, function (snapshot) {
	let data = snapshot.val();
	console.log(data);
});

// console
{text: 'AAA'}
{text: 'BBB'}
{text: 'CCC'}
```

- 取得したデータを取り出す場合

```jsx
onChildAdded(dbref, function (snapshot) {
	let data = snapshot.val();
	//keyを取り出す
	console.log(snapshot.key);
	//valueを取り出す
	console.log(data.text);
});

// console
-N1tBd7XQJeUgymBbrbq
AAA
-N1wM58eG8pztMDv7gFJ
BBB
-N1tBd7XQJeUgymBbrbq
CCC
```

### onChildRemoved

`onChildAdded` の逆バーション。
指定されたパス直下のデータが削除されるとトリガーされる。
引数のsnapshotには、削除されたデータのオブジェクトが入る。

```jsx
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.8.1/firebase-app.js";
import { getDatabase, ref, push, set, onChildAdded, remove, onChildRemoved, onValue } from "https://www.gstatic.com/firebasejs/9.8.1/firebase-database.js";

const firebaseConfig = {}
const app = initializeApp(firebaseConfig);
const db = getDatabase(app);

//chat/www/内の text: 'AAA' を削除
document.getElementById("delete").addEventListener("click", function () {
	// chat/www/内の text: 'AAA' のユニークキーを指定
	remove(ref(db, "chat/messages/-N1tBd7XQJeUgymBbrbq"));
});

//削除ボタンが押されるとsnapshotに削除されたデータが入ってくる
onChildRemoved(ref(db, "chat/messages"), function(snapshot){
	console.log(snapshot.key);
	console.log(snapshot.val());
});

//console
-N1tBd7XQJeUgymBbrbq
{text: 'AAA'}
```

### onValue

指定されたパスに新しいデータが追加されるまたはデータが削除されると、そのたびにトリガーされ、オブジェクト（スナップショット）をリストとして取得できる。

```jsx
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.8.1/firebase-app.js";
import { getDatabase, ref, push, set, onChildAdded, remove, onChildRemoved, onValue } from "https://www.gstatic.com/firebasejs/9.8.1/firebase-database.js";

const firebaseConfig = {}
const app = initializeApp(firebaseConfig);
const db = getDatabase(app);
const dbref = ref(db, "chat/www"); //取得したいデータのパスを指定

onValue(dbref, function(snapshots){
		console.log(snapshots);
});

//console
オブジェクト（DataSnapshot）が入った配列
```

- 取得したデータを取り出す場合
取得したデータを取り出すにはループ処理が必要。

```jsx
onValue(dbref, function(snapshots){
	snapshots.forEach(function(snapshot){
		let data = snapshot.val();
		console.log(snapshot.key); //keyを取り出す
		console.log(data.text); //valueを取り出す
	});
});

//console
-N1tBd7XQJeUgymBbrbq
AAA
-N1wM58eG8pztMDv7gFJ
BBB
-N1tBd7XQJeUgymBbrbq
CCC
```

## データを並びかえる

データベースの階層は下記のディレクトリとする。

```bash
chat
   └ www
       ┝ -N1tBd7XQJeUgymBbrbq
       │ └ create_at: 1653225899020
       ┝ -N1wM58eG8pztMDv7gFJ
       │ └ create_at: 1653225897020
       └ -N1tBd7XQJeUgymBbrbq
         └ create_at: 1653225898020
```

### orderByChild

`www` 直下にあるデータを `create_at` のUnixTime順で取得し、`getChat` 関数を実行。

```jsx
onValue(query(ref(db, "chat/www"), orderByChild("create_at")), getChat);
```

> [データを並べ替える（公式）](https://firebase.google.com/docs/database/web/lists-of-data?hl=ja#sort_data)

## データのフィルタリング

### limitToFirst

`www` 直下にあるデータの１番目から2件のみ取得し、`getChat` 関数を実行。

```jsx
onValue(query(ref(db, "chat/www"), limitToFirst(2)), getChat);
```

### 任意のキーワードに該当するデータを表示

`<input type="text" id="search_keyword">` で文字が入力される度にイベントを発火させ、入力した文字列で`calender/schedules` 直下に保存されているスケジュールのタイトルから該当するものを取得。入力したキーワードにて前方一致検索を行う。

```html
<input type="text" id="search">
```


```jsx
document.getElementById("serch").addEventListener("input", addSchedule);

const searchSchedule = function () {
	const keyword = document.getElementById("search_keyword").value;
	get(query(ref(db, "calender/schedules"), orderByChild("title"), startAt(keyword), endAt(keyword + '\uf8ff')))
		.then(function (snapshots) {
			//成功した時の処理
		}).catch(function (e) {
			//成功した時の処理
		});
}
```


> [データのフィルタリング（公式）](https://firebase.google.com/docs/database/web/lists-of-data?hl=ja#filtering_data)

# 最後に

何か間違っている箇所がありましたら、ご指摘くださると幸いです。

# 参考

- [Fetch/Get All Data from Firebase Realtime DB (Version 9) in TABLE using JavaScript（Youtube）](https://www.youtube.com/watch?v=KnAsYNhI_CY)
- [Getting Started with the Firebase Realtime Database on the Web - Firebase Fundamentals（Firebase公式YouTube）](https://www.youtube.com/watch?v=pP7quzFmWBY)
- [【Firebase】Authentication（version 9）の使い方](https://qiita.com/shiho97797/items/22cbda84274bfecde98f)
- [Firebase Realtime Databaseのクエリの書き方(JavaScript version9対応)](https://qiita.com/GrowUp-Haruno/items/f956f424c9a6e5fd3480)