📜 ⬆️ ⬇️

Oh, those modal windows or why I loved the render functions in VueJs

Hello to all!
My first posting went off with an unpleasant aftertaste. I promised to correct this misunderstanding and present your first article-lesson on VueJs to your court. I hope it will be useful. A lot of thoughts, a lot of experience too. All my life I learn from other people's articles, lessons. It's time to share knowledge too.
And we will create modal windows. Yes, again they are. But not as simple as described in my first (not my) publication.

A lot of them already created for Vue . Used everyones. And apparently, when you reach a certain level of ownership of the instrument (in this case, Vue ), you immediately want to make a bicycle, but of course with your own bells and whistles, like, so much the coolest, etc. And I did not become an exception to the rule.

Of all the available modal components, I used mostly this one - Vuedals .
But I decided to upgrade it. In principle, only the EventBus remains from the base and the interaction of events related to the opening-closing of windows. The main component was rewritten and became a container wrapper and a new component was added - the modal window itself.
But first things first. And the article will turn out to be very rather big, who will master, that handsome :)

Most modal windows in all examples are called in this style:
')
<template> <button @click="visible = true">Show modal</button> <modal :show="visible"> <div>  </div> </modal> </template> <script> export default { data() { return { visible: false } } </script> 

It seems everything is beautiful. But!

What I see the disadvantages of this approach.

First, the modal window template is located inside the parent component, where we call it. And the window context is not isolated from the parent. It is not always convenient and necessary for me.
Secondly, if the same window is used in several places, it is necessary to duplicate the code. What is not good!

Thirdly, and that is probably the most important drawback - we can use the modal window only inside pages or other Vue components, but in places like Vuex , Router , and generally we cannot in any scripts. For example, I need to call the modal login / registration window from the router or from the store at some event. Examples are milen.

Therefore, the approach used in Vuedals , when we open / close windows by calling a function with parameters and passing a “raw” component, of the form -

 { name: 'simple-modal', props: ['test'], template: "<div>{{test}}</div>" } 

or full-fledged, which we imported from the outside, I was more to my liking.
More control, reusability and call such a window can be almost anywhere.

In general, it looks like this; we have the ModalWrapper component, which we import into the application and insert, for example, into the root App component. Somewhere below.

Then we call the this. $ Modals.open ({component: SimpleModal, title: 'Simple modal'}) method anywhere, where we pass the settings of our window and the component that we will show and see our modal window, which is rendered in ModalWrapper .

There are a lot of events that occur during all manipulations with windows, these events are controlled using EventBus , and you can listen to them and somehow react.

This is input data to make information easier to learn. Basically, the article is more for newbies in Vue. But I hope there will be a couple of interesting and sophisticated moments.

I will throw pieces of code and comment on what is happening. There is a link to examples and sources at the end.

Well, and let's start with the main file -

index.js
 import Bus from './utils/bus'; import ModalWrapper from './modal-wrapper'; import Modal from './modal' const VuModal = {} VuModal.install = (Vue) => { Vue.prototype.$modals = new Vue({ name: '$modals', created() { Bus.$on('opened', data => { this.$emit('modals:opened', data); }); Bus.$on('closed', data => { this.$emit('modals:closed', data); }); Bus.$on('destroyed', data => { this.$emit('modals:destroyed', data); }); this.$on('new', options => { this.open(options); }); this.$on('close', data => { this.close(data); }); this.$on('dismiss', index => { this.dismiss(index || null); }); }, methods: { open(options = null) { Bus.$emit('new', options); }, close(data = null) { Bus.$emit('close', data); }, dismiss(index = null) { Bus.$emit('dismiss', index); } } }); Vue.mixin({ created() { this.$on('modals:new', options => { Bus.$emit('new', options); }); this.$on('modals:close', data => { Bus.$emit('close', data); }); this.$on('modals:dismiss', index => { Bus.$emit('dismiss', index); }); } }); } if (typeof window !== 'undefined' && window.Vue) { window.Vue.use(VuModal); } export default VuModal; export { ModalWrapper, Modal, Bus } 


In it, we import the components used for our modal-pre-modal windows:

  1. ModalWrapper.js - a generic wrapper for outputting our windows
  2. Modal.js is the modal window component itself. It is not in the original Vuedals . It is not necessary to use it directly. He works in any case under the hood. Further along the play you will see this trick with your ears and it will become clear why I added it.
  3. Bus.js - EventBus for communication between a wrapper component ( ModalWrapper ), modal windows ( Modal ) and our VueJs application.

Immediately bring the code Bus.js and describe what is happening there. As mentioned earlier, EventBus left as it is in the original.

Bus.js
 let instance = null; class EventBus { constructor() { if (!instance) { this.events = {}; instance = this; } return instance; } $emit(event, message) { if (!this.events[event]) return; const callbacks = this.events[event]; for (let i = 0, l = callbacks.length; i < l; i++) { const callback = callbacks[i]; callback.call(this, message); } } $on(event, callback) { if (!this.events[event]) this.events[event] = []; this.events[event].push(callback); } } export default new EventBus(); 


Here we create a singleton instance of EventBus , which can subscribe to events ( $ on ) and trigger ( $ emit ) events. I think there is nothing special to explain here. EventBus collects callbacks and when they need to call. Further along the way, it will be seen and understandable how it connects all our components.

And now by index.js

Here we export the default default function install - to connect our windows to the application (using Vue.use () ) and the ModalWrapper , Modal and Bus components. Well, when you connect VueUniversalModal via a script tag in the browser, activate our component if global VueJs is connected on the page.

And in order:

$ modals
 Vue.prototype.$modals = new Vue({ name: '$modals', created() { Bus.$on('opened', data => { this.$emit('modals:opened', data); }); Bus.$on('closed', data => { this.$emit('modals:closed', data); }); Bus.$on('destroyed', data => { this.$emit('modals:destroyed', data); }); this.$on('new', options => { this.open(options); }); this.$on('close', data => { this.close(data); }); this.$on('dismiss', index => { this.dismiss(index || null); }); }, methods: { open(options = null) { Bus.$emit('new', options); }, close(data = null) { Bus.$emit('close', data); }, dismiss(index = null) { Bus.$emit('dismiss', index); } } }); 


Here we hook to the global VueJs (via prototype ) instance of Vue under the name $ modals .

In his created method (which will start immediately after launching the application), we sign our EventBus to the events opened (window opening), closed (window closing) and destroyed (no windows, remove ModalWrapper ). When these events occur, EventBus will emit modals: opened , modals: closed and modals: destroyed events into the $ modals component. We can listen to these events wherever VueJs is available.

In general, I first wanted to throw half of these communications, as some are not at all obligatory, but I left it after thinking. It may be useful to collect some statistics on modal windows, for example. Yes, and beginners may understand something for themselves in this seemingly porridge from $ on , $ emit - calls.

Next this. $ On ...

Here we enable listening for new , close , dismiss events by the $ modals component itself . When these events occur, the corresponding methods of the $ modals component are called . Which in turn open ( open ), close ( close ) and cancel ( dismiss ) the window.

As you can see, we have two ways to close the window - dismiss (cancel or bourgeois - cancel - from the same opera) and close . The difference is that when you close a modal window through close , we can pass data to the onClose callback function (we will look at later), which we cling to the options of our new modal window.

And the actual methods open , close and dismiss of the $ modals component. We run them through EventBus , the events new , close and dismiss in our ModalWrapper . There already will be all the magic.

And the last thing to install is the index.js file.

 Vue.mixin({ created() { this.$on('modals:new', options => { Bus.$emit('new', options); }); this.$on('modals:close', data => { Bus.$emit('close', data); }); this.$on('modals:dismiss', index => { Bus.$emit('dismiss', index); }); } }); 

Here we extend the created method through Vue -mixin to all components of Vue , in which we start listening on components by the modals: new , modals: close and modals: dismiss events and when they are called, via EventBus we also trigger the corresponding events in ModalWrapper .

All these hellish calls here are needed to manage our modal windows. And they give us 4 options for running open , close and dismiss events .

The first way to call our modal window in the application:

 this.$modals.open(options) 

The second way:
 this.$modals.$emit('new', options) 

Third:

 this.$emit('modals:new', options) 

And the fourth (for this method we need to import Bus.js, but this gives us the opportunity to call the window not from the Vue component, but from any script):

 Bus.$emit('new', options) 

Well, close , dismiss by analogy.

Here, as they say - "the taste and color."

The next patient is Modal.js or we are not looking for easy ways.


Then the code will go more fun. Recently, in my components I use render -functions (both standard and in jsx- format). I started using it everywhere when I realized that they provide more opportunities for rendering. With render functions, all the power of Javascript plus the cool internal VueJs kitchen with vNode gives tangible bonuses. At the moment of their appearance, I somehow looked at them obliquely, thought it was necessary and continued to draw the components in the template template . But now I know where the dog is buried.

Modal is a full-fledged component that renders the modal window itself. He has a bunch of input parameters:

modal.js - props
  props: { title: { //   type: String, default: '' }, className: { //  css-    type: String, default: '' }, isScroll: { //  ,   -       type: Boolean, default: false }, escapable: { // dismiss()    Esc- type: Boolean, default: false }, dismissable: { // dismiss()               () type: Boolean, default: true }, fullscreen: { //   type: Boolean, default: false }, isTop: { //      type: Boolean, default: false }, isBottom: { //      type: Boolean, default: false }, isLeft: { //       type: Boolean, default: false }, isRight: { //       type: Boolean, default: false }, center: { //    type: Boolean, default: false }, size: { //   () type: String, default: 'md' }, bodyPadding: { // padding-  body - ,     type: Boolean, default: true } }, 


In the code I commented on all the parameters so that it would be more visible. And I throw the code in the spoiler so that the footcloth does not turn out. So a lot of text.

Farther:

 import CloseIcon from './close-icon' export default { name: 'vu-modal', componentName: 'vu-modal', ... } 

First, we import the cross-icon as a functional component, which renders it in the SVG format.

close-icon.js
 export default { name: 'close-icon', functional: true, render(h) { return h('svg', { attrs: { width: '12px', height: '12px', viewBox: '0 0 12 12', xmlSpace: 'preserve' } }, [ h('line', { attrs: { x1: 1, y1: 11, x2: 11, y2: 1 }, style: { strokeLinecap: 'round', strokeLinejoin: 'round', } }), h('line', { attrs: { x1: 1, y1: 1, x2: 11, y2: 11 }, style: { strokeLinecap: 'round', strokeLinejoin: 'round', } }) ]) } } 


Why did I think so, I do not even know.

Next parameter name - well, this is a standard move.

But componentName is no accident here. We will need it further, in ModalWrapper when rendering.

And the propsData calculated parameter:

 export default { ... computed: { propsData() { return (this.$parent.$vnode.data.props && this.$parent.$vnode.data.props.vModal) ? this.$parent.$vnode.data.props.vModal : this.$props } } } 

Here is more difficult.

And the thing is this. In the original Vuedals, all windows are also invoked using the 4 methods described above. In each of them we have to pass the component that we want to show in the window and the window parameters (they all now are in the incoming Modal parameters and plus some new ones are added). And if we want to run the same window in different parts of the application, we each time pass the window parameters (dimensions, other settings). Which again is a duplication. Yes, and not confused for long. And we, programmers, are extremely lazy creatures at their core. Therefore, this component Modal was created.

Now we can create a modal window component, for example, like this:

simple-modal.js
 <template lang="html"> <vu-modal title="Test modal" :isScroll="true" size="p50" :center="true" :escapable="true"> <div slot="header" style="display: flex; justify-content: left; align-items: center;" v-if="header"> <div style="padding-left: 10px">Simple modal</div> </div> <div> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quia consequuntur minus sint quidem ut tenetur dicta sunt voluptates numquam. Eum totam ex maxime aut recusandae quae laborum fugit ab autem.</p> </div> <div slot="footer"> <button class="uk-button uk-button-smaller uk-button-primary" @click="close">Cancel</button> </div> </vu-modal> </template> <script> export default { name: 'simple-modal', props: { lorem: { type: Boolean, default: true } } }; </script> 


That is, the standard component. Wrapped by our Modal ( vu-modal ). To this vu-modal we pass the parameters we need. They will be the default values ​​for this window.

And now we call this window like this:

 import SimpleModal from './modals/simple' ... this.$modals.open({ component: SimpleModal }) 

All the default settings for the window we need are taken from the same SimpleModal component taken from the vu-modal wrapper. We once created a window component with the settings we need and then use it anywhere without worrying about the settings. Moreover, if we need to reassign those default values, we specify the values ​​we need when we call this window:

 import SimpleModal from './modals/simple' ... this.$modals.open({ component: SimpleModal, center: false }) 

Now the center parameter will replace the default parameter specified in the window template - SimpleModal .

That is, the priority is when merging (merging) parameters:


The lower, the more important.

So, the calculated propsData property in the vu-modal component returns us the correct input parameters ( props ), taking into account whether this instance of vu-modal is a wrapper in some component ( SimpleModal ) or not.
To do this, when rendering a window in ModalWrapper , if the component of this window is wrapped in vu-modal, we will transmit the prompt props under the name vModal , otherwise we will transmit the usual props .

But as in the case when the component is wrapped in vu-modal , when rendering the props will get into this parent component ( SimpleModal ), we check if the parent component has an input parameter named vModal . If there is, then we take these values, otherwise the standard props- s.

And we check not with this. $ Parent. $ Options.propsData , but with this. $ Parent. $ Vnode.data.props , because if the parent component does not have a vModal parameter in the props , then during rendering this vModal we can only see with this. $ parent. $ vnode.data.props . This includes all the parameters we passed without exception. And then it is filtered and superfluous discarded.

I will cite once again this piece of code, it is small, so as not to knock it off with a thought.

 export default { ... computed: { propsData() { return (this.$parent.$vnode.data.props && this.$parent.$vnode.data.props.vModal) ? this.$parent.$vnode.data.props.vModal : this.$props } } } 

Now perhaps not quite everything is clear. There is a lot of information, and not everything is standard, as in many lessons. I am writing this kind of article for the first time; it’s not quite clear how to teach better. In the guts of Vue many probably dig, but very few people write about it. Himself for a long time looking for info on such moments. I found something, the rest was picking myself. And I want to talk about such things.

But it will become more clear when we disassemble ModalWrapper . There we will form and send all the props to our windows.

Well, the render function of our Modal ( vu-modal ) component remains:

render (h) "
 render(h) { const { dismissable, title, isScroll, fullscreen, isTop, isBottom, isLeft, isRight, center, size, className, bodyPadding } = this.propsData const closeBtn = dismissable ? h('div', { class: 'vu-modal__close-btn', on: { click: () => {this.$modals.dismiss()} } }, [h(CloseIcon)]) : null const headerContent = this.$slots.header ? this.$slots.header : title ? h('span', {class: ['vu-modal__cmp-header-title']}, title) : null const header = headerContent ? h('div', { class: ['vu-modal__cmp-header'] }, [ headerContent ]) : null const body = h('div', { class: ['vu-modal__cmp-body'], style: { overflowY: isScroll ? 'auto' : null, padding: bodyPadding ? '1em' : 0 } }, [ this.$slots.default ]) const footer = this.$slots.footer ? h('div', { class: ['vu-modal__cmp-footer'] }, [ this.$slots.footer ]) : null let style = {} let translateX = '-50%' let translateY = '0' if(center) { translateX = '-50%' translateY = '-50%' } if(isRight || isLeft) { translateX = '0%' } if((isTop || isBottom) && !isScroll && !center) { translateY = '0%' } style.transform = `translate(${translateX}, ${translateY})` return h('div', { style, class: ['vu-modal__cmp', { 'vu-modal__cmp--is-fullscreen': fullscreen, 'vu-modal__cmp--is-center': center, 'vu-modal__cmp--is-top': isTop && !isScroll && !center, 'vu-modal__cmp--is-bottom': isBottom && !isScroll && !center, 'vu-modal__cmp--is-left': isLeft, 'vu-modal__cmp--is-right': isRight }, isScroll && fullscreen && 'vu-modal__cmp--is-scroll-fullscreen', isScroll && !fullscreen && 'vu-modal__cmp--is-scroll', !fullscreen && `vu-modal__cmp--${size}`, className ], on: {click: (event) => {event.stopPropagation()}} }, [ closeBtn, header, body, footer ]) } 


There seems to be nothing unusual.

First, we pull out all our parameters from the previously described computed propsData value.

Display the cross-button, which causes the dismiss event (cancellation of the window) if the dismissable property is true .

We form the header - if a slot with the name header ( this. $ Slots.header ) is transferred to our vu-modal , if we have the title property passed, we display it, otherwise we don’t show the header at all.

We form the body block with the contents of the default slot ( this. $ Slots.default ).
And after the footer - if the slot is passed to the footer ( this. $ Slots.footer ).
Next, we define the correct values ​​for the css property of the transform: translate (x, y) of our window. Namely, the parameters X and Y , depending on the transferred properties to our window. And then we render this transform when rendering to the main window div for correct positioning.

Well and we render all this business, in passing calculating the necessary class.

And plus we hang on the main div.vu-modal__cmp onClick handler, with event.stopPropagation () , so that the click on the window does not pop up higher, so as not to activate the click on the div (mask), which wraps every window and that responds to the click and causes dismiss . Otherwise, this dismiss event will work on the mask and our window will close.
Ufff!

The final component is ModalWrapper


Start modal-wrapper.js

import './style.scss'
import Bus from './utils/bus'
import ModalCmp from './modal'

export default {
name: 'vu-modal-wrapper',
data () {
return {
modals: []
}
},
mounted () {
if (typeof document! == 'undefined') {
document.body.addEventListener ('keyup', this.handleEscapeKey)
}
},
destroyed () {
if (typeof document! == 'undefined') {
document.body.removeEventListener ('keyup', this.handleEscapeKey)
}
},

We connect our styles:
style.scss
 body.modals-open { overflow: hidden; } .vu-modal { &__wrapper { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 5000; overflow-x: hidden; overflow-y: auto; transition: opacity .4s ease; } &__mask { background-color: rgba(0, 0, 0, .5); position: absolute; width: 100%; height: 100%; overflow-y: scroll; &--disabled { background-color: rgba(0, 0, 0, 0); } } &__cmp { display: flex; flex-direction: column; border-radius: 0px; background: #FFF; box-shadow: 3px 5px 20px #333; margin: 30px auto; position: absolute; left: 50%; transform: translateX(-50%); width: 650px; &--is-center { margin: auto; top: 50%; } &--is-scroll { max-height: 90%; } &--is-scroll-fullscreen { max-height: 100%; } &--is-fullscreen { width: 100%; min-height: 100%; margin: 0 0; } &--is-bottom { bottom: 0; } &--is-top { top: 0; } &--is-right { right: 0; margin-right: 30px; } &--is-left { left: 0; margin-left: 30px; } &--xl { width: 1024px; } &--lg { width: 850px; } &--md { width: 650px; } &--sm { width: 550px; } &--xs { width: 350px; } &--p50 { width: 50%; } &--p70 { width: 70%; } &--p90 { width: 90%; } &-body { padding: 1em; &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); } &::-webkit-scrollbar-thumb { background-color: darkgrey; outline: 1px solid slategrey; } } &-header { user-select: none; border-bottom: 1px solid #EEE; padding: 1em; text-align: left; &-title { font-size: 16px; font-weight: 800; } } &-footer { border-top: solid 1px #EEE; user-select: none; padding: 1em; text-align: right; } } &__close-btn { user-select: none; position: absolute; right: 12px; top: 5px; line { stroke: grey; stroke-width: 2; } &:hover { cursor: pointer; line { stroke: black; } } } } 


In the modals array , we will store our windows that are currently active.

Well, when mounting and removing our ModalWrapper component, we hang the keyup handler on the window (if the window is there), which runs the handleEscapeKey method:

handleEscapeKey
 handleEscapeKey(e) { if (e.keyCode === 27 && this.modals.length) { if (!this.modals.length) return; if (this.current.options.escapable) this.dismiss(); } } 


Which, in turn, if the Esc key is pressed and there is a window (a) and the escapable property of true (last launched) window, runs the dismiss method, which closes this current window itself.

Well, perhaps the most interesting began. I'll try to describe what is happening right in the code, maybe it will be better.

When creating our ModalWrapper, we enable wiretapping of events from the EventBus . The ones that run in the $ modals methods described earlier:

created ()
 created() { Bus.$on('new', options => { //  ,   const defaults = { //    ,      Modal title: '', dismissable: true, center: false, fullscreen: false, isTop: false, isBottom: false, isLeft: false, isRight: false, isScroll: false, className: '', size: 'md', escapable: false, bodyPadding: true }; //     ! //   props-   .    . // rendered     ,      options,   Modal (vu-modal)   let instance = {} // ,       modals let rendered if(options.component.template) { //    ""   template,   Modal    ,    .     .    .    . rendered = false } else { //    render  ,    componentOptions rendered = options.component.render.call(this, this.$createElement) } //             componentName  Modal.      'vu-modal',     Modal (vu-modal) if(rendered && rendered.componentOptions && rendered.componentOptions.Ctor.extendOptions.componentName === 'vu-modal') { //      props-,       template-  vu-modal const propsData = rendered.componentOptions.propsData instance = { isVmodal: true, //      ,      options: Object.assign(defaults, propsData, options) //       } } else { instance = { isVmodal: false, //       vu-modal options: Object.assign(defaults, options) //      } } rendered = null this.modals.push(instance); //   modals Bus.$emit('opened', { //       EventBus c    index: this.$last, //    ,  instance //   }); this.body && this.body.classList.add('modals-open'); //    body    }); 


Further events:

close and dismiss
 Bus.$on('close', data => { // ,            close let index = null; if (data && data.$index) index = data.$index; //      if (index === null) index = this.$last; //   ,    this.close(data, index); //   close     }); Bus.$on('dismiss', index => { //     dismiss,      if (index === null) index = this.$last; //  ,   this.dismiss(index); //   dismiss   }); 


Now the methods are:

splice
 methods: { splice(index = null) { //   ,    if (index === -1) return; if (!this.modals.length) return; if (index === null) //    ,    this.modals.pop(); else this.modals.splice(index, 1); if (!this.modals.length) { //    this.body && this.body.classList.remove('modals-open'); //  body   'modals-open' Bus.$emit('destroyed'); //   ,  EventBus,  ,     } } } 


close
 doClose(index) { //      modals ,    splice,   if (!this.modals.length) return; if (!this.modals[index]) return; this.splice(index); }, // ,    ,  close.            close(data = null, index = null) { if (this.modals.length === 0) return; let localIndex = index; //   index  ,      ,           .      ,  -  if (index && typeof index === 'function') { localIndex = index(data, this.modals); } if (typeof localIndex !== 'number') localIndex = this.$last; //     // , ,      callback- onClose,     ,    //     onClose - ,    false,     if (localIndex !== false && this.modals[localIndex]) { if(this.modals[localIndex].options.onClose(data) === false) { return } } Bus.$emit('closed', { //   'closed'      index: localIndex, //   instance: this.modals[index], //   data //  ,   }); //  ,     ,     modals,     this.doClose(localIndex); }, 


In the dismiss method , everything is similar to the close method :

dismiss
 dismiss(index = null) { let localIndex = index; if (index && typeof index === 'function') localIndex = index(this.$last); if (typeof localIndex !== 'number') localIndex = this.$last; if (this.modals[localIndex].options.onDismiss() === false) return; Bus.$emit('dismissed', { index: localIndex, instance: this.modals[localIndex] }); this.doClose(localIndex); }, 


Calculated properties:

computed
 computed: { current() { //   return this.modals[this.$last]; }, $last() { //   ()  return this.modals.length - 1; }, body() { //  body,  ,  /  'modals-open' if (typeof document !== 'undefined') { return document.querySelector('body'); } } } 


Well, the last function, now my favorite:

render (h)
 render(h) { //   ,    if(!this.modals.length) { return null }; //     let modals = this.modals.map((modal, index) => { //    let modalComponent //      if(modal.isVmodal) { //        Modal (vu-modal) //       props-,    vModal c    vu-modal   props-   ,    modalComponent = h(modal.options.component, { props: Object.assign({}, {vModal: modal.options}, modal.options.props) }) } else { //    Modal          ,     props-,  ,     modalComponent = h(ModalCmp, { props: modal.options }, [ h(modal.options.component, { props: modal.options.props }) ]) } //      ,     -    css //  dismissable   true,  ,   return h('div', { class: ['vu-modal__mask', {'vu-modal__mask--disabled': index != this.$last }], on: {click: () => {modal.options.dismissable && this.dismiss()}}, key: index }, [ modalComponent //     ]) }) //        return h('div', { class: 'vu-modal__wrapper', }, [ modals ]) } // ! :) 


Here is a story. Long story. The next time I will try in short, if necessary.

Well, in the end, a sample code for opening a window so that the information is better understood.

 this.$modals.open({ title: 'Modal with callbacks', component: Example, props: { lorem: true, test: true }, onDismiss() { console.log('Dismiss ok!') } onClose(data) { if(data.ended) return false console.log('Ok!') } }) 

And we run close , for example, by a button in our window, we transfer data there:

 this.$modals.close({ ended: true }) 

In this case, our onclose callback is launched before closing .

By analogy, onDismiss works . This callback starts when you click on the button-cross, window mask or directly in our window, for example, when clicking in the footer on the button 'Cancel':

 this.$modals.dismiss() 

And further.Concerning render- functions. They certainly do not look as presentable as the code in the template . But they can do what is impossible in the template , or perhaps, but with crutches and an order of magnitude more code than is obtained in the render function. And if the paint components in the render function which is very carefully modify them props- and data- properties from which render dependent, otherwise the risk to go into an endless cycle of updates ( the update ) component.

Probably for now. And so a bunch of dashed off. But I wanted to describe the whole movement. The next couple of articles will be shorter. But, also with the nuances that I want to talk about.

And thanks to everyone who lived to this line!

PS Here are examples of windows. There is also a link to Github with sources. I add documentation, in Russian too.

Source: https://habr.com/ru/post/350232/


All Articles