Skip to content

Commit 502eba9

Browse files
authored
feat(BvEvent): subclass BvEvent as BvModalEvent (#3024)
1 parent e03de63 commit 502eba9

File tree

8 files changed

+293
-52
lines changed

8 files changed

+293
-52
lines changed

src/components/modal/README.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,8 @@ export default {
180180

181181
To prevent `<b-modal>` from closing (for example when validation fails). you can call the
182182
`.preventDefault()` method of the event object passed to your `ok` (**OK** button), `cancel`
183-
(**Cancel** button) and `hide` event handlers. Note that `.preventDefault()`, when used, must be
184-
called synchronously, as async is not supported.
183+
(**Cancel** button) and `hide` event handlers. Note that `.preventDefault()`, when used, **must**
184+
be called synchronously, as async is not supported.
185185

186186
```html
187187
<template>
@@ -223,9 +223,9 @@ called synchronously, as async is not supported.
223223
clearName() {
224224
this.name = ''
225225
},
226-
handleOk(evt) {
226+
handleOk(bvModalEvt) {
227227
// Prevent modal from closing
228-
evt.preventDefault()
228+
bvModalEvt.preventDefault()
229229
if (!this.name) {
230230
alert('Please enter your name')
231231
} else {
@@ -257,13 +257,14 @@ The `ok`, `cancel`, and `hide` event object contains several properties and meth
257257

258258
| Property or Method | Type | Description |
259259
| -------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
260-
| `e.preventDefault()` | Method | When called prevents the modal from closing |
261-
| `trigger` | Property | Will be one of: `ok` (Default **OK** Clicked), `cancel` (Default **Cancel** clicked), `esc` (if the <kbd>ESC</kbd> key was pressed), `backdrop` (if the backdrop was clicked), `headerclose` (if the header X button was clicked), the argument provided to the `hide()` method, or `undefined` otherwise. |
262-
| `target` | Property | A reference to the modal element |
263-
| `vueTarget` | property | A reference to the modal's Vue VM instance |
260+
| `preventDefault()` | Method | When called prevents the modal from closing |
261+
| `trigger` | Property | Will be one of: `ok` (Default **OK** Clicked), `cancel` (Default **Cancel** clicked), `esc` (if the <kbd>ESC</kbd> key was pressed), `backdrop` (if the backdrop was clicked), `headerclose` (if the header X button was clicked), the argument provided to the `hide()` method, or `undefined` otherwise. |
262+
| `target` | Property | A reference to the modal element |
263+
| `vueTarget` | property | A reference to the modal's Vue VM instance |
264+
| `modalId` | property | The modal's ID |
264265

265266
You can set the value of `trigger` by passing an argument to the component's `hide()` method for
266-
advanced control.
267+
advanced control (i.e. detecting what button or action triggerd the modal to hide).
267268

268269
**Note:** `ok` and `cancel` events will be only emitted when the argument to `hide()` is strictly
269270
`'ok'` or `'cancel'` respectively. The argument passed to `hide()` will be placed into the `trigger`
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import BvEvent from '../../../utils/bv-event.class'
2+
import warn from '../../../utils/warn'
3+
import { defineProperties, readonlyDescriptor } from '../../../utils/object'
4+
5+
class BvModalEvent extends BvEvent {
6+
constructor(type, eventInit = {}) {
7+
super(type, eventInit)
8+
// Freeze our new props as readonly, but leave them enumerable.
9+
defineProperties(this, {
10+
modalId: readonlyDescriptor(),
11+
trigger: readonlyDescriptor()
12+
})
13+
}
14+
15+
cancel() /* istanbul ignore next */ {
16+
// Backwards compatibility for 1.x BootstrapVue
17+
warn('b-modal: evt.cancel() is deprecated. Please use evt.preventDefault().')
18+
this.preventDefault()
19+
}
20+
21+
static get Defaults() {
22+
return {
23+
...super.Defaults,
24+
modalId: null,
25+
trigger: null
26+
}
27+
}
28+
}
29+
30+
// Named Exports
31+
export { BvModalEvent }
32+
33+
// Default Export
34+
export default BvModalEvent
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import BvModalEvent from './bv-modal-event.class'
2+
import BvEvent from '../../../utils/bv-event.class'
3+
4+
describe('modal > BvModalEvent', () => {
5+
it('works', async () => {
6+
const evt = new BvModalEvent('foobar')
7+
expect(evt).toBeInstanceOf(BvModalEvent)
8+
expect(evt).toBeInstanceOf(BvEvent)
9+
expect(evt.type).toBe('foobar')
10+
})
11+
12+
it('throws exception if no type given', async () => {
13+
let evt = null
14+
let failed = false
15+
try {
16+
evt = new BvModalEvent()
17+
} catch (e) {
18+
failed = true
19+
}
20+
expect(evt).not.toBeInstanceOf(BvModalEvent)
21+
expect(evt).not.toBeInstanceOf(BvEvent)
22+
expect(evt).toBe(null)
23+
expect(failed).toBe(true)
24+
})
25+
26+
it('supports cancelable events via evt.preventDefault()', async () => {
27+
const evt = new BvModalEvent('foobar', {
28+
cancelable: true
29+
})
30+
expect(evt).toBeInstanceOf(BvModalEvent)
31+
expect(evt.type).toBe('foobar')
32+
expect(evt.cancelable).toBe(true)
33+
expect(evt.defaultPrevented).toBe(false)
34+
evt.preventDefault()
35+
expect(evt.defaultPrevented).toBe(true)
36+
})
37+
38+
it('supports cancelable events via deprecated evt.cancel()', async () => {
39+
const evt = new BvModalEvent('foobar', {
40+
cancelable: true
41+
})
42+
expect(evt).toBeInstanceOf(BvModalEvent)
43+
expect(evt.type).toBe('foobar')
44+
expect(evt.cancelable).toBe(true)
45+
expect(evt.defaultPrevented).toBe(false)
46+
evt.cancel()
47+
expect(evt.defaultPrevented).toBe(true)
48+
})
49+
50+
it('supports non cancelable events', async () => {
51+
const evt = new BvModalEvent('foobar', {
52+
cancelable: false
53+
})
54+
expect(evt).toBeInstanceOf(BvModalEvent)
55+
expect(evt.type).toBe('foobar')
56+
expect(evt.cancelable).toBe(false)
57+
expect(evt.defaultPrevented).toBe(false)
58+
evt.preventDefault()
59+
expect(evt.defaultPrevented).toBe(false)
60+
})
61+
62+
it('supports built in properties', async () => {
63+
const evt = new BvModalEvent('foobar', {
64+
target: 'baz',
65+
trigger: 'ok',
66+
modalId: 'foo'
67+
})
68+
expect(evt).toBeInstanceOf(BvModalEvent)
69+
expect(evt.type).toBe('foobar')
70+
expect(evt.cancelable).toBe(true)
71+
expect(evt.target).toBe('baz')
72+
expect(evt.trigger).toBe('ok')
73+
expect(evt.modalId).toBe('foo')
74+
75+
let failed = false
76+
try {
77+
evt.trigger = 'foobar'
78+
} catch (e) {
79+
failed = true
80+
}
81+
expect(failed).toBe(true)
82+
expect(evt.trigger).toBe('ok')
83+
84+
failed = false
85+
try {
86+
evt.modalId = 'fail'
87+
} catch (e) {
88+
failed = true
89+
}
90+
expect(failed).toBe(true)
91+
expect(evt.modalId).toBe('foo')
92+
})
93+
94+
it('supports custom properties', async () => {
95+
const evt = new BvEvent('foobar', {
96+
custom: 123
97+
})
98+
expect(evt).toBeInstanceOf(BvEvent)
99+
expect(evt.type).toBe('foobar')
100+
expect(evt.cancelable).toBe(true)
101+
expect(evt.custom).toBe(123)
102+
})
103+
})

src/components/modal/modal.js

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import Vue from 'vue'
2+
import modalManager from './helpers/modal-manager'
3+
import BvModalEvent from './helpers/bv-modal-event.class'
24
import BButton from '../button/button'
35
import BButtonClose from '../button/button-close'
4-
import modalManager from './helpers/modal-manager'
56
import idMixin from '../../mixins/id'
67
import listenOnRootMixin from '../../mixins/listen-on-root'
78
import observeDom from '../../utils/observe-dom'
8-
import warn from '../../utils/warn'
99
import KeyCodes from '../../utils/key-codes'
10-
import BvEvent from '../../utils/bv-event.class'
1110
import { inBrowser } from '../../utils/env'
1211
import { getComponentConfig } from '../../utils/config'
1312
import { stripTags } from '../../utils/html'
@@ -364,12 +363,11 @@ export default Vue.extend({
364363
return
365364
}
366365
this.is_opening = true
367-
const showEvt = new BvEvent('show', {
366+
const showEvt = new BvModalEvent('show', {
368367
cancelable: true,
369368
vueTarget: this,
370369
target: this.$refs.modal,
371370
relatedTarget: null,
372-
// Modal specifi properties
373371
modalId: this.safeId()
374372
})
375373
this.emitEvent(showEvt)
@@ -387,20 +385,13 @@ export default Vue.extend({
387385
return
388386
}
389387
this.is_closing = true
390-
const hideEvt = new BvEvent('hide', {
391-
// BvEvent standard properties
388+
const hideEvt = new BvModalEvent('hide', {
392389
cancelable: true,
393390
vueTarget: this,
394391
target: this.$refs.modal,
395392
relatedTarget: null,
396-
// Modal specific properties and methods
397393
modalId: this.safeId(),
398-
trigger: trigger || null,
399-
cancel() /* istanbul ignore next */ {
400-
// Backwards compatibility
401-
warn('b-modal: evt.cancel() is deprecated. Please use evt.preventDefault().')
402-
this.preventDefault()
403-
}
394+
trigger: trigger || null
404395
})
405396
// We emit specific event for one of the three built-in buttons
406397
if (trigger === 'ok') {
@@ -475,7 +466,7 @@ export default Vue.extend({
475466
this.is_show = true
476467
this.is_transitioning = false
477468
this.$nextTick(() => {
478-
const shownEvt = new BvEvent('shown', {
469+
const shownEvt = new BvModalEvent('shown', {
479470
cancelable: false,
480471
vueTarget: this,
481472
target: this.$refs.modal,
@@ -503,7 +494,7 @@ export default Vue.extend({
503494
this.$nextTick(() => {
504495
this.returnFocusTo()
505496
this.is_closing = false
506-
const hiddenEvt = new BvEvent('hidden', {
497+
const hiddenEvt = new BvModalEvent('hidden', {
507498
cancelable: false,
508499
vueTarget: this,
509500
target: this.lazy ? null : this.$refs.modal,

src/components/modal/modal.spec.js

Lines changed: 111 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import BModal from './modal'
2-
import BvEvent from '../../utils/bv-event.class'
2+
import BvModalEvent from './helpers/bv-modal-event.class'
33

44
import { mount, createWrapper } from '@vue/test-utils'
55

@@ -353,7 +353,7 @@ describe('modal', () => {
353353
// Try and close modal (but we prevent it)
354354
$close.trigger('click')
355355
expect(trigger).toEqual('headerclose')
356-
expect(evt).toBeInstanceOf(BvEvent)
356+
expect(evt).toBeInstanceOf(BvModalEvent)
357357

358358
await wrapper.vm.$nextTick()
359359
await waitAF()
@@ -369,7 +369,7 @@ describe('modal', () => {
369369
evt = null
370370
$close.trigger('click')
371371
expect(trigger).toEqual('headerclose')
372-
expect(evt).toBeInstanceOf(BvEvent)
372+
expect(evt).toBeInstanceOf(BvModalEvent)
373373

374374
await wrapper.vm.$nextTick()
375375
await waitAF()
@@ -722,6 +722,66 @@ describe('modal', () => {
722722
wrapper.destroy()
723723
})
724724

725+
it('$root bv::toggle::modal works', async () => {
726+
const wrapper = mount(BModal, {
727+
attachToDocument: true,
728+
stubs: {
729+
transition: false
730+
},
731+
propsData: {
732+
id: 'test',
733+
visible: false
734+
}
735+
})
736+
737+
expect(wrapper.isVueInstance()).toBe(true)
738+
739+
await wrapper.vm.$nextTick()
740+
await waitAF()
741+
await wrapper.vm.$nextTick()
742+
await waitAF()
743+
744+
const $modal = wrapper.find('div.modal')
745+
expect($modal.exists()).toBe(true)
746+
747+
expect($modal.element.style.display).toEqual('none')
748+
749+
// Try and open modal via `bv::toggle::modal`
750+
wrapper.vm.$root.$emit('bv::toggle::modal', 'test')
751+
752+
await wrapper.vm.$nextTick()
753+
await waitAF()
754+
await wrapper.vm.$nextTick()
755+
await waitAF()
756+
757+
// Modal should now be open
758+
expect($modal.element.style.display).toEqual('')
759+
760+
// Try and close modal via `bv::toggle::modal`
761+
wrapper.vm.$root.$emit('bv::toggle::modal', 'test')
762+
763+
await wrapper.vm.$nextTick()
764+
await waitAF()
765+
await wrapper.vm.$nextTick()
766+
await waitAF()
767+
768+
// Modal should now be closed
769+
expect($modal.element.style.display).toEqual('none')
770+
771+
// Try and open modal via `bv::toggle::modal` with wrong ID
772+
wrapper.vm.$root.$emit('bv::toggle::modal', 'not-test')
773+
774+
await wrapper.vm.$nextTick()
775+
await waitAF()
776+
await wrapper.vm.$nextTick()
777+
await waitAF()
778+
779+
// Modal should now be open
780+
expect($modal.element.style.display).toEqual('none')
781+
782+
wrapper.destroy()
783+
})
784+
725785
it('show event is cancellable', async () => {
726786
let prevent = true
727787
let called = 0
@@ -790,5 +850,53 @@ describe('modal', () => {
790850

791851
wrapper.destroy()
792852
})
853+
854+
it('instance .toggle() methods works', async () => {
855+
const wrapper = mount(BModal, {
856+
attachToDocument: true,
857+
stubs: {
858+
transition: false
859+
},
860+
propsData: {
861+
id: 'test',
862+
visible: false
863+
}
864+
})
865+
866+
expect(wrapper.isVueInstance()).toBe(true)
867+
868+
await wrapper.vm.$nextTick()
869+
await waitAF()
870+
await wrapper.vm.$nextTick()
871+
await waitAF()
872+
873+
const $modal = wrapper.find('div.modal')
874+
expect($modal.exists()).toBe(true)
875+
876+
expect($modal.element.style.display).toEqual('none')
877+
878+
// Try and open modal via .toggle() method
879+
wrapper.vm.toggle()
880+
881+
await wrapper.vm.$nextTick()
882+
await waitAF()
883+
await wrapper.vm.$nextTick()
884+
await waitAF()
885+
886+
// Modal should now be open
887+
expect($modal.element.style.display).toEqual('')
888+
889+
// Try and close modal via .toggle()
890+
wrapper.vm.toggle()
891+
892+
await wrapper.vm.$nextTick()
893+
await waitAF()
894+
await wrapper.vm.$nextTick()
895+
await waitAF()
896+
897+
// Modal should now be closed
898+
expect($modal.element.style.display).toEqual('none')
899+
wrapper.destroy()
900+
})
793901
})
794902
})

0 commit comments

Comments
 (0)