ああ、これらのモーダルウィンドウまたはVueJsのレンダリング関数が好きな理由

みなさんこんにちは!

私の最初の出版物は不快な後味でした。 私はこの誤解を正すことを約束し、あなたの法廷にVueJsに関する最初の記事のレッスンを提供します。 役に立つことを願っています。 多くの考え、多くの経験。 私は他の人の記事、教訓に沿って勉強してきました。 知識を共有する時です。

そして、モーダルウィンドウを作成します。 はい、彼らは再びです。 しかし、私の最初の(私のものではない)出版物で説明されているほど単純ではありません。



Vueの多くはすでに作成されています。 みんな使用しました。 そして、明らかに、楽器の所有権の特定のレベル(この場合はVue )に達したら、すぐに自転車を作りたいと思うでしょう。 そして、私は規則の例外でもありませんでした。



利用可能なすべてのモーダルコンポーネントのうち、主にこれを使用しました-Vuedals

しかし、私はそれをアップグレードすることにしました。 原則として、 EventBusとウィンドウの開閉に関連するイベントの相互作用のみがコアから残りました。 メインコンポーネントが書き直されてラッパーコンテナーになり、新しいコンポーネント(モーダルウィンドウ自体)が追加されました。

しかし、まず最初に。 そして、記事は非常に大きくなり、誰がマスターするでしょう、そのハンサム:)



基本的に、すべての例のモーダルウィンドウは、このスタイルで呼び出されます。



<template> <button @click="visible = true">Show modal</button> <modal :show="visible"> <div>  </div> </modal> </template> <script> export default { data() { return { visible: false } } </script>
      
      





すべてが美しいようです。 しかし!



このアプローチの欠点は何ですか?



まず、モーダルウィンドウテンプレートは親コンポーネント内にあり、親コンポーネントと呼ばれます。 また、ウィンドウコンテキストは親から分離されていません。 必ずしも便利で必要なわけではありません。

次に、同じウィンドウが複数の場所で使用されている場合、コードを複製する必要があります。 ぶんぶんうんじゃない!



第三に、これはおそらく最も重要な欠点です-モーダルウィンドウは、ページまたは他のVueコンポーネント内でのみ使用できますが、 VuexRouterなどの場所では使用できません。 たとえば、何らかのイベントでルーターまたはストアからモーダルログイン/登録ウィンドウを呼び出す必要があります。 例はミリオンによって与えることができます。



したがって、パラメーターを使用して関数を呼び出し、次の形式の「生の」コンポーネントを渡すことでウィンドウを開閉するときに、 Vuedalsが使用するアプローチ-



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





または外部からインポートした本格的なものが好きでした。

より多くの制御、再利用オプション、およびそのようなウィンドウをほぼどこからでも呼び出すことができます。



一般的にはこのように見えますが、 たとえば 、アプリケーションにインポートしてルートAppコンポーネントに貼り付けるModalWrapperコンポーネントがあります。 下のどこか。



次に、どこでもthis。$ Modals.openメソッド({component:SimpleModal、title: 'Simple modal'})を呼び出します。ここで、 ModalWrapperでレンダリングされるモーダルウィンドウを表示および表示するウィンドウとコンポーネントの設定を転送します。



ウィンドウでのすべての操作中に発生するイベントの束があり、これらのイベントはEventBusを使用して制御され、それらを聞いて何らかの形で反応することができます。



これは、情報を学習しやすくするための入力です。 基本的に、この記事はVueの初心者向けです。 しかし、私はいくつかの興味深い魅力的な瞬間があることを願っています。



途中でいくつかのコードとコメントをスローします。 最後に例とソースへのリンクがあります。



さて、メインファイルから始めましょう-



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 }
      
      







その中で、モーダルプリモーダルウィンドウに使用されるコンポーネントをインポートします。



  1. ModalWrapper.js-ウィンドウを表示するための一般的なラッパー
  2. Modal.js-モーダルウィンドウ自体のコンポーネント。 オリジナルのVuedalにはありません。 直接使用する必要はありません。 いずれにせよ、それは内部で機能します。 劇に沿って、あなたは耳でこの気味を見るでしょう、そして、なぜ私がそれを加えたのかが明らかになるでしょう。
  3. Bus.js-ラッパーコンポーネント( ModalWrapper )、モーダルウィンドウ( Modal )、およびVueJsアプリケーション間の通信用のEventBus


Bus.jsコードをすぐに提供し、そこで何が起こるかを説明します。 前述したように、 EventBusは元の状態のままになりました。



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();
      
      







ここでは、イベント( $ on )をサブスクライブし、イベントをトリガー( $ emit )できるEventBusのシングルトンインスタンスを作成します。 説明する特別なものはないと思います。 EventBusはコールバックを収集し、必要に応じてそれらを呼び出します。 さらに、すべてのコンポーネントをどのように接続するかが明確に理解できるようになります。



そして今、index.js



ここで、デフォルトのデフォルトのインストール関数をエクスポートします-ウィンドウをアプリケーション( Vue.use()を使用)とModalWrapperModalおよびBusコンポーネントに接続します。 ブラウザのスクリプトタグを介してVueUniversalModalを接続する場合、ページ上のグローバルVueJが接続されている場合、コンポーネントをアクティブにします。



そして順番に:



$モーダル
 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); } } });
      
      







ここでは、 $ modalsと呼ばれるVueのプロトタイプを介し )グローバルVueJsインスタンスにフックします。



作成されたメソッド(アプリケーションの開始直後に開始)で、 EventBusを、 開いた (ウィンドウを開く)、 閉じた (ウィンドウを閉じる)、 破棄した (ウィンドウがない、 ModalWrapperを削除する)イベントに署名します。 これらのイベントが発生すると、EventBusmodals: openmodals:closed、およびmodals:destroyedイベント$ modalsコンポーネントに出力します。 VueJsが個人的に利用可能な場合はいつでも、これらのイベントを聞くことができます。



一般に、一部は完全にオプションであったため、最初はこれらの通信の半分を捨てたいと思っていましたが、考えた後、それを残しました。 たとえば、モーダルウィンドウの統計情報を収集する場合に役立ちます。 そして初心者は、この一見混乱した$ on$ emit-呼び出しで、おそらく自分で何かを理解するでしょう。



さらにこれ。$ On ...



ここでは、 $ modalsコンポーネント自体によるイベントリスナーnewclosedismiss有効にし ます 。 これらのイベントが発生すると、 $ modalsコンポーネントの対応するメソッドが呼び出されます。 ウィンドウを開く( open )、閉じる( close )、キャンセル( dismiss )します。



ご覧のように、ウィンドウを閉じるには、2つの方法があります-同じオペラから閉じる(キャンセルまたはブルジョア-キャンセル-)および閉じる (閉じる)。 違いは、 closeを介してモーダルウィンドウを閉じると 、新しいモーダルウィンドウのオプションにフックするonCloseコールバック関数にデータを転送できることです(後で検討します)。



そして、実際には$ modalsコンポーネントのopenclose 、およびdismissメソッド 。 それらの中で、 ModalWrapper新しいclosedismissイベントであるEventBusを実行します。 そこで、すべての魔法が起こります。



そして、index.jsファイルのインストール機能の最後。



 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); }); } });
      
      





ここでは、 作成されたメソッドをVueミックスインを介してすべてのVueコンポーネントに拡張し、起動時にコンポーネントがモーダル:newmodals:closeおよびmodals:dismissイベントリッスンできるようにし、EventBusを介してそれらが呼び出されると、対応するイベントをModalWrapperで再度開始ます



モーダルウィンドウを制御するには、これらすべての地獄のような呼び出しがここで必要です。 また、 openclose、およびdismissイベントをトリガーするための4つのオプションを提供します



アプリケーションでモーダルウィンドウを呼び出す最初の方法:



 this.$modals.open(options)
      
      





2番目の方法:

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





第三:



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





4番目(このメソッドの場合、Bus.jsをインポートする必要がありますが、これにより、 Vueコンポーネントからではなく、任意のスクリプトからウィンドウを呼び出すことができます):



 Bus.$emit('new', options)
      
      





まあ、 近い 、アナロジーで却下します。



ここで、彼らが言うように-「味と色へ」。



次の患者はModal.jsか、簡単な方法を探していません



より楽しいコードが追加されます。 最近、コンポーネント(標準形式とjsx形式の両方)でレンダリング関数を使用しています 。 それらがレンダリングのためのより多くの機会を提供することに気づいたとき、私はどこでもそれを使い始めました。 レンダリング関数を使用すると、 Javascriptのすべての機能に加えて、 vNodeを備えたクールな内部VueJsキッチンが具体的なボーナスをもたらします。 それらが登場したとき、私はそれらをどういうわけか質問し、 テンプレートテンプレートにコンポーネントを描画し、何のために描画し続けました。 しかし、今、私は犬が埋葬されている場所を知っています。



モーダルは、モーダルウィンドウ自体をレンダリングする本格的なコンポーネントです。 入力パラメーターの束があります。



modal.js-小道具
  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 } },
      
      







彼は、コード内のすべてのパラメーターについてコメントして、より明確にしました。 そして、フットクロスが機能しないように、コードをスポイラーにスローします。 たくさんのテキスト。



次:



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





最初に、 SVG形式でレンダリングする機能コンポーネントの形式で十字アイコンをインポートします。



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', } }) ]) } }
      
      







なぜ私は彼女に気付いたのか、私も知らない



次に、 nameパラメーター-これは標準的な動きです。



しかし、ここではcomponentName偶然ではありません。 レンダリング時にModalWrapperでさらに必要になります。



そして、計算されたパラメーターpropsData



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





ここはもっと複雑です。



しかし、問題はこれです。 元のVuedalでは、すべてのウィンドウは上記の4つのメソッドを使用して呼び出されます。 それぞれに、ウィンドウに表示するコンポーネントとウィンドウパラメーターを渡す必要があります(これらはすべて、受信モーダルパラメーターに含まれ、さらにいくつかの新しいパラメーターが追加されます)。 また、アプリケーションの異なる部分で同じウィンドウを実行したい場合は、毎回ウィンドウパラメーター(ディメンション、その他の設定)を渡します。 これも重複です。 はい、長い間混乱しないでください。 そして、私たちプログラマーは、生き物はコアで非常に怠け者です。 したがって、このモーダルコンポーネントが作成されました。



これで、次のようなモーダルウィンドウコンポーネントを作成できます。



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>
      
      







つまり、標準コンポーネントです。 Modalvu-modal )によってラップされます。 このvu-modalに、必要なパラメーターを渡します。 これらは、このウィンドウのデフォルト値になります。



そして、このウィンドウを次のように呼び出します。



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





必要なウィンドウ設定のデフォルト値はすべて、 vu-modalラッパーから取得した同じSimpleModalコンポーネントから自動的に取得されます。 必要な設定でウィンドウコンポーネントを作成した後、設定を気にせずにどこでも使用できます。 さらに、これらのデフォルト値を再割り当てする必要がある場合、このウィンドウが呼び出されるときに必要な値を指定します。



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





これで、 中央のパラメーターがウィンドウテンプレートで指定されたデフォルトのパラメーターSimpleModalに置き換わります。



つまり、パラメーターをマージ(マージ)するときの優先順位は次のとおりです。





低いほど重要です。



そのため、 vu-modalコンポーネントの計算されたpropsDataプロパティは、 vu-modalのこのインスタンスが何らかのコンポーネントのラッパー( SimpleModal )であるかどうかを考慮して、正しい入力パラメーター( props )を返します。

これを行うには、 ModalWrapperでウィンドウをレンダリングするときに、このウィンドウのコンポーネントがvu-modalでラップされている場合、愚かな支柱vModalという名前で転送します。そうでない場合は、通常の支柱を転送します。



ただし、コンポーネントがvu-modalにラップされている場合、 小道具をレンダリングするときにこの親コンポーネント( SimpleModal )に分類されるため、親コンポーネントにvModalという名前の入力パラメーターがあるかどうかを確認します。 存在する場合、これらの値を取得します。それ以外の場合は、標準propsを取得します。



そして、 これをチェックしません$ Parent。$ Options.propsData 、しかしこれ。$ Parent。$ Vnode.data.propsは、親コンポーネントのpropsに vModalパラメーターがない場合、これをレンダリングするときにこれを見ることができるvModal$ parent。$ vnode.data.propsです。 これには、例外なく渡したすべてのパラメーターが含まれます。 そして、それらはフィルタリングされ、余分な部分は破棄されます。



このコードをもう一度教えてください。これは、思考と混同しないように小さいものです。



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





今、おそらくすべてが明確ではない。 多くの情報がありますが、多くのレッスンのようにすべてが標準ではありません。 私はこの種の記事を初めて書いていますが、どのように教えるのが最善かはまだ明確ではありません。 おそらく多くの人がVueの内部を掘り下げていますが、それについて書く人はほとんどいません。 長い間、私はそのような瞬間に関する情報を探しました。 私は何かを見つけ、残りを自分で選びました。 そして、私はそのようなことについて話したいです。



しかし、 ModalWrapper分解すると、より明確になります。 そこで、ステンドプロップを作成して窓に送ります。



さて、 モーダルコンポーネントのレンダリング機能( vu-modal )が残っています



レンダリング(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 ]) }
      
      







ここでは珍しいことは何もないようです。



最初に、前述のpropsDataの計算値からすべてのパラメーターを引き出します。



dismissableプロパティがtrueの場合、 dismissイベント(ウィンドウのキャンセル)をトリガーする十字ボタンを表示します



ヘッダーを形成します-headerという名前のスロット( this。$ Slots.header )がvu-modalに渡される場合、 titleプロパティが渡される場合、このスロットを描画します-それを印刷します。そうでなければ、 ヘッダーをまったく表示しません。



デフォルトのスロット( this。$ Slots.default )の内容で本体ブロックを形成します。

そして、 フッター - フッタースロット( this。$ Slots.footer )が渡された場合

次に、 transformcss-プロパティの正しい値を決定しますウィンドウのtranslate(x、y) 。 つまり、ウィンドウに転送されたプロパティに応じて、パラメーターXおよびYが使用されます。 そして、レンダリング時の適切な配置のために、この変換をメインウィンドウdivにレンダリングします。



さて、全体をレンダリングし、同時に必要なクラスを計算します。



さらに、メインのdiv.vu-modal__cmp onClick-ハンドラーでevent.stopPropagation()を使用して電話切ると、各ウィンドウをラップし、クリックに応答するdiv (マスク)のクリックがアクティブにならないように、ウィンドウのクリックが上にポップアップしませんそしてdismissを呼び出します。 そうでない場合、マスクのこのdismissイベントが発生し、ウィンドウが閉じます。

うわー!



最後のコンポーネントはModalWrapperです



modal-wrapper.jsを開始します


インポート './style.scss'

'./utils/bus'からのバスのインポート

ModalCmpを「./modal」からインポートします



エクスポートのデフォルト{

名前:「vu-modal-wrapper」、

データ(){

return {

モーダル:[]

}

}、

マウント済み(){

if(typeof document!== 'undefined'){

document.body.addEventListener( 'keyup'、this.handleEscapeKey)

}

}、

破壊された(){

if(typeof document!== 'undefined'){

document.body.removeEventListener( 'keyup'、this.handleEscapeKey)

}

}、



スタイルを接続します。

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; } } } }
      
      







モーダル配列には、現在アクティブなウィンドウを保存します。



さて、 ModalWrapperコンポーネントをマウントおよび削除するとき、 キーアップハンドラーをウィンドウウィンドウがある場合)で切断しhandleEscapeKeyメソッドを起動します



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







次に、 Escキーが押され、ウィンドウがあり、現在の(最後に起動された)ウィンドウがある場合、 escapableプロパティはtrueになり 、この現在のウィンドウを閉じるdismissメソッドを実行します。



さて、おそらく最も興味深いことが始まっています。 コードで何が起きているのかを説明しようと思いますが、多分それは改善されるでしょう。



ModalWrapperを作成するとき、 EventBusからのイベントリスニングを有効にます 。 前述の$ modalsメソッドで実行されるもの:



作成済み()
 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    });
      
      







その他のイベント:



閉じて却下
 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   });
      
      







次にメソッド:



スプライス
 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,  ,     } } }
      
      







閉じる
 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); },
      
      







dismissメソッドでは、すべてがcloseメソッドに似ています



却下する
 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); },
      
      







計算されたプロパティ:



計算された
 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'); } } }
      
      







さて、最後の機能、今私のお気に入り:



レンダリング(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 ]) } // ! :)
      
      







ここに物語があります。長い話。次回は必要に応じて短くしてみます。



最後に、情報をよりよく理解するためにウィンドウを開くためのコードの例を示します。



 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!') } })
      
      





そして、たとえば、ウィンドウのボタンを使用して、そこにデータを転送するcloseを開始します。



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





この場合、閉じる前に、onClose コールバックが起動されます。



同様に、onDismissは機能します。このコールバックは、たとえばフッターの[キャンセル]ボタンをクリックしたときに、クロスボタン、ウィンドウマスクをクリックするか、ウィンドウ内で直接クリックすることでトリガーされます。



 this.$modals.dismiss()
      
      





その他。レンダリング機能について。それらは確かにtemplateのコードほど見栄えがよくありません。しかし、それらでは、テンプレートは不可能なことを行うことができますが、おそらく、松葉杖とレンダリング関数で得られるよりもはるかに多くのコードでできます。そして、で塗料成分であれば、レンダリングは非常に慎重に変更された関数props-DATA-依存レンダリングし、そこからプロパティを、アップデートの無限のサイクル(へ行くにはそうでないリスク更新)コンポーネント。



おそらく今のところすべて。そして、彼は束を打ち切りました。しかし、私は全体の動きを説明したかった。次の記事は短くなります。しかし、私が話したいニュアンスもあります。



そして、このラインに住んでいたみんなに感謝します!



PS ここにウィンドウの例があります。ソースコードを含むGithubへのリンクもあります。ロシア語でもドキュメントを追加します。



All Articles