Problem with Recursive Components in Vue.js - vue.js

I'm a beginner in Vue.js and I'm getting stuck on using recursive components.
When I try to recall the GroupFilter component in the GroupFilter component, the test message "New filter group" is displayed fine, but not the new GroupFilter component.
I have tried using an import and using the name given to the component, but in both cases it does not work.
Does anyone know how to make this work ? Thank you in advance for your help !
<template>
<div class="groupFilter">
<p>OR</p>
<div class='groupFilterForms'>
<div class="selectButton">
<button type="button" class="btn btn-primary" v-on:click="addNewFilter">+ Add Filter</button>
<button type="button" class="btn btn-success" v-on:click="addNewGroupFliter">+ Add Filter Group</button>
</div>
<div v-for="(child, index) in this.$store.state.storeData[this.index].$all" :index="index" :key="index">
<div v-if="child.condition">
<FilterTemplate :index='index'/>
</div>
<div v-else-if="child.$all">
<h3>New filter group</h3>
<GroupFilter/>
</div>
</div>
</div>
</div>
</template>
<script>
import FilterTemplate from'./FilterTemplate';
import GroupFilter from './GroupFilterTemplate'
export default {
name: 'GroupFilter',
components: {
FilterTemplate,
GroupFilter
},
data: function (){
return {
storeData: [],
props: this.index,
toto: Array
}
},
props: ['index'],
mounted: function(){
this.storeData = this.$store.state.storeData
},
methods: {
addNewFilter(){
console.log("new filter TEST")
this.storeData[this.props].$all.push(
{condition:{
"attr":"group_id",
"ope":"eq",
"value":106
}}
);
this.$store.commit('setStoreData', this.storeData);
console.log(this.storeData[index])
},
addNewGroupFliter(){
console.log("new group filter TEST")
this.storeData[this.props].$all.push({
$all:[
{condition:{
"attr":"group_id",
"ope":"eq",
"value":106
}}
]
})
this.$store.commit('setStoreData', this.storeData);
}
}
}
</script>

Related

import dynamically fontawesome icons in Vue

I have a problem rendering icons dynamically. I use v-for to get all the data from the object array. Also, I have a second array where I save the name of the icons I worked with. However, when the first array is looping, the second array (icons) doesn't move.
I tried to create a method that maps the data from the first and second array to create a new array. But nothing happens.
My code:
Component.vue
<template>
<div class="items">
<div class="item" v-for="(param, index) in params" :key="index">
<font-awesome-icon :icon="['fab', 'temp']" :temp="getIcon" :key="index" class="fab fa" />
<h3 class="skills-title">{{ param.name }}.</h3>
<p style="display: none">{{ param.description }}.</p>
</div>
</div>
</template>
<script>
export default {
name: "PxSkillCard",
data() {
return {
params: [],
icons: ["laravel", "wordpress-simple"],
};
},
methods: {
getIcon() {
let temp = this.params.map((aux, index) => {
return [aux, this.icons[index]];
});
},
},
};
</script>
And I separated the fontawesome file in a apart module
fontawesome.js
import Vue from "vue";
import { library } from "#fortawesome/fontawesome-svg-core";
import {
faLaravel,
faWordpressSimple
} from "#fortawesome/free-brands-svg-icons";
import { faPlus } from "#fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "#fortawesome/vue-fontawesome";
library.add(
faLaravel,
faWordpressSimple
);
Vue.component("font-awesome-icon", FontAwesomeIcon);
The final result is:
What about with my code (or my logic)?
You are already looping through everything in your template, there's no need to loop again in your function.
Something like this should work.
<template>
<div class="items">
<div class="item" v-for="(param, index) in params" :key="index">
<font-awesome-icon :icon="['fab', icons[index]]" :key="index" class="fab fa" />
<h3 class="skills-title">{{ param.name }}.</h3>
<p style="display: none">{{ param.description }}.</p>
</div>
</div>
</template>
<script>
export default {
name: "PxSkillCard",
data() {
return {
params: [],
icons: ["laravel", "wordpress-simple"],
};
},
};
</script>
This assume, both arrays are the same size and the data in params and icons are in the correct order.

Removing specific object from array keeps removing last item

Here is what I have and I will explain it as much as I can:
I have a modal inside my HTML code as shown below:
<div id="favorites-modal-edit" class="modal">
<div class="modal-background"></div>
<div class="modal-card px-4">
<header class="modal-card-head">
<p class="modal-card-title">Favorites</p>
<button class="delete" aria-label="close"></button>
</header>
<section class="modal-card-body">
<div class="container">
<div id="favorites-modal-edit-wrapper" class="columns is-multiline buttons">
<favorites-edit-component v-for="(favorite, index) in favorites_list" :key="favorite.id" :favorite="favorite" />
</div>
</div>
</section>
<footer class="modal-card-foot">
<button class="button" #click="addItem">
Add Item
</button>
</footer>
</div>
</div>
The id="favorites-modal-edit" is the Vue.js app, then I have the <favorites-edit-component /> vue.js component.
Here is the JS code that I have:
I have my favorites_list generated which is an array of objects as shown below:
const favorites_list = [
{
id: 1,
name: 'Horse',
url: 'www.example.com',
},
{
id: 2,
name: 'Sheep',
url: 'www.example2.com',
},
{
id: 3,
name: 'Octopus',
url: 'www.example2.com',
},
{
id: 4,
name: 'Deer',
url: 'www.example2.com',
},
{
id: 5,
name: 'Hamster',
url: 'www.example2.com',
},
];
Then, I have my vue.js component, which is the favorites-edit-component that takes in the #click="removeItem(this.index) which is coming back as undefined on the index.
Vue.component('favorites-edit-component', {
template: `
<div class="column is-half">
<button class="button is-fullwidth is-danger is-outlined mb-0">
<span>{{ favorite.name }}</span>
<span class="icon is-small favorite-delete" #click="removeItem(this.index)">
<i class="fas fa-times"></i>
</span>
</button>
</div>
`,
props: {
favorite: Object
},
methods: {
removeItem: function(index) {
this.$parent.removeItem(index);
},
}
});
Then I have the vue.js app that is the parent as shown below:
new Vue({
el: '#favorites-modal-edit',
// Return the data in a function instead of a single object
data: function() {
return {
favorites_list
};
},
methods: {
addItem: function() {
console.log('Added item');
},
removeItem: function(index) {
console.log(index);
console.log(this.favorites_list);
this.favorites_list.splice(this.favorites_list.indexOf(index), 1);
},
},
});
The problem:
For some reason, each time I go to delete a item from the list, it's deleting the last item in the list and I don't know why it's doing it, check out what is happening:
This is the guide that I am following:
How to remove an item from an array in Vue.js
The item keeps coming back as undefined each time the remoteItem() function is triggered as shown below:
All help is appreciated!
There is an error in your favorites-edit-component template, actually in vue template, when you want to use prop, data, computed, mehods,..., dont't use this
=> there is an error here: #click="removeItem(this.index)"
=> in addition, where is index declared ? data ? prop ?
you're calling this.$parent.removeItem(index); then in removeItem you're doing this.favorites_list.splice(this.favorites_list.indexOf(index), 1); this means that you want to remove the value equal to index in you array no the value positioned at the index
=> this.favorites_list[index] != this.favorites_list[this.favorites_list.indexOf(index)]
In addition, I would suggest you to modify the favorites-edit-component component to use event so it can be more reusable:
favorites-edit-component:
<template>
<div class="column is-half">
<button class="button is-fullwidth is-danger is-outlined mb-0">
<span>{{ favorite.name }}</span>
<span class="icon is-small favorite-delete" #click="$emit('removeItem', favorite.id)">
<i class="fas fa-times"></i>
</span>
</button>
</div>
</template>
and in the parent component:
<template>
...
<div id="favorites-modal-edit-wrapper" class="columns is-multiline buttons">
<favorites-edit-component
v-for="favorite in favorites_list"
:key="favorite.id"
:favorite="favorite"
#removeItem="removeItem($event)"
/>
</div>
...
</template>
<script>
export default {
data: function () {
return {
favorites_list: [],
};
},
methods: {
...
removeItem(id) {
this.favorites_list = this.favorites_list.filter((favorite) => favorite.id !== id);
}
...
},
};
I would restructure your code a bit.
In your favorites-edit-component
change your removeItem method to be
removeItem() {
this.$emit('delete');
},
Then, where you are using your component (in the template of the parent)
Add an event catcher to catch the emitted "delete" event from the child.
<favorites-edit-component v-for="(favorite, index) in favorites_list" :key="favorite.id" :favorite="favorite" #delete="removeItem(index)"/>
The problem you have right now, is that you are trying to refer to "this.index" inside your child component, but the child component does not know what index it is being rendered as, unless you specifically pass it down to the child as a prop.
Also, if you pass the index down as a prop, you must refer to it as "index" and not "this.index" while in the template.

Vue - Accessing Attributes From One Component to Another

New to vue and struggling to understand how data is passed back and forth between components. I'm aware of props and emit on the parent/child, child/parent, but I can't quite understand how they work in my case. I have two components: a parent component called "Letters" and a child called "ClaimantSearch". Claimant search return data about a person based on a call to a flask backend:
<div>
<b-form #submit="onSubmit" class="w-100">
<b-form-group id="form-title-group"
label="Claim Number:"
label-for="form-claim-number-input">
<b-form-input id="form-claim-number-input"
type="text"
v-model="claimNumberForm.claimNumber"
required
placeholder="Enter claim number">
</b-form-input>
<button
type="button"
class="btn btn-warning btn-sm"
#click="getClaimant(claimNumber)">
Get Claimant
</button>
</b-form-group>
</b-form>
<span v-if="claimant">
<p> {{ claimant.name }} </p>
<p> {{ claimant.address1 }} </p>
<p> {{ claimant.address2 }} </p>
<p> {{ claimant.city }}, {{ claimant.state }} </p>
<p> {{ claimant.zip }} </p>
</span>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
claimant: '',
claimNumberForm: {
claimNumber: '',
},
};
},
methods: {
getClaimant(number) {
const path = `http://localhost:5000/claimant/${number}`;
axios.get(path)
.then((res) => {
this.claimant = res.data.claimant;
})
.catch((error) => {
// eslint-disable-next-line
console.error(error);
});
},
onSubmit(evt) {
evt.preventDefault();
this.getClaimant(this.claimNumberForm.claimNumber);
},
},
};
</script>
I then have a Letters parent component:
<template>
<div>
<claimant></claimant>
</div>
</template>
<script>
// import axios from 'axios';
import ClaimantSearch from './ClaimantSearch.vue';
export default {
data() {
return {
claimant: '',
claimNumberForm: {
claimNumber: '',
},
};
},
components: {
claimant: ClaimantSearch,
},
methods: {
},
};
</script>
What I'd like to be able to do is access {{claimant}} outside of the <claimant> tag, if that makes sense. So inside Letters I'd like to do something like:
<template>
<div>
<div>
<claimant></claimant>
</div>
<div>
Dear Mr. {{claimant.name}},
Please get bent. Sincerly, crappy insurance company.
</div>
</div>
</template>
I can't remember exactly where I found this link, but there's an excellent post on medium, with code samples, that discusses all the state management patterns in vue starting with props and events, eventbus, simple store and then vuex.
https://medium.com/fullstackio/managing-state-in-vue-js-23a0352b1c87

Prevent child elements for receiving click event not working

I am trying to make a modal component and dismiss it when I click outside of the component. Here is my current setup:
Auth component with click event set on a div element:
<template> <div>
<transition name="modal">
<div class="modal-mask" #click="$parent.$emit('close')">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">Default Header</slot>
</div>
<div class="model-body">
<slot name="body">Default Body</slot>
</div>
<div class="modal-footer">
<slot name="footer">Default Footer</slot>
</div>
</div>
</div>
</div>
</transition> </div> </template>
SignIn component that injects necessary information:
<template>
<div>
<Auth />
</div>
</template>
Home component that uses the SignIn component:
<template>
<div class="home">
<SignIn v-if="showModal" #close="showModal = false" />
</div>
</template>
Right now when I click outside the modal it behaves ok, the close event is called.
But it is also called when I click inside the modal.
Not I tried to use #click.self , but now it doesn't work anymore even when clicking outside the modal.
<div class="modal-mask" #click.self="$parent.$emit('close')">
I am currently learning VueJs, but I don't understand how this works. I thought self will prevent propagating click event to child elements and thats it.
Anyone has an idea what is going on ?
PS: I am using this setup, because I want to have a SignIn and SignUp using the Auth component.
Either <div class="modal-wrapper"> or <div class="modal-container"> needs #click.prevent.stop
<template>
<div>
<transition name="modal">
<div class="modal-mask" #click="$parent.$emit('close')">
<div class="modal-wrapper">
<div class="modal-container" #click.prevent.stop>
<div class="modal-header">
<slot name="header">Default Header</slot>
</div>
<div class="model-body">
<slot name="body">Default Body</slot>
</div>
<div class="modal-footer">
<slot name="footer">Default Footer</slot>
</div>
</div>
</div>
</div>
</transition>
</div>
</template>
With this code you don't have to worry about click event's propagation #click.stop, for the style purpose I am using bootstrap.css but you can write your own style.
Here is the reusable component BsModal.vue
<template lang="pug">
div(v-if="showModal")
.modal.fade.d-block(tabindex='-1', role='dialog', :class="{'show': addShowClassToModal}")
.modal-dialog(role='document')
.modal-content.border-0
.modal-header.border-0
h5.modal-title
slot(name="title")
button.close(type='button', data-dismiss='modal', aria-label='Close', #click="hideModal")
span ×
.modal-body.p-0
slot
.modal-backdrop.fade(:class="{ 'show': addShowClassToModalBackdrop }")
</template>
<script>
export default {
name: 'BsModal',
props: {
showModal: {
default: false,
type: Boolean,
},
},
data() {
return {
addShowClassToModal: false,
addShowClassToModalBackdrop: false,
};
},
mounted() {
this.toggleBodyClass('addClass', 'modal-open');
setTimeout(() => {
this.addShowClassToModalBackdrop = true;
}, 100);
setTimeout(() => {
this.addShowClassToModal = true;
}, 400);
},
destroyed() {
this.toggleBodyClass('removeClass', 'modal-open');
},
methods: {
hideModal() {
setTimeout(() => {
this.addShowClassToModal = false;
}, 100);
setTimeout(() => {
this.addShowClassToModalBackdrop = false;
this.$emit('hide-modal', false);
}, 400);
},
toggleBodyClass(addRemoveClass, className) {
const elBody = document.body;
if (addRemoveClass === 'addClass') {
elBody.classList.add(className);
} else {
elBody.classList.remove(className);
}
},
},
};
</script>
And use it wherever you need by importing it:
<template lang="pug">
div
button(#click="showModal = true")
| Show Modal
bs-modal(
v-if="showModal",
:show-modal="showModal",
#hide-modal="showModal = false"
).modal
template(slot="title") Modal Title
// Modal Body content here
</template>
<script>
import BsModal from '~/components/BsModal.vue';
export default {
name: 'your component',
components: { BsModal },
data() {
return {
showModal: false,
};
},
};
</script>
If you don't like pug template language then you can convert PUG to HTML here: https://pughtml.com/

Vue Eventbus: handler.apply is not a function

I want to reload one of my components when an other component changes (for example I send a put with axios)
I tried it with eventbus but I got this error:
handler.apply is not a function
In the component where I want to trigger:
EventBus.$emit('compose-reload', Math.random()*100);
Where I want to be triggered:
<template>
<div class="">
<div class="">
<div class="columns">
<div class="column is-one-fifth">
<div class="box">
<Menu></Menu>
</div>
</div>
<div class="column is-four-fifth">
<div class="box">
<router-view :key="key"></router-view>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Menu from './includes/Menu'
import EventBus from '../../../event-bus';
export default {
components: {
Menu,
},
data() {
return {
key: null
}
},
mounted(){
EventBus.$on('compose-reload', this.key);
},
created(){
this.key = Math.random()*100
}
}
</script>
EventBus.$on expects a handler function as a second argument but the variable this.key is passed in, hence the error.
You should change this :
mounted(){
EventBus.$on('compose-reload', this.key);
}
To this :
mounted(){
EventBus.$on('compose-reload', key => {
this.key = key;
});
}