4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

サーバーサイドエンジニアによる完全無料SSPA30分クッキング

Posted at

はじめに

長い間サーバーサイド専属エンジニアをしていたので、フロント復帰の意思表示として
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プロジェクト作成を行います。

  1. プロジェクトの追加
  2. ウェブアプリにFirebaseを追加、でスクリプトタグを取得する
  3. Databaseを使ってみる
  4. Cloud Firestore Betaを選択
  5. ロックモードで開始
    いったんfirebase console上で動作確認をしたいだけのため、きついセキュリティで開始
  6. コレクションを追加
  7. 食べたものを管理するアプリを作ると想定して、foodsというコレクション名にする
  8. データの追加
  9. 画面上で保存されていることを確認

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
フロントの適当な箇所に下記のコードを記述して
コンソールログに登録済みデータが出力されることを確認

food-list.js
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

food-list.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

food-list.js
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

edit-food.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

edit-food.js
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

food-list.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

food-list.js
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を使います。
手順はリンク先のまんま。

動作

front.gif

総括

Vue.jsもfirebaseもとても使い勝手がよく理解しやすいですね。
ただ、やりたかったことの半分もできておらず。。。
個人的課題としては

デザイン

  • UIフレームワークの知識習得

javascript

  • ライフワークの把握
    どの処理をどこに書くべきかが曖昧
  • パッケージわけをどのようにすべきか知りたい
  • ReactiveXのAPIもっと自由自在に使えるようになりたい。
    doメソッドとかなぜかエラーになって使えなかった。

firebase

  • 認証
  • 通知
  • 権限管理

ここらへんもっとナレッジをためたいと思いました。
参考サイト等教えてもらえたらうれしいです。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?