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.
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 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 am using babel-plugin-rewire (https://www.npmjs.com/package/babel-plugin-rewire) in main.test.js to test non-exported functions in main.js. This has been working except in the case where the function is not referenced in main.js; in this case I get the following error: TypeError: _get__(...) is not a function.
Only after I add a reference to the function in main.js I am able to access it in the test file (it works even if I don't actually call the function). However I do not want to make any changes to main.js. Is this the expected behavior of babel-plugin-rewire, and is there a workaround for this?
//main.js
function test123() {
return true;
}
test123; //Cannot access function in test file unless I add this reference!
//main.test.js
const test123 = require('./main').__get__('test123');
test('test123', () => {
expect(test123()).toEqual(true);
});
You can test non-exported functions in main.js, but the non-exported functions need to be used in at least one exported function in the same file.
In your case, this will work without a reference.
//main.js
export default function foo() {
test123();
}
function test123() {
return true;
}
This is a very simple example of what I am trying to achieve, basically I want to call a function by a string value of it's name, e.g. "hello" should call hello()
I have a helper.js file which contains an exported function e.g.
export function hello() {
console.log('is it me you`re looking for?');
}
I am importing this into another js file for usage
import {hello} from './helper';
I have tried using eval, window and new Function to call my function but no luck
//getting console error "hello is not defined"
eval('hello()');
var fn = window['hello()'];
fn();
var fn = new Function('hello()');
fn();
If I wrap the function like so, the eval fires the wrapper.
function helloWrapper() {
hello();
}
eval('helloWrapper()');
I just cant seem to fire the exported hello() function directly. I have around 10 functions I'll need to fire so having a wrapper for each seems a bit hacky and wondering if there is a way I can achieve this?
Any help would be greatly appreciated if anyone can point me in the right direction.
Thanks in advance
eval("hello()") should work just fine -- but it's not how you should do this. :-)
Instead, build an object containing the functions:
import {hello} from './helper'; // In some environments, these need the
import {groot} from './groot'; // .js on the filenames.
// ...
const functions = {hello, groot/*, ... */};
and then call them like this:
functions[name]();
Live example on plnkr.co
Generally, referring functions by their names is unsafe in client-side code - or any other that can be minified. The approach explained in the question will work only because hello isn't just function name but an import. Due to how ES modules work, import names will be preserved on minification.
In order for a function to be referred by its name, it should be object property. In case of imports there's already such object, it's module export:
import * as helper from './helper';
helper['hello']();
In case there are multiple modules where functions may originate from, there should be intermediate module that re-exports them.
export * from './helper';
export * from './another-helper';
All functions from underlying modules will be available as properties when it's imported as *:
import * as helper from './reexported-helpers';
helper['hello']();
helper['bye']();