Reputation: 206
Goal: I want to be able to have a modal template that I can extend in other pages in my Vue.js (Nuxt.js) application
ModalTemplate.vue:
<!-- Base Modal Component -->
<template>
<!-- Modal -->
<div class="modal opacity-0 pointer-events-none fixed w-full h-full top-0 left-0 flex items-center justify-center">
<div class="modal-overlay absolute w-full h-full bg-gray-900 opacity-50"></div>
<!-- Modal Container -->
<div class="modal-container bg-gray-300 w-5/12 mx-auto rounded shadow-lg z-50 overflow-y-auto">
<!-- Top Right escape button (needs to be within the container for z-index purposes) -->
<div class="modal-close absolute top-0 right-0 cursor-pointer flex flex-col items-center mt-4 mr-4 text-white text-sm z-50">
<fa icon="times" class="fa-2x"></fa>
<span class="text-sm">(Esc)</span>
</div>
<div class="modal-content">
<!-- Title of Modal -->
<div class="modal-title-container">
<slot name="modal-header"></slot>
</div>
<!-- Body of Modal -->
<div class="modal-body-container">
<slot></slot>
</div>
<!-- Footer of Modal -->
<div class="modal-footer-container">
<slot name="modal-footer"></slot>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ModalTemplate',
data() {
return {
}
},
methods: {
toggleModal: function() {
var body = document.querySelector('body');
var modal = document.querySelector('.modal');
modal.classList.toggle('opacity-0');
modal.classList.toggle('pointer-events-none');
body.classList.toggle('modal-active');
},
modalHidden: function () {
this.toggleModal();
this.messageBus.$emit('closing')
},
keyDownPressed: function(keyPressed) {
var isEscape = false
if (keyPressed.key === "Escape" || keyPressed.key === "Esc") {
isEscape = true
} else {
isEscape = (keyPressed.keyCode === 27)
}
if (isEscape && document.querySelector('body').classList.contains('modal-active')) {
this.modalHidden();
}
}
},
created: function() {
window.addEventListener('keydown', this.keyDownPressed)
},
destroyed: function() {
window.removeEventListener('keydown', this.keyDownPressed)
},
mounted: function() {
this.toggleModal();
var closeModalSelector = document.querySelectorAll('.modal-close')
for (var i = 0; i < closeModalSelector.length; i++) {
closeModalSelector[i].addEventListener('click', this.modalHidden)
}
const overlay = document.querySelector('.modal-overlay')
overlay.addEventListener('click', this.modalHidden);
}
}
</script>
<style lang="postcss">
.modal-page {
@apply pointer-events-none;
@apply fixed;
@apply w-full;
@apply h-full;
@apply top-0;
@apply left-0;
@apply flex;
@apply items-center;
@apply justify-center;
}
.modal-overlay {
@apply absolute;
@apply w-full;
@apply h-full;
@apply bg-gray-900;
}
.modal-container {
@apply bg-gray-300;
@apply mx-auto;
@apply rounded;
@apply shadow-lg;
@apply z-50;
@apply overflow-y-auto;
}
.modal-content {
@apply py-4;
}
.modal-title-container {
@apply flex;
@apply justify-between;
@apply items-center;
@apply border-b;
@apply border-gray-400;
@apply px-4;
@apply pb-3;
}
.modal-body-container {
@apply py-2;
@apply px-4;
}
.modal-footer-container {
@apply flex;
@apply border-t;
@apply border-gray-400;
@apply px-4;
@apply pt-2;
}
</style>
CertificateDetailsModal.vue:
<template>
<ModalTemplate ref="modal">
<template v-slot:modal-header>
This is a header
</template>
</ModalTemplate>
</template>
<script>
import ModalTemplate from '~/components/Modals/ModalTemplate'
export default {
name: 'DetailsModal',
components: {
ModalTemplate
},
model: {
prop: 'certificate',
event: 'input'
},
props: {
certificate: {
type: Object,
default: null
}
},
mounted() {
},
methods: {
closeModal: function() {
alert('closing modal!')
this.$store.dispatch('certificates/loadCertificates')
this.$emit('input', null);
}
}
}
</script>
<style scoped>
</style>
I looked at extending the modal, but it gave me quite a few errors when I tried to dismiss the modal (I can provide the code if needed).
How can I extend the Modal (getting all the functionality of the functions) while adding additional functionality in the CertificateDetailsModal
(such as functions, methods, and html)?
Upvotes: 0
Views: 1008
Reputation: 138536
You could re-declare the slots in your wrapper component's template. For instance, the following template declares a modal-footer
slot and a default
slot (unnamed assumed to have a name of default
):
<!-- CertificateDetailsModal.vue -->
<template>
<ModalTemplate>
<template v-slot:modal-header>
My header
</template>
<template v-slot:modal-footer> <!-- pass `modal-footer` slot to ModalTemplate -->
<slot name="modal-footer"></slot>
</template>
<slot /> <!-- pass `default` slot to ModalTemplate -->
</ModalTemplate>
</template>
Then your app could use the CertificateDetailsModal
like this:
<!-- App.vue -->
<template>
<CertificateDetailsModal>
<template v-slot:modal-footer>
<footer>My footer</footer>
</template>
<span>My default</span>
</CertificateDetailsModal>
</template>
Upvotes: 2