Skip to content

Instantly share code, notes, and snippets.

@blackfyre
Last active June 12, 2024 11:54
Show Gist options
  • Save blackfyre/ccd2a207948912594b5ac6cf67a11ef8 to your computer and use it in GitHub Desktop.
Save blackfyre/ccd2a207948912594b5ac6cf67a11ef8 to your computer and use it in GitHub Desktop.
Modals in Laravel Nova Tools
<template>
<modal @modal-close="handleClose">
<form
@submit.prevent="handleConfirm"
slot-scope="props"
class="bg-white rounded-lg shadow-lg overflow-hidden"
style="width: 460px"
>
<slot :uppercaseMode="uppercaseMode" :mode="mode">
<div class="p-8">
<heading :level="2" class="mb-6">{{ __('General Modal') }}</heading>
<p class="text-80 leading-normal">{{__('General modal contents')}}</p>
</div>
</slot>
<div class="bg-30 px-6 py-3 flex">
<div class="ml-auto">
<button type="button" data-testid="cancel-button" dusk="cancel-general-button" @click.prevent="handleClose" class="btn text-80 font-normal h-9 px-3 mr-3 btn-link">{{__('Cancel')}}</button>
<button id="confirm-delete-button" ref="confirmButton" data-testid="confirm-button" type="submit" class="btn btn-default btn-danger">{{ __(uppercaseMode) }}</button>
</div>
</div>
</form>
</modal>
</template>
<script>
export default {
name: "GeneralModal",
methods: {
handleClose() {
this.$emit('close')
},
handleConfirm() {
this.$emit('confirm')
},
},
/**
* Mount the component.
*/
mounted() {
this.$refs.confirmButton.focus()
},
}
</script>
<style scoped>
</style>
<template>
<div>
<button @click="openModal">{{__('Open Modal')}}</button>
<portal to="modals">
<transition name="fade">
<general-modal
v-if="modalOpen"
@confirm="confirmModal"
@close="closeModal"
/>
</transition>
</portal>
</div>
</template>
<script>
import GeneralModal from './parts/modals/GeneralModal.vue';
export default {
props: ["resourceName", "resourceId", "field"],
data() {
return {
modalOpen: false
}
},
components: {
GeneralModal
},
mounted() {
},
methods: {
openModal() {
this.modalOpen = true;
},
confirmModal() {
this.modalOpen = false;
},
closeModal() {
this.modalOpen = false;
}
}
};
</script>
@jgusta
Copy link

jgusta commented Apr 16, 2019

freakin' lifesaver. Thank you for this.

@joseph-abell
Copy link

Almost a year later and this is still useful. Thank you! 👯‍♀️ 🎉 🎆

@maherelgamil
Copy link

@blackfyre thanks 🙏 😊

@jrodas4044
Copy link

Thanks

@tomscholz
Copy link

I'm new to vue.js and laravel. Can somebody explain to me what the portal component does?

@blackfyre
Copy link
Author

@tomscholz
If I remember correctly this is the package in question: https://github.com/LinusBorg/portal-vue
It essentially allows you to render html outside of the component.

If you're interested, I can throw up a general modal component as well... but this one is for Laravel Nova Tools.

@minedun6
Copy link

@blackfyre
You're missing the modal component as well ^^'.

@blackfyre
Copy link
Author

@minedun6

As the title and the previous comment states this modal solution is for Laravel Nova tools, and expects and relies on Nova's original modal component to be present. This gist only enables you to utilize that modal component at your leisure.

@henryavila
Copy link

Hi. I'm new to vueJs. Can anyone, please, tell me how to use? where put what code?

@blackfyre
Copy link
Author

blackfyre commented Mar 17, 2020

@henryavila This isn't for a general Vue.js application! This snippet expects to be in an environment provided by Laravel Nova.
If you're interested in a general use modal component for Vue.js, I strongly suggest you check out the examples section in the docs: https://vuejs.org/v2/examples/modal.html

@henryavila
Copy link

Thanks for quick response @blackfyre. In fact I'm using Laravel Nova. I'm new in VueJS and automatically the way Laravel Nova use it. I'm a backend (php) developer starting with the frontend with Laravel Nova.

If you could give some direction, I will be so gald.
For example. I create a Card in Laravel Nova. And whant this card to display a Modal. In this case, the Tool.vue in your gist represent my Card vue file? Then where do I put the GeneralModal.vue (from your gist) file?

@blackfyre
Copy link
Author

@henryavila
Thanks for the background, this puts things into perspective 😄
Haven't developed a Card before, but the match between the Card & Tool vue files seems reasonable.
As for placing the GeneralModal.vue file is totally up to you! At some point, you'll have to reference it in your card vue file.

@finoghentov
Copy link

finoghentov commented Mar 27, 2020

Hello everyone, today i face one problem that I can't use improted component inside this <modal> tag, maybe I do something wrong, I wanted to use vuedraggable to make some functionality inside my project, but i couldn't because <draggable> tag is not initialize like draggable component. After I tried to make my own component just with test content like "Hello world" and import that component and use it inside <modal> tag, the same result. Laravel Nova

@bamatic
Copy link

bamatic commented Mar 28, 2020

If i need two modals in my tool.vue, i need to make two but this does not work I mean if i have two components GeneralModal and TableModal both using difference betwen componentes is only the contenu in the Nova modal's slot, how could I have two modals ?

@provydon
Copy link

thanks for this mehn

@bmoex
Copy link

bmoex commented Feb 22, 2023

Thank you for your sharing; I fixed it for Nova 4 like this:
Similar to your implementation but changed to match laravel/nova/resources/js/components/Modals/DeleteResourceModal.vue

GeneralModal.vue

<template>
    <Modal @modal-close="handleClose"
           :show="show"
           role="alertdialog"
           size="sm">
        <form
            @submit.prevent="handleConfirm"
            class="mx-auto bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden"
        >
            <slot/>
            <ModalFooter>
                <div class="ml-auto">
                    <LinkButton
                        type="button"
                        data-testid="cancel-button"
                        dusk="cancel-delete-button"
                        @click.prevent="handleClose"
                        class="mr-3"
                    >
                        {{ __('Cancel') }}
                    </LinkButton>

                    <LoadingButton
                        ref="confirmButton"
                        dusk="confirm-delete-button"
                        :processing="working"
                        :disabled="working"
                        component="DangerButton"
                        type="submit"
                    >
                        {{ confirmButtonText }}
                    </LoadingButton>
                </div>
            </ModalFooter>
        </form>
    </Modal>
</template>

<script setup>
import {ref, watchEffect} from 'vue'

const props = defineProps({
    confirmButtonText: {
        type: String,
        default: 'Delete'
    }
})
const emit = defineEmits(['close', 'confirm'])

const confirmButton = ref(null)
watchEffect(() => {
    // Only focus when mounted (e.g. if hidden through :show)
    if (confirmButton.value) {
        confirmButton.value.focus()
    }
})

const handleClose = () => {
    emit('close')
};

const handleConfirm = () => {
    emit('confirm')
};
</script>

Tool.vue

...
        <GeneralModal
            :show="modalOpen"
            confirmButtonText="Delete"
            @close="closeModal"
            @confirm="confirmModal">
            <ModalHeader>Delete resource</ModalHeader>
            <ModalContent>
                <p class="leading-normal">
                    Are you sure you want to delete the resource?
                </p>
            </ModalContent>
        </GeneralModal>
...

@BobbyBorisov
Copy link

@bmoex thanks for the snippet. any idea how to trigger the auto close on click away functionality? I can see that Nova 4 native modals have it

@blackfyre
Copy link
Author

@bmoex thanks for the snippet. any idea how to trigger the auto close on click away functionality? I can see that Nova 4 native modals have it

You really can't do that without modifying the nova code as it usually involves a click listener on the backdrop.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment