この記事では.
part1では,環境構築を行いました.
今回は,firebaseとの連携を行い実際にtodo-appを作ろうと思います.
(最近流行りの構成なので,vue-firebaseで調べるとたくさんありました.特にオリジナリティはないです.勉強なので..)
firebase
たくさん機能があります.各機能を3行でまとめてくれてます.
Firebaseの各機能を3行で説明する
今回は2つの機能を使います.
- Firebase Realtime Database
- Firebase Authentication
データ構造
- マスターデータ:category,state
- トランザクションデータ:item
(今にして思うと,stateは優先順位なのでpriority,基本的に複数形で命名すべきでした..)
データ操作
最後にコンポーネントごとのソース張り付けてます(github上にあげろという話ですが..)
抜粋して連携部分のみ貼り付けます.少し整形しています.
データ取得
2通りのパターンで取得します.同期する方法と1回きり取得する方法です.
getData:function () {
// firebaseからデータを取得する
this.database = firebase.database();
// アイテムはリアルタイムで更新
this.spotsRef = this.database.ref("item");
const _this = this;
this.spotsRef.on("value", function (snapshot) {
_this.item = snapshot.val();
});
// ここからはマスタデータなので1回取得でいい
this.spotsRef = this.database
.ref("category")
.once("value")
.then(snapshot => {
this.category = snapshot.val();
});
}
データ更新
新規の場合はchild("posts").push().keyで新規keyを取得する必要がある.
削除の場合は違うコマンドがありますが,nullでアップデートすると同様な挙動になるらしいので,今回は全てupdateで対応しました.
new: function() {
const data = { ...this.inputData, isDone: false };
// 新規のkeyを取得
const key = firebase
.database()
.ref("item")
.child("posts")
.push().key;
}
// 登録内容objを作る
const updates = {};
updates["/item/" + key] = data;
// 更新
firebase
.database()
.ref()
.update(updates);
auth設定
今回はgoogleの承認を利用します.対象行をクリックして有効にするだけです.
連携方法
今回は,signInWithPopupという方法を使います.このおかげで画面などを用意する必要はありませんでした.
// ログイン
login: function() {
// グーグルの承認
const provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider);
// authの情報を取得
this.onAuth();
},
// ログアウト
logout: function() {
firebase.auth().signOut();
this.onAuth();
},
onAuth: function() {
// 今回はgoogleの表示名のみをもらう.本来なら全てを保持したい?
firebase.auth().onAuthStateChanged(user => {
this.userName = user ? user.displayName : null;
});
}
ToDo-app
機能は以下のみです.
- Form
- List
pageがHomeでForm,Listがコンポーネントです.
Homeは最初にFiarebaseからデータを取得します.
Form,ListはFirebaseにデータを直接更新します.
Form,Listの切り替えはそれぞれemitでopenForm,closeFormという関数を呼び出して処理してます.
Form
登録フォームをコンポーネントにしました.(特に今回の規模では分けなくてもいいですけど練習として.)
<template>
<v-container>
<v-card>
<v-card-title>
<h2>Form</h2>
</v-card-title>
<v-card-text>
<v-form>
<v-text-field v-model="inputData.title" label="タイトル"></v-text-field>
<v-text-field v-model="inputData.text" label="内容"></v-text-field>
<v-select
v-model="inputData.category"
:items="category"
item-text="name"
item-value="id"
return-masked-value
single-line
label="分類"
></v-select>
<v-select
v-model="inputData.state"
:items="state"
item-text="name"
item-value="id"
return-masked-value
single-line
label="優先度"
></v-select>
<v-btn color="success" @click="commit">commit</v-btn>
<v-btn color="error" @click="closeForm">cancel</v-btn>
</v-form>
</v-card-text>
</v-card>
</v-container>
</template>
<script>
import firebase from "firebase";
export default {
name: "Form",
props: {
// idはない場合は新規
id: {
default: null
},
inputData: {},
category: {},
state: {}
},
methods: {
commit: function() {
// 新規でも編集でも登録するならば、ステータスは戻す
const data = { ...this.inputData, isDone: false };
let key = this.id;
// keyがない場合は新規
if (key == null) {
// 新規keyを取得
key = firebase
.database()
.ref("item")
.child("posts")
.push().key;
}
// 登録内容objを作る
const updates = {};
updates["/item/" + key] = data;
// 更新
firebase
.database()
.ref()
.update(updates);
// フォーム閉じる
this.closeForm();
},
closeForm: function() {
// 親にイベント送信
this.$emit("closeForm");
}
}
};
</script>
List
<template>
<v-container>
<v-layout row wrap>
<v-flex xs12>
<v-card>
<v-card-title>
<v-flex xs3>
<h2>リスト</h2>
</v-flex>
<v-flex xs6>
<v-checkbox v-model="displayNotIsDone" label="未実施のみ表示"></v-checkbox>
</v-flex>
<v-btn color="pink" dark absolute bottom right fab @click="openForm(null)">
<v-icon>add</v-icon>
</v-btn>
</v-card-title>
</v-card>
</v-flex>
<v-flex xs12 md6 v-for="(data,key) in displayItem" :key="key">
<v-card>
<v-card-title>
<v-flex xs1>
<v-checkbox v-model="data.isDone" color="red" @change="changeDone(key)"></v-checkbox>
</v-flex>
<v-flex xs11 class="indigo--text">
<v-badge right color="red">
<span v-if="data.state==0" slot="badge">!</span>
<h2>{{data.title}}</h2>
</v-badge>
<v-badge right color="red"></v-badge>
</v-flex>
<v-flex xs12>
<span>{{data.text}}</span>
</v-flex>
</v-card-title>
<v-card-text>
<v-layout row wrap>
<v-flex xs6>{{ getValue(category,data.category)}} / {{ getValue(state,data.state)}}</v-flex>
<v-flex xs6>
<v-btn color="error right" fab dark small @click="del(key)">
<v-icon>delete</v-icon>
</v-btn>
<v-btn color="indigo right" fab dark small @click="openForm(key)">
<v-icon>edit</v-icon>
</v-btn>
</v-flex>
</v-layout>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
import firebase from "firebase";
export default {
name: "List",
props: {
item: {
default: ""
},
category: {},
state: {}
},
data() {
return {
displayNotIsDone: false
};
},
computed: {
// 未実施のみなどで表示アイテムの条件が変わる
displayItem: function() {
const displayItem = {};
Object.keys(this.item).forEach(key => {
if (!this.displayNotIsDone || this.item[key].isDone) {
displayItem[[key]] = this.item[key];
}
});
return displayItem;
}
},
methods: {
openForm: function(key) {
// 親にイベント送信、編集時はペイロードでkeyも渡す
this.$emit("openForm", { id: key });
},
del: function(key) {
const updates = {};
// nullでアップデートを行うと削除と同じになる
updates["/item/" + key] = null;
firebase
.database()
.ref()
.update(updates);
},
// チェック変更でも更新する
changeDone: function(key) {
const data = this.item[key];
const updates = {};
updates["/item/" + key] = data;
firebase
.database()
.ref()
.update(updates);
},
// カテゴリと優先順位マスタからid指定でnameを取得
getValue: function(data, id) {
const index = data.findIndex(val => {
return val.id == id;
});
if (index !== -1) {
return data[index].name;
} else {
return null;
}
}
}
};
</script>
Home
<template>
<v-container grid-list-md>
<v-layout row wrap>
<v-flex xs12>
<v-card>
<v-card-title>
<h1>ToDoList</h1>
<v-btn v-if="userName == null" color="lime" small fab dark @click="login">
<v-icon>account_circle</v-icon>
</v-btn>
<v-btn v-else color="lime" small fab @click="logout">{{userName[0]}}</v-btn>
</v-card-title>
</v-card>
</v-flex>
<Form
v-if="addFormFlag"
:id="id"
:inputData="inputData"
:category="category"
:state="state"
@closeForm="closeForm"
></Form>
<List v-else :item="item" :category="category" :state="state" @openForm="openForm"></List>
</v-layout>
</v-container>
</template>
<script>
import firebase from "firebase";
import Form from "@/components/Form.vue";
import List from "@/components/List.vue";
export default {
name: "Home",
components: {
Form,
List
},
created: function() {
// firebaseからデータを取得する
this.database = firebase.database();
// アイテムはリアルタイムで更新
this.spotsRef = this.database.ref("item");
const _this = this;
this.spotsRef.on("value", function(snapshot) {
_this.item = snapshot.val();
});
// ここからはマスタデータなので1回取得でいい
this.spotsRef = this.database
.ref("category")
.once("value")
.then(snapshot => {
this.category = snapshot.val();
});
this.spotsRef = this.database
.ref("state")
.once("value")
.then(snapshot => {
this.state = snapshot.val();
});
},
data() {
return {
id: null,
database: null,
spotsRef: null,
item: {},
category: [],
state: [],
inputData: { title: null, text: null, category: null, state: null },
addFormFlag: false,
userName: null
};
},
methods: {
// リスト表示(子供から呼び出される)
closeForm: function() {
this.addFormFlag = false;
},
// フォーム表示(子供から呼び出される)
openForm: function(dict) {
// dictはペイロードでidが入っている
const id = dict.id;
// ない場合は新規
if (id != null) {
this.inputData = this.item[id];
} else {
this.inputData = this.inputInit();
}
this.id = id;
this.addFormFlag = true;
},
// 入力データを初期化
inputInit: function() {
return { title: null, text: null, category: null, state: null };
},
// ログイン
login: function() {
// グーグルの承認
const provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider);
// authの情報を取得
this.onAuth();
},
// ログアウト
logout: function() {
firebase.auth().signOut();
this.onAuth();
},
onAuth: function() {
// 今回はgoogleの表示名のみをもらう.本来なら全てを保持したい?
firebase.auth().onAuthStateChanged(user => {
this.userName = user ? user.displayName : null;
});
}
}
};
</script>
ログイン
ただ実装しました.特に機能はないです..
やるとしたら,itemをuserIdでネストして公開todo,プライベートtodoに分ける程度かと.
まとめ
vue-firebaseを連携させました.いろいろとお勉強になりました.
ただし,vuexまで使っていないので,Homeからコンポーネントへマスタ情報まで渡したりとしてしまっています.(業務では使ってますが,今回はこの規模ならと思いましたが,いびつなきがしてしまいます.)また,firebaseにアクセスするものどのコンポーネントから行ってしまってます..改善個所はたくさんあります.
今回初めてfirebaseを触りバックエンドの面倒をやらずに楽なことを知りました.
今後はNoSQLの勉強をしてもっと実践的なアプリ作りたいと思います!!!