Javascript Syntax Confussion - javascript

// The keys and notes variables store the piano keys
const keys = ['c-key', 'd-key', 'e-key', 'f-key', 'g-key', 'a-key', 'b-key',
'high-c-key', 'c-sharp-key', 'd-sharp-key', 'f-sharp-key', 'g-sharp-key', 'a-
sharp-key'];
const notes = [];
keys.forEach(function(key){
notes.push(document.getElementById(key));
})
// Write named functions that change the color of the keys below
const keyPlay = function(event){
event.target.style.backgroundColor = "#ababab";
}
const keyReturn = function(event){
event.target.style.backgroundColor = "";
}
// Write a named function with event handler properties
function eventAssignment(note){
note.onmousedown = keyPlay;
note.onmouseup = function(){
keyReturn(event);
}
}
// Write a loop that runs the array elements through the function
notes.forEach(eventAssignment);
LINE-17 and LINE-18 serve similar purposes by triggering event handlers well the instructor tells me not to use this syntax at LINE-17 even though it works fine. he sort of mentions something which completely hops over my mind "we can't define note.onmousedown to the keyPlay function as it would just redefine the function (i have no idea which function is he referring to as being redefined)"
Any help would be appreciated.

First line will call keyPlay directly on mouse down, meanwhile the second one will create a function that then will call keyReturn. The second line is actually wrong as event is undefined (you have to declare it in function's input). I prefer the first line as it allows you to keep code cleaner.

Related

Why is Visual Studio Code recognizing my function as a constructor function?

Description of Issue
I was reviewing my code for one of my projects when I noticed that one of my function's name is coloured green while all of the other functions are yellow.
When I hover over the function in the readButton.addEventListener("click", toggleRead); it says: Class toggleRead - function toggleRead(e: any): void
Also, when I hover over the actual function declaration, it says This constructor function may be converted to a class declaration.ts(80002)
Additional Info
I am using the toggleRead function to toggle an attribute. The this keyword refers to the button that calls the function. It doesn't seem to cause any issues in my program and it is working as intended.
Question
Am I breaking some kind of code convention that results in this hint, or am I otherwise doing something wrong? Also, why does visual studio think this function is a constructor?
Similar Issue
I found this question on stack overflow but the person asking the question is only interested in turning the hint off. I want to know if I did something wrong.
Code
readButton.addEventListener("click", toggleRead);
// Toggle book read
function toggleRead(e) {
const bookIndex = getBookIndex(this);
const bookObject = library.at(bookIndex);
if (bookObject.read) {
bookObject.read = false;
this.setAttribute("is-read", "false");
this.innerHTML = "Unread";
} else {
bookObject.read = true;
this.setAttribute("is-read", "true");
this.innerHTML = "Read";
}
}
As mentioned in the comments, the analyzer probably marks it as a constructor because of how it's defined + what is inside of it, which resembles how a constructor would be written.
A solution may be to define it using const and a lambda expression, as follows.
Change the first line:
function toggleRead(e) {
to this:
const toggleRead = (e) => {
An additional benefit of using lambda notation is that you can use such functions without first calling bind on them.
So if you are now doing this.toggleRead = this.toggleRead.bind(this); then you can safely remove it because the this inside a lambda is by default always the object in which the lambda is being created.
I wanted to post Raymond's recommendation from their comment under my question as this resolved the issue for me:
The solution was to replace this with the more idiomatic e.currentTarget.
// Toggle book read
function toggleRead(e) {
const bookIndex = getBookIndex(e.currentTarget);
const bookObject = library.at(bookIndex);
if (bookObject.read) {
bookObject.read = false;
e.currentTarget.setAttribute("is-read", "false");
e.currentTarget.innerHTML = "Unread";
} else {
bookObject.read = true;
e.currentTarget.setAttribute("is-read", "true");
e.currentTarget.innerHTML = "Read";
}
}

Trigger function from within formatter? (responsiveLayout: collapse > toggleList)

Is there a way to trigger a function from within a rowFormatter? I'm using the responsiveLayout: "collapse"-option, and I really like it.
However, I would like to trigger the toggleList function (or what's it's called.... 1 from '19)
I would like to not go the .click() way, so I created my own (rip-off) solution within the rowClick:
let isOpen = row._row.modules.responsiveLayout.open;
var collapseEl = row._row.element.querySelector('div.tabulator-responsive-collapse');
if (!(isOpen)) {
collapseEl.classList.add("open");
if (collapseEl) {
collapseEl.style.display = '';
}
} else {
collapseEl.classList.remove("open");
if (collapseEl) {
collapseEl.style.display = 'none';
}
}
row._row.modules.responsiveLayout.open = !(isOpen);
But... There must be a good way to trigger toggleList(), instead of writing a rip-off function, which doing the same thing...
I've tried to look through the values and functions in row._row, with no luck. I'm 99.7% sure that I missed this part in the documentation........ But I've really tried to search the best I could.
TL;DR: I would like to trigger the toggleList() function defined within formatter, in my rowClick() event-function. Is that possible?
There is no toggleList function built into Tabulator.
In the example you reference there it is simply a function called toggleList that is defined inside the row formatter and triggered when an element added by the row formatted is clicked.
Because the toggleClick function is defined inside the row formatter its scope is limited to that formatter function so it cannot be accessed from outside it.
one way to get around this would be to assign the function to a property on the row data object then you could access it from else where in the table.
So if we take the example you provided a link to and at the top of the customResponsiveCollapseFormatter function add the following:
var data = cell.getData(); //retrieve the row data object
Yhen where we define the toggleList function, instead of the simple function definition we can assign it to a property on the data object, lets call it collapseToggle, we will also tweak it so it dosnt need the isOpen property passed in and insted flips the state of the open variable itself, that way it can be called from anywhere outside the formatter without knowledge of the current state:
data.collapseToggle = function toggleList(){
open = !open;
Then in our cellClick function we can check to see if the collapseToggle property is defined on the row data and then call it:
cellClick:function(e, cell){
var data = cell.getData();
if(data.collapseToggle){
data.collapseToggle();
}
}

JavaScript Adding to requestAnimationFrame function

The question could be referred to Three.JS framework, but I think it's a general JS problem.
animate();
function animate() {
{doSomething}
requestAnimationFrame( animate );
}
So the function animate() runs every frame and does something.
The question is how to add {doAnotherSomething} to animate() on the fly?
function anEventHappened(){
//adding some methods to animate
}
There are several ways to do this. But you'll somehow have to foresee something in this animate function that can detect that more needs to be done. This "something" could be an array with functions to execute as extras. Initially that array would be empty, but upon some event you could add a function to that list (or remove one from it).
Here is a practical example:
let container = document.querySelector("pre");
let todoList = []; // <--- list of functions to execute also as extras
animate();
function animate() {
todoList.forEach(f => f()); // execute whatever is in the todo-list
moveText(container);
requestAnimationFrame(animate);
}
document.querySelector("button").onclick = function anEventHappened() {
let newContainer = document.createElement("pre");
newContainer.textContent = Math.random();
document.body.appendChild(newContainer);
todoList.push(() => moveText(newContainer)); // add something extra
}
// Helper function to perform some HTML-text manipulation
function moveText(what) {
what.textContent = what.textContent.includes("<") ? what.textContent.slice(1)
: what.textContent.length < 60 ? " " + what.textContent
: what.textContent.replace(/ (\S)/, "<$1");
}
<button>Do extra</button>
<pre>phrase</pre>
It's quite easy once you know what to look for.
A simplest solution is to keep a collection of functions to be called every time (a collection of strategies)
In order not to share this collection among the whole code base you can apply Subscriber - Publisher approach where your module containing animate subscribes to events in the system that shall change what is to be rendered on the screen.
Here's a good reference about basic design patterns: https://refactoring.guru/design-patterns/
So... going back to your original question, what you can do is to send an event with a callback in it dispatchEvent({ type: 'render'; strategy: <some_function> }) and animate's module will listen to such events .addEventListener('render', event => strategies.push(event.strategy)).
Remarks:
Don't forget to remove old strategies.
strategies could be a dictionary instead of an array. In that case event render should also have some sort of a key to prevent multiple identical strategies from been pushed to the "queue"
https://threejs.org/docs/index.html#api/en/core/EventDispatcher

Javascript not setting this to value with apply or call

Edit: the code below was made up on the spot to show how I was going about what I was doing. It definietely won't run, it is missing a lot of things.
Here is a working example in codepen: https://codepen.io/goducks/pen/XvgpYW
much shorter example: https://codepen.io/goducks/pen/ymXMyB
When creating a function that is using call or apply, the this value stays null when using getPerson. however, when I use apply or call with getPerson it returns the correct person.
Please critique, I am really starting to learn more and more. I am in the middle of a project section so it might be hard to change all the code, but my next project could implement this better.
call and apply are setting to the window and not the object.
I will provide code that is much simpler with the same concept of what I am talking about.
function createPerson(){
this.manager = null;
this.teamManager = null;
this.setTeamManager = function(val){
this.teamManager = val;
}
this.setManager = function(val){
console.log('setting manager to',val);
this.teamManager = val;
}
this.getTeamManager = function(){
console.log('setting team manager to',val);
return this.teamManager ;
}
this.getManager = function(){
return this.manager;
}
this.appendSelect = function(elem){
var that = this;
createOtherSelects(that,elem);
}
//some functions that create selects with managers etc
//now assume there are other selects that will filter down the teams,
//so we might have a function that creates on change events
function createOtherSelects(that){
//code that creates locations, depending on location chosen will
//filter the managers
$('#location').on('change',function(){
//do some stuff
//... then call create management
createManagement(that,elem);
});
}
function createManagement(that,elem){
var currentLocation = that.location; //works
var area = that.area;//works ... assume these are set above
//code that returns a filter and unique set of managers back
that.teamManager = [...new Set(
data.map(person=>{
if(person.area==area &&
person.currentLocation==currentLocation
){
return person;
}
})
)].filter(d=>{if(d){return d}});
if(elem.length>0){
var selectNames = ['selectManager','selectTeamManager'];
var fcns = [that.setManager,that.setTeamManager];
for(var i = 0; i < selectNames.length;i++){
//do stuff
if(certainCriteriaMet){
// filter items
if(filteredManager == 1){
fcns[i].call(null,currentManager);//
}
}
}
}
}
}
var xx = new createPerson()
In console I see setting manager and setting team manager to with the correct values.
however when I call xx in console, I see everything else set except for
xx.teamManager and xx.manager
instead it is applying to the window, so if I type teamManager in the console, it will return with the correct person.
If I straight up say
that.setManager('Steve')
or even it works just fine.
xx.setManager('steve')
the this value in setManager is somehow changing from the current instance of the object to this window. I don't know why, and I would like to learn how to use apply and call using that for future reference.
I think the issue is with your following code
fcns[i].call(null,currentManager)
If you are not supplying "this" to call, it will be replaced with global object in non-strict mode.
fcns[i].call(that,currentManager)
See mdn article here
From your codepen example, you need to change that line
fcnset[0].apply(that,[randomName]);
The first argument of the apply method is the context, if you are not giving it the context of your method it's using the global context be default. That's why you end up mutating the window object, and not the one you want !

angular.copy and angular.extend - copies still referencing the original array

I checked SO and found this question angular.copy() isn't creating an independent copy of an object - The title describes the issue i'm having, though the answers didn't help.
I am attempting to copy an array, broadcast an event with that array and then clear the source array. In doing this my copy is also empty. I can verify that the copy works in as much as my destination is filled by commenting out code that clears the source.
I have tried both angular.copy and angular.extend (ensuring i have the parameters the correct way around, as they are opposite for these 2 methods)
I have also tried with arrays that are on the scope rather than just local variables too - this made no difference
The parts of code that are using this are:
dz.on('queuecomplete', function () {
console.log('queue complete')
console.log(uploadedFiles)
var filesOut = [];
angular.copy(uploadedFiles, filesOut);
$rootScope.$broadcast('file.upload.complete', { files: filesOut });
scope.uploadComplete = true;
scope.completeMessage = 'Wooohoo!'
resetAll();
});
function resetAll() {
dz.removeAllFiles();
scope.hasTooMany = false;
scope.hasBadTypes = false;
scope.manualUpload = false;
hasHadErrors = false;
// If i comment this out then my listener (below) shows all files
// if i leave this as-is then both source and dest arrays are empty
uploadedFiles = [];
scope.$apply();
}
In another controller I am listening to the $broadcast
var filesUploaded = $scope.$on('file.upload.complete', function (event, data) {
console.log('files uploaded:')
// if 'uploadedFiled = []' is not commented out then data.files is an empty array
console.log(data);
});
As mentioned, i've tried this with extend too.
What am i missing? Or am i expecting something that the methods aren't designed for?

Categories