I'm trying to use requireJS but I want to build a hierarchy of dependencies: main requires obr.platcom and obr.platcom requires obr (for example).
I have this hierarchy of files:
- index.html
-> js
- main.js
-> lib
- jquery.js
- require.js
-> obr [my own 'libraries']
- obr.js
- obr.platcom.js
index.html
<!DOCTYPE html>
<html lang="es">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<title>Platcom</title>
<script type="text/javascript" src="js/lib/jquery.js"></script>
<script data-main="js/main" src="js/lib/require.js"></script>
</head>
<body>
</body>
</html>
main.js
$(document).ready(function() {
require(["obr/obr.platcom"], function() {
obr.hola();
var myPlatcom = obr.platcom();
myPlatcom.test();
});
});
obr.js
var obr = {};
obr.hola = function() {
alert('Hola OBR');
};
obr.platcom.js
require(["obr.js"],function() {
obr.platcom = function(params) {
var that = {};
var test = function test() {
alert('Hola Platcom!');
}
that.test = test;
return that;
}
});
If I require both obr and obr.platcom files in the main all works, but if I use this nested style I receive the next error:
Uncaught ReferenceError: obr is not defined main.js:3
Do you know what I'm doing wrong?
Thank you in advance.
Alright, you've done several things wrong.
You need to specify as an argument the dependency you're injecting. For example, require(["obr/obr.platcom"], function() { won't do much unless you specify how the required module can be called. You should need this:
require(["obr/obr.platcom"], function( obr ) {
This way, you know in which variable your required object is.
The obr.js variables are in the global scope. You need to wrap them in a require or define function call. The following would work:
define(function() {
var obr = {};
obr.hola = function() {};
return obr;
});
You may have noticed some things that are wrong with your last file.
If you want your module to be imported somewhere, you have to define it. So you have to use the define function, not the require one. And the define function must return an object. Here is a fixed obr.platcom.js file:
// If you don't use "define" for the obr.js file too, this won't work
define(['obr'], function( obr ) {
obr.platcom = function() {};
// Don't forget to return the obr object, or the require of
// the main file won't return anything
return obr;
});
This way, things are done the correct way. Or at least, the way require.js wants you to do stuff.
I hope this reveals you how require.js can be effectively used to easily separate your code in modules :)
Related
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;
}`;
In my app, we are using a third party analytics library, which, apart from other things, is also using postMessage to post some info to the parent window (possibly for iframe use cases for the analytics library).
In our case, this info is sensitive and we do not want to send it to parent window (if our app is opened in an iframe or as a child window by someone) as we wouldn't have any control over what the parent window does with this information (even for valid parents). There is no configuration in the library to switch off this functionality.
As a potential solution, we figured out that if we can intercept all postMessages being sent out to the parent window and just filter this message , it would solve our purpose for now. To achieve this, I have been looking at proxying window.postMessage or redefining the method for adding this validation.
I am just trying to understand if this is feasible, and how?
is it possible from the current window to override the parentWindow.postMessage (or for that matter, override a childWindow.postMessage?)
What have i tried so far:
From window A, I am opening a window B, and then sending a postMessage from windowA to windowB. (This requires a reference to windowB in windowA, so that I can do windowB.postMessage)
If I proxy window.postMessage, it is not intercepting the windowB.postMessage call.
If I proxy windowB.postMessage, it is intercepting the windowB.postMessage call.
I am just trying to understand if the same can work for parentWindow, and what are the other constraints (same origin, etc)
codesandbox link: https://codesandbox.io/s/postmessage-forked-slv9b?file=/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Static Template</title>
</head>
<body>
<input class="s" type="text" />
<button class="s">send</button>
<script>
const handler = {
apply: function (target, thisArg, argumentsList) {
console.log(`window post message proxy: ${argumentsList[0]}`);
return target(...argumentsList);
}
};
const write = (str) =>
document.body.insertAdjacentHTML("beforeend", str + "<br>");
write("<hr>");
// --
const windowFeatures =
"menubar=yes,location=yes,resizable=yes,scrollbars=yes,status=yes";
/** #type { Window | undefined } */
let subWindow;
/** #type { HTMLButtonElement } */
const button = document.querySelector("button.s");
/** #type { HTMLInputElement } */
const input = document.querySelector("input.s");
button.onclick = () => {
if (!subWindow) {
subWindow = window.open("/reciver.html", "shipping", windowFeatures);
setTimeout(() => {
// THIS HANDLING HERE
subWindow.postMessage = new Proxy(subWindow.postMessage, handler);
subWindow.postMessage(input.value, window.location.origin);
}, 3000);
} else {
subWindow.postMessage(input.value, window.location.origin);
}
write(input.value);
};
</script>
</body>
</html>
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.
I'm exploring web-components custom HTML elements, and I am running into a problem adding custom attributes to my custom elements: any value I set in the markup is never honored.
For a simple element like this, which should show the text supplied in the "flagtext" attribute, it does always show the default value.
<test-flag flagtext="my text"></test-flag>
Full JSBin sample is here.
The JSBin uses the Polymer library (as this is the only thing I can pull in there). I am using webcomponents.js generally, same result. Both in Chrome 49 and in Firefox 45 gives same result. There is no error showing in the console or debugger.
Every sample I find on the web has similar code but I tried various versions and it always refuses to update. I even copied a few samples into JSBin and they do not work either.
What could be wrong? I understand it is experimental technology but the consistency with which this isn't working is still surprising. Has this standard been abandoned? (I see that the latest April 2016 W3C draft of custom elements has entirely changed its approach.)
When I define "attributeChangedCallback" function, it does not fire.
I can easily modify the property via Javascript, this is not a problem.
But why can I not specify the property in markup, as I am supposed to?
Edit - full code
Note that you'll need to put these into separate files for the HTML import, and that you need the "webcomponents-lite.js" library.
Main HTML file
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
test-flag
{
border: 1px solid blue;
}
</style>
</head>
<body>
<script src="lib/webcomponents-lite.min.js"></script>
<link rel="import" href="test-flag.html">
Here is the custom test-flag component:
<p>
<test-flag flagtext="my text"></test-flag>
</body>
</html>
File: test-flag.html
<template>
<style>
</style>
Content:
<div id="testflagID">
</div>
</template>
<script>
(function() {
var _currentScript = (document._currentScript || document.currentScript);
var importDoc = _currentScript.ownerDocument;
var customPrototype = Object.create(HTMLElement.prototype);
Object.defineProperty(customPrototype, 'flagtext', {value: '(default)', writable: true});
document.registerElement('test-flag', {prototype: customPrototype});
customPrototype.createdCallback = function()
{
var template = importDoc.querySelector('template');
var clone = document.importNode(template.content, true);
var idx = clone.querySelector("#testflagID");
idx.textContent = this.flagtext;
var root = this;
var createdElement = root.appendChild(clone);
};
})();
</script>
There are 2 concepts that are not automatically linked together.
In the HTML code:
<test-flag flagtext="my text"></test-flag>
...term flagtext is an HTML attribute (not a property).
In the JavaScript code:
Object.defineProperty(customPrototype, 'flagtext', {value: '(default)', writable: true})
...term flagtext is a JavaScript property (not an attribute).
For standard elements, the browser automatically binds the property value to the attribute value (and vice versa). For Custom Elements, too (with standard attributes). But if you want to add a custom attribute, you'll have to bind it manually.
For example, in the createdCallback() method, add:
this.flagtext = this.getAttribute( 'flagtext' ) || '(default)'
Live sample:
document.registerElement( 'test-flag' , {
prototype: Object.create( HTMLElement.prototype, {
flagtext: {
value: '(default)',
writable: true
},
createdCallback: {
value: function()
{
this.flagtext = this.getAttribute( 'flagtext' ) || this.flagtext
this.innerHTML = 'flagtext=' + this.flagtext
}
},
} )
} )
<test-flag flagtext='new content'>Hello</test-flag>
NB: the attributeChangedCallback() method is fired only when an attribute is changed after element creation (which is not the case here).
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 :)