I have this react project that doesn't work in Internet Explorer, and we don't intend it to work in IE.
So when rendering the index.html there is the usual root div that renders react.
I want not to render that root div when browser is IE but a different div, with a message warning that the app doesn't work in IE
I can know if the browser is IE like so:
const isIE = !!window.MSInputMethodContext && !!document.documentMode
So I'm trying to change the html div output depending of isIE and I'm not quite sure how.
Logic:
if isIE - true render <div id="root"></div>
if !isIE - false render <div>Browser not supported</div>
Tried something like this:
<html>
<head>
<meta charset="utf-8" />
<% _.map(css, (item) => { %><link href="<%= item %>" rel="stylesheet"><% }) %>
</head>
<body>
<script>
const isIE = !!window.MSInputMethodContext && !!document.documentMode;
if (isIE) { document.getElementById('root').innerHTML += '<p>IE!!</p>' } // or something similar
</script>
<div id="root"></div>
<script>window.__ENVIRONMENT__ = Object.freeze(<%= JSON.stringify(environment) %>)
</script>
<% _.map(js, (item) => { %><script src="<%= item %>"></script><% }) %>
</body>
</html>
Also tried a logic returning the html as a string like so:
<script>
if (isIE) { return '<div>Browser not supported</div>'; }
else { return '<div id="root"></div> }
</script>
And this
const IEdiv = '<div>This is IE</div>';
const rootDiv = '<div id="root"></div>';
if (isIE) { IEdiv.append('body'); }
else { rootDiv.append('body'); }
None of this works
React shouldn't work out of the box with any version of IE as far as I can tell. It requires (or at least used to require) react-app-polyfill to get anywhere.
You're also going to have problems with const as it's only supported by IE11. If the only version of IE you're concerned with is IE11 you should be fine for this.
UPDATED ANSWER
I created a sample myself to see what I could do. I first experimented with loading the <div> into the page with JS, and hit a nasty IE error in being unable to add elements to the document.
So I changed my approach back to yours - editing the existing <div>. The problem isn't your code, the problem is that your script is being ran before the <div> is present in the DOM (to be accessed). Put the <div> above the script (or move the JS to an external file and load it in) and it should work as you intend it to.
Here's my sample for reference:
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div id="root"></div>
<script>
var isIE = !!window.MSInputMethodContext && !!document.documentMode;
console.warn(isIE);
if (isIE) {
var root = document.getElementById('root');
root.innerHTML += 'IE!!';
}
else {
var root = document.getElementById('root');
root.innerHTML += 'hello hello hello';
// you may also need to defer React being loaded in
}
</script>
</body>
</html>
Related
I want to embed PitchPrint app on a React website. They have a vanilla html/js integration tutorial here. I added script tags with links to jQuery and their app file in my index.html file, as they require and then created a separate jsx file that suposed to return a button witch opens the app. The problem is, when I try to build, it throws an error 'PitchPrintClient' is not defined witch suposed to come from their files.
My index.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://pitchprint.io/rsc/js/client.js"></script>
<title>App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
My jsx file:
import React from 'react';
const AppButton = () => {
let _launchButton = document.getElementById('launch_btn');
let _previewDiv = document.getElementById('pp_preview_div');
let _loaderDiv = document.getElementById('pp_loader_div');
_launchButton.setAttribute('disabled', 'disabled');
var ppclient = new PitchPrintClient({
apiKey: 'f80b84b4eb5cc81a140cb90f52e824f6', //Kinldy provide your own APIKey
designId: '3d8f3899904ef2392795c681091600d0', //Change this to your designId
custom: true
});
//Function to run once the app is validated (ready to be used)
var appValidated = () => {
_launchButton.removeAttribute('disabled'); //Enable the Launch button
_launchButton.onclick = () => ppclient.showApp(); //Attach event listener to the button when clicked to show the app
_loaderDiv.style.display = 'none'; //Hide the loader
};
//Function to run once the user has saved their project
var projectSaved = (_val) => {
let _data = _val.data; //You can console.log the _data varaible to see all that's passed down
if (_data && _data.previews && _data.previews.length) {
_previewDiv.innerHTML = _data.previews.reduce((_str, _prev) => `${_str}<img src="${_prev}">`, ''); //Show the preview images
}
};
ppclient.on('app-validated', appValidated);
ppclient.on('project-saved', projectSaved);
return <div>
<div id="pp_loader_div"><img src="https://pitchprint.io/rsc/images/loaders/spinner_new.svg" /></div>
<button id="launch_btn" >Launch Designer</button>
<div id="pp_preview_div"></div>
</div>;
};
export default AppButton;
PS: I know getElementById does not realy work with react, I'll deal with that later, for now I just want to initialize this app.
that's because the component is not mounted yet.
you need to call document.getElementById once the component is mounted, and in order to access dom elements inside the component you need to call it inside useEffect hook
useEffect(() => {
let _launchButton = document.getElementById("launch_btn");
let _previewDiv = document.getElementById("pp_preview_div");
let _loaderDiv = document.getElementById("pp_loader_div");
_launchButton.setAttribute("disabled", "disabled");
var ppclient = new PitchPrintClient({
apiKey: "f80b84b4eb5cc81a140cb90f52e824f6", //Kinldy provide your own APIKey
designId: "3d8f3899904ef2392795c681091600d0", //Change this to your designId
custom: true,
});
//Function to run once the app is validated (ready to be used)
var appValidated = () => {
_launchButton.removeAttribute("disabled"); //Enable the Launch button
_launchButton.onclick = () => ppclient.showApp(); //Attach event listener to the button when clicked to show the app
_loaderDiv.style.display = "none"; //Hide the loader
};
//Function to run once the user has saved their project
var projectSaved = (_val) => {
let _data = _val.data; //You can console.log the _data varaible to see all that's passed down
if (_data && _data.previews && _data.previews.length) {
_previewDiv.innerHTML = _data.previews.reduce(
(_str, _prev) => `${_str}<img src="${_prev}">`,
""
); //Show the preview images
}
};
ppclient.on("app-validated", appValidated);
ppclient.on("project-saved", projectSaved);
}, []);
Read more about React hooks and their constraints.
https://reactjs.org/docs/hooks-intro.html
also make sure to access global variables using window.PitchPrintClient
also, make sure your app is mounted once the dom is ready. by moving your script tags to the end of the body tag or using jquery on ready callback.
PS: The answer is not considering the best practices of writing react components, but I encourage you to use refs and minimize accessing to dom as much as you could.
I am trying to provide a property to an iframe via its contentWindow and would like to do it in a synchronous manner.
I know I can access and set properties on the contentWindow of the iframe but the modification will occur after the iframe has loaded.
Parent
<body>
<script>
const frame = document.createElement('iframe')
document.body.appendChild(frame)
frame.src = 'frame.html'
frame.contentWindow.foobar = 'foobar'
</script>
</body>
iframe
<body>
<script>
console.log(window.foobar) // undefined
</script>
</body>
Is it possible to achieve or do I have to do this asynchronously by dispatching an event from the parent notifying the iframe that the property has been set?
I was able to find a solution to this. My goal was to create a "mock console" where any calls to console.log in the iframe document would be intercepted by the parent window. The problem was similar to OP's in that even though I overrode frame.contentWindow.console, it seemed to get re-set when the window loaded.
I'm not sure how exactly I solved it (I tried copying the replit previewer, luckily they had sourcemaps enabled), but nonetheless here is a plunkr showing a working example:
https://plnkr.co/edit/OLQ0wm8sZVUlMT0p?open=lib%2Fscript.js
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<p>Preview frame</p>
<div id="userCodePreviewWrapper"></div>
<p>Mock Console</p>
<div id="mockConsole"></div>
<script>
let contentWindow;
const consoleDiv = document.querySelector('#mockConsole');
function createConsole(frame) {
// Override the console object in the frame
contentWindow = frame.contentWindow;
frame.contentWindow.__nativeConsole = frame.contentWindow.console;
frame.contentWindow.console = {
log: function(msg) {
console.log("Logging console message", msg);
consoleDiv.innerHTML += `<p>${msg}</p>`;
}
}
}
document.addEventListener('DOMContentLoaded', () => {
const previewFrameDiv = document.querySelector('#userCodePreviewWrapper');
const previewFrame = document.createElement('iframe');
previewFrame.src = 'user-code.html';
previewFrame.setAttribute('id', 'userCodePreview');
previewFrame.setAttribute(
'sandbox',
'allow-forms allow-pointer-lock allow-popups ' +
'allow-same-origin allow-scripts allow-modals',
);
previewFrame.setAttribute('frameborder', 0);
previewFrameDiv.appendChild(previewFrame);
createConsole(previewFrame);
});
</script>
</body>
</html>
I expected SVG nodes to be first class citizen in html5 but I get an unexpected behavior (under Firefox 55.0.2 and Chrome 54.0.2840.71).
In the following html file, I expect a big circle to be dynamically added to a newly created svg element. Instead :
The Inspector tells me the DOM was correctly modified
Nothing is displayed
when I copy paste the DOM (copy -> outer HTML, script deleted) in a new file, the resulting static html file is perfectly fine.
What do I miss ? Why do I have this discrepancy between the DOM and the rendered version of it ? How can I correct this ? re-draw ?
When I use NS suffixed versions of functions (ie. createElementNS and setAttributeNS) I get similar results and nothing is rendered.
Here is the culprit:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>bug dynamic svg</title>
<script type="text/javascript">
Element.prototype.grow = function (tag, attribute_map) {
var child = document.createElement(tag);
if ( attribute_map !== undefined ) {
for (let key in attribute_map) {
child.setAttribute(key, attribute_map[key]);
}
}
this.appendChild(child);
return child;
};
</script>
</head>
<body>
<div id="sandbox"><svg viewbox="0 0 200 200"></svg></div>
<script>
var g_svg = document.getElementById("sandbox").firstElementChild;
g_svg.grow('circle', {'cx':"100", 'cy':"100", 'r':"32"});
</script>
</html>
and here is the DOM-copy-pasted result I get via the inspector (script removed manually) :
<html><head>
<meta charset="utf-8">
<title>bug dynamic svg</title>
</head>
<body>
<div id="sandbox"><svg viewBox="0 0 200 200"><circle cx="100" cy="100" r="32"></circle></svg></div>
</body></html>
Elements go in the SVG namespace, attributes usually don't.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>bug dynamic svg</title>
<script type="text/javascript">
Element.prototype.grow = function (tag, attribute_map) {
var child = document.createElementNS('http://www.w3.org/2000/svg', tag);
if ( attribute_map !== undefined ) {
for (let key in attribute_map) {
child.setAttribute(key, attribute_map[key]);
}
}
this.appendChild(child);
return child;
};
</script>
</head>
<body>
<div id="sandbox"><svg viewbox="0 0 200 200"></svg></div>
<script>
var g_svg = document.getElementById("sandbox").firstElementChild;
g_svg.grow('circle', {'cx':"100", 'cy':"100", 'r':"32"});
</script>
</html>
The seo company I work closely with told me I needed to add this code inbetween the body tags of my meteor project.
<script type="text/javascript">
/* <![CDATA[ */
var google_conversion_id = 123456789;
var google_custom_params = window.google_tag_params;
var google_remarketing_only = true;
/* ]]> */
</script>
<script type="text/javascript" src="//www.googleadservices.com/pagead/conversion.js">
</script>
<noscript>
<div style="display:inline;">
<img height="1" width="1" style="border-style:none;" alt=""src="//googleads.g.doubleclick.net/pagead/viewthroughconversion/949352235 /?value=0&guid=ON&script=0"/>
</div>
</noscript>
However as we know script tags don't get executed properly inside body tags.
So I searched on google to find an answer and I found this code that supposedly works but it's in REACT. How can I convert this react code into normal javascript that I can refer from a template or something. I'm thinking in a onCreated and or onRendered function.
GoogleTag = React.createClass({
displayName : 'GoogleTag',
render(){
var src = '';
if(this.props.type === 'conversion'){
src = _.template('//www.googleadservices.com/pagead/conversion/<%=id%>/?label=<%=label%>&guid=ON&script=0'),
src = src({id : this.props.id, label : this.props.label})
}
if (this.props.type === 'remarketing') {
src = _.template('//googleads.g.doubleclick.net/pagead/viewthroughconversion/<%=id%>/?value=0&guid=ON&script=0'),
src = src({id: this.props.id})
}
var style = {
display : "inline"
},
imgStyle = {
borderStyle : "none"
}
return (
<div style={style}>
<img height="1" width="1" style={imgStyle} alt="" src={src}/>
</div>
)
}
})
You can use Google Remarketing library for async actions and write your meta-data via direct call tracking function:
/* remarketingTrack utility */
export default function remarketingTrack(data = {}) {
try {
trackConversion({
google_conversion_id: SOME_ID,
google_custom_params: data,
google_remarketing_only: true
});
} catch (e) {
// error
}
}
/** somewhere in ReactJS component **/
componentDidMount() {
remarketingTrack({
flight_originid: XXX,
flight_destid: XXX,
flight_startdate: date,
flight_pagetype: 'home',
});
}
I think that it's more flexible and neat solution
Place
<script type="text/javascript">
var google_conversion_id = 123456789;
var google_custom_params = window.google_tag_params;
var google_remarketing_only = true;
</script>
<script type="text/javascript" src="//www.googleadservices.com/pagead/conversion.js"></script>
in your head and you will be fine.
In Meteor you can only have one <head> defined in your project, just create a file called head.html on the client and place the code above inside a body tag in the file.
First, remove any code that you have regarding this. A standard implementation won't work with Meteor without a custom written implementation.
Second, take the code block that Google gives you. Throw out all of the JavaScript, and all of the script tags. You don't need them.
We'll be running a 'non-standard implementation', but it work's the same as far as we're concerned. Take the code within the noscript tags (the div with the img tag), and we need to place it in every place our pages render. So, place it in your Blaze or React layout template, and you should be done.
Google will detect the noscript implementation (which they create so it'll work for visitors who don't have JavaScript enabled, but we are hacking it for use with our implementation), all Google really needs to see is the url call from the img tag :)
index.html
<!DOCTYPE html>
<html>
<head>
<script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script>
<script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></script>
<script>
traceur.options.experimental = true;
</script>
<link rel="import" href="x-item.html">
</head>
<body>
<x-item></x-item>
</body>
</html>
and my web component:
x-item.html
<template id="itemtemplate">
<span>test</span>
</template>
<script type="module">
class Item extends HTMLElement {
constructor() {
let owner = document.currentScript.ownerDocument;
let template = owner.querySelector("#itemtemplate");
let clone = template.content.cloneNode(true);
let root = this.createShadowRoot();
root.appendChild(clone);
}
}
Item.prototype.createdCallback = Item.prototype.constructor;
Item = document.registerElement('x-item', Item);
</script>
and I get no error nor what I expect to be displayed, any idea if this should actually work?
Is this how one would extend an HTMLElement in ECMA6 syntax?
E: putting it altogether in one page solves the problem at least now I know its the right way to create a custom component, but the problem is having it in a separate file I think it has to do with how traceur handles <link rel="import" href="x-item.html"> I tried adding the type attribute to the import with no luck.
Traceur's inline processor does not appear to have support for finding <script> tags inside <link import>. All of traceur's code seems to access document directly, which results in traceur only looking at index.html and never seeing any <scripts> inside x-item.html. Here's a work around that works on Chrome. Change x-item.html to be:
<template id="itemtemplate">
<span>test</span>
</template>
<script type="module">
(function() {
let owner = document.currentScript.ownerDocument;
class Item extends HTMLElement {
constructor() {
// At the point where the constructor is executed, the code
// is not inside a <script> tag, which results in currentScript
// being undefined. Define owner above at compile time.
//let owner = document.currentScript.ownerDocument;
let template = owner.querySelector("#itemtemplate");
let clone = template.content.cloneNode(true);
let root = this.createShadowRoot();
root.appendChild(clone);
}
}
Item.prototype.createdCallback = Item.prototype.constructor;
Item = document.registerElement('x-item', Item);
})();
</script>
<script>
// Boilerplate to get traceur to compile the ECMA6 scripts in this include.
// May be a better way to do this. Code based on:
// new traceur.WebPageTranscoder().selectAndProcessScripts
// We can't use that method as it accesses 'document' which gives the parent
// document, not this include document.
(function processInclude() {
var doc = document.currentScript.ownerDocument,
transcoder = new traceur.WebPageTranscoder(doc.URL),
selector = 'script[type="module"],script[type="text/traceur"]',
scripts = doc.querySelectorAll(selector);
if (scripts.length) {
transcoder.addFilesFromScriptElements(scripts, function() {
console.log("done processing");
});
}
})();
</script>
Another possible solution would be to pre-compile the ECMA6 into ECMA5 and include the ECMA5 only. This would avoid the problem of traceur not finding the <script> tags in the import and would remove the need for the boilerplate.