My question is about core logic for the thing below
1) I have a Section Component and a Modal Component
2) In the Section Component i have an array of objects with title and description
3) In the Modal Component i have a template for the modal (im using Bulma framework for the modal)
Now, in the Section i have 7 buttons with a v-for loop to assign 'is-active' class and on each button click an individual modal opens.
Question: How do i parse data into the modal ? I want my modal to be reusable. So at the start my modal is empty at all. On button1 click i have title1 descr1, on button2 title2 descr2 and etc
My code:
Section Component:
<template>
<base-section name="clusters">
<div class="section-map">
<button
class="section-btn"
v-for="(item, index) in sectionItems"
:key="index"
:class="[`section-btn-num${index + 1}`, {'is-active': item.state}]"
#click="toggleModal(item)"
>
<div class="section-btn__text">
{{ item.title }}
</div>
</button>
</div>
<div class="modal"
v-for="(item, index) in sectionItems"
:key="index"
:class="{'is-active': item.state}"
>
<div class="modal-background"></div>
<div class="modal-content">
</div>
<button
#click="item.state = false"
class="modal-close is-large"
aria-label="close">
</button>
</div>
</base-section>
</template>
<script>
import BaseSection from './Section';
import BaseModal from './Modal';
export default {
components: {
BaseSection,
BaseModal,
},
methods: {
toggleModal: (item) => {
item.state = !item.state;
}
},
data() {
return {
sectionItems: [
{
title: 'title1',
},
{
title: 'title2',
description: 'descr',
},
{
title: 'title3',
description: 'descr',
},
{
title: 'title4',
description: 'descr',
},
{
title: 'title5',
description: 'descr',
},
{
title: 'title6',
description: 'descr',
},
{
title: 'title7',
description: 'descr',
},
].map(item => ({ ...item, state: false }))
};
}
};
</script>
<styles>
/* are skipped */
</styles>
Modal Component:
<template>
<div class="modal">
<div class="modal-background"></div>
<div class="modal-content">
<div class="modal__header">
<slot name="modal__header">
</slot>
</div>
<div class="modal__body">
<slot name="modal__body">
</slot>
</div>
</div>
<button
#click="item.state = false"
class="modal-close is-large"
aria-label="close">
</button>
</div>
</template>
<script>
import ClustersSection from './ClustersSection';
export default {
components: {
ClustersSection,
},
props: {
}
},
};
</script>
<style lang="scss">
</style>
Since you already defined slots in your component you can pass data to it via slots. E.g.
<your-modal-component>
<template slot="modal__header">
header bla bla a
</template>
<template slot="modal__body">
body anything here like <p> hehe</p>
</template>
</your-modal-component>
Related
I have an icon in the v-for loop and I want the action to be performed specifically to the one icon but it is happening for all the icons that are repeating because of v-for.
<template>
<div>
<tr
v-for="(randomData, randomIndex) in obj['randomData']"
:key="randomIndex"
class="random-table"
>
<td data-label="Activity Alert">
<el-popover
v-model="remindMeVisibility"
placement="left-start"
width="500"
trigger="manual"
class="popover-form"
>
<RemindMe />
<i
slot="reference"
class="el-icon-message-solid reminder-icon"
#click="remindMeVisibility = true"
></i>
</el-popover>
</td>
</tr>
</div>
</template>
<script>
export default {
data() {
return {
remindMeVisibility: false,
}
},
}
</script>
Here is a working example on how to have a specific popover edit per element for a given list.
<template>
<div>
<pre>{{ cityNames }}</pre>
<section>
<div v-for="city in cityNames" :key="city.id">
<span>City {{ city.name }}</span>
<el-popover v-model="city.popoverOpen" placement="top" width="160" class="popover">
<el-button slot="reference">Update visibility</el-button>
<p>Change the visibility of the City?</p>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" #click="toggleCityVisibility({ id: city.id, visibility: false })">
Hide it
</el-button>
<el-button type="primary" size="mini" #click="toggleCityVisibility({ id: city.id, visibility: true })">
Make visible
</el-button>
</div>
</el-popover>
</div>
</section>
</div>
</template>
<script>
export default {
data() {
return {
cityNames: [
{
id: 1,
name: 'beijing',
remindMeVisibility: false,
popoverOpen: false,
},
{
id: 2,
name: 'shanghai',
remindMeVisibility: false,
popoverOpen: false,
},
{
id: 3,
name: 'guangzhou',
remindMeVisibility: false,
popoverOpen: false,
}
]
};
},
methods: {
toggleCityVisibility({ id, visibility }) {
console.log('id', id, 'visibility', visibility)
this.targetedCity = this.cityNames.find(city => city.id === id)
this.targetedCity.remindMeVisibility = visibility
}
},
}
</script>
<style scoped>
.popover {
margin-left: 1rem;
}
</style>
Here, we do have a list of cities, and we want to toggle their visibility (remindMeVisibility).
The id in the list was given for the :key uniqueness + find it later on when editing the visibility of it.
I didn't expected popoverOpen to be required here, but it is (a global popover state is not feasible from what I've tried).
This is how it looks
I'm working on this app and the idea is to show details of the cars in a sidebar on click. There are several issues like the sidebar is showing four times and I resolve it somehow but I don't know why is it showing four times. now I don't getting any response on emit call help me out please, I try $parent.$emit, $root.$emit but not seems working!!!
<template>
<div class="home">
<!-- warehouse details -->
<div
v-for="(detail, detailindex) in details"
:key="detailindex"
class="container mt-5 mb-5"
>
<h1>
{{ detail.name }}
<span class="location">{{ detail.cars.location }}</span>
</h1>
<!-- vehicle details -->
<SingleGarage :detail="detail"> </SingleGarage>
</div>
<b-sidebar
id="my-sidebar"
title="Sidebar with backdrop"
backdrop-variant="dark"
ref="mySidebar"
backdrop
shadow
#emitData="testingEmit()"
>
<div class="px-3 py-2">
<h1>{{currentCar}}</h1>
</div>
</b-sidebar>
</div>
</template>
<script>
// # is an alias to /src
import axios from "axios";
import SingleGarage from "../components/SingleGarage";
export default {
components: { SingleGarage },
name: "Home",
data: () => ({
details: String,
currentCar: 'String',
}),
methods:{
testingEmit(data){
this.currentCar = data
console.log('data from emit',data)
}
},
mounted() {
axios
.get("https://api.jsonbin.io/b/5ebe673947a2266b1478d892")
.then((response) => {
var results;
response.data.forEach((element) => {
element.cars.vehicles.sort((a, b) => {
a = new Date(a.date_added);
b = new Date(b.date_added);
results = a > b ? -1 : a < b ? 1 : 0;
return results * -1;
});
});
this.details = response.data;
});
},
};
</script>
<template>
<div class="vGrid mt-4">
<div
class="gridItem border vehicle singleCar"
v-for="(vehicle, vehicleIndex) in detail.cars.vehicles"
:class="'griditem' + vehicleIndex"
:key="vehicle._id"
>
<SingleCar
:vehicle="vehicle"
#click.native="testingTef(vehicleIndex)"
></SingleCar>
</div>
</div>
</template>
<script>
import SingleCar from "#/components/SingleCar";
export default {
name: "SingleGarage",
components: { SingleCar },
props: ["detail"],
data: () => ({
dummyImg: require("#/assets/img/dummycar.png"),
currentCar : 1
}),
methods: {
testingTef(vehicleIndex) {
this.$parent.$emit('emitData',this.detail.cars.vehicles[vehicleIndex].make)
this.$root.$emit('bv::toggle::collapse', 'my-sidebar')
console.log(this.detail.cars.vehicles[vehicleIndex].make)
console.log(this.detail.cars.vehicles[vehicleIndex].date_added)
this.currentCar = this.detail.cars.vehicles[vehicleIndex].make;
},
},
};
</script>
<template>
<div class="singleCar">
<!-- conditionally show image -->
<img
class="carImg"
:src="vehicle.img"
v-if="vehicle.img"
alt="No Preview"
/>
<img class="carImg" :src="dummyImg" v-else alt="No Preview" />
<div class="p-3">
<h3 class="make">{{ vehicle.make }}</h3>
<div class="modelDetails">
<div class="model d-flex ">
<p class="bold">Model:</p>
<p class="price ml-auto ">{{ vehicle.model }}</p>
</div>
<div class="price d-flex ">
<p class="bold">Price:</p>
<p class="price ml-auto ">€{{ vehicle.price }}</p>
</div>
</div>
<p class="dateAdded ml-auto ">{{ vehicle.date_added }}</p>
</div>
</div>
</template>
<script>
export default {
name: "SingleCar",
props: ["vehicle"],
data: () => ({
dummyImg: require("#/assets/img/dummycar.png"),
}),
methods:{
working(){
console.log('working');
console.log(this.vehicle.make)
}
}
};
</script>
Thanks for your help.
So a few things you can try to fix this
in your Home.vue you can change
#emitData="testingEmit()"
to
#emitData="testingEmit"
// or
#emitData="testingEmit($event)"
You are telling to the function testingEmit that is not params to parse. So you need to take out the () and Vue will parse everything that comes from the $event or you cant say put the $event as a param in your testingEmit (second option).
For your SingleGarage.vue you can take the $parent.$emit and replace it with
this.$emit('emitData',this.detail.cars.vehicles[vehicleIndex].make)
I am new to Vue. I am building a simple app that will list all countries and when you click on a particular country it shows you more details about the country. Idea is to open country details in a modal.
I'm stuck with displaying that modal. The modal opens, but in the background. It also opens a detail page.
CountryDetail.vue:
<script>
import axios from 'axios';
export default {
name: 'country-detail',
props: [ 'isDarkTheme' ],
data () {
return {
pending: false,
error: null,
countryInfo: null,
alpha3Code: [],
alpha3CodetoString: [],
}
},
mounted () {
this.pending = true;
axios
.get(`https://restcountries.eu/rest/v2/name/${this.$route.params.country}?fullText=true`)
.then((response) => {
(this.countryInfo = response.data)
this.alpha3CodetoString = this.alpha3Code.join(';');
})
.catch(error => (this.error = error ))
.finally( () => { this.pending = false });
},
filters: {
formatNumbers (value) {
return `${value.toLocaleString()}`
}
}
}
</script>
<template>
<modal v-model="show">
<div class="modal-mask" :class="{ darkTheme : isDarkTheme }" name="modal">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
<h1 v-if="error !== null">Sorry, an error has occurred {{error}}</h1>
<div class="loaderFlex"><div v-if="pending" class="loader"></div></div>
</slot>
</div>
<div v-for="country in countryInfo" class="countryTile modal-body" v-bind:key="country.id">
<slot name="body">
<img v-bind:src="country.flag" alt="Country Flag" class="flag">
<div class="country-details">
<h1>{{country.name}}</h1>
<div class="listDiv">
<ul>
<li><span>Population:</span> {{country.population | formatNumbers }}</li>
<li><span>Capital:</span> {{country.capital}}</li>
<li><span>Iso:</span> {{country.alpha3Code}}</li>
</ul>
<ul>
<li><span>Currencies:</span> {{country.currencies['0'].name}}</li>
<li><span>Languages:</span>
<span
v-for="(language, index) in country.languages"
v-bind:key="index"
class="languages">
{{language.name}}<span v-if="index + 1 < country.languages.length">, </span>
</span>
</li>
</ul>
</div>
</div>
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
<a #click="$router.go(-1)" class="backBtn"><i class="fas fa-arrow-left" />Go Back</a>
</slot>
</div>
</div>
</div>
</div>
</modal>
</template>
Home.vue:
<script>
import axios from 'axios';
export default {
name: 'home',
props: [ 'isDarkTheme' ],
data () {
return {
pending: false,
error: null,
countryInfo: null,
search: '',
darkMode: false,
}
},
mounted () {
this.pending = true;
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => (this.countryInfo = response.data))
.catch(error => (this.error = error ))
.finally( () => { this.pending = false });
},
filters: {
formatNumbers (value) {
return `${value.toLocaleString()}`
}
},
computed: {
filteredCountries: function () {
return this.countryInfo.filter((country) => {
if (this.region === '' ) {
return country.name.toLowerCase().match(this.search.toLowerCase());
} else if (this.search !== '') {
return country.name.toLowerCase().match(this.search.toLowerCase());
} else {
return ('blbla');
}
})
}
},
}
</script>
<template>
<div class="home" :class="{ darkTheme : isDarkTheme }">
<div class="searchBar">
<div class="searchContainer">
<i class="fas fa-search searchIcon"></i>
<input
class="searchInput"
type="text"
v-model="search"
aria-label="Search for a country..."
placeholder="Search for a country..."
/>
<ul class="searchResults"></ul>
</div>
</div>
<h1 v-if="error !== null">Sorry, an error has occurred {{error}}</h1>
<div class="loaderFlex"><div v-if="pending" class="loader"></div></div>
<div v-if="countryInfo" class="tileGrid" #click="showModal = true">
<div v-for="country in filteredCountries" class="countryTile" v-bind:key="country.id">
<router-link
:to="{ name: 'country-detail', params: {country: country.name }}"
class="linkTile"
>
<img v-bind:src="country.flag" alt="Country Flag" class="flag">
<div class="text">
<h1>{{ country.name }}</h1>
</div>
</router-link>
</div>
</div>
</div>
</template>
The router-link will always redirect you to another page, because its basically <a href="..."> see here. You don't need router if you just want to show the detail on a modal, you could just add the modal component inside the Home.vue component, then bind the modal and the countryName with props, then pass them in when clicking a button.
Home.vue:
<template>
<div>
<button #click="showDetail">
Show Detail
</button>
<CountryDetail :countryName="countryName" :showModal="showModal"/>
<div>
</template>
<script>
import CountryDetail from './CountryDetail.vue'
export default {
name: 'Home',
components: { CountryDetail },
data: () => ({
countryName: '',
showModal: false,
}),
methods: {
showDetail() {
this.showModal = true;
},
},
}
</script>
And instead of making request on mounted, you could use watch to do something like watching for the showModal prop, and make request everytime it has a truthy value. Like this:
CountryDetail.vue:
<template>
<modal v-model="showModal">
<!-- modal content -->
</modal>
</template>
<script>
export default {
name: 'CountryDetail',
props: ['countryName', 'showModal'],
watch: {
'showModal': {
deep: true,
handler(val) {
if (val && this.countryName !== '') {
// Make request
}
}
}
}
}
</script>
I want to change only the color of the string in 'subMsg'. I don't want the color to affect the 'HomeContent' too.
<template>
<div class="submit">
<HomeContent v-bind:style="{ color: color }" subMsg="* Required"/>
<div id="buttons">
<button type="button" class="btn btn-light"><router-link to="/about">Back</router-link></button>
<button type="button" class="btn btn-primary" id="submit">Submit</button>
</div>
</div>
</template>
I alocated a different string to the same subMsg in another component but in this component. I want the color to the different for only this subMsg component.
import HomeContent from "#/components/HomeContent.vue";
export default {
name: "Submit",
components: {
HomeContent
},
data() {
return {
color: "red"
}
}
}
Is it possible to change the color?
you can do something like this:
parent component:
//HTML part
<HomeContent :subMsg="{color:subMsgColor,message:'* Required'}"/>
// script part
data() {
return {
subMsgColor: "red"
}
HomeContent component:
<template>
<div class="hello">
<div class="card">
<div class="card-content">
<h1>{{ msg }}</h1>
<p :style="{color:subMsg.color}">{{ subMsg.message }}</p>
</div>
</div>
</div>
</template>
<script>
export default {
name: "hello",
data() {
return {
msg: "Data Collection"
};
},
props: {
subMsg: {
type:Object,
default:null
}
}
}
</script>
I have 2 modal windows: register and login. When I click to "Sign Up" button, the modal window should change. What should I do?
This is a project link.
https://jsfiddle.net/Alienwave/0kqj7tr1/4/
Vue.component('signup', {
template: '#signup-template'
})
Vue.component('login', {
template: '#login-template',
data() {
return {
loginInput: '',
passwordInput: ''
}
},
methods: {
sendRequest(e) {
//code not here
},
changeModal() {
// THIS!!
}
}
});
new Vue({
el: "#app",
data() {
return {
showLogin: true,
showSignup: false
}
}
});
This is login template:
<template id="login-template">
<transition name="modal">
<div class="login-mask">
<div class="login-wrapper">
<div class="login-container">
<div class="login-footer">
<slot name="footer">
<div class="change-mode">
<button class="change-mode-reg" #click="">Sign up</button> <!-- THIS BUTTON SHOULD CHANGE MODAL! -->
</div>
</slot>
</div>
</div>
</div>
</div>
</transition>
</template>
Register template looks the same.
I cut a big chunk.
This is a good use case for Vue's custom events. I would update your code as follows:
#login-template
...
<div class="login-footer">
<slot name="footer">
<div class="change-mode">
<button class="change-mode-reg" #click="changeModal">Sign up</button>
<div class="change-mode-line"></div>
</div>
</slot>
</div>
...
login component
Vue.component('login', {
template: '#login-template',
data() {
return {
loginInput: '',
passwordInput: ''
}
},
methods: {
sendRequest(e) {
//code not here
},
changeModal() {
this.$emit('change');
}
}
});
#app
<div id="app">
<login v-if="showLogin" #close="showLogin = false" #change="changeModal"></login>
<signup v-if="showSignup" #close="showSignup = false"></signup>
</div>
Here is an updated fiddle.
(NOTE: it looks like you might have some other issues going on here, but this gets your modal switching issue fixed.)