How do I display a route parameter (Vue / Firebase) - firebase

I'm creating a blog with free sewing patterns as content. I'm using route parameters to receive each blog individually. However, I'm getting a blank page when trying to retrieve its data from firebase firestore. Please help.
The blog's id appears on my address bar:
http://localhost:8080/#/admin/single-pattern/4LIS362IEWa7RKEv79g8
But it renders a blank page. I cant see my blog content.
This is my route path code. I've added a parameter of :id in my singlepattern. The SinglePattern component is where I will get the individual blog's data:
{
path: "/admin",
name: "admin",
component: Admin,
meta: {
auth: true,
},
children: [
{
path: "dashboard",
name: "dashboard",
component: Dashboard,
},
{
path: "manage-patterns",
name: "manage-patterns",
component: ManagePatterns,
},
{
path: "single-pattern/:id",
name: "single-pattern",
component: SinglePattern,
},
],
},
Here is my "ListPattern" component's code. ListPattern is where all my sewing blogs are displayed.
<template>
<div class="list-blogs">
<h1>LIST BLOG TITLES</h1>
<br />
<input type="text" v-model="search" placeholder="search blogs" />
<div
class="blog-cover"
v-for="pattern in filteredPatterns"
:key="pattern.id"
>
<div>
<router-link v-bind:to="'/admin/single-pattern/' + pattern.id">
<h3 style="cursor: pointer" v-rainbow>
{{ pattern.title | uppercase }}
</h3></router-link
>
</div>
<p
:style="'background-color: var(--lightgrey)'"
:inner-html.prop="pattern.description | snippet"
></p>
</div>
</div>
</template>
<script>
import firebase from "firebase";
import searchMixin from "../mixins/searchMixin";
// Basic Use - Covers most scenarios
import { VueEditor } from "vue2-editor";
import Quill from "quill";
const AlignStyle = Quill.import("attributors/style/align");
Quill.register(AlignStyle, true);
// import $ from "jquery";
import Swal from "sweetalert2";
window.Swal = Swal;
const Toast = Swal.mixin({
toast: true,
position: "top-end",
showConfirmButton: false,
timer: 3000,
});
window.Toast = Toast;
export default {
name: "ManagePatterns",
components: { VueEditor },
data() {
return {
patterns: [],
pattern: {
title: null,
description: null,
image: null,
},
search: "",
};
},
firestore() {
return {
patterns: firebase.firestore().collection("free-patterns"),
};
},
computed: {},
},
};
</script>
And this is my 'SinglePattern' component where the clicked blog/pattern is displayed.
<template>
<div class="single-pattern">
<div class="blog-cover">
<div>
</div>
<div v-if="pattern">
<h3 style="cursor: pointer">
{{ pattern.title }}
</h3>
<div v-if="pattern.description">
<p
:style="'background-color: var(--lightgrey)'"
:inner-html.prop="pattern.description"
></p>
</div>
</div>
</div>
</div>
</template>
<script>
import firebase from "firebase";
import searchMixin from "../../mixins/searchMixin";
export default {
data() {
return {
id: this.$route.params.id,
patterns: [],
pattern: {
title: null,
description: null,
image: null,
},
};
},
firestore() {
return {
patterns: firebase.firestore().collection("free-patterns"),
};
},
mixins: [searchMixin],
created() {
console.log(this.$route.params.id);
var pat = this;
firebase
.firestore()
.collection("free-patterns")
.doc(this.$route.params.id)
.get()
.then(function(doc) {
if (doc.exists) {
pat.pattern = doc.data().pattern;
} else {
console.log("no such doc");
}
});
},
methods: {},
};
</script>

It works. I just had to change the code in my created() hook in 'SingePattern' component.
created() {
console.log(this.$route.params.id);
var docRef = firebase
.firestore()
.collection("free-patterns")
.doc(this.$route.params.id);
docRef
.get()
.then((doc) => {
if (doc.exists) {
this.pattern = doc.data();
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
})
.catch((error) => {
console.log("Error getting document:", error);
});

Related

Can I load the component template dynamically

Is it possible to load the template from server (include components) dynamically? Or can I change the template before it rendered?
I would like to let user to store their own form template into a database and generate the form according to the template-id.
I tried to change the this.$options.template, but it seems like only work for vue2.
<!-- static/myproj/js/my-field.vue -->
<template>
<label :for="name+'Fld'" v-html="title"></label>
<input :name="name" :type="text" :value="value" :id="name+'Fld'"/>
</template>
<script>
export default{
props: {
name: {type:String, required:true},
value: {type:String, required:false, default:''},
type: {type:String, required:true},
title: {type:String, required:false, default:'Field: '},
},
data: function(){ return {}; },
}
</script>
// index.vue
const loadVueModuleOpts= {
moduleCache: {vue: Vue},
async getFile(url) {
const res = await fetch(url);
if ( !res.ok )
throw Object.assign(new Error(res.statusText + ' ' + url), { res });
return {
getContentData: asBinary => asBinary ? res.arrayBuffer() : res.text(),
}
},
};
export default{
props: {
id: {required:true, type:String, default:'abcdefg'},
},
data: function(){
this.loadSource();
return {
source: null,
target: null,
};
},
template: '<div>I\'m here to be replaced.</div>',
created: async function(){
this.$options.template=await axios.get(`/api/template/${id}`).then(resp=>resp.data);
},
components: {
'my-field': Vue.defineAsyncComponent( ()=>loadModule('/static/myproj/js/my-field.vue', loadVueModuleOpts)),
}
<!-- server response for /api/template/abcdefg -->
<form action="POST">
<my-field name="name" title="Your Name: " type="text"/>
<my-field name="email" title="Email: " type="email"/>
<input type="submit"/><input type="reset"/>
</form>
Thanks.
Finally, I got the solution. According to Vue3: How to use Vue.compile in Vue3, we can render the template directly by Vue3 like this:
// index.vue
import { h, compile } from 'vue';
const loadVueModuleOpts= {
moduleCache: {vue: Vue},
async getFile(url) {
const res = await fetch(url);
if ( !res.ok )
throw Object.assign(new Error(res.statusText + ' ' + url), { res });
return {
getContentData: asBinary => asBinary ? res.arrayBuffer() : res.text(),
}
},
};
export default{
props: {
id: {required:true, type:String, default:'abcdefg'},
},
data: function(){
this.loadSource();
return {
source: null,
target: null,
};
},
// Magic here
render: function(){
if(this.target)
return h(compile(this.target).bind(this));
return h('div', 'Loading...');
},
created: async function(){
this.$options.template=await axios.get(`/api/template/${id}`).then(resp=>resp.data);
},
components: {
'my-field': Vue.defineAsyncComponent( ()=>loadModule('/static/myproj/js/my-field.vue', loadVueModuleOpts)),
},
}

Vue Router link Ana Router exact in children

there are two components Dashboard and Apps and they have children. What I want to do is, when clicking any of the Main Components
showMenu is showing but,
I don't want the main components to be opened. and I want the main components and active class to be written
I want it to act as Dashboard and Appsself
app.vue
<template>
<router-link :to="{name:'dashboard'}" #click="isShow('dashboard')">Dashboard</router-link>
<router-link :to="{name:'apps'}" #click="isShow('apps')">Apps</router-link>
<div class="children">
<div class="dashboard" v-show="showName ==='dashboard' && showMenu">
<router-link exact :to="{name:'home'}">Home</router-link>
<router-link exact :to="{name:'about'}">About</router-link>
</div>
<div class="apps" v-show="showName ==='apps' && showMenu">
<router-link exact :to="{name:'setting'}">Setting</router-link>
<router-link exact :to="{name:'profile'}">Profile</router-link>
</div>
</div>
<div class="container">
<router-view/>
</div>
</template>
<script setup>
const showName = ref('')
const showMenu = ref(false)
function isShow(name) {
if (showName.value !== name) {
showMenu.value = true
}
if (showName.value === name) {
showMenu.value = !showMenu.value
} else {
showName.value = name
}
}
</script>
router.js
import {createRouter, createWebHistory, RouterView} from 'vue-router'
const routes = [
{
path: '/dashboards',
name: 'dashboards',
component: RouterView,
children: [
{
path: 'home',
component: () => import('../Views/home.vue'),
name: 'home',
},
{
path: 'about',
component: () => import('../Views/about.vue'),
name: 'about',
},
]
},
{
path: '/apps',
name: 'apps',
component: RouterView,
children: [
{
path: 'setting',
component: () => import('../Views/setting.vue'),
name: 'setting',
},
{
path: 'profile',
component: () => import('../Views/profile.vue'),
name: 'profile',
},
]
},
];
const router = createRouter({
linkActiveClass: "item-active",
linkExactActiveClass: "exact-active",
history: createWebHistory(),
routes
})
export default router;

Vue3 - Routes if page doesn't exist with dynamic routes not working with my 404?

I am using Vue3 and have my Router setup for detail pages. Any title returns the same data and 404 is being ignored even after adding the regEx inside the routes.
Routes:
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import HomeView from "../views/HomeView.vue";
import ErrorView from "../views/ErrorView.vue";
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "home",
component: HomeView,
},
{
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/AboutView.vue"),
},
{
path: "/article/:slug",
name: "article",
component: () =>
import(/* webpackChunkName: "article" */ "../views/ArticleView.vue"),
},
{
path: "/404",
name: "PageNotExist",
component: () => import("../views/ErrorView.vue"),
},
{
path: "/:catchAll(.*)", // Unrecognized path automatically matches 404
redirect: "/404",
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;
Article:
<template>
<div>
<h1>{{ data.title }}</h1>
<h3>{{ data.textarea }}</h3>
</div>
</template>
<script lang="ts">
import { useSanityFetcher } from "vue-sanity";
import { defineComponent } from "vue";
export default defineComponent({
name: "ArticleView",
setup: () => {
const articleQuery = `*[_type == "article"][0] {
title,
textarea,
}`;
const options = {
listen: true,
clientOnly: true,
};
const { data } = useSanityFetcher<object>(articleQuery, options);
return { data };
},
});
</script>

Need help in my VueJS + VueX + Firebase project

I am trying to show the current userName and title which was already created by signin and now stored in Firebase Cloud Firestore database.
But I don't know how to handle this, I can't find an error in the code by my self.
Here is the code:
<template>
<div id="dashboard">
<section>
<div class="col1">
<div class="profile">
<h5>{{ userProfile.name }}</h5>
<p>{{ userProfile.title }}</p>
<div class="create-post">
<p>create a post</p>
<form #submit.prevent>
<textarea v-model.trim="post.content"></textarea>
<button #click="createPost"
class="button">post</button>
</form>
</div>
</div>
</div>
<div class="col2">
<div>
<p class="no-results">There are currently no posts</p>
</div>
</div>
</section>
</div>
</template>
<script>
const fb = require('../firebaseConfig.js')
import { mapState } from 'vuex'
import firebase from 'firebase'
const db = firebase.firestore()
export default {
data() {
return {
post: {
content: ''
}
}
},
computed: {
...mapState(['userProfile'])
},
methods: {
createPost() {
fb.postsCollection.add({
createdOn: new Date(),
content: this.post.content,
userId: this.currentUser.uid,
userName: this.userProfile.name,
comments: 0,
likes: 0
}).then(ref => {
this.post.content = ''
}).catch(err => {
console.log(err)
})
}
}
}
</script>
Store.js
import Vue from 'vue'
import Vuex from 'vuex'
const fb = require('./firebaseConfig.js')
Vue.use(Vuex)
// handle page reload
fb.auth.onAuthStateChanged(user => {
if (user) {
store.commit('setCurrentUser', user)
store.dispatch('fetchUserProfile')
}
})
export const store = new Vuex.Store({
state: {
currentUser: null,
userProfile: {}
},
actions: {
clearData ({ commit }) {
commit('setCurrentUser', null)
commit('setUserProfile', {})
},
fetchUserProfile ({
commit,
state
}) {
fb.usersCollection.doc(state.currentUser.uid).get().then(res => {
commit('setUserProfile', state.res.data())
}).catch(err => {
console.log(err)
})
console.log(state)
}
},
mutations: {
setCurrentUser (state, val) {
state.currentUser = val
},
setUserProfile (state, val) {
state.userProfile = val
}
}
})
Error output:
userProfile output as Object:

vue router error with firebase auth

I tried to auth using vue.js and firebase.
and error occurs router.beforeEach function,
Anyone has any idea why it might happen?
console error
vue-router.esm.js?fe87:16 [vue-router] uncaught error during route navigation:
warn # vue-router.esm.js?fe87:16
abort # vue-router.esm.js?fe87:1904
iterator # vue-router.esm.js?fe87:1968
step # vue-router.esm.js?fe87:1717
runQueue # vue-router.esm.js?fe87:1725
confirmTransition # vue-router.esm.js?fe87:1972
transitionTo # vue-router.esm.js?fe87:1874
push # vue-router.esm.js?fe87:2181
(anonymous) # vue-router.esm.js?fe87:1960
(anonymous) # index.js?3672:44
router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import addPost from '#/components/addPost'
import showPost from '#/components/showPost'
import Login from '#/components/Login'
import SignUp from '#/components/SignUp'
import firebase from 'firebase'
Vue.use(Router)
const router = new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'app',
component: showPost
},
{
path: '/add',
component: addPost,
meta: {
requiresAuth: true
}
},
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/signup',
name: 'SignUp',
component: SignUp
}
]
})
router.beforeEach((to, from, next) => {
let currentUser = firebase.auth().currentUser;
let requiresAuth = to.matched.some(record => record.meta.requiresAuth);
if (requiresAuth && !currentUser) next('/login')
else if (!requiresAuth && currentUser) next('/')
else next()
})
export default router
main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import VueFire from 'vuefire'
import firebase from 'firebase'
Vue.use(VueFire)
Vue.config.productionTip = false
let app;
firebase.auth().onAuthStateChanged(function(user) {
if(!app) {
app = new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})
}
})
App.vue
<template>
<div id="app">
<app-header></app-header>
<router-view></router-view>
<button #click="logout">Logout</button>
</div>
</template>
<script>
import header from './components/header'
import firebase from 'firebase'
export default {
name: 'app',
components: {
'app-header': header
},
methods: {
logout: function() {
firebase.auth().signOut().then(() => {
this.$router.replace('login')
})
}
}
}
</script>
Login.vue
import firebase from 'firebase'
import db from '../firebaseInit'
const postRef = db.ref('posts')
export default {
name: 'login',
data: function() {
return {
email: '',
password: ''
}
},
methods: {
signIn: function() {
firebase.auth().signInWithEmailAndPassword(this.email, this.password).then(
(user) => {
this.$router.replace('/')
},
(err) => {
alert('Oops ' + err.message)
}
);
}
}
}
</script>
addPost.vue
<template>
<div id="add-blog">
<h2>Add a New Post</h2>
<form v-if="!submitted">
<label>Title:</label>
<input type="text" v-model="newPost.title" required />
<p>{{ getDate }}</p>
<label for="">Content:</label>
<textarea v-model.trim="newPost.content"></textarea>
<div id="checkboxes">
<p>Categories:</p>
<label>Vue.js</label>
<input type="checkbox" value="vue" v-model="newPost.categories" />
<label>CSS Magic</label>
<input type="checkbox" value="css" v-model="newPost.categories" />
</div>
<label>Author:</label>
<select v-model="newPost.author">
<option v-for="author in authors">{{ author }}</option>
</select>
<button #click.prevent="addPost">Add Post</button>
</form>
<div v-if="submitted">
<p>Congraturation!</p>
</div>
<div id="preview">
<h3>Preview Post</h3>
<h4>Title {{ newPost.title }}</h4>
<h4>Content </h4>
<p style="white-space: pre">{{ newPost.content }}</p>
<ul>
<li v-for="category in newPost.categories">{{ category }}</li>
</ul>
<p>{{ newPost.author }}</p>
</div>
</div>
</template>
<script>
import db from '../firebaseInit'
const postRef = db.ref('posts')
export default {
data() {
return {
newPost: {
date: '',
title: '',
author: '',
content: '',
categories: []
},
authors: ['Naeun', 'Raphael'],
submitted: false,
items: []
}
},
methods: {
addPost: function() {
postRef.push(this.newPost)
this.newPost.date = '',
this.newPost.title = '',
this.newPost.author = '',
this.newPost.content = '',
this.newPost.categories = ''
},
removePost: function() {
postRef.child(post['.key']).remove()
}
},
computed: {
getDate: function() {
const toTwoDigits = num => num < 10 ? '0' + num : num;
let today = new Date();
let year = today.getFullYear();
let month = toTwoDigits(today.getMonth() + 1);
let day = toTwoDigits(today.getDate());
return this.newPost.date = `${year}-${month}-${day}`;
}
}
}
</script>
Make sure that the next function is called exactly once in any given pass through the navigation guard. It can appear more than once, but only if the logical paths have no overlap, otherwise the hook will never be resolved or produce errors.
Your else if condition else if (!requiresAuth && currentUser) next('/') violates this rule.
Change your route guarding logic
// check if route requiresAuth
router.beforeEach((to, from, next) => {
if (to.matched.some(rec => rec.meta.requiresAuth)) {
const user = firebase.auth().currentUser;
// check auth state of user
user ? next() : next('/login') // user not signed in, route to login
} else {
next(); // route does not require auth
}
});
https://router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards
Hope this helps.

Resources