this.interval is undefined inside a class function [duplicate] - javascript

This question already has answers here:
"this" keyword in event methods when using JavaScript prototype object
(4 answers)
Closed 3 years ago.
in summarize I want to blink an object material in BJS, so I create two functions blink() and stopBlink():
function blink(obj, delay, box) {
var changeMaterial = function(obj, newMaterial) {
{
obj.material = newMaterial;
}
};
oldMaterial = obj.material;
interval = (function() {
var Toggle = true;
return setInterval(function() {
if (Toggle) changeMaterial(obj, box);
else {
changeMaterial(obj, OriginalStairMaterial);
}
Toggle = !Toggle;
}, delay);
})();
}
function stopBlink(obj, duration) {
setTimeout(function() {
obj.material = oldMaterial;
clearInterval(interval);
}, duration);
}
So I called my function inside JQuery Ready function :
$(document).ready(function() {
$("#btn1").click(function() {
if(Toggle1)
blink(stairMesh,100,boxMaterial[0]);
else
stopBlink(stairMesh,500);
Toggle1=!Toggle1;
}
);
until now everything works good :), I decided to creat a class and implement my functions inside it (because I want to use polymorphism to redefine blink() and stopBlink() functions).
class A {
blink() {}
stopBlink() {}
}
class B extends A {
constructor(oldMaterial) {
super();
this.oldMaterial = oldMaterial;
this.interval = null;
}
blink(obj, delay, box, duration) {
var changeMaterial = function(obj, newMaterial) {
{
obj.material = newMaterial;
}
};
//var oldMaterial=obj.material;
this.interval = (function() {
var Toggle = true;
return setInterval(
function() {
if (Toggle) changeMaterial(obj, box);
else {
changeMaterial(obj, OriginalStairMaterial);
}
Toggle = !Toggle;
},
delay
);
})();
console.log(this.interval);
}
stopBlink(obj, duration) {
setTimeout(function() {
obj.material = oldMaterial;
console.log(this.interval);
clearInterval(this.interval);
}, duration);
}
}
and I create a new object inside ready function:
$(document).ready(function() {
var a = new B();
$("#btn1").click(function() {
if(Toggle1)
a.blink(stairMesh,100,boxMaterial[0]);
else
a.stopBlink(stairMesh,500);
Toggle1=!Toggle1;
}
);
The problem is, when I click the button the blink() function works goos and my object blinks, but when I click again in order to stop blining it continues to blink!
I tried to console.log(this.interval) in blink(), it gives me a number (11), but when I console it in stopBlink() it is undefined!

Your click handlers are not being bound to the context of your class. An easy fix is to bind them inside your constructor. So, you're setting this.interval inside of blink but it only exists in the context of that function. By binding it to the class, this will refer to the context of the class, instead of the function context.
constructor(oldMaterial)
{
super();
this.oldMaterial=oldMaterial;
this.interval=null;
this.blink = this.blink.bind(this);
this.stopBlink = this.stopBlink.bind(this);
}

Related

setInterval calling class method

I cannot make the timer to call my class method. This works fine:
MyInput = class {
constructor(params) {
let input = params.input;
input.addEventListener('focus', this.setTimer);
}
setTimer() {
this.timer = window.setInterval(() => {
console.log('yay');
}, 300);
}
};
but this:
MyInput = class {
constructor(params) {
let input = params.input;
input.addEventListener('focus', this.setTimer);
}
setTimer() {
this.timer = window.setInterval(this.lookupOptions, 300);
}
lookupOptions() {
console.log('yay');
}
};
doesn't call the lookupOptions method but instead, my developer tools in the browser stops debugger every 300ms (checked with different values - always in synch with the timer). After a while, it opens some strange file VM[number] with different numbers. I have no idea why it doesn't work. When I used timer outside the class in the same way it worked fine it seems like some problem with calling the class method but I cannot figure out what the problem might be. Could you please help?
When setInterval calls the function it changes the context of this to the scope of the setInterval, which would be the window object.
Scope the callback in setInterval by wrapping it with an arrow function expression. This prevents the scope from being changed.
MyInput = class {
constructor(params) {
let input = params.input;
input.addEventListener("focus", () => {
this.setTimer();
});
}
setTimer() {
this.timer = window.setInterval(() => {
this.lookupOptions();
}, 300);
}
lookupOptions() {
console.log("yay");
}
}
Or use the .bind() method, that is present on each function object to manually set the scope for this.
MyInput = class {
constructor(params) {
let input = params.input;
const boundSetTimer = this.setTimer.bind(this)
input.addEventListener("focus", boundSetTimer);
}
setTimer() {
const boundLookupOptions = this.lookupOptions.bind(this)
this.timer = window.setInterval(boundLookupOptions, 300);
}
lookupOptions() {
console.log("yay");
}
}
Or use the experimental public fields, which do somewhat the same as arrow function expressions, but as a method of an object.
MyInput = class {
constructor(params) {
let input = params.input;
input.addEventListener("focus", this.setTimer);
}
setTimer = () => {
this.timer = window.setInterval(this.lookupOptions, 300);
}
lookupOptions = () => {
console.log("yay");
}
}

can I call a private function from within the same object javascript

ETA: I don't believe this question is a duplicate to the one linked. I know how to return a private variable (as shown below in the code), this question was about how to call a private function within the same object.
I'm having trouble finding relevant information for javascript specifically. I have an object declared and within that object I have declared four functions (three that are object methods and one that is not).I want to make the fourth one an object method so that I can call it separately from jquery (timer.displayTime(); ) but when I do that startIntervalTimer use of the function stops working. Is what I'm trying to do even possible?
var startStopTime = function() {
//This function is currently called inside startIntervalTimer();
function displayTime() {
//Display correct time
};
//I WANT SOMETHING LIKE THIS INSTEAD BUT (SEE startIntervalTimer)
this.displayTime = function() {
//Display correct time
}
var intervalTimer;
this.startIntervalTimer = function() {
console.log(timeSetMS);
intervalTimer = setInterval(function() {
if(timeSetMS > 0) {
timeSetMS -= 1000;
displayTime(); //THIS IS WHERE AND HOW IT IS CALLED
this.displayTime(); //THIS IS WHAT I'M GOING FOR, BUT IT WON'T WORK
console.log(timeSetMS);
} else if(timeSetMS <= 0) {
clearInterval(intervalTimer);
console.log("timer stopped");
}
}, 1000
);
}
};
and then in the jquery I have:
var timer = new startStopTime();
$("#timer-container, #timer-label").click(function() {
if(power == "off") {
power = "on";
timer.startIntervalTimer();
} else if (power == "on") {
power = "off";
timer.stopTimer();
}
});
//I want to add this, below
$("#session-length").click(function() {
//Change the display
timer.displayTime();
displayTime(); // This won't work obviously because it's out of scope
});
You can declare another variable inside the object, ie. self:
var startStopTime = function() {
//declare self
var self = this;
this.displayTime = function() {
//Display correct time
}
var intervalTimer;
this.startIntervalTimer = function() {
console.log(timeSetMS);
intervalTimer = setInterval(function() {
if(timeSetMS > 0) {
timeSetMS -= 1000;
displayTime();
self.displayTime(); // use self instead
console.log(timeSetMS);
} else if(timeSetMS <= 0) {
clearInterval(intervalTimer);
console.log("timer stopped");
}
}, 1000
);
}
};

Trouble with setInterval in an object's method

I can't figure out why when I call the reset method of the object, the timer is still null. I simplified version of my object is below, followed by the jQuery that constructs a new object and runs the code. See UPPERCASE comments for my specific question points. Thanks!
var countdownTimer = {
// Default vars
milliseconds: 120000,
interval: 1000,
timer: false,
/* ... stuff ... */
countdown: function () {
var root = this;
var originalTime = root.milliseconds;
/* ... stuff */
// IN MY MIND THIS NEXT LINE SETS THE INSTANCE OF THIS OBJECT'S TIMER PROPERTY TO THE setIterval's ID. BUT DOESN'T SEEM TO BE CORRECT. WHY?
root.timer = setInterval(function () {
if (root.milliseconds < 1) {
clearInterval(root.timer); // THIS LINE SEEMS TO WORK
root.countdownComplete(); // callback function
return false;
}
root.milliseconds = root.milliseconds - root.interval;
/* .... stuff ... */
}, root.interval);
},
start: function (ms) {
if (ms) {
this.milliseconds = ms;
}
if(this.timer) {
clearInterval(this.timer); // NOT SURE IF THIS WORKS OR NOT
}
this.countdown();
},
reset: function (ms) {
var root = this;
if(root.timer) {
clearInterval(root.timer); // THIS DOES NOT WORK
} else {
console.log('timer not exist!!!!'); // ALWAYS END UP HERE. WHY?
}
/* .... stuff ... */
},
countdownComplete: function() { }
};
// Setting up click events to create instances of the countdownTimer
$(function () {
var thisPageCountdown = 4000;
$('[data-countdown]').on('click', '[data-countdown-start], [data-countdown-reset]', function () {
var $this = $(this);
var $wrap = $this.closest('[data-countdown]');
// create instance of countdownTimer
var myCountdown = Object.create(countdownTimer);
if ($this.is('[data-countdown-start]')) {
$this.hide();
$('[data-countdown-reset]', $wrap).css('display', 'block');
myCountdown.$wrap = $wrap;
myCountdown.start(thisPageCountdown);
// myCountdown.countdownComplete = function() {
// alert("Updated Callback!");
// };
}
if ($this.is('[data-countdown-reset')) {
$this.hide();
$('[data-countdown-start]', $wrap).css('display', 'block');
// RESET CALLED HERE BUT DOESN'T WORK RIGHT. SAYS myCountdown.timer IS STILL null. WHY?
myCountdown.reset(thisPageCountdown);
}
});
});
When you use var myCountdown = Object.create(countdownTimer); inside of your click function callback you are scoping it only to that callback and once the callback has executed it is garbage collected. You need to only create one instance of the countdownTimer, and it should be outside of your click event handler.
var thisPageCountdown = 4000;
// create instance of countdownTimer
var myCountdown = Object.create(countdownTimer);
$('[data-countdown]').on('click', '[data-countdown-start], [data-countdown-reset]', function () {
var $this = $(this);
var $wrap = $this.closest('[data-countdown]');
TL;DR You can fix your issue by avoiding use of the keyword this in static methods.
When you use the keyword this in a static javascript method, it refers to the item before the last dot from the call point. Example:
foo.bar(); // inside, this will refer to foo
foo.bar.foobar(); //inside, this will refer to foo.bar
var a = foo.bar.foobar();
a(); //this will refer to either null or window - oops
To prevent this behavior, you should always use the fully qualified name reference in static methods instead of relying on the this keyword.
Example from above:
reset: function (ms) {
//var root = this; // don't do this
if(countdownTimer.timer) {
clearInterval(countdownTimer.timer);
} else {
console.log('timer not exist!!!!');
}
/* .... stuff ... */
}

Custom Events in CLASS

I need to launch custom events from CLASS. I know to do this with DOM objects and jquery, using triggerHandler, like $(object)..triggerHandler("inputChange", {param:X});
The problem is when i try this with a Class, like this:
var MyClass = (function(){
var static_var = 1;
var MyClass = function () {
var privateVar;
var privateFn = function(){ alert('Im private!'); };
this.someProperty = 5;
this.someFunction = function () {
alert('Im public!');
};
this.say = function() {
alert('Num ' + this.someProperty);
$(this).triggerHandler("eventCustom");
}
this.alter = function() {
this.someProperty ++;
}
};
return MyClass;
})();
TheClass = new MyClass();
$(TheClass).on('eventCustom', function() {
alert('Event!');
});
TheClass.say();
This doesn't launch warnings or errors, but the events listener is not working (or event is not dispatched). I think the jQuery event system doesn't work with not DOM object, correct?
Any other way (I need events, not callbacks for my specific case) to launch the events?
Thanks a lot!
I wrote an ES6 event class for nowadays in under 100 lines of code without using JQuery. If you don't want to use DOM-events you can extend your class, which should deal with Events.
For listening to events, you can use on, once, onReady, onceReady. On is execute the callbackfunction every time the label is trigger. Once only one time. The "ready"-functions execute the callback, if the label had been already triggerd before.
For triggering an event, use a trigger. To remove an eventhandler, use off.
I hope the example makes it clear:
class ClassEventsES6 {
constructor() {
this.listeners = new Map();
this.onceListeners = new Map();
this.triggerdLabels = new Map();
}
// help-function for onReady and onceReady
// the callbackfunction will execute,
// if the label has already been triggerd with the last called parameters
_fCheckPast(label, callback) {
if (this.triggerdLabels.has(label)) {
callback(this.triggerdLabels.get(label));
return true;
} else {
return false;
}
}
// execute the callback everytime the label is trigger
on(label, callback, checkPast = false) {
this.listeners.has(label) || this.listeners.set(label, []);
this.listeners.get(label).push(callback);
if (checkPast)
this._fCheckPast(label, callback);
}
// execute the callback everytime the label is trigger
// check if the label had been already called
// and if so excute the callback immediately
onReady(label, callback) {
this.on(label, callback, true);
}
// execute the callback onetime the label is trigger
once(label, callback, checkPast = false) {
this.onceListeners.has(label) || this.onceListeners.set(label, []);
if (!(checkPast && this._fCheckPast(label, callback))) {
// label wurde nocht nicht aufgerufen und
// der callback in _fCheckPast nicht ausgeführt
this.onceListeners.get(label).push(callback);
}
}
// execute the callback onetime the label is trigger
// or execute the callback if the label had been called already
onceReady(label, callback) {
this.once(label, callback, true);
}
// remove the callback for a label
off(label, callback = true) {
if (callback === true) {
// remove listeners for all callbackfunctions
this.listeners.delete(label);
this.onceListeners.delete(label);
} else {
// remove listeners only with match callbackfunctions
let _off = (inListener) => {
let listeners = inListener.get(label);
if (listeners) {
inListener.set(label, listeners.filter((value) => !(value === callback)));
}
};
_off(this.listeners);
_off(this.onceListeners);
}
}
// trigger the event with the label
trigger(label, ...args) {
let res = false;
this.triggerdLabels.set(label, ...args); // save all triggerd labels for onready and onceready
let _trigger = (inListener, label, ...args) => {
let listeners = inListener.get(label);
if (listeners && listeners.length) {
listeners.forEach((listener) => {
listener(...args);
});
res = true;
}
};
_trigger(this.onceListeners, label, ...args);
_trigger(this.listeners, label, ...args);
this.onceListeners.delete(label); // callback for once executed, so delete it.
return res;
}
}
// +++ here starts the example +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class TestClassEvents extends ClassEventsES6 {
constructor() {
super();
this.once('sayHallo', this.fStartToTalk);
this.on('sayHallo', this.fSayHallo);
}
fStartToTalk() {
console.log('I start to talk... ');
}
fSayHallo(name = 'Nobody') {
console.log('Hallo ' + name);
}
}
let testClassEvents = new TestClassEvents();
testClassEvents.trigger('sayHallo', 'Tony');
testClassEvents.trigger('sayHallo', 'Tim');
testClassEvents.onReady('sayHallo', e => console.log('I already said hello to ' + e));
testClassEvents.trigger('sayHallo', 'Angie');
testClassEvents.off('sayHallo');
testClassEvents.trigger('sayHallo', 'Peter');
console.log('I dont say hallo to Peter, because the event is off!')
Your understanding of how javascript works is limited since you are approaching it from a traditional OOP point of view. Take a look at this fiddle http://jsfiddle.net/9pCmh/ & you will see that you can actually pass functions as variables to other functions. There are no classes in javascript, only functions which can be closures which can be made to emulate traditional classes:
var MyClass = (function(){
var static_var = 1;
var MyClass = function ( callback ) {
var privateVar;
var privateFn = function(){ alert('Im private!'); };
this.someProperty = 5;
this.someFunction = function () {
alert('Im public!');
};
this.say = function() {
alert('Num ' + this.someProperty);
callback();
}
this.alter = function() {
this.someProperty ++;
}
};
return MyClass;
})();
TheClass = new MyClass(function() {
alert('Event!');
});
TheClass.say();
Alternatively you could create a function in your "class" to configure the callback/trigger instead of passing it into the constructor.
Have a look at this as a start for your further reading on this concept... How do JavaScript closures work?
Edit
To appease those critics looking for an eventQueue here is an updated jsfiddle :)
http://jsfiddle.net/Qxtnd/9/
var events = new function() {
var _triggers = {};
this.on = function(event,callback) {
if(!_triggers[event])
_triggers[event] = [];
_triggers[event].push( callback );
}
this.triggerHandler = function(event,params) {
if( _triggers[event] ) {
for( i in _triggers[event] )
_triggers[event][i](params);
}
}
};
var MyClass = (function(){
var MyClass = function () {
this.say = function() {
alert('Num ' + this.someProperty);
events.triggerHandler('eventCustom');
}
};
return MyClass;
})();
TheClass = new MyClass();
events.on('eventCustom', function() {
alert('Event!');
});
events.on('eventCustom', function() {
alert('Another Event!');
});
TheClass.say();

Change JS function to an object

So I asked a question awhile ago here > Cons of MouseOver for webpages and ran into some issues with enabling/disabling events. According to the answer from the post, I was supposed to update my function as an object to call it out easily. However after a few hours of trial and error as well as online research, I still don't understand how the object works
So this is the function I want to put into an object,
$(function () {
$('#01 img:gt(0)').hide();
setInterval(function () {
$('#01 :first-child').fadeOut(1500)
.next('img').fadeIn(1500)
.end().appendTo('#01');
}, 3000);
});
And this was the code provided to initialize my object,
var Slideshow = (function () {
this.interval;
this.start = function () {
...
initialize
...
// catch the interval ID so you can stop it later on
this.interval = window.setInterval(this.next, 3000);
};
this.next = function () {
/*
* You cannot refer to the keyword this in this function
* since it gets executed outside the object's context.
*/
...
your logic
...
};
this.stop = function () {
window.clearInterval(this.interval);
};
})();
So how exactly should I implement my function into the object so that it works?
I would structure it like this:
function Slideshow(container) {
this.interval = undefined;
this.start = function () {
container.find("img").first().show();
container.find("img:gt(0)").hide();
this.interval = window.setInterval(this.next, 3000);
};
this.next = function () {
var first = container.find(":first-child");
first.fadeOut(1500, function () {
first.next("img").fadeIn(1500);
first.appendTo(container);
});
};
this.stop = function () {
window.clearInterval(this.interval);
};
}
$(function () {
var div = $("#div01");
var slides = new Slideshow(div);
div.hover(function() {
slides.stop();
}, function() {
slides.start();
});
slides.start();
});
DEMO: http://jsfiddle.net/STcvq/5/
latest version courtesy of #Bergi
What you should aim to do, looking at the recommended code, is to move the logic of your setInterval inside the Slideshow.next() function. That basically covers your fadeout, fadein logic.
So your function would look something like:
this.next = function() {
$('#01 :first-child').fadeOut(1500)
.next('img').fadeIn(1500)
.end().appendTo('#01');
};
in the simplest of worlds.
Ideally, you would want to instantiate your Slideshow by telling it which id it should use, by passing that in the constructor. That is, you should be able to call
new Slideshow('#01') as well as new Slideshow('#02') so that you can truly reuse it.
Then, your next function would change to look something like (assuming the id is stored in this.elementId):
this.next = function() {
$(this.elementId + ':first-child').fadeOut(1500)
.next('img').fadeIn(1500)
.end().appendTo('#01');
};
Hope this helps
change syntax to :
var Slideshow = (function () {
return {
interval:null,
start : function () {
// catch the interval ID so you can stop it later on
this.interval = window.setInterval(this.next, 3000);
},
next: function () {
},
stop : function () {
window.clearInterval(this.interval);
}
};
})();
as you are using jquery, a better ans is to create a little plugin:
http://learn.jquery.com/plugins/basic-plugin-creation/

Categories

Resources