如何在Vue的iframe中渲染子组件?

时间:2019-11-06 03:19:22

标签: javascript vue.js iframe

因此,我想向用户显示电子邮件发送之前的预览。为了避免样式从父页面泄漏到预览中,我决定使用iframe。我希望在用户输入表单详细信息时实时更新预览。

我将如何在iframe中渲染组件,以便在更新父窗体时自动更新其道具?这是我到目前为止的代码:

这是html:

<template>
    <div id="confirmation">
        <h2>Give a gift</h2>
        <form @submit.prevent="checkout()">
            <div class="date-section">
                <label class="wide">Send</label>
                <input type="radio" name="sendLater" v-model="sendLater" required :value="false">
                <span>Now</span>
                <input type="radio" name="sendLater" v-model="sendLater" required :value="true">
                <span style="margin-right: 5px;">Later: </span>
                <date-picker :disabled="!sendLater" v-model="date" lang="en" />
            </div>
            <div>
                <label>Recipient Email</label>
                <input type="email" class="custom-text"  v-model="form.email" required>
            </div>
            <div>
                <label>Recipient Name</label>
                <input type="text" class="custom-text"  v-model="form.name" required>
            </div>
            <div>
                <label>Add a personal message</label>
                <textarea v-model="form.message" />
            </div>
            <p class="error" v-if="error">Please enter a valid date.</p>
            <div class="button-row">
                <button class="trumpet-button" type="submit">Next</button>
                <button class="trumpet-button gray ml10" type="button" @click="cancel()">Cancel</button>
            </div>
        </form>
        <iframe id="preview-frame">
            <preview-component :form="form" :sender-email="senderEmail" :term="term" />
        </iframe>
    </div>
</template>

这是js(注意:PreviewComponent是将在iframe中呈现的实际预览):

export default {
    name: 'ConfirmationComponent',
    components: {
        DatePicker,
        PreviewComponent
    },
    props: {
        term: {
            required: true,
            type: Object
        }
    },
    data() {
        return {
            form: {
                name: null,
                email: null,
                message: null,
                date: null
            },
            date: null,
            sendLater: false,
            error: false
        }
    },
    computed: {
        senderEmail() {
            // utils comes from a separate file called utils.js
            return utils.user.email || ''
        }
    },
    watch: {
        'form.name'(val) {
            this.renderIframe()
        },
        'form.email'(val) {
            this.renderIframe()
        }
    },
    methods: {
        renderIframe() {
            if (this.form.name != null && this.form.email != null) {
                console.log('rendering iframe')
                // not sure what to do here......
            }
        }        
    }
}

我已经尝试过各种方法,但是似乎最难的是正确设置预览组件的道具。大家能提供的任何帮助将不胜感激。

1 个答案:

答案 0 :(得分:0)

因此,如评论之一中所述,Vuex对此非常有效。

我还最终创建了一个自定义“ IFrame”组件,该组件将呈现iframe中其插槽内的所有内容。

这是我的Vuex商店:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export const store = new Vuex.Store({
    state: {
        form: {
            name: null,
            email: null,
            message: null
        },
        senderEmail: null,
        term: null,
        styles: null
    },
    mutations: {
        updateForm(state, form) {
            state.form = form
        },
        updateEmail(state, email) {
            state.senderEmail = email
        },
        updateTerm(state, term) {
            state.term = term
        },
        stylesChange(state, styles) {
            state.styles = styles
        }
    }
})

我的IFrame组件:

import Vue from 'vue'
import { store } from '../../store'

export default {
    name: 'IFrame',
    data() {
        return {
            iApp: null,

        }
    },
    computed: {
        styles() {
            return this.$store.state.styles
        }
    },
    render(h) {
        return h('iframe', {
            on: {
                load: this.renderChildren
            }
        })
    },
    watch: {
        styles(val) {
            const head = this.$el.contentDocument.head

            $(head).html(val)
        }
    },
    beforeUpdate() {
        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',
                store,
                data() {
                    return {
                        children: Object.freeze(children)
                    }
                },
                render(h) {
                    return h('div', this.children)
                }
            })

            iApp.$mount(el)

            this.iApp = iApp
        }
    }
}

最后,这是将数据从ConfirmationComponent传递到PreviewComponent的方式:

export default {
    name: 'ConfirmationComponent',
    mounted() {
        this.$store.commit('updateEmail', this.senderEmail)
        this.$store.commit('updateTerm', this.term)
    },
    watch: {
        'form.name'(val) {
            this.updateIframe()
        },
        'form.email'(val) {
            this.updateIframe()
        }
    },
    methods: {
        updateIframe() {
            this.$store.commit('updateForm', this.form)
        }
    }
}

最后是实际的PreviewComponent:

import styles from '../../../templates/styles'

export default {
    name: 'PreviewComponent',
    mounted() {
        this.$store.commit('stylesChange', styles)
    },
    computed: {
        redemption_url() {
            return `${window.config.stitcher_website}/gift?code=`
        },
        custom_message() {
            if (this.form.message) {
                let div = document.createElement('div')

                div.innerHTML = this.form.message

                let text = div.textContent || div.innerText || ''

                return text.replace(/(?:\r\n|\r|\n)/g, '<br>')
            }
            return null
        },
        form() {
            return this.$store.state.form
        },
        term() {
            return this.$store.state.term
        },
        senderEmail() {
            return this.$store.state.senderEmail
        }
    }
}

希望这会对某人有所帮助。