Popup on top of each other VUEJS3 - vue.js

I want to put several Buttons, each one opening a different Popup. A Popup would print data concerning its Button.
I have a Popup.vue component :
<template>
<div class="popup">
<div class="popup-inner">
<slot />
<Button class="popup-close" #click="TogglePopup()">
Close
</Button>
</div>
</div>
</template>
<script>
export default {
props: ['TogglePopup']
}
</script>
And in another .vue I call it like that :
<template>
<div v-for="infoItem in data" :key="infoItem.name"> // loop to create several buttons
<Button
icon="pi pi-eye"
#click="() => TogglePopup('buttonTriggerDetail')">
</Button>
<Popup
v-if="popupTriggers.buttonTriggerDetail"
:TogglePopup="() => TogglePopup('buttonTriggerDetail')"
>
{{ infoItem.control }}
</Popup>
</div>
</template>
<script>
import ...
export default {
computed: {
data() {
return this.$store.state.data;
},
},
mounted() {
this.$store.dispatch("getData");
},
setup() {
const popupTriggers = ref({
buttonTriggerDetail: false
});
const TogglePopup = (trigger) => {
popupTriggers.value[trigger] = !popupTriggers.value[trigger];
};
return {
Popup,
TogglePopup,
popupTriggers,
};
},
};
</script>
So it prints several Button but when I click on one, it don't open the Popup with the data of this Button, it always prints the data of the last Button. I think in reality it places all the pop up on top of each others.
How can I do to open only the good Popup with the good data ?
Thanks

The mistake is that you are rendering 3 popups when you need only one with certain props. Think of your popups as of tabs: let popup be rendered by clicking on certain button and pass in props you need, for example:
<template>
<div v-for="infoItem in data" :key="infoItem.name"> // loop to create several buttons
<Button
icon="pi pi-eye"
#click="clickHandler">
</Button>
</div>
<Popup
v-if="popupTriggered"
:TogglePopup="() => TogglePopup('buttonTriggerDetail')"
>
{{ data[currentActiveItem].control }}
</Popup>
</template>
<script>
import ...
export default {
data () {
currentActiveItem: 0,
popupTriggered: false
},
methods: {
clickHandler (index) {
this.popupTriggered = true
this.currentActiveItem = index
}
}
... // other component data
};
</script>
I wrote my example in Vue 2 style because I haven't worked with Composition API yet but I hope you got the idea.

Related

How to change a prop value in a generated vue components for single instance or for all instances?

Trying to create a simple blog style page. Every post has a like button, that increments when clicked. I generate 10 of these components with a v-for loop, taking data from a vuex store. However, I'd like there to be a button on the home page that resets all of the like counters.
By googling I seem to find and get working solutions that do either one or the other, not together. Yet to get anything working at all except singular counters.
How can I add a button that resets all the PostEntity counter props? Or how should I restructure it? I've thought about somehow doing in with states.
This is my post component, that gets looped in the main view .vue object:
<template>
<div class="post">
<div class="postheader">
<img :src="profilePic" alt="profilepic" class="profilepic" />
<p>{{ postDate }}</p>
</div>
<div class="postbody">
<img :src="postImage" />
<p>{{ postParagraph }}</p>
</div>
<div class="postfooter">
<!--<img :src="require('#/assets/' +nation.drapeau)"/> -->
<img
:src="require('#/assets/like.png')"
class="likepilt"
#click.prevent="increment"
/>
<p>Number of likes: {{ count }}</p>
</div>
</div>
</template>
<script>
export default {
name: 'PostEntity',
props: {
postDate: String,
postImage: String,
profilePic: String,
postParagraph: String
},
data: function () {
return {
count: 0
};
},
methods: {
increment() {
this.count++;
}
}
};
</script>
This is how I retrieve info from my VueX store:
getters: {
postListStuff: state => {
const postListStuff = state.postList.map(post => {
return {
id: post.id,
img: post.img,
profilepic: post.profilepic,
date: post.date,
paragraph: post.paragraph
};
});
return postListStuff;
}
}
This is how I display the components and generate the posts:
<template>
<HeaderBox title-text="Homepage" />
<div v-for="post in postListStuff" :key="post.id" class="posts">
<PostEntity
:post-date="post.date"
:profile-pic="post.profilepic"
:post-image="post.img"
:post-paragraph="post.paragraph"
></PostEntity>
</div>
<FooterBox />
<HelloWorld />
</template>
<script>
import HelloWorld from './components/HelloWorld.vue';
import HeaderBox from '#/components/Header';
import FooterBox from '#/components/Footer';
import PostEntity from '#/components/Post';
export default {
name: 'App',
components: {
FooterBox,
HeaderBox,
HelloWorld,
PostEntity
},
computed: {
postListStuff() {
return this.$store.getters.postListStuff;
}
}
};
</script>
There are multiple possible ways to go about doing this, but the simplest way I can think of with least amount of code would be:
Add a reset method to the PostEntity component that sets count to 0.
methods: {
increment() {
this.count++;
},
reset() {
this.count = 0;
}
}
Then in the parent component add a ref to the PostEntity components inside the v-for loop, then add a new button with onclick method resetCounters:
<div v-for="post in postListStuff" :key="post.id" class="posts">
<PostEntity
ref="post"
:post-date="post.date"
:profile-pic="post.profilepic"
:post-image="post.img"
:post-paragraph="post.paragraph"
></PostEntity>
</div>
<button #click="resetCounters">Reset</button>
resetCounters will loop through the array of PostEntity refs and call the reset method on each of them.
methods: {
resetCounters() {
this.$refs.post.forEach(p => p.reset());
}
}

How can I hide the other elements when one dropdown menu is click

I'm still new to Vue.js so far I'm liking it. Currently stuck on drop menus.
If one drop down menu is click is there a way to hide the other menus that are open?
<script setup>
import { ref } from 'vue';
const hideshow1= ref(false);
const hideshow2= ref(false);
const hideshow3= ref(false);
function show1() {
this.hideshow1= !this.hideshow1;
};
function show2() {
this.hideshow2= !this.hideshow2;
};
function show3() {
this.hideshow3= !this.hideshow3;
};
</script>
<template>
<button #click="show1()" type="button"> show</button>
<button #click="show2()" type="button"> show</button>
<button #click="show3()" type="button"> show</button>
<div :class="{'block':hideshow1, 'hidden': ! hideshow1}" class="sm:hidden ">show1</div>
<div :class="{'block':hideshow2, 'hidden': ! hideshow2}" class="sm:hidden ">show2</div>
<div :class="{'block':hideshow3, 'hidden': ! hideshow3}" class="sm:hidden ">show3</div>
</template>
Maybe I'm grossly over-simplifying your problem, but as I mentioned in comment, why not change your apps data state with the buttons, and then use a v-if to check state of your data, toggling visibility. The data probably should be an array of objects, perhaps something like this:
<template>
<h2>Show Hide Menus</h2>
<button v-for="item in hideShows" :key="item.text" #click="show(item)">
Button {{ item.text }}
</button>
<div v-for="item in hideShows" :key="item.text">
<span v-if="item.value">Show {{ item.text }}</span>
</div>
</template>
<script setup>
import { ref } from 'vue';
const hideShows = ref([
{
text: "1",
value: true
},
{
text: "2",
value: false
},
{
text: "3",
value: false
}
]);
function setAllFalse() {
hideShows.value.forEach(hideShow => {
hideShow.value = false;
})
}
function show(item) {
setAllFalse();
item.value = !item.value;
}
</script>

Vue: How to change a value of state and use it in other page and change page structure at starting by it?

In this project, we can login from login.vue by clicking login button and if it is success then we can see Lnb.vue in dashboard.vue
I thought if i code like this then pageSso will be 1 when I check the checkbox in login.vue in Lnb.vue then it will not show only "Account" menu.
When I used console.log(pageSso) at mounted cycle it showed pageSso was 0. What would be the problem?
store/store.js
export const state = () => ({
pageSso: 0,
})
export const getters = {
pageSso: (state) => state.pageSso,
}
export const mutations = {
setPageSso(state, data) {
console.log('mutations setPageSso data', data)
state.pageSso = data
}
}
export const actions = {
setPageSso({
commit
}, data) {
console.log('actions setPageSso data', data)
commit('setPageSso', data)
},
}
pages/login.vue
<template>
<input
class="checkbox_sso"
type="checkbox"
v-model="sso"
true-value="1"
false-value="0" >SSO checkbox
<button class="point" #click="submit">login</button>
</template>
<script>
export default {
data() {
return {
sso: '',
}
},
computed: {},
methods: {
submit() {
this.$store.dispatch('store/setPageSso', this.sso)
//this.$store.dispatch('store/login', data)
},
</script>
pages/dashboard.vue
<template>
<div class="base flex">
<Lnb />
<div class="main">
<Gnb />
<nuxt-child />
</div>
</div>
</template>
<script>
import Lnb from '#/components/Lnb'
import Gnb from '#/components/Gnb'
export default {
components: {
Lnb,
Gnb
},
mounted() {},
}
</script>
components/Lnb.vue
<template>
<ul>
<li :class="{ active: navbarState == 7 ? true : false }">
<a href="/dashboard/settings">
<img src="../assets/images/ico_settings.svg" alt="icon" /> Settings
</a>
</li>
<li v-show="pageSso != 1" :class="{ active: navbarState == 8 ? true : false }">
<a href="/dashboard/user">
<img src="../assets/images/ico_user.svg" alt="icon" />
Account
</a>
</li>
</ul>
</template>
<script>
import {
mapState
} from 'vuex'
export default {
data() {
return {}
},
computed: {
...mapState('store', {
// navbarState: (state) => state.navbarState,
pageSso: (state) => state.pageSso,
}),
},
mounted() {
console.log('pageSso ->', this.pageSso);
},
methods: {
},
}
</script>
Your console.log(pageSso) logs 0 because the mounted hook of Lnb.vue happens once, and it happens when the component is inserted into the DOM.
You insert Lnb into the DOM unconditionally in this line of dashboard.vue:
<Lnb />
and this is roughly when it's mounted hook is triggered.
Your pageSso seems to be changed only after you triggered the submit() method, which — I guess — happens way later, when you submit the login form.
Your Lnb.vue currently is always mounted. If you don't want to show it until pageSso is equal to 1, add a v-if on it in dashboard.vue like this:
<Lnb v-if="pageSso === 1" />
You currently don't have pageSso variable in dashboard.vue, you must take it from the store.
N.B.: Mind the difference between v-show and v-if directives: v-show only hides the component with display: none; while v-if actually removes or inserts the component from/to the DOM. With v-show, the component gets mounted even if you don't see it. With v-if, the component's mounted hook will fire each time the condition evaluates to true.

How can I emit from component and listen from another one?

I have in Layout.vue to components one TheSidebar second TheHeader, there is a button in TheHeader to open the sidebar in TheSidebarcomponent.
I need to when I click the button in header open the sidebar:
My try:
in TheHeader:
methods: {
openSidebar() {
this.$root.$emit("open-sidebar");
},
},
in TheSidebar
data() {
return {
sidebarOpen: false,
};
},
mounted() {
this.$root.$on("open-sidebar", (this.sidebarOpen = true));
},
I'm using VUE 3 so I got this error in console: TypeError: this.$root.$on is not a function so How can communicate ?
you can use something like tiny emitter it works fine and doesn't care about parent child relationship
var emitter = require('tiny-emitter/instance');
emitter.on('open-sidebar', ({isOpen}) => {
//
});
emitter.emit('open-sidebar', {isOpen : true} );
You can only pass props to a direct child component, and
you can only emit an event to a direct parent. But
you can provide and eject from anywhere to anywhere
Per another answer, provide and eject may be your best bet in Vue 3, but I created a simple example of how to implement with props/events. Built with Vue 2 as I haven't worked with 3 yet, but should be usable in Vue 3 as well.
Parent.vue
<template>
<div class="parent">
<div class="row">
<div class="col-md-6">
<h4>Parent</h4>
<hr>
<child-one #show-child-two-event="handleShowChildTwoEvent" />
<hr>
<child-two v-if="showChildTwo" />
</div>
</div>
</div>
</template>
<script>
import ChildOne from './ChildOne.vue'
import ChildTwo from './ChildTwo.vue'
export default {
components: {
ChildOne,
ChildTwo
},
data() {
return {
showChildTwo: false
}
},
methods: {
handleShowChildTwoEvent() {
this.showChildTwo = true;
}
}
}
</script>
ChildOne.vue
<template>
<div class="child-one">
<h4>Child One</h4>
<button class="btn btn-secondary" #click="showChildTwo">Show Child Two</button>
</div>
</template>
<script>
export default {
methods: {
showChildTwo() {
this.$emit('show-child-two-event');
}
}
}
</script>
ChildTwo.vue
<template>
<div class="child-two">
<h4>Child Two</h4>
</div>
</template>

VueJS - Swap component on click

In my application I have many buttons. When I press it the button I would like to load a template (that replaces the selected button):
Templates:
Vue.component('component-1', {...});
Vue.component('component-2', {...});
Buttons:
<div id="toReplace">
<button>Button1</button>
<button>Button2</button>
</div>
In this case, when I press Button1 the content of the div toReplace should be component-1.
Sure, each component should have a "close" button that will show the buttons again (in short, the div toReplace) on press.
You need to bind a variable to :is property. And change this variable on button click. Also you will need to combine it with some v-show condition. Like so:
<div id="toReplace">
<div :is="currentComponent"></div>
<div v-show="!currentComponent" v-for="component in componentsArray">
<button #click="swapComponent(component)">{{component}}</button>
</div>
</div>
<button #click="swapComponent(null)">Close</button>
new Vue({
el: 'body',
data: {
currentComponent: null,
componentsArray: ['foo', 'bar']
},
components: {
'foo': {
template: '<h1>Foo component</h1>'
},
'bar': {
template: '<h1>Bar component</h1>'
}
},
methods: {
swapComponent: function(component)
{
this.currentComponent = component;
}
}
});
Here is quick example:
http://jsbin.com/miwuduliyu/edit?html,js,console,output
You can use v-bind:is="CurrentComponent" and #click=ChangeComponent('SecondComponent'). The argument has to be a string. To use the <component/> make sure your ExampleComponent has a slot
<Template>
<div>
<ExampleComponent>
<component v-bind:is = "CurrentComponent"/>
</ExampleCompoent>
<button #click="ChangeComponent('SecondComponent')">
</button>
</div>
</template>
<script>
import ExampleComponent from '#/components/ExampleComponent.vue'
import SecondComponent from '#/components/SecontComponent.vue'
export default{
...
data(){
return{
CurrentComponet:null
}
}
methods:{
ChangeComponent(NewComponent){
this.CurrentComponent = NewComponent
}