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.)
Related
I'm opening the same component content since i have a different ones, ( Rendering problem )
I have a modal component called modal.vue (Re-usable)
so each time I create a component I call the modal component and I inject new content,
The problem is : I have two-component ( country.vue & city.vue ) I import both of them in my index.vue
but each time I click on a city button to load city component, I load the country modal, just like there is a rendering problem ( i can't re-render )
can someone explain to me the solution please, this is my code
modal.vue ( re-usable )
<template>
<div class="right-bar" :class="(size == null) ? 'right-bar-35' : size">
<simplebar class="h-100">
<div class="rightbar-title px-3 py-4">
<div class="row">
<div class="col-lg-10">
<h5 class="m-0">{{title}}</h5>
</div>
<div class="col-lg-2 text-right">
<span class="clickable" #click="hideRightSidebar"><i class="mdi mdi-close-thick"></i></span>
</div>
</div>
</div>
<div class="px-3 py-4">
<slot/>
</div>
<footer class="modal-footer">
<button type="button" class="btn btn-secondary" style="float:left;">Cancel</button>
<slot name="footer"/>
</footer>
</simplebar>
</div>
</template>
as you can see i have a <slot/> in my component so each time in inject a new content.
This is my country.vue component ( i use here modal.vue component )
<template>
<div>
<button class="btn btn-sm btn-white" #click="init">Filter <i class="mdi mdi-filter"></i></button>
<modal title="countries" v-if="showModal">
i 'am the country modal
</modal>
</div>
</template>
<script>
import RightBar from "#/components/modal";
export default {
data() {
return {
showModal: false
}
},
components:{
modal,
},
methods: {
init: function(){
this.showModal= true;
}
}
}
</script>
This is my city.vue component ( i use here modal.vue component )
<template>
<div>
<button class="btn btn-sm btn-white" #click="init">Filter <i class="mdi mdi-filter"></i></button>
<modal title="cities" v-if="showModal">
i 'am the citymodal
</modal>
</div>
</template>
<script>
import RightBar from "#/components/modal";
export default {
data() {
return {
showModal: false
}
},
components:{
modal,
},
methods: {
init: function(){
this.showModal= true;
}
}
}
</script>
This is my index.vue ( where i load city.vue & country.vue ) as you can see both of my components have a button
<template>
<div>
<city></city>
<country></country>
</div>
</template>
<script>
import addContact from "./city.vue";
import filterContact from "./country.vue";
export default {
components:{city,country}
data(){
return {
}
}
}
</script>
So when i click on city i see the country modal ( re-rendering problem ) how can i solve that
Here's an example:
in city and country component add a function that tells the parent component that there will be a rerendering. you call this function once you click on the show modal button.
init(){
this.rerender()
this.showModal= true
},
rerender(){
this.$emit('forceRerender', true)
}
then in the parent component we'll listen to this function and rerender.
<city v-if="isCountry" v-on:forceRerender="hideCountry" />
<country v-if="isCity" v-on:forceRerender="hideCity" />
data(){
return{
isCountry: true,
isCity: true,
}
},
methods:{
hideCountry(){
this.isCountry= false
this.$nextTick(()=>{
this.isCountry= ture
})
},
hideCity(){
this.isCity= false
this.$nextTick(()=>{
this.isCity= ture
})
}
}
to break it down. when we want to load the modal from the city component we tell the country component to close the modal first. I hope this works for you.
another way with a switch statement:
city:
rerender(){
this.$emit('forceRerender', 'city')
}
country:
rerender(){
this.$emit('forceRerender', 'country')
}
parent component:
<city v-if="isCountry" v-on:forceRerender="setActiveElement" />
<country v-if="isCity" v-on:forceRerender="setActiveElement" />
setActiveElement(element){
switch(element){
case 'city':
this.isCity = true
this.isCountry = false
this.$nextTick().then(()=>{
this.isCountry = true
})
break
case 'country':
this.isCountry = true
this.isCity = false
this.$nextTick().then(()=>{
this.isCity= true
})
break
default:
this.isCountry = this.isCity = true
break
}
}
I have this project where I try to add a mobile view menu. This menu is displayed by clicked a button. The button I have created switches a boolean on and off. But when the value of the boolean changes the v:if on the menu doesn't hide it. It keeps on showing.
This is my menu item inside the template:
<template>
<div>
<div> ... main menu ... </div>
<div :v-if="menuOpened" class="bg-purple-primary h-10 z-20">
<p>Hello World</p>
</div>
</div>
</template>
<script>
export default {
name: 'Header',
data () {
return {
menuOpened: false
}
},
methods: {
switchMenuState () {
this.menuOpened = !this.menuOpened
console.log(this.menuOpened)
}
}
}
</script>
You do not need that colon before v-if directive
<template>
<div>
<button #click="switchMenuState()">Switch</button>
<div v-if="menuOpened" class="bg-purple-primary h-10 z-20">
<p>Hello World</p>
</div>
</div>
</template>
<script>
export default {
name: "Header",
data() {
return {
menuOpened: false,
};
},
methods: {
switchMenuState() {
this.menuOpened = !this.menuOpened;
},
},
};
</script>
I need to make the modal visible on click, I work through components, new Vue constructs do not work
I've never worked with Vue, now I'm asking for help
Error:
64:4 error 'showModal:' is defined but never used no-unused-labels
<template>
<modalPhoto v-if="showModal == 'true'"></modalPhoto>
<div class="block-inputs">
<div class="input-header">
<h3>Photos</h3>
</div>
<div class="photos">
<div class="photo-block">
<img src="../assets/photo.jpg" class="photo modal-img" v-on:click="viewPhoto('true')">
</div>
</div>
</div>
<div class="save-changes">
<div style="margin: 0 auto;">
<button class="button-add button-save">Save</button>
</div>
</div>
<script>
import modalPhoto from './modal/modalPhoto.vue';
export default {
name: 'Photos',
data() {
showModal: false
},
methods: {
viewPhoto() {
this.showModal = true;
}
},
components: {
modalPhoto,
}
}
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>
My goal is when clicking on any search item to open the Popup.
Why does not work — this.$emit('openPopup', bookId); in the method selectBook(bookId)
There is a component of Results.vue, which displays search results by using Google Books API:
<template>
<div class="results">
<ul class="results-items">
<li
class="book"
#click="selectBook(result.id)"
>
<img
:src="'http://books.google.com/books/content?id=' + result.id + '&printsec=frontcover&img=1&zoom=1&source=gbs_api'"
class="cover"
>
<div class="item-info">
<div class="bTitle">{{ result.volumeInfo.title }}</div>
<div
class="bAutors"
v-if="result.volumeInfo.authors"
>
{{ result.volumeInfo.authors[0] }}
</div>
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
props: ['result'],
data() {
return {
bookId: '',
};
},
methods: {
selectBook(bookId) {
this.$router.push(`bookId${bookId}`);
this.$store.dispatch('selectBook', bookId);
this.$emit('hideElements', bookId);
this.$emit('openPopup', bookId);
},
},
};
</script>
Rusults screenshot
Is the main App.vue, which I want to display Popup:
<template>
<div id="app">
<div class="container">
<Header />
<popup
v-if="isOpenPopup"
#closePopup="closeInfoPopup"
#openPopup="showModal"
/>
<router-view/>
</div>
</div>
</template>
<script>
import Header from '#/components/Header.vue';
import Popup from '#/components/Popup.vue';
import 'bootstrap/dist/css/bootstrap.css';
export default {
components: {
Header,
Popup,
},
data() {
return {
isOpenPopup: false,
};
},
methods: {
showModal() {
console.log('click');
this.isOpenPopup = true;
},
closeInfoPopup() {
this.isOpenPopup = false;
},
},
};
</script>
The component Popup.vue
<template>
<div class="popup">
<div class="popup_header">
<span>Popup name</span>
</div>
<div class="popup_content">
<h1>Hi</h1>
<slot></slot>
<button #click="closePopup">Close</button>
</div>
</div>
</template>
<script>
export default {
name: 'popup',
props: {},
data() {
return {};
},
methods: {
closePopup() {
this.$emit('closePopup');
},
},
};
</script>
You are listening on popup component but triggering events on Result
Don't remember that events are bound to component.
You have several options how to handle it
Add event listeners (like #openPopup) only on Result component and use portal-vue library to render popup on top level
use global events, this.$root.emit and listen also on this.$root. In this case you add event listener in mount() hook and don't forget unregister it in beforeDestroy() hook
you can use Vuex and popup visibility and related data in global application store provided by Vuex