I am testing ES6 Modules and want to let the user access some imported functions using onclick:
test.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Module Test</title>
</head>
<body>
<input type="button" value="click me" onclick="hello();"/>
<script type="module">import {hello} from "./test.js";</script>
</body>
</html>
test.js:
export function hello() {console.log("hello");}
When I click the button, the developer console says: ReferenceError: hello is not defined. How can I import functions from modules so that they are available as onclick functions?
I am using Firefox 54.0 with dom.moduleScripts.enabled set to true.
Module creates a scope to avoid name collisions.
Either expose your function to window object
import {hello} from './test.js'
window.hello = hello
Or use addEventListener to bind handler. Demo
<button type="button" id="hello">Click Me</button>
<script type="module">
import {hello} from './test.js'
document.querySelector('#hello').addEventListener('click', hello)
</script>
Module scope in ES6 modules:
When you import a script in the following manner using type="module":
<script type="module">import {hello} from "./test.js";</script>
You are creating a certain scope called module scope. Here is where modules scope is relative to other levels of scope. Starting from global they are:
Global scope: All declarations outside a module on the outermost level (i.e. not in a function, and for const and let not in a block) are accessible from everywhere in the Javascript runtime environment
Module scope: All declarations inside a module are scoped to the module. When other javascipt tries to access these declarations it will throw a reference error.
Function scope: All the variables declared inside (the top level of) a function body are function scoped
Block scope: let and const variables are block scoped
You were getting the referenceError because the hello() function was declared in the module, which was module scoped. As we saw earlier declarations inside module scope are only available within that module, and you tried to use it ouside the module.
We can make declarations inside a module global when we explicitly put it on the window object so we can use it outside of the module. For example:
window.hello = hello; // putting the hello function as a property on the window object
While the accepted answer is correct, it scales poorly once you start importing from multiple modules, or declaring multiple functions . Plus, as noted by #Quentin, it risks global name space pollution.
I prefer a slight modification
import { doThis, doThat } from './doStuff.js'
import { foo1, foo2 } from './foo.js'
function yetAnotherFunction() { ... }
window._allTheFns = { doThis, doThat, foo1, foo2, yetAnotherFunction }
// or, if you prefer, can subdivide
window._doStuffjs = { doThis, doThat }
window._foojs = { foo1, foo2 }
Uses an "unusual" property name to (hopefully) avoid global namespace issues
No need to immediately attach (or forget to attach) an EventListener.
You can even put the listener in the HTML, e.g. <button onclick="window._foojs.foo1(this)">Click Me</button>
As of 2022 I don't know of any other solution than the provided, except that you don't need to use window, but you can use globalThis
I don't know if this makes it any better.
advantages:
it is a tick more readable
you don't get a compiler error when checkJS is on (attribute "..." is not found on window etc...)
I'm even using LitHtml where you have #event notation, looks like this:
<element #click="${(e)=>console.log(e)}"></element>
but sadly there is no clean way to actually get a reference of the object that has the listener attached. the target, originalTarget and explicitOrOriginalTarget can be anything in the bubbling queue, which is super annoying and super confusing, but if you don't need that, LitHTML would be the way to go.
Related
Hi all I'm learning javascript and have run into a problem when defining a function in head utilizing imported functions:
<script type="module">
import {g1} From './scripts/main.js';
myFunction(){
do something, g1 is called
}
</script>
when I call myFunction in body I get a myFunction() not defined error:
<script>
myFunction();
</script>
My understanding is this should work, am I making a mistake with the type attribute in head?
In JS the scope of a given code is executed simply like so:
(1) Global scope > (2) Module scope and then the > (3) Function scope
Meaning that you try to access a module scope variable/function in the global scope, you need to restructure you code someway. Your options are:
Either to put the code within the same module
Or find a way to import one module into the other one
I am testing ES6 Modules and want to let the user access some imported functions using onclick:
test.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Module Test</title>
</head>
<body>
<input type="button" value="click me" onclick="hello();"/>
<script type="module">import {hello} from "./test.js";</script>
</body>
</html>
test.js:
export function hello() {console.log("hello");}
When I click the button, the developer console says: ReferenceError: hello is not defined. How can I import functions from modules so that they are available as onclick functions?
I am using Firefox 54.0 with dom.moduleScripts.enabled set to true.
Module creates a scope to avoid name collisions.
Either expose your function to window object
import {hello} from './test.js'
window.hello = hello
Or use addEventListener to bind handler. Demo
<button type="button" id="hello">Click Me</button>
<script type="module">
import {hello} from './test.js'
document.querySelector('#hello').addEventListener('click', hello)
</script>
Module scope in ES6 modules:
When you import a script in the following manner using type="module":
<script type="module">import {hello} from "./test.js";</script>
You are creating a certain scope called module scope. Here is where modules scope is relative to other levels of scope. Starting from global they are:
Global scope: All declarations outside a module on the outermost level (i.e. not in a function, and for const and let not in a block) are accessible from everywhere in the Javascript runtime environment
Module scope: All declarations inside a module are scoped to the module. When other javascipt tries to access these declarations it will throw a reference error.
Function scope: All the variables declared inside (the top level of) a function body are function scoped
Block scope: let and const variables are block scoped
You were getting the referenceError because the hello() function was declared in the module, which was module scoped. As we saw earlier declarations inside module scope are only available within that module, and you tried to use it ouside the module.
We can make declarations inside a module global when we explicitly put it on the window object so we can use it outside of the module. For example:
window.hello = hello; // putting the hello function as a property on the window object
While the accepted answer is correct, it scales poorly once you start importing from multiple modules, or declaring multiple functions . Plus, as noted by #Quentin, it risks global name space pollution.
I prefer a slight modification
import { doThis, doThat } from './doStuff.js'
import { foo1, foo2 } from './foo.js'
function yetAnotherFunction() { ... }
window._allTheFns = { doThis, doThat, foo1, foo2, yetAnotherFunction }
// or, if you prefer, can subdivide
window._doStuffjs = { doThis, doThat }
window._foojs = { foo1, foo2 }
Uses an "unusual" property name to (hopefully) avoid global namespace issues
No need to immediately attach (or forget to attach) an EventListener.
You can even put the listener in the HTML, e.g. <button onclick="window._foojs.foo1(this)">Click Me</button>
As of 2022 I don't know of any other solution than the provided, except that you don't need to use window, but you can use globalThis
I don't know if this makes it any better.
advantages:
it is a tick more readable
you don't get a compiler error when checkJS is on (attribute "..." is not found on window etc...)
I'm even using LitHtml where you have #event notation, looks like this:
<element #click="${(e)=>console.log(e)}"></element>
but sadly there is no clean way to actually get a reference of the object that has the listener attached. the target, originalTarget and explicitOrOriginalTarget can be anything in the bubbling queue, which is super annoying and super confusing, but if you don't need that, LitHTML would be the way to go.
I have html:
<div id="test" onclick="test()">TEST</div>
Also I have main.js which is included in the file app.js
import "./main"
The file main.js contains a function:
function test() {
alert('Test');
}
But when I click on the test block, I get an error:
Uncaught TypeError: test is not a function
Please tell me how to properly access functions using the attribute onclick?
You need to make the function available to the global context like this:
window.test = function() {
alert('Test');
}
Exposing to global scope via window property will work, but since you have the function in a module, just make an advantage of it. Directive import is meant to be used together with export one (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export):
app.js:
import { test } from "./main"
main.js:
export function test() {
alert('Test');
}
What do we gain? test function doesn't pollute the global scope. It's isolated, but shared between app.js and main.js. However in your case you refer to the function from HTML, meaning addressing global scope. So to make it "best practice", you could do in app.js:
document.querySelector("#test").addEventListener("click", test, false);
and remove onClick in the HTML.
I am curious as to why I do not have access to defined variables in the browser console when I have my script type set to type="module".
Below is a hypothetical set up:
<!DOCTYPE html>
<html>
<head>
<div id="containerFirst">...</div>
<div id="differentContainer">...</div>
</head>
<body>
...
</body>
<script type="module" src="module.js"></script>
<script src="normal.js"></script>
</html>
And here are the two JS files, first module.js:
export const firstContainer = document.getElementById('containerFirst');
and similar variable structure in normal.js:
const otherContainer = document.getElementById('differentContainer');
When I run this in a browser, I can access the variable defined in normal.js by typing it directly into the console but not the one from module.js. I'm hoping to find some clarity on this matter.
Thanks!
You'd be able to access the module variables if you were paused on a breakpoint in the relevant module's code, but not if you aren't. Top-level declarations in modules aren't globals, by design. Each module gets its own scope, kind of like they're a function that gets called just once (that's a rough analogy), so top-level declarations are private to the module's scope (unless you export them, of course).
Among JavaScript libraries (jQuery, MathJax, marked Markdown rendering library, etc.), I often see various patterns:
Pattern #1
index.html
...
<script src="mylibrary.js"></script>
</body>
</html>
mylibrary.js
var mylibrary = (function() {
...
var myVar = ...
...
})();
Pattern #2
index.html
<html>
<head>
...
<script src="mylibrary.js"></script>
</head>
<body>
...
</body>
</html>
Same mylibrary.js
Pattern #3
index.html
<html>
<head>
...
<script src="mylibrary.js"></script>
</head>
<body>
...
<script>
mylibrary.run(); // explicitly needs to be run
</script>
</body>
</html>
Same mylibrary.js
Questions:
What's the purpose of the var mylibrary = (function() { ... })(); trick?
Is there a difference between pattern #1 and #2? (I would say no)
Is there any reason to prefer #3 rather than #2?
It seems that both could be interesting. Example for MathJax: pattern #2 makes sense (it would autoparse the DOM and render math formulas), it's what has been chosen by them (see this example). Pattern #3, requiring an action to actually run the rendering would make sense as well.
The purpose of that is that it's creating a modularized IIFE (Immediately Invoked Function Expression). This is useful to avoid polluting/assigning variables to the global namespace and helps prevent conflicts with other external plugins/dependencies that might appear in your app.
Yes. In the first your scripts are loaded in the head whereas your scripts are loaded in before the closing of the body in #2. It's recommend to load your scripts before the closing of the body so your page doesn't hang as it loads in your javascript files.
Go with #3 over #2. As to why, refer to point #1.
Update
Here's an example of how things would look if you were to simply assign a variable within the global namespace.
var exampleGlobal = "I'm an example global variable";
function one() {
exampleGlobal = 1;
}
....
function two() {
exampleGlobal.charAt(0);
}
one(); // Changes the exampleGlobal to a Number
two(); // Tries to use a string-based instance method but throws an error since exampleGlobal is now a number
Here we see that the exampleGlobal is exposed and available to all functions within this script. In this case it's very easy to err and change the value/purpose of this variable anywhere in your script. Here's where IIFE's come into play:
var exampleModule = (function() {
var privatized = "I'm only accessible within this module";
return {
privatized: privatized
}
}());
In the above we're creating just but one global variable that serves as a module and encapsulates values/functions that would only pertain exclusively to it. Try accessing privatized globally. You'll find that it returns undefined as it's not available within the global scope unless you explicitly invoke the module and it's return value:
var exampleModule = (function() {...}());
console.log(privatized); // undefined
console.log(exampleModule.privatized); // returns: "I'm only accessible within this module"
IIFE's are also advantageous in the fact that you can safely avoid naming conflicts that may occur within your app. As an example, jQuery and Mootools share the $ character to invoke their respective function calls. If you'd like to safely use the jQuery without any conflicts with Mootools you pass the $ object as an argument into your IIFE, which will then shield it from the global scope.
(function($) { // Here this is just the parameter we'd like to refer jQuery as
$('div');
}(jQuery)); // You pass in the actual jQuery object here which is when/where the function is immediately invoked.
You can't access myVar outside mylibrary.
See Should I write script in the body or the head of the html?
Pattern #3 gives you more control of when and how you actually use mylibrary.