Skip to content

Commit

Permalink
Introduced visitModal method
Browse files Browse the repository at this point in the history
  • Loading branch information
pascalbaljet committed Oct 2, 2024
1 parent c862022 commit 2d30c3e
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 27 deletions.
24 changes: 24 additions & 0 deletions demo-app/resources/js/Pages/Visit.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script setup>
import Container from './Container.vue'
import { Modal, visitModal } from 'inertiaui/modal'
</script>

<template>
<Container>
<div class="flex justify-between">
<h2 class="text-lg font-medium text-gray-900">Visit programmatically</h2>

<button @click="visitModal('#local')" type="button">
Open Local Modal
</button>

<button @click="visitModal('/data', { method: 'post', data: {message: 'Hi again!'}})" type="button">
Open Route Modal
</button>
</div>
</Container>

<Modal name="local">
Hi there!
</Modal>
</template>
35 changes: 35 additions & 0 deletions demo-app/tests/Browser/VisitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Tests\Browser;

use PHPUnit\Framework\Attributes\Test;
use Tests\DuskTestCase;

class VisitTest extends DuskTestCase
{
#[Test]
public function it_can_programmatically_visit_a_local_modal()
{
$this->browse(function (Browser $browser) {

$browser->visit('/visit')
->waitForText('Visit programmatically')
->press('Open Local Modal')
->waitFor('.im-dialog')
->assertSeeIn('.im-modal-content', 'Hi there!');
});
}

#[Test]
public function it_can_programmatically_visit_a_modal()
{
$this->browse(function (Browser $browser) {

$browser->visit('/visit')
->waitForText('Visit programmatically')
->press('Open Route Modal')
->waitFor('.im-dialog')
->assertSeeIn('.im-modal-content', 'Hi again!');
});
}
}
42 changes: 42 additions & 0 deletions docs/basic-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,45 @@ Then there are two more events: `@close` and `@after-leave`. The `@close` event
### Customizing

Just like the `Modal` component, you can pass additional props to the `ModalLink` component to customize its behavior and style. Check out the [Configuration](/configuration.html) section for a list of all available props.

## Programmatic Usage

Instead of using the `ModalLink` component, you can also open a modal programmatically using the `visitModal` method.

```vue
<script setup>
import { visitModal } from '@inertiaui/modal-vue'
function createUserModal() {
visitModal('/users/create')
}
</script>
<template>
<button @click="createUserModal">Create User</button>
</template>
```

If you want to open a [Local Modal](/local-modals.html), you must prepend the URL with a `#`:

```js
visitModal('#confirm-action')
```

The `visitModal` method accepts a second argument, which is an object with options:

```js
visitModal('/users/create', {
method: 'post',
data: { default_name: 'John Doe' },
headers: { 'X-Header': 'Value' },
config: {
slideover: true,
}
onClose: () => console.log('Modal closed'),
onAfterLeave: () => console.log('Modal removed from DOM'),
queryStringArrayFormat: 'brackets',
})
```

The `config` option allows you to customize the behavior and style of the modal. You should check out the [Configuration](/configuration.html#default-configuration) section for a list of all available options. The `queryStringArrayFormat` can be set to either `brackets` or `indices`.
30 changes: 15 additions & 15 deletions vue/src/ModalLink.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup>
import { modalPropNames, useModalStack } from './modalStack'
import { nextTick, ref, provide, watch, onMounted, useAttrs, onBeforeUnmount } from 'vue'
import { ref, provide, watch, onMounted, useAttrs, onBeforeUnmount } from 'vue'
import { only, rejectNullValues } from './helpers'
const props = defineProps({
Expand Down Expand Up @@ -118,11 +118,7 @@ watch(modalContext, (value, oldValue) => {
}
registerEventListeners()
nextTick(() => {
modalContext.value.open = true
emit('success')
})
emit('success')
}
})
Expand All @@ -143,18 +139,22 @@ function handle() {
return
}
const modalProps = rejectNullValues(only(props, modalPropNames))
if (props.href.startsWith('#')) {
modalContext.value = modalStack.callLocalModal(props.href.substring(1), modalProps, onClose, onAfterLeave)
return
if (!props.href.startsWith('#')) {
loading.value = true
emit('start')
}
loading.value = true
emit('start')
modalStack
.visit(props.href, props.method, props.data, props.headers, modalProps, onClose, onAfterLeave, props.queryStringArrayFormat)
.visit(
props.href,
props.method,
props.data,
props.headers,
rejectNullValues(only(props, modalPropNames)),
onClose,
onAfterLeave,
props.queryStringArrayFormat,
)
.then((context) => (modalContext.value = context))
.catch((error) => emit('error', error))
.finally(() => (loading.value = false))
Expand Down
16 changes: 15 additions & 1 deletion vue/src/inertiauiModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,19 @@ import { getConfig, putConfig, resetConfig } from './config.js'
import Modal from './Modal.vue'
import ModalLink from './ModalLink.vue'
import ModalRoot from './ModalRoot.vue'
import { useModalStack } from './modalStack.js'

export { Modal, ModalLink, ModalRoot, getConfig, putConfig, resetConfig }
function visitModal(url, options = {}) {
return useModalStack().visit(
url,
options.method ?? 'get',
options.data ?? {},
options.headers ?? {},
options.config ?? {},
options.onClose,
options.onAfterLeave,
options.queryStringArrayFormat ?? 'brackets',
)
}

export { Modal, ModalLink, ModalRoot, getConfig, putConfig, resetConfig, visitModal }
28 changes: 17 additions & 11 deletions vue/src/modalStack.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const localModals = ref({})
class Modal {
constructor(component, response, modalProps, onClose, afterLeave) {
this.id = Modal.generateId()
this.open = false
this.open = true
this.listeners = {}

this.component = component
Expand Down Expand Up @@ -51,13 +51,13 @@ class Modal {
})

stack.value[index].open = false
this.onCloseCallback()
this.onCloseCallback?.()
}
}

afterLeave = () => {
stack.value = stack.value.filter((m) => m.id !== this.id)
this.afterLeaveCallback()
this.afterLeaveCallback?.()
}

on = (event, callback) => {
Expand Down Expand Up @@ -126,19 +126,26 @@ function registerLocalModal(name, callback) {
localModals.value[name] = { name, callback }
}

function callLocalModal(name, modalProps, onClose, afterLeave) {
if (localModals.value[name]) {
const modal = push(null, {}, modalProps, onClose, afterLeave)
modal.name = name
localModals.value[name].callback(modal)
return modal
function pushLocalModal(name, modalProps, onClose, afterLeave) {
if (!localModals.value[name]) {
throw new Error(`The local modal "${name}" has not been registered.`)
}

const modal = push(null, {}, modalProps, onClose, afterLeave)
modal.name = name
localModals.value[name].callback(modal)
return modal
}

function visit(href, method, payload = {}, headers = {}, modalProps = {}, onClose, onAfterLeave, queryStringArrayFormat = 'brackets') {
function visit(href, method, payload = {}, headers = {}, modalProps = {}, onClose = null, onAfterLeave = null, queryStringArrayFormat = 'brackets') {
const [url, data] = mergeDataIntoQueryString(method, href || '', payload, queryStringArrayFormat)

return new Promise((resolve, reject) => {
if (href.startsWith('#')) {
resolve(pushLocalModal(href.substring(1), modalProps, onClose, onAfterLeave))
return
}

Axios({
url,
method,
Expand Down Expand Up @@ -179,7 +186,6 @@ export function useModalStack() {
push,
reset: () => (stack.value = []),
visit,
callLocalModal,
registerLocalModal,
removeLocalModal: (name) => delete localModals.value[name],
rootPresent,
Expand Down

0 comments on commit 2d30c3e

Please sign in to comment.