I'm relatively new to the latest and greatest of Javascript (as a holdover from my IE6 days) and I dont fully understand the syntax. I am writing a Wordpress theme using Sage's Roots. The way they set up the JS is that each page gets a boilerplate JS file
home.js
export default {
init() {
},
finalize() {
},
}
Where init is called on page load and finalize on unload. I'm trying to break up my init into functions but I cant figure out the scoping issues.
export default {
init() {
let trigger = document.body;
trigger.addEventListener('click', function() {
// how do i call 'something'?
});
},
finalize() {
},
something() {
console.log('something happened');
}
}
Normally, I might create a variable in a higher scope and save this upon init, e.g., var klass = this and reference it using klass.something() but I cant figure out where to even put that line.
How do I reference the something method when this has been overwritten in a different scope?
EDIT: Also noteworthy: I want to avoid polluting the global namespace.
As you're exporting an object, you can an arrow function. Arrow functions don't define their own this and so you can use the this of the object.
export default {
init: () => {
let trigger = document.body;
trigger.addEventListener("click", () => {
this.something();
});
},
finalize: () => {},
something: () => {
console.log("something happened");
}
};
Arrow function in Javascript are lexically scoped, they define 'this' based on where they are written.
Instead going for arrow function go with regular function declaration which is 'dynamically scoped' and here you will be able to call 'something()' using 'this'
Change you code like below -
export default {
init() {
let trigger = document.body;
trigger.addEventListener('click', function () {
this.something();
});
},
finalize() {
},
something() {
console.log('something happened');
}
}
Related
I am currently working on a project where I want to deference an array of functions (function references) and excecute the function.
This does only work, if I don't call another class method within the function.
Otherwise I get "Uncaught TypeError" and I can't figure out how to solve this error.
Here's my code sample 'working' the same way my original project does:
After calling function2 the engine cannot find this.log...
Do you have ideas? Thank you very much in advance.
KR, Robert
class ArrayWithFunctions {
constructor() {
this.functionTable = [
this.function1,
this.function2,
];
}
execute(index) {
return (this.functionTable[index])();
}
log(chars) {
console.log(chars);
}
function1() {
console.log('I am Function 1.');
}
function2() {
this.log('I am Function 2.');
}
}
let example = new ArrayWithFunctions();
example.execute(0);
example.execute(1);
This is an example of Javascript's execution contexts in action. In this situation, to avoid losing the correct reference to the class, you can bind the functions when putting them inside the array, or initialize them as arrow functions:
Example 1: Bind them in the constructor:
constructor() {
this.functionTable = [
this.function1.bind(this),
this.function2.bind(this),
];
}
Example 2: Create them as arrow functions:
class ArrayWithFunctions {
// ...
function1 = () => {
console.log('I am Function 1.');
}
function2 = () => {
this.log('I am Function 2.');
}
}
You can use arrow functions to dodge scoping issues:
function2 = () => {
this.log('I am function 2.');
}
Related: How to access the correct `this` inside a callback (and you might also want to take a look at How does the "this" keyword work?).
In this case you can simply set the correct this value by calling the function with .call:
return this.functionTable[index].call(this);
Does anyone know if is it syntactically possible to apply the parameter properties as local scope methods for the parent function to reference.
The module is very large which increase page load times and shouldn't be imported via import() at top level.
import {method1, method2, method3} from "./module.js" //not an option
//Working example.
$('some-id').on('click', () => {
import ("./module.js")
.then( (module) => {
module.method();
module.method2();
module.method3();
// module methods...
});
}
//Something along these lines.
$('some-id').on('click', () => {
import ("./module.js")
.then( ( module ) => {
method();
method2();
method3();
//Apply the module methods to function scope without direct reference to the parameter
//this would save some time and loads of repetition if possible, question is can anything similar be done.
});
}
This would be pretty similar!
$('some-id').on('click', async () => {
const { method, method2, method3 } = await import("./module.js");
method();
method2();
method3();
});
Is there a way in which I can make functions that are specific to the template and accessible by the template's event handlers? I don't want to pollute the global namespace.
I came upon this problem when I first noticed that I was repeating a lot of code between my event handlers in a manner similar to this:
Template.options.events({
"click .btn-1": function(e) {
// do something
var div;
div = $(e.target);
if (div.hasClass("active")) {
return div.removeClass("active");
} else {
return div.addClass("active");
}
},
"click .btn-2": function(e) {
// do something else
var div;
div = $(e.target);
if (div.hasClass("active")) {
return div.removeClass("active");
} else {
return div.addClass("active");
}
}
});
Note that I'm not trying to find a way to combine selectors, I already know how to do that. Notice that each button does something different, but have a few lines of repeated code. I want to keep this as DRY as possible without polluting the global namespace.
Each file in a Meteor project has its own local scope. If you define a function in one file using function blah() { ... } or var blah = function () { ... }, it will be inaccessible outside of that file. Only blah = function () { ... } (without var) defines a true global.
If this isn't enough, you could define the local function in an immediately-invoked function expression (IIFE):
(function () {
function toggleActive(div) {
if (div.hasClass("active")) {
div.removeClass("active");
} else {
div.addClass("active");
}
}
Template.options.events({
"click .btn-1": function (e) {
// do something
toggleActive($(e.target));
},
"click .btn-2": function (e) {
// do something else
toggleActive($(e.target));
}
});
})();
// toggleActive is inaccessible here
This would be better explained in code:
var FileResourceManager = {
LoadRequiredFiles: function (config) {
config = config || {};
this.OnLoading = config.onLoadingCallback;
this.OnComplete = config.onCompleteCallback;
//this works fine here.
if (this.OnLoading) {
this.OnLoading();
}
Modernizr.load([{
load: 'somefile.js',
complete: function () {
//Error in this callback here.
if (this.OnComplete) {
this.OnComplete();
}
}
});
}
};
FileResourceManager.LoadRequiredFiles({
onLoadingCallback: function () {
alert('started');
},
onCompleteCallback: function () {
alert('complete');
}
});
As you can see, in the callback for Modernizr.load's complete event, I want to call the method of the parent/outer object. But this actually became the Modernizr object. How can I access the properties of the outer object inside an event?
I've seen this done in the backbone.js project, by using some form of binding. I'm not sure if I need to write something like this.
var self = this;
Modernizr.load([{
load: 'somefile.js',
complete: function () {
//Error in this callback here.
if (self.OnComplete) {
self.OnComplete();
}
}
});
Modernizr.load([{
load: 'somefile.js',
complete: (function () {
//Error in this callback here.
if (this.OnComplete) {
this.OnComplete();
}).bind(this)
}
});
The this object redefined in the scope of the Modernizr function. It is customary to define a variable called that outside the scope and use it to refer to the outer this.
Alternatively, you could just use the config object, like this:
complete: function () {
if (config.OnComplete) {
config.OnComplete();
}
}
var Test = (function() {
return {
useSub: function () {
this.Sub.sayHi();
},
init: function () {
$(document).ready(this.useSub);
}
};
})();
Test.Sub = (function () {
return {
sayHi: function () {
alert('hi');
}
};
})();
Test.useSub(); // works
Test.init(); // explodes
Above I am trying to create a Test namespace and add an object Sub to it. I was doing fine until I tried using the object in jQuery. The error is "Uncaught TypeError: Cannot call method 'sayHi' of undefined". If there is a better way to do this, I am open to it.
Edit:
Obviously this was demo code. In my real application the solution that I went with because I think it is the most clear is this one:
var Namespace (function () {
return {
init: function () {
$(document).ready(function() {
Namespace.onReady();
}
},
onReady: function() {
alert('Now I am back in the Namespace scope. Proceed as planned');
}
};
})();
Edit2: All jQuery callbacks seem to require they are used in this manner or else the scoping is screwed up.
I think it is a scope problem. If you do
$(document).ready(this.useSub);
then this.useSub will be executed in the window scope (so inside the function, this refers to the window object) and there doesn't exist a Sub attribute.
Try:
init: function () {
var obj = this;
$(function(){obj.useSub()});
}
For some reason it does not work using $(document).ready(function(){obj.useSub()}); but it works with the $() shortcut.
Here is one way
var Test = {
useSub : function () {
Test.Sub.sayHi();
},
init: function () {
$(document).ready(Test.useSub);
},
Sub: {
sayHi: function () {
alert('hi');
}
}
};
in this line:
$(document).ready(this.useSub);
you're passing a reference to a function and the scope is lost- when the function runs, this no longer means Test.