Vue: how to call 3 different v-on: click functions - vue.js

I have a main page with 3 buttons that lead to the other 3 pages. All 4 pages (components) are connected through App.vue.
I use :click="goPage('pageName') at the main page with 3 different pageNames. And method:
goPage: function (status) {
this.$emit(status)
}
I am trying to pass my "pageName" to App.vue
<main-page v-if="status === 'mainPage'"
v-on:goPage="goPage($event)"
and
goPage: function(status){
console.log(status)
this.status = status
}
I used $emit approach and it worked for a single page. But I have no idea how to work with multiple calls.
Do $emit or should use something other?

You need to pass method as prop to your main page component. on main page component, get chageStatus function from prop then for each button set an #click to change your status to desire page name. on top component you need to define a method to change status for you:
<template>
<div>
<main-page v-if="status === 'mainPage'" :changeStatus ="this.changeStatus" />
<other-page v-else-if="status === 'otherPage'"/>
<another-page v-else-if="status === 'anotherPage'"/>
<div>
</template>
<script>
export default {
data(){
return {
status: 'mainPage'
}
}
methods: {
changeStatus(dst) {
this.status = dst
}
}
}
</script>

Related

Vue – pass variable from child template to App.vue

I read up on passing variables from child to parent using $emit but I can't fully figure it out yet.
In App.vue I have a <header/> component for the page header containing a button which controls the mobile navigation's visibility. On click it changes its class:
<button #click="toggleMobileNavigation" :class="isOpen ? 'is-open' : 'is-closed'">
The <header/>'s js:
export default {
data() {
return {
isOpen: false,
};
},
methods: {
toggleMobileNavigation() {
if(!this.isOpen) {
this.isOpen = true;
} else {
this.isOpen = false;
}
this.$emit(this.isOpen)
}
}
}
The App.vue:
<Header />
<main id="main" tabindex="-1" class="main" :class="isOpen">
This obviously this doesn't work and I can't figure out what the right way is to catch the $emit.
Thanks for any tips!
I would say you are on the right track, this child needs to emit some event to alert its parent of an important change.
But instead of doing this in your Header component:
this.$emit(this.isOpen)
Supply an event name:
this.$emit('opened', this.isOpen)
// or:
if (this.isOpen) {
this.$emit('opened');
} else {
this.$emit('closed');
}
The way you catch this event in the parent component (App.vue) should be:
<Header #opened="handleOpenedEvent"> // will call method handleOpenedEvent
// alternatively:
<Header #opened="menuStatus = $event"> // $event contains data you supply as second argument to your this.$emit(name, ...) call
// #[eventname] is one way of doing it, v-on is the same:
<Header v-on:opened="handleEvent">

Call API automatically to fetch data with prop value when component is displayed in Vue.js

I have a page which displays a list of mutual funds. With each mutual fund, I have a button to display their NAV history. This button calls a component which has an embedded API call to fetch the NAV history. I pass the fund code for which the data is to be fetched as a prop to the component. However, I am not able to trigger the API call automatically when the prop is called.
this is my code as of now:
Parent component (main page):
<template>
<!-- some code -->
<a href="#" #click="fetchNavHistory(fund)">
<v-icon small>history</v-icon>
</a>
<!-- some more code -->
<NAVHistory
:show="showNavHistory"
:amfi_code="amfi_code"
ref="history"
#hide="showNavHistory=false"
/>
</template>
export default {
name: "FundList",
components: {
NAVHistory
},
data() {
return {
showNavHistory: false,
amfi_code: 0
}
},
methods:{
fetchNavHistory(fund){
this.amfi_code = fund.amfi_code
this.showNavHistory = true
var child = this.$refs.history
child.fetchNavHistory()
}
}
}
Child component (where NAV history is displayed):
<template>
<!-- some code -->
</template>
<script>
export default {
props: {
show: Boolean,
amfi_code: Number
},
data(){
return{
apiURL: process.env.VUE_APP_BASEURL,
navHistory: [],
}
},
methods: {
async fetchNavHistory(){
try{
const response = await fetch(this.apiURL + '/navhistory', {
method: 'POST',
body: JSON.stringify({"amfi_code": this.amfi_code}),
headers: {'content-type': 'application/json; charset=UTF-8'},
})
const data = await response.json()
console.log(data)
this.navHistory = data
} catch(error){
console.log(error)
}
}
}
}
</script>
At first I tried calling the fetchNavHistory() method on updated() event. But that kept calling the API non-stop when the component was displayed on the screen.
Then I tried adding a watch for the show prop. But that didn't work at all.
Finally, as a workaround, I called the API from the parent component itself. While that is working, it is calling the component with the previous value of the amfi_code, rather than the updated value. So the first time it gets called, the amfi_code is passed as 0.
Is there a way to safely trigger the API call when the component is displayed, i.e., the show prop is set to true?
You can try watch with deep:true option that way the watch will be triggered when a component will be mounted. Or you can call API on mounted hook and check show prop in it.
deep:true means a watch will look at if changes occur not only for a watched prop but additionally at all nested props.
immediate:true means that a watch will fire after a component is mounted (when a watched prop has initial value).

How to dynamically mount vue component with props

Scenario / context
I have an overview component which contains a table and an add button. The add button opens a modal component. When i fill in some text fields in the modal and click the save button, a callback (given as prop) is called so the parent component (the overview) is updated. The save button also triggers the model toggle function so the model closes.
So far works everything like expected but when i want to add a second entry, the modal is "pre-filled" with the data of the recently added item.
Its clear to me that this happens because the model component keeps mounted in the background (so its just hidden). I could solve this by "reset" the modals data when the toggle function is triggered but i think there should be a better way.
I have a similar issue when i want to fetch data in a modal. Currently i call the fetch function in the mounted hook of the modal. So in this case the fetch happens when the parent component mounts the modal. This does not make sense as it should only (and each time) fetch when the modal is opened.
I think the nicest way to solve this is to mount the modal component dynamically when i click the "add" (open modal) button but i can't find how i can achieve this. This also avoids that a lot of components are mounted in the background which are possibly not used.
Screenshot
Example code
Overview:
<template>
<div>
// mount of my modal component
<example-modal
:toggleConstant = modalToggleUuid
:submitHandler = submitHandler />
// The overview component HTML is here
</div>
</template>
<script>
export default {
data() {
return {
modalToggleUuid: someUuid,
someList: [],
}
},
mounted() {
},
methods: {
showModal: function() {
EventBus.$emit(this.modalToggleUuid);
},
submitHandler: function(item) {
this.someList.push(item);
}
}
}
</script>
Modal:
<template>
<div>
<input v-model="item.type">
<input v-model="item.name">
<input v-model="item.location">
</div>
</template>
<script>
export default {
data() {
return {
modalToggleUuid: someUuid,
item: {},
}
},
mounted() {
// in some cases i fetch something here. The data should be fetched each time the modal is opened
},
methods: {
showModal: function() {
EventBus.$emit(this.modalToggleUuid);
},
submitHandler: function(item) {
this.someList.push(item);
}
}
}
</script>
Question
What is the best practive to deal with the above described scenario?
Should i mount the modal component dynamically?
Do i mount the component correctly and should i reset the content all the time?
You are on the right way and in order to achieve what you want, you can approach this issue with v-if solution like this - then mounted() hook will run every time when you toggle modal and it also will not be present in DOM when you are not using it.
<template>
<div>
// mount of my modal component
<example-modal
v-if="isShowModal"
:toggleConstant="modalToggleUuid"
:submitHandler="submitHandler"
/>
// The overview component HTML is here
</div>
</template>
<script>
export default {
data() {
return {
isShowModal: false,
modalToggleUuid: someUuid,
someList: []
};
},
mounted() {},
methods: {
showModal: function() {
this.isShowModal = true;
},
submitHandler: function(item) {
this.someList.push(item);
this.isShowModal = false;
}
}
};
</script>

Dynamically show components depending on the current route, vuejs

In vue how do I dynamically show components based on the current route?
I only want the custom-componentto be visible if not on the homepage. And by default, I set that the user is on the homepage so the component doesn't show on load.
I've tried several router methods with no luck: https://router.vuejs.org/api/#router-instance-methods.
app.vue:
<template>
<div id="app" :class="$style.app">
<navbar/>
<custom-component v-bind:is="homePage"></custom-component>
<router-view :class="$style.content"/>
<footer/>
</div>
</template>
data() {
return {
homePage: true
}
},
methods: {
homePage() {
if(this.$route.path("/") || this.$route.path("/home")) {
this.homePage = true
} else {
this.homePage = false
}
}
}
This is close, but doesn't achieve the desired results: VueJS - Load child components dynamically.
Also would this be the best way to do it in app.vue as I am trying now. Or should I have this logic in the custom-component instead?
Why not using a computed property instead of a method ?
export default {
computed: {
homePage() {
if(this.$route.path == "/" || this.$route.path == "/home" ) {
return true
} else {
return false
}
}
}
}
You can achieve your goal by using v-if or v-show directives
<custom-component v-show="homePage"></custom-component>
Or
<custom-component v-if="homePage"></custom-component>
If i were you i would watch the route object for further changes like this and use one of the option above according to this statement
Generally speaking, v-if has higher toggle costs while v-show has
higher initial render costs. So prefer v-show if you need to toggle
something very often, and prefer v-if if the condition is unlikely to
change at runtime.
You can check out further details about conditional rendering from vue.js doc
Also would this be the best way to do it in app.vue as I am trying
now.
No, you shouldn't bloat your app.vue file whilst you can handle the same
problem with different components in more modular way.
Or should I have this logic in the custom-component instead?
In general if you can assume that this chunk of code can be used in different parts of the application, it's better to implement as different component.
The easiest option to show some component on the page is a method that return true or false. When we use v-if, component will be ignored during rendering.
<template>
<div id="app" :class="$style.app">
<navbar/>
<custom-component v-if="homePage()"></custom-component>
<router-view :class="$style.content"/>
<footer/>
</div>
</template>
<script>
export default {
methods: {
homePage() {
if(this.$route.path == "/" || this.$route.path == "/home" ) {
return true
} else {
return false
}
}
}
}
</script>
Use the v-if binding in your component
<custom-component v-if="homePage" v-bind:is="homePage"></custom-component>
where homePage is a bool value.

How to set keyup on whole page in Vue.js

Is it possible to set a v-on:keyup.enter on the whole page, not only for an input element in javascript framework Vue.js ?
Perhaps a better way to do this is with a Vue component. This would allow you to control when you listen to events by including or not including the component. Then you could attach event listeners to Nuxt using the no-ssr component.
Here is how you create the component:
<template>
<div></div>
</template>
<script>
export default {
created() {
const component = this;
this.handler = function (e) {
component.$emit('keyup', e);
}
window.addEventListener('keyup', this.handler);
},
beforeDestroy() {
window.removeEventListener('keyup', this.handler);
}
}
</script>
<style lang="stylus" scoped>
div {
display: none;
}
</style>
Then on the page you want to use that component you'd add this HTML:
<keyboard-events v-on:keyup="keyboardEvent"></keyboard-events>
And then you'll have to add your event handler method:
methods: {
keyboardEvent (e) {
if (e.which === 13) {
// run your code
}
}
}
Short answer is yes, but how depends on your context. If you are using vue-router as I am on a current project, you would want to add to the outer-most element you want that applied to. In my case I'm using the actual app.vue entry point's initial div element.
There is one catch that I believe is a hard requirement, the element has to be within the potentially focusable elements. The way I'm dealing with that is setting a -1 tabindex and just declaring my super-hotkeys (mostly for debug purposes right now) on the parent element in my app.
<template>
<div
id="app-main"
tabindex="-1"
#keyup.enter="doSomething"
>
<everything-else></everything-else>
</div>
</template>
EDIT:
As a side note, I also added a touch of additional configuration to my vue-router to make sure the right element is focused when I transition pages. This allows the pageup/pagedown scrolling to already be in the right section based on the content area being the only scrollable section. You'd also have to add the tabindex="-1" to the app-content element as well.
router.afterEach(function (transition) {
document.getElementById('app-content').focus();
});
and the basis of my app-content component:
<template>
<div id="app-content" tabindex="-1">
<router-view
id="app-view"
transition="app-view__transition"
transition-mode="out-in"
></router-view>
</div>
</template>
I created a small npm module that takes care of global keypress events in Vue, hope it makes someone's life easier:
https://github.com/lupas/vue-keypress
My simplest approach:
Add into your root Vue component (or any other component):
new Vue({
//...
created() {
window.addEventListener('keypress', this.onKeyPress);
},
beforeDestroy() {
window.removeEventListener('keypress', this.onKeyPress);
},
methods: {
onKeyPress(e) {
console.log('KEYPRESS EVENT', e)
//... your code
}
}
//...
})
In Vue 3 composition API, you can do it with a composable:
import { onMounted, onUnmounted } from "vue";
export function useKeyupEvent(handler) {
onMounted(() => document.addEventListener("keyup", handler));
onUnmounted(() => document.removeEventListener("keyup", handler));
}
and then in your component setup:
useKeyupEvent( event => console.log(event))