How to listen all child components rendered in a parent component? - vue.js

synonym.vue
<template>
<div id="synonym-container">
<div></div>
<div id="synonym-group-list-wrapper">
<ul id="synonym-group-list">
<li v-for="synonymGroup of synonymGroupList" :key="synonymGroup._id">
<carousel :synonyms="synonymGroup.synonyms"></carousel>
</li>
</ul>
</div>
</div>
</template>
<script>
import Carousel from './synonym-carousel.vue'
import $ from 'jquery'
export default {
components: {
'carousel': Carousel,
},
mounted() {
console.log($('.synonym-list'));
},
}
</script>
synonym-carousel.vue
<template>
<div class="synonym-group">
<ul class="synonym-list">
<li></li>
</ul>
</div>
</template>
<script>
export default {
}
</script>
My goal is that I want to get $('.synonym-list').width() in synonym.vue file so I can change it for all child component in parent component, but I don't know how to manage it.I checked document theres no hook for it. If u have any idea please tell me, thanks!

Use events.
<carousel #update="onCarouselUpdate" :synonyms="synonymGroup.synonyms"></carousel>
...
export default {
components: { ... },
mounted () { ... },
methods: {
onCarouselUpdate () {
console.log('Carousel Updated!')
}
}
}
synonym-carousel:
export default {
updated () {
this.$emit('update')
}
}
docs: https://v2.vuejs.org/v2/guide/components.html#Custom-Events

synonym.js
<li v-for="(synonymGroup, i) of synonymGroupList" :key="synonymGroup._id">
<carousel #update="onCarouselUpdate" :synonyms="synonymGroup.synonyms" :isLast="i === (synonymGroupList.length - 1)? true: false"></carousel>
</li>
export default {
components: { ... },
mounted () { ... },
methods: {
onCarouselUpdate () {
console.log('Carousel Updated!')
}
}
}
synonym-carousel:
export default {
mounted() {
if(this.isLast) {
this.$emit('update');
}
}
Based on #CodinCat answer, this is my final resolution. It has the advantage that it does not trigger update for each list-item (since v-for is used) but only for the last one, so when 'everything is ready'.

Related

function not updating vue property

I have this component:
<template>
<div class="hello">
<div>
My prop: {{ myprop }}?
</div>
<div>
<button class="fas fa-lock-open lock" #click="changeText()">Click</button>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'StartPage',
props: {
myprop: {
type: String
}
},
model: {
prop: 'myprop',
event: 'click'
},
methods: {
changeText () {
this.$emit('click', 'sometext')
console.log('this.myprop', this.myprop)
}
}
})
</script>
Im using vue v3. Everytime I click on the button, I still see the text "My prop: ?" in the browser.
And in the console I can see: "this.myprop undefined" every time I click on the button.
What am I doing wrong?
As per my understanding, You are trying to update the prop text on click of button from the child component. If Yes, you can achieve it simply by emitting a new text and updating that in the parent component.
Live Demo :
const ShowPropText = {
template: `<div class="hello">
<div>
My prop: {{ myprop }}
</div>
<div>
<button class="fas fa-lock-open lock" #click="changeText()">Click</button>
</div>
</div>`,
props: ['myprop'],
methods: {
changeText() {
this.$emit('click-event', 'sometext')
}
}
}
const app = Vue.createApp({
components: {
'show-prop-text': ShowPropText
},
data() {
return {
text: 'This is default text'
}
},
methods: {
methodCall(e) {
this.text = e;
}
}
})
app.mount('#app')
<script src="https://cdn.jsdelivr.net/npm/vue#next"></script>
<div id="app">
<show-prop-text :myprop="text" #click-event="methodCall"></show-prop-text>
</div>

How to emit component's method from router-view?

I have 50 models and for All model CRUDs, I would like to make toolbar for each page (like index, create, update, delete and etc.).
Look at this picture please:
My folder structure:
App.vue
<template>
<div id="app">
<ul class="nav">
<router-link to="/posts">Posts</router-link>|
<router-link to="/products">Products</router-link>|
</ul>
<hr>
<router-view class="content"/>
<hr>
<router-view name="toolbar" />
</div>
</template>
<script>
import Posts from "./views/posts/Index";
import Products from "./views/products/Index";
export default {
name: "App",
components: {
Posts,
Products
},
data() {
return {
status: "This is the default status message"
};
}
};
</script>
views/posts/Index.vue
<template>
<div class="w-full">
<div class="card-header">
<span>test</span>
</div>
</div>
</template>
<script>
export default {
methods: {
my_func(type) {
this.$notification[type]({
message: "Notification Title",
description: "This test."
});
}
}
};
</script>
views/posts/components/Toolbar.vue
<template>
<toolbar>
<toolbar-section>
<div class="toolbar-link">
<button></button>
</div>
</toolbar-section>
</toolbar>
</template>
<script>
export default {
data() {
return {
checked: null
};
},
methods: {
update: function() {
this.$emit("my_func");
}
}
};
</script>
Fiddle: https://codesandbox.io/s/trigger-event-views-165yz?fontsize=14
UPDATE
Now, I want when the user clicks on the edit button, I check the table and find the selected row and redirect to the update page and if a row does not select, something alerted.
You can have a another vue instance just for Events.
vueEventManager.js
import Vue from 'vue';
class vueEventManager {
constructor() {
this.vue = new Vue;
}
trigger(event, data = null) {
this.vue.$emit(event, data);
}
listen(event, callback) {
this.vue.$on(event, callback);
}
off(event, callback) {
this.vue.$off(event, callback);
}
once(event, callback) {
this.vue.$once(event, callback);
}
}
export default vueEventManager;
Then you can register it in your main.js file:
import vueEventManager from './folder/vueeventmanager';
window.Event = new vueEventManager();
Now you can use it in your components to emit events.
Event.trigger('eventName', {'valueName': value})
And listen to them
Event.listen('eventName', (value) => {
//do something
});

VueJS display dynamic modal component

I have posts and replys s.t. replies belong to posts via the attribute reply.posts_id.
I am attempting to show the reply form as a modal for the user to enter a reply. However, I want to create a generic Modal component that I can use everywhere with content that is specified in another component built for a specific context.
Reply to post is the first place I woul like this to work.
Currently, the Vuex correctly returns Modal visible:true when the reply button is clicked, but the modal does not render and I get the error message showing that the Modal component is not found:
Unknown custom element: <ModalReplyForm> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
I am using vuex to manage the visibility of the modal. Here are the relevant files:
store.js:
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
...
Vue.use(Vuex)
export default new Vuex.Store({
state: {
status: '',
...
modalVisible: false,
modalComponent: null
},
mutations: {
...
showModal(state, componentName) {
console.log('showing the modal')
state.modalVisible = true;
state.modalComponent = componentName;
},
hideModal(state) {
console.log('hiding the modal')
state.modalVisible = false;
}
},
actions: {
...
}
},
getters: {
isAuthenticated: state => !!state.user,
authStatus: state => state.status,
user: state => state.user,
token: state => state.token,
posts: state => {
return state.posts;
}
...
}
})
App.vue
<template>
<div id="app">
<app-modal></app-modal>
<NavigationBar />
<div class="container mt-20">
<router-view />
</div>
<vue-snotify></vue-snotify>
</div>
</template>
<script>
import AppModal from '#/components/global/AppModal';
import NavigationBar from '#/components/layout/NavigationBar'
export default {
name: "App",
components: {
AppModal,
NavigationBar
}
};
</script>
<style>
body {
background-color: #f7f7f7;
}
.is-danger {
color: #9f3a38;
}
</style>
Post.vue (houses the button to call the reply modal):
<template>
<div class="row ui dividing header news">
<!-- Label -->
<div class="m-1 col-md-2 ui image justify-content-center align-self-center">
<img v-if="post.avatar_url" :src="post.avatar_url" class="mini rounded"/>
<v-gravatar v-else :email="post.email" class="mini thumbnail rounded image rounded-circle z-depth-1-half"/>
</div>
<!-- Excerpt -->
<div class="col-md-9 excerpt">
...
<!-- Feed footer -->
<div class="feed-footer row">
<div class="small"> {{ post.created_at | timeAgo }}</div>
<button type="button" flat color="green" #click="showModal('ModalReplyForm')">
<i class="fa fa-reply" ></i>
...
<div v-show="postOwner(post)" class="">
<button type="button" flat color="grey" #click="deletePost(post.id)">
<i class="fa fa-trash " ></i>
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex';
import PostsService from '../../services/PostsService'
import RepliesService from '../../services/RepliesService'
import Replies from '#/components/Reply/Replies'
import ReplyForm from '#/components/Reply/ReplyForm'
export default {
name: "Post",
props: {
post: {
type: Object,
required: true
}
},
components: {
Replies,
ReplyForm
},
computed: {
me() {
return this.$store.getters.user
}
},
methods: {
...mapMutations(['showModal']),
...
}
};
</script>
AppModal.vue - generic Modal component
<template>
<div class="c-appModal">
<div class="c-appModal__overlay" v-if="visible"></div>
<div class="c-appModal__content" v-if="visible" #click.self="hideModal"></div>
<div class="c-appModal__innerContent">
<component :is="component"></component>
</div>
</div>
</template>
<script>
import Vue from 'vue';
import { mapState, mapMutations } from 'vuex';
export default {
name: 'AppModal',
data() {
return {
component: null
}
},
computed: {
...mapState({
visible: 'modalVisible',
modalComponent: 'modalComponent'
}),
},
methods: {
...mapMutations(['hideModal'])
},
watch: {
modalComponent(componentName) {
if (!componentName) return;
Vue.component(componentName, () => import(`#/components/modals/${componentName}`));
this.component = componentName;
}
},
created() {
const escapeHandler = (e) => {
if (e.key === 'Escape' && this.visible) {
this.hideModal();
}
};
document.addEventListener('keydown', escapeHandler);
this.$once('hook:destroyed', () => {
document.removeEventListener('keydown', escapeHandler);
});
},
};
</script>
ModalReplyForm - specific reply modal content
<template>
<div>
<div class="c-modalReply">
<div>
<label for="reply">Your comment</label>
<div class="field">
<textarea name="reply" v-model="reply" rows="2" placeholder="Compose reply"></textarea>
</div>
</div>
<button class="c-modalReply__cancel" #click="hideModal">Cancel</button>
<button class="c-modalReply__post" :disabled="!isFormValid" #click="createReply">Reply</button>
</div>
</div>
</template>
<script>
import RepliesService from '#/services/RepliesService'
import { mapMutations } from 'vuex';
export default {
name: "ModalReplyForm",
// props: {
// post: {
// type: Object,
// required: true
// }
// },
data() {
return {
reply: ""
};
},
computed: {
isFormValid() {
return !!this.reply;
},
currentGroup() {
return this.$store.getters.currentPost;
}
},
methods: {
...mapMutations([
'hideModal'
]),
async createReply () {
let result = await RepliesService.addReply({
reply: {
body: this.reply,
postId: this.post.id
}
});
this.$emit("reply-created");
this.hideModal();
}
}
};
</script>
Unknown custom element: - did you register the
component correctly? For recursive components, make sure to provide
the "name" option.
This message says that you never imported/defined ModalReplyForm, which you have not.
In my own generic modal, I ended up having to import all the components that might appear within the modal itself.
If you add a:
import ModalReportForm from ...
and a:
components: {
ModalReplyForm
}
to AppModal.vue, the modal should then do what you expect.

Pass data to vue component

i'm learning Vue.js right now, but i have a little problem on understanding a quite easy task ( maybe my idea of programming is too old ).
i've created a little component with this code.
<template>
<div class="tabSelectorRoot">
<ul>
<li v-for="(element,index) in elements" v-on:click="changeSelected(index)">
<a :class="{ 'selected': activeIndex === index }" :data-value="element.value"> {{ element.text }}</a>
</li>
</ul>
<div class="indicator"></div>
</div>
</template>
<script>
export default {
name: "TabSelectorComponent",
data () {
return {
activeIndex : 0,
elements: [
{ 'text':'Images', 'value': 'immagini','selected':true},
{ 'text':'WallArts', 'value': 'wallart'}
]
}
},
created: function () {
},
methods: {
'changeSelected' : function( index,evt) {
if ( index == this.activeIndex) { return; }
this.activeIndex = index;
document.querySelector('.indicator').style.left= 90 * index +'px';
this.$emit('tabSelector:nameChanged',)
}
}
}
</script>
and this is the root
<template>
<div id="app">
<tab-selector-component></tab-selector-component>
</div>
</template>
<script>
import TabSelectorComponent from "./TabSelectorComponent";
export default {
name: 'app',
components: {TabSelectorComponent},
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
},
'mounted' : function() {
console.log(this)
//EventManager.eventify(this,window.eventManager);
/*this.register('tabSelector:changeValue',function(el){
console.log(el);
});*/
}
}
</script>
All of this renders in something like this
I'd like to reuse the component by varying the number of objects inside the list but i cannot understand how to accomplish this simple task
The basic way to communicate between components in Vue is using properties and events. In your case, what you would want to do is add an elements property to your TabSelectorComponent that is set by the parent.
TabSelectorComponent
export default {
name: "TabSelectorComponent",
props: ["elements"],
data () {
return {
activeIndex : 0
}
},
...
}
In your parent:
<tab-selector-component :elements="elementArray"></tab-selector-component>
This is covered in the documentation here.

Click button in child component to pass data to parent (via one other child)

Is it possible to pass data by clicking on the button in nested child component, pass it to anoter child component that is level above and then to parent component?
My components are nested this way:
Parent
----Child
---------Child 2 (with the button that should trigger action)
So Child 2 look like this:(I added the addList() method that should push the variable to the list that is in the parent)
<template>
<li class="movie">
<div class="movie__image">
<img class="movie__poster" :src="imageURL" :alt="movie.Title"/>
<button class="movie__more" :href="`https://www.imdb.com/title/${movie.imdbID}`" target="_blank">Add</button>
</div>
</li>
</template>
<script>
export default {
name: 'movie-list-item',
props: {
movie: {
type: String,
required: true
}
},
methods: {
addList(event) {
this.$emit('clicked', this.movie.imdbID)
}
}
</script>
Child 1
<template>
<ul class="grid">
<movie-list-item li v-for="movie in movies" :movie="movie" :key="movie.imdbID" />
</ul>
</template>
<script>
import MovieListItem from './MovieListItem'
export default {
name: 'movie-list',
components: {
MovieListItem
},
props: {
movies: {
type: Array,
required: true
}
}
}
</script>
Parent:(I want to push the item from child 2 component to picked list.
<template>
<div>
<movie-list :movies="movies"></movie-list>
</div>
</template>
<script>
import MovieList from './components/MovieList'
export default {
name: 'app',
components: {
MovieList,
},
data () {
return {
show:false,
movies: [],
picked:[]
}
}
</script>
Without using any kind of state management, you'll have to emit two time.
Otherwise, you can use Vuex and have a state management.
Child1
<template>
<ul class="grid">
<movie-list-item li v-for="movie in movies" :movie="movie" :key="movie.imdbID" #clicked="handleClick(movie.imdbID)" />
</ul>
</template>
<script>
import MovieListItem from './MovieListItem'
export default {
.....
methods: {
handleClick(imdbID) {
this.$emit("clicked", imdbID);
}
}
}
</script>
Parent
<template>
<div>
<movie-list :movies="movies" #clicked="handleClick($event)"></movie-list>
</div>
</template>
<script>
import MovieList from './components/MovieList'
export default {
...
data () {
return {
show:false,
movies: [],
picked:[]
}
},
methods: {
handleClick(imdbID) {
this.picked.push(imdbID);
}
}
</script>