Having trouble passing data between Components - vue.js

How do I pass an Object Array created by a method within my component to another component? I've tried using props but perhaps my understanding of how props work isn't correct.
<template>
<div class="media-body">
<ul>
<li v-for="item in items" v-bind:class="{ active: item.active }">{{ item.text }}
</li>
</ul>
<button type="button" class="btn btn-danger" v-on:click="addItem">Test</button>
</template>
<script>
export default {
data() {
return {
item: "a",
item2: "b",
item3: "c",
item4: "d",
items: [],
}
},
methods: {
addItem: function () {
var testArray = this.item.concat(this.item2, this.item3, this.item4);
for (var i = 0; i < testArray.length; i++) {
this.items.push({
text: testArray[i],
active: false });
}
// if I check console.log(this.items) at this point I can see the data I want to pass
},
}
}
</script>
Secondary Component I'm trying to pass Data to.
<template>
<div class="media-body">
<div class="media-label">Title:
<textarea class="form-control" placeholder="Title"></textarea>
</div>
</div>
</template>
<script>
export default {
props: ['items'],
data() {
return {
}
},
</script>

To pass the props to other component you have to write following code in the first component:
added <secondComponent :items="items" /> in HTML code.
import and use secondComponent in vue component like this: components: [secondComponent]
Here is complete code with these changes:
<template>
<div class="media-body">
<ul>
<li v-for="item in items" v-bind:class="{ active: item.active }">{{ item.text }}
</li>
</ul>
<button type="button" class="btn btn-danger" v-on:click="addItem">Test</button>
<secondComponent :items="items" />
</template>
<script>
import secondComponent from '/path/of/secondComponent'
export default {
components: [secondComponent]
data() {
return {
item: "a",
item2: "b",
item3: "c",
item4: "d",
items: [],
}
},
methods: {
addItem: function () {
var testArray = this.item.concat(this.item2, this.item3, this.item4);
for (var i = 0; i < testArray.length; i++) {
this.items.push({
text: testArray[i],
active: false });
}
// if I check console.log(this.items) at this point I can see the data I want to pass
},
}
}
</script>
In the second component you have already defined items as props, which you can as well use in template/HTML of second component.

Related

can't display vuejs data property inside vue template

I'm trying to use eventbus to send data from component A:
<template>
<div v-for="(user, index) in users" :key="index" class="col-lg-6">
<div class="card card-primary card-outline">
<div class="card-body d-flex">
<h1 class="mr-auto">{{ user.name }}</h1>
Afficher
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
users: {},
}
},
methods: {
envoyerDetails($data){
Fire.$emit('envoyer_details_projet', $data);
this.$router.push('details-projet');
},
loadUser() {
if(this.$gate.isAdmin()){
axios.get("api/user").then(({ data }) => (this.users = data.data));
}
}
},
mounted() {
this.loadUser()
}
}
</script>
In component B, i receive the data and i want to display it inside the template this way:
<template>
<div class="right_col text-center" role="main">
<h5><b>name: {{ user.name }}</b> </h5>
</div>
</template>
export default {
data() {
return {
user: {},
}
},
methods: {
afficherDetails (args) {
this.user = args;
console.log(this.user.name);
}
},
mounted() {
Fire.$on('envoyer_details_projet', this.afficheDetails);
}
}
The data is not displayed in the template but it is displayed in the console. What am i missing?
Maybe when you emit the event envoyer_details_projet in component A, but component B is not mounted yet so that it can't receive the data.

How to pass data on the root components in Vue

How can I pass the page_id to the Sidebar component method highlightNode(), because I want to highlight a newly added item. My current code is the page id is undefined.
This is my code & structure.
my root component is Sidebar.vue
<template>
<div>
<ul>
<li v-for="page in pages">
<div :class="{ 'highlight': highlightedNode == page.id }">
<router-link :to="'/view/' + page.id" #click.native="highlightNode(page.id)">
<span v-title="page.title"></span>
</router-link>
</div>
</li>
</ul>
</div>
</template>
export default {
data () {
return {
pages: [],
highlightedNode: null
}
},
mounted() {
this.getPages()
this.$root.$refs.Sidebar = this
},
methods: {
getPages() {
axios.get('/get-pages').then(response => {
this.pages = response.data
});
},
highlightNode(id) {
this.highlightedNode = id
},
}
}
my add new Page component AddNewPage.vue
<template>
<div>
<div class="main-header">
<div class="page-title">
<input type="text" v-model="page.title" class="form-control">
</div>
</div>
<div class="main-footer text-right">
<button class="btn btn-success btn-sm" #click="saveChanges()">Save and Publish</button>
</div>
</div>
</template>
export default {
data () {
return {
page: {
title: null,
},
}
},
mounted() {
//
},
methods: {
saveChanges() {
axios.post('/store-new-filter', this.page)
.then(response => {
const id = response.data.id // return page id
this.$root.$refs.Sidebar.highlightNode(id) // <-- this line, I want to pass page id to hightlight the newly added page.
})
.catch( error => {
})
},
}
}
Or any alternative way to achieve my expected output.
Thanks in advance.

Change component state form another component rendered in a v-for

I have a list of components rendered in a a v-for. I want to set the "show" Boolean property as false in the other components when one of them is set to true:
To simplify I am only adding two components
Main component code:
<template>
<aside class="main-sidebar">
<section class="sidebar">
<ul class="sidebar-menu" data-widget="tree">
<nav-bar-user-profile-item></nav-bar-user-profile-item>
<nav-bar-item></nav-bar-item>
<nav-bar-item></nav-bar-item>
</ul>
</section>
</aside>
</template>
<script>
import NavBarUserProfileItem from '#/components/NavBar/NavBarUserProfileItem';
import NavBarItem from '#/components/NavBar/NavBarItem';
export default {
name: 'NavBar',
components: {
NavBarUserProfileItem,
NavBarItem
},
methods: {
MenuHasBeenToggled(event) {
console.log(event);
}
}
}
NavBarItemComponent
<template>
<li class="treeview2 item" :class="{'menu-open': isOpen, 'active': menu.active}" #click="ToggleState">
<a href="#">
<i class="fa fa-th"></i>
<span>{{ menu.title }}</span>
<span class="pull-right-container">
<i class="fa fa-angle-right pull-right"></i>
</span>
</a>
<collapse-transition>
<ul class="treeview-menu" v-show="isOpen">
<li v-for="submenu in menu.submenus" :key="submenu.title" :class="{'active': (('active' in submenu) ? submenu.active : false)}">
<b-link :href="submenu.link">
<i class="fa fa-circle-thin"></i>
{{ submenu.title }}
</b-link>
</li>
</ul>
</collapse-transition>
</li>
</template>
<script>
export default {
name: 'NavBarItem',
data: function () {
return {
isOpen: false
}
},
computed: {
},
methods: {
ToggleState() {
this.isOpen = !this.isOpen;
this.$emit("toggle-state");
}
},
props: {
menu: {
type: Object,
default: function() {
return {
link: "#",
title: "Main menu",
active: true,
submenus: [
{
link: "#",
title: "Submenu 1",
},
{
link: "#",
title: "Submenu 2",
active: true
},
{
link: "#",
title: "Submenu 3",
},
]
}
}
}
}
}
</script>
<style scoped>
</style>
The goal is to click on one of the and show the menu contents while at the same time collapse the other components.
I thought about using an array of variables and bind it to the "show" prop and with an event listen to it and set every variable to false except the one form the component that sent the event.
How can I know which component sent the event?
Any better idea on how to accomplish this task?
I think, the best way to do it is to add a uniuque identifier property to each NavBarItem and a property for a selected NavBarItem. Then in the main component you can on click on NavBarItem set selected NavBarItem and in NavBarItem make the isOpen computed on the basis if current NavBarItem identifier equals the clicked NavBarItem. Something like this:
<template>
<aside class="main-sidebar">
<section class="sidebar">
<ul class="sidebar-menu" data-widget="tree">
<nav-bar-user-profile-item></nav-bar-user-profile-item>
<nav-bar-item item-id="1" :selected-item-id="selectedNavbarItemId" #click="selectedNavBarItemId = 1"></nav-bar-item>
<nav-bar-item item-id="2" :selected-item-id="selectedNavbarItemId" #click="selectedNavBarItemId = 2"></nav-bar-item>
</ul>
</section>
</aside>
</template>
<script>
import NavBarUserProfileItem from '#/components/NavBar/NavBarUserProfileItem';
import NavBarItem from '#/components/NavBar/NavBarItem';
export default {
name: 'NavBar',
components: {
NavBarUserProfileItem,
NavBarItem
},
data: function(){
return {
selectedNavBarItemId: 0
}
},
methods: {
MenuHasBeenToggled(event) {
console.log(event);
}
}
}
And in NavBarItem
<template>
<li class="treeview2 item" :class="{'menu-open': isOpen, 'active': menu.active}" #click="ToggleState">
<a href="#">
<i class="fa fa-th"></i>
<span>{{ menu.title }}</span>
<span class="pull-right-container">
<i class="fa fa-angle-right pull-right"></i>
</span>
</a>
<collapse-transition>
<ul class="treeview-menu" v-show="isOpen">
<li v-for="submenu in menu.submenus" :key="submenu.title" :class="{'active': (('active' in submenu) ? submenu.active : false)}">
<b-link :href="submenu.link">
<i class="fa fa-circle-thin"></i>
{{ submenu.title }}
</b-link>
</li>
</ul>
</collapse-transition>
</li>
</template>
<script>
export default {
name: 'NavBarItem',
data: function () {
return {
}
},
computed: {
isOpen:function(){
return itemId == selectedItemId;
}
},
methods: {
},
props: {
itemId:Number,
selectedItemId:Number,
menu: {
type: Object,
default: function() {
return {
link: "#",
title: "Main menu",
active: true,
submenus: [
{
link: "#",
title: "Submenu 1",
},
{
link: "#",
title: "Submenu 2",
active: true
},
{
link: "#",
title: "Submenu 3",
},
]
}
}
}
}
}
</script>
<style scoped>
</style>

Send data from one component to another in vue

Hi I'm trying to send data from one component to another but not sure how to approach it.
I've got one component that loops through an array of items and displays them. Then I have another component that contains a form/input and this should submit the data to the array in the other component.
I'm not sure on what I should be doing to send the date to the other component any help would be great.
Component to loop through items
<template>
<div class="container-flex">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Name</p>
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in entries">
{{ entry.name }}
</li>
</ul>
</div>
<add-entry />
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
export default {
name: 'entry-list',
components: {
addEntry
},
data: function() {
return {
entries: [
{
name: 'Paul'
},
{
name: 'Barry'
},
{
name: 'Craig'
},
{
name: 'Zoe'
}
]
}
}
}
</script>
Component for adding / sending data
<template>
<div
class="entry-add"
v-bind:class="{ 'entry-add--open': addEntryIsOpen }">
<input
type="text"
name="addEntry"
#keyup.enter="addEntries"
v-model="newEntries">
</input>
<button #click="addEntries">Add Entries</button>
<div
class="entry-add__btn"
v-on:click="openAddEntry">
<span>+</span>
</div>
</div>
</template>
<script>
export default {
name: 'add-entry',
data: function() {
return {
addEntryIsOpen: false,
newEntries: ''
}
},
methods: {
addEntries: function() {
this.entries.push(this.newEntries);
this.newEntries = '';
},
openAddEntry() {
this.addEntryIsOpen = !this.addEntryIsOpen;
}
}
}
</script>
Sync the property between the 2:
<add-entry :entries.sync="entries"/>
Add it as a prop to the add-entry component:
props: ['entries']
Then do a shallow merge of the 2 and emit it back to the parent:
this.$emit('entries:update', [].concat(this.entries, this.newEntries))
(This was a comment but became to big :D)
Is there a way to pass in the key of name? The entry gets added but doesn't display because im looping and outputting {{ entry.name }}
That's happening probably because when you pass "complex objects" through parameters, the embed objects/collections are being seen as observable objects, even if you sync the properties, when the component is mounted, only loads first level data, in your case, the objects inside the array, this is performance friendly but sometimes a bit annoying, you have two options, the first one is to declare a computed property which returns the property passed from the parent controller, or secondly (dirty and ugly but works) is to JSON.stringify the collection passed and then JSON.parse to convert it back to an object without the observable properties.
Hope this helps you in any way.
Cheers.
So with help from #Ohgodwhy I managed to get it working. I'm not sure if it's the right way but it does seem to work without errors. Please add a better solution if there is one and I'll mark that as the answer.
I follow what Ohmygod said but the this.$emit('entries:update', [].concat(this.entries, this.newEntries)) didn't work. Well I never even need to add it.
This is my add-entry.vue component
<template>
<div
class="add-entry"
v-bind:class="{ 'add-entry--open': addEntryIsOpen }">
<input
class="add-entry__input"
type="text"
name="addEntry"
placeholder="Add Entry"
#keyup.enter="addEntries"
v-model="newEntries"
/>
<button
class="add-entry__btn"
#click="addEntries">Add</button>
</div>
</template>
<script>
export default {
name: 'add-entry',
props: ['entries'],
data: function() {
return {
addEntryIsOpen: false,
newEntries: ''
}
},
methods: {
addEntries: function() {
this.entries.push({name:this.newEntries});
this.newEntries = '';
}
}
}
</script>
And my list-entries.vue component
<template>
<div class="container-flex">
<div class="wrapper">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Competition Entries</p>
</div>
<div class="entries__header__search">
<input
type="text"
name="Search"
class="input input--search"
placeholder="Search..."
v-model="search">
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in filteredEntries">
{{ entry.name }}
</li>
</ul>
</div>
<add-entry :entries.sync="entries"/>
</div>
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
import pickWinner from '#/components/pick-winner.vue'
export default {
name: 'entry-list',
components: {
addEntry,
pickWinner
},
data: function() {
return {
search: '',
entries: [
{
name: 'Geoff'
},
{
name: 'Stu'
},
{
name: 'Craig'
},
{
name: 'Mark'
},
{
name: 'Zoe'
}
]
}
},
computed: {
filteredEntries() {
if(this.search === '') return this.entries
return this.entries.filter(entry => {
return entry.name.toLowerCase().includes(this.search.toLowerCase())
})
}
}
}
</script>

Why is object not defined?

I want to try Vue.js 2 and started with a simple example. I've took this one from here https://jsfiddle.net/gmsa/gfg30Lgv/ and created a simple project with it. After I divided this code into files the project doesn't work. So I've made a data property a function:
data: function(){
return {
tabs: [{
name: "tab1",
id : 0,
isActive: true
}],
activeTab: {}
}
},
But there's an error in a console: Uncaught ReferenceError: newTab is not defined.
Project: https://github.com/rinatoptimus/vue-webpack-delete
File QueryBrowserContainer:
<template>
<div id="queryBrowserContainer">
<p>queryBrowserContainer text</p>
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" v-for="tab in tabs" :class="{active:tab.isActive}">
{{ tab.name }}
</li>
<li>
<button type="button" class="btn btn-primary" #click="openNewTab">New tab</button>
</li>
</ul>
<div class="tab-content">
<div v-for="tab in tabs" role="tabpanel" class="tab-pane" :class="{active:tab.isActive}">
<app-querybrowsertab :tab="tab"></app-querybrowsertab>
</div>
</div>
<pre>{{ $data | json }}</pre>
</div>
</template>
<script>
import QueryBrowserTab from './QueryBrowserTab.vue';
export default {
data: function(){
return {
tabs: [{
name: "tab1",
id : 0,
isActive: true
}],
activeTab: {}
}
},
ready: function () {
this.setActive(this.tabs[0]);
},
methods: {
setActive: function (tab) {
var self = this;
tab.isActive = true;
this.activeTab = tab;
/*this.activeTab.isActive = true;
console.log("activeTab name=" + this.activeTab.name);*/
this.tabs.forEach(function (tab) {
if (tab.id !== self.activeTab.id) { tab.isActive = false;}
});
},
openNewTab: function () {
newTab = {
name: "tab" + (this.tabs.length + 1),
id: this.tabs.length,
isActive: true
};
this.tabs.push(newTab);
this.setActive(newTab);
/*this.activeTab = newTab;
console.log("### newtab name=" + newTab.name);*/
},
test: function() {
alert('676767');
},
closeTab: function () {
console.log("### CLOSE!");
}
}
}
File QueryBrowserTab:
<template>
<div>
<p>querybbbTab</p>
<h3>{{tab.name}}</h3>
<h3>{{tab.id}}</h3>
</div>
</template>
<script>
import QueryBrowserContainer from './QueryBrowserContainer.vue';
export default {
data: function () {
return {
databaseOptions: [],
};
},
props: ['tab'],
methods: {},
components: {
'app-querybrowsercontainer': QueryBrowserContainer
}
}
</script>
File App:
<template>
<div id="app">
<app-message></app-message>
<app-querybrowsertab></app-querybrowsertab>
<app-querybrowsercontainer></app-querybrowsercontainer>
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {}
}
}
</script>
It seems in file: QueryBrowserTab, you have not passed tab props, but you are using it, make sure you pass tab as props from whatever places you are using it.
As stated in the docs here, you can pass props to a component like following:
<app-querybrowsertab :tab="tab"></app-querybrowsertab>
which you are already doing in app-querybrowsercontainer,but in file App, you are not passing the prop, which might be the source of error for you.