VueJS: Run function in parent component when event triggered in child component - javascript

This is my first proper Vue JS project...I've searched Stack O and can't find anything that specifically addresses my issue, but I apologise if this has already been asked.
The Problem
I have two components, the parent is the page layout, while the child is a module of that page. When somebody clicks on a button in the child component, I want to trigger a function in the parent component.
The $emit part of the process is firing correctly according to the VueJS Dev Tools, but the function in the parent doesn't trigger.
Where am I going wrong?
Here is my abberviated code (I've removed anything not related to the issue)...
Parent Component
<template>
<div :toggle-nav="showHideNav" :class="navState">
<div class="wrapper">
<layout-toolbar></layout-toolbar>
</div>
</div>
</template>
<script>
import layoutToolbar from '~/components/layout/toolbar.vue'
export default {
components: {
layoutToolbar,
},
data: function() {
return {
navState: 'menu-closed'
}
},
methods: {
showHideNav: function(event) {
if (this.navState == 'menu-closed') {
this.navState = 'menu-open'
} else {
this.navState = 'menu-closed'
}
}
}
}
</script>
Child Component
<template>
<div class="toolbar" role="navigation">
<div class="tools" role="group">
<button
class="button-icon"
aria-label="Open Navigation"
#click="$emit('toggle-nav')">
<i aria-hidden="true" class="far fa-bars" title="Open Navigation"></i>
<span class="sr-only">Open Navigation</span>
</button>
</div>
</div>
</template>
Do I need to be making use of props?
Appreciate any help that can be offered.

You have to pass a function as a prop and then emit that function from your child component. So your parent template looks like this .
<template>
<div :toggle-nav="showHideNav" :class="navState">
<div class="wrapper">
<layout-toolbar #showHideNav="showHideNav"></layout-toolbar>
</div>
</div>
</template>
And your child template looks like this
<template>
<div class="toolbar" role="navigation">
<div class="tools" role="group">
<button
class="button-icon"
aria-label="Open Navigation"
#click="$emit('showHideNav',<pass-your-event-or-data>)">
<i aria-hidden="true" class="far fa-bars" title="Open Navigation"></i>
<span class="sr-only">Open Navigation</span>
</button>
</div>
</div>
</template>

Related

medium-zoom pagination issues

In my project I use medium-zoom with pagination. On first page it works well but on second, third, fourth... I have to click several times for close image...on second page...two times, on third 3 times...
it seems to be a problem with maybe any index number?
Here is my code:
<template>
<div>
<div v-if="loading" class="text-center">
<i class="fas fa-spinner fa-pulse fa-5x"></i>
</div>
<div v-else>
<div v-for="(item, imageIndex) in pageOfItems" :key="item.id" class="m-3">
<div class="row mt-3">
<div class="col-lg-9 my-auto" v-html="item.mytext"></div>
<div class="col-lg-3 my-auto text-center">
<article class="container">
<img
class="img-thumbnail"
:src="'http://localhost:4000/api/galeria/' + item.galeriaId + '_f.jpg'"
/>
</article>
</div>
</div>
<hr class="hr1" />
</div>
</div>
<div class="pb-0 pt-3 text-center">
<jw-pagination :items="info" :page-size="10" #changePage="onChangePage"></jw-pagination>
</div>
</div>
</template>
<script>
import mediumZoom from 'medium-zoom'
import axios from 'axios'
export default {
data() {
return {
info: [],
customLabels,
pageOfItems: [],
loading: true,
}
},
mounted() {
axios
.get('http://localhost:4000/api/fetch_galeria.php/')
.then((response) => (this.info = response.data))
.finally(() => (this.loading = false))
},
updated() {
mediumZoom('article img', {
background: 'transparent',
})
},
methods: {
onChangePage(pageOfItems) {
// update page of items
this.pageOfItems = pageOfItems
},
},
}
</script>
You will pretty much need to detach the event listeners here. Because it should be a lot of them added everytime due to the updated hook.
A naive implementation would be to add mediumZoom on the mounted hook. And when you do have a new changePage event, to detach it from all the images, then to apply it to the new ones with the same mediumZoom call.
Below is a way to see which event listeners (and probably how many) you have linked to a specific VueJS component. Select it in the Vue devtools and then, you will have access to the element's properties via $vm0.

I want to show only one input clicked (on vue.js)

I would like to show an input on the click, but being in a for loop I would like to show only the clicked one
<div v-for="(todo, n) in todos">
<i class="fas fa-minus ml-2"></i>
<li class="mt-2 todo">
{{ todo }}
</li>
<li class="button-container">
<button class="ml-1 btn btn-primary rounded-circle btn-sm" v-if="isHidden" v-on:click="isHidden = false"><i
class="THIS-CLICK"></i></button>
<input class="ml-5 border border-primary rounded" v-if="!isHidden" v-model="todo">
<button class="ml-1 btn btn-success rounded-circle btn-sm" v-if="!isHidden" v-on:click="isHidden = true"
#click="modifyTodo(n, todo)"><i class="far fa-save"></i></button>
</li>
</div>
I would like that on clicking on THIS-CLICK, only one input (that of the button clicked) is visible, but being in a for loop I don't know if it can be done
I would suggest to change the structure a bit in your app. You can clean up your code a lot by using v-if inside the button instead of two different buttons.
Also, moving as much javascript out from the markup as possible is a good practice.
I have an example below where this is done.
Regarding your question, you would have to add the key to each todo item.
new Vue({
el: "#app",
data() {
return {
todos: [{
name: 'wash hands',
isHidden: true
},
{
name: 'Stay home',
isHidden: true
}
],
};
},
methods: {
toggleTodo(n, todo) {
const hidden = todo.isHidden;
if (hidden) {
this.modifyTodo(n, todo);
todo.isHidden = false;
} else {
todo.isHidden = true;
}
},
modifyTodo(n, todo) {
//Some logic...
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<div v-for="(todo, n) in todos">
<i class="fas fa-minus ml-2"></i>
<li class="mt-2 todo">
{{ todo.name }}
</li>
<li class="button-container">
<input class="ml-5 border border-primary rounded" v-if="!todo.isHidden" v-model="todo.name">
<button #click="toggleTodo(n, todo)">
<i v-if="todo.isHidden" class="THIS-CLICK">click-this</i>
<i v-else class="far fa-save">save</i>
</button>
</li>
</div>
</div>
If you cannot do this, you could go with adding a new key to data like: hiddenTodos that would be an array of ids/a unique identifier to the todo you selected to hide.
in the template, it would be something like this:
<button #click="hiddenTodos.push(todo)">
...
<div v-if="hiddenTodos.includes(todo)"

emitting data from chiled to parent using vue.js and laravel

i new in vue.js i have test component is child and showdata component is parent my proplem is when i emit data from child to parent it is emitted successfully but when i show data in parent by #click="showusersdata1(listdata.id) i get empty data like attached image so how to show user data
here is my code
showdata.vue
<template>
<div>
id={{setUserData.id}},
name={{setUserData.name}}
email={{setUserData.email}}
<test v-on:showusersdata1="userData($event)"></test>
</div>
</template>
<script>
import MyHome from "./home";
// let Test=require('./components/test.vue').default
import test from "./test"
export default {
// components: {MyHome},
name: "showData",
data:function () {
return{
setUserData:{}
}
},
components:{
test
},
methods:{
userData:function (passedata) {
console.log(passedata)
// this.setUserData={}
this.setUserData= this.setUserData.push(passedata)
}
}
}
</script>
test.vue
<template>
<div class="row">
<div class="col-8">
<h1>this is test components1</h1>
<!-- List group -->
<div class="input-group mb-3">
<input type="text" class="form-control" placeholder="Recipient's username" aria-label="Recipient's username" aria-describedby="button-addon2">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" id="button-addon2">Button</button>
</div>
</div>
<div class="list-group" id="myList" role="tablist">
<a v-for ="(listdata,key) in list" class="list-group-item list-group-item-action active" data-toggle="list" href="#home" role="tab">
<ul class="test">
<i class="fas fa-trash" aria-hidden="true"></i>
<i class="fas fa-edit"></i>
<i #click="showusersdata1(listdata.id)" class="fas fa-eye"></i>
</ul> {{listdata.name}}</a>
</div>
</div>
<h1>this is cchiled show data coponent</h1>
</div>
</template>
<script>
import ShowData from "./showdata";
export default {
name: "Test",
components:{
ShowData
},
data: function () {
return {
list:{},
errors:{},
}
},
mounted(){
axios.post('/getAllData')
// .then((response) =>this.list=response.data )
.then((response) =>{
this.list=response.data
} )
.catch((error) =>this.errors=error.response.data.errors )
},
methods:{
showusersdata1:function (key) {
var index = this.list.find( ({ id }) => id == key );
this.$emit('userData', index)
}
}
}
</script>
<style scoped>
.test{
float:right
}
</style>
You are emiting here:
this.$emit('userData', index)
Which means the name of your event is 'userData'. That's what you have to listen to in the parent. But if you check your parent, you are listening to the event like this:
v-on:showusersdata1="userData($event)"
This means you are trying to listen to the 'showusersdata1' event, which is not fired. You are confusing the method name in your child for your event name. Instead of what you did, you can listen to your event like this:
v-on:userData="userData"
It's also kind of a convention to name event listeners by adding "on" in front of them, example:
event name is 'userDataReceived'
event listener would be onUserDataReceived

Nesting a component inside another component without nesting it in the routes

I am currently learning Vue JS and practising it by making some apps. I've encountered a problem, which I wasn't able to figure out and I need your help with it.
I have a dashboard component where a user can do CRUD operations for articles and announcements. The template is structured like this:
<template>
<div class="dashboard">
<div class="panel">
<div class="panel-header">
<div>
<router-link to="/settings"
tag="button"
class="btn btn-action"><i class="icon website-icon"></i></router-link>
<router-link to="/settings"
tag="button"
class="btn btn-action"><i class="icon messages-icon"></i></router-link>
</div>
<div class="panel-title">Admin Panel</div>
<div>
<router-link to="/settings"
tag="button"
class="btn btn-action"><i class="icon settings-icon"></i></router-link>
<router-link to="/logout"
tag="button"
class="btn btn-action" style="font-size: 1rem"><i class="icon logout-icon"></i></router-link>
</div>
</div>
<div class="panel-nav">
<ul class="tab tab-block">
<router-link to="/dashboard" exact
tag="li"
class="tab-item"
active-class="active">Dashboard</router-link>
<router-link to="/dashboard/articles"
tag="li"
class="tab-item"
active-class="active">Articles</router-link>
<router-link to="/dashboard/announcements"
tag="li"
class="tab-item"
active-class="active">Announcements</router-link>
</ul>
</div>
<div v-if="$route.path=='/dashboard/articles' || $route.path=='/dashboard/announcements'" class="panel-sub-nav">
<button class="btn btn-unfavorite" style="width: 25%"><i class="icon unfavorite-icon"></i> Unfavorite All</button>
<div style="width: 50%; text-align: center">0 / 5</div>
<button v-if="$route.path=='/dashboard/articles'" class="btn btn-add" style="width: 25%"><i class="icon add-icon"></i> Add Article</button>
<button v-if="$route.path=='/dashboard/announcements'" class="btn btn-add" style="width: 25%"><i class="icon add-icon"></i> Add Announcement</button>
</div>
<div class="panel-body">
<router-view></router-view>
</div>
</div>
</div>
</template>
The route file is structured like this:
import Dashboard from '../components/Dashboard'
import ArticleIndex from '../components/ArticleIndex'
import AnnIndex from '../components/AnnIndex'
import Settings from '../components/Settings'
export const routes = [
{ path: '/dashboard', component: Dashboard, children: [
{ path: 'articles', component: ArticleIndex },
{ path: 'announcements', component: AnnIndex },
] },
{ path: '/settings', component: Settings }
]
The problem is that I want users to access settings component inside the dashboard component. But they should access the settings component through /settings not /dashboard/settings.
Since I've not nested the settings component inside dashboard route.
Settings component is not opening up inside the dashboard component.
What should I do to have Settings component open inside Dashboard component but be accessed through /settings?
I think is a very easy problem to solve and sure some people already asked this question before but unfortunately, I wasn't able to find an answer.
Any help appreciated. Thank you.
Well, what you could do is keep views separated from your components. So what I would do is the following. Create
components/Settings.vue
And
views/DashboardView.vue
views/SettingsView.vue
And then you import your Settings in DashboardView and SettingsView
And in your router you of course import your {x}View components.

Meteor Uncaught TypeError: Cannot read property 'push' of undefined

I'm getting 2 errors with my meteor app. These are the errors below. I'm using accounts-ui-bootstrap-3 and I'm trying to display my header template inside layout.html.
Here are my 2 errors with some code.
Uncaught TypeError: Cannot read property 'push' of undefined
Uncaught Error: There are multiple templates named 'layout'. Each template needs a unique name.
^^Even though I only have one template named layout.
Code:
layout.html
<template name="layout">
<div class="container">
{{>header}}
<div id="main" class="row-fluid">
{{>yield}}
</div>
</div>
</template>
header.html
<template name="header">
<header class="navbar">
<div class="navbar-inner">
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="{{pathFor 'postsList'}}">Test</a>
<div class="nav-collapse collapse">
<ul class="nav pull-right">
<li>{{> loginButtons}}</li>
</ul>
</div>
</div>
</header>
</template>
Why isn't it displaying the loginbuttons?
Router.js
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn: function() { return Meteor.subscribe('posts'); }
});
Router.map(function() {
this.route('postsList', {path: '/'});
this.route('postPage', {
path: '/posts/:_id',
data: function() { return Posts.findOne(this.params._id); }
});
});
Do you use mrt:accounts-ui-bootstrap-3 or ian:accounts-ui-bootstrap-3? if you use mrt:accounts-ui-bootstrap-3, it won't work because not supported anymore. Use ian:accounts-ui-bootstrap-3 instead.
You can omit the li element for the login buttons. Change your header inclusion to look like this {{> header}}, notice the little caret.
EDIT:
Instead of using a Router.map try using Router.route like this:
Router.route('/', {
name: 'postsList'
});
Router.route({'/posts/:_id',
name: 'postPage',
data: function() {
return Posts.findOne(this.params._id);
}
});

Categories