Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Possible to open modal from outside the vue component

Is it possible to call a method from outside the component to make the component reuseable?

Right now I add my button to open the modal in a template slot:

index.php

<modal>
    <template slot="button">
        <button class="btn">Open modal</button>
    </template>
    Some modal text
</modal>

Modal.vue

<template>
    <div>
        <div @click="showModal"><slot name="button"></slot></div>
        <div v-if="showingModal"><slot></slot></div>
    </div>
</template>

<script>
    export default {

        data () {
            return {
                showingModal: false,
            }
        },

        methods: {
            showModal() {
                this.showingModal = true;
            },
        }
    }
</script>

I feel like there is an better option but I can't figure it out.

like image 825
twoam Avatar asked Apr 25 '18 16:04

twoam


2 Answers

Yes,you can call a method from outside the component!

Parent component

<template>
 <div>
   <modal ref="modal"></modal>
   <button @click="openModal">Open Modal</button>
 </div>
</template>

<script>
  import modal from './child.vue'
  export default {
    components: { modal }
    methods: {
     openModal() { this.$refs.modal.show() }//executing the show method of child
    }
  }
</script>

Child component

<template>
  <div v-if="showModal">
    <div id="modal">
      <p>Hello i am a modal
      </p>
      <button @click="hide">Close</button>
    </div> 
  </div>
</template>

<script>
 export default {
   data() {
     return {
      showModal: false
     }
   },
   methods: {
     show() {
      this.showModal = true
     },
     hide(){
      this.showModal = false
     }
   }
 }
</script>

See it in action here

like image 51
Roland Avatar answered Oct 09 '22 03:10

Roland


Put you modal component instance into Vue.prototype, then invoke show/hide at anywhere you can access Vue instance context.

Below is one demo:

let vm = null // the instance for your Vue modal
let timeout = null //async/delay popup

const SModal = {
  isActive: false,

  show ({
    delay = 500,
    message = '',
    customClass = 'my-modal-class'
  } = {}) {
    if (this.isActive) {
      vm && vm.$forceUpdate()
      return
    }

    timeout = setTimeout(() => {
      timeout = null

      const node = document.createElement('div')
      document.body.appendChild(node)
      let staticClass = ''
      vm = new this.__Vue({
        name: 's-modal',
        el: node,
        render (h) { // uses render() which is a closer-to-the-compiler alternative to templates
          return h('div', {
            staticClass,
            'class': customClass,
            domProps: {
              innerHTML: message
            }
          })
        }
      })
    }, delay)

    this.isActive = true
  },
  hide () {
    if (!this.isActive) {
      return
    }

    if (timeout) {
      clearTimeout(timeout)
      timeout = null
    } else {
      vm.$destroy()
      document.body.removeChild(vm.$el)
      vm = null
    }

    this.isActive = false
  },

  __Vue: null,
  __installed: false,
  install ({ $my, Vue }) {
    if (this.__installed) { return }
    this.__installed = true
    $my.SModal = SModal // added your SModal object to $my
    this.__Vue = Vue //get the Vue constructor
  }
}

let installFunc = function (_Vue, opts = {}) {
  if (this.__installed) {
    return
  }
  this.__installed = true
  const $my = {
    'memo': 'I am a plugin management.'
  }
  if (opts.plugins) {
    Object.keys(opts.plugins).forEach(key => {
      const p = opts.plugins[key]
      if (typeof p.install === 'function') {
        p.install({ $my, Vue: _Vue })
      }
    })
  }
  _Vue.prototype.$my = $my
}

Vue.use(installFunc, {
  plugins: [SModal]
})

app = new Vue({
  el: "#app",
  data: {
    'test 1': 'Cat in Boots'
  },
  methods: {
    openModal: function () {
      this.$my.SModal.show({'message':'test', 'delay':1000})
    },
    closeModal: function () {
      this.$my.SModal.hide()
    }
  }
})
.my-modal-class {
  position:absolute;
  top:50px;
  left:20px;
  width:100px;
  height:100px;
  background-color:red;
  z-index:9999;
}
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<div id="app">
    <button @click="openModal()">Open Modal!!!</button>
    <button @click="closeModal()">Close Modal!!!</button>
</div>

Rough Steps for vue-cli project:

In ./plugins/SModal.js (follow the tutorial in official document to create one vue instance then add it into the document.body):

let vm = null // the instance for your Vue modal
let timeout = null //async/delay popup

const SModal = {
  isActive: false,

  show ({
    delay = 500,
    message = '',
    customClass = ''
  } = {}) {
    if (this.isActive) {
      vm && vm.$forceUpdate()
      return
    }

    timeout = setTimeout(() => {
      timeout = null

      const node = document.createElement('div')
      document.body.appendChild(node)

      vm = new this.__Vue({
        name: 's-modal',
        el: node,
        render (h) { // uses render() which is a closer-to-the-compiler alternative to templates
          return h('div', {
            staticClass,
            'class': props.customClass
          })
        }
      })
    }, delay)

    this.isActive = true
  },
  hide () {
    if (!this.isActive) {
      return
    }

    if (timeout) {
      clearTimeout(timeout)
      timeout = null
    } else {
      vm.$destroy()
      document.body.removeChild(vm.$el)
      vm = null
    }

    this.isActive = false
  },

  __Vue: null,
  __installed: false,
  install ({ $my, Vue }) {
    if (this.__installed) { return }
    this.__installed = true
    $my.SModal = SModal // added your SModal object to $my
    this.__Vue = Vue //get the Vue constructor
  }
}

export default SModal

As the official document said, A Vue.js plugin should expose an install method. The method will be called with the Vue constructor as the first argument, along with possible options

In install.js (also you can move this method into main.js based on your design):

// loop all plugins under the folder ./plugins/, then install it.
export default function (_Vue, opts = {}) {
  if (this.__installed) {
    return
  }
  this.__installed = true
  const $my = {
    'memo': 'I am a plugin management.'
  }
  if (opts.plugins) {
    Object.keys(opts.plugins).forEach(key => {
      const p = opts.plugins[key]
      if (typeof p.install === 'function') {
        p.install({ $my, Vue: _Vue })
      }
    })
  }

  _Vue.prototype.$my = $my
}

In main.js (finally uses your plugins):

import install from './install'
import * as plugins from './plugins'

Vue.use({ install }, {
  plugins
})

Finally in your view/component, you can show/hide your modal like this:

this.$my.SModal.show()
this.$my.SModal.hide()
like image 31
Sphinx Avatar answered Oct 09 '22 02:10

Sphinx