What is the best way to display a child component inside an iframe using Vue.js?

Looking to provide a live preview of an email before sending it out, I've opted to use an iframe to contain the preview and prevent style leaks. The goal is for the preview to update dynamically as the user fills out form details.

How can I display a component within an iframe with automatic prop updates whenever the parent form changes? Below is the current code in place:

This is the HTML structure:

<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>

Below is the JavaScript where PreviewComponent handles the actual rendering in the 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......
            }
        }        
    }
}

I have tried various approaches, but setting the correct props for the preview-component seems particularly challenging. Any assistance or guidance on this matter would be greatly appreciated.

Answer №1

If you're looking for a solution, Vuex is the way to go.

To handle this, I came up with a unique "IFrame" component that displays content inside an iframe.

Take a look at my Vuex store:

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
        }
    }
})

Here's my custom IFrame component:

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')
            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
        }
    }
}

Now, let me show you how data flows from the ConfirmationComponent to the 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)
        }
    }
}

And finally, here's how the actual PreviewComponent looks like:

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
        }
    }
}

I hope this serves as a useful reference for someone in need.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Vanish and reappear: Javascript block that disappears when clicked and shows up somewhere else

My goal is to make three squares randomly disappear when clicked on the page. However, I am facing an issue where some of them, usually the 2nd or 3rd one, reappear elsewhere on the page after being clicked. I have created a jsfiddle to demonstrate this: ...

Changing the color of Material-UI's Toggle component: A step-by-step guide

After placing my Toggle button in the AppBar, I encountered an issue where both items were the same color when the Toggle was selected. Despite attempting various solutions (seen below), I have not been successful in changing its color. import React fr ...

The button click event fails to trigger after entering text into an input field

I created a basic calculator. Users can input a value in the designated field and then click on the '+' button. The cursor stays in the input field, allowing users to immediately enter a new value after clicking the '+'. The mouse poi ...

Module '../../third_party/github.com/chalk/supports-color' not found in the directory

Within my tutoring-frontend-main project folder There is a file named package.json { "name": "app-frontend", "version": "0.0.0", "license": "MIT", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build --prod", "test": "n ...

Utilizing JSON for live population of search filter results

I'm currently developing a search function for my website that will sift through a JSON Object using regular expressions. My goal is to have the results displayed in real time as the user types, similar to how Google shows search suggestions. However ...

What could be causing issues with my JavaScript AJAX?

I'm in the process of developing a basic chat system that automatically loads new messages as they come in. Initially, I used to fetch all messages from the database. However, I encountered an issue where the scroll bar would constantly jump to the bo ...

Add an element to the parent body from an iframe with this easy tutorial!

Within the iframe, I have the following code snippet. <script type="text/javascript"> $(document).ready(function(){ $("body").append($("#ctap").html()); }); </script> I am looking to append the HTML content of #ctap to the par ...

The error code 13:5 indicates that the "Home" component has been registered in the Vue application but is not being used, leading to the error message "vue/no-unused-components"

I encountered this issue while working with Vue for the first time. I was attempting to construct a website using Vue/CLI by reorganizing and building from the inside out. However, I am unfamiliar with Vue and unsure how to resolve this error. The changes ...

Tips for transferring complete HTML content within a JSON payload

I've been working on a jQuery script to convert an entire HTML page into JSON text. My goal now is to transform this code into a JSON string. In the resulting JSON: The 'text1' value will store the complete HTML in a single string format ...

Attempting to modify text using the header parameter has proven to be ineffective

pages/_middleware.ts import { NextRequest, NextResponse } from 'next/server'; const isMobile = (userAgent: string) => /iPhone|iPad|iPod|Android/i.test(userAgent); const propName = 'x-rewrite'; enum Device { desktop = 'no& ...

Two DataTables on a Single Page - Odd Initialization in the Second One

My page contains two dataTable elements and I've created a method as shown below: function ToDataTable() { $(".dataTable").css("width", "100%"); $(".dataTable").each(function () { var $that = $(this); /* Start of custom ...

JavaScript document string separation

Hi there, I'm a newbie here and could really use some assistance. I am struggling with creating a function and would appreciate any ideas... To give you an idea of what I need help with, I have a String and I want to check if it contains a specific w ...

Invoking a method in Vue.js from another component

I'm just starting out with VUE and I have a unique challenge for my project. I want to have a button on one page that triggers a function on another page. I know some may ask why not keep the button and function on the same page, but I am exploring ho ...

Are there any alternatives to ui-ace specifically designed for Angular 2?

I am currently working on an Angular2 project and I'm looking to display my JSON data in an editor. Previously, while working with AngularJS, I was able to achieve this using ui-ace. Here is an example of how I did it: <textarea ui-ace="{ us ...

Is it possible to exclude certain static files from being served in express.static?

const express = require('express'); const app = express(); app.use('/app', express.static(path.resolve(__dirname, './app'), { maxage: '600s' })) app.listen(9292, function(err){ if (err) console.log(err); ...

Express-hbs: Dynamic Helper Function with Additional Features

I am currently utilizing express-hbs and Async Helpers in my project. However, I am facing an issue with passing options to the helper as async helpers do not seem to support this feature (or maybe I am unaware of how to do it correctly). In the code snipp ...

What is the process for generating a null result cursor upon publication?

Is it possible to return an empty cursor in Meteor? Meteor.publish('example', function(id) { check(id, Match.Maybe(String)) if (!this.userId) return [] }) Although I want the publication to return an empty result when the user is not lo ...

VueJS - Identical keys found while iterating through md-table items

I am attempting to display a material table for a database object that repeats its IDs. <md-table v-model="data"> <md-table-toolbar> <div class="md-toolbar-section-start"> <h1 class="md-t ...

Using webpack to bundle node_modules into your application

I am facing an issue while trying to load some modules like: moment echarts In my package.json file, I have the following versions specified: "echarts": "^3.1.10" "moment": "^2.14.1" However, I am encountering the errors below: VM2282:1 Uncaught Ref ...

Issue with Highcharts: The useHTML flag is not functioning properly when trying to render labels

Currently, I am utilizing highcharts and a phantomjs server for rendering charts and labels. However, I have encountered an issue where the useHTML flag does not function as expected when rendering the labels. Following the instructions in the documentatio ...