web-dev-qa-db-fra.com

Comment définir le contenu iframe d'un composant react

J'essaie de définir le contenu d'un iframe dans un composant React mais je ne peux pas le faire. J'ai un composant dans lequel contient une fonction qui doit être appelée à la fin du chargement de l'iframe. Dans cette fonction, je mets le contenu mais il ne semble pas que la fonction onload soit appelée du tout. Je le teste dans le navigateur chrome. J'essaie ce qui suit:

var MyIframe = React.createClass({
    componentDidMount : function(){
        var iframe = this.refs.iframe.getDOMNode();
        if(iframe.attachEvent){
            iframe.attacheEvent("onload", this.props.onLoad);
        }else{
            iframe.onload = this.props.onLoad;
        }
    },
    render: function(){
        return <iframe ref="iframe" {...this.props}/>;
    }
});

var Display = React.createClass({
    getInitialState : function(){
        return {
            oasData : ""
        };
    },
    iframeOnLoad : function(){
        var iframe = this.refs.bannerIframe;
        iframe.contentDocument.open();
        iframe.contentDocument.write(['<head></head><style>body {margin: 0; overflow: hidden;display:inline-block;} html{ margin: 0 auto; text-align: center;} body > a > img {max-width: 100%; height: inherit;}', extraCss, '</style></head><body>', this.state.oasData.Ad[0].Text, '</body>'].join(''));
        iframe.contentDocument.close();
    },
    setOasData : function(data){
        this.setState({
            oasData : JSON.parse(data)
        });
    },
    componentDidMount : function(){
        var url = "getJsonDataUrl";

        var xhttp = new XMLHttpRequest();
        var changeOasDataFunction = this.setOasData;
        xhttp.onreadystatechange = function () {
            if (xhttp.readyState == 4 && xhttp.status == 200) {
                changeOasDataFunction(xhttp.responseText);
            }
        };
        xhttp.open("GET", url, true);
        xhttp.send();
    },
    render : function(){
        return (
            <MyIframe refs="bannerIframe" onLoad={this.iframeOnLoad} />
        );
    }
});

module.exports = Display;

Qu'est-ce que je fais mal?

12
user3807940

Edit 10-25-2018

L'ensemble de ce cadre est devenu vraiment trivial avec l'introduction de Portals dans React 16 . Non seulement l’implémentation et l’utilisation sont beaucoup plus simples, mais le contenu de l’iframe est aussi l’enfant du dom virtuel «parent», ce qui signifie système d’événements partagé, contextes, etc. Génial, non?

import React, { Component } from 'react'
import { createPortal } from 'react-dom'

export default class Frame extends Component {
  constructor(props) {
    super(props)

    this.setContentRef = node =>
      (this.contentRef =
        ((!node || !node.contentWindow) && null) ||
        node.contentWindow.document.body)
  }

  render() {
    const { children, ...props } = this.props // eslint-disable-line
    return (
      <iframe {...props} ref={this.setContentRef}>
        {this.contentRef &&
          createPortal(
            React.Children.only(children),
            this.contentRef
          )}
      </iframe>
    )
  }
}

Cela devient encore plus concis lorsque vous utilisez React Hooks :

import React, { useState } from 'react'
import { createPortal } from 'react-dom'

export const IFrame = ({ children, ...props }) => {
  const [contentRef, setContentRef] = useState(null)
  const mountNode = contentRef && contentRef.contentWindow.document.body

  return (
    <iframe {...props} ref={setContentRef}>
      {mountNode &&
        createPortal(
          React.Children.only(children),
          mountNode
        )}
    </iframe>
  )
}

Usage:

import Frame from './Frame'

const MyComp = () => <Frame><h1>Hello Content!</h1></Frame>

Un contrôle supplémentaire, par exemple sur les iframes <head>, peut facilement être réalisé sous la forme this Gist

Il existe également react-frame-composant , un paquet que IMHO offre à peu près tout ce dont vous avez besoin lorsque vous travaillez avec des iframes dans React. 

SSR

À mon avis, une chose que vous aurez rarement avec les portails, c'est le rendu côté serveur, car les portails ne sont jamais rendus, quand ils ont une référence à un nœud DOM réel ... ...

... alors vous pourriez partir de quelque chose comme ceci:

import React, { Component } from 'react'
import { renderToString } from 'react-dom/server'
import { hydrate, render } from 'react-dom'   

const wrapWithMountNode = html => {
  return `<!DOCTYPE html><html><head></head><body><div id="frame">${html}</div></body></html>`.trim()
}

export default class SSRFrame extends Component {
  constructor(props) {
    super(props)
    this.initialMarkup = wrapWithMountNode(
      renderToString(
        React.Children.only(this.props.children)
      )
    )

    this.contentRef = null
    this.setContentRef = node => {
      this.contentRef =
        ((!node || !node.contentWindow) && null) ||
        node.contentWindow.document.getElementById('frame')
    }
  }

  componentDidMount() {
    this.contentRef &&
      hydrate(
        React.Children.only(this.props.children),
        this.contentRef
      )
  }

  componentDidUpdate() {
    this.contentRef &&
      render(
        React.Children.only(this.props.children),
        this.contentRef
      )
  }

  componentWillUnmount() {
    this.contentRef = null
  }

  render() {
    const { children, ...props } = this.props // eslint-disable-line
    return (
      <iframe
        {...props}
        ref={this.setContentRef}
        srcDoc={
          (!this.contentRef && this.initialMarkup) ||
          undefined
        }
      />
    )
  }
}

Bon cadrage en 2018!

Message original

En ce qui concerne l'IFrame, la solution est très simple: vous devez créer un nouveau rendu DOM pour le contenu de l'IFrame et le synchroniser avec le reste de l'application. Un tel composant pourrait ressembler à quelque chose comme ça:

var React =  require('react');
var ReactDOM = require('react-dom');

var IFrame = React.createClass({

    propTypes = {
        frameProps: React.PropTypes.object,
    },

    defaultProps = {
        frameProps: {
            frameBorder: 0
        }
    },

    updateIFrameContents: function() {
        ReactDOM.render((
            // Here you put the components that should be in the IFrame
            <div {...this.props}>Hello IFrame!</div>
        ), this.el);
    },

    render: function() {
        return (
            <iframe {...this.props.frameProps} />
        );
    },

    componentDidMount: function() {
        var frameBody = ReactDOM.findDOMNode(this).contentDocument.body,
            el = document.createElement('div');
            frameBody.appendChild(el);
        this.el = el;
        this.updateIFrameContents();
    },

    componentDidUpdate: function() {
        this.updateIFrameContents();
    }
});

Maintenant, ce n'est pas très favorable à la composition. Vous ne pouvez pas utiliser React.props.children.only et les «j'aime», car ceux-ci pointent toujours sur des éléments déjà compilés/créés qui font déjà partie de l'arbre de diff. Et parce que nous voulons une nouvelle arborescence de diff pour les contenus encadrés, vous devez définir un nouveau composant pour chaque contenu encadré. 

Entrez Composants d'ordre supérieur . Le but est de créer une sorte de décorateur qui peut être appliqué à tout élément que vous voulez encadrer:

function FramedComponent(Component) {
    return React.createClass({

        propTypes = {
            frameProps: React.PropTypes.object,
        },

        defaultProps = {
            frameProps: {
                frameBorder: 0
            }
        },

        updateIFrameContents: function() {
            ReactDOM.render((
                <Component {...this.props} />
            ), this.el);
        },

        render: function() {
            return (
                <iframe {...this.props.frameProps} />
            );
        },

        componentDidMount: function() {
            var frameBody = ReactDOM.findDOMNode(this).contentDocument.body,
                el = document.createElement('div');
                frameBody.appendChild(el);
            this.el = el;
            this.updateIFrameContents();
        },

        componentDidUpdate: function() {
            this.updateIFrameContents();
        }
    });
}

Utilisez comme ceci:

var MyFramedComponent = FramedComponent(MyComponent);

Edit 3-7-2017 L'un des principaux avantages de React par rapport à toutes les autres bibliothèques DOM virtuelles en vogue actuellement est son système d'événements synthétiques. Bon lawd, ça marche et ça bouillonne, donc très facilement. 

Avec cette approche cependant, vous allez (délibérément) couper un arbre de diff d'un arbre à l'autre, ce qui vaut également pour le système d'événements. Pour la plupart des événements, cela ne fait pas beaucoup de différence:

var logNestedClicks = function(event) {
    console.log(event);
}
// and then
<MyFramedComponent onClick={logNestedClicks) />

Cela fonctionnera bien. Toutefois, il existe certaines exceptions moins notables, qui, en particulier du fait que les iFrames contrôlés dans React sont le plus souvent utilisées pour simplement créer une portée, ne fonctionnent tout simplement pas comme prévu. Encore un exemple pas si important: onBeforeInsert. Ce qui rend scoping Draft instances une tâche très fastidieuse. Et là encore, cela n’est probablement pas pertinent pour la plupart des cas d’utilisation. Assurez-vous simplement que votre merde est capturée comme vous le souhaitez avant de présenter un cas (WYSIWYG) pour iFrames dans React. Été là, fait ça, crois-moi .

20
Lukas Bünger

Il existe une solution plus simple si quelqu'un veut simplement afficher un petit code HTML dans l'iframe. 

<iframe src={"data:text/html,"+encodeURIComponent(content)}/>

La longueur maximale du contenu est de 32 768 caractères.

Il est également facile d’utiliser le package react-frame-composant mentionné dans la réponse acceptée.

2
Jakub Zawiślak

Vous pouvez utiliser l'attribut srcdoc de iframe. Ça va marcher!

srcdoc: HTML incorporé à incorporer en remplaçant l'attribut src.

Lire: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe

1
Kaleem Elahi

Cela fonctionne aussi (non pris en charge dans IE).

const myHTML = <h1>Hello World</h1>
<iframe srcDoc={myHTML} />

Plus d'infos ici: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe

0
Sam Walpole