I have a list of child components that dynamically gets filled with component instances
data: function () {
return {
child_components: []
}
}
I would like them to be rendered in the template as the list changes, this doesn't work:
<li v-for="c in child_components">
{{c.$el}}
</li>
Is it possible to render the actual component instances? Because in the component templates there are inputs that change their data, and I would like to access that data from the child_components list.
Thank you
I think you need something. child_components is key of object, so we can't use it like that.
data: {
child_components: function () {
return [
{ el: 'example' },
{ el2: 'example2' },
];
}
}
<li v-for="c in child_components">
{{c.el}}
</li>
Related
I'm just playing around with some component instance rendering within a Vue application and I was wondering, when pushing components to an array - how do we then access the data() from that given instance of the component?
So say I have something like this in App.vue (the "Grandfather" of all of my components). I have managed to push instances of both the CodeBlock and QuoteBlock components to the pageBlocks array (i.e., my front end app appends the component where I want it to be). Here is a snippet of my App.vue file:
components: {
CodeBlock,
QuoteBlock
},
data () {
return {
pageBlocks: []
}
},
methods: {
addPageBlock (componentNomen) {
this.pageBlocks.push({ componentName: componentNomen })
},
saveDraftPage () {
for (let pageBlock of this.pageBlocks) {
console.log(pageBlock.data)
}
}
}
And here is an example of my CodeBlock data (the Quote block is "modelled" almost like for like except for a few variable name changes to distinguish it inside the component):
export default {
name: 'CodeBlock',
props: [ 'type' ],
computed: {},
data () {
return {
debug: true,
codeBlock: null,
codeBlockRows: [{
'id': 1,
'text': '$ click to edit this code block'
}],
}
},
}
I've stripped out most of this component to keep things simple.
So, my question is, if the pageBlocks array in App.vue contains instances of the above exported component...how do I access the data within?
In my naiavity I thought it would be as simple as something like this:
for (let pageBlock of this.pageBlocks) {
console.log(pageBlock.data);
}
But, alas, no luck yet...any tips?
I'm building a small vue application where among other things it is possible to delete an entry of a music collection. So at this point I have a list of music albums and next to the entry I have a "delete" button. When I do the following:
<li v-for="cd in cds">
<span>{{cd.artist}} - {{cd.album}}</span> <button v-on:click="deleteAlbum(cd.ID)">Delete</button>
</li>
and then in my methods do:
deleteAlbum(id){
this.$http.delete('/api/cds/delete/'+id)
.then(function(response){
this.fetchAll()
// });
},
this works fine so far, but to make it more nice, I want the delete functionality to appear in a modal/popup, so I made the following changes:
<li v-for="cd in cds">
<div class="cd-wrap">
<span>{{cd.artist}} - {{cd.album}}</span>
<button #click="showDeleteModal({id: cd.ID, artist: cd.artist, album: cd.album})" class="btn">Delete</button>
</div>
<delete-modal v-if="showDelete" #close="showDelete = false" #showDeleteModal="cd.ID = $event"></delete-modal>
</li>
so, as seen above I created a <delete-modal>-component. When I click on the delete button I want to pass the data from the entry to <delete-modal> component with the help of an eventbus. For that, inside my methods I did this:
showDeleteModal(item) {
this.showDelete = true
eventBus.$emit('showDeleteModal', {item: item})
}
Then, in the <delete-modal>, inside the created()-lifecycle I did this:
created(){
eventBus.$on('showDeleteModal', (item) => {
console.log('bus data: ', item)
})
}
this gives me plenty of empty opened popups/modals!!??
Can someone tell me what I'm doing wrong here?
** EDIT **
After a good suggestion I dumped the eventBus method and pass the data as props to the <delete-modal> so now it looks like this:
<delete-modal :id="cd.ID" :artist="cd.artist" :album="cd.album"></delete-modal>
and the delete-modal component:
export default {
props: ['id', 'artist', 'album'],
data() {
return {
isOpen: false
}
},
created(){
this.isOpen = true
}
}
Only issue I have now, is that it tries to open a modal for each entry, how can I detect the correct ID/entry?
I am going to show you how to do it with props since it is a parent-child relation.I will show you a simple way of doing it.You need to modify or add some code of course in order to work in your app.
Parent component
<template>
<div>
<li v-for="cd in cds" :key="cd.ID">
<div class="cd-wrap">
<span>{{cd.artist}} - {{cd.album}}</span>
<button
#click="showDeleteModal({id: cd.ID, artist: cd.artist, album: cd.album})"
class="btn"
>
Delete
</button>
</div>
<delete-modal v-if="showDelete" :modal.sync="showDelte" :passedObject="objectToPass"></delete-modal>
</li>
</div>
</template>
<script>
import Child from 'Child'
export default {
components: {
'delete-modal': Child
},
data() {
return {
showDelete: false,
objectToPass: null,
//here put your other properties
}
},
methods: {
showDeleteModal(item) {
this.showDelete = true
this.objectToPass = item
}
}
}
</script>
Child Component
<template>
/* Here put your logic component */
</template>
<script>
export default {
props: {
modal:{
default:false
},
passedObject: {
type: Object
}
},
methods: {
closeModal() { //the method to close the modal
this.$emit('update:modal')
}
}
//here put your other vue.js code
}
</script>
When you use the .sync modifier to pass a prop in child component then,there (in child cmp) you have to emit an event like:
this.$emit('update:modal')
And with that the modal will close and open.Also using props we have passed to child component the object that contains the id and other stuff.
If you want to learn more about props, click here
I am wondering what the cleanest way to expose data properties on a single-file Vue component without polluting the global namespace is.
In my main entry file(app.js) for Vue I am setting up Vue like so:
import components from './components/components';
window.app = new Vue({
el: '#vue',
store,
// etc.
});
My components.js imports all the components that I want to use as HTML snippets. Some of these components are import components on their own that are not directly set as components on my root instance.
What would be the best way to expose some data properties of certain single-file Vue components?
For instance I have a Search.vue component where I would like to send my first 3 objects from an array of results to Google Analytics:
// Expose this on `Search.vue`:
data: {
// Filled with data from ajax request.
results: []
}
My Vue instance is available globally. Is there an easy way to access a certain component perhaps by name or something?
Edit
So far the best option currently looks like accessing my property(which I have available inside my store) through a getter like so:
this.$store.getters.propertyINeed
Any suggestions on how to improve on this are welcome.
I suggest you store the data you need in a Vuex store. As you can see, the srch-component has a computed property which gives the result and there's a watcher that will automatically dispatch the data to the store. Then you can use something like app.$store to access the data without tampering the components.
Note that you can also better manage the store using modules (link).
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
topSrchResult: []
},
mutations: {
updateTopSrchResult: (state, payload) => {
state.topSrchResult = payload
}
},
actions: {
UPDATE_TOP_SRCH_RESULT: ({ commit }, data) => {
commit('updateTopSrchResult', data)
}
}
})
Vue.component('srch-component', {
template: `
<div>
<div>Input: <input v-model="inputVal" type="text"/></div>
<div>Search Results:</div>
<ul>
<li v-for="item in srchResult">{{item}}</li>
</ul>
</div>
`,
data() {
return {
inputVal: '',
dummyData: [
'Scrubs', 'Hannah Montana', '30 Rock', 'Wizards of Waverly Place',
'How I Met Your Mother', 'Community', 'South Park', 'Parks and Recreation',
'The Office', 'Brooklyn Nine-Nine', 'Simpsons', 'Fringe', 'Chuck'
]
}
},
watch: {
srchResult() {
const payload = this.srchResult.length <= 3 ? this.srchResult : this.srchResult.slice(0,3)
this.$store.dispatch('UPDATE_TOP_SRCH_RESULT', payload)
}
},
computed: {
srchResult() {
return this.dummyData
.filter(
(item) => item.toLowerCase().includes(this.inputVal.toLowerCase())
)
}
}
})
const app = new Vue({
el: '#app',
computed: {
topSrchResult() {
return this.$store.state.topSrchResult
}
},
store
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.4.1/vuex.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.js"></script>
<div id="app">
<div>Top Results</div>
<ul>
<li v-for="item in topSrchResult">{{item}}</li>
</ul>
<srch-component></srch-component>
</div>
just like the title i need to dynamically emit an event to parent component's methods, i have a component structured like this
<TableComponent
:actionmenus="actionmenus"
#edit="parenteditmethod"
#delete="parentdeletemethod">
</TableComponent>
here is actionmenus object
actionmenus: [
{title: 'Edit', emitevent: 'edit'},
{title: 'Delete', emitevent: 'delete'}
]
and then here is snippet of my tablecomponent
...
<ul>
<li v-for="menu in actionmenus"><a #click="$emit(menu.emitevent)" class="text-menu">{{ menu.title }}</a></li>
</ul>
...
i know this should be easily done by $emit('edit') or $emit('delete') without using actionmenus object but the $emit() part should be dynamic based on the passed array actionmenus so that the tablecomponent can be re-used on different case. how should i approaching this? is there any way?
From what I understand, you would like to emit an event from the child component to the parent, and pass some data with the emit (sorry if thats not the case).
As you know, you can emit events in the child component like this :
$emit("EVENT");
And Catch it in the parent like this :
<childTag v-on:EVENT="parentFunction"></childTag>
You can also pass data to the parent from the child like this :
$emit("EVENT",DATA);
And catch the data in the parent function like this
<childTag v-on:EVENT="parentFunction"></childTag>
...
methods{
parentFunction(DATA){
//Handle the DATA object from the child
}
}
Hope this helps and best of luck!
#codincat is right, that it all works. It's true also for Vue3
const rootComponent = {
el: "#app",
data: function () {
return {
actionmenus: [
{ title: "Edit", emitevent: "edit" },
{ title: "Delete", emitevent: "delete" }
]
};
},
methods: {
parenteditmethod() {
console.log("edit");
},
parentdeletemethod() {
console.log("delete");
}
}
};
const app = Vue.createApp(rootComponent);
app.component("table-component", {
props: { actionmenus: Array },
template: `
<ul>
<li v-for="menu in actionmenus">
<a #click="$emit(menu.emitevent)" class="text-menu">{{ menu.title }}</a>
</li>
</ul>`
});
const rootComponentInstance = app.mount("#app");
<!-- https://stackoverflow.com/questions/43750969/vuejs2-how-to-dynamically-emit-event-to-parent-component -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.2.29/vue.global.min.js"></script>
<div id="app">
<table-component :actionmenus="actionmenus" #edit="parenteditmethod" #delete="parentdeletemethod">
</table-component>
</div>
I'm using Vue v1.0.28 and vue-resource to call my API and get the resource data. So I have a parent component, called Role, which has a child InputOptions. It has a foreach that iterates over the roles.
The big picture of all this is a list of items that can be selected, so the API can return items that are selected beforehand because the user saved/selected them time ago. The point is I can't fill selectedOptions of InputOptions. How could I get that information from parent component? Is that the way to do it, right?
I pasted here a chunk of my code, to try to show better picture of my problem:
role.vue
<template>
<div class="option-blocks">
<input-options
:options="roles"
:selected-options="selected"
:label-key-name.once="'name'"
:on-update="onUpdate"
v-ref:input-options
></input-options>
</div>
</template>
<script type="text/babel">
import InputOptions from 'components/input-options/default'
import Titles from 'steps/titles'
export default {
title: Titles.role,
components: { InputOptions },
methods: {
onUpdate(newSelectedOptions, oldSelectedOptions) {
this.selected = newSelectedOptions
}
},
data() {
return {
roles: [],
selected: [],
}
},
ready() {
this.$http.get('/ajax/roles').then((response) => {
this.roles = response.body
this.selected = this.roles.filter(role => role.checked)
})
}
}
</script>
InputOptions
<template>
<ul class="option-blocks centered">
<li class="option-block" :class="{ active: isSelected(option) }" v-for="option in options" #click="toggleSelect(option)">
<label>{{ option[labelKeyName] }}</label>
</li>
</ul>
</template>
<script type="text/babel">
import Props from 'components/input-options/mixins/props'
export default {
mixins: [ Props ],
computed: {
isSingleSelection() {
return 1 === this.max
}
},
methods: {
toggleSelect(option) {
//...
},
isSelected(option) {
return this.selectedOptions.includes(option)
}
},
data() {
return {}
},
ready() {
// I can't figure out how to do it
// I guess it's here where I need to get that information,
// resolved in a promise of the parent component
this.$watch('selectedOptions', this.onUpdate)
}
}
</script>
Props
export default {
props: {
options: {
required: true
},
labelKeyName: {
required: true
},
max: {},
min: {},
onUpdate: {
required: true
},
noneOptionLabel: {},
selectedOptions: {
type: Array
default: () => []
}
}
}
EDIT
I'm now getting this warning in the console:
[Vue warn]: Data field "selectedOptions" is already defined as a prop. To provide default value for a prop, use the "default" prop option; if you want to pass prop values to an instantiation call, use the "propsData" option. (found in component: <default-input-options>)
Are you using Vue.js version 2.0.3? If so, there is no ready function as specified in http://vuejs.org/api. You can do it in created hook of the component as follows:
// InputOptions component
// ...
data: function() {
return {
selectedOptions: []
}
},
created: function() {
this.$watch('selectedOptions', this.onUpdate)
}
In your InputOptions component, you have the following code:
this.$watch('selectedOptions', this.onUpdate)
But I am unable to see a onUpdate function defined in methods. Instead, it is defined in the parent component role. Can you insert a console.log("selectedOptions updated") to check if it is getting called as per your expectation? I think Vue.js expects methods to be present in the same component.
Alternatively in the above case, I think you are allowed to do this.$parent.onUpdate inside this.$watch(...) - something I have not tried but might work for you.
EDIT: some more thoughts
You may have few more issues - you are trying to observe an array - selectedOptions which is a risky strategy. Arrays don't change - they are like containers for list of objects. But the individual objects inside will change. Therefore your $watch might not trigger for selectedOptions.
Based on my experience with Vue.js till now, I have observed that array changes are registered when you add or delete an item, but not when you change a single object - something you need to verify on your own.
To work around this behaviour, you may have separate component (input-one-option) for each of your input options, in which it is easier to observe changes.
Finally, I found the bug. I wasn't binding the prop as kebab-case