LoginSignup
2
1

More than 5 years have passed since last update.

vuetify環境を構築してfirebaseにhostするまでをまとめてみました.part2-todoアプリ作成編

Last updated at Posted at 2019-01-21

この記事では.

part1では,環境構築を行いました.
今回は,firebaseとの連携を行い実際にtodo-appを作ろうと思います.
(最近流行りの構成なので,vue-firebaseで調べるとたくさんありました.特にオリジナリティはないです.勉強なので..)

作ったもの
スクリーンショット 2019-01-20 22.47.51.png

firebase

たくさん機能があります.各機能を3行でまとめてくれてます.
Firebaseの各機能を3行で説明する

今回は2つの機能を使います.

  • Firebase Realtime Database
  • Firebase Authentication

データ構造

  • マスターデータ:category,state
  • トランザクションデータ:item

(今にして思うと,stateは優先順位なのでpriority,基本的に複数形で命名すべきでした..)
image.png

データ操作

最後にコンポーネントごとのソース張り付けてます(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の承認を利用します.対象行をクリックして有効にするだけです.
image.png

連携方法

今回は,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

登録フォームをコンポーネントにしました.(特に今回の規模では分けなくてもいいですけど練習として.)
スクリーンショット 2019-01-20 23.00.21.png

Form.vue
<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

リストです.
スクリーンショット 2019-01-20 23.11.58.png

Form.vue
<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

Home.vue
<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に分ける程度かと.
loginout.gif

まとめ

vue-firebaseを連携させました.いろいろとお勉強になりました.
ただし,vuexまで使っていないので,Homeからコンポーネントへマスタ情報まで渡したりとしてしまっています.(業務では使ってますが,今回はこの規模ならと思いましたが,いびつなきがしてしまいます.)また,firebaseにアクセスするものどのコンポーネントから行ってしまってます..改善個所はたくさんあります.

今回初めてfirebaseを触りバックエンドの面倒をやらずに楽なことを知りました.
今後はNoSQLの勉強をしてもっと実践的なアプリ作りたいと思います!!!

2
1
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
2
1