はじめに
長い間サーバーサイド専属エンジニアをしていたので、フロント復帰の意思表示として
Vue.js & ReactiveX & element-ui & Firebase で
完全無料サーバーレスシングルページアプリケーションを作成してみました。
最近ダイエット中なので、日々食べたものを登録していくアプリケーションにしてみようと思います。
フロントご無沙汰サーバーサイドエンジニアにどこまでできるのか・・・
Vue.js環境作成
vue-cliでプロジェクトの作成を行います。
# vue-cliのインストール
yarn global add @vue/cli
# project作成
# 1. manualを選択
# 2. javascriptを選択
# 3. In package.jsonを選択
# 4. Use Yarnを選択
vue create sample
# projectの起動
cd sample
yarn serve
コレでプロトタイプは完成。
簡単!!
firebaseのプロジェクト作成
トップページより下記手順でfirebaseプロジェクト作成を行います。
- プロジェクトの追加
- ウェブアプリにFirebaseを追加、でスクリプトタグを取得する
- Databaseを使ってみる
- Cloud Firestore Betaを選択
- ロックモードで開始
いったんfirebase console上で動作確認をしたいだけのため、きついセキュリティで開始 - コレクションを追加
- 食べたものを管理するアプリを作ると想定して、foodsというコレクション名にする
- データの追加
- 画面上で保存されていることを確認
javascriptでfirebase検索
frontendプロジェクト配下で下記のコマンドを実行しfirebaseをインストール
yarn add firebase
とりあえずfirestoreのfoods配下のアクセス権限を与える
service cloud.firestore {
match /databases/{database}/documents {
match /foods/{food} {
allow read: if true;
allow write: if true;
}
}
}
■javascript
フロントの適当な箇所に下記のコードを記述して
コンソールログに登録済みデータが出力されることを確認
var firebase = require('firebase')
require('firebase/firestore')
// Initialize Firebase
var config = {
apiKey: "<API_KEY>",
authDomain: "<PROJECT_ID>.firebaseapp.com",
databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
storageBucket: "<BUCKET>.appspot.com",
messagingSenderId: "<SENDER_ID>",
}
firebase.initializeApp(config)
var db = firebase.firestore()
db
.collection('foods')
.get()
.then(qst => {
qst.forEach(doc => {
console.log(doc.data())
})
})
リスト表示
vue-rx,rxjs,element-uiのインストール
yarn add vue-rx rxjs element-ui
■html
<el-button type="primary" icon="el-icon-search" v-stream:click="plus$">Search</el-button>
<el-table v-loading="loading" :data="foods" style="width: 100%">
<el-table-column prop="名前" label="名前" width="280">
</el-table-column>
<el-table-column prop="タンパク質" label="タンパク質" width="280">
</el-table-column>
<el-table-column prop="脂質" label="脂質" width="280">
</el-table-column>
<el-table-column prop="炭水化物" label="炭水化物" width="280">
</el-table-column>
</el-table>
■javascript
import Vue from 'vue'
import Rx from 'rxjs/Rx'
import VueRx from 'vue-rx'
Vue.use(VueRx, Rx)
export default {
name: 'FoodList',
props: {
msg: String
},
subscriptions: {
foods: Rx.Observable.fromPromise(
db
.collection('foods')
.get()
.then(qst => qst.docs.map(doc => doc.data()))
)
}
}
新規追加/編集処理
食事を登録する画面を作ります。
■html
<span class="input-label">名前</span>
<el-input v-model="name"></el-input>
<span class="input-label">タンパク質</span>
<el-input v-model="protein"></el-input>
<span class="input-label">脂質</span>
<el-input v-model="lipid"></el-input>
<span class="input-label">炭水化物</span>
<el-input v-model="carbo"></el-input>
<span class="input-label">食べた日</span>
<el-input v-model="eatDate"></el-input>
<el-button v-if="$route.params.id" v-stream:click="setFood$">更新</el-button>
<el-button v-else v-stream:click="addFood$">新規追加</el-button>
<el-dialog title="Tips" :visible.sync="dialogVisible" width="30%">
<span>登録完了しました</span>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="dialogVisible = false">確認</el-button>
</span>
</el-dialog>
■javascript
const db = firebase.firestore()
const addPms = obj =>
Rx.Observable.fromPromise(
db.collection('foods').add({
名前: obj.name,
タンパク質: obj.protein,
脂質: obj.lipid,
炭水化物: obj.carbo,
食べた日: obj.eatDate
})
)
const setPms = obj =>
Rx.Observable.fromPromise(
db
.collection('foods')
.doc(obj.id)
.set({
名前: obj.name,
タンパク質: obj.protein,
脂質: obj.lipid,
炭水化物: obj.carbo,
食べた日: obj.eatDate
})
)
const foodPms = id => {
if (id) {
return Rx.Observable.fromPromise(
db
.collection('foods')
.doc(id)
.get()
.then(doc => doc || { data: {} })
.then(doc => doc.data())
)
} else {
return Rx.Observable.empty()
}
}
export default {
name: 'EditFood',
props: {
msg: String,
id: String
},
data() {
return {
name: '',
protein: '',
lipid: '',
carbo: '',
eatDate: '',
dialogVisible: false
}
},
methods: {
clear() {
this.name = ''
this.protein = ''
this.lipid = ''
this.carbo = ''
this.eatDate = ''
this.dialogVisible = true
},
initialize(obj) {
this.name = obj.名前
this.protein = obj.タンパク質
this.lipid = obj.脂質
this.carbo = obj.炭水化物
this.eatDate = obj.食べた日
}
},
domStreams: ['addFood$', 'setFood$'],
mounted() {
foodPms(this.$route.params.id).subscribe(this.initialize)
this.addFood$
.flatMap(() =>
addPms({
name: this.name,
protein: this.protein,
lipid: this.lipid,
carbo: this.carbo,
eatDate: this.eatDate
})
)
.subscribe(o => this.clear())
this.setFood$
.flatMap(() =>
setPms(
Object.assign(
{
id: this.$route.params.id
},
this
)
)
)
.subscribe(o => this.clear())
}
}
削除処理
リスト表示の画面にボタン追加しました
■html
<el-table-column label="処理">
<template slot-scope="scope">
<router-link :to="{ name: 'edit', params: { id: scope.row.id }}" tag="el-button">Edit</router-link>
<el-button v-stream:click="{ subject: delete$, data: scope }">Delete</el-button>
</template>
</el-table-column>
■javascript
const deletePms = id =>
Rx.Observable.fromPromise(
db
.collection('foods')
.doc(id)
.delete()
.then(console.log)
)
export default {
//... 略
subscriptions() {
const deleteOps = this.delete$.pluck('data', 'row', 'id').flatMap(deletePms)
const searchOps = this.plus$
return {
foods: Rx.Observable.merge(deleteOps, searchOps).flatMap(foodsPms)
}
}
}
公開
ここまで来て力尽きたので公開。
firebase hostingを使います。
手順はリンク先のまんま。
動作
総括
Vue.jsもfirebaseもとても使い勝手がよく理解しやすいですね。
ただ、やりたかったことの半分もできておらず。。。
個人的課題としては
デザイン
- UIフレームワークの知識習得
javascript
- ライフワークの把握
どの処理をどこに書くべきかが曖昧 - パッケージわけをどのようにすべきか知りたい
- ReactiveXのAPIもっと自由自在に使えるようになりたい。
doメソッドとかなぜかエラーになって使えなかった。
firebase
- 認証
- 通知
- 権限管理
ここらへんもっとナレッジをためたいと思いました。
参考サイト等教えてもらえたらうれしいです。