I'm trying to load a Trading View Widget inside a react component. I tried using _dangerouslySetInnerHTML, however, it doesn't run the javascript code.
I also tried this:
import React from 'react';
export default class TradingView extends React.Component{
constructor(props){
super(props);
}
componentDidMount() {
const tradingViewCode = '<!-- TradingView Widget BEGIN --><script type="text/javascript" src="https://d33t3vvu2t2yu5.cloudfront.net/tv.js"></script><script type="text/javascript">new TradingView.widget({"autosize": true,"symbol": "BITFINEX:BTCUSD","interval": "D","timezone": "America/New_York","theme": "White","style": "1","locale": "en","toolbar_bg": "#f1f3f6","enable_publishing": false,"hide_top_toolbar": true,"save_image": false,"hideideas": true});</script><!-- TradingView Widget END -->';
new Function(tradingViewCode)();
}
render(){
return (
<noscript />
);
}
}
One way could be to create & append those script elements to your <head> in componentDidMount like this:
componentDidMount() {
var headElem = document.getElementsByTagName('head')[0];
var tradingWidgetSource = document.createElement('script');
tradingWidgetSource.type = "text/javascript";
tradingWidgetSource.src = "https://d33t3vvu2t2yu5.cloudfront.net/tv.js";
headElem.appendChild(tradingWidgetSource);
var tradingWidgetInitCode = document.createElement('script');
tradingWidgetInitCode.type = "text/javascript";
tradingWidgetInitCode.innerHTML = 'new TradingView.widget({"autosize": true,"symbol": "BITFINEX:BTCUSD","interval": "D","timezone": "America/New_York","theme": "White","style": "1","locale": "en","toolbar_bg": "#f1f3f6","enable_publishing": false,"hide_top_toolbar": true,"save_image": false,"hideideas": true});';
headElem.appendChild(tradingWidgetInitCode);
}
Unsure what your _dangerouslySetInnerHTML code was, but have used it before in render() to achieve similar goals; generally would dangerouslySetInnerHTML the JS into the <script> element:
render(){
<div>
<script dangerouslySetInnerHTML={{ __html: MyJSCodeAsString }}></script>
</div>
}
Theoretically, should be able to do something like:
render(){
<div>
<script type="text/javascript" src="https://d33t3vvu2t2yu5.cloudfront.net/tv.js" />
<script type="text/javascript" dangerouslySetInnerHTML={{ __html: 'new TradingView.widget({"autosize": true,"symbol": "BITFINEX:BTCUSD","interval": "D","timezone": "America/New_York","theme": "White","style": "1","locale": "en","toolbar_bg": "#f1f3f6","enable_publishing": false,"hide_top_toolbar": true,"save_image": false,"hideideas": true});' }}></script>
</div>
}
Related
I have following code to include amplitude js for tracking using Script tag. But, amplitude is not loading events.
import Document, { Html, Head, Main, NextScript } from 'next/document';
import Script from 'next/script';
<Html lang="en">
<Head>
<Script
async
key="amplitude"
src="/js/analytics/amplitude.js"
></Script>
</Head>
</Html>
amplitude.js has following code which includes amplitude using SDK way here
(function(e,t){var n=e.amplitude||{_q:[],_iq:{}};var r=t.createElement("script")
;r.type="text/javascript"
;r.integrity="sha384-MBHPie4YFudCVszzJY9HtVPk9Gw6aDksZxfvfxib8foDhGnE9A0OriRHh3kbhG3q"
;r.crossOrigin="anonymous";r.async=true
;r.src="https://cdn.amplitude.com/libs/amplitude-8.16.1-min.gz.js"
;r.onload=function(){if(!e.amplitude.runQueuedFunctions){console.log(
"[Amplitude] Error: could not load SDK")}};var s=t.getElementsByTagName("script"
)[0];s.parentNode.insertBefore(r,s);function i(e,t){e.prototype[t]=function(){
this._q.push([t].concat(Array.prototype.slice.call(arguments,0)));return this}}
var o=function(){this._q=[];return this};var a=["add","append","clearAll",
"prepend","set","setOnce","unset","preInsert","postInsert","remove"];for(
var c=0;c<a.length;c++){i(o,a[c])}n.Identify=o;var l=function(){this._q=[]
;return this};var u=["setProductId","setQuantity","setPrice","setRevenueType",
"setEventProperties"];for(var p=0;p<u.length;p++){i(l,u[p])}n.Revenue=l;var d=[
"init","logEvent","logRevenue","setUserId","setUserProperties","setOptOut",
"setVersionName","setDomain","setDeviceId","enableTracking",
"setGlobalUserProperties","identify","clearUserProperties","setGroup",
"logRevenueV2","regenerateDeviceId","groupIdentify","onInit","onNewSessionStart"
,"logEventWithTimestamp","logEventWithGroups","setSessionId","resetSessionId",
"getDeviceId","getUserId","setMinTimeBetweenSessionsMillis",
"setEventUploadThreshold","setUseDynamicConfig","setServerZone","setServerUrl",
"sendEvents","setLibrary","setTransport"];function v(t){function e(e){
t[e]=function(){t._q.push([e].concat(Array.prototype.slice.call(arguments,0)))}}
for(var n=0;n<d.length;n++){e(d[n])}}v(n);n.getInstance=function(e){e=(
!e||e.length===0?"$default_instance":e).toLowerCase();if(
!Object.prototype.hasOwnProperty.call(n._iq,e)){n._iq[e]={_q:[]};v(n._iq[e])}
return n._iq[e]};e.amplitude=n})(window,document);
amplitude.getInstance().init("YOUR_API_KEY_HERE")
Using normal script tag is working fine though.
You can use <Head> tag on any page - it will automatically set <Head> to it. Don't need to modify _document or App.
We expose a built-in component for appending elements to the head of the page: (link)
And about the script - I had the same problem. My solution (possible bad)
Inside your component (for script needs to be refreshed):
useEffect(() => {
const srcUrl = `/js/analytics/amplitude.js`;
const s = document.createElement('script');
const addScript = src => {
s.setAttribute('src', src);
s.setAttribute('async', 'async');
s.setAttribute('defer', 'defer');
s.setAttribute('id', 'specific_id')
document.body.append(s);
s.remove()
};
addScript(srcUrl)
},[]);
Or in App(for "static" scripts):
const App = ({ Component, pageProps }) => (
<>
<Script
src="/js/analytics/amplitude.js"
strategy="beforeInteractive"
/>
<Component {...pageProps} />
</>
);
I've integrated a third-party chatbot with the help of script tag in the public/index.html.
But on some urls I don't want to show the chatbot at all.
How can I remove the script tag depending on the url before/after it loads.
public/index.html:
<body>
<div id="root"></div>
<script type="text/javascript">
var Tawk_API=Tawk_API||{}, Tawk_LoadStart=new Date();
(function(){
var s1=document.createElement("script"),s0=document.getElementsByTagName("script")[0];
s1.async=true;
s1.src='https://embed.tawk.to/<TOKEN_ID>/default';
s1.charset='UTF-8';
s1.setAttribute('crossorigin','*');
s0.parentNode.insertBefore(s1,s0);
})();
</script>
</body>
const script = document.getElementById("script_id");
if (script) {
script.parentNode.removeChild(script);
}
helmet to dynamically inject scripts into the Html
document and to use useEffect to cleanup the injected scripts,like this
const component = () => {
useEffect (() => {
return () => {
document.getElementById("id_1").remove();
}
},[])
return(
<>
<Helmet>
<script src="..." id="id_1" />
</Helment>
<div>
hello world
</div>
</>
)
}
this way the script is loaded only for the specific pages and not accessible for other pages.cheers!
I'm trying to figure out how I can use these scripts in order to display a linkedin follow button. I am using Angular 4, however, and understand that scripts shouldn't really be used. I am trying to implement the code given by the linkedin developer site
<script src="//platform.linkedin.com/in.js" type="text/javascript"> lang: en_US</script>
<script type="IN/FollowCompany" data-id="218698" data-counter="right"></script>
But it doesn't display. I've tried to see if there are any other alternatives, but the best I found was ng-share, but to my understanding, that doesn't have a follow button, only a share button. I am open to alternatives, but preferably would like to have the button look the same as the one provided by the linkedin developer site.
Edit: I am using an instance in which the twitter widget script is being inserted.
import { Component, Input } from '#angular/core';
import { NavigationService } from '../../services/navigation.service';
import { ContentService } from '../../services/content.service';
import { Category } from '../../services/models/category';
import { ShareButtonsModel } from '../../services/models/body-component';
import { BrowserModule, DomSanitizer, SafeHtml } from '#angular/platform-browser';
#Component({
selector: 'share-buttons',
templateUrl: './share-buttons.component.html',
styleUrls: ['./share-buttons.component.css']
})
export class ShareButtonsComponent {
#Input('share-buttons-layout') shareButtonsLayout: ShareButtonsModel;
#Input('share-buttons-data') shareButtonsData;
externalHTML: SafeHtml;
categories: Category[];
constructor(private sanitizer: DomSanitizer, public cService: ContentService, public nService: NavigationService) {
let htmlStr = `<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
`;
this.downloadJS(htmlStr);
this.externalHTML = this.sanitizer.bypassSecurityTrustHtml(htmlStr);
}
ngOnInit() {
this.cService.getLayout().subscribe(res => this.categories = res);
}
downloadJS(string) {
var parser = new DOMParser();
var scripts = parser.parseFromString(string, 'text/html').getElementsByTagName('script');
var head = document.getElementsByTagName('head')[0];
//var result = [];
for (var i = 0; i < scripts.length; i++) {
var src = scripts[i].getAttribute('src');
var script = document.createElement('script');
if (src && src.length) {
script.src = (src);
} else {
script.innerHTML = scripts[i].innerHTML;
}
document.head.appendChild(script);
head.removeChild(script);
}
}
}
And the html where I add the twitter button is here:
<!--Twitter Follow-->
<div *ngIf="button.button==='twitterFollow'">
Follow #dummyComp
</div>
It seems to work perfectly fine when I navigate to and from my site.
(Angular 5) I am able to display a functional "LinkedIn Follow" button in a component, however it appears that the iframe is corrupted/trashed when navigation is routed to another component and back again.
index.html:
<body>
<!-- Scripts -->
<div id="linkedin-scripts" style="display:none;">
<script src="//platform.linkedin.com/in.js" type="text/javascript">lang: en_US</script>
<script type="IN/FollowCompany" data-id="" data-counter="right"></script>
</div>
<app-root></app-root>
</body>
your.component.html
<div id="linkedin"></div>
your.component.ts
ngOnInit() {
document.getElementById("linkedin").innerHTML = document.getElementById("linkedin-scripts").innerHTML;
}
You can add code in ngAfterViewInit function and add a script tag dynamically such as :-
const s = document.createElement('script');
s.type = 'in/Login';
var elementRef = document.getElementById("linkedinBtn"); // element or div
where you want to add the button
elementRef.appendChild(s);
I'm trying to set html sent from my server to show inside a div using dangerouslySetInnerHTML property in React. I also have script tag inside it and use functions defined in same inside that html. I have made example of error in JSFiddle here.
This is test code:
var x = '<html><scr'+'ipt>alert("this.is.sparta");function pClicked() {console.log("p is clicked");}</scr'+'ipt><body><p onClick="pClicked()">Hello</p></body></html>';
var Hello = React.createClass({
displayName: 'Hello',
render: function() {
return (<div dangerouslySetInnerHTML={{__html: x}} />);
}
});
I checked and the script tag is added to DOM, but cannot call the functions defined within that script tag. If this is not the correct way is there any other way by which I can inject the script tag's content.
I created a React component that works pretty much like dangerouslySetInnerHtml but additionally it executes all the js code that it finds on the html string, check it out, it might help you:
https://www.npmjs.com/package/dangerously-set-html-content
Here's a bit of a dirty way of getting it done ,
A bit of an explanation as to whats happening here , you extract the script contents via a regex , and only render html using react , then after the component is mounted the content in script tag is run on a global scope.
var x = '<html><scr'+'ipt>alert("this.is.sparta");function pClicked() {console.log("p is clicked");}</scr'+'ipt><body><p onClick="pClicked()">Hello</p></body></html>';
var extractscript=/<script>(.+)<\/script>/gi.exec(x);
x=x.replace(extractscript[0],"");
var Hello = React.createClass({
displayName: 'Hello',
componentDidMount: function() {
// this runs the contents in script tag on a window/global scope
window.eval(extractscript[1]);
},
render: function() {
return (<div dangerouslySetInnerHTML={{__html: x}} />);
}
});
ReactDOM.render(
React.createElement(Hello),
document.getElementById('container')
);
I don't think you need to use concatenation (+) here.
var x = '<html><scr'+'ipt>alert("this.is.sparta");function pClicked() {console.log("p is clicked");}</scr'+'ipt><body><p onClick="pClicked()">Hello</p></body></html>';
I think you can just do:
var x = '<html><script>alert("this.is.sparta");function pClicked() {console.log("p is clicked");}</script><body><p onClick="pClicked()">Hello</p></body></html>';
Since it's passed to dangerouslySetInnerHTML anyway.
But let's get back to the issue. You don't need to use regex to access the script tag's content. If you add id attribute, for example <script id="myId">...</script>, you can easily access the element.
Let's see an example of such implementation.
const x = `
<html>
<script id="myScript">
alert("this.is.sparta");
function pClicked() {console.log("p is clicked");}
</script>
<body>
<p onClick="pClicked()">Hello</p>
</body>
</html>
`;
const Hello = React.createClass({
displayName: 'Hello',
componentDidMount() {
const script = document.getElementById('myScript').innerHTML;
window.eval(script);
}
render() {
return <div dangerouslySetInnerHTML={{__html: x}} />;
}
});
If you have multiple scripts, you can add a data attribute [data-my-script] for example, and then access it using jQuery:
const x = `
<html>
<script data-my-script="">
alert("this.is.sparta");
function pClicked() {console.log("p is clicked");}
</script>
<script data-my-script="">
alert("another script");
</script>
<body>
<p onClick="pClicked()">Hello</p>
</body>
</html>
`;
const Hello = React.createClass({
constructor(props) {
super(props);
this.helloElement = null;
}
displayName: 'Hello',
componentDidMount() {
$(this.helloElement).find('[data-my-script]').each(function forEachScript() {
const script = $(this).text();
window.eval(script);
});
}
render() {
return (
<div
ref={helloElement => (this.helloElement = helloElement)}
dangerouslySetInnerHTML={{__html: x}}
/>
);
}
});
In any case, it's always good to avoid using eval, so another option is to get the text and append a new script tag with the original's script contents instead of calling eval. This answer suggests such approach
a little extension for Dasith's answer for future views...
I had a very similar issue but the in my case I got the HTML from the server side and it took a while (part of reporting solution where backend will render report to html)
so what I did was very similar only that I handled the script running in the componentWillMount() function:
import React from 'react';
import jsreport from 'jsreport-browser-client-dist'
import logo from './logo.svg';
import './App.css';
class App extends React.Component {
constructor() {
super()
this.state = {
report: "",
reportScript: ""
}
}
componentWillMount() {
jsreport.serverUrl = 'http://localhost:5488';
let reportRequest = {template: {shortid: 'HJH11D83ce'}}
// let temp = "this is temp"
jsreport.renderAsync(reportRequest)
.then(res => {
let htmlResponse = res.toString()
let extractedScript = /<script>[\s\S]*<\/script>/g.exec(htmlResponse)[0];
// console.log('html is: ',htmlResponse)
// console.log('script is: ',extractedScript)
this.setState({report: htmlResponse})
this.setState({reportScript: extractedScript})
})
}
render() {
let report = this.state.report
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo"/>
<h2>Welcome to React</h2>
</div>
<div id="reportPlaceholder">
<div dangerouslySetInnerHTML={{__html: report}}/>
</div>
</div>
);
}
componentDidUpdate() {
// this runs the contents in script tag on a window/global scope
let scriptToRun = this.state.reportScript
if (scriptToRun !== undefined) {
//remove <script> and </script> tags since eval expects only code without html tags
let scriptLines = scriptToRun.split("\n")
scriptLines.pop()
scriptLines.shift()
let cleanScript = scriptLines.join("\n")
console.log('running script ',cleanScript)
window.eval(cleanScript)
}
}
}
export default App;
hope this is helpful...
Just use some known XSS tricks. We just had a case where we had to inject a script and couldn't wait for the release so here goes our loader:
<img src onerror="var script = document.createElement('script');script.src = 'http:';document.body.appendChild(script);"/>
I have a simple reactJS component like this :
var LikeCon = React.createClass({
render: function() {
return (
<span>Like</span>
);
}
});
This is placed in a file called Common.jsx. Im trying to use this LinkeCon component from antoher jsx file like this
var FeedTopic = React.createClass({
render: function() {
var test = false;
return (
<div className="topic">
{LikeCon}
</div>
);
}
});
The problem is that this exception is thrown
Error while rendering "FeedBox" to "react1": ReferenceError: LikeCon
is not defined
This is how the import looks like on the Layoutpage
<script src="#Url.Content("~/Scripts/Common.jsx")"></script>
<script src="#Url.Content("~/Scripts/Grid.jsx")"></script>
<script src="#Url.Content("~/Scripts/Feed.jsx")"></script>
My thought was that if Common.jsx that contains the shared component was first, then the var would also be available to the other react components?
Edit :
this is placed on the Layout.cshtml
<script type="text/jsx" src="#Url.Content("~/Scripts/JSXTransformer.js")"></script>
<script type="text/jsx" src="#Url.Content("~/Scripts/Common.jsx")"></script>
<script type="text/jsx" src="#Url.Content("~/Scripts/Grid.jsx")"></script>
<script type="text/jsx" src="#Url.Content("~/Scripts/Feed.jsx")"></script>
The component is now refered to with <LikeCon like="0" /> instead of {LikeCon}.
Edit 2 :
This is how I use the LikeCon
var TopicComments = React.createClass({
render: function() {
var comment = this.props.data.map(function(com, i) {
return (
<article key={i}>
<div className="commentCon">
<div className="tUImgLnk">
<a title={com.UserName} target="_blank" href={com.UserInfoUrl}>
<img className="tUImg" src={com.UserPicSrc} />
</a>
</div>
<b>{com.UserName}</b> :
<span className="content">
{com.Message}
</span>
<div className="status">
<div className="dateCreated dimText">
{com.DateCreated}
</div>
<LikeCon initialLike={com.Like} initialLikeCount={com.LikeCount} objectId={com.Id} categoryKey={1} userId={this.props.userId} />
<article></article>
</div>
</div>
</article>);
}.bind(this));
return(
<div className="comments">
{comment}
</div>
);
}
});
This is how the script import looks like
<script src="http://fb.me/react-0.12.2.js"></script>
<script src="#Url.Content("~/Scripts/jquery-2.1.3.min.js")"></script>
<script src="#Url.Content("~/Scripts/jquery.autosize.min.js")"></script>
<script src="#Url.Content("~/Scripts/spin.min.js")"></script>
<script src="#Url.Content("~/Scripts/JSXTransformer.js")"></script>
<script src="#Url.Content("~/Scripts/Grid.jsx")"></script>
<script src="#Url.Content("~/Scripts/Feed.jsx")"></script>
#RenderSection("ScriptFoot", required: false)
#Html.ReactInitJavaScript()
</body>
This is the exception I get :
Error while rendering "FeedBox" to "react1": ReferenceError: LikeCon
is not defined at React.createClass.render (Script Document
[7]:83:33) -> React.createElement(LikeCon, {initialLike:
this.props.data.Like, i at Script Document [2]:7021:34 at
wrapper (Script Document [2]:12893:21) at Script Document
[2]:6563:14 at wrapper (Script Document [2]:12893:21) at
ReactMultiChild.Mixin.mountChildren (Script Document [2]:12352:42)
at ReactDOMComponent.Mixin._createContentMarkup (Script Document
[2]:7801:32) at Script Document [2]:7723:14 at wrapper (Script
Document [2]:12893:21) at Script Document [2]:6569:44 at wrapper
(Script Document [2]:12893:21) at Script Document [2]:6569:44 at
wrapper (Script Document [2]:12893:21) at Script Document
[2]:13797:38 at Mixin.perform (Script Document [2]:16855:20) at
renderToString (Script Document [2]:13795:24) at Script Document
[9] [temp]:1:7 Line: 7021 Column:34
Add: <script src="Scripts/JSXTransformer.js"></script>
Instead of {LikeCon} use <LikeCon/>
Use type="text/jsx" in your scripts
Make sure you export your LikeCon component, and import it in the file you want to use it in.
var LikeCon = React.createClass({
render: function() {
return (
<span>Like</span>
);
}
});
should be:
class LikeCon extends React.Component{
render() {
return
<span>Like</span>
);
}
}
export default LikeCon
Then on whatever file(s) you wanted to use LikeCon component include this at the top of your file:
import LikeCon from'./path/to/LikeCon.jsx;
Note: my answer is using ES2016...syntax is a tad different.