Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Open a VueJS component on a new window

I have a basic VueJS application with only one page. It's not a SPA, and I do not use vue-router.

I would like to implement a button that when clicked executes the window.open() function with content from one of my Vue Components.

Looking at the documentation from window.open() I saw the following statement for URL:

URL accepts a path or URL to an HTML page, image file, or any other resource which is supported by the browser.

Is it possible to pass a component as an argument for window.open()?

like image 897
user3142 Avatar asked Apr 04 '18 17:04

user3142


1 Answers

I was able to use some insights from an article about Portals in React to create a Vue component which is able to mount its children in a new window, while preserving reactivity! It's as simple as:

<window-portal>
  I appear in a new window!
</window-portal>

Try it in this codesandbox!

The code for this component is as follows:

<template>
  <div v-if="open">
    <slot />
  </div>
</template>

<script>
export default {
  name: 'window-portal',
  props: {
    open: {
      type: Boolean,
      default: false,
    }
  },
  data() {
    return {
      windowRef: null,
    }
  },
  watch: {
    open(newOpen) {
      if(newOpen) {
        this.openPortal();
      } else {
        this.closePortal();
      }
    }
  },
  methods: {
    openPortal() {
      this.windowRef = window.open("", "", "width=600,height=400,left=200,top=200");
      this.windowRef.addEventListener('beforeunload', this.closePortal);
      // magic!
      this.windowRef.document.body.appendChild(this.$el);
    },
    closePortal() {
      if(this.windowRef) {
        this.windowRef.close();
        this.windowRef = null;
        this.$emit('close');
      }
    }
  },
  mounted() {
    if(this.open) {
      this.openPortal();
    }
  },
  beforeDestroy() {
    if (this.windowRef) {
      this.closePortal();
    }
  }
}
</script>

The key is the line this.windowRef.document.body.appendChild(this.$el); this line effectively removes the DOM element associated with the Vue component (the top-level <div>) from the parent window and inserts it into the body of the child window. Since this element is the same reference as the one Vue would normally update, just in a different place, everything Just Works - Vue continues to update the element in response to databinding changes, despite it being mounted in a new window. I was actually quite surprised at how simple this was!

like image 200
Alex Van Liew Avatar answered Oct 16 '22 19:10

Alex Van Liew