Value in constructor doesn't change - javascript

I'm kinda new to writing OO JavaScript. I'm trying to do this one just for the sake of learning. My problem is, I defined a service with constructor, but it is ran only once.
Here's the code:
const app = angular.module('app', []);
class ViewportChecker {
constructor() {
this.scrollTop = window.pageYOffset;
}
}
app.directive('sectionModule', ($window) => {
return {
link(scope, el) {
const service = new ViewportChecker,
win = angular.element($window);
win.on('scroll', () => {
console.log(service.scrollTop);
});
}
};
});
I only get 0 in console. What am I doing wrong?

in the second you instantiate ViewportChecker the variable
window.pageYOfsset will probably be 0 (since you didn't scroll yet)
so when you later console.log (service.scrollTop) this variable will still be 0.
you assign this.scrollTop = 0 so to say, hence service.scrollTop will be always 0 in future.
what you can do is for example
constructor() {
this.getScrollTop = function () { return window.pageYOffset; }
}
and
win.on('scroll', () => {
console.log(service.getScrollTop());
});

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");
}
}

How to write a unit test for typed class?

I've been learning to write unit tests of JavaScript/TypeScript code using Jest library. Here's an example I don't know how to approach to. It's typed with TypeScript - there's only two public methods and a constructor which require service1 argument.
I think I need to test two situations:
if this.attr is <= 42 and incrementation happens,
if this.attr is > 42 and method end() fires.
My problems are:
I can't access attr property, it's private and I don't know how to assign any value to it (maybe while making an instance in test but idk how)
I don't know what's this.service1.get() function is. I don't see any implementation of it in code and I don't know how it works. Should I pass it as an argument to an instance of this class?
I got confused should I use fakeTimers or mock/spy in this particular example?
export class Class4 {
private attr: number;
private intervalId;
constructor(private service1) { }
public method() {
this.intervalId = setInterval(() => {
if (this.service1.get() > 42) {
this.end()
} else {
this.attr++;
}
}, 100);
}
public getAttr() {
return this.attr;
}
private end() {
clearInterval(this.intervalId);
}
}
I need your help in writing test in Jest only for 2 situations I described.
Edit.
Here's simple test based on this class. It's not assigning a value of this.attr (my argument's value gets assinged to service1 though) and after running a test I receive an error message
Expected: 40
Received: undefined
Code:
it('should stop incrementing Class4.attr if it\'s > 42', () => {
const class4 = new Class4(40);
const attrVal = class4.getAttr();
expect(attrVal).toBe(40);
});
I'm not very sure this can help but below is an example of how you could use Jest to test something like this.
It's your code translated from typescript to es6 with a light fake Jest implementation attached.
It's in a separate script to leave the example itself alone.
The fake Jest only implements the required Jest matchers in this test: expect, toBeGreaterThan, not, toHaveBeenCalledTimes.
And the following Jest utilities: useFakeTimers, advanceTimersByTime, clearAllTimers, mock
// self calling function is required to simulate Class4 module and for fake Jest mock to work
(function() {
// translated from typescript to es6
class Class4 {
attr = 0;
intervalId = null;
constructor(service1) {
this.service1 = service1;
}
method() {
this.intervalId = setInterval(() => {
if (this.service1.get() > 42) {
this.end();
} else {
this.attr++;
}
}, 100);
}
getAttr() {
return this.attr;
}
end() {
clearInterval(this.intervalId);
}
}
// this is required to simulate Class4 module and for fake Jest mock to work
window.Class4 = Class4;
})();
// even if we do not know exactly what Service is,
// we know that it has a get method which returns a varying number.
// so this implementation of Service will do
// (it's ok since we're testing Class4, not Service)
class ServiceImpl {
v = 0;
set(v) { this.v = v; }
get() { return this.v; }
}
// after this call, jest will control the flow of
// time in the following tests
// (reimplements the global methods setInterval, setTimeout...etc)
jest.useFakeTimers();
// actually it should be jest.mock('<path to your module>')
// but remember we're using a fake Jest working in SO's snippet)
// now Class4 is a mock
jest.mock(Class4);
// we need a Service instance for a Class4 object to be instanciated
const service = new ServiceImpl();
const class4 = new Class4(service);
it('Class4 constructor has been called 1 time', () => {
expect(Class4).toHaveBeenCalledTimes(1);
});
it('should be incrementing Class4.attr if service.get() < 42', () => {
// service.get() will return 40
service.set(40);
// storing initial attr val
let lastAttrVal = class4.getAttr();
// now class4 is running and should be incrementing
class4.method();
// jest controls the time, advances time by 1 second
jest.advanceTimersByTime(1000);
expect(class4.getAttr()).toBeGreaterThan(lastAttrVal);
});
it('should have been called Class4.end 0 time', () => {
expect(Class4.mock.instances[0].end).toHaveBeenCalledTimes(0);
});
it('should stop incrementing Class4.attr if service.get() > 42', () => {
// service.get() will now return 45, this should end class4
// incrementation in the next interval
service.set(45);
// storing current attr val
let lastAttrVal = class4.getAttr();
jest.advanceTimersByTime(1000);
expect(class4.getAttr()).not.toBeGreaterThan(lastAttrVal);
});
it('end should have been called end 1 time', () => {
expect(Class4.mock.instances[0].end).toHaveBeenCalledTimes(1);
});
jest.clearAllTimers();
<script type="text/javascript">
window.jest = {};
jest.useFakeTimers = () => {
jest.oldSetTimeout = window.setTimeout;
jest.oldSetInterval = window.setInterval;
jest.oldClearTimeout = window.clearTimeout;
jest.oldClearInterval = window.clearInterval;
jest.time = 0;
jest.runningIntervals = [];
window.setInterval = (callback, delay) => {
let interIndex = jest.runningIntervals.findIndex(i => i.cancelled);
let inter = interIndex !== -1 && jest.runningIntervals[interIndex];
if (!inter) {
inter = {};
interIndex = jest.runningIntervals.length;
jest.runningIntervals.push(inter);
}
Object.assign(
inter,
{
start: jest.time,
last: jest.time,
callback,
delay,
cancelled: false
}
);
callback();
return interIndex;
};
window.clearInterval = idx => {
jest.runningIntervals[idx].cancelled = true;
};
jest.advanceTimersByTime = advance => {
for (const end = jest.time + advance;jest.time < end; jest.time++) {
jest.runningIntervals.forEach(inter => {
if (!inter.cancelled && jest.time - inter.last >= inter.delay) {
inter.last = jest.time;
inter.callback();
}
});
}
};
jest.clearAllTimers = () => {
jest.runningIntervals.length = 0;
window.setTimeout = jest.oldSetTimeout;
window.setInterval = jest.oldSetInterval;
window.clearTimeout = jest.oldClearTimeout;
window.clearInterval = jest.oldClearInterval;
};
};
jest.resolve = (v) => {
console.log(v ? 'PASS' : 'FAIL');
}
window.it = (description, test) => {
console.log(description);
test();
};
window.expect = (received) => {
return {
toBeGreaterThan: (expected) => jest.resolve(received > expected),
not: {
toBeGreaterThan: (expected) => jest.resolve(received <= expected),
},
toHaveBeenCalledTimes: (expected) => jest.resolve((received ? received.mock.calls.length : 0) === expected),
}
}
jest.mock = (cls) => {
if (cls.mock) return;
const mock = {
instances: [],
calls: []
}
const proto0 = cls.prototype;
function ClassMock(...args) {
mock.calls.push(args);
this.instance = new proto0.constructor(...args);
this.instanceMock = {};
mock.instances.push(this.instanceMock);
Object.getOwnPropertyNames(proto0).forEach((member) => {
if (member === 'constructor' || typeof proto0[member] !== 'function') return;
this.instanceMock[member] = this.instanceMock[member] || { mock: { calls: [] } };
this.instance[member] = (function(...args) {
this.instanceMock[member].mock.calls.push(args);
return proto0[member].apply(this.instance, [args]);
}).bind(this);
});
}
Object.getOwnPropertyNames(proto0).forEach((member) => {
if (member === 'constructor' || typeof proto0[member] !== 'function') return;
ClassMock.prototype[member] = function(...args) {
return this.instance[member](...args);
}
});
ClassMock.mock = mock;
window[proto0.constructor.name] = ClassMock;
}
</script>

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 ... */
}

Typescript: How to create 'lambda function call' for external method?

The Problem
I am picking up Typescript and just learned that lambda functions are used to (edit) set the value of this. However, I'm not sure how to pass my view model's this into a function that calls another method that I have not defined. In my case, I'm trying to call a Knockout method. See example:
Desired JavaScript:
var MyViewModel = (function () {
function MyViewModel() {
var _this = this;
...
this.someMethod = function () {
ko.utils.arrayForEach(this.array1(), function (item) {
while (item.array2().length < _this.array3.length) {
item.array2.push(12345);
}
});
};
...
Actual JavaScript:
var MyViewModel = (function () {
function MyViewModel() {
var _this = this;
...
this.someMethod = function () {
ko.utils.arrayForEach(_this.array1(), function (item) {
while (item.array2().length < this.array3.length) {
item.array2.push(12345);
}
});
};
...
TypeScript:
method = () => {
ko.utils.arrayForEach(this.array1(), function(item){
while(item.array2().length < this.array3().length){
item.array2.push(0);
}
})
}
One Solution...
One solution I've used was to manually set this.array3().length to _this.array3.length(), but that's pretty hacky and I do not like it.
How should I go about passing the right this into my inner function?
You need to use another lambda to continue the chain of this :
method = () => {
ko.utils.arrayForEach(this.array1(), (item) => { // NOTE here
while(item.array2().length < this.array3().length){
item.array2.push(0);
}
})
}
Tips on this in TypeScript : https://www.youtube.com/watch?v=tvocUcbCupA&hd=1

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