How do I refresh vuejs page on delete? - firebase

I have the following which works fine to delete from firebase, however, it doesn't reflect on the front end that the item was deleted until you refresh the page. How would I set it so that it also removes from the front end without having to manually hit refresh? Same question for editing too
<template>
<v-dialog max-width="600px" v-model="dialog">
<template v-slot:activator="{ on }">
<v-icon class="pr-1" v-on="on">mdi-square-edit-outline</v-icon>
</template>
<v-card>
<v-card-title>
<h2 class="font-weight-light">Edit Project <v-btn class="red darken-4 white--text" #click="onDelete(projectId)">Delete</v-btn></h2>
</v-card-title>
<v-card-text>
<v-form>
<v-row>
<v-col>
<v-text-field label="Title" v-model="title"></v-text-field>
</v-col>
<v-col>
<v-text-field label="Client" v-model="client"></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="6">
<h3>Add Milestone</h3>
<v-text-field label="Milestone" v-model="name"></v-text-field>
<v-btn #click="addMilestone">Add</v-btn>
</v-col>
<v-col cols="6">
<h3>Milestones</h3>
<v-list dense="dense">
<v-list-item v-for="mile in milestone" :key="mile.id">
<v-list-item-content>
{{ mile.id }}.{{ mile.text }}
</v-list-item-content>
</v-list-item>
</v-list>
</v-col>
</v-row>
<v-spacer></v-spacer>
<v-row>
<v-spacer></v-spacer>
<v-col class="6">
<v-btn #click="editProject(projectId)">Confirm Changes</v-btn>
</v-col>
</v-row>
</v-form>
</v-card-text>
</v-card>
</v-dialog>
</template>
<script>
import db from '#/fb.js'
export default {
data(){
return {
milestone: [],
name: null,
id: 1
}
},
props: {
projectId: String,
title: String,
client: String
},
methods: {
addMilestone() {
this.milestone.push({
text: this.name,
id: this.id++
});
},
editProject(id) {
db.collection('project').doc(id).update({
title: this.title,
client: this.client
}).then(() => {
this.dialog = false;
});
},
onDelete(id) {
db.collection('project').doc(id).delete()
}
}
}
</script>
<style scoped>
</style>
I tried the following when trying to use snapshot to update but got error that docChanges is not a function
onDelete(id) {
db.collection('project').doc(id).onSnapshot(snapshot => {
const changes = snapshot.docChanges();
changes.forEach (change => {
if(change.type === 'removed') {
this.projects.querySelector('[data-id=' + change.doc.id + ']')
}
})
})
}

U can simply add emit event and in created method make on function it's allow the system know when there a saved emit so do some thing
in you app.js add:
window.Fire = new Vue();
and in export default make created function like this:
created() {
Fire.$on('refresh',()=>{
make some thing ....
});
}
and when you want to fire the emit just do this:
Fire.$emit('refresh');

Related

How can I pass class and style attributes on a vue component to a different element like $attrs?

I have a single file component called confirm-document that looks something like this:
Sandbox
<template>
<v-dialog>
<template v-slot:activator="{ on }">
<v-btn v-bind="$attrs" :class="activatorClass" v-on="on">{{
title
}}</v-btn>
</template>
<v-card>
<v-card-title>{{ title }}</v-card-title>
<v-card-text><slot></slot></v-card-text>
</v-card>
</v-dialog>
</template>
<script>
export default {
name: "ConfirmDocument",
props: {
title: String,
activatorClass: {},
},
};
</script>
So when I then use this component like:
<ConfirmDocument
class="useless-class"
activator-class="mt-4 ml-n4"
title="Consent"
> Document Content </ConfirmDocument>
Sandbox
The classes get applied to the v-dialog, which ends up as an invisible div with nothing inside and both the activator and modal attached as sibling nodes.
Since this is mainly a wrapper component to provide a consistent UI, I actually only need for the activator to be positionable. So I want to pass the class and style props to the v-activator.
The activator-class prop that I have declared actualy works fine. But I am very curious if there a way to change the element to which the component's class and style attributes are bound, so that I can use class instead?
This is fixed in Vue.js v3 ref
For Vue.js v2, you can try this
Check v-bind and attrs computed property below
<template>
<v-dialog>
<template v-slot:activator="{ on }">
<v-btn v-bind="attrs" v-on="on">{{
title
}}</v-btn>
</template>
<v-card>
<v-card-title>{{ title }}</v-card-title>
<v-card-text><slot></slot></v-card-text>
</v-card>
</v-dialog>
</template>
<script>
export default {
name: "ConfirmDocument",
inheritAttrs: false, // <- added just for safety
props: {
title: String,
},
computed: {
attrs() {
const attrs = { ...self.$attrs }
// adding class and style for `v-bind`
attrs.class = self.$vnode.data.staticClass
attrs.style = self.$vnode.data.staticStyle
return attrs
}
}
};
</script>
Explanation - In Vue.js version 2.x.x, $attrs does not include class and style (ref) but there are many scenarios in which we wanted to pass on all the props along with class and style into another component so, $attrs should have class and style in it which they did add in version 3 of vue.js.
There is a detailed discussion on this topic on github do check it out for more details.
For version 2, what we wanted to achieve is that class and style can be passed to another component. We can pull it off by getting the class [as string] and style [as object] from the current component node i.e. vnode and pass it on to the other component using v-bind.
Now, you can pass props along with class and style into another component inside ConfirmDocument directly from the parent (or caller) component
What's about simple using props to handle this?
<template>
<v-dialog>
<template v-slot:activator="{ on }">
<v-btn :class="btnClass" v-on="on">Read {{ title }}</v-btn>
</template>
<v-card>
<slot></slot>
</v-card>
</v-dialog>
</template>
<script>
export default {
props: {
btnClass: { type: String },
title: { type: String }
}
}
</script>
and using the component:
<confirm-document
btn-class="mt-0 mb-0"
title="Privacy Policy"
>
Lorem ipsum dolor sit amet
</confirm-document>
I think you can use the inheritAttrs: false property. What it does it to make sure that attributes are not applied automatically to the root element and it lets you choose where to apply them instead.
<template>
<v-dialog>
<template v-slot:activator="{ on }">
<v-btn v-bind="buttonAttrs" >Read {{ title }}</v-btn>
</template>
<v-card>
<slot></slot>
</v-card>
</v-dialog>
</template>
<script>
export default {
inheritAttrs: false,
props: {
title: {type: String},
},
computed: {
buttonAttrs () {
// select which attrs to apply
const { title, ...rest } = this.$attrs;
return rest;
}
}
}
</script>
A working (and a bit cluttered) example can be found here.

How to change the cursor to progress when the image is loading and change it to pointer when the image is fully loaded in vuejs?

I want the cursor to be a progress when the image inside the card has not loaded. And change the cursor to pointer when the image inside the card has fully loaded. I used style binding to achieve this. :style="{ cursor: imageLoaded[index] ? pointer : progress }". But the cursor always remains a pointer. This is my code:
<v-row>
<v-col v-for="(option, index ) in options" :key="index" cols="6">
<v-lazy :options="{ threshold: 0.5 }" min-height="130">
<v-hover v-slot:default="{ hover }">
<v-card
link
width="160"
id="options_card"
:class="[option.selected ? 'elevation-8 primary' : 'elevation-2']"
#click="OptionSelected( index, option )"
:style="{ cursor: imageLoaded[index] ? pointer : progress }"
>
<v-list-item>
<v-list-item-content>
<span id="option_title">{{ option.title }}</span>
</v-list-item-content>
</v-list-item>
<v-img
width="100%"
height="130"
:src="option.thumbnail"
id="thumbnail"
#load="imageLoaded[index]=true"
>
<template v-slot:placeholder>
<v-sheet color="grey lighten-4" class="px-3 pt-3 pb-6 fill-height">
<v-skeleton-loader type="image">
</v-skeleton-loader>
</v-sheet>
</template>
</v-img>
</v-card>
</v-hover>
</v-lazy>
</v-col>
</v-row>
data () {
return {
imageLoaded: []
}
}
Can anyone help?
Thanks.
I think it's not working because, the array is to reactive because to has zero elements in the beginning.
You have to populate the imageLoaded array with false and then change the item at the required index to true when image is loaded
something like.
data() {
return {
imageLoaded: [...options.map(op => false)]
};
}

Need Assistance with Vuejs/Veutify (proboblem with input field and deleting items in list)

Im busy learning Veujs and I have two Issues.
I'm creating a simple todo app with CRUD functionality and I am passing data through an Input field and it doesn't want to set to full width if I set any rules for it in CSS
Secondly is I have a delete button that shows when you check the checkbox as complete, but I don't know what I am doing wrong I followed the vuejs documentation googled but the button doesn't want to remove the item from my list
Any help would be appreciated.
<template>
<v-card class="wrapper mx-auto">
<v-list-item>
<v-list-item-content>
<v-list-item-title c class="title">Your Todo's</v-list-item-title>
</v-list-item-content>
<v-spacer></v-spacer>
<v-text-field
prepend-inner-icon="mdi-magnify"
label="Search"
single-line
clearable
clear-icon="mdi-close-circle-outline"
></v-text-field>
</v-list-item>
<v-divider></v-divider>
<v-container id="todoApp">
<v-form name="todo-form" method="post" action v-on:submit.prevent="addTask">
<v-text-field
v-model="addTodoInput"
v-bind:class="{error: hasError}"
label="What are you working on?"
solo
#keydown.enter="create"
>
<v-fade-transition v-slot:append></v-fade-transition>
</v-text-field>
</v-form>
<v-divider class="mb-4"></v-divider>
<v-card class="todo-lists" v-if="lists.length">
<v-list-item v-for="list in lists" :key="list.id">
<v-checkbox v-model="list.isComplete" :color="list.isComplete ? 'success' : 'primary'"></v-checkbox>
<v-list-item-action>
<input class="input-width" type="text" v-model="list.text" />
</v-list-item-action>
<v-spacer></v-spacer>
<v-scroll-x-transition>
<div v-if="list.isComplete">
<v-btn class="ma-2" v-on:click="removeTask(id)" tile large color="error" icon>
<v-icon>mdi-trash-can-outline</v-icon>
</v-btn>
</div>
</v-scroll-x-transition>
</v-list-item>
</v-card>
</v-container>
</v-card>
</template>
<script>
export default {
data: () => ({
addTodoInput: null,
lists: [
{ id: 1, isComplete: true, text: "go home" },
{ id: 2, isComplete: true, text: "go home" }
],
hasError: false // <-- to handle errors
}),
methods: {
addTask: function() {
if (!this.addTodoInput) {
// <--- If no value then we are setting error to `true`
this.hasError = true;
return;
}
this.hasError = false; // <--- If textbox is filled then setting error to `false`
this.lists.push({
id: this.lists.length + 1,
text: this.addTodoInput,
isComplete: false
});
this.addTodoInput = ""; //clear the input after successful submission
},
updateTask: function(e, list) {
e.preventDefault();
list.title = e.target.innerText;
e.target.blur();
},
create() {
console.log("create");
},
removeTodo: function(lists) {
this.todos.splice(list, 1);
}
}
};
</script>
<style scoped>
.wrapper {
height: 100%;
}
input.input-width {
width: 100%;
}
</style>
Your code is right, But you need to remember you variable names, LOL.
Your delete button is calling removeTask but in your methods it is renamed as removeTodo. In the same method you are trying to splice from data todos. But in your data, its renamed as lists. Also, you are passing lists as an argument but it is then used as list
<v-btn class="ma-2" v-on:click="removeTask(id)" tile large color="error" icon>
<v-icon>mdi-trash-can-outline</v-icon>
</v-btn>
removeTodo: function(lists) {
this.todos.splice(list, 1);
}
To get everything work you just need to correct the method to remove todo as below
removeTask: function(list) {
this.lists.splice(list, 1);
}
WIDTH
You need to set width for v-card, so just update your CSS class as follows.
.wrapper {
height: 100%;
width: 100%;
}
for styling and making your input full width use <v-row></v-row> and <v-col></v-col> combination which is important part of Vuetify structure. for example:
<v-row align="center" justify="center">
<v-col cols="12" sm="12">
<your-input-goes-here/>
</v-col>
<v-row>
use tag instead of div in showing delete section, using div will affect your styling.
<template v-if="list.isComplete"> ... </template>
you called removeTask(id) function which doesn't exist !
either change it to removeTodo(list) or write new function !
<v-scroll-x-transition>
<template v-if="list.isComplete">
<v-btn class="ma-2" v-on:click="removeTodo(list)" tile large color="error" icon>
<v-icon>mdi-trash-can-outline</v-icon>
</v-btn>
</template >
</v-scroll-x-transition>

Firestore onSnapshot duplicates all DOM elements instead of replacing/updating them

I am updating the information in my database and I want that data to update the Cards I am using to display the data RealTime. This works fine but when I make a change to the database the cards I am using to display the info just duplicate on every change made.
I Want it to replace/update the Cards I am displaying But it makes a new card and adds it to the screen along with all old cards on every change.
Pretty new to Vue and Firebase cant seem to get it to work.
<template>
<v-flex xs12 sm6 md4 lg3 xlg3 v-for="user in users" :key="user.id">
<v-card raised :class="`${user.Status} text-xs-center ma-3`" >
<v-responsive align="middle" class="pt-4">
<v-icon :class=" `${user.Status} ma-n1 mb-n1`" size="55px" >account_box</v-icon>
</v-responsive>
<v-card-text>
<div align="middle" class="subheading White">{{user.Name}}</div>
<div align="middle" class="grey--text ma-1">
<v-chip color="" small ripple :class="`${user.Status} caption my-3`"><div class="White mr-2">Status: </div><div :class="`${user.Status}`"> {{user.Status}}</div> </v-chip>
</div>
<v-divider></v-divider>
<div align="middle" class="White ma-1">Unit: {{user.GroupHex}} - {{user.UserIDInt}} </div>
<div align="middle" class="grey--text mt-3">Last Message:<div class="Green">{{user.TimeStamp}}</div></div>
</v-card-text>
<v-card-actions raised align="center">
<v-spacer></v-spacer>
<router-link class="noLink" :to="{ path: '/Logs'}" append>
<v-btn color=" " small align="middle" flat class="" >
<v-icon color="grey" class="mr-2">assessment</v-icon>
<span>Log Data</span>
</v-btn>
</router-link>
<v-spacer></v-spacer>
</v-card-actions>
</v-card>
</v-flex>
</v-layout>
<NewEmployee />
</v-container>
</v-app>
</template>
<script>
import firestore from "firestore";
// eslint-disable-next-line
import EmployeePop from "#/components/updatePopups/EmployeePop";
import NewEmployee from "#/components/updatePopups/NewEmployee";
import db from "#/components/fbInit";
import firebase from "firebase";
export default {
// eslint-disable-next-line
components: {EmployeePop, NewEmployee},
data() {
return {
users: [],
links: [
{ route: "/logs" },
]
};
},
created() {
var user = firebase.auth().currentUser;
var employeeRef = db.collection('userProfiles').doc(user.uid).collection('employees')
employeeRef.onSnapshot(snap => {
snap.forEach(doc => {
const data = {
id: doc.id,
Name: doc.data().Name,
GroupHex: doc.data().GroupHex,
UserIDInt: doc.data().UserIDInt,
SuperID: doc.data().SuperID,
misc: doc.data().misc,
Status: doc.data().Status,
TimeStamp: doc.data().TimeStamp,
Original: doc.data().Original
}
this.users.push(data);
});
})
},
</script>
Duplicates v-cards instead of replacing with new data.
This is because each time a document is added to the collection the listener receives the entire set of documents of the collection and you push those documents to the users array without re-initializing the array.
You can verify that by adding a console.log() as follows, which will log the snapshot's size:
employeeRef.onSnapshot(snap => {
console.log(snap.size); <-- Add here
snap.forEach(doc => {...})
//...
});
You could re-initialize the users array each time the listener is triggered, as follows:
employeeRef.onSnapshot(snap => {
this.users = []; <-- re-initialize the array
snap.forEach(doc => {...})
//.....
});
But there is a better solution, as explained in the doc: to analyse "the actual changes to query results between query snapshots".
For example, for catching only the documents that are added to the collection, you would do as follows:
created() {
const user = firebase.auth().currentUser;
const employeeRef = db.collection('userProfiles').doc(user.uid).collection('employees')
employeeRef.onSnapshot(snap => {
snap.docChanges().forEach(change => {
if (change.type === "added") {
const data = {
id: change.doc.id,
Name: change.doc.data().Name,
....
};
this.users.push(data);
}
});
});
}

Update Firestore document ID and auth email with same method

I am teaching myself how to code by building a little project site but have been stuck for a few days trying to figure out how to update users' profile and account information. I am able to figure out how to retrieve and display the information but am having difficulty in getting them to update it
Goal:
Update the account email that they use at login
Update their full name stored in firestore (I figured this one out)
Update the document id of their user in Firestore (which is used for their profile slug)
So far I have been able to figure out how to display all three in the form fields, but only how to update the user's full name in Firestore with point 1 and 3 still escaping me.
For Goal 2 I used "Update a document" from Firestore documentation here https://firebase.google.com/docs/firestore/manage-data/add-data which works successfully
For updating user email I attempted to use the update email method from here https://firebase.google.com/docs/auth/web/manage-users
Screenshot of document in Firestore
<template>
<v-container fill-height>
<v-layout justify-center align-center v-if="profile">
<v-flex xs12 sm8 md8 style="max-width: 600px">
<v-card >
<v-toolbar dark color="primary">
<v-toolbar-title>Profile</v-toolbar-title>
</v-toolbar>
<v-flex class="ml-3 my-4">
<v-avatar size="75px" class="mr-2" >
<img
class="img-circle elevation-2 "
src="https://raw.githubusercontent.com/vuetifyjs/docs/dev/static/doc-images/lists/1.jpg"
>
</v-avatar>
<v-btn color="primary" >Upload</v-btn>
<v-btn>Delete</v-btn>
</v-flex>
<v-spacer></v-spacer>
<v-divider></v-divider>
<v-spacer></v-spacer>
<v-card-text>
<v-form>
<v-text-field
prepend-icon="person"
required
v-model="profile.fullname"
name="fullname"
label="Full Name"
type="text">
</v-text-field>
<v-text-field
v-if="user"
prepend-icon="email"
required
v-model="user.email"
name="email"
label="Email"
type="text">
</v-text-field>
<v-text-field
v-model="this.profile.id"
hint="Create a unique URL for your profile. Many people use their first and last name. <br> [Ex: reel.ly/misha.collins]"
persistent-hint
id="username"
prepend-icon="link"
name="username"
required
label="Profile URL "
type="text">
</v-text-field>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<!-- <p class="red-text center" v-if="feedback">{{ feedback }}</p> -->
<v-btn flat #click.native="updatemyProfile" color="primary">Save</v-btn>
</v-card-actions>
</v-card>
<!-- <v-card style="margin-top: 30px" class="elevation-2">
<v-toolbar dark color="primary">
<v-toolbar-title>Billing</v-toolbar-title>
</v-toolbar>
<v-card-text>
<v-form>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<p class="red-text center" v-if="feedback">{{ feedback }}</p>
<v-btn flat #click.native="updateBilling" color="primary">Update Account</v-btn>
</v-card-actions>
</v-card> -->
</v-flex>
</v-layout>
</v-container>
</template>
<script>
import db from '#/firebase/init'
import firebase from 'firebase'
import slugify from 'slugify'
export default {
name: 'Account',
data () {
return {
user: null,
profile: null,
feedback: null,
docid: null
}
},
created () {
let ref = db.collection('users')
// get current profile to list full name
ref.where('user_id', '==', firebase.auth().currentUser.uid).get()
.then(snapshot => {
snapshot.forEach(doc => {
this.profile = doc.data(),
this.profile.id = doc.id
})
})
// get current user to list email
firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.user = user
// console.log(this.user)
} else {
this.user.uid = null
}
})
},
methods: {
updatemyProfile() {
// working to change fullname but not document id
let ref = db.collection('users')
// get current profile
ref.where('user_id', '==', firebase.auth().currentUser.uid).get()
.then(snapshot => {
snapshot.forEach(doc => {
this.profile = doc.data(),
this.profile.id = doc.id
})
})
var docRef = db.collection("users").doc(this.profile.id)
return docRef.update({
id: this.profile.id, // this is adding an id field and assigning this.profile.id value to it instead of updating the document id of the user
fullname: this.profile.fullname
})
// update email address
var user = firebase.auth().currentUser
user.updateEmail(this.user.email).then(function() {
console.log("success")
}).catch(function(error) {
// An error happened.
})
}
}
}
</script>
Any help is much appreciated!
Look at this bit of code at the end of your function:
var docRef = db.collection("users").doc(this.profile.id)
return docRef.update({
id: this.profile.id,
fullname: this.profile.fullname
})
// update email address
var user = firebase.auth().currentUser
user.updateEmail(this.user.email).then(function() {
console.log("success")
}).catch(function(error) {
// An error happened.
})
You're returning early from the function with the return keyword after you call the update() method, which means that any code after that in the function (the update of email address) won't get run.

Resources