javascript - 将模态框放置在 Vue.js 的子组件中

标签 javascript css vue.js modal-dialog

背景

通常,在 Vue.js 组件中使用模态时,通常创建一个可重用的 modal 组件,然后使用子组件中的事件控制该组件的状态。

例如,考虑以下代码:

App.vue

<div id="app">

    <!-- Main Application content here... -->

    <!-- Place any modal components here... -->
    <modal ref="ContactForm"></modal>

</div>

ChildComponent.Vue

要从子组件打开模式,我们只需触发以下事件:

bus.$emit('open-modal', 'ContactForm');

注释 1: bus 是一个单独的 Vue 实例,它允许在所有组件之间触发事件,无论它们之间的关系如何。

注2:我故意省略了我的modal组件代码,因为它与问题无关。

问题

虽然上述工作绝对正常,但有一个关键问题......

为了将modal添加到我的应用程序中,我必须将all<,而不是将modal组件放置在引用它的子组件中/strong> App.vue 中的模态,因为这确保它们尽可能位于 DOM 树的最高位置(以确保它们出现在所有内容之上)。

因此,我的 App.vue 最终可能会看起来像这样:

<div id="app">

    <!-- Main Application content here... -->

    <!-- Place any modal components here... -->
    <modal ref="SomeModal1"></modal>
    <modal ref="SomeModal2"></modal>
    <modal ref="SomeModal3"></modal>
    <modal ref="SomeModal4"></modal>
    <modal ref="SomeModal5"></modal>
    <modal ref="SomeModal6"></modal>
    <modal ref="SomeModal7"></modal>

</div>

如果能够将 modal 组件放置在子组件的 DOM 中,会更加简洁。

但是,为了确保模式出现在 DOM 内的所有内容之上(特别是具有设置 z-index 的元素),我看不到上述内容的替代方案...

任何人都可以建议一种方法来确保我的模式能够正常工作,即使它们放置在子组件中也是如此?

潜在的解决方案

我确实考虑过以下解决方案,但它看起来很脏......

  1. 触发开放模式事件
  2. 将相关modal组件移动到父App.vue组件
  3. 显示模态框

其他信息

如果上述内容不清楚,我试图避免在我的 App.vue 组件中定义所有模式,并允许在任何子组件中定义我的模式组件。

目前我无法执行此操作的原因是模态的 HTML 必须尽可能出现在 DOM 树中的较高位置,以确保它们出现在所有内容之上。

最佳答案

这就是我所说的:

在助手中创建一个 addProgrammaticComponent 函数,遵循以下原则:

import Vue from 'vue';

export function addProgrammaticComponent(parent, component, dataFn, extraProps = {}) {
  const ComponentClass = Vue.extend(component);
  // this can probably be simplified. 
  // It largely depends on how much flexibility you need in building your component
  // gist being: dynamically add props and data at $mount time
  const initData = dataFn ? dataFn() : {};
  const data = {};
  const propsData = {};
  const propKeys = Object.keys(ComponentClass.options.props || {});

  Object.keys(initData).forEach((key) => {
    if (propKeys.includes(key)) {
      propsData[key] = initData[key];
    } else {
      data[key] = initData[key];
    }
  });

  // add store props if you use Vuex

  // extraProps can include dynamic methods or computed, which will be merged
  // onto what has been defined in the .vue file

  const instance = new ComponentClass({
    /* store, */ data, propsData, ...extraProps,
  });

  instance.$mount(document.createElement('div'));

  // generic helper for passing data to/from parent:
  const dataSetter = (data) => {
    Object.keys(data).forEach((key) => {
        instance[key] = data[key];
    });
  };

  // set unwatch on parent as you call it after you destroy the instance
  const unwatch = parent.$watch(dataFn || {}, dataSetter);

  return {
    instance,
    update: () => dataSetter(dataFn ? dataFn() : {}),
    dispose: () => {
        unwatch();
        instance.$destroy();
    },
  };
}

...现在,你在哪里使用它:

Modal.vue 是一个典型的 modal component ,但您可以通过关闭 EscDel 按键等来增强它...

您要在哪里打开模式:

 methods: {
   openFancyModal() {
     const component = addProgrammaticComponent(
       this,
       Modal,
       () => ({
         title: 'Some title',
         message: 'Some message',
         show: true,
         allowDismiss: true,
         /* any other props you want to pass to the programmatic component... */
       }),
     );

     document.body.appendChild(component.instance.$el);

     // here you have full access to both the programmatic component 
     // as well as the parent, so you can add logic

     component.instance.$once('close', component.dispose);

     // if you don't want to destroy the instance, just hide it
     component.instance.$on('cancel', () => {
       component.instance.show = false;
     });

     // define any number of events and listen to them: i.e:
     component.instance.$on('confirm', (args) => {
       component.instance.show = false;
       this.parentMethod(args);
     });
   },
   /* args from programmatic component */
   parentMethod(args) {
     /* you can even pass on the component itself, 
        and .dispose() when you no longer need it */
   }
 }    

话虽这么说,没有人会阻止您创建多个 Modal/Dialog/Popup 组件,要么是因为它可能有不同的模板,要么因为它可能具有重要的附加功能,这会污染通用 Modal 组件(即:LoginModal.vueAddReportModal.vueAddUserModal.vueAddCommentModal.vue)。

这里的重点是:它们不会添加到应用程序(DOM)中,直到您实际 $mount 它们。您没有将标记放置在父组件中。您可以在开头 fn 中定义要传递哪些 Prop 、要听什么等等...

除了在父级上触发的 unwatch 方法之外,所有事件都绑定(bind)到programmaticComponent 实例,因此没有垃圾。

这就是我所说的,在您打开 DOM 之前,没有真正的隐藏模式实例潜伏在 DOM 上。

甚至不能说这种方法一定比其他方法更好(但它有一些优点)。从我的观点来看,它只是受到 Vue 的灵活性和核心原则的启发,这显然是可能的,并且它允许灵活地 .$mount 并将任何组件(不仅是模态)部署到任何组件上或从任何组件上处理组件。

当您需要从同一复杂应用程序的多个 Angular 落打开同一组件并且您认真对待 DRY 时,它尤其有用。

参见vm.$mount文档。

关于javascript - 将模态框放置在 Vue.js 的子组件中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57964041/

相关文章:

javascript - 如何在 Vue Js 中使用字符串变量模式创建输入名称字段?

javascript - 如何从 json 中获取与 javascript 中下拉列表中的用户输入相匹配的键值?

php - 莱格伯排序的问题

javascript - 在 Handlebars [object Object] 错误中访问 JSON 对象

javascript - 如何更改 CircularProgress 条的颜色

css - Bootstrap 2.3.2 css 在 Safari 中打破分页前(?)

javascript - 如何在 Vue 中有条件地添加一个类?

javascript - 如何根据另一个日期限制 jQuery-ui 日期选择器的最小和最大日期

jquery - Cordova - 不要在最大比例 > 1 时在 inAppBrowser 中缩放或缩放页眉页脚

javascript - Vue-strap 单选按钮不适用于 vue.js