VueJs-#13 Vue Cloud FireStore

Pendahuluan

Pada tutorial sebelumnya kita membuat Vue Login menggunakan FirebaseUI, sekarang kita akan membuat ToDoList Page dan menyimpannya ke dalam FireStore.

Kita akan menggunakan proyek tutorial sebelumnya dan menambahkan ToDoList Page.

Persiapan

  1. Buat Proyek Vue Login menggunakan FirebaseUI

Membuat Page ToDoList

Siapkan Route, edit router.js, tambahkan baris berikut :

import Vue from "vue";
import Router from "vue-router";
import Home from "./views/Home.vue";
import Login from "./components/Login.vue";
import Profile from "./components/Profile.vue";
import Todos from '@/components/Todos'

import firebase from 'firebase/app';
import 'firebase/auth';

Vue.use(Router);

let router =  new Router({
  mode: "history",
  base: process.env.BASE_URL,
  routes: [
    {
        path: "/home",
        name: "Home",
        component: Home,
        meta: {
            auth: true
        }
    },
    {
        path: '/about',
        name: 'about',
        component: () => import('./views/About.vue'),
        meta: {
            auth: true
        }
    },
    {
        path: "/",
        name: "Login",
        component: Login,
        meta: {
            guest: true
        }
    },
    {
        path: "/profile",
        name: "Profile",
        component: Profile,
        meta: {
            auth: true
        }
    },
    {
        path: '/todo',
        name: 'Todos',
        component: Todos,
        meta: {
            auth: true
        }
    }
  ]
});
router.beforeEach((to, from, next) => {
    if (to.matched.some(record => record.meta.auth)) {
      firebase.auth().onAuthStateChanged(user => {
        if (user) {
          next()
        } else {
          next({
            path: "/",
          })
        }
      })
    } else if (to.matched.some(record => record.meta.guest)) {
      firebase.auth().onAuthStateChanged(user => {
        if (user) {
          next({
            path: "/profile",
          })
        } else {
          next()
        }
      })
    } else {
      next()
    }
  })
  export default router

Buat Component, components/Todos.vue

<template>
    <div>
        <b-card no-body style="max-width: 20rem;margin:auto;" class="mt-5 text-center">
            <template v-slot:header>
                <h4 class="mb-0">To-Dos</h4>
            </template>
            <b-card-body>
                 <b-input-group prepend="To Do" class="mt-3">
                    <b-form-input id="new_todo" type="text" class="validate" v-model="todo.title"></b-form-input>
                    <b-input-group-append>
                        <b-button variant="outline-success" @click="addTodo">Add</b-button>
                    </b-input-group-append>
                </b-input-group>
            </b-card-body>
            <b-list-group flush>
                <b-list-group-item class="d-flex justify-content-between align-items-center"
                    v-for="todo in todos"
                    :key="todo.id"
                    :class="{ fade: todo.isCompleted }"
                >
                    <b-button variant="danger" size="sm" class="deleteIcon" @click="deleteToDo(todo.id)">X</b-button>
                    {{todo.title}}
                    <b-form-checkbox
                        type="checkbox"
                        :checked="todo.isCompleted"
                        @change="updateTodoItem(todo.id, $event)"
                        switch
                    >
                    </b-form-checkbox>
                </b-list-group-item>
            </b-list-group>
        </b-card>
    </div>
</template>
<script>
import firebase from 'firebase/app';
import 'firebase/firestore';
export default {
    data() {
        return {
            todos: [],
            todo: {
                title: ""
            }
        };
    },
    created() {
        this.getTodos();
    },
    methods: {
        addTodo() {
            firebase
                .firestore()
                .collection("users")
                .doc(firebase.auth().currentUser.uid)
                .collection("todos")
                .add({
                    title: this.todo.title,
                    createdAt: new Date(),
                    isCompleted: false
                });
        },
        async getTodos() {
            var todosRef = await firebase
                .firestore()
                .collection("users")
                .doc(firebase.auth().currentUser.uid)
                .collection("todos");
            todosRef.onSnapshot(snap => {
                this.todos = [];
                snap.forEach(doc => {
                    var todo = doc.data();
                    todo.id = doc.id;
                    this.todos.push(todo);
                });
            });
        },
        updateTodoItem(docId, e) {
            var isChecked = e;
            firebase
                .firestore()
                .collection("users")
                .doc(firebase.auth().currentUser.uid)
                .collection("todos")
                .doc(docId)
                .update({
                    isCompleted: isChecked
                });
        },
        deleteToDo(docId) {
            firebase
                .firestore()
                .collection("users")
                .doc(firebase.auth().currentUser.uid)
                .collection("todos")
                .doc(docId)
                .delete();
        }
    }
};
</script>
<style>
.fade {
    opacity: 0.4 !important;Jala
}
.collection.with-header {
    max-width: 500px;
    margin: 0 auto;
}
.deleteIcon {
    margin-right: 10px;
    cursor: pointer;
}
.deleteIcon:hover {
    opacity: 0.5;
}
</style>

Jalankan program, masuk menu “ToDo” :

ToDos
Struktur Cloud Firestore

VueJs-#08a Vuex State

Pengenalan

Vuex adalah library Vue.js yang dapat digunakan untuk meng-handle state (state manajemen), dimana data yang ada dapat didefinisikan dan dipusatkan pada sebuah file sehingga bisa digunakan oleh semua component yang ada.

Dengan menggunakan Vuex, komunikasi antar component menjadi lebih mudah. Sebagaimana yang kita ketahui bahwasanya dalam interaksi antar component agar dapat saling bertukar data adalah dengan menggunakan props, maka dengan Vuex kamu cukup mengakses state yang telah didefinisikan pada Vuex Store.

Persiapan

Artikel ini merupakan bagian pertama, maka kita memulainya dengan project yang masih fresh installInstall Vue.js dengan menggunakan vue cli:

vue create vue-bootstrap-vuex

Pilih enable options router & vuex

Sehingga Struktur direktorinya menjadi seperti gambar di bawah ini :

vue with router, vuex, & bootstrap

Perhatikan gambar di atas pada folder src terdapat file-file bawaan options penginstallan router & vuex :

  • router.js, file ini berfunsi untuk mendefinisikan router2 berdasarkan komponen, dibuat otomatis karena kita menyertakan options vue-router
  • store.js, file ini berisi state vuex dibuat otomatis karena kita menyertakan options vuex
  • views, folder ini berisi template views untuk komponen dibuat otomatis karena kita menyertakan options vue-router

Setelah selesai untuk mempercantik tampilan kita menggunakan theme bootstrap Install bootstrapVue

vue add bootstrap-vue

Sekarang kita ubah src/App.vue

<template>
  <div id="app">
    <div>
      <b-navbar toggleable="lg" type="dark" variant="info">
        <b-navbar-brand href="#">NavBar</b-navbar-brand>
     
        <b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
     
        <b-collapse id="nav-collapse" is-nav>
          <b-navbar-nav>
            <b-nav-item to="/">Home</b-nav-item>
            <b-nav-item to="About">About</b-nav-item>
            <b-nav-item to="#" disabled>Disabled</b-nav-item>
          </b-navbar-nav>
     
          <!-- Right aligned nav items -->
          <b-navbar-nav class="ml-auto">
            <b-nav-form>
              <b-form-input size="sm" class="mr-sm-2" placeholder="Search"></b-form-input>
              <b-button size="sm" class="my-2 my-sm-0" type="submit">Search</b-button>
            </b-nav-form>
     
            <b-nav-item-dropdown text="Lang" right>
              <b-dropdown-item href="#">EN</b-dropdown-item>
              <b-dropdown-item href="#">ES</b-dropdown-item>
              <b-dropdown-item href="#">RU</b-dropdown-item>
              <b-dropdown-item href="#">FA</b-dropdown-item>
            </b-nav-item-dropdown>
     
            <b-nav-item-dropdown right>
              <!-- Using 'button-content' slot -->
              <template v-slot:button-content>
                <em>User</em>
              </template>
              <b-dropdown-item href="#">Profile</b-dropdown-item>
              <b-dropdown-item href="#">Sign Out</b-dropdown-item>
            </b-nav-item-dropdown>
          </b-navbar-nav>
        </b-collapse>
      </b-navbar>
    </div>
    <div class="container mt-2">
      <router-view></router-view>
    </div>
  </div>
  <!--<div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </div>
    <router-view />
  </div>-->
</template>

<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
#nav {
  padding: 30px;
}

#nav a {
  font-weight: bold;
  color: #2c3e50;
}

#nav a.router-link-exact-active {
  color: #42b983;
}
</style>

Jalankan service vue:

npm run serve
Tampilan dengan Bootstrap

Manajemen State Dengan Vuex

Tambahkan Menu baru pada src/App.vue

<template>
  <div id="app">
    <div>
      <b-navbar toggleable="lg" type="dark" variant="info">
        <b-navbar-brand href="#">NavBar</b-navbar-brand>
        <b-navbar-toggle target="nav-collapse"></b-navbar-toggle> 
        <b-collapse id="nav-collapse" is-nav>
          <b-navbar-nav>
            <b-nav-item to="/">Home</b-nav-item>
            <b-nav-item to="About">About</b-nav-item>
            <b-nav-item to="LearningVuex">Learning Vuex</b-nav-item>
            <b-nav-item to="#" disabled>Disabled</b-nav-item>
          </b-navbar-nav>

          <!-- Right aligned nav items -->
          <b-navbar-nav class="ml-auto">
            <b-nav-form>
              <b-form-input size="sm" class="mr-sm-2" placeholder="Search"></b-form-input>
              <b-button size="sm" class="my-2 my-sm-0" type="submit">Search</b-button>
            </b-nav-form> 
            <b-nav-item-dropdown text="Lang" right>
              <b-dropdown-item href="#">EN</b-dropdown-item>
              <b-dropdown-item href="#">ES</b-dropdown-item>
              <b-dropdown-item href="#">RU</b-dropdown-item>
              <b-dropdown-item href="#">FA</b-dropdown-item>
            </b-nav-item-dropdown>
     
            <b-nav-item-dropdown right>
              <!-- Using 'button-content' slot -->
              <template v-slot:button-content>
                <em>User</em>
              </template>
              <b-dropdown-item href="#">Profile</b-dropdown-item>
              <b-dropdown-item href="#">Sign Out</b-dropdown-item>
            </b-nav-item-dropdown>
          </b-navbar-nav>
        </b-collapse>
      </b-navbar>
    </div>
    <div class="container mt-2">
      <router-view/>
    </div>
  </div>
</template>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
</style>

Siapkan route “Learning Vuex” di src/router.js

import Vue from "vue";
import Router from "vue-router";
import Home from "./views/Home.vue";

Vue.use(Router);

export default new Router({
  mode: "history",
  base: process.env.BASE_URL,
  routes: [
    {
      path: "/",
      name: "home",
      component: Home
    },
    {
      path: "/about",
      name: "about",
      // route level code-splitting
      // this generates a separate chunk (about.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () =>
        import(/* webpackChunkName: "about" */ "./views/About.vue")
    },
    {
      path: "/learningvuex",
      name: "LearningVuex",
      component: () =>
        import("./views/LearningVuex.vue")
    }
  ]
});

Buka file src/store.js

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
      agenda: [
        { hari: 'Senin', kegiatan: 'Belajar Vuejs' },
        { hari: 'Selasa', kegiatan: 'Belajar Laravel' },
        { hari: 'Rabu', kegiatan: 'Belajar Mysql' }
      ],
  },
  mutations: {},
  actions: {}
});

Sekarang kita akan membuat Component memanggil state di atas:

Membuat Page Views LearningVuex.vue untuk memanggil kedua Component

<template>
	<div id="app" class="container">
		<div class="row py-4">
			<agenda-show post-title="Daftar Agenda"/>
		</div>
	</div>
</template>

<script>
	import AgendaShow from '../components/AgendaShow.vue'

	export default {
		name: 'app',
		components: {
			AgendaShow
		}
	}
</script>

Membuat Component :

src/components/AgendaShow.vue

<template>
    <div class="col-md-6">
        <div class="card">
            <div class="card-header">
                <h3 class="card-title">{{postTitle}}</h3>
            </div>
            <div class="card-body">
                <div class="table-responsive">
                    <table class="table table-hover table-bordered">
                        <thead>
                            <tr>
                                <th>#</th>
                                <th>Hari</th>
                                <th>Agenda</th>
                                <th></th>
                            </tr>
                        </thead>
                        <tbody>
                            <!-- Memanggil state agenda dari STORE -->
                            <tr v-for="(row, index) in this.$store.state.agenda" :key="index">
                                <td>{{ index+1 }}</td>
                                <td>{{ row.hari }}</td>
                                <td>{{ row.kegiatan }}</td>
                                <td></td>
                            </tr>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
</template>
<script>
    export default {
        name: 'AgendaShow',
        props: ['postTitle'],
    }
</script>

Buka kembali browser http://localhost:8080

Table Agenda

VueJs-#12 Vue Login FirebaseUI

Firebase adalah infrastruktur cloud real-time untuk aplikasi sisi klien, yang memungkinkan pengembang mengubah aplikasi kerangka Front-end apa pun seperti Vue, React, Angular, iOS atau Android menjadi produk tumpukan penuh yang mampu melakukan penskalaan tanpa batas di cloud.

Persiapan

Install Project menggunakan Vue Cli

vue create vue-firebase

Pilih Manually select feature, lalu ceklis pilihan berikut :

Select Options

Install Theme BootstrapVue

vue add bootstrap-vue

Install Firebase into Vue Project

npm install firebase --save

Buat Project di FireBase

Sebelum memulai membuat Program, kita akan menyiapkan Proyek di Firebase, masuk ke Firebase Console buat Proyek baru, setelah selesai masuk ke Menu Project Overview => Setelan proyek

Setelan Proyek

Menambahkan Firebase ke Aplikasi Web

Parameter Pengaturan Firebase

Sekarang kita akan membuat setingan untuk firebase di Program Vue kita sesuaikan dengan gambar di atas. Buka main.js tambahkan baris berikut diantara import dan Vue.config.productionTip = false;

import firebase from 'firebase'

const firebaseConfig = {
  apiKey: "*****",
  authDomain: "firebaseUIAuth-*****.firebaseapp.com",
  databaseURL: "https://****-709a3.firebaseio.com",
  projectId: "firebaseUIAuth-709a3",
  storageBucket: "",
  messagingSenderId: "2547636***397",
  appId: "1:254763***397:web:***c15c671b5c"
};
firebase.initializeApp(firebaseConfig);

Menyiapkan Route

router.js

import Vue from "vue";
import Router from "vue-router";
import Home from "./views/Home.vue";
import Login from "@/components/Login.vue";
import Profile from "@/components/Profile.vue";
import Todos from "@/components/Todos";

import firebase from 'firebase/app';
import 'firebase/auth';

Vue.use(Router);

let router =  new Router({
  mode: "history",
  base: process.env.BASE_URL,
  routes: [
    {
        path: "/home",
        name: "Home",
        component: Home,
        meta: {
            auth: true
        }
    },
    {
        path: '/about',
        name: 'about',
        component: () => import('./views/About.vue')
    },
    {
        path: "/",
        name: "Login",
        component: Login,
        meta: {
            guest: true
        }
    },
    {
        path: "/profile",
        name: "Profile",
        component: Profile,
        meta: {
            auth: true
        }
    },
    {
        path: '/todo',
        name: 'Todos',
        component: Todos,
        meta: {
            auth: true
        }
    }
  ]
});
router.beforeEach((to, from, next) => {
    if (to.matched.some(record => record.meta.auth)) {
      firebase.auth().onAuthStateChanged(user => {
        if (user) {
          next()
        } else {
          next({
            path: "/",
          })
        }
      })
    } else if (to.matched.some(record => record.meta.guest)) {
      firebase.auth().onAuthStateChanged(user => {
        if (user) {
          next({
            path: "/profile",
          })
        } else {
          next()
        }
      })
    } else {
      next()
    }
});
export default router

router.beforeEach berfungsi untuk meredirect page yang memiliki property :

meta: {
   auth: true
}

Singkatnya jika belum login atau user kosong route akan diredirect kembali ke route Login.

Menyiapkan Store

Kita perlu menyiapkan store untuk menyimpan status login & data user yang nantinya akan ditampilkan pada component Profile.vue

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    isLogin:false,
    user:null
  },
  mutations: {},
  actions: {}
});

Login component menggunakan FirebaseUI

FirebaseUI adalah library yang dibuat sebagai tambahan dari Firebase Authentication SDK yang menyediakan alur UI drop-in untuk digunakan dalam aplikasi Anda. Baca penjelasannya disini.

Sekarang kita akan menambahkan FirebaseUI di program Vue kita

npm install firebaseui --save

Karena kita akan login menggunakan gmail atau email, maka kita harus mengaktifkan options Authentication pada Firebase Console. Masuk ke Menu Authentication=>Pilih Tab Metode Login lalu aktifkan pilihan Email & Google

Metode Login

Setelah itu kita buat component Login.vue yang nantinya akan menampilkan halaman login.

<template>
    <div>
        <br/><br/><br/><br/>
        <h5 class="center-align">Login</h5>
        <section id="firebaseui-auth-container"></section>
    </div>
</template>

<script>
import firebase from "firebase";
import * as firebaseui from "firebaseui";
import "firebaseui/dist/firebaseui.css";
export default {
    name: "Login",
    data() {
        return {};
    },
    mounted() {
        let ui = firebaseui.auth.AuthUI.getInstance();
        if (!ui) {
            ui = new firebaseui.auth.AuthUI(firebase.auth());
        }
        var uiConfig = {
            signInSuccessUrl: "/profile", // This redirect can be achived by route using callback.
            signInFlow: "popup",
            signInOptions: [
                //firebase.auth.FacebookAuthProvider.PROVIDER_ID,
                firebase.auth.GoogleAuthProvider.PROVIDER_ID,
                firebase.auth.EmailAuthProvider.PROVIDER_ID
            ]
        };
        ui.start("#firebaseui-auth-container", uiConfig);
    }
};
</script>

<style>
</style>

Perhatikan baris signInSuccessUrl: “/profile”, Jika login berhasil route akan diarahkan ke route /profile.

Buat Program

Selanjutnya kita akan membuat sebuah component NavBar.vue yang nantinya bisa diakses di setiap component jika dalam posisi login, untuk mempercantik tampilannya kita akan menambahkan BootstrapVue proyek kita.

vue add bootstrap-vue

components/NavBar.vue

<template>
    <section>
        <b-navbar toggleable="lg" type="dark" variant="info">
            <b-navbar-brand href="#">ombagoes.com</b-navbar-brand>
            <b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
            <b-collapse id="nav-collapse" is-nav>
                <b-navbar-nav>
                    <b-nav-item to="/Home">Home</b-nav-item>
                    <b-nav-item to="About">About</b-nav-item>
                    <b-nav-item to="#" disabled>Disabled</b-nav-item>
                </b-navbar-nav>
                <b-navbar-nav class="ml-auto">
                    <b-nav-item v-show="!user" href="/">Login</b-nav-item>
                    <b-nav-item-dropdown v-if="user" right>
                        <template v-slot:button-content>
                            <em>{{user.displayName}}</em>
                        </template>
                        <b-dropdown-item to="/profile">Profile</b-dropdown-item>
                        <b-dropdown-item @click="signoutButtonPressed">
                            Sign Out
                        </b-dropdown-item>
                    </b-nav-item-dropdown>
                </b-navbar-nav>
            </b-collapse>
        </b-navbar>
    </section>
</template>

<script>
import firebase from "firebase";
export default {
    data() {
        return {
            user: null
        };
    },
    created() {
        firebase.auth().onAuthStateChanged(user => {
            if (user) {
                this.user = user;
                this.$store.state.isLogin=true;
                this.$store.state.user=user;
            }
        });
    },
    methods: {
        signoutButtonPressed(e) {
            e.stopPropagation();
            firebase.auth().signOut();
            this.$router.push({ name: "Login" });
        }
    }
};
</script>

NavBar.vue mengatur penyimpanan status login jika ada perubahan status jika login akan menyimpan data user di store dan menghapus data user jika sebaliknya.

Lalu buat layout pada App.vue

<template>
    <div id="app">
        <navigation v-show="getAuth"/>
        <router-view/>
    </div>
</template>

<script>
import navigation from "@/components/NavBar.vue";
export default {
    name: "App",
    components: {
        navigation
    },
    computed: {
        getAuth: function (){
            return this.$store.state.isLogin;
        }
    }
};
</script>

<style>
#app {
    font-family: 'Avenir', Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
}
</style>

perhatikan baris <navigation v-show=”getAuth” /> component ini hanya akan tampil jika posisi user login dimana getAuth diambil dari store.

Menyiapkan component Profile

components/Profile.vue akan menampilkan data user yang sebelumnya disimpan pada store.

<template>
    <section>
        <div v-if="getUser">
            <b-card v-bind:img-src="getUser.photoURL" img-alt="Card image" img-left class="mb-3">
                <b-card-text>
                    <p>
                        name:<strong>{{getUser.displayName}}</strong>
                        <br />email:
                        <strong>{{getUser.email}}</strong>
                        <br />uid:
                        <strong>{{getUser.uid}}</strong>
                        <br />provider:
                        <strong class="teal-text">{{getUser.providerData[0].providerId}}</strong>
                    </p>
                </b-card-text>
            </b-card>
        </div>
    </section>
</template>

<script>
import firebase from "firebase";
export default {
    data() {
        return {};
    },
    computed:{
        getUser:function(){
            return this.$store.state.user;
        }
    },
};
</script>

Jalankan hasilnya sbb:

Login
Pilih Email
Profile

Source

  1. https://softauthor.com/firebaseui-vue-login-with-facebook-google-and-email-pasword/