皆さん、こんにちは!
株式会社TSOneでSES営業を担当しているHYです。

弊社が得意としているVue.jsとFireBaseを利用したアプリ開発を素人がどこまでできるのか?ブログで紹介していきたいと思います♪

改めて、何を開発したいかというとSES営業と、現場常駐エンジニアとの情報共有ができるアプリ。名付けて営業推進ツール。エンジニアがお客様先で仕入れたお仕事情報を、営業と共有できるアプリを開発していきます!

前回、エンジニアがお客様先で仕入れたお仕事情報を登録する案件情報登録画面を開発したので、今回はお仕事情報を一覧で表示する画面を開発します。開発する内容として必要な要素を考えた結果、以下3ステップの実装が今回の目標です。

  1. データベースに登録された情報を取得する
  2. 情報を一覧として表示する
  3. 一覧から選択した情報の詳細情報を表示する

[参考資料]
https://firebase.google.com/docs/firestore/query-data/queries?hl=ja
注意:今回はFirebase V8での実装です。現在の最新はV9となり若干実装が変わります。ごめんなさい。

早速開発を進めていきます!

データベース(Firestore DB)に登録された情報を取得する

今回もプログラムをcomponetsフォルダに作成していきたいと思います。
ファイルはProjList.vue という名前にしました。それではFirestore DBからの情報を取得する処理を実装していきます。まずは処理を制御する<script>タグから開発します。

data()内での定義

画面表示領域で利用するための配列情報を定義します。一覧表示として複数の情報を保持するために配列として定義しています。この配列projectsにはFirestore DB上に登録されているお仕事情報を格納します。

src/components/ProjList.vue

    data() {
        return {
            projects:[],
        };
    };

methods:内での定義

FirestoreDBから情報を取得するメソッドを作ります。メソッド名は今回getListにしました。やりたいことは、FirestoreDBから取得した情報を画面で表示するためのデータ配列projects[]への詰め込みです。

src/components/ProjList.vue

<script>
    import firebase from "@/firebase/firestore";    ①
    export default {
    data() {
        return {
           projects: [],
        };
    },
    methods: {
        async getList () { 
            const db = firebase.firestore();
            await db.collection("EntryInfo").get()  ②
            .then((querySnapshot) => {
                const array = [];
                let index = 0;
              querySnapshot.forEach((doc) => {      ③
                  array.push(doc.data());           ④
                  array[index]["documentID"] = doc.id;
                  index++;
                });
                this.projects = array;              ⑤
            });
        },
    },
    };
</script>

①import でfirebase/firestoreを使用する宣言をします。
②FirestoreDBからEntryInfoという名前のコレクション(テーブル)情報を取得
※EntryInfoには案件情報登録画面で登録された情報が格納されています。
③データが存在していたら、存在するデータ(レコード)数分 forEach() 内の処理を繰り返す。
④forEach()内ではFirestoreDBから取得したデータを、
1行(1レコード)づつメソッド内で定義した array という配列(変数) にdocumentID というキー情報を付けて移し替えています。
※documentIDというキー情報で保存している doc.id はFirestoreDB上でのレコード一意キー情報です。今回作成している一覧表示では使用しませんが、
後々の詳細画面を表示する際のキー情報として使用します。
※尚、FirestoreDBから取得する値は key(カラム名)/value(値)のペア(JSON)形式となっています。
⑤forEach()内の処理が終了後 arrayから data()で定義したprojectsに移し替えています。

取得した情報を一覧として画面表示する

次は画面描画の処理としてタグ側を実装します。一覧表示する際に便利なタグはないか?画面表示を開発する前にはvuetifyをチェックすることをお薦めします。
vuetifyのページ↓
https://vuetifyjs.com/ja/introduction/why-vuetify/
vuetifyをチェックしてみると<v-data-table>というタグが画面で一覧表示する際にとても便利そうなので今回採用してみます。

<v-data-table>を実装する

src/components/ProjList.vue

    <template >

    <v-data-table :headers="headers" :items="projects">
    </v-data-table>

    </template >

v-data-tableタグの基本要素は [headers] と[items]があります。詳細はvuetifyのマニュアルを参照いただきたいのですが、headersでは一覧で表示する項目。itemsはheadersで指定された項目と対となる実際の値。
を定義します。

itemsには今回projectsという値を定義(代入)していますが、これは前述のFirestoreDBから取得した値が格納された配列オブジェクトです。headersはまだ実装していないので、以下のように

data()内でheadersの定義

src/components/ProjList.vue

    data() {
      return {
           projects: [],
      headers: [                ①
           {
              text: "案件概要",
              value: "projectname",
            },
            {
              text: "作業場所",
              value: "location",
            },
      ],
        };
    },

<v-data-table>タグのプロパティ [headers]にはtextとvalueの要素があります。textはヘッダーとして画面表示される名前。valueはitemsで定義されたオブジェクト(今回はprojects)内のヘッダーと対になる要素を指定します。
[補足]
v-data-tableではheadersで指定した項目のみを一覧表示対象とします。
items内に要素が、例えばA,B,C,Dと4つあったとしても
headersでA,B,Cと定義とした場合、一覧の表示対象はA,B,Cの3要素のみとなります。

headersに表示する項目定義を行ったら、一覧画面を作る基礎が完了です。

Vue Routerへの定義

最後に開発したProjList.vueをVue Routerに定義することで画面表示が可能となります。

src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Entry from '@/components/Entry.vue'
import ProjList from '@/components/ProjList.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/Entry',
    name: 'Entry',
    component: Entry,
  },
  {
    path: '/',                    ①
    name: 'ProjList',
    component: ProjList
  },
]

const router = new VueRouter({
  mode: 'history',
  routes
})

export default router

①一覧画面(ProjList)の画面パスを”/”としました。

一覧画面から登録画面への画面遷移を実装する。

画面遷移を 一覧画面(ProjList) → 登録画面(Entry) としたいので
一覧画面から登録画面に遷移するボタンを追加していきたいと思います。

src/components/ProjList.vue

<templete>


<v-btn color="success" dark @click="entry()">
 <v-icon dark right class="pr-6"> fas fa-edit </v-icon>  ①
   案 件 登 録
</v-btn>

</templete>
<script>

entry: function () {
  this.$router.push("Entry");    ②
},


</script>

①機能とは関係ないですが、折角なのでボタンにアイコンをつけて装飾しています。<v-btn>の中で<v-icon>を定義することでボタンにアイコンを表示することができます。※使用できるアイコン情報については以下Vuetifyのページで確認できます。アイコン・コンポーネント — Vuetify (vuetifyjs.com)
this.$router.push("Entry");
push()の引数に、Vue Routerで定義したコンポーネント名を指定することで画面遷移(描画)を行います。純粋に画面遷移だけを行う場合は、この記述のみでOKです。

早速、Vueプロジェクト(=アプリ)を起動して画面表示してみます。
正常にサーバーが起動されたらブラウザで「http://localhost:8080/」を入力すると、

一覧画面が表示されました。まだデータ登録がされていないので、一覧のヘッダ※のみ表示されている状態です。
<v-data-table>のheadersで定義した要素[text]の内容

案件情報を登録して、一覧画面に表示する情報を用意する

それでは、登録画面へ遷移して、データ登録を行います。先ほど作成した「案件登録」ボタンをクリックすると、

無事、案件情報登録画面が表示されました。

内容を入力して、案件情報を登録します。

一覧画面に登録したデータが表示されました!!

ちょっと脱線しますが、せっかくなので、一覧表示する際に偶数行に背景色をつけて、一覧を装飾してみようと思います。
src/components/ProjList.vue

<style>
.v-data-table td {
  background: #f0f8ff;
}
.v-data-table tr:nth-child(odd) td {
  background: #fff;
}
</style>

vue.jsの構成<style>タグ内で↑の定義をすると
偶数行は水色っぽくなります。なんとなくメリハリがついて良い感じ♪

③一覧から選択した情報の詳細情報を表示する

最後に、一覧から指定したデータの詳細(明細)画面を表示したいと思います。

[画面遷移イメージ]

今回、一覧で表示している項目 案件概要 をリンクにして、クリックした案件概要の詳細画面に遷移(描画)する機能を開発していきます。ここからはネット情報を頼り(コピペ?)に「Don’t think. FEEL!」で実装していきます。

まずは詳細画面に遷移するためのイベントとなるリンクを一覧画面に作ります。

src/components/ProjList.vue

<template >

<v-data-table :headers="headers" :items="projects">
<template v-slot:[`item.projectname`]="{ item }">
 <a href="" @click.prevent.stop="showProjDitail(item)">
       {{ item.projectname }}
 </a>
</template>
</v-data-table>

</template >
  • タグ内にタグ(リンク)を定義します。
  • v-slot という機能を利用して、で作成した一覧情報の projectnameというキーの内容をタグ内の内容で置き換える指示をしています。
  • 重要なポイントとして、必ず内で定義する必要があります。
  • タグではhrefの設定を空白にして、@clickのメソッド内で指定した詳細画面へ遷移するようにします。@click.prevent.stopと記載することで、リンクを押した際にhrefで指定した空白のページへ遷移しないようにしています。

[めも]
<templete>タグ内で item という変数が突然でてきますが、これは<v-data-table>タグ内で定義された
items(配列) の1レコード(要素)分のデータ(オブジェクト)になります。

一覧画面から指定の案件概要をクリックされたときに実行されるshowProjDitail()メソッドを実装します。

src/components/ProjList.vue

showProjDitail(item) {
 this.$store.commit(
  "setProjInfo",
  {selectedDocid: item.documentID,}
 );
 this.$router.push("projDetail");
},

ここで三種の神器の一つ、Vuex(以降storeと呼びます。)が登場します。
storeではデータベースに保存するのではなく、一時的に情報を保存できる領域を提供してくれます。A画面で入力 → B画面で入力した内容を確認 → C画面でDBに格納。という遷移があったときに、A画面で入力した内容をC画面まで一時的にstoreという領域に保存することができます。B画面からA画面に戻ったときも、storeに保存していた内容でA画面を復元することもできます。storeはデータを一元管理する箱として利用され、極端な理解かもしれませんがJavaでいうSession情報のようなものかもしれません。

this.$store.commit("setProjInfo", {selectedDocid: item.documentID,});
↑の一行で。setProjInfo というメソッドを呼び出して、selsectedDocidというキー名で、指定のdocumentIDを保存します。何のことやら?という感じですが、、定義ファイル store.js への設定をしつつ説明したいと思います。

Storeを利用するための定義ファイルを実装します。

src/store.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

function getDefaultProjState() {  ④
  return {
    selectedDocid:""
  }
}

export default new Vuex.Store({
  state: {
    ProjInfo: {         ①
      selectedDocid:""
    }
  },  
  mutations: {
    setProjInfo(state, data) {  ②
      Object.assign(state.ProjInfo, data);
    },

    clearProjInfo(state) {    ③
      Object.assign(state.ProjInfo, getDefaultProjState());
    }
  }
})

①Storeで保持しておきたい情報の定義
Storeでは保存したいデータを定義するファイル[store.js]が必要となります。
参考にしたネット情報が srcの直下にstore.js を作成していたので同じように作成しました。データを保存する箱の名前を ProjInfo、保存したい項目名をselectedDocidと定義しました。保存したい項目は key:value のJSON形式で記載します。今回の例ではselectedDocidの1つだけですが、複数定義することができます。箱の名前も、項目名も好きな名前で大丈夫です。箱も複数定義できますので、用途によって増やして利用することができます。
②先ほど、ProjListで実行したthis.$store.commit()の引数で指定していたのはこのメソッドです。第1引数で指定された箱(今回だとProjInfo)に、第2引数で指定された値(今回だとselectedDocid)を詰め込み(設定)します。このメソッドを用意することで、vue.jsからstoreへの情報保存処理を呼び出すことができるようになります。
③は引数で指定された箱(今回だとProjInfo)の情報を初期化する為のメソッドです。
④のgetDefaultProjState()を実行して、今回だとselectedDocidに初期値””を設定しています。使いどころとしては、ログアウト処理のようにシステムを終了するときに利用する感じです。

ここまでで、store側(store.js)の準備ができたので、
もう1度ProjListのshowProjDitail()の処理を見てみます。
src/components/ProjList.vue

showProjDitail(item) {
 this.$store.commit(
  "setProjInfo",
  {selectedDocid: item.documentID,}
 );
 this.$router.push("projDetail");
},

store.jsで定義した”setProjInfo”を実行して、引数にdocumentIDを渡しています。これによって、一覧から選択された案件概要のdocumentIDをstoreに保存します。そして、projDetailという画面への遷移(描画)を指定します。
このdocumentIDは一覧情報を取得した際に保存しておいた対象のレコードのキー情報です。routerで呼び出された詳細画面(projDetail)処理で参照するために、storeに保存しています。

一旦ここまでの状態で一覧画面を表示してみたいと思います。

案件概要がリンクになっている!
でも詳細画面(projDetail)を実装していないので、今のままではリンククリックしても真っ白の画面しかでません。

ということで、詳細画面を実装していきます。
ポイントは、

  • 一覧から選択された、詳細を表示したい案件概要のキー情報(documentID)をstoreから取得
  • FirestoreDBから指定のキー情報でデータを取得
  • 取得した情報を画面に表示

いままでと同様componetsフォルダでプログラムを作成していきたいと思います。ファイルは ProjDetail.vue という名前にしました。それでは表示する情報のキー情報をstoreから取得する処理を実装していきます。

一覧で選択された、詳細を表示したい案件概要のキー情報(documentID)をstoreから取得

src/components/ProjDetail.vue

<script>
import firebase from "@/firebase/firestore";  
export default {
data() {
    return {
	projectname:"",
	location:"",
	requiredSkills:"",
	~~省略~~
    };
},
methods: {
},
  async mounted() {  ①
    var db = firebase.firestore();
    this.selectedDocid = this.$store.state.ProjInfo.selectedDocid;  ②
    await db
      .collection("EntryInfo")        ③
      .doc(this.selectedDocid) 
      .get()
      .then((querySnapshot) => {
        if (querySnapshot.exists){
          const data = querySnapshot.data();   ④
          (this.projectname = data.projectname),
            (this.location = data.location),
            (this.requiredSkills = data.requiredSkills),
		~~省略~~
        }else{
           console.log("No such document!");
        }
      });
  },
</script>

①今回、画面表示する情報の取得は mounted() で実装しています。
vue.jsにはライフサイクルフックと言われる画面表示や画面を閉じるときに自動で呼び出されるメソッドが用意されています。
いくつか用意されており、それぞれ実行される順番も決まっています。
詳細は公式で確認してください。https://v3.ja.vuejs.org/api/options-lifecycle-hooks.html
※今回実装するmoutedもライフサイクルフックの一つで指定のvue.jsが実行された際に自動的に実行されるメソッドです。他にもmouted よりも先に実行されるcreateがあるのですが、このタイミングでは$el(storeやrouter等)が利用できないため、一般的にはmountedで初期表示処理を実装するようです。
②storeからレコードのキーの取得
この処理で一覧画面でstoreに保存したキー情報を this.selectedDocid として内部の変数へ保存します。storeから情報を取り出したいときは、storeに定義した箱の名前と項目名を指定するだけ。this.$store.state.箱の名前.項目名;
③FirestoreDBから指定キーのレコードを取得
FirestoreDBのEntryInfoテーブルからdoc()関数で指定したキー指定でデータを取得します。今回はキー指定なので、1レコードのみの取得となります。
サンプルで記載している querySnapshot の中身は1レコードの為、前述の一覧画面を開発したような forEach() を使用した繰り返し処理はしません。
④取得したレコードから項目を取り出し、画面表示で利用する内部の変数へ移し替え。※この内部の変数(this.項目名)はdata()関数内で定義され、領域から参照(バインド)される項目です。

データの取得ができたので、領域で項目を表示する為のタグを作っていきます。今回使用したタグは<v-card-text>です。

src/components/ProjDetail.vue

<v-row>
 <v-col align="right"><v-card-text>必須スキル:</v-card-text></v-col>
 <v-col>
  <v-card-text>{{ requiredSkills }}</v-card-text>
 </v-col>
</v-row>

バインドしている項目を領域でそのまま表示したい場合は{{}} でくくるだけでOKのようです。

ここまでで詳細画面の開発は終了です。作成したProjDetail.vueを表示できるように、Vue Routerへの追加をして、早速案件概要のリンクをクリックしてみると、でました詳細画面!!

見た目は改良の余地がありますが…(^▽^;)
これで、登録、一覧表示、詳細表示 までの一通りの画面開発が完了です!!

次は、いよいよアプリを公開してエンジニアにも利用してもらう設定をします。FirebaseへのホスティングとPWAへの挑戦に続く。