Confusion with the use of higher order function in JS - javascript

I am new to JS and was learning how to create and use higher order functions in JS. I faced a bit of confusion with this code:
function elListMap(transform, list) {
// list might be a NodeList, which doesn't have .map(), so we convert
// it to an array.
return [...list].map(transform);
}
function addSpinnerClass(el) {
el.classList.add('spinner');
return el;
}
// Find all the buttons with class 'loader'
const loadButtons = document.querySelectorAll('button.loader');
// Add the spinner class to all the buttons we found.
elListMap(addSpinnerClass, loadButtons);
The question is Why don't we pass argument to addSpinnerClass when addSpinnerClass itself is passed to elListMap. I mean shouldn't we have elListMap(addSpinnerClass(loadButtons), loadButtons); instead of elListMap(addSpinnerClass, loadButtons);

loadButtons is a list of elements. If you passed it to addSpinnerClass it would try to call the classList.add() method of that list of elements.
Since it is a list of elements and not an element, this would error and the program would stop.
If it didn't error, then you would pass the return value of addSpinnerClass (the value you passed into it: i.e. the list) as the first argument to elListMap.
elListMap would then pass that as the first argument to map. The first argument to map needs to be a function, but you would be passing a list of elements. So, again, it would error.

Related

Function returns [object Set] instead of an actual Set

I have a function
function recursiveDivisionWrap(width, height, colStart, colEnd, rowStart, rowEnd)
in which i have an empty set
let blockedNodes = new Set();
inside this function I have another, recursive function
function recursiveDivision(width, height, colStart, colEnd, rowStart, rowEnd)
On each call of the recursiveDivision function i update a blockedNodes set by adding value:
for (let i = colStart; i < colEnd; i++) {
blockedNodes.add(`c${i}r${wallStart}`);
}
If I console.log blockedNodes set inside recursiveDivision function, I'm getting desired result, for example:
Set(43) {'c0r7', 'c1r7', 'c2r7', 'c3r7', 'c4r7', …}
However when I console.log blockedNodes set inside recursiveDivisionWrap and not in recursiveDivision, I'll get comma separated object:
c0r7,c1r7,c2r7,c3r7,c4r7,c5r7,c6r7,c7r7,c8r7,c9r7,c9r0,c9r1,c9r2,c9r3,c9r4,c9r5,c9r6,c8r0,c8r1,c8r2,c8r3,c8r4,c8r5,c8r6,c3r0,c3r1,c3r2,c3r3,c3r4,c3r5,c3r6,c4r6,c5r6,c6r6,c7r6,c4r1,c5r1,c6r1,c7r1,c4r5,c5r5,c6r5,c7r5
I've also tried with array and the result was the same.
Why doesn't it return Set(43) {'c0r7', 'c1r7', 'c2r7', 'c3r7', 'c4r7', …} if the blockedNodes set is defined outside the recursiveDivision function and inside recursiveDivisionWrap and why does the inner recursiveDivision function returns correct set?
I would appreciate any help in finding answer to that question.
This behaviour has nothing to do with the place where the output is made, but how the output is formatted.
You have these statements:
console.log(blockedNodes);
And:
console.log(`Is this a set? ${blockedNodes}`);
And:
let ecie = Array.from(blockedNodes);
console.log(`Is this an array? ${ecie}`)
They don't do the same thing.
console.log(blockedNodes) leaves the rendering to the console API, which may provide additional information, like the name of the constructor of the object (Set in this case), the number of items this collection has, ...etc. It may even add user-interface controls to expand/collapse the contents of the collection and potential nested data structures.
console.log(`Is this a set? ${blockedNodes}`) will implicitly call blockedNodes.toString() in order to produce the string. Unless toString has been overriden, that will render as [object Set], and so the total output will be "Is this a set? [object Set]".
console.log(`Is this an array? ${ecie}`) will also call the toString method, but this time on ecie, which is an array (as that is what Array.from returned). The toString method on arrays produces a comma separated list of the values, which explains the output you mentioned.

Looping through an array (`Object.keys(obj)`) to find a value

I'm learning Javascript, so pardon any mistakes in how I phrase the question.
I am writing a chess program to practice and learn. Currently, I am trying to write a function to find the color of a piece with the position as the parameter. The relevant pieces of code are as follows. The first two work as they were designed to, but the last does not.
let allPieces = board.getElementsByClassName('piece');
This sets allPieces as an object with the key values the html elemnts representing each piece, both black and white.
const getPiecePosition = function(element) {
let position = window.getComputedStyle(element).getPropertyValue('grid-row-start');
let letterIndex = alphabet.findIndex(function(letter) {
return letter === position[0];
});
letterIndex += 1;
return [letterIndex, Number(position[1])];
}
This takes a parameter in the form of the allPieces object with a specific key and returns the position as an array with the column number first and the row number second. ex. [2,3].
const getPieceByPosition = function(position) {
let pce = Object.keys(allPieces).forEach(function(piece) {
if (getPiecePosition(allPieces[piece]) == position) {
return allPieces[piece].borderColor;
}
})
return pce;
}
This is the function I am having trouble with. The idea behind it is that it will take each key in the allPieces object and loop through them using forEach() into the getPiecePosition() function to compare it with the position entered as the parameter. Since only one piece can inhabit any tile at once, it should never return multiple values.
I honestly don't know where to start debugging this code, but I have been trying for about an hour. It always just returns undefined instead of a truthy value of any kind.
Your last function has a few issues:
getPiecePosition(allPieces[piece]) == position
Assuming position is an array, you're trying to compare an array with an array here using ==. However, since the two arrays are different references in memory, this will always give false, even if they contain the same elements:
console.log([2, 3] == [2, 3]); // false
You're trying to return from the callback of .forEach(). This won't achieve what you want, as return will jump out of the .forEach callback function, not your outer getPieceByPosition() function. This leads me to your final issue:
The .forEach() method doesn't return anything. That is, it doesn't evaluate to a value once it is called. This means that let pce will always be undefined since you're trying to set it to the return value of .forEach(). This, in contrast to let letterIndex, is different, as letterIndex is set to the return value of .findIndex(), which does have a return value and is determined by the function you pass it.
One additional thing you can fix up is the use of Object.keys(allPieces). While this works, it's not the best approach for looping over your elements. Ideally, you would be able to do allPieces.forEach() to loop over all your elements. However, since allPieces is a HTMLCollection, you won't be able to do that. Instead, you can use a regular for loop or a for..of loop to loop over the values in your HTMLCollection.
Alternatively, there is a way to make allPieces.forEach() work.
Instead of using board.getElementsByClassName('piece');, you can use the method .querySelectorAll('.piece'), which will give you a NodeList. Unlike a HTMLCollection, a NodeList allows you to use .forEach() on it to loop through its elements.
The return type of getElementsByClassName HTMLCollection Object. You should't use Object.keys to loop through each of 'piece' element. Insted, use the follow.
for(var i = 0 ; i < allPieces.length ; i++){
var piece = allPieces[i];
... // and, do whatever with the getPiecePosition(piece)
}

How to render multiple HTML parts with a plain Javascript function

This is a static website with hundreds of pages. I need to render elements like a topnav or a newsletter or a strap of content and changing those contents periodically, from JS.
This is what I tried:
const components = {
compartirEnFlex: `<h4>Newsletter</h4>`,
newsletterEs: `<h4>Compartir</h4>`,
}
const ids = ['newsletterEs', 'compartirEnFlex', 'infoArticulo', 'infoDeLaWebEnFlexIzq']
function renderComponents(objWithComp, idsArr){
return idsArr.map(function(id){
for(let component in objWithComp){
let arrOfIds = Object.keys(objWithComp);
arrOfIds.map(key => key)
if(id === key){
document.getElementById(id).insertAdjacentHTML('afterbegin', objWithComp[id])
}
}
})
}
renderComponents(components, ids);
Each id has its counterpart in the HTML structure. When I do this individually it works. However, I have to handle this in an elegant way (and there is no possibility for a JS framework like React in this project).
Thanks for the help!
When you run your code, you'll see the error Uncaught ReferenceError: key is not defined in the console.
That's because key in if(id === key) is not defined. The line arrOfIds.map(key => key) returns the same exact array as arrOfIds because Array.prototype.map "returns a new array populated with the results of calling a provided function on every element in the calling array."
Here, you don't assign that new array to a variable, so nothing happens. Even if it was, that new array would be a copy of arrOfIds because your mapping function (key) => key returns key for every key -- meaning that the output is the same as the input.
However, that's not an issue here. If I understand your question correctly, then this demo should show an example of what you're trying to accomplish. If that's what you want to achieve, then here's a solution:
First, you don't need to iterate for component in objWithComponent inside idArr -- you're already doing that in the idArr. You don't need the ids array either, because you can get the keys of the components from the components object using Object.keys().
Let's say your HTML looks something like this:
<div>
<div id="newsletterEs"></div>
<div id="compartirEnFlex"></div>
<div id="infoArticulo"></div>
<div id="infoDeLaWebEnFlexIzq"></div>
</div>
Then, using Object.keys(components) to get an array of the ids of the components that you have, you can map those to HTML tags. In fact, map is not necessary here because map returns a new array, and unless you need that array later, there's no reason to use map. Instead, you can use Object.prototype.forEach.
Here's what that would look like:
const components = {
compartirEnFlex: `<h4>Newsletter</h4>`,
newsletterEs: `<h4>Compartir</h4>`,
}
function renderComponents(objWithComp) {
Object
.keys(components)
.forEach((id) => {
const element = document.getElementById(id)
const component = objWithComp[id]
if (component && element) {
element.insertAdjacentHTML('afterbegin', component)
}
})
}
renderComponents(components)
Then, when you call renderComponents, you can pass just the components argument, and only render the components for which divs with corresponding ids exist with an if statement.

Passing array of strings to a function in a string literal

When I pass an array of strings and an index to an onclick event, the callback function receives the parameters from the first two values of the array as a number instead of the array and the index.
I have tried to convert it to an array using the Array.from function.
let presentantes = ["28411", "199904", "214966", "16226"];
console.log('presentantes', presentantes);
//presentantes (4) ["28411", "199904", "214966", "16226"]
let id = 1
let listNominated = `<li onClick="cargaPresentantes(${presentantes}, ${i})">`
function cargaPresentantes(presentantes, id) {
console.log('presentantes', presentantes);
console.log('id', id)
//presentantes 28411
//id 199904
}
I was expecting to get an array ["28411", "199904", "214966", "16226"] and the index 1
Actually template literals work something like this - If the variable which is passed to the placeholder is not a string(an array in this case) then it CONVERTS it to string.
So in your code the value of listNominated becomes '28411,199904,214966,16226,1' and thus it takes the first two arguements i.e. 28411 and 199904.
You cannot pass the parameters in that way... you should create a “onclick listener function” and then associate it to the “li” element.
As Andrea said I had to add an onclcik listener function. To do this, I had to append the string literal to the document first.

How to take value from the element in the Cypress tool?

I have a problem with taking value of the element like:
<div class="Test">Number 10</div>
Let say, that I have 10-20 classes with values like here, then I can use:
cy.get('.Test').invoke('text').as.('Field1')
to get proper value, but it is only possible in different test by using this.Field1.
How to get value in the same test without using:
then and .text()?
Is it possible? I have many fields and I would like do it in one single test to check proper values in the next view.
Did anyone has similar problem? Thanks
It sounds as though you may want to use .its
cy.get('selector').its(propertyName).should('contain', 'string')
its needs to be chained off a previous command like get. You can also use this in a function as per Cypress's example
Cypress shows an example for DOM elements
Get the length property of a DOM element
cy
.get('ul li') // this yields us a jquery object
.its('length') // calls 'length' property returning that value
.should('be.gt', 2) // ensure the length is greater than 2
})
You can access functions to then drill into their own properties
instead of invoking them.
// Your app code
// a basic Factory constructor
const Factory = (arg) => {
// ...
}
Factory.create = (arg) => {
return new Factory(arg)
}
// assign it to the window
window.Factory = Factory
cy
.window() // yields window object
.its('Factory') // yields Factory function
.invoke('create', 'arg') // now invoke properties on itv
You can drill into nested properties by using dot notation.
const user = {
contacts: {
work: {
name: 'Kamil'
}
}
}
cy.wrap(user).its('contacts.work.name').should('eq', 'Kamil') // true
For the full list of examples and rules check out Cypress.io documents https://docs.cypress.io/api/commands/its.html#Syntax

Categories

Resources