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

VueJS - Validating Props with Objects

What is the best way to validate Object type props in VueJS to guarantee that certain fields are defined within the object? For instance, I need to make sure that the user prop includes 'name', 'birthDate', and other specific fields. ...

Having issues installing Parcel through npm - Encountered an error while parsing package.json information

After creating a package.json file using the command npm init in my project folder, I proceeded to run npm i parcel --save-dev which resulted in an error message: C:\Users\XPRESS\Desktop\starter>npm i parcel --save-dev npm ERR! code ...

I am encountering an issue while developing a JavaScript filtering system

Hey everyone, I need help with coding a filter in JavaScript and I'm running into an error (Uncaught TypeError: todos.forEach is not a function) that I can't figure out. Can someone assist me in resolving this issue? const todoFilter = docume ...

The response from the ajax call is still pending

I am currently working on integrating MySQL data (select query) using Spring and MyBatis. I have a controller function that is called via ajax in JavaScript to retrieve the database data. For example: ajax url: /testmysql controller requestmapp ...

The intervals in hooks seem to be malfunctioning and not updating the date and time as expected

I am trying to continuously update the date and time every minute using React hooks. However, I am facing difficulty in updating the time properly. const Link = (props) => { let date = new Date(); const [dateTime, setDateTime] = useState({ cu ...

Trouble arises when attempting to establish an isolated scope within Angular alongside UI Bootstrap

My table of data is set up with AngularJS, and one of the columns is calculated using a function in the controller. On my webpage, I have a button that opens a modal. When I use UI Bootstrap to open the modal, it creates a new isolated scope (child of the ...

Obtain the ID of element 1 by clicking on element 2 using JQuery

In the world of javascript/jquery, When button1 is clicked, we can get its id like this: var button1id = $(this).attr("id"); If button2 is clicked, how do we retrieve button1's id? This brings us to the question: How does button2 access the i ...

Halting execution: Trying to use the keyword 'import' which is not allowed here

0. April 2023: error cannot be reproduced anymore see here The error is no longer replicable due to bug fixes in react-scripts 5.0.1. 1 Even though the error is gone, the question and my self-answer still seem relevant to Angular users and others as we ...

Tips for showcasing a calculated value in vue-table-2

When using an API to retrieve a dataset from a server, the date/time is in epoch format (eventTime). How can I convert this into a human-readable date/time format? What is the correct method for achieving this? Template for displaying table <div class ...

Begin the jQuery ResponsiveSlides Slider with the final image in the <ul> list

Currently utilizing the responsiveSlides image slider from responsiveSlides on our website. This jQuery slider uses an HTML unordered list of images to slide through automatically. The issue I'm facing is that before the slider actually starts (meani ...

What could be the reason for the Checkbox's value not showing up after making changes?

In my React and Material UI project, I am facing an issue where I want to check all checkboxes in a list by simply checking one checkbox in a parent component. Despite passing down the correct value of the parent checkbox through props, the visual changes ...

Analyzing a CSV file and executing asynchronous operations on specific columns with the help of ajax requests

I possess a CSV file that contains placement links and target links formatted as follows: CSV Example [placement url],[target url] [placement url],[target url] [placement url],[target url] My objective is to parse the CSV file line by line using JavaScri ...

"Utilizing jQuery's bind method with IE 7 compatibility and the use of

One of the scripts I'm working with is a treeview script, and a portion of it appears like this: root.find("." + classControl).each(function () { $(this).bind('click', function () { if ($(this).text() == "-") { $(thi ...

Route Separation with ExpressJS and Socket.IO

As I delve into the world of ExpressJS and Socket.IO, I find myself facing a puzzling situation. My routes are neatly organized in a separate file that I include from my app.js: var express = require('express') , db = require('./db&ap ...

Is it necessary to re-export a module after modifying an attribute in it in JS/ES6?

From my understanding of the module system, when I use import 'some_module' in a file, I will always receive the same instance of that module and not a new instance each time. However, I am a bit puzzled by a pattern I have observed in certain a ...

Steps to retrieve data (token) from developer tools and incorporate it into a fetch Post request

Is there a simple way to extract data using dev tools and insert it into a fetch request? I am trying to make a POST request through the console, but I am struggling to correctly copy a token. I attempted to use querySelector but instead of finding the t ...

Would it cause any issues if I have two onMounted() functions in a single component?

Can I use multiple onMounted() calls for different features in a single component? MyComponent.vue <script setup> import { onMounted } from 'vue'; // Feature A onMounted(() => { // Do something for feature A. }); // Feature B onMoun ...

Leveraging the useRef hook to adjust the CSS styling of a React component

Currently, I am working on changing the style of a react component by utilizing the useRef hook. So far, I have implemented the useRef hook to reference the specific component whose style I want to modify when clicking on two buttons. Despite my efforts, I ...

Would you prefer to generate fresh HTML using JavaScript or dynamically load an existing HTML layout using AJAX?

I have a project where I need to generate a large amount of HTML that isn't currently on the page. Up until now, I've been using jQuery to construct the page piece by piece with JavaScript, adding divs and adjusting layouts as needed. Lately, I ...

What are some ways to make source code more visually appealing on an HTML/JSP page as it is being

I've recently created a webpage with some Java source code, neatly organized within blocks. However, I'm looking to enhance its appearance so it truly looks like Java code. Take a look at my page's code here for reference: Are there any onl ...