Can't write HTML in React Components - javascript

I am using React without Node, or any other thing (I cannot use it, they asked me to do use react in this way in my job... nothing I can do about it), I am using a HTML file with references to different scripts.
So, I managed to make this code work:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Add React in One Minute</title>
<script src="https://unpkg.com/react#16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js" crossorigin></script>
</head>
<body>
<h1>HTML with multiple scripts using React.js</h1>
<p>React.js without Node.js or any other thing</p>
<p>React.js is loaded as script tags.</p>
<div id="like_button_container"></div>
<div id="like_button_container2"></div>
<script src="like_button.js"></script>
<script src="like_button2.js"></script>
</body>
</html>
like_button.js :
'use strict';
const e = React.createElement;
class LikeButton extends React.Component {
constructor(props) {
super(props);
this.state = { liked: false };
}
render() {
if (this.state.liked) {
return 'You liked this.';
}
return e(
'button',
{ onClick: () => this.setState({ liked: true }) },
'Like'
);
}
}
const domContainer = document.querySelector('#like_button_container');
ReactDOM.render(e(LikeButton), domContainer);
like_button2.js (yes is an H1, not a button, just testing):
'use strict';
const f = React.createElement;
class LikeButton2 extends React.Component {
constructor(props) {
super(props);
this.state = { liked: false };
}
render() {
if (this.state.liked) {
return 'You liked this.';
}
return f(
'h1',
{ onClick: () => this.setState({ liked: true }) },
'Like'
);
}
}
const domContainer2 = document.querySelector('#like_button_container2');
ReactDOM.render(f(LikeButton2), domContainer2);
However, I cannot write "normal" HTML in the return of the component, cause it won't render and will give me an error:
return f(
<div>LOL</div>
);
Pic of the error:
https://i.imgur.com/VqxaQof.png
How can I solve this? is really limitating and awful to write HTMl within quotation marks... remember, cannot use Node or any other thing.
And also, ¿how can I render everything within the same div? If I try to render both components on the same div, it only renders the first script but the weird part is that one script is "aware" of the existence of the other (can't use same name for variables, for example).
Thank you in advance, I'm really suffering with this.

The biggest problem is that support for import statements is limited to more modern browsers. So if you want to keep the elements within the same div id="root", then you'll either have to define them within the same file or use a third-party library. That said, JSX isn't valid HTML, so you're either stuck forcing users to transpile the JSX into valid JS for every visit (bad -- slow performance, unoptimized code, and waste of bandwidth), or you're going to want to compile your JSX into a minified/optimized js file:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Add React in One Minute</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="application/javascript">
"use strict";function _instanceof(e,t){return null!=t&&"undefined"!=typeof Symbol&&t[Symbol.hasInstance]?!!t[Symbol.hasInstance](e):e instanceof t}function _typeof(e){return(_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function _classCallCheck(e,t){if(!_instanceof(e,t))throw new TypeError("Cannot call a class as a function")}function _defineProperties(e,t){for(var n=0;n<t.length;n++){var i=t[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}function _createClass(e,t,n){return t&&_defineProperties(e.prototype,t),n&&_defineProperties(e,n),e}function _possibleConstructorReturn(e,t){return!t||"object"!==_typeof(t)&&"function"!=typeof t?_assertThisInitialized(e):t}function _getPrototypeOf(e){return(_getPrototypeOf=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function _assertThisInitialized(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&_setPrototypeOf(e,t)}function _setPrototypeOf(e,t){return(_setPrototypeOf=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}var H1LikeButton=function(e){function t(e){var n;return _classCallCheck(this,t),(n=_possibleConstructorReturn(this,_getPrototypeOf(t).call(this,e))).state={isLiked:!1},n.handleClick=n.handleClick.bind(_assertThisInitialized(n)),n}return _inherits(t,React.Component),_createClass(t,[{key:"handleClick",value:function(){this.setState(function(e){return{isLiked:!e.isLiked}})}},{key:"render",value:function(){return React.createElement(React.Fragment,null,this.state.isLiked&&React.createElement("p",null,"This is liked."),React.createElement("h1",{onClick:this.handleClick},!this.state.isLiked?"Like":"Dislike"))}}]),t}(),LikeButton=function(e){function t(e){var n;return _classCallCheck(this,t),(n=_possibleConstructorReturn(this,_getPrototypeOf(t).call(this,e))).state={isLiked:!1},n.handleClick=n.handleClick.bind(_assertThisInitialized(n)),n}return _inherits(t,React.Component),_createClass(t,[{key:"handleClick",value:function(){this.setState(function(e){return{isLiked:!e.isLiked}})}},{key:"render",value:function(){return React.createElement("div",null,this.state.isLiked&&React.createElement("p",null,"This is liked."),React.createElement("button",{onClick:this.handleClick},!this.state.isLiked?"Like":"Dislike"),React.createElement("br",null),React.createElement(H1LikeButton,null))}}]),t}();ReactDOM.render(React.createElement(LikeButton,null),document.getElementById("root"));
</script>
</body>
</html>
So... compile everything into a single file, or... you'll have to use a third party library that allows importing/exporting js files.
Working repo example (using requirejs): https://github.com/mattcarlotta/standalone-requirejs-example
Some tools to help you:
Compile Babel ES6+ to ES5 JS
Javascript Minifier
In short, it's uncommon to not use some sort of bundler (webpack, rollup, gulp, parcel, browserify, ...etc), so while it's possible to work with JSX in development, you'll still want to compile it for production. By not being able to use node and a bundler, expect to be handicapped throughout development/production.

Related

Is there a way to write html for a web component in a separate file? [duplicate]

I have a web component x-counter, which is in a single file.
const template = document.createElement('template');
template.innerHTML = `
<style>
button, p {
display: inline-block;
}
</style>
<button aria-label="decrement">-</button>
<p>0</p>
<button aria-label="increment">+</button>
`;
class XCounter extends HTMLElement {
set value(value) {
this._value = value;
this.valueElement.innerText = this._value;
}
get value() {
return this._value;
}
constructor() {
super();
this._value = 0;
this.root = this.attachShadow({ mode: 'open' });
this.root.appendChild(template.content.cloneNode(true));
this.valueElement = this.root.querySelector('p');
this.incrementButton = this.root.querySelectorAll('button')[1];
this.decrementButton = this.root.querySelectorAll('button')[0];
this.incrementButton
.addEventListener('click', (e) => this.value++);
this.decrementButton
.addEventListener('click', (e) => this.value--);
}
}
customElements.define('x-counter', XCounter);
Here the template is defined as using JavaScript and html contents are added as inline string. Is there a way to separate template to an x-counter.html file, css to say, x-counter.css and corresponding JavaScript code to xcounter.js and load them in index.html?
Every example I lookup has web components mixed. I would like to have separation of concerns, but I am not sure how to do that with components. Could you provide a sample code? Thanks.
In the main file, use <script> to load the Javascript file x-counter.js.
In the Javascript file, use fetch() to load the HTML code x-counter.html.
In the HTML file, use <link rel="stylesheet"> to load the CSS file x-counter.css.
CSS file : x-counter.css
button, p {
display: inline-block;
color: dodgerblue;
}
HTML file : x-counter.html
<link rel="stylesheet" href="x-counter.css">
<button aria-label="decrement">-</button>
<p>0</p>
<button aria-label="increment">+</button>
Javascript file : x-counter.js
fetch("x-counter.html")
.then(stream => stream.text())
.then(text => define(text));
function define(html) {
class XCounter extends HTMLElement {
set value(value) {
this._value = value;
this.valueElement.innerText = this._value;
}
get value() {
return this._value;
}
constructor() {
super();
this._value = 0;
var shadow = this.attachShadow({mode: 'open'});
shadow.innerHTML = html;
this.valueElement = shadow.querySelector('p');
var incrementButton = shadow.querySelectorAll('button')[1];
var decrementButton = shadow.querySelectorAll('button')[0];
incrementButton.onclick = () => this.value++;
decrementButton.onclick = () => this.value--;
}
}
customElements.define('x-counter', XCounter);
}
Main file : index.html
<html>
<head>
<!-- ... -->
<script src="x-counter.js"></script>
</head>
<body>
<x-counter></x-counter>
</body>
</html>
A generic pattern using top level await without side-effects:
my-component/
element.js
template.html
styles.css
template.html (be sure to link to styles.css)
<template>
<link rel="stylesheet" href="./styles.css" />
<!-- other HTML/Slots/Etc. -->
<slot></slot>
</template>
styles.css (regular CSS file)
:host {
border: 1px solid red;
}
element.js (uses top level await in export)
const setup = async () => {
const parser = new DOMParser()
const resp = await fetch('./template.html')
const html = await resp.text()
const template = parser.parseFromString(html, 'text/html').querySelector('template')
return class MyComponent extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open'}).appendChild(template.content.cloneNode(true))
}
// Rest of element implementation...
}
}
export default await setup()
index.html (loading and defining the element)
<!doctype html>
<html>
<head>
<title>Custom Element Separate Files</title>
<script type="module">
import MyComponent from './element.js'
if (!customElements.get('my-component')) {
customElements.define('my-component', MyComponent)
}
</script>
</head>
<body>
<my-component>hello world</my-component>
</body>
</html>
You can and should make side-effects (like registering a custom element in the global scope) explicit. Aside from creating some init function to call on your element, you can also provide a distinct import path, for example:
defined.js (sibling to element.js)
import MyComponent from './element.js'
const define = async () => {
let ctor = null
customElements.define('my-component', MyComponent)
ctor = await customElements.whenDefined('my-component')
return ctor
}
export default await define()
index.html (side-effect made explicit via import path)
<!doctype html>
<html>
<head>
<title>Custom Element Separate Files</title>
<script type="module" src="./defined.js"></script>
</head>
<body>
<my-component>hello world</my-component>
</body>
</html>
This approach can also support arbitrary names when defining the custom element by including something like this inside define:
new URL(import.meta.url).searchParams.get('name')
and then passing the name query param in the import specifier:
<script type="module" src="./defined.js?name=custom-name"></script>
<custom-name>hello</custom-name>
Here's an example snippet using tts-element that combines all three approaches (see the network tab in dev console):
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>tts-element combined example</title>
<style>
text-to-speech:not(:defined), my-tts:not(:defined), speech-synth:not(:defined) {
display: none;
}
</style>
<script type="module" src="https://unpkg.com/tts-element/dist/text-to-speech/defined.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/tts-element#0.0.3-beta/dist/text-to-speech/defined.js?name=my-tts"></script>
<script type="module">
import ctor from 'https://unpkg.com/tts-element/dist/text-to-speech/element.js'
customElements.define('speech-synth', ctor)
</script>
</head>
<body>
<text-to-speech>Your run-of-the-mill text-to-speech example.</text-to-speech>
<my-tts>Example using the "name" query parameter.</my-tts>
<speech-synth>Example using element.js.</speech-synth>
</body>
</html>
I've wanted a solution for this as well - but didn't find a satisfying way to do this.
I see that fetching the CSS/HTML has been suggested here - but I find this a bit troublesome. For me this seems to be a bit too much overhead and might cause some problems or performant issues.
I wanted to see if I could find other solutions.
"CSS Module scripts" seems to bee coming soon, but browsers like Safari doesn't support this.
Webpack compiling with raw-loader is another solution - or Rollup. But I find that it is too slow or too much config.
I ended up creating my own CLI tool - which I have set up in my IDE (PHPStorm) - so it automatically compiles CSS and HTML into native JavaScript modules that exports ready HTMLTemplate with code.
I have also an example on how to achieve the same in VSCode.
Maybe this could be an alternative approach for some - so I wanted to share it.
It is available as a NPM package here:
https://www.npmjs.com/package/csshtml-module
I can now write HTML files and even SCSS files - which PHPStorm automatically compiles to CSS with Autoprefixer and CSSO (optimizer) - and then it compiles these to a native JS module with template.
Like this:
export const template = document.createElement('template');
template.innerHTML = `<style>button{background-color:red}</style><button>Hello</button>`;
You can set it to compile a single file as well, like CSS - which compiles to a module:
// language=css
export const css = `button {
background-color: red;
}`;

How to load an external script and access it from the main bundle in nextJS

I am working with mercadopago library. It's necessary to load the mercadopago script file and make an instance of the MercadoPago object. The thing is, nextJS loads the main bundle before including the mercadopago file, so I got an execution error because the object is not defined.
I tried different ways, loading the script file into the Head component with a normal tag and with the Next/Script object like:
<script src="https://sdk.mercadopago.com/js/v2" strategy="beforeInteractive"/>
Does not matter what I do, next always loads the script after the main bundle file. If I set a setTimeout to wait to instance the Mercadopago object it runs, but obviously this is not a good option. Which is the correct way to solve this?
Load the script in _document.js before next.js scripts, create _document.js in pages directory to extend the html document the way you like.
import Document, { Html, Head, Main, NextScript } from "next/document";
export default class MyDocument extends Document {
render(){
return (
<Html>
<Head>
/*This is loads the script in head tag of your html document*/
<script src="https://sdk.mercadopago.com/js/v2" strategy="beforeInteractive"/>
</Head>
<body>
/*your script can also go here before loading next scripts*/
<Main />
<NextScript />
</body>
</Html>
)
}
}
OK, I solved this using the onLoad method available on Next/Script component. What I needed to do was to move the script inclusion to my own component and include the component there adding the onLoad props and passing a function that executed my object instance after loading it.
Here my code:
const onload = () => {
const controller = new Controller(props);
setController(controller);
};
const pay = () => controller.load(props.disabled);
const attrs = {onClick: pay};
if (!controller || props.disabled) attrs.disabled = true;
return(
<>
<section className="mercadopago-checkout-pro-component">
<div ref={refContainer} className="cho-container">
<button className="btn btn-secondary" {...attrs}>
Pay
</button>
</div>
</section>
<Script src="https://sdk.mercadopago.com/js/v2" onLoad={onload}/>
</>
);
Go to your component where you need this script.
import Script from 'next/script'
const myComponent = () => {
const [razorpayInstance, setRazorpayInstance] = useState();
const handleLoadScript= () => {
var options: any = {
"key": "myKey"
};
res = new Razorpay(options);
setRazorpayInstance(razorpay)
}
return( <> <Script id="onload-id" src="https://mycdn.com/slugs" onLoad={handleLoadScript} /> </> )};
export default myComponent;

Insert script tag into Angular 4 HTML Component

I am trying to implement the HTML5 version of the PhotoeditorSDK from this website - https://docs.photoeditorsdk.com/guides/html5/v4/introduction/getting_started
I have tried to implement using the Angular Github repo but it would seem that package.json illustrate this only works for Angular 5 & 6 and our app currently is a little outdated being Angular 4.x (and we cannot upgrade to 5 at this time)
Using the HTML5 method is fairly easy in a simple application but within an Angular 4 this seems to be tricky as due to Angular restrictions I am unable to use <script> tags within the component.
In the index <head> I have added the following:
<head>
<!-- React Dependencies for the SDK UI -->
<script src="js/vendor/react.production.min.js"></script>
<script src="js/vendor/react-dom.production.min.js"></script>
<!-- PhotoEditor SDK-->
<script src="js/PhotoEditorSDK.min.js"></script>
<!-- PhotoEditor SDK UI -->
<script src="js/PhotoEditorSDK.UI.DesktopUI.min.js"></script>
<link rel="stylesheet" href="css/PhotoEditorSDK.UI.DesktopUI.min.css"/>
</head>
In the template itself there is a simple <div> as follows :
<div id="editor" style="width: 100vw; height: 100vh;"></div>
The script tag itself looks as following - and would basically attach an image that would show within the editor to edit etc..
<script>
window.onload = function () {
var image = new Image()
image.onload = function () {
var container = document.getElementById('editor')
var editor = new PhotoEditorSDK.UI.DesktopUI({
container: container,
// Please replace this with your license: https://www.photoeditorsdk.com/dashboard/subscriptions
license: '{"owner":"Imgly Inc.","version":"2.1", ...}',
editor: {
image: image
},
assets: {
// This should be the absolute path to your `assets` directory
baseUrl: '/assets'
}
})
}
image.src = './example.jpg'
}
</script>
I am trying to figure out the best way to use <script> above directly within an Angular 4 Component - I know this is best practice but what is the best way to do this?
Your component has an element with id editor. This will only be available in the ngAfterViewInit hook of the component. Once this is called, the window.onload has been called ages ago. Also when the onload is called, the element doesn't exist at all yet, so it's also a bad idea to put it there.
Best would be to call the method from inside the ngAfterViewInit of your component, and use the #ViewChild annotation:
declare const PhotoEditorSDK: any;
#Component({
template: `<div style="width: 100vw; height: 100vh;" #editor></div>`
})
export class EditorComponent implements AfterViewInit {
#ViewChild('editor') editor: ElementRef<HTMLElement>;
ngAfterViewInit(): void {
const image = new Image();
image.addEventListener('load', () => {
const editor = new PhotoEditorSDK.UI.DesktopUI({
container: this.editor.nativeElement,
license: '{"owner":"Imgly Inc.","version":"2.1", ...}',
editor: {
image
},
assets: {
baseUrl: '/assets'
}
});
});
image.src = './example.jpg'
}
}

Rendering Dynamic Components in vue.js?

I'm trying to render a dynamic component in vue.js by faking an ajax call by using set timeout. It should register a new component then set it in the view! I'm using vue.js 1. Please show me a solution as to how I can get this working please.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>test dynamic components</title>
</head>
<body>
<div v-component="{{currentView}}"></div>
<script src="vue-v1.js"></script>
<script>
//Create the 'default' component
Vue.component("default", {
template: '<div>This should be replaced (and will be in 2 seconds)</div>'
});
//Create the parent ViewModel
var vm = new Vue({
el: "body",
data: {
currentView: "default"
}
});
//Pretend to load the data from the server
//This would actually be $.get("/url", data, function(){...});
window.setTimeout(function () {
//Create the new component using the template we received
Vue.component("BoardFeed", {
template: '<div>Template returned from server, what I really want</div><br>And directives work too:<div v-repeat="items">{{$value}}</div>',
data: function () {
return {
items: [1, 2, 3]
};
}
});
//And then change the page to that component
vm.currentView = "BoardFeed";
}, 2000);
</script>
</body>
</html>
ISSUE:
I'm using Vue.js 1 and I can't interpolate the view of my component! I don't think using the v-component even is used anymore
Your code works fine with Vue.js 1 when you make two small changes:
Use <component :is="currentView"></component> to include the components. v-component has been removed quite some time ago in 0.12.0.
Use <div v-for="value in items">{{value}}</div> to iterate over an array. v-repeat has been deprecated in 1.0.
Here's a working JSFiddle.

Typescript window.onload not being called after adding requirejs for phaser game

I am trying to make a game with phaser and Typescript. I followed the instructions here and it worked initialy. The problems came when I tried to modularise my code using AMD and requirejs
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Resonate</title>
<link rel="stylesheet" href="app.css" type="text/css" />
<script src="phaser.js"></script>
<script src="http://requirejs.org/docs/release/2.1.20/minified/require.js" data-main="app"></script>
</head>
<body>
<h1>RESONATE</h1>
<div id="content"></div>
</body>
</html>
Player.ts
export class Player {
color: string;
constructor() {
this.color = "#0000ff";
}
}
app.ts
import player = require("Player");
class PhaserDemo {
game: Phaser.Game;
constructor() {
this.game = new Phaser.Game(800, 600, Phaser.WEBGL, 'content', { preload: this.preload, create: this.create });
}
preload() {
this.game.load.image('phaser_run', 'run.png');
}
create() {
console.log("Before");
var p = new player.Player();
this.game.stage.backgroundColor = p.color;
}
}
window.onload = () => {
console.log("ONLOAD");
var game = new PhaserDemo();
};
Now when I load it up window.onload is never called, I tried following
window.onload not calling function
for the Javascript solution but I can't get it to work in typescript.
Also here is the generated Javascript if you want to take a look https://gist.github.com/koreus7/06bee4a30d22bdc76d62
for the Javascript solution but I can't get it to work in typescript.
Basically if the window is already loaded attaching to window.onload will not call the attached function.
Check for document.readyState === "complete". Alternatively use something like jquery ready : https://api.jquery.com/ready/
I believe it is caused by the require.js loader, it is without I explicitly know it a bit similar to jspm system.js , and so we don't need to ensure that the window is loaded because the loader already has ensured that.
I just removed the window.onload and it worked fine :)

Categories