Skip to content

feat(modal): support for optionally scoped slots and new Vue.prototype.$bvModal helper #3056

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 111 commits into from
Apr 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
6748ef3
feat(modal): optionally scoped slots
tmorehouse Apr 9, 2019
304daa4
lint
tmorehouse Apr 9, 2019
25c44fd
Update modal.js
tmorehouse Apr 9, 2019
70f1659
lint
tmorehouse Apr 9, 2019
f3794db
Update modal.js
tmorehouse Apr 9, 2019
75dadae
Update package.json
tmorehouse Apr 9, 2019
bd65a0c
Update modal.js
tmorehouse Apr 9, 2019
a14d197
Merge branch 'dev' into tmorehouse/modal-scoped
tmorehouse Apr 9, 2019
c667dfe
Update package.json
tmorehouse Apr 9, 2019
b6b85e4
Update package.json
tmorehouse Apr 9, 2019
b52244c
Merge branch 'dev' into tmorehouse/modal-scoped
tmorehouse Apr 10, 2019
55842ea
Update env.js
tmorehouse Apr 10, 2019
7aea37d
lint
tmorehouse Apr 10, 2019
0a5b49c
Create bv-modal.js
tmorehouse Apr 10, 2019
f1305be
Update bv-modal.js
tmorehouse Apr 10, 2019
1205502
lint
tmorehouse Apr 10, 2019
70e1501
Update bv-modal.js
tmorehouse Apr 10, 2019
4021a3c
Update bv-modal.js
tmorehouse Apr 10, 2019
6130404
Update bv-modal.js
tmorehouse Apr 10, 2019
6eecec6
Update bv-modal.js
tmorehouse Apr 10, 2019
5a0a096
Merge branch 'dev' into tmorehouse/modal-scoped
tmorehouse Apr 10, 2019
0d4f21d
Merge branch 'dev' into tmorehouse/modal-scoped
tmorehouse Apr 10, 2019
c5bf2c7
Update index.js
tmorehouse Apr 10, 2019
ecf8377
Update bv-modal.js
tmorehouse Apr 10, 2019
5af28e7
Update bv-modal.js
tmorehouse Apr 10, 2019
0d8653c
Update bv-modal.js
tmorehouse Apr 10, 2019
767709c
Update bv-modal.js
tmorehouse Apr 10, 2019
3842d2b
Update bv-modal.js
tmorehouse Apr 10, 2019
e4c40fd
Update bv-modal.js
tmorehouse Apr 10, 2019
470593e
Update bv-modal.js
tmorehouse Apr 10, 2019
5994edc
Update bv-modal.js
tmorehouse Apr 10, 2019
e9bf056
Merge branch 'dev' into tmorehouse/modal-scoped
tmorehouse Apr 10, 2019
b0fa942
Update bv-modal.js
tmorehouse Apr 10, 2019
7ce871c
Update bv-modal.js
tmorehouse Apr 10, 2019
1e51cdc
Update bv-modal.js
tmorehouse Apr 10, 2019
bc0e7cc
Update bv-modal.js
tmorehouse Apr 10, 2019
f7b03ac
Merge branch 'dev' into tmorehouse/modal-scoped
tmorehouse Apr 10, 2019
64f5d02
Update bv-modal.js
tmorehouse Apr 10, 2019
fc97524
Update bv-modal.js
tmorehouse Apr 10, 2019
1a4e509
Merge branch 'dev' into tmorehouse/modal-scoped
tmorehouse Apr 11, 2019
21d002b
Merge branch 'dev' into tmorehouse/modal-scoped
tmorehouse Apr 12, 2019
a371838
Update env.js
tmorehouse Apr 12, 2019
07910d6
Update bv-modal.js
tmorehouse Apr 12, 2019
3636b4b
Update bv-modal.js
tmorehouse Apr 12, 2019
ab6b7de
Update bv-modal.js
tmorehouse Apr 12, 2019
7aabfd6
Merge branch 'dev' into tmorehouse/modal-scoped
tmorehouse Apr 12, 2019
41281f7
Update bv-modal.js
tmorehouse Apr 12, 2019
ea51e23
Create inspect.js
tmorehouse Apr 12, 2019
ec65cd2
Update bv-modal.js
tmorehouse Apr 12, 2019
09d93c0
Update bv-modal.js
tmorehouse Apr 12, 2019
29bc8cb
Update inspect.js
tmorehouse Apr 12, 2019
95ec30f
Update bv-modal.js
tmorehouse Apr 12, 2019
7ad4096
Update bv-modal.js
tmorehouse Apr 12, 2019
e216dde
Update bv-modal.js
tmorehouse Apr 12, 2019
09aff01
Update bv-modal.js
tmorehouse Apr 12, 2019
21e556e
Update bv-modal.js
tmorehouse Apr 12, 2019
1258c37
Update bv-modal.js
tmorehouse Apr 12, 2019
1fb1bff
Update modal.js
tmorehouse Apr 13, 2019
7ff3a73
Update bv-modal.js
tmorehouse Apr 13, 2019
985355a
lint
tmorehouse Apr 13, 2019
dc9886b
Update modal.js
tmorehouse Apr 13, 2019
6717c5a
Create inspect.spec.js
tmorehouse Apr 13, 2019
59be7b8
Update inspect.spec.js
tmorehouse Apr 13, 2019
5e0f47d
Update inspect.spec.js
tmorehouse Apr 13, 2019
9b0b1f8
Update inspect.js
tmorehouse Apr 13, 2019
e6e5521
Update inspect.js
tmorehouse Apr 13, 2019
9c61065
Update bv-modal.js
tmorehouse Apr 13, 2019
c1522ee
Update bv-modal.js
tmorehouse Apr 13, 2019
bf4c3fb
Update bv-modal.js
tmorehouse Apr 13, 2019
eb3bb4e
Update bv-modal.js
tmorehouse Apr 13, 2019
4a7aeae
Update bv-modal.js
tmorehouse Apr 13, 2019
053bb6e
Update bv-modal.js
tmorehouse Apr 13, 2019
2c73da2
Create bv-modal.spec.js
tmorehouse Apr 13, 2019
706dff2
Update bv-modal.spec.js
tmorehouse Apr 13, 2019
2d940e1
Update bv-modal.spec.js
tmorehouse Apr 13, 2019
e5bd56b
Update bv-modal.spec.js
tmorehouse Apr 13, 2019
ce4c291
Update inspect.spec.js
tmorehouse Apr 13, 2019
445afa5
Update bv-modal.spec.js
tmorehouse Apr 13, 2019
89e03a4
Update bv-modal.spec.js
tmorehouse Apr 13, 2019
df80f5f
Update bv-modal.spec.js
tmorehouse Apr 13, 2019
11759ed
Update bv-modal.spec.js
tmorehouse Apr 13, 2019
8eca789
Update bv-modal.spec.js
tmorehouse Apr 13, 2019
9651ef4
Update bv-modal.spec.js
tmorehouse Apr 13, 2019
3f70bf3
Update bv-modal.js
tmorehouse Apr 13, 2019
c2d6543
Update bv-modal.js
tmorehouse Apr 13, 2019
98a16f0
Update bv-modal.spec.js
tmorehouse Apr 13, 2019
a1cc6ba
Update bv-modal.js
tmorehouse Apr 13, 2019
a8773ff
Update bv-modal.spec.js
tmorehouse Apr 13, 2019
16fb6f4
Update bv-modal.js
tmorehouse Apr 13, 2019
ba861e5
Merge branch 'dev' into tmorehouse/modal-scoped
jacobmllr95 Apr 13, 2019
795cac6
Update README.md
tmorehouse Apr 13, 2019
77c59e5
Update README.md
tmorehouse Apr 13, 2019
53a900b
Merge branch 'dev' into tmorehouse/modal-scoped
tmorehouse Apr 13, 2019
ceb52fb
Update README.md
tmorehouse Apr 13, 2019
b3912fe
Update README.md
tmorehouse Apr 13, 2019
642b12e
Update README.md
tmorehouse Apr 13, 2019
e5a4d65
Update README.md
tmorehouse Apr 13, 2019
af31069
Update README.md
tmorehouse Apr 13, 2019
68b7f02
Update README.md
tmorehouse Apr 13, 2019
cc4392f
Update README.md
tmorehouse Apr 13, 2019
304d50e
Update README.md
tmorehouse Apr 13, 2019
522af26
Merge branch 'dev' into tmorehouse/modal-scoped
tmorehouse Apr 14, 2019
acfc75f
Update package.json
tmorehouse Apr 14, 2019
672d71a
Update README.md
tmorehouse Apr 14, 2019
5c2402d
Update modal.js
tmorehouse Apr 14, 2019
a2f2df3
Update README.md
tmorehouse Apr 14, 2019
82710a0
Update README.md
tmorehouse Apr 14, 2019
c4ad6a3
Update README.md
tmorehouse Apr 14, 2019
f0d5ec6
Update README.md
tmorehouse Apr 14, 2019
fca92c1
Update README.md
jacobmllr95 Apr 14, 2019
aa71a77
Final tweaks
jacobmllr95 Apr 14, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
306 changes: 291 additions & 15 deletions src/components/modal/README.md

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions src/components/modal/helpers/bv-modal-event.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import { defineProperties, readonlyDescriptor } from '../../../utils/object'
class BvModalEvent extends BvEvent {
constructor(type, eventInit = {}) {
super(type, eventInit)
// Freeze our new props as readonly, but leave them enumerable.
// Freeze our new props as readonly, but leave them enumerable
defineProperties(this, {
modalId: readonlyDescriptor(),
trigger: readonlyDescriptor()
})
}

cancel() /* istanbul ignore next */ {
// Backwards compatibility for 1.x BootstrapVue
// Backwards compatibility for BootstrapVue 1.x
warn('b-modal: evt.cancel() is deprecated. Please use evt.preventDefault().')
this.preventDefault()
}
Expand All @@ -27,8 +27,8 @@ class BvModalEvent extends BvEvent {
}
}

// Named Exports
// Named exports
export { BvModalEvent }

// Default Export
// Default export
export default BvModalEvent
292 changes: 292 additions & 0 deletions src/components/modal/helpers/bv-modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
/**
* Plugin for adding `$bvModal` property to all Vue instances
*/

import Vue from 'vue'
import BModal, { props as modalProps } from '../modal'
import warn from '../../../utils/warn'
import { getComponentConfig } from '../../../utils/config'
import { inBrowser, hasPromiseSupport } from '../../../utils/env'
import { isUndefined, isFunction } from '../../../utils/inspect'
import { assign, keys, omit, defineProperty, defineProperties } from '../../../utils/object'

// --- Constants ---

// Base Modal Props that are allowed
// Some may be ignored or overridden on some message boxes
// Prop ID is allowed, but really only should be used for testing
// We need to add it in explicitly as it comes from the IdMixin
const BASE_PROPS = keys(omit(modalProps, ['busy', 'lazy', 'noStacking', 'visible']))
BASE_PROPS.push('id')

// Fallback event resolver (returns undefined)
const defaultResolver = bvModalEvt => {}

// Map prop names to modal slot names
const propsToSlots = {
msgBoxContent: 'default',
title: 'modal-title',
okTitle: 'modal-ok',
cancelTitle: 'modal-cancel'
}

// --- Utility methods ---

// Utility methods that produce warns
const noPromises = () => {
/* istanbul ignore else */
if (hasPromiseSupport) {
return false
} else {
warn('this.$bvModal: Requires Promise support for Message Boxes')
return true
}
}

const notClient = method => {
/* istanbul ignore else */
if (inBrowser) {
return false
} else {
warn('this.$bvModal$: Message Boxes can not be called during SSR')
return true
}
}

// Method to filter only recognized props that are not undefined
const filterOptions = options => {
return BASE_PROPS.reduce((memo, key) => {
if (!isUndefined(options[key])) {
memo[key] = options[key]
}
return memo
}, {})
}

// Private read-only descriptor helper
const privateRODescriptor = () => ({ enumerable: false, configurable: false, writable: false })

// Create a private sub-component that extends BModal
// which self-destructs after hidden
// @vue/component
const MsgBox = Vue.extend({
name: 'BMsgBox',
extends: BModal,
destroyed() {
// Make sure we not in document any more
if (this.$el && this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el)
}
},
mounted() {
// Self destruct handler
const handleDestroy = () => {
const self = this
this.$nextTick(() => {
// In a `setTimeout()` to release control back to application
setTimeout(() => self.$destroy(), 0)
})
}
// Self destruct if parent destroyed
this.$parent.$once('hook:destroyed', handleDestroy)
// Self destruct after hidden
this.$once('hidden', handleDestroy)
// Self destruct on route change
/* istanbul ignore if */
if (this.$router && this.$route) {
const unwatch = this.$watch('$router', handleDestroy)
this.$once('hook:beforeDestroy', unwatch)
}
// Should we also self destruct on parent deactivation?

// Show the `MsgBox`
this.show()
}
})

// Method to generate the on-demand modal message box
// Returns a promise that resolves to a value returned by the resolve
const asyncMsgBox = (props, $parent, resolver = defaultResolver) => {
if (noPromises() || notClient()) {
// Should this throw an error?
/* istanbul ignore next */
return
}
// Create an instance of `MsgBox` component
const msgBox = new MsgBox({
// We set parent as the local VM so these modals can emit events on
// the app `$root`, as needed by things like tooltips and popovers
// And it helps to ensure `MsgBox` is destroyed when parent is destroyed
parent: $parent,
// Preset the prop values
propsData: {
// Add in optional global config for modal defaults before the following
// TODO: Add in specific defaults for `BMsgBox`
// Will need special handling as most defaults are undefined
...filterOptions(getComponentConfig('BModal') || {}),
// Defaults that user can override
hideHeaderClose: true,
hideHeader: !(props.title || props.titleHtml),
// Add in (filtered) user supplied props
...omit(props, ['msgBoxContent']),
// Props that can't be overridden
lazy: false,
busy: false,
visible: false,
noStacking: false,
noEnforceFocus: false
}
})

// Convert certain props to scoped slots
keys(propsToSlots).forEach(prop => {
if (!isUndefined(props[prop])) {
// Can be a string, or array of VNodes.
// Alternatively, user can use HTML version of prop to pass an HTML string.
msgBox.$slots[propsToSlots[prop]] = props[prop]
}
})

// Create a mount point (a DIV)
const div = document.createElement('div')
document.body.appendChild(div)

// Return a promise that resolves when hidden, or rejects on destroyed
return new Promise((resolve, reject) => {
let resolved = false
msgBox.$once('hook:destroyed', () => {
if (!resolved) {
/* istanbul ignore next */
reject(new Error('BootstrapVue MsgBox destroyed before resolve'))
}
})
msgBox.$on('hide', bvModalEvt => {
if (!bvModalEvt.defaultPrevented) {
const result = resolver(bvModalEvt)
// If resolver didn't cancel hide, we resolve
if (!bvModalEvt.defaultPrevented) {
resolved = true
resolve(result)
}
}
})
// Mount the `MsgBox`, which will auto-trigger it to show
msgBox.$mount(div)
})
}

// BvModal instance property class
class BvModal {
constructor(vm) {
// Assign the new properties to this instance
assign(this, { _vm: vm, _root: vm.$root })
// Set these properties as read-only and non-enumerable
defineProperties(this, {
_vm: privateRODescriptor(),
_root: privateRODescriptor()
})
}

// --- Instance methods ---

// Show modal with the specified ID args are for future use
show(id, ...args) {
if (id && this._root) {
this._root.$emit('bv::show::modal', id, ...args)
}
}

// Hide modal with the specified ID args are for future use
hide(id, ...args) {
if (id && this._root) {
this._root.$emit('bv::hide::modal', id, ...args)
}
}

// TODO: Could make Promise versions of above that first checks
// if modal is in document (by ID) and if not found reject
// the Promise. Otherwise waits for hide/hidden event and
// then resolves returning the `BvModalEvent` object
// (which contains the details)

// The following methods require Promise support!
// IE 11 and others do not support Promise natively, so users
// should have a Polyfill loaded (which they need anyways for IE 11 support)

// Opens a user defined message box and returns a promise
msgBox(content, options = {}, resolver) {
if (!content || noPromises() || notClient() || !isFunction(resolver)) {
// Should this throw an error?
/* istanbul ignore next */
return
}
const props = {
...filterOptions(options),
msgBoxContent: content
}
return asyncMsgBox(props, this._vm, resolver)
}

// Open a message box with OK button only and returns a promise
msgBoxOk(message, options = {}) {
// Pick the modal props we support from options
const props = {
...options,
// Add in overrides and our content prop
okOnly: true,
okDisabled: false,
hideFooter: false,
msgBoxContent: message
}
return this.msgBox(message, props, bvModalEvt => {
// Always resolve to true for OK
return true
})
}

// Open a message box modal with OK and CANCEL buttons
// and returns a promise
msgBoxConfirm(message, options = {}) {
// Set the modal props we support from options
const props = {
...options,
// Add in overrides and our content prop
okOnly: false,
okDisabled: false,
cancelDisabled: false,
hideFooter: false
}
return this.msgBox(message, props, bvModalEvt => {
const trigger = bvModalEvt.trigger
return trigger === 'ok' ? true : trigger === 'cancel' ? false : null
})
}
}

// Method to install `$bvModal` VM injection
const install = _Vue => {
if (install._installed) {
// Only install once
/* istanbul ignore next */
return
}
install._installed = true

// Add our instance mixin
_Vue.mixin({
beforeCreate() {
// Because we need access to `$root` for `$emits`, and VM for parenting,
// we have to create a fresh instance of `BvModal` for each VM
this._bv__modal = new BvModal(this)
}
})

// Define our read-only `$bvModal` instance property
defineProperty(_Vue.prototype, '$bvModal', {
get() {
return this._bv__modal
}
})
}

export default install
Loading