I'm working on an application which supports both client and server-side rendering using Facebook's React JS framework.
I need to render an iframe, which has some html inside of it. The HTML is created using a script that I have access to.
However, I want the inner content to be rendered on the server, so that the HTML shows up in search engines. The problem is that for the inner content to be created, it normally needs to wait for the iframe to 'load', which does not happen on the server.
How can I do this?
Here's what I tried, which doesn't work:
render: function() {
return (
<div>
<iframe
ref="myIframe">
</iframe>
</div>
);
},
componentDidMount : function() {
var iFrameNode = this.refs.myIframe,
frameDoc = iFrameNode.contentWindow.document;
frameDoc.write('<html><body style="margin:0px;"><div><script type="text/javascript" src="..."></script></div> ... more html');
}
Note that I'm adding the content on componentDidMount because otherwise it gets 'erased' when the iframe loads.
A good way to do it is to use data URI scheme. It allows inserting html content to an iframe via the src attribute.
Currently supported on all browsers except IE (partial support - no html option) - caniuse.
This will allow google search engine to read the content of the iframe on the server side.
So your code should be -
render: function() {
var frameSrc = browser.ie ? '' : 'data:text/html,<html><body style="margin:0px;">...more html">'
return (
<div>
<iframe
ref="myIframe"
src="{frameSrc}"
</iframe>
</div>
);
},
componentDidMount : function() {
if (browser.ie) { //some browser detection library/code
var iFrameNode = this.refs.myIframe,
frameDoc = iFrameNode.contentWindow.document;
frameDoc.write('<html><body style="margin:0px;"><div><script type="text/javascript" src="..."></script></div> ... more html');
}
}
IFrames are sometimes used to display content on web pages. Content
displayed via iFrames may not be indexed and available to appear in
Google's search results. We recommend that you avoid the use of
iFrames to display content. If you do include iFrames, make sure to
provide additional text-based links to the content they display, so
that Googlebot can crawl and index this content.
- Google Webmaster Guidelines
So from a SEO standpoint, you either need to stop using iframes here or accept that it won't be indexed.
Clever tricks like putting it in the html, and then switching it to an iframe won't help because Googlebot uses JavaScript... unless you do useragent sniffing to send empty JavaScript files, but I don't recommend that.
The direct answer is to use __dangerouslySetInnerHTML if !this.state.x, and in componentDidMount setTimeout(() => this.setState({x: true}), 0), and inject the html into the iframe in componentDidUpdate.
There is a React component to render iframes: https://github.com/svenanders/react-iframe
You can get it with npm install react-iframe, then use it like this in your React code:
import React from 'react';
import Iframe from 'react-iframe';
...
// In your render function:
<Iframe url="whatever.html" />
Unfortunately for me, I would actually prefer to render my server-generated HTML into a <div> rather than an actual <iframe>...
Related
I am trying to implement some payment system (MercadoPago).
According to the doc, it's just pasting this:
<form method="POST">
<script
src="https://www.mercadopago.com.pe/integrations/v1/web-payment-checkout.js"
data-preference-id="589788715-2e52aeec-8275-487c-88ee-1a08cff37c08"
></script>
</form>
Pasting it in a pure html file works fine: a button appears and clicking it opens a modal to pay with a credit card as expected. However this doesn't work in React since it's dynamically loading a script. Hence, I tried using an effect hook to insert the <script> on load as such:
const App = () => {
const setMercadoPagoPreferences = async () => {
const script = document.createElement('script');
script.src =
'https://www.mercadopago.com.ar/integrations/v1/web-payment-checkout.js';
script.async = true; // commenting or uncommenting seems to have no effect
script.setAttribute(
'data-preference-id',
'589788715-2e52aeec-8275-487c-88ee-1a08cff37c08'
);
document.getElementById('mercadoForm').appendChild(script);
};
useEffect(() => {
setMercadoPagoPreferences();
}, []);
return <form action='/procesar-pago' method='POST' id='mercadoForm' />;
};
This loads correctly the script, it seems, as a button to pay is appended to the page. Clicking it however opens a modal that says "oh no, something bad happened". This doesn't happen on my .html example above; so it must be because of how React is loading the script or something like that. It doesn't work on either the dev or the production build.
Edit: As suggested, I tried using refs instead of directly appending childs but this did not seem to have any impact, it still won't work.
I dont know this framework but maybe thats the way: It looks like this guy has similar problem right here.
I think there is problem because script does not load on time. So maybe lets try this:
script.onload = () => {
document.getElementById('mercadoForm').appendChild(script);
};
In addtion there is build in mechanism in react ref like HERE instead of document.getElementById
Your technique would've work if it weren't for mercadolibre.
Apparently, the use of the page you are trying to load is not allowed by mercadolibre. It's like you're trying to load mercadolibre inside an Iframe which they probably blocked using CSP header. Specifically they are setting the Content-Security-Policy tag to frame-ancestors 'self'. It's a security restriction standard that does not allow the use of pages from that domain within elements Iframe.
I'm trying to create a login widget in an iframe that can be used on a clients website. This iframe will be using jQuery, but I first wont to be able to check if the parent document has jQuery loaded, if not, load it.
I've tried several different techniques but none seem to wont to work, or decide to load the jQuery library twice.
Any help would be greatly appreciated.
This code block is within the page loaded inside the iframe, which refuses to co-operate.
<script>
var jQOutput = false;
function initjQuery() {
if (typeof(jQuery) == 'undefined'){
if (!jQOutput){
jQOutput = true;
var jScript = document.createElement('script');
jScript.setAttribute("type", "text/javascript");
jScript.setAttribute("src", "js/libs/jquery/jquery-min.js");
}
setTimeout("initjQuery()", 50);
} else {
$(function() {
$("#email").css({"background":"red"});
//visual aid to see if jQuery is loaded.
});
}
}
</script>
I just want to check and see if the parent to the iframe has loaded jQuery, and if not, load it myself, as I'll be using it to perform several tasks needed to complete the login proceedure.
Entented comment:
I dont think what you want to archive is posible because of same origin policy (Cross domain access via JavaScript) - look it op on Google... http://google.com/search?q=same+origin+policy
If you on the other hand dont violate the same origin policy you can archive what you want using something like this:
var parent = window.parent; // This refers to parent's window object
if ( parent && parent.jQuery ) { // Check to see if parent and parent.jQuery is truly
window.jQuery = parent.jQuery;
window.$ = parent.jQuery;
}
else {
// load jQuery here
}
The JavaScript code of your widget has to be divided into two parts. One of them would be loaded on a client's site. It would have permissions to access the DOM of the site: load jQuery if needed, create an iframe, etc. (Be especially careful with global variables there! Try not to use them at all since they can conflict with the code of the site.) Note that this part wouldn't have access to the DOM of the iframe. That's why you need the second part, which would be loaded inside of the iframe. You can use cross-domain techniques for the parts to exchange messages with each other. I'd advise you to check out the easyXDM library.
I have a doubt. I am having an obfuscated html i want to load that in an iframe component. I want to unobfuscate that before loading it in the iframe component. Is it possible? Is there any javascript tool like that?
Any ideas?
Added a link.
http://colddata.com/developers/online_tools/obfuscator.shtml#obfuscator_view
Original Code:
obfuscated Code:
<script type='text/javascript'>
<!--
var s="=iunm?=cpez?=ejw!dmbtt>#b#?=0ejw?=0cpez?=0iunm?";
m=""; for (i=0; i<s.length; i++) { if(s.charCodeAt(i) == 28){ m+= '&';} else if (s.charCodeAt(i) == 23) { m+= '!';} else { m+=String.fromCharCode(s.charCodeAt(i)-1); }}document.write(m);//-->
</script>
So finally i will be having a file like this but when i am about to load in the iframe component i want to see the real code.
The reason why i want use the obfuscated code is because i will be keeping some static html's in an android device and load those html's since i am keeping it in the device i want to obfuscate. Initially i though of encrypting. But this will cause performance impact.
To load HTML in an iframe with jQuery:
var html = '<div>Your HTML</div>';
$("iframe").contents().find("body").html(html);
The iframe's domain and its parent's must match though.
As for the obfuscation thing, don't do that, if you can un-obfuscate it, everybody can as well.
If you are obfuscating just for the purpose of preventing people from searching the OS quickly for the content, e.g. to hide solutions in a simple game, this would be acceptable on modern devices:
var htmlString = 'this is a test', base64EncodedString = '';
base64EncodedString = window.bToA( htmlString );
You then just need to use the following to reverse:
htmlString = window.aToB( base64EncodedString );
You can then just use Pioul's method for placing it in the iframe.
However as people have stated, obfuscation for other reasons - i.e. proper security protection - is rather pointless, as with a bit of knowledge of your app coders could easily reverse whatever you do (especially as everything involved in the obfuscation is working client-side).
Let's say that I have a single page site, and I want to wait to load any content below the fold (using ajax) until the user clicks a link to go to that content on the same page. However, I don't want users who have JavaScript disabled to just not see any content below the fold. The whole reason to load the content with ajax is so that the initial page load is faster, so having the content load either way and then hide until the user clicks would be pointless. I'm sure it's possible to only load content if JavaScript is enabled, but is it possible to not load specific "static" or server-side generated content if JavaScript is enabled in the browser?
Is there any way to prevent specific static content from being loaded by the server on initial page load at all if JavaScript is enabled in the browser?
You might consider using the noscript tag.
<body>
<h1>Above the fold header</h1>
<video src="fancy-cat.mp4" ... />
<div id="below-the-fold">
<noscript>
<!-- Users with Javascript disabled get this content -->
<h2>My cat plays piano</h2>
<video src="piano-cat.mp4" ... />
<h2>My cat knows how to use the toilet</h2>
<video src="potty-cat.mp4" ... />
</noscript>
</div>
You can then use javascript to copy the contents of these <noscript> tags on page load and insert them into the DOM.
The innerHTML property of a noscript tag will return an encoded string of HTML. But you can use the handy dandy Encoder script to convert it to something the DOM will like.
<script src="http://www.strictly-software.com/scripts/downloads/encoder.js"></script>
<script>
$(window).load(function() {
// Copy the 'noscript' contents into the DOM
$("noscript").each(function() {
$(this).after(Encoder.htmlDecode($(this).html()));
});
});
</script>
</body>
Alternatively, if the "below the fold" content is really image/video heavy, you might just want to consider Lazy Loading the content.
If you want to avoid loading data in the case of a client with JS enabled, then you might have to combine server-side and client-side techniques.
This could be used as a rough guide. Disclosure - I've not tested any of this!
For example, if your site structure looked like this:
/
page1.jsp
fragment1_1.jsp
fragment1_2.jsp
page2.jsp
fragment2_1.jsp
fragment2_2.jsp
...
Then page1.jsp could look like this (apologies if you don't know JSP and jQuery, but this is mostly pseudo-code anyway):
<%!
// Define a method to help us import fragments into the current page.
// Conditional import of fragment based on isJSEnabled
void myImport (String fragment, boolean isJSEnabled, HttpServletResponse res) {
if (!isJSEnabled) {
// output fragment contents directly to response
String contents = // get contents of fragment
res.getWriter().write(contents);
}
}
%>
<%
// How to work out if JS is enabled on the server-side?
// Not sure it can be done. So need to be told by the browser somehow.
// Maybe a request parameter. So if param not present, assume no JS.
boolean isJSEnabled = (null != req.getParameter("js"));
%>
<html>
<head>
<script>
// Try and redirect to JS version of page as soon as possible,
// if we're not already using the JS version of the page.
// This code does not take into account any existing request parameters for
// the sake of brevity.
// A browser with JS-enabled that was incrementally downloading and parsing
// the page would go to the new URL.
if (window.location.href.indexOf("js=true") < 0) {
window.location.href += "?js=true";
}
</script>
</head>
<body>
<h1 class="fragment_header" data-fragment-id="fragment1_1">Fragment 1</h1>
<div>
<%
// Conditionally import "fragment1_1".
myImport("fragment1_1", isJSEnabled);
%>
</div>
<h1 class="fragment_header" data-fragment-id="fragment1_2">Fragment 2</h1>
<div>
<%
// Conditionally import "fragment1_2".
myImport("fragment1_2", isJSEnabled);
%>
</div>
<script>
// For each fragment header, we attach a click handler that loads the
// appropriate content for that header.
$(".fragment_header").click(function (evt) {
var header = $(evt.target);
// Find the div following the header.
var div = header.next("div");
if (div.children().length < 1) {
// Only load content if there is nothing already there.
div.load(header.attr("data-fragment-id") + ".jsp");
}
});
$("a").click(function (evt) {
// Stop the browser handling the link normally.
evt.preventDefault();
// Rudimentary way of trying to ensure that links clicked use the
// js=true parameter to keep us is JS mode.
var href = anchor.attr("href");
var isLocalHref = // logic to determine if link is local and should be JS-ified.
if (isLocalHref) {
href = href + "?js=true";
}
window.location.href = href;
});
</script>
</body>
</html>
A user browsing to page1.jsp would get the static version initially, though a JS enabled browser would switch to the JS enabled version as soon as possible.
As we attach click handlers to all the links, we can control the loading of the next page. If JS is switched off at some point, the js=true parameter will not be appended to each href and the user will revert to a static page.
I'm looking for a light weight method for client-side includes of HTML files. In particular, I want to enable client-side includes of publication pages of researchr.org, on third party web pages. For example, I'd like to export a page like
http://researchr.org/profile/eelcovisser/publications
(probably just the publications box of that page.)
Using an iframe it is possible to include HTML pages:
<iframe class="foo" style="height: 50em;" width="100%" frameborder="0"
src="http://researchr.org/profile/eelcovisser/publications">
</iframe>
However, iframes require specification of a fixed height, while the pages I'm exporting don't have a fixed height. The result has an ugly scrollbar:
http://swerl.tudelft.nl/bin/view/EelcoVisser/PublicationsResearchr
I found one reference to a method that appears to be appealing
http://www.webdeveloper.com/forum/archive/index.php/t-26436.html
It uses an iframe to import the html, and then a javascript call from the included document to a function defined in the including document, which places the contents of the body of the included file in a div of the including file. This does not work in my scenario, probably due to the same origin policy for javascript, i.e. the including and included page are not from the same domain (which is the whole point).
Any ideas for solving this? Which could be either:
a CSS trick to make the height of the iframe flexible
a javascript technique to lift the contents of the iframe to a div in the including page
some other approach I've overlooked
Requirement: the code to include on should be minimal.
No. The same-origin policy prevents you from doing any of that stuff (and rightly). You will have to go server-side, have a script on your server access that page and copy its contents into your own page (prefeably at build-time/in the background; you could do it at access-time or via AJAX but that would involve a lot of scraping traffic between your server and theirs, which may not be appreciated.
Or just put up with the scrollbar or make the iframe very tall.
As far as I know there is no CSS trick, the only way is to query the iFrame's document.documentElement.offsetHeight or scrollHeight, depending on which is higher, take that value and apply it on the iframe's css height ( add the + 'px' ).
try this ajax with cross domain capability
Why don't you use AJAX?
Try this:
<div id="content"></div>
<script type="text/javascript">
function AJAXObj () {
var obj = null;
if (window.XMLHttpRequest) {
obj = new XMLHttpRequest();
} else if (window.ActiveXObject) {
obj = new ActiveXObject("Microsoft.XMLHTTP");
}
return obj;
}
var retriever = new AJAXObj();
function getContent(url)
{
if (retriever != null) {
retriever.open('GET', url, true);
retriever.onreadystatechange = function() {
if (retriever.readyState == 4) {
document.getElementsById('content').innerHTML(retriever.responseText);
}
}
retriever.send(null);
}
}
getContent('http://researchr.org/profile/eelcovisser/publications');
</script>
And then, you can parse the received page content with JS with regular expressions, extracting whatever content you want from that page.
Edit:
Sorry, I guess I missed the fact that it's a different domain. But as ceejayoz said, you could use a proxy for that.
If you're using jQuery, you can use the load method to retrieve a page via AJAX, optionally scrape content from it, and inject it into an existing element. The only problem is that it requires JavaScript.