Skip to content

Commit 9ff2377

Browse files
authored
Introduced visitModal method (#5)
1 parent c862022 commit 9ff2377

File tree

6 files changed

+148
-27
lines changed

6 files changed

+148
-27
lines changed

demo-app/resources/js/Pages/Visit.vue

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script setup>
2+
import Container from './Container.vue'
3+
import { Modal, visitModal } from 'inertiaui/modal'
4+
</script>
5+
6+
<template>
7+
<Container>
8+
<div class="flex justify-between">
9+
<h2 class="text-lg font-medium text-gray-900">Visit programmatically</h2>
10+
11+
<button @click="visitModal('#local')" type="button">
12+
Open Local Modal
13+
</button>
14+
15+
<button @click="visitModal('/data', { method: 'post', data: {message: 'Hi again!'}})" type="button">
16+
Open Route Modal
17+
</button>
18+
</div>
19+
</Container>
20+
21+
<Modal name="local">
22+
Hi there!
23+
</Modal>
24+
</template>

demo-app/tests/Browser/VisitTest.php

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Tests\Browser;
4+
5+
use PHPUnit\Framework\Attributes\Test;
6+
use Tests\DuskTestCase;
7+
8+
class VisitTest extends DuskTestCase
9+
{
10+
#[Test]
11+
public function it_can_programmatically_visit_a_local_modal()
12+
{
13+
$this->browse(function (Browser $browser) {
14+
15+
$browser->visit('/visit')
16+
->waitForText('Visit programmatically')
17+
->press('Open Local Modal')
18+
->waitFor('.im-dialog')
19+
->assertSeeIn('.im-modal-content', 'Hi there!');
20+
});
21+
}
22+
23+
#[Test]
24+
public function it_can_programmatically_visit_a_modal()
25+
{
26+
$this->browse(function (Browser $browser) {
27+
28+
$browser->visit('/visit')
29+
->waitForText('Visit programmatically')
30+
->press('Open Route Modal')
31+
->waitFor('.im-dialog')
32+
->assertSeeIn('.im-modal-content', 'Hi again!');
33+
});
34+
}
35+
}

docs/basic-usage.md

+42
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,45 @@ Then there are two more events: `@close` and `@after-leave`. The `@close` event
156156
### Customizing
157157

158158
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.
159+
160+
## Programmatic Usage
161+
162+
Instead of using the `ModalLink` component, you can also open a modal programmatically using the `visitModal` method.
163+
164+
```vue
165+
<script setup>
166+
import { visitModal } from '@inertiaui/modal-vue'
167+
168+
function createUserModal() {
169+
visitModal('/users/create')
170+
}
171+
</script>
172+
173+
<template>
174+
<button @click="createUserModal">Create User</button>
175+
</template>
176+
```
177+
178+
If you want to open a [Local Modal](/local-modals.html), you must prepend the URL with a `#`:
179+
180+
```js
181+
visitModal('#confirm-action')
182+
```
183+
184+
The `visitModal` method accepts a second argument, which is an object with options:
185+
186+
```js
187+
visitModal('/users/create', {
188+
method: 'post',
189+
data: { default_name: 'John Doe' },
190+
headers: { 'X-Header': 'Value' },
191+
config: {
192+
slideover: true,
193+
}
194+
onClose: () => console.log('Modal closed'),
195+
onAfterLeave: () => console.log('Modal removed from DOM'),
196+
queryStringArrayFormat: 'brackets',
197+
})
198+
```
199+
200+
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`.

vue/src/ModalLink.vue

+15-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup>
22
import { modalPropNames, useModalStack } from './modalStack'
3-
import { nextTick, ref, provide, watch, onMounted, useAttrs, onBeforeUnmount } from 'vue'
3+
import { ref, provide, watch, onMounted, useAttrs, onBeforeUnmount } from 'vue'
44
import { only, rejectNullValues } from './helpers'
55
66
const props = defineProps({
@@ -118,11 +118,7 @@ watch(modalContext, (value, oldValue) => {
118118
}
119119
120120
registerEventListeners()
121-
122-
nextTick(() => {
123-
modalContext.value.open = true
124-
emit('success')
125-
})
121+
emit('success')
126122
}
127123
})
128124
@@ -143,18 +139,22 @@ function handle() {
143139
return
144140
}
145141
146-
const modalProps = rejectNullValues(only(props, modalPropNames))
147-
148-
if (props.href.startsWith('#')) {
149-
modalContext.value = modalStack.callLocalModal(props.href.substring(1), modalProps, onClose, onAfterLeave)
150-
return
142+
if (!props.href.startsWith('#')) {
143+
loading.value = true
144+
emit('start')
151145
}
152146
153-
loading.value = true
154-
emit('start')
155-
156147
modalStack
157-
.visit(props.href, props.method, props.data, props.headers, modalProps, onClose, onAfterLeave, props.queryStringArrayFormat)
148+
.visit(
149+
props.href,
150+
props.method,
151+
props.data,
152+
props.headers,
153+
rejectNullValues(only(props, modalPropNames)),
154+
onClose,
155+
onAfterLeave,
156+
props.queryStringArrayFormat,
157+
)
158158
.then((context) => (modalContext.value = context))
159159
.catch((error) => emit('error', error))
160160
.finally(() => (loading.value = false))

vue/src/inertiauiModal.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,19 @@ import { getConfig, putConfig, resetConfig } from './config.js'
22
import Modal from './Modal.vue'
33
import ModalLink from './ModalLink.vue'
44
import ModalRoot from './ModalRoot.vue'
5+
import { useModalStack } from './modalStack.js'
56

6-
export { Modal, ModalLink, ModalRoot, getConfig, putConfig, resetConfig }
7+
function visitModal(url, options = {}) {
8+
return useModalStack().visit(
9+
url,
10+
options.method ?? 'get',
11+
options.data ?? {},
12+
options.headers ?? {},
13+
options.config ?? {},
14+
options.onClose,
15+
options.onAfterLeave,
16+
options.queryStringArrayFormat ?? 'brackets',
17+
)
18+
}
19+
20+
export { Modal, ModalLink, ModalRoot, getConfig, putConfig, resetConfig, visitModal }

vue/src/modalStack.js

+17-11
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const localModals = ref({})
1010
class Modal {
1111
constructor(component, response, modalProps, onClose, afterLeave) {
1212
this.id = Modal.generateId()
13-
this.open = false
13+
this.open = true
1414
this.listeners = {}
1515

1616
this.component = component
@@ -51,13 +51,13 @@ class Modal {
5151
})
5252

5353
stack.value[index].open = false
54-
this.onCloseCallback()
54+
this.onCloseCallback?.()
5555
}
5656
}
5757

5858
afterLeave = () => {
5959
stack.value = stack.value.filter((m) => m.id !== this.id)
60-
this.afterLeaveCallback()
60+
this.afterLeaveCallback?.()
6161
}
6262

6363
on = (event, callback) => {
@@ -126,19 +126,26 @@ function registerLocalModal(name, callback) {
126126
localModals.value[name] = { name, callback }
127127
}
128128

129-
function callLocalModal(name, modalProps, onClose, afterLeave) {
130-
if (localModals.value[name]) {
131-
const modal = push(null, {}, modalProps, onClose, afterLeave)
132-
modal.name = name
133-
localModals.value[name].callback(modal)
134-
return modal
129+
function pushLocalModal(name, modalProps, onClose, afterLeave) {
130+
if (!localModals.value[name]) {
131+
throw new Error(`The local modal "${name}" has not been registered.`)
135132
}
133+
134+
const modal = push(null, {}, modalProps, onClose, afterLeave)
135+
modal.name = name
136+
localModals.value[name].callback(modal)
137+
return modal
136138
}
137139

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

141143
return new Promise((resolve, reject) => {
144+
if (href.startsWith('#')) {
145+
resolve(pushLocalModal(href.substring(1), modalProps, onClose, onAfterLeave))
146+
return
147+
}
148+
142149
Axios({
143150
url,
144151
method,
@@ -179,7 +186,6 @@ export function useModalStack() {
179186
push,
180187
reset: () => (stack.value = []),
181188
visit,
182-
callLocalModal,
183189
registerLocalModal,
184190
removeLocalModal: (name) => delete localModals.value[name],
185191
rootPresent,

0 commit comments

Comments
 (0)