Can I use jsx without React to inline HTML in script? - javascript

Can I use inline HTML in a script as below by using a library like jsx:
<script src="jsx-transform.js"></script>
<script type="text/jsx">
define('component', function () {
return (<div>test html code</div>);
});
</script>

I was able to write JSX files and inject them into an HTML page using a 'fake' React file.
no-react.js
/**
* Include this script in your HTML to use JSX compiled code without React.
*/
const React = {
createElement: function (tag, attrs, children) {
var element = document.createElement(tag);
for (let name in attrs) {
if (name && attrs.hasOwnProperty(name)) {
let value = attrs[name];
if (value === true) {
element.setAttribute(name, name);
} else if (value !== false && value != null) {
element.setAttribute(name, value.toString());
}
}
}
for (let i = 2; i < arguments.length; i++) {
let child = arguments[i];
element.appendChild(
child.nodeType == null ?
document.createTextNode(child.toString()) : child);
}
return element;
}
};
Then compile your jsx.
test.jsx
const title = "Hello World";
document.getElementById('app').appendChild(
<div>
<h1>{title}</h1>
<h2>This is a template written in TSX, then compiled to JSX by tsc (the Typescript compiler), and finally
injected into a web page using a script</h2>
</div>
);
Resulting compiled 'test.js'
var title = "Hello World";
document.querySelector('#app').appendChild(React.createElement("div", null,
React.createElement("h1", null, title),
React.createElement("h2", null, "This is a template written in TSX, then compiled to JSX by tsc (the Typescript compiler), and finally" + " " + "injected into a web page")));
And finally, the HTML page that includes both scripts.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>no-react</title>
<script src="no-react.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
<script src="test.js"></script>

React renders JSX html syntax to JS using functions such as React.createElement (among others for Fragments and so on). But that all boils down to the #babel/plugin-transform-react-jsx plugin which does the transpiling of this:
return(<div id="hello">Hello World</div>)
into this...
return React.createElement('div', {id: 'hello'}, 'Hello World');
However you can replace React.createElement with you're own function to do this. You can read more on that here: https://babeljs.io/docs/en/next/babel-plugin-transform-react-jsx.html
You should also look at libraries which do exactly this such as nervjs, jsx-render and deku. All of these use a JSX html syntax without react. Some (such as jsx-render) are only focused on converting JSX to the final JS, which might be what you're looking for.
The author of that package wrote an article on it here: https://itnext.io/lessons-learned-using-jsx-without-react-bbddb6c28561
Also Typescript can do this if you use that...but I've no first hand experience with it.
To sum up
You can do it without React, but not without Babel or Typescript.

JSX is not a string-based templating language; it compiles to actual JavaScript function calls. For example,
<div attr1="something" attr2="other">
Here are some <span>children</span>
</div>
transpiles to
React.createElement("div", {attr1: "something", attr2: "other"},
"Here are some ", React.createElement("span", null, "children")
)

I was looking for something like this myself.
A way to write Components and JSX like for simple projects.
Didn't find one that I liked so I've built this:
https://github.com/SagiMedina/Aviya#aviya
Have a look, maybe it will answer your problem as well.

You will need something to transform the JSX into JS function calls. React uses Babel to do this -- you would probably be best off with that too.
There's a library by the creators of Preact that essentially does what you're after called vhtml. The tagline is "Render JSX/Hyperscript to HTML strings, without VDOM".
Here is a copy of the Readme at time of writing:
Usage
// import the library:
import h from 'vhtml';
// tell babel to transpile JSX to h() calls:
/** #jsx h */
// now render JSX to an HTML string!
let items = ['one', 'two', 'three'];
document.body.innerHTML = (
<div class="foo">
<h1>Hi!</h1>
<p>Here is a list of {items.length} items:</p>
<ul>
{ items.map( item => (
<li>{ item }</li>
)) }
</ul>
</div>
);
New: "Sortof" Components!
vhtml intentionally does not transform JSX to a Virtual DOM, instead serializing it directly to HTML. However, it's still possible to make use of basic Pure Functional Components as a sort of "template partial".
When vhtml is given a Function as the JSX tag name, it will invoke that function and pass it { children, ...props }. This is the same signature as a Pure Functional Component in react/preact, except children is an Array of already-serialized HTML strings.
This actually means it's possible to build compositional template modifiers with these simple Components, or even higher-order components.
Here's a more complex version of the previous example that uses a component to encapsulate iteration items:
let items = ['one', 'two'];
const Item = ({ item, index, children }) => (
<li id={index}>
<h4>{item}</h4>
{children}
</li>
);
console.log(
<div class="foo">
<h1>Hi!</h1>
<ul>
{ items.map( (item, index) => (
<Item {...{ item, index }}>
This is item {item}!
</Item>
)) }
</ul>
</div>
);
The above outputs the following HTML:
<div class="foo">
<h1>Hi!</h1>
<ul>
<li id="0">
<h4>one</h4>This is item one!
</li>
<li id="1">
<h4>two</h4>This is item two!
</li>
</ul>
</div>

It looks like the dom-chef package can do this. From the readme:
No API, JSX gets auto transformed into actual DOM elements.
// babel.config.js
const plugins = [
[
'#babel/plugin-transform-react-jsx',
{
pragma: 'h',
pragmaFrag: 'DocumentFragment',
}
]
];
// ...
const {h} = require('dom-chef');
const handleClick = e => {
// <a> was clicked
};
const el = (
<div class="header">
<a href="#" class="link" onClick={handleClick}>Download</a>
</div>
);
document.body.appendChild(el);

You can have a look at documentation: in-browser JSX transform,
and also see Babel documentation
You can achieve what you want, but it is discouraged in production...

Jsx parse the return (<div>test html code</div>); to something like this return React.createElement('div', {...});
then if you don't using react.js, then browser will not know what React is and trigger an error.

Related

Can insert React JSX <Link> with innerHTML method?

I have some html (JSX) in my react app that looks like this:
<div><h3>Title</h3><p> some message...</p></div>
I am then assigning this to a variable called msg using innerHTML like so:
let msg
msg.innerHTML = "<div><h3>Title</h3><p> some message...</p></div>"
//then i append it to my app like this
document.body.appendChild(msg)
Now i would like to surround the html above with a native React Link element like so
msg.innerHTML = "<Link to"/"><div><h3>Title</h3><p> some message...</p></div></Link>"
of course it doesn't work and it is compiled as regular html when it is rendered to the page as and doesn't act as a react link
How can i achieve this, is it even possible or is there a totally different way of approaching this issue?
I tried doing this and many different variations but to no cigar:
msg.innerHTML = `${<Link to"/">}<div><h3>Title</h3><p> some message...</p></div>${</Link}`
There is ReactDOMServer.renderToString that will render a React element to its initial HTML.
...
import ReactDOMServer from "react-dom/server";
export default function App() {
useEffect(() => {
const msg = document.createElement("div");
msg.innerHTML = ReactDOMServer.renderToString(
<Link to="/">
<div>
<h3>Title</h3>
<p> some message...</p>
</div>
</Link>
);
document.body.appendChild(msg);
}, []);
return null;
}

How to convert inline babel script to JS

I am new to React JS and written some React JS code directly to HTML and it is working fine.
Now when I converted this inline code to JS using babel converter using Online Babel Converter and link the Converted JS to HTML, it is showng the blank the UI with no error or warning on browser console window.
I've written the inline babel script in <script type="text/babel> ... </script> tag
Note: I converted the inline code with default selected options in Online Babel Converter, Evaluate in Settings, stage-2 & react in Presets
Edit: Added some portion of code
<!DOCTYPE HTML>
<html>
<body>
<script type="text/babel>
class App extends React.Component {
createCircles = () => {
let circles = [];
for(let i = 1; i <= this.props.count; i++){
circles.push(<div className = "smallCircle" id={'circle'+i} key={i}><code className="circles" id={'id'+i}>{i}</code></div>);
}
return circles;
}
render(){
return (
<div id="circles">
<div className = "bigCircle" id="bigCircle">
<img id="bigCircleImage" src="http://localhost" />
</div>
<div className = "smallCircles">
{this.createCircles()}
</div>
</div>
);
}
}
function AppLoader(){
return (
<App />
);
}
ReactDOM.render(<AppLoader />, document.getElementById('root'));
</script>
<div id="root"></div>
</body>
</html>
Agreed with #JoeWarner answer to not to use extra AppLoader function if you are not returning more than one component.
Coming to the question, I saw that you written the script before the div tag of id root. After converting your script, import the script below the tag to see the changes

React: Script tag not working when inserted using dangerouslySetInnerHTML

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);"/>

Handlebars: 2 sources 1 template

I have one handlebars template but I want to include variables from two different sources in this template.
<script id="notification-menu-item" type="text/x-handlebars-template">
I have tried to make both of the sources go to the same template id. Both files have this:
var source = $("#notification-menu-item").html();
var template = Handlebars.compile(source);
But only one of sources' variable come through to the template. Is there anyway to have one template get its {{variables}} from two different sources?
Edit: The code
This is the template:
<script id="notification-menu-item" type="text/x-handlebars-template">
<div id="navmenucontainer" class="container">
<div id="navmenuv">
<ul class="nav">
<li>Topics</li>
<li>Help</li>
{{#if logged_user}}
<li>Notifications</li>
{{#if pro}}
<li>My Data</li>
{{/if}}
{{/if}}
</ul>
</div>
</div>
</script>
pro comes from one .js file and logged_user comes from a separate .js file. Is there a way for both of these variable to be used in the same template?
You'll have to centralize the rendering of the template into one function somehow if you want to composite the data before passing it into the Handlebars.compile() function. I guess you're going to have to somehow guarantee the order in which these "plugin" js files call this new function. Otherwise it turns into something really janky like this:
Example:
Class1.js
var source = $("#notification-menu-item").html();
var html = Notification.renderNotification(source, logged_user, undefined);
if (typeof html !== 'undefined') {
$('body').prepend(html);
}
Class2.js
var source = $("#notification-menu-item").html();
var html = Notification.renderNotification(source, undefined, pro);
if (typeof html !== 'undefined') {
$('body').prepend(html);
}
Notification.js
window.Notification = function() {
var logged_user = undefined;
var pro = undefined;
return {
renderNotification: function(source, user, isPro) {
if (typeof user !== 'undefined') {
logged_user = user;
}
if (typeof pro !== 'undefined') {
pro = isPro;
}
if(typeof logged_user !== 'undefined'
&& typeof pro !== 'undefined') {
var template = Handlebars.compile(source);
var html = template({logged_user: logged_user, pro: pro});
return html;
}
}
}
Obviously this is not elegant and far from maintainable. Without getting into the specifics of how Discourse works though, I'm not sure what to tell you. At render time of the template, a full object containing all the relevant data should be passed. Subsequent calls to Handlebars.compile() would require the full set of data. Maybe should consider finding a way to split these templates up and render them into separate page elements asynchronously, or look into Partials
Disclaimer: I'm not an expert on JS or logicless templates.

TypeError: document.getElementById(...) is null

I am trying to build a template builder using http://ejohn.org/blog/javascript-micro-templating
My html has this script tag
<script type="text/html" id="item_tmpl">
<div>
<div class="grid_1 alpha right">
</div>
<div class="grid_6 omega contents">
<p><b><%=AdTitle%>:</b> <%=AdTitle%></p>
</div>
</div>
</script>
<script src="${URLUtils.staticURL('/js/shoptheAd.js')}"type="text/javascript"></script>
The Script contains the following code
(function(app){
if (app) {
var cache = {};
this.tmpl = function tmpl(str, data){
// Figure out if we're getting a template, or if we need to
// load the template - and be sure to cache the result.
var fn = !/\W/.test(str) ?
cache[str] = cache[str] ||
tmpl(document.getElementById(str).innerHTML) :
// Generate a reusable function that will serve as a template
// generator (and which will be cached).
new Function("obj",
"var p=[],print=function(){p.push.apply(p,arguments);};" +
// Introduce the data as local variables using with(){}
"with(obj){p.push('" +
// Convert the template into pure JavaScript
str
.replace(/[\r\t\n]/g, " ")
.split("<%").join("\t")
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
.replace(/\t=(.*?)%>/g, "',$1,'")
.split("\t").join("');")
.split("%>").join("p.push('")
.split("\r").join("\\'")
+ "');}return p.join('');");
// Provide some basic currying to the user
return data ? fn( data ) : fn;
};
var sitecoresuggestions = {
"suggestions": [
{
"AdTitle": "CheckAd",
"AdDescription": "",
"AdImageUrl": "http://demo-kiehls.loreal.photoninfotech.com/~/media/Advertisement Images/emma-watson-3.ashx",
"Count": 2,
"Hit": 0
},
{
"AdTitle": "CheckAd",
"AdDescription": "",
"AdImageUrl": "http://demo-kiehls.loreal.photoninfotech.com/~/media/Advertisement Images/kate2.ashx",
"Count": 2,
"Hit": 0
}
]
} ;
var show_user = tmpl("item_tmpl"), html = "";
for ( var i = 0; i < sitecoresuggestions.suggestions.length; i++ ) {
html += show_user( sitecoresuggestions.suggestions[i] );
}
console.log(html);
} else {
// namespace has not been defined yet
alert("app namespace is not loaded yet!");
}
})(app);
When the show_user = tmpl("item_tmpl") is executed
i get the error TypeError: document.getElementById(...) is null
on debugging i have figured out that due to some reason
<script type="text/html" id="item_tmpl">
<div>
<div class="grid_1 alpha right">
</div>
<div class="grid_6 omega contents">
<p><b><%=AdTitle%>:</b> <%=AdTitle%></p>
</div>
</div>
</script>
does not get loaded in the browser any ideas why it is not getting loaded even though it is included inside the head tag or any other pointers for the cause of the error
Per the post:
Quick tip: Embedding scripts in your page that have a unknown content-type (such is the case here - >the browser doesn't know how to execute a text/html script) are simply ignored by the browser - and >by search engines and screenreaders. It's a perfect cloaking device for sneaking templates into >your page. I like to use this technique for quick-and-dirty cases where I just need a little >template or two on the page and want something light and fast.
So the page doesn't actually render the HTML, and I would assume you would only have reference to it in the page so that you can extract and apply to other objects or items. And as the blogger states you would use it like:
var results = document.getElementById("results");
results.innerHTML = tmpl("item_tmpl", dataObject);

Categories

Resources