Vue SPA - call method from any component - javascript

I'm working on a notification functionality that can be called from any component. It's a simple Vuetify v-snackbar.
In App.vue
<router-view :key="$route.fullPath"></router-view>
<v-snackbar :value="showNotification" :multi-line="false">{{
notificationText
}}
</v-snackbar>
and method
methods:{
notify(text){
this.notificationText = text
this.showNotification = true
}
}
this obviously work inside App.vue but I'd like to call notify from any component. How can I do that? I use router

In main.js add the global properties :
import Vue from 'vue'
Vue.prototype.$notificationText = ''
Vue.prototype.$showNotification = false
....
App.vue
<router-view :key="$route.fullPath"></router-view>
<v-snackbar :value="$showNotification" :multi-line="false">{{
$notificationText
}}
</v-snackbar>
then any component do :
methods:{
notify(text){
this.$notificationText = text
this.$showNotification = true
}
}

You can use a state manager (vuex) for this functionality.
// store.js
const store = new Vuex.Store({
state: {
showNotification: false,
notificationText: ''
},
mutations: {
changeNotificationState (state, value) {
state.showNotification = true
},
changeNotificationText (state, value) {
state.notificationText = value
}
},
actions: {
notify({ commit }, text) {
commit('changeNotificationState', true)
commit('changeNotificationText', 'some text')
}
}
})
<router-view :key="$route.fullPath"></router-view>
<v-snackbar :value="$store.state.showNotification" :multi-line="false">{{
$store.state.notificationText
}}
</v-snackbar>
Now from every component in your app you can call the notification function like this:
this.$store.dispatch('notify', 'notification text')

Related

Vue not reacting to a computed props change

I am using the Vue composition API in one of my components and am having some trouble getting a component to show the correct rendered value from a computed prop change. It seems that if I feed the prop directly into the components render it reacts as it should but when I pass it through a computed property it does not.
I am not sure why this is as I would have expected it to be reactive in the computed property too?
Here is my code:
App.vue
<template>
<div id="app">
<Tester :testNumber="testNumber" />
</div>
</template>
<script>
import Tester from "./components/Tester";
export default {
name: "App",
components: {
Tester,
},
data() {
return {
testNumber: 1,
};
},
mounted() {
setTimeout(() => {
this.testNumber = 2;
}, 2000);
},
};
</script>
Tester.vue
<template>
<div>
<p>Here is the number straight from the props: {{ testNumber }}</p>
<p>
Here is the number when it goes through computed (does not update):
{{ testNumberComputed }}
</p>
</div>
</template>
<script>
import { computed } from "#vue/composition-api";
export default {
props: {
testNumber: {
type: Number,
required: true,
},
},
setup({ testNumber }) {
return {
testNumberComputed: computed(() => {
return testNumber;
}),
};
},
};
</script>
Here is a working codesandbox:
https://codesandbox.io/s/vue-composition-api-example-forked-l4xpo?file=/src/components/Tester.vue
I know I could use a watcher but I would like to avoid that if I can as it's cleaner the current way I have it
Don't destruct the prop in order to keep its reactivity setup({ testNumber }) :
setup(props) {
return {
testNumberComputed: computed(() => {
return props.testNumber;
}),
};
}

how to pass data to mixins and then displaying them in your component?

I want to pass data to my mixin's method, and then display it in my component. Something like:
//component A
mixins: [mixinOne],
data(){
return{
val = null
}
},
mounted(){
this.mixinMethod('good value', this.val);
}
//mixinOne
mixinMethod(valOne, valTwo) {
valTwo = valOne;
}
And in my template I want to display val:
// component A
<template>
{{val}}
</template>
I have written the above code and it doesn't work. It returns null for {{val}}! So basically I want to see 'good value' in my component for {{val}} which is setup through my mixin. How can I do that?
You Should put your Data section in mixin then change it and render it in your component.
// MmixinOne
data () {
return {
val = null
}
},
methods: {
mixinMethod (valOne, valTwo) {
valTwo = valOne
}
}
// Component A
<template>
{{val}}
</template>
<script>
import MmixinOne from './MmixinOne'
export default {
mixins: [MmixinOne],
mounted () {
this.mixinMethod('good value', this.val)
}
}
</script>
Anyway you dont need a method to set value on "val".
you can just set your value directly in mounted:
mounted () {
this.val = 'good value'
}

Advanced Vue.js Dynamic Functional Component using `:is` syntax and render function

Background: I've built a standard single file component that takes a name prop and looks in different places my app's directory structure and provides the first matched component with that name. It was created to allow for "child theming" in my Vue.js CMS, called Resto. It's a similar principle to how WordPress looks for template files, first by checking the Child theme location, then reverting to the parent them if not found, etc.
Usage : The component can be used like this:
<!-- Find the PageHeader component
in the current child theme, parent theme,
or base components folder --->
<theme-component name="PageHeader">
<h1>Maybe I'm a slot for the page title!</h1>
</theme-component>
My goal : I want to convert to a functional component so it doesn't affect my app's render performance or show up in the Vue devtools. It looks like this:
<template>
<component
:is="dynamicComponent"
v-if="dynamicComponent"
v-bind="{ ...$attrs, ...$props }"
v-on="$listeners"
#hook:mounted="$emit('mounted')"
>
<slot />
</component>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'ThemeComponent',
props: {
name: {
type: String,
required: true,
default: '',
},
},
data() {
return {
dynamicComponent: null,
resolvedPath: '',
}
},
computed: {
...mapGetters('site', ['getThemeName']),
customThemeLoader() {
if (!this.name.length) {
return null
}
// console.log(`Trying custom theme component for ${this.customThemePath}`)
return () => import(`#themes/${this.customThemePath}`)
},
defaultThemeLoader() {
if (!this.name.length) {
return null
}
// console.log(`Trying default component for ${this.name}`)
return () => import(`#restoBaseTheme/${this.componentPath}`)
},
baseComponentLoader() {
if (!this.name.length) {
return null
}
// console.log(`Trying base component for ${this.name}`)
return () => import(`#components/Base/${this.name}`)
},
componentPath() {
return `components/${this.name}`
}, // componentPath
customThemePath() {
return `${this.getThemeName}/${this.componentPath}`
}, // customThemePath()
},
mounted() {
this.customThemeLoader()
.then(() => {
// If found in the current custom Theme dir, load from there
this.dynamicComponent = () => this.customThemeLoader()
this.resolvedPath = `#themes/${this.customThemePath}`
})
.catch(() => {
this.defaultThemeLoader()
.then(() => {
// If found in the default Theme dir, load from there
this.dynamicComponent = () => this.defaultThemeLoader()
this.resolvedPath = `#restoBaseTheme/${this.defaultThemePath}`
})
.catch(() => {
this.baseComponentLoader()
.then(() => {
// Finally, if it can't be found, try the Base folder
this.dynamicComponent = () => this.baseComponentLoader()
this.resolvedPath = `#components/Base/${this.name}`
})
.catch(() => {
// If found in the /components dir, load from there
this.dynamicComponent = () => import(`#components/${this.name}`)
this.resolvedPath = `#components/${this.name}`
})
})
})
},
}
</script>
I've tried SO many different approaches but I'm fairly new to functional components and render functions (never got into React).
The roadblock : I can't seem to figure out how to run the chained functions that I call in my original mounted() function. I've tried running it from inside the render function with no success.
Big Question
How can I find and dynamically import the component I'm targeting before I pass that component to the createElement function (or within my single file <template functional><template/>)?
Thanks all you Vue-heads! ✌️
Update: I stumbled across this solution for using the h() render function and randomly loading a component, but I'm not sure how to make it work to accept the name prop...
Late to the party, but I was in a similar situation, where I had a component in charge of conditionally render one of 11 different child components:
<template>
<v-row>
<v-col>
<custom-title v-if="type === 'title'" :data="data" />
<custom-paragraph v-else-if="type === 'paragraph'" :data="data" />
<custom-text v-else-if="type === 'text'" :data="data" />
... 8 more times
</v-col>
</v-row>
</template>
<script>
export default {
name: 'ProjectDynamicFormFieldDetail',
components: {
CustomTitle: () => import('#/modules/path/to/CustomTitle'),
CustomParagraph: () => import('#/modules/path/to/CustomParagraph'),
CustomText: () => import('#/modules/path/to/CustomText'),
... 8 more times
},
props: {
type: {
type: String,
required: true,
},
data: {
type: Object,
default: null,
}
},
}
</script>
which of course is not ideal and pretty ugly.
The functional equivalent I came up with is the following
import Vue from 'vue'
export default {
functional: true,
props: { type: { type: String, required: true }, data: { type: Object, default: null } },
render(createElement, { props: { type, data } } ) {
// prop 'type' === ['Title', 'Paragraph', 'Text', etc]
const element = `Custom${type}`
// register the custom component globally
Vue.component(element, require(`#/modules/path/to/${element}`).default)
return createElement(element, { props: { data } })
}
}
Couple of things:
lazy imports don't seem to work inside Vue.component, hence require().default is the way to go
in this case the prop 'type' needs to be formatted, either in the parent component or right here

how to move items to another component by click - vue.js

I was wondering how can I move my items -> book, from one component to another. I took this books from api and I show them in the list, so I have an ID.
<template>
<v-flex v-for="(book, index) in allBooks">
<div>Title: {{ book.title }}</div>
<i #click="markAsFavorite(book)" :class="{isActive: isMark}" class="fas fa-heart"></i>
</template>
//component books
<script>
name: 'Books',
data () {
return {
allBooks: [],
isMark: false,
favouriteBooks: []
}
},
mounted() {
axios.get("https://www.googleapis.com/books/v1/volumes?q=id:" + this.id)
.then(response => {
console.log(response)
this.allBooks = response.data.items.map(item => ({...item, isMark: false}))
console.log(this.allBooks)
})
.catch(error => {
console.log(error)
})
},
methods: {
markAsFavorite(book) {
this.isMark = !this.isMark
let favouriteAllBooks = this.favouriteBooks.push(book => {
book.id = // i dont know what?
})
},
}
</script>
//component favourite
<template>
<div class=showFavouriteBook>
<p></p>
</div>
</template>
I tried to compare this marked book ID to something, and then this array with marked books show in second template favourite. But I have no idea how to do this. Maybe somebody can prompt me something?
You should use a global eventBus for that. An 'eventBus' is another instance of Vue which is used to pass data via components tied to the main application.
At the root script of your application append the following:
const eventBus = new Vue({
data: function() {
return {
some_var: null,
}
}
});
You can use Vue mixin to have your event bus accessible globally easily:
Vue.mixin({
data: function() {
return {
eventBus: eventBus,
}
}
});
Now when you want to pass data between components you can use the bus:
Component 1
// for the sake of demo I'll use mounted method, which is invoked each time component is mounted
<script>
export default {
mounted: function() {
this.eventBus.some_var = 'it works!'
}
}
</script>
Component 2
<template>
<div>
{{ eventBus.some_var }} <!-- it works -->
</div>
</template>
In addition you can use $emit and $on.
Component 1
// for the sake of demo I'll use mounted method, which is invoked each time component is mounted
<script>
export default {
mounted: function() {
// emit 'emittedVarValue' event with parameter 'it works'
this.eventBus.$emit('emittedVarValue', 'it works!')
}
}
</script>
Component 2
<template>
<div>
{{ some_var }} <!-- "it works" once eventBus receives event "emittedVarValue" -->
</div>
</template>
<script>
export default {
data: function() {
return {
some_var: null
}
},
mounted: function() {
// waiting for "emittedVarValue" event
this.eventBus.$on('emittedVarValue', (data)=>{
this.some_var = data;
})
}
}
</script>
Hope this answer helps you.

How to dynamic change state in another vuex component?

I've got a problem with state in component. I have websocket and over it come changes, which I put it in some state. It's ok in one component, it dynamically changes value. But, when I go to the next component (vue-router). Its state changes as well, but is not dynamic. hmmmmm... in console.log changes coming, but not change value in another component.
How can I make it?
Let's see some code:
Here my action, with change states
actions: {
play(ctx, array){
axios.get('http://localhost/task_run?task_id='+array.id)
var conn = new WebSocket('ws://localhost:8080', "protocol");
conn.onmessage = function (ev) {
ctx.commit('procent', {key:array.key, val:ev.data});
ctx.commit('procentOne', {key:array.key, val:ev.data});
console.log('Message: ', ev);
};
},
},
mutations: {
procent(state, val){
var array = JSON.parse(val.val);
state.process[val.key] = array.procent;
state.processOnePersone[array.comp] = array.procent;
}
},
state: {
process: [],
processOnePersone:[],
},
getters: {
process(state){
return state.process
},
processOnePersone(state){
return state.processOnePersone;
}
}
I have one compenent, where it works
<v-progress-circular
:rotate="-90"
:size="50"
:width="5"
:value="process[key]"
color="primary"
>
{{ process[key] }}
</v-progress-circular>
<script>
import {mapGetters} from 'vuex';
export default {
name: 'taskListComponent',
computed: {
...mapGetters(['process',]),
},
}
And component where it doesn't work
<v-progress-circular
:rotate="-90"
:size="50"
:width="5"
:value="processOnePersone[key]"
color="primary"
>
{{ processOnePersone[key] }}
</v-progress-circular>
<script>
import {mapGetters} from 'vuex';
export default {
name: 'queueComponent',
computed: {
...mapGetters(['processOnePersone',]),
},
}

Categories

Resources