I'm creating a SPA using vuejs with vuetify and authenticating with a laravel/passport api. I'm having a hard time nailing down the conditional rendering of my nav icons. Obvs I want them to not be visible to unauthenticated users and then to show when the user is authenticated and redirected to my main page. Here is my App.vue that has the v-app and nav:
<template>
<div>
<v-app>
<v-navigation-drawer app absolute v-model="drawer"></v-navigation-drawer>
<v-toolbar app color="primary" class="white--text">
<v-toolbar-side-icon #click="drawer = !drawer" dark></v-toolbar-side-icon>
<v-toolbar-title>App</v-toolbar-title>
<v-spacer></v-spacer>
<v-toolbar-items class="hidden-sm-and-down">
<v-tooltip bottom>
<v-btn
flat
to="/"
slot="activator"
dark
v-if="showNav"
>
<v-icon>home</v-icon>
</v-btn>
<span>Dashboard</span>
</v-tooltip>
<v-tooltip bottom>
<v-btn
flat
to="/SubmitBug"
slot="activator"
dark
v-if="showNav"
>
<v-icon>add</v-icon>
</v-btn>
<span>Submit a new Bug</span>
</v-tooltip>
<v-tooltip bottom>
<v-btn
flat
to="/logout"
slot="activator"
dark
v-if="showNav"
>
<v-icon>close</v-icon>
</v-btn>
<span>Logout</span>
</v-tooltip>
</v-toolbar-items>
</v-toolbar>
<v-content>
<v-container fluid>
<router-view></router-view>
</v-container>
</v-content>
<v-footer class="pa-3 white--text" color="primary">
<v-spacer></v-spacer>
<div>© {{ new Date().getFullYear() }} SomeCoolCompany</div>
</v-footer>
</v-app>
<script>
export default {
data: () => ({
drawer: false,
showNav: false
}),
beforeCreate () {
User.checkAuth() ? this.showNav = true : this.showNav = false
console.log(this.showNav)
},
created () {
EventBus.$on('logout', () => {
User.logout()
})
}
}
</script>
And here is the User.js helper that is actually doing the api call:
import AppStorage from './AppStorage'
import Auth from '../models/Auth'
class User {
login (creds) {
axios
.post('/oauth/token', {
grant_type: 'password',
client_id: 2,
client_secret: 'secret',
username: creds.username,
password: creds.password
})
.then(response => {
this.setAuth(response.data)
})
.catch(error => console.error(error))
}
setAuth (data) {
const token = new Auth(data).accessToken
AppStorage.store(token)
}
checkAuth () {
if (AppStorage.getToken()) {
return true
}
return false
}
logout () {
AppStorage.clear()
window.location = '/'
}
}
export default User = new User()
The console log in the beforeCreate hook is logging true, but I have to refresh to get the icons to show. The User.checkAuth() returns a boolean. If I place the User.checkAuth() ? this.showNav = true : this.showNav = false in the created method, it initially logs false, but then true on refresh. Anyone know a graceful way to handle this? Thanks!
Use a getter for it, beforeCreate doesn't matter here because the elements aren't available anyway, so using a getter makes the most sense as it will fire only when the component is available:
computed: {
showNav() {
return User.checkAuth()
}
}
Alright, figured this out by calling window.location = '/' in my User.js login method, rather than this.$router.replace('/') in the login component's login method. Timing is everything.
Related
I create vuex with addNews method Inside of it and pass two arguments, title and body of item. But after calling that method inside my component it only prints my first argument title in HTML. Also i try to console.log body and i get data
This is my method in vuex
async addNews({ commit }, title, body) {
const response = await axios.post(
`https://jsonplaceholder.typicode.com/posts`,
{ title: title, body: body }
);
commit("addNews", response.data);
},
And here is component where I call it
<template>
<div class="text-center">
<v-dialog v-model="dialog" width="500">
<template v-slot:activator="{ on, attrs }">
<v-btn class="mb-5" fab dark color="primary" v-bind="attrs" v-on="on">
<v-icon dark> mdi-plus </v-icon>
</v-btn>
</template>
<v-card>
<v-card-title class="text-h5 blue lighten-2">
ADD NEW POST
</v-card-title>
<v-text-field v-model="title" required></v-text-field>
<v-text-field v-model="body" required></v-text-field>
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="primary"
class="mr-4"
#click="
onSubmit();
dialog = false;
"
>
Do It
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import { mapActions } from "vuex";
export default {
name: "AddNews",
data() {
return {
title: "",
body: "",
dialog: false,
};
},
methods: {
...mapActions(["addNews"]),
onSubmit() {
this.addNews(this.title, this.body);
this.title = "";
this.body = "";
},
},
};
</script>
Try passing an object with arguments to your action:
async addNews({ commit }, { title, body }) {
const response = await axios.post(
`https://jsonplaceholder.typicode.com/posts`,
{ title, body }
);
commit("addNews", response.data);
},
I am trying to build a reusable component that called v-dialog.
The idea is that I when the the dialog pop up there will be consist of 2 buttons which is called submit and cancel.
For the submit button of dialog component will be linked with different actions based on user clicks which button.
For example button A will call function name A and button B will call function name B and so on when user clicks on submit button of the dialog.
Let's say this is a component file I called DialogReusable.vue
<v-dialog
v-model="dialog"
persistent
max-width="300"
>
<v-card>
<v-card-title
class="text-h5"
style="word-break: break-word"
>
Title
</v-card-title>
<v-card-actions>
<v-spacer />
<v-btn
color="green darken-1"
text
#click="dialog = false"
>
Cancel Button
</v-btn>
<v-btn
class="primary"
#click="functionSubmits()"
>
Submit
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
And this is the parent file that I called MainParent.vue
For this file it has like 3 buttons which link to different function.
When user clicks on each button the Dialog should appears and when user clicks on the submit button of the dialog then it will call the respective function name that I set #click on each button.
<v-btn
v-if="condition"
color="primary"
dark
#click="functionA()"
>
Function A
</v-btn>
<v-btn
v-if="condition"
class="primary"
#click="functionB()"
>
Function B
</v-btn>
<v-btn
v-if="condition"
class="primary"
#click="functionC()"
>
Function C
</v-btn>
That is the concept of passing the functions through props attribute in Vue.
Let's me show you the first example of passing the function from parent into child component.
Getting a value from the parent
If you want a child component to access a parent's method, it seems obvious to just pass the method straight down as a prop.
<!-- Parent -->
<template>
<ChildComponent :method="parentMethod" />
</template>
// Parent
export default {
methods: {
parentMethod() {
// ...
}
}
}
And you child component would be like:
// Child
export default {
props: {
method: { type: Function },
},
mounted() {
// Use the parent function directly here
this.method();
}
}
Getting a value from the child
Other case you might want to get a value from child into parent component, for example the function in parent component has a param name.
<!-- Parent -->
<template>
<ChildComponent :method="parentMethod" />
</template>
// Parent
export default {
methods: {
parentMethod(valueFromChild) {
// Do something with the value
console.log('From the child:', valueFromChild);
}
}
}
Where in the child component you pass the value in when calling it:
// Child
export default {
props: {
method: { type: Function },
},
mounted() {
// Pass a value to the parent through the function
this.method("some param name");
}
}
Regarding you question, I think you can achieve by the following:
Let's say you have 3 submit buttons and each button has different action.
First let's create the reusable pop up dialog that acts like a child component.
<template>
<v-dialog
:value="dialog"
persistent
max-width="300"
#input="$emit('input', $event)"
>
<v-card>
<v-card-title
class="text-h5"
style="word-break: break-word"
>
Title Dialog
</v-card-title>
<v-card-actions>
<v-spacer />
<v-btn
color="green darken-1"
text
#click="close"
>
Cancel
</v-btn>
<v-btn
class="primary"
#click="onBtnSubmit"
>
Confirm
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
name: 'Dialog',
props: {
dialog: {
type: Boolean,
required: true
},
buttonActionName: {
type: String,
required: true
},
method: {
type: Function,
required: true
}
},
methods: {
close() {
this.$emit('close-dialog')
},
onReportBtnSubmit() {
if (this.buttonActionName.length > 0) {
this.method(this.buttonActionName)
} else {
this.method()
}
this.close()
}
}
}
</script>
<style scoped>
</style>
I assumed that you understand the concept of passing the functions through props attribute in Vue.
// This dialog will pass function `firstBtnSubmit` without param name
<Dialog
:dialog.sync="firstBtnDialog"
:method="firstBtnSubmit"
button-action-name=""
#close-dialog="firstBtnDialog= false;"
/>
<v-btn
#click="showDialogFirstBtn">
firstBtnClick
</v-btn>
// This dialog will pass function `secondBtnSubmit` with param name
<Dialog
:dialog.sync="secondBtnDialog"
:method="secondBtnSubmit"
button-action-name="id1"
#close-dialog="secondBtnDialog = false;"
/>
<v-btn
#click="showDialogSecBtn">
secondBtnClick
</v-btn>
// you can create a third button below by yourself.
export default {
data() {
return {
firstBtnDialog: false,
secondBtnDialog: false,
thirdBtnDialog: false,
},
methods: {
firstBtnSubmit() {
// Do your stuff
},
secondBtnSubmit(param) {
// do your staff with param
},
thirdBtnSubmit () {
// do your stuff
},
// To trigger dialog to pop up
showDialogFirstBtn() {
this.firstBtnDialog = true;
},
showDialogSecBtn() {
this.secondBtnDialog= true;
},
showDialogThirdBtn() {
this.thirdBtnDialog= true;
}
}
}
Note
I do NOT own all contents that I wrote here.
All contents I got from different site
Concept to pass function as props
How to use reusable component in Vue: Stackoverflow
I have a app, which is basically a Call centrum. You can receive calls, call to someone, receive sms and send sms etc.. I have problem with showing my SMS on screen, when I receive event from backend, I am showing that data on screen using vuex and V-for for specific component. Problem is that when I receive another event from backend with different number, I would like to show it under that first sms, but it will overwrite first sms and show only that new sms. I was trying multiple approaches, but nothing worked for me so I hope someone will be able to show me my mistake.
Here is photo of screen with one sms (red box is where second sms should be with own informations like number...)..
Here is code where I receive events.
export default function setupStream(){
let evtSource = new EventSource('/hzs/events.sse');
evtSource.addEventListener('receive_sms', event => {
let sms_data = JSON.parse(event.data);
store.dispatch('receiveSMS', sms_data);
}, false)
}
Here is my vuex code
const state = {
sms: [],
};
const getters = {
getSMS: (state) => state.sms,
};
const actions = {
receiveSMS({ commit }, sms_data) {
commit('setSMS', sms_data);
},
};
const mutations = {
setSMS: (state, sms) => (state.sms = sms),
};
export default {
state,
getters,
actions,
mutations
}
And here is component.
<template>
<v-card>
<v-card-title class="primary white--text">
{{ $t("Communication") }}
</v-card-title>
<v-card d-flex flex-column height="100%" class="card-outter scroll">
<v-col>
<div v-for="sms in getSMS" :key="sms.id">
<v-card-actions>
<v-row>
<v-btn #click="openChat" icon class="mt-4"
><v-img
max-width="30px"
max-height="30px"
class="mt-2"
src="#/assets/icons/icon-sms.svg"
alt="icon-sms"
/></v-btn>
<v-col>
<span>{{sms.date_time}}</span> <br />
<h4>{{sms.sender}}</h4>
<!-- Dialog for Adding new Note -->
<v-dialog
v-model="showEditor"
max-width="400px"
persistent
scrollable
>
<template v-slot:activator="{ on, attrs }">
<v-btn
#click="showEditor = true"
depressed
small
v-bind="attrs"
v-on="on"
>{{$t("Add Note")}}</v-btn
>
</template>
<AddNoteDialog v-on:close-card="showEditor = false"
/></v-dialog>
</v-col>
<v-spacer></v-spacer>
<v-btn class="mt-5" icon #click="deleteCommunication"
><v-img
max-width="20px"
src="#/assets/icons/icon-delete.svg"
alt="icon-delete"
/></v-btn>
</v-row>
</v-card-actions>
<v-divider></v-divider>
</div>
<v-spacer></v-spacer>
<v-divider></v-divider>
<v-card-actions class="card-actions">
<v-row>
<v-text-field
class="ml-4"
color="primary white--text"
required
:label="$t('Mobile number')"
clearable
></v-text-field>
<v-dialog
v-model="showEditor1"
max-width="450px"
persistent
scrollable
>
<template v-slot:activator="{ on, attrs }">
<v-btn
#click="showEditor1 = true"
class="mt-5 mr-4"
depressed
icon
v-bind="attrs"
v-on="on"
><v-icon>mdi-plus-circle</v-icon></v-btn
>
</template>
<AddNummberDialog v-on:close-card="showEditor1 = false"
/></v-dialog>
</v-row>
</v-card-actions>
</v-col>
</v-card>
</v-card>
</template>
<script>
import AddNoteDialog from "#/components/UI/AddNoteDialog";
import AddNummberDialog from "#/components/UI/AddNummberDialog";
import { mapGetters, mapActions } from 'vuex';
export default {
name: "Communication",
data() {
return {
dialog: false,
showEditor: false,
showEditor1: false,
note: '',
chat: this.switchChat,
};
},
computed: {
...mapGetters(['getSMS']),
},
components: { AddNoteDialog, AddNummberDialog },
props: ["switchChat"],
methods: {
...mapActions(['setupEvents']),
openChat() {
this.$emit('openChat')
},
async deleteCommunication() {
alert("Deleted");
},
},
};
</script>
<style>
.scroll {
overflow-y: scroll;
}
.card-outter {
padding-bottom: 50px;
}
.card-actions {
position: absolute;
bottom: 0;
width: 100%;
}
</style>
I think that solution is creating new array, where I will store every single SMS that I receive. Problem is that I don't know how and where to do it.
You already have your vue state array called sms which is a good start. You'll need to update your Vuex to have an additional mutation called "addNewSMS" or something:
const mutations = {
setSMS: (state, sms) => (state.sms = sms),
addNewSMS: (state, newSMS) => state.sms.push(newSMS),
};
This will update your state.sms array to include more than one element, which you should be able to loop through using a v-for loop in your template.
Of course, you'll also need to update your actions like this:
const actions = {
receiveSMS({ commit }, sms_data) {
commit('addNewSMS', sms_data);
},
};
As a sidenote, I'd personally change the sms variable name to messages so its clearer to you and other coders that it contains multiple objects.
I have three components and one of those is the parent of the others I'm trying to pass an object called talk between siblings emiting it inside an event from FollowedBrowser to LeftBar and then passing it via prop from LeftBar to TalksList component, after that another event is emited by TalksList and listened one more time for LeftBar and finally this component redefine the talk object as an empty object.
This is my parent component LeftBar.
<template>
<v-navigation-drawer width="25%" permanent clipped app light>
<talks-list v-if="inRoute('messages')" :talk="talk" #talkAdded="talkAdded()"/>
<template v-if="inRoute('messages')" v-slot:prepend>
<followed-browser #newTalk="addTalk($event)"/>
</template>
</v-navigation-drawer>
</template>
<script>
import FollowedBrowser from "./FollowedBrowser";
import TalksList from "./TalksList";
import { mapGetters } from "vuex";
export default {
data(){
return {
talk: {}
}
},
components: {
FollowedBrowser,
TalksList
},
methods: {
addTalk(talk){
this.talk = talk;
},
talkAdded(){
this.talk = {};
}
}
}
</script>
And this is my two children:
TalksList.vue
<template>
<v-container class="my-0 px-5">
<v-list flat>
<v-list-item-group class="my-0">
<div class="ma-0 pa-0" v-for="(talk, index) in talks" :key="index">
<v-divider v-if="talk.divider"></v-divider>
<v-list-item v-else class="px-2" style="cursor: pointer">
<template>
<v-list-item-avatar>
<v-img :src="correctedImageUrl(talk.recipient)"></v-img>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>
<span class="blue--text text--lighten-1">{{ completeName(talk.recipient) }}</span>
</v-list-item-title>
<v-list-item-subtitle>
<span>{{ talk.recipient.username }}</span>
</v-list-item-subtitle>
</v-list-item-content>
</template>
</v-list-item>
</div>
</v-list-item-group>
</v-list>
</v-container>
</template>
<script>
import axios from "axios";
export default {
data(){
return {
talks: []
}
},
props: {
talk: {
type: Object,
default: null,
required: true
}
},
watch: {
talk(val){
if(val){
this.talks.splice(0, 1, val);
this.$emit("talkAdded");
}
}
}
}
</script>
FollowedBrowsed.vue
<template>
<div style="display: inline">
<v-dialog scrollable v-model="dialog" max-width="400px" max-height="500px">
<v-card :loading="loading">
<v-text-field dense outlined color="blue lighten-1" label="Nombre de usuario" class="px-5" append-icon="mdi-magnify" v-model="browsedUsername"/>
<v-divider></v-divider>
<v-card-text style="height: 300px;" class="px-2">
<v-list>
<v-list-item class="px-2" style="cursor: pointer" v-for="listUser in filteredFollowed" :key="listUser.id" #click.prevent="newTalk(listUser)">
<v-list-item-content>
<v-list-item-title>
<span class="blue--text text--lighten-1">{{ completeName(listUser) }}</span>
</v-list-item-title>
<v-list-item-subtitle>
<span>{{ listUser.username }}</span>
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list>
</v-card-text>
</v-card>
</v-dialog>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import axios from "axios";
export default {
data(){
return {
browsedUsername: "",
loading: false,
dialog: false,
skeleton: true,
followed: []
}
},
watch: {
dialog(dialog){
if(!dialog){
this.browsedUsername = "";
this.item = null;
}
}
},
computed: {
...mapGetters({
authenticated: "auth/authenticated",
user: "auth/user"
}),
filteredFollowed(){
return this.followed.filter((user) => {
return user.username.toLowerCase().indexOf(this.browsedUsername.toLowerCase()) !== -1;
})
}
},
mounted(){
axios.get("all_followers_followed/followed")
.then((response) => {
if(response.data){
this.followed = response.data;
this.skeleton = false;
}
})
.catch((error) => {
console.log(error)
});
},
methods: {
async newTalk(user){
this.loading = "blue lighten-1";
await axios.post("messages/new_talk", {recipient_id: user.id})
.then((response) => {
if(response.data){
this.dialog = false;
this.$emit("newTalk", {
messages_number: 0,
recipient: user,
sender: this.user
});
}
})
.catch((error) => {
console.log(error);
});
}
}
}
When the newTalk method is called inside FollowedBrowser component newTalk event is emited but after that my screen freezes like the app was inside infinite loop and I don't know why. I omitted some code that I thought was irrelevant.
Can anybody help me.
Thanks in advance.
I solved... So simple, I just had to get a copy of talk prop inside TalksList, inside watch just put this:
watch: {
talk(val){
if(val){
if(this.talks.length){
this.talks.unshift({ divider: true });
}
let buffer = new Object();
let talk = new Object();
buffer.data = val;
talk = buffer.data;
this.talks.unshift(talk);
}
}
},
I have a Vue component that passes a callback function to another child component via props. However, it is the only piece that is undefined in the child.
I have created a repo for this so the files can be looked at. In the file brDialog.vue, I am passing button to the function click(), which should have access to the buttons callback that was passed within props from App.vue, however it is undefined within brDialog while the other two things passed with it are present(label and data).
I'll post the brDialog file, and will post the others if needed, but figured it would be easier to link a repo than post all the different files. I'm a bit new to Vue, so possibly something I'm missing in the documentation.
If you run the repo and click the Form Test button in the header, this is where the issue is.
brDialog.vue
<template>
<v-container>
<v-layout row wrap>
<v-flex xs12>
<v-dialog
v-model="show"
width="500"
persistent
>
<v-card>
<v-card-title> {{ title }} </v-card-title>
<slot name="content"></slot>
<v-card-actions>
<v-btn
v-for="button in buttons"
:key="button.label"
small
#click.native="click(button)"
>
{{ button.label }}
</v-btn>
<v-btn
v-if="showCloseButton"
small
#click.native="closeDialog()"
>
{{ closeButtonLabel }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
import { props } from './props.js'
export default {
name: 'brForm',
components: {
brTextField: () => import('#/controls/brTextField/brTextField.vue'),
brTextArea: () => import('#/controls/brTextArea/brTextArea.vue'),
brSelectList: () => import('#/controls/brSelectList/brSelectList.vue')
},
props: props,
data () {
return {
}
},
methods: {
async click (button) {
const response = await button.callback(button.data)
if (response.close) {
this.closeDialog()
}
},
closeDialog () {
this.$emit('close')
}
},
computed: {
}
}
</script>
<style>
</style>
Maybe this is something I'm missing with an $emit in Vue or something, but it seems it should be working. Can someone point out why the callback is undefined after being passed to brDialog?
callback is undefined because you define your data property (App.vue from your repo) with an arrow function and loose the Vue context on this:
data: () => {
return {
testingForm: {
//...
dialog: {
props: {
buttonCallback: this.testingFormSave, //<-- here
buttons: [
{
label: 'Save',
data: {},
callback: this.testingFormSave //<-- and here
}
]
}
}
}
}
},
To fix your issue, change data: () => {...} to data () {...}