I'm using an animation library that uses "scenes". Scenes are structured like this:
const animations = animationLibrary({
scenes: {
sceneOne: {
appear() {
// do something when scene appears
},
disappear() {
// do something when scene disappears
}
},
sceneTwo: {
appear() {
// do something when scene appears
},
disappear() {
// do something when scene disappears
}
},
}
})
I have a lot of scenes, 40 or so, for a large project. In the past for this project, scope has not been an issue, as I generally do not need to be accessing the same variable in multiple functions, so it works for me to just define a variable inside a function and use it there. However, there are a growing number of cases where I would like to execute a setInterval in one method and a clearInterval in a different function. I am currently doing that like this:
var setIntervalId;
const animations = animationLibrary({
scenes: {
sceneOne: {
appear() {
setIntervalId = setInterval(myFunc, 1000)
},
disappear() {
clearIntervalId(setIntervalId)
}
},
}
})
The problem I am having is as the main scenes object grows, with many scenes inside of it, it seems badly organized to have a number of variables, unrelated to each other, stored outside of the animations const for scope reasons. I have tried writing my own functions inside of a scene, or adding objects inside scenes to store values, but whenever I try to return them inside a function I'm getting undefined errors in the console.
Is there a way for me to store variables or an object of values that is nested inside a single scene and still be able to access and update those variables/values across functions within that same single scene?
You can use Immediately Invoked Function Expression to create a separate variable scope for each of your scene objects.
const animations = animationLibrary({
scenes: {
sceneOne: (function () {
var setIntervalId;
return {
appear() {
setIntervalId = setInterval(myFunc, 1000)
},
disappear() {
clearInterval(setIntervalId)
}
};
})()
}
})
You could probably use factory method createScene() for creating scene objects with less code. But in this case, your animation library must always call appear() and disappear() methods on the scene object (e.g. scenes.sceneOne.appear()), otherwise it will not work due to different meaning of the this keyword.
const animations = animationLibrary({
scenes: {
sceneOne: createScene(
function () {
this.setIntervalId = setInterval(myFunc, 1000)
},
function () {
clearInterval(this.setIntervalId)
}
)
}
})
function createScene(appearFunc, disappearFunc) {
return {
appear: appearFunc,
disappear: disappearFunc,
setIntervalId: undefined
}
}
Just add a property for interval as well.
let animation = {
scenes: {
sceneOne: {
intervalRef:null,
count:0,
appear() {
console.log("Start");
this.intervalRef = setInterval(()=> console.log(this.count++), 1000);
},
disappear() {
console.log("Finish");
clearInterval(this.intervalRef);
},
},
},
};
animation.scenes.sceneOne.appear();
setTimeout(()=>{
animation.scenes.sceneOne.disappear();
},5000)
You could approach the topic with JS Classes
Scene Class
class Scene {
constructor(intervalId, data, callBack) {
this.intervalId = intervalId;
this.data = data;
this.callBack = callBack;
}
appear() {
// do something when scene appears
console.log('appear');
}
disappear() {
// do something when scene disappears
console.log('disappear');
}
getData(key) {
return this.data[key];
}
foo() {
console.log('foo(): bar');
}
bar() {
console.log('class callBack function');
this.callBack();
}
}
Instantiation
const callBack = () => { console.log('passed callBack function'); };
let scene = new Scene(1, {foo: 'bar'}, callBack);
let scene2 = new Scene(2, { foo: scene.getData('foo') }, callBack);
let scene3 = new Scene(3, scene.data, callBack);
In your case
const animations = animationLibrary({
scenes: {
sceneOne: scene,
sceneTwo: scene2,
sceneThree: scene3,
sceneFour: new Scene(4, {lorem: 'ipsum'}, function() {console.log('pass another callback');})
},
});
Output
scene.appear();
// appear
scene.getData('foo');
// "bar"
scene.foo();
// foo(): bar
scene.bar();
// class callBack function
// passed callBack function
Related
Scenario: I am developing a chrome extension and I have a bunch of listeners I need to add in the foreground.
What I would like to do: create an object named 'listeners' containing only functions (functions that will run addListeners), and a function called 'init' that would iterate my 'listeners' object and dynamically execute every function.
Why: I would like to add new listeners to the object without worrying about having to call them directly one by one in my init function. I know it would not be too much of a hassle doing so but it would be interesting if I could make the thing more dynamic.
Is this possible?
Something like:
const listeners = {
func1: function (){...},
func2: function (){...},
func3: function (){...}
}
function init(){
for (let func in listeners){
//somehow execute func
//func() appearently does not work
//()=>func appearently does not work
}
}
init();
The for...in loop iterates the keys of the object, so the func variable is a string, and not a function. To run the function use listeners[func]();.
You can use Object.values() to get an of functions, and then iterate it with for...of:
const listeners = {
func1(){ console.log(1); },
func2(){ console.log(2); },
func3(){ console.log(3); }
}
function init(){
for (const func of Object.values(listeners)){
func()
}
}
init();
Or do the same with Array.forEach():
const listeners = {
func1(){ console.log(1); },
func2(){ console.log(2); },
func3(){ console.log(3); }
}
const init = () => Object.values(listeners).forEach(func => func())
init();
Try this:
const listeners = {
func1: function () {
console.log(1);
},
func2: function () {
console.log(2);
},
func3: function () {
console.log(3);
}
}
function init() {
for (let func in listeners) {
listeners[func]();
}
}
init();
On top of the other answers. I think Array structure is more appropriate in your case.
const listeners = [
function () {
console.log(1);
},
function () {
console.log(2);
},
function () {
console.log(3);
},
];
function init() {
listeners.forEach(func => func());
}
init();
you will just need to do it like that listeners[func]() in the loop , in for in loop, keys are the one iterated, so you call items like that obj/array[key] func in your case
and while you have functions you need to add ().
check inspect in this fiddler link
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');
}
}
How do I stop a function from further execution ? Answers to my this question didn't fulfill my need of stopping/terminating a function from further execution.
I have this kind of hierarchy of Constructor functions :
//First Constructor function
function Foo() {
this.foo1 = async function() {
alert("Foo 1 hs bee called");
return true;
}
this.foo2 = async function() {
alert("Foo 2 hs bee called");
return true;
}
}
//Second Constructor function
function Bar() {
var f = new Foo(),
_this = this;
f.foo1().then(function() {
f.foo2().then(function() {
_this.bar1();
})
})
this.bar1 = function() {
alert("Bar 1 hs bee called");
_this.bar2();
}
this.bar2 = function() {
alert("Bar 2 hs bee called");
}
}
exports.module = {
Bar
}
I have exported the second function as a module, and used that in another file in this way :
//Imported Module
const myModule = require("./algorithm");
//Initiating the Bar() constructor
var x = new myModule.Bar();
This is how it works, but to make it more clear, all the work is done by the Bar() constructor, which hits the Foo() constructor for getting the data.
I hit the Bar() constructor when a button is clicked, but at the same time, when all these functions starts working, I want to terminate it any time, using another button on the same page. Let's say one button will initiate the functions and the other will stop the initiated functions, and this is my problem I can't go through.
I came up with a solution, as #Jared Smith mentioned in the comments, the only thing I have done is to add a Flag which was, TERMINATE_FUNCTION = false. Whenever the stop button is clicked, the TERMINATE_FUNCTION is set to true and on each functions in Foo() constructor it's been checked whether the TERMINATE_FUNCTION === true or not, both in the beginning and return time of the functions, and there it stop the function :
The code is :
//Boolean variable
TERMINATE_FUNCTION = false;
//Listen to event occured
event.on("stopScrapping", function(b) {
TERMINATE_FUNCTION = b;
});
//First Constructor function
function Foo() {
if (TERMINATE_FUNCTION) return;
this.foo1 = async function() {
alert("Foo 1 hs bee called");
if (!TERMINATE_FUNCTION) {
return true;
} else {
return "SCRAPPER_STOPPED";
}
}
this.foo2 = async function() {
if (TERMINATE_FUNCTION) return;
alert("Foo 2 hs bee called");
if (!TERMINATE_FUNCTION) {
return true;
} else {
return "SCRAPPER_STOPPED";
}
}
}
So far it seems goal is to control one NodeJS-based processing(running infinite loop) from completely different process/request(initiating by clicking button in browser).
There are different ways to implement inter-process communication(that is how it's named). One of them is node-ipc
Completely different approach could be moving this continuous operation to some queue manager. This way you might get additional abilities like failproof execution(so you can continue process exactly from point where it broke) or transparent parallelization.
Okay, this is a basic outline of my code
exports.entity = {
name:"Foo",
//Etc...
start:function() {
this.attack();
},
attack:function() {
setTimeout(attack, 1000); //Doesn't work
setTimeout(this.attack, 1000); //Doesn't work
setTimeout(this, 1000); //Doesn't work
}
}
As you can probably see, I would like to call attack() from inside that function using a setTimeout. Unfortunately, everything I've tried doesn't work, and I'm running dry on ideas. I couldn't find anything on the internet. Does anyone know how I can achieve this?
Note:
When I say doesn't work, I mean that it gives me an error saying something like (insert what I tried here, e.g. 'this.attack') is not a function
What I usually do to avoid problems of scope is to split the functions. And then export the object.
const attack = () => {
console.log('attacking');
setTimeout(() => {
stop();
}, 1000)
};
const stop = () => {
console.log('stopping');
}
const start = () => {
attack();
}
module.exports = { start, stop, attack }
Otherwise you can bind(this) as it was suggested in the comments.
setTimeout has own scope that is why It is overriding this
You can use bind or take variable outside setTimeout
//using bind
setTimeout((function() {
this.attack();
}).bind(this));
//using variable
var context = this;
setTimeout(context.attack, 1000);
It can look like this:
let entity = {};
entity.name = "Foo";
// Etc...
entity.start = function() {
this.attack();
}.bind(entity);
entity.attack = function() {
console.log('attack');
setTimeout(this.attack, 1000); //Doesn't work
}.bind(entity);
exports = { entity };
I have a fairly standardised revealing module pattern to create objects for use as instances. Sometimes, within these patterns there are timeouts or intervals that need to be cancelled in the case that the module is no longer being used or referenced within external code.
Simplified example of this pattern:
function test() {
window.timer = maketimer();
}
function maketimer() {
var cls, my;
my = {
increment: 0,
timerid: null,
exec_timer: function() {
my.timerid = window.setInterval(my.time, 2000);
},
time: function() {
console.log("timer: ", my.timerid, my.increment++);
}
},
cls = {
//...
}
my.exec_timer();
return cls;
};
test();
// some time later...
test();
In the case that test is called twice, for whatever reason, the variable window.timer is replaced with a second instance of maketimer but the first instance timer continues to run.
A lot of the time, my modules are intrinsically linked to DOM nodes, and more often than not the DOM nodes are removed with the old instances, so I could in theory check for the non-existence of the node or its placement outside of the DOM, and then cancel the interval in this case.
This is far more generic however, and I would like to be able to deal with timeouts outside of the DOM environment.
In this case I would wrap the whole function in an IIFE that contains an instance variable. In it, you save the timer. And every time a new one is started, the old one is destroyed:
(function(window) {
var timerInstance = null;
window.maketimer = function() {
var cls, my;
if(timerInstance) {
timerInstance.destroyInstance();
}
my = {
increment: 0,
timerid: null,
exec_timer: function() {
my.timerid = window.setInterval(my.time, 2000);
},
time: function() {
console.log("timer: ", my.timerid, my.increment++);
},
destroyInstance: function() {
window.clearInterval(my.timerid);
}
},
cls = {
//...
}
my.exec_timer();
timerInstance = my;
return cls;
}
})(window);
function test() {
window.timer = maketimer();
}
test();
test();
Just out of curiosity, why do you need to have the instance on a global variable? window.timer is pretty generic and could be overridden by other scripts.
Try it: Update your code below..
var settimer;
function test() {
clearTimeout(settimer);
settimer= setTimeout(function () {
window.timer = maketimer();
}, 100);
}
Expanding on the answer given by #nils, I've created the below example for constructing a module which takes in a single DOM node and will only clear the previous instance/timer if the DOM node has already been used:
<div id="element1">[code here that may be updated with XHR]</div>
<div id="element2">[code here that may be updated with XHR]</div>
(function(window) {
var timerInstances = [];
window.maketimer = function(element) {
var cls, my, a;
// find instances where the passed element matches
for (a = 0; a < timerInstances.length; a += 1) {
if (timerInstances[a].element === element) {
console.log("instance already exists for element", element, "destroying...");
timerInstances[a].in.destroyInstance();
}
}
my = {
increment: 0,
timerid: null,
exec_timer: function() {
my.timerid = window.setInterval(my.time, 2000);
},
time: function() {
console.log("timer: ", my.timerid, my.increment++);
},
destroyInstance: function() {
window.clearInterval(my.timerid);
}
},
cls = {
//...
}
my.exec_timer();
timerInstances.push({
'element': element,
'in': my
});
return cls;
}
})(window);
function test(element) {
window.timer = maketimer(element);
}
test(document.getElementById("element1")); // produces 1 timer
test(document.getElementById("element1")); // cancels last, produces 1 timer
test(document.getElementById("element2")); // produces 1 timer
The identifying argument could be anything here - in this case it's a DOM node, but it could easily be a Number, String etc.
The accepted answer is very helpful and is still preferred if you wish to maintain the rule of having one instance at a time on the document.