I am stuck making a modal component for my app in VueJS. I want to be able to put it in its own component for reusability, be able to pass custom content and have multiple modals.
I made the component and it looks like this from the VueJS docs:
Modal.vue
<template>
<div ref="modal">
<script type="text/x-template" id="modal">
<transition name="modal">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
Header
</slot>
</div>
<div class="modal-body">
<slot name="body">
Body
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
Footer
</slot>
<button class="btn btn-primary" #click="$emit('close')">
Button
</button>
</div>
</div>
</div>
</div>
</transition>
</script>
</div>
</template>
<script>
export default {
components: { },
props: ['header', 'footer', 'body', 'button'],
data: () => ({
showModal: false
}),
methods: {
open () {
console.log('Opening modal')
this.showModal = true
}
}
}
</script>
<style>
#modal {
/*color: #000;*/
}
.modal-mask {
position: fixed;
z-index: 9998;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .5);
display: table;
transition: opacity .3s ease;
}
.modal-wrapper {
display: table-cell;
vertical-align: middle;
}
.modal-container {
width: 300px;
margin: 0px auto;
/*padding: 20px 30px;*/
background-color: #25262D;
color: #FEFEFE;
border-radius: 3px;
box-shadow: 0 2px 8px rgba(0, 0, 0, .33);
transition: all .3s ease;
font-size: 1.2em;
}
.modal-header {
border-radius: 3px;
background-color: #202128;
border: none;
}
.modal-header h3 {
margin-top: 0;
color: #42b983;
}
.modal-body {
margin: 20px 0;
/*background-color: #202128;*/
border: none;
}
.modal-footer {
text-align: center;
border: none;
}
.modal-footer .btn {
font-size: 1.0em;
}
/*
* The following styles are auto-applied to elements with
* transition="modal" when their visibility is toggled
* by Vue.js.
*
* You can easily play with the modal transition by editing
* these styles.
*/
.modal-enter {
opacity: 0;
}
.modal-leave-active {
opacity: 0;
}
.modal-enter .modal-container,
.modal-leave-active .modal-container {
-webkit-transform: scale(1.1);
transform: scale(1.1);
}
</style>
I add a modal in my App.vue:
<button #click="openUpdatedModal()" type="button" class="btn btn-default" data-toggle="modal" data-target="#modal">
Launch Updated Modal
</button>
<modal ref="updatedModal" v-if="showModal" #close="showModal = false">
<!--
you can use custom content here to overwrite
default content
-->
<div slot="header">custom header</div>
</modal>
<script>
import Modal from './components/Modal'
export default {
components: { Modal },
data: () => ({
showModal: false
}),
methods: {
openUpdatedModal () {
this.$refs.updatedModal.open()
}
}
}
</script>
When I click the button I get the text in console "Opening modal" but nothing happens.
I can summon it and it works if I have ALL the code in App.vue
What am I missing?
I made it work by creating a ModalComponent.vue and removing the script tag as Phil suggested in the comments. Here's the code.
ModalComponent.vue:
<template>
<transition name="modal">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
default header
</slot>
</div>
<div class="modal-body">
<slot name="body">
default body
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
default footer
<button class="modal-default-button" #click="$emit('close')">
OK
</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
</template>
<script>
export default{
}
</script>
Here's how I defined my component:
Vue.component('modal', ModalComponent);
Modal button in markup:
<button id="show-modal" #click="showModal = true">Show Modal</button>
And the modal component being called on the page:
<modal v-if="showModal" #close="showModal = false">
<!--
you can use custom content here to overwrite
default content
-->
<h3 slot="header">custom header</h3>
</modal>
It's worth noting that I had to declare the showModal property wherever I use my modal.
Related
I have an issue in my vue template down below. I fetched data from axios but it couldn't store the data on this.sub_brand variable. console.log(this.sub_brand) is only working for inside axios. here is the full code -
<template>
<div>
<div class="row" >
<div class="col-md-4 pt-5 pl-5">
<div class="product">
<div class="product__images">
<img
src="http://127.0.0.1:8000/images/gsm.jpg"
alt="google pixel 6"
class="product__main-image"
id="main-image"
/>
<div class="product__slider-wrap">
<div class="product__slider">
<img
src="http://127.0.0.1:8000/images/gsm.jpg"
alt="google pixel 6"
class="product__image product__image--active"
/>
<img
src="http://127.0.0.1:8000/images/gsm.jpg"
alt="google pixel 6"
class="product__image"
/>
<img
src="http://127.0.0.1:8000/images/gsm.jpg"
alt="google pixel 6"
class="product__image"
/>
<img
src="http://127.0.0.1:8000/images/gsm.jpg"
alt="google pixel 6"
class="product__image"
/>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-5 pt-5 pr-5">
<h4>{{sub_brand}}</h4>
<!-- <input type="text" class="form-control" name="" id="" v-model="sub_brand"> -->
<h5>Star</h5>
<div class="bg-light mt-4" style="padding: 10px 0px">
<h2 class="pl-4 pt-3">RM869.00</h2>
</div>
<div class="mt-4">
<h3>Variation</h3>
</div>
<div class="bg-light mt-4" style="padding: 10px 0px">
<h5 class="pl-4">Quantity: </h5>
<input type="number" class="form-control pl-4" placeholder="0" style="width: 12%; margin-left: 4%" min="0">
</div>
<div class="row mt-5">
<button class="btn btn-secondary ml-3">Add to Cart</button>
<button class="btn btn-primary ml-3">Buy Now</button>
</div>
<hr class="mt-5">
</div>
<div class="col-md-3 bg-light">
<div class="pl-5">
<h3><strong>Samsung</strong></h3>
</div>
<div class="pl-5 custom_pan">
Chat
Video Chat
View
</div>
<hr>
<div class="pl-5 mt-4 pr-4">
<strong>Shipping Details</strong>
<p style="color: #D1D3D4">2A, Changkat Duta Kiara, Mont Kiara, 50480 Kuala Lumpur, Willayah Persekutuan Kuala Lumpur</p>
<strong>From: China Logistic, Shankal</strong>
<br><br>
<strong>To: Kuala Lumpur, Willayah Persekutuan</strong>
<br><br>
<strong>Shipping Fee: RM8.00</strong>
</div>
<hr>
<div class="pl-5 mt-5">
<h5>Authentic</h5>
<br>
<h5>15 Days Return</h5>
<br>
<h5>Free Shipping</h5>
</div>
<br><br>
</div>
</div>
<div class="row bg-light">
<div class="card">
<div class="card-header">
<h4>Product Description</h4>
</div>
<div class="card-body">
Samsung
</div>
</div>
</div>
<div class="row bg-light">
<div class="card">
<div class="card-header">
<h4>Review</h4>
</div>
<div class="card-body">
Samsung
</div>
</div>
</div>
<div class="row bg-light">
<div class="card">
<div class="card-header">
<h4>You may also like other product from same merchant</h4>
</div>
<div class="card-body">
Samsung
</div>
</div>
</div>
<div class="row bg-light">
<br><br>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
mounted() {
// console.log('Component mounted.');
this.fetchProduct();
},
data() {
return{
single_product: [],
sub_brand: "",
product_id: 6,
}
},
methods: {
fetchProduct(){
axios.get('https://admin.globalshopping-mall.site/api/single_sub_brand_detail/'+ this.product_id)
.then(res => {
this.single_product = res.data.data[0];
this.sub_brand = res.data.data[0].GUID;
}).catch(err => {console.log(err);});
},
},
}
</script>
<style>
.card{
margin-top: 2%;
margin-left: 4%;
background-color: white;
width: 71%;
/* height: 30%; */
}
.card-header{
background-color: white
}
.product {
width: 100%;
height: 68%;
}
.product__images {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.product__main-image {
max-width: 500px;
max-height: 600px;
object-fit: cover;
cursor: pointer;
border: 1px solid #070707;
}
.product__slider-wrap {
max-width: 500px;
min-height: 100px;
display: flex;
align-items: center;
}
.product__slider {
width: 98%;
display: flex;
flex-wrap: nowrap;
overflow-x: auto;
padding-left: 2%;
}
.product__image {
max-width: 180px;
max-height: 100px;
object-fit: cover;
cursor: pointer;
opacity: 0.5;
margin: 0.25rem;
border: 1px solid #070707;
}
.product__image:first-child {
margin-left: 0;
}
.product__image:last-child {
margin-right: 0;
}
.product__image:hover {
opacity: 1;
}
.product__image--active {
opacity: 1;
}
.product__slider::-webkit-scrollbar {
height: 10px;
}
.product__slider::-webkit-scrollbar-thumb {
background-color: #D1AD33;
border-radius: 50px;
}
</style>
Look, the problem is, your context this. works inside axios with context of axios
If you want to use context this. of vue component, you have to get data from axios outside.
You have to use async/await for you vue method fetchProduct
Something like that
methods: {
async fetchProduct() {
let result = await axios.get("https://admin.globalshopping-mall.site/api/single_sub_brand_detail/" +this.product_id)
.then((res) => {
return res.data.data[0];
})
.catch((err) => {console.log(err);});
this.single_product = result;
this.sub_brand = result.GUID;
},
},
or
You can save your context this to the variable before axios
and use this variable inside the axios function
Something like this
methods: {
fetchProduct() {
let _this = this;
axios.get("https://admin.globalshopping-mall.site/api/single_sub_brand_detail/" + this.product_id)
.then((res) => {
_this.single_product = res.data.data[0];
_this.sub_brand = res.data.data[0].GUID;
})
.catch((err) => {console.log(err);});
console.log(this);
},
},
Root cause : As axios API calls works asynchronously, But other code will execute synchronously. Hence, While trying to logging this.sub_brand it will work synchronously and will invoke before the axios call (response time might vary).
Solution : If you want to access the API response immediately after a call. You have to use async/await which make promises easier to write.
async makes a function return a Promise
await makes a function wait for a Promise
Live Demo :
methods: {
async fetchProduct(){
const res = await axios.get('https://admin.globalshopping-mall.site/api/single_sub_brand_detail/'+ this.product_id)
.then(res => {
return res.data.data[0];
}).catch(err => {console.log(err);});
}
this.single_product = res;
this.sub_brand = res.GUID;
}
This is the logic algorithm I'm converting and it's not working.
Popup.vue
<script>
import '../../assets/style.css'
export default {
data(){
return{
ceos: [{name_ceo:"Mr. A",
position_ceo:"CEO-GOSTREAM",
content:"Lorem."},
{name_ceo:"Mr. B",
position_ceo:"CTO-GOSTREAM",
content:"Lorem2."},
{name_ceo:"Mr. C",
position_ceo:"CGO-GOSTREAM",
content:"Lorem3."},
]
}
},
methods:{
modalCeo(i){
var modal = this.$refs.modal;
modal[0].style.display="flex";
ceos.forEach(element => {
name_ceo1.innerHTML=ceos[i].name_ceo;
position.innerHTML=ceos[i].position_ceo;
content.innerHTML=ceos[i].content;
});
},
closemodal(){
var modal = this.$refs.modal;
modal[0].style.display="none";
}
}
}
</script>
<style>
</style>
My theme:
<template>
<div class="ceo">
<div ref="modal" class="ceo-popup">
<div class="modal-content-ceo">
<div class="btn-close-popup">
<img src="../../assets/close.svg" id="btn-close-modal" type="button" #click="closemodal()">
</div>
<div class="content-main" v-for="(detail, index) in ceos" :key="index">
<h3 id="name"></h3>
<h5 id="position"></h5>
<h6 id="intro-ceo"></h6>
</div>
<img class="buiding-popup" src="../../assets/Group 226.png" alt="">
</div>
</div>
<div class="title">
<h5>Đội ngũ GoStream</h5>
<h2>Co-Founders</h2>
</div>
<div class="avatar-buiding">
<img class="buiding" src="../../assets/building-0122.png" alt="">
<div class="avatar ">
<img src="../../assets/Profile1.png" #click="modalCeo(0)">
<img src="../../assets//Profile2.png" alt="" #click="modalCeo(1)">
<img src="../../assets/Profile3.png" alt="" #click="modalCeo(2)">
</div>
</div>
</div>
</template>
I want when I click it, a pop up will appear showing the values I have given.
Can you guys give me a solution to this problem?
<div class="avatar" v-for="(item, index) in images" :key="index" >
<img :src="item.url" #click="modalCeo(index)">
</div>
Script:
images:[
{url: '../../assets/Profile1.png'},
{url: '../../assets/Profile2.png'},
{url: '../../assets/Profile3.png'},
]
Update on the code I worked on but it still can't render the image.
You can simply achieve it by using Vue Modal component.
Demo :
new Vue({
el: '#app',
data: {
showModal: false,
selectedCeo: [],
ceos: [
{name_ceo:"Mr. A",
position_ceo:"CEO-GOSTREAM",
content:"Lorem."},
{name_ceo:"Mr. B",
position_ceo:"CTO-GOSTREAM",
content:"Lorem2."},
{name_ceo:"Mr. C",
position_ceo:"CGO-GOSTREAM",
content:"Lorem3."}
]
},
methods: {
modalCeo(index) {
this.showModal = true;
this.selectedCeo = this.ceos[index];
}
}
});
.modal-vue .overlay {
position: fixed;
z-index: 9998;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .5);
}
.modal-vue .modal {
position: relative;
width: 300px;
z-index: 9999;
margin: 0 auto;
padding: 20px 30px;
background-color: #fff;
}
.modal-vue .close{
position: absolute;
top: 10px;
right: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<!-- app -->
<div id="app" class="modal-vue">
<!-- button show -->
<button v-for="(item, index) in ceos" :key="index" #click="modalCeo(index)">{{ item.name_ceo }}</button>
<!-- overlay -->
<div class="overlay" v-if="showModal" #click="showModal = false"></div>
<!-- modal -->
<div class="modal" v-if="showModal">
<button class="close" #click="showModal = false">x</button>
<p>Name : {{ selectedCeo.name_ceo }}</p>
<p>Position : {{ selectedCeo.position_ceo }}</p>
<p>Content : {{ selectedCeo.content }}</p>
</div>
</div>
Note : I updated code snippet as per your comment. Instead of the profile pictures I used buttons just for the demo.
here's what I did, I added a transition to the router link. and everything works as it is, but when I enter contact, the modal works because it is modal, but when I switch to other pages again, it does not switch.
App.vue
<template>
<header class="bg-white dark:bg-black">
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
<router-link to="/contact">Contact</router-link>
</header>
<div class="flex flex-row justify-center items-center mx-auto w-full">
<router-view v-slot="{ Component, route }">
<transition name="route" mode="out-in">
<component :is="Component" :key="route.path"/>
</transition>
</router-view>
</div>
</template>
<style>
.route-enter-from,
.route-leave-to {
opacity: 0;
transform: translateY(-30px);
}
.route-enter-active,
.route-leave-active {
transition: all 0.3s ease;
}
.route-enter-to,
.route-leave-from {
transform: translateY(0);
}
</style>
contact.vue
<template>
<button id="show-modal" #click="showModal = true">Show Modal</button>
<Teleport to="body">
<modal :show="showModal" #close="showModal = false">
<template #header>
<h3>custom header</h3>
</template>
</modal>
</Teleport>
</template>
<script>
import Modal from './Modal.vue'
export default {
components: {
Modal
},
data() {
return {
showModal: false
}
}
}
</script>
modal.vue
<script>
export default {
props: {
show: Boolean
}
}
</script>
<template>
<Transition name="modal">
<div v-if="show" class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">default header</slot>
</div>
<div class="modal-body">
<slot name="body">default body</slot>
</div>
<div class="modal-footer">
<slot name="footer">
default footer
<button
class="modal-default-button"
#click="$emit('close')"
>OK</button>
</slot>
</div>
</div>
</div>
</div>
</Transition>
</template>
<style>
.modal-mask {
position: fixed;
z-index: 9998;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: table;
transition: opacity 0.3s ease;
}
.modal-wrapper {
display: table-cell;
vertical-align: middle;
}
.modal-container {
width: 300px;
margin: 0px auto;
padding: 20px 30px;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
transition: all 0.3s ease;
}
.modal-header h3 {
margin-top: 0;
color: #42b983;
}
.modal-body {
margin: 20px 0;
}
.modal-default-button {
float: right;
}
/*
* The following styles are auto-applied to elements with
* transition="modal" when their visibility is toggled
* by Vue.js.
*
* You can easily play with the modal transition by editing
* these styles.
*/
.modal-enter-from {
opacity: 0;
}
.modal-leave-to {
opacity: 0;
}
.modal-enter-from .modal-container,
.modal-leave-to .modal-container {
-webkit-transform: scale(1.1);
transform: scale(1.1);
}
</style>
Im getting used to Vue, but before I installed Bootstrap, my modal worked completely fine. Now it is a black screen. Also when I have my button inside of a div, the button does not render. When I take the button outside of it's div, the modal does not even toggle.
here is the products component i'm importing my modal into:
<template>
<div class="products">
<h1>All Products</h1>
<div v-for="product in products" class="single-product">
<h2 class="title">{{product.title}} - ${{product.price}}</h2>
<img class="images" :src="product.photo_url"></img>
<div class="modal">
<button
type="button"
class="btn btn-primary"
#click="showModal"
>
Show Product Info
</button>
<modal
v-show="isModalVisible"
#close="closeModal"
/>
</div>
</div>
</div>
</template>
<script>
//imports
import Vue from 'vue'
import Modal from './Modal.vue'
//import so I can use vue resource for an http request
import VueResource from 'vue-resource'
Vue.use(VueResource)
export default {
components: {
'modal': Modal
},
data() {
return {
//empty products array to be filled after get request
products: [],
//set modal visibility to false by default
isModalVisible: false,
}
},
methods: {
//show modal when user clicks view product info
showModal() {
this.isModalVisible = true;
},
closeModal() {
//close modal when user clicks the close button inside the modal
this.isModalVisible = false;
}
},
//request to retrieve all products from API using Created
created() {
//http request using vue resource from dependencies
this.$http.get('https://tap-on-it-exercise-backend.herokuapp.com/products').then(function(data) {
//fill products array by grabbing the data, slicing ot, then setting it to products array
this.products = data.body.slice(0, data.body.length)
})
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.images {
width: 100px;
height: 100px;
}
</style>
Here is my modal component that is exported and then imported into products component:
<script>
export default {
name: 'Modal',
methods: {
close() {
this.$emit('close');
},
},
};
</script>
<template>
<transition name="modal-fade">
<div class="modal-backdrop">
<div class="modal" role="dialog" aria-labelledby="productTitle" aria-describedby="productDescription">
<header class="modal-header" id="productTitle">
<slot name="header">
Product Title
</slot>
</header>
<section class="product-photo" id="productPhoto">
<slot name="photo">
Product Photo
</slot>
</section>
<section class="product-description" id="productDescription">
<slot name="body">
Product Description
</slot>
</section>
<footer class="modal-footer">
<slot name="footer">
<button type="button" class="btn-green" aria-label="Close modal">
Like
</button>
<button type="button" class="btn-green" #click="close" aria-label="Close modal">
Close
</button>
</slot>
</footer>
</div>
</div>
</transition>
</template>
<style>
.modal-backdrop {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
}
.modal {
background: #FFFFFF;
box-shadow: 2px 2px 20px 1px;
overflow-x: auto;
display: flex;
flex-direction: column;
}
.modal-header,
.modal-footer {
padding: 15px;
display: flex;
}
.modal-header {
border-bottom: 1px solid #eeeeee;
color: #4AAE9B;
justify-content: space-between;
}
.modal-footer {
border-top: 1px solid #eeeeee;
justify-content: flex-end;
}
.product-description {
position: relative;
padding: 20px 10px;
}
.btn-close {
border: none;
font-size: 20px;
padding: 20px;
cursor: pointer;
font-weight: bold;
color: #4AAE9B;
background: transparent;
}
.btn-green {
color: white;
background: #4AAE9B;
border: 1px solid #4AAE9B;
border-radius: 2px;
}
</style>
m pulling the last strand of ny hair working onto toggle functionality using vuejs the console i dont find anything
my code seems to be
<template>
<div class="wrapper">
<nav class="nav flex-column sideBar" v-show='isOpen'>
<a class="nav-link active" href="#">Home</a>
<a class="nav-link" href="#">Contact</a>
</nav>
<div class="container-fluid">
<nav>
<div class="content">
<div class="navbar navbar-expand-lg">
<button type="button" class="btn btn-info" #click='toggleSideBar'>
<i class="fas fa-align-left"></i>
</button>
</div>
</div>
</nav>
</div>
</div>
</template>
<script>
export default{
data(){
isOpen=false;
},
method(){
toggleSideBar();
},
toggleSideBar(){
console.log("==========="+this.isOpen)
this.isOpen=!this.isOpen;
}
}
</script>
button on click the togglle fuctionality doesnt work out doent work out
any help much appreciated
this is the inspect html in browser
<button type="button" class="btn btn-info">
and not
<button type="button" class="btn btn-info" onclick='toggleSidebar()'>
the click function not added to it
m using sass
.wrapper {
display: flex;
align-items: stretch;
width:100%;
}
.sideBar{
border: 1px solid;
width: 30%;
height: 100vh;
margin-left: 0;
transition: all 0.5s;
background-color: #f9f9f7;
}
.container-fluid {
padding-right: 0px;
padding-left: 0px;
}
.navbar{
#extend .navbar;
background-color: #eae9e5;
}
.sidebar.active {
margin-left: -250px;
}
first, add return then change = to : and ; to , in your data()
data(){
return{
isOpen: false,
}
},
then put the toggleSideBar() inside your method like this:
methods:{
toggleSideBar(){
console.log("================"+this.isOpen)
this.isOpen=!this.isOpen;
}
}
FINAL OUTPUT
export default{
data(){
return{
isOpen: false,
}
},
methods:{
toggleSideBar(){
console.log("================"+this.isOpen)
this.isOpen=!this.isOpen;
},
}
}
<script>
export default{
data(){
isOpen: false; //Change this line to : instead of =
},
method(){
toggleSideBar: function(){
console.log("================"+this.isOpen)
this.isOpen=!this.isOpen;
}
},
}
</script>