Rendering Vue Slots content to an iframe
by Andy You
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<label for="">
Change this to Change IFrame content <br>
<input type="text" v-model="dynamicPart">
</label>
<div>
<i-frame class="my-frame">
<test-child title="This is a Title from a Prop!">
<p>This was rendered in an iframe!</p>
<p>Content of Input: <strong>{{dynamicPart}}</strong></p>
<button @click="show = true">Click me to see events work, too!</button>
</test-child>
</i-frame>
<div v-if="show">
<p>This paragraph appeared because of the button click in the iframe! </p>
</div>
</div>
</div>
SCSS
body {
font-family: sans-serif;
}
.my-frame {
border: 1px solid #CCC;
box-shadow: 0 0 3px 2x rgba(0,0,0,.3);
margin: 20px auto;
height: 200px;
width: 95%;
}
Babel + JSX
Vue.component('i-frame', {
render(h) {
return h('iframe', {
on: { load: this.renderChildren }
})
},
beforeUpdate() {
//freezing to prevent unnessessary Reactifiation of vNodes
this.iApp.children = Object.freeze(this.$slots.default)
},
methods: {
renderChildren() {
const children = this.$slots.default
const body = this.$el.contentDocument.body
const el = document.createElement('DIV') // we will mount or nested app to this element
body.appendChild(el)
const iApp = new Vue({
name: 'iApp',
//freezing to prevent unnessessary Reactifiation of vNodes
data: { children: Object.freeze(children) },
render(h) {
return h('div', this.children)
},
})
iApp.$mount(el) // mount into iframe
this.iApp = iApp // cache instance for later updates
}
}
})
Vue.component('test-child', {
template: `<div>
<h3>{{ title }}</h3>
<p>
<slot/>
</p>
</div>`,
props: ['title'],
methods: {
log: _.debounce(function() {
console.log('resize!')
}, 200)
},
mounted() {
this.$nextTick(() => {
const doc = this.$el.ownerDocument
const win = doc.defaultView
win.addEventListener('resize', this.log)
})
},
beforeDestroy() {
const doc = this.$el.ownerDocument
const win = doc.defaultView
win.removeEventListener('resize', this.log)
}
})
new Vue({
el: '#app',
data: {
dynamicPart: 'InputContent',
show: false,
}
})