Loop through Array and assign variables name and id - javascript

I want to loop through an array and give their name.
I tried to use template literals, but it doesn't work.
const colors = ['yellow', 'green', 'brown', 'blue', 'pink','black']
for (let color of colors){
const `${color}Button` = document.querySelector(`#${color}`);
}
the results I want should be something like this
yellowButton = document.querySelector(#yellow);
greenButton = document.querySelector(#green);
.
.
.
.
blackButton = document.querySelector(#black);
Could you guys please revise my code?

You can attach variables onto the window object, making it accessible as a global variable. However this is a bad practice, since it pollutes namespace, causing unnecessary bugs and much headache in the future. A much better approach to this is to use native javascript objects, as this is the exact use case it was made for.
With your example:
const colors = ['yellow', 'green', 'brown', 'blue', 'pink', 'black']
const buttons = colors.reduce((accum, color) => {
accum[`${color}Button`] = document.querySelector(`#${color}`);
return accum;
}, {});
console.log(buttons)
// to access certain element:
const elem = buttons.yellowButton
console.log(elem)
<button id='yellow'></button>
<button id='green'></button>
<button id='brown'></button>
<button id='blue'></button>
<button id='pink'></button>
<button id='black'></button>

In the browser, you can attach variables to the window interface and reference them as if they were defined in the global scope.
for (const color of colors) {
window[`${color}Button`] = document.querySelector(`#${color}`)
}
console.log(redButton.textContent)
However, as others have mentioned, it may not be the best approach.

Related

I want to randomize the css classes the items in a grid using React with hooks

I've created a grid component in React. I have an array of strings named 'availableColors' in which I am storing the css class names I want to use.
In the 'RandomColorGrid' component I'm setting the initial colors of each grid item in 'useState', assigning an index from 'availableColors' to each item.
Each item in the grid calls 'changeColors()' onClick. Inside that method I reassign the calue of each 'box' in 'colors' with a new randomly chosen index from 'availableColors'.
This works well enough but feels a little clunky. There are two things I am trying to improve but am getting stuck.
First; I would like to use each color only once when the 'changeColors()' function is called. Currently it's possible for the same color to be used on more than one grid item and I would like them to be four unique colours each time.
Second; I would like for no item to be the same color twice in a row. So for any given item I would line to exclude that item's current color from the possible random choices.
I've been trying to achieve this by taking the color index of the current color and forming a new array of colors to randomly select from for each item and then another array to try and track the colors that have already been used in order to avoid duplicates but in doing so have gotten into a real mess. This leads me to believe that my design is probably bad from the start.
How can I improve on this?
import React, { useState } from "react";
const availableColors = ["red", "green", "blue", "yellow"];
const changeColors = (colors, setColors) => {
colors.box1 = availableColors[randomNumber(colors.box1)];
colors.box2 = availableColors[randomNumber(colors.box2)];
colors.box3 = availableColors[randomNumber(colors.box3)];
colors.box4 = availableColors[randomNumber(colors.box4)];
setColors({ ...colors });
};
const randomNumber = (currentColour) => {
let indices = [0, 1, 2, 3];
indices.splice(availableColors.indexOf(currentColour), 1);
return indices[Math.floor(Math.random() * indices.length)];
};
export const RandomColorGrid = () => {
let [colors, setColors] = useState({
box1: availableColors[0],
box2: availableColors[1],
box3: availableColors[2],
box4: availableColors[3],
});
return (
<div className="grid">
<div
className={`${colors.box1}`}
onClick={() => changeColors(colors, setColors)}
/>
<div
className={`${colors.box2}`}
onClick={() => changeColors(colors, setColors)}
/>
<div
className={`${colors.box3}`}
onClick={() => changeColors(colors, setColors)}
/>
<div
className={`${colors.box4}`}
onClick={() => changeColors(colors, setColors)}
/>
</div>
);
};
Your problems come from not respecting the immutability of objects.
You change an object and rely on the object not changing in the next line (in changeColors)
The solution would be to copy new arrays of the available colors, and using .filter to make sure we dont repeat the same colors twice by replacing the new currentlyAvailableColors array to only include colors that are ok to use
const changeColors = (colors, setCurrentColours) => {
const nextColors = {};
let currentlyAvailableColors = [...availableColors];
nextColors.box1 = getRandomOption(colors.box1, currentlyAvailableColors)
currentlyAvailableColors = currentlyAvailableColors.filter(col => col !== nextColors.box1);
nextColors.box2 = getRandomOption(colors.box2, currentlyAvailableColors)
currentlyAvailableColors = currentlyAvailableColors.filter(col => col !== nextColors.box2);
nextColors.box3 = getRandomOption(colors.box3, currentlyAvailableColors)
currentlyAvailableColors = currentlyAvailableColors.filter(col => col !== nextColors.box3);
nextColors.box4 = getRandomOption(colors.box4, currentlyAvailableColors)
currentlyAvailableColors = currentlyAvailableColors.filter(col => col !== nextColors.box4);
setCurrentColours({ ...nextColors });
};
Heres a working codepen
https://codepen.io/yftachman/pen/XWZMqVZ

how to remove text added in a modal from an object after closing it

I'm a beginner and I'm trying to add random jokes to a modal from an object. Until now everything works. What I want now is to clear the modal after I click the close button so that every time I click "lets have a laugh..." a new joke will appear without having to refresh the page.
Other suggestions to make the code cleaner are welcome.
const JokesObject = {
joke1: {
question: "what is the ultimate paradox",
answer: "There is no absolute truth"
},
joke2: {
question: "what turns coffee into code",
answer: "A programmer"
}
};
function jokie () {
const ListJokes = Object.keys(JokesObject);
let randomJoke = ListJokes[Math.floor(Math.random() * ListJokes.length)];
const joke = JokesObject[randomJoke];
const jokeModalQuestion = document.getElementById('joke');
const jokeModalAnswer = document.getElementById('answer');
const addJoke = document.createTextNode(joke.question);
const addAnswer = document.createTextNode(joke.answer);
jokeModalQuestion.appendChild(addJoke);
function answer () {
jokeModalAnswer.appendChild(addAnswer);
};
document.getElementById('giveAnswer').addEventListener('click', answer)
};
jokie();
const toggleModal = () => {
document.querySelector('.modal').classList.toggle('modal-hidden');
};
document.querySelector('.show-modal').addEventListener('click', toggleModal);
document.querySelector('.modal__close-bar').addEventListener('click', toggleModal);
To make that possible, you need to move your answer function outside the main function (jokie), and whenever you open your modal, you set a variable with the answer the current joke contains.
That way, when the answer button is clicked, it will always be filled with the currently selected joke.
Here are some suggestions to improve the quality of your code.
Please remember that beautiful code is subjective, some people might prefer a different way of doing things, but I think there's a general consensus about what "good" code looks like.
Use an array instead of an object
You are using an object when what you want is actually an array.
Instead of
const colors = { color1: 'yellow', color2: 'red' };
const firstColorKey = Object.keys(colors)[0];
const firstColor = colors[firstColorKey];
Do this
const colors = [ 'yellow', 'red' ];
const firstColor = colors[0];
Use better namings
Don't try to be fun when naming methods / variables. Instead of jokie, use setRandomJoke. Try to be as verbose as possible.
Think of functions as units of work
Instead of having a single function doing all the work, try to separate work into different units. For instance, your jokie function was doing two things: getting a random joke and filling the modal with the joke text. That can be decomposed into two different functions: getRandomJoke and fillJoke (again, remember function names should be as descriptive as possible).
Use consistent naming
If you are using a rule for, say, class selectors, keep it consistent. You are using giveAnswer, which uses camelCase, when other class selectors are using hyphen-case.
Plase your constants values outside the function
You are calling getElementById to get the DOM elements all the time, when only once should suffice. You can achieve that by placing the constants outside your function.
Use a code beautifier
To stop thinking about indentation, people nowadays use a tool to automatically format the code. Prettier is heavily used among Javscript devs https://prettier.io/
Look into BEM for naming CSS classes
BEM (or alternative frameworks / methodologies) is used to keep a consistent naming on CSS code. In your code, you are already using some of that by using __, but you can improve that by instead of using joke and answer class names, using modal__joke and modal__answer.
Code
const jokes = [
{
question: "what is the ultimate paradox",
answer: "There is no absolute truth",
},
{
question: "what turns coffee into code",
answer: "A programmer",
},
];
const jokeModalQuestion = document.getElementById("modal__joke");
const jokeModalAnswer = document.getElementById("modal__answer");
document.getElementById("answer").addEventListener("click", answer);
let currentAnswerTextNode;
function answer() {
jokeModalAnswer.innerText = currentAnswerTextNode;
}
function getRandomJoke(jokeList) {
return jokeList[Math.floor(Math.random() * jokeList.length)];
}
function fillJoke(joke) {
currentAnswerTextNode = joke.answer;
jokeModalQuestion.innerText = joke.question;
jokeModalAnswer.innerText = "";
}
function showModal() {
const joke = getRandomJoke(jokes);
fillJoke(joke);
document.querySelector(".modal").classList.remove("modal-hidden");
}
function hideModal() {
document.querySelector(".modal").classList.add("modal-hidden");
}
document.querySelector(".show-modal").addEventListener("click", showModal);
document
.querySelector(".modal__close-bar")
.addEventListener("click", hideModal);
.modal-hidden {
display: none
}
<button class="show-modal">Show Modal</button>
<div class="modal modal-hidden">
<button class="modal__close-bar">Close Modal</button>
<div id="modal__joke"></div>
<div id="modal__answer"></div>
<button id="answer">Answer</button>
</div>

Is it possible to destructure an object with default key names [duplicate]

I'd like to do something like this:
const vegetableColors = {corn: 'yellow', peas: 'green'};
const {*} = vegetableColors;
console.log(corn);// yellow
console.log(peas);// green
I can't seem to find or figure out how to do this but I really thought I had seen it done somewhere before! :P
NOTE: I'm using Babel with stage set to 0;
CONTEXT: I'm trying to be drier in JSX and not reference this.state or this.props everywhere. And also not have to keep adding properties to destructure if the data changes.
I think you're looking for the with statement, it does exactly what you are asking for:
const vegetableColors = {corn: 'yellow', peas: 'green'};
with (vegetableColors) {
console.log(corn);// yellow
console.log(peas);// green
}
However, it is deprecated (in strict mode, which includes ES6 modules), for good reason.
destructure all properties into the current scope
You cannot in ES61. And that's a good thing. Be explicit about the variables you're introducing:
const {corn, peas} = vegetableColors;
Alternatively, you can extend the global object with Object.assign(global, vegetableColors) to put them in the global scope, but really, that's worse than a with statement.
1: … and while I don't know whether there is a draft to allow such things in ES7, I can tell you that any proposal will be nuked by the TC :-)
I wouldn't recommend it, but you can use eval() to accomplish something similar:
vegetableColors = {corn: 'yellow', peas: 'green'};
function test() {
for ( let i=0; i < Object.keys(vegetableColors).length; i++ ) {
let k = Object.keys(vegetableColors)[i];
eval(`var ${k} = vegetableColors['${k}']`);
}
console.log(corn); // yellow
}
test();
console.log(corn); // undefined (out of scope)
I think you're looking for:
const {corn, peas} = vegetableColors;
Live on Babel's REPL
If Pointy's right that you're asking how to do this without knowing the names corn and peas, you can't with destructuring assignment.
You can at global scope only, using a loop, but I'm sure you don't want to do this at global scope. Still, just in case:
// I'm sure you don't really want this, just being thorough
Object.keys(vegetableColors).forEach((key) => {
Object.defineProperty(this, key, {
value: vegetableColors[key]
});
});
(Throw enumerable: true on there if you want these pseudo-constants to be enumerable.)
That works at global scope because this refers to the global object.
I came upon a situation where the object was user-created and the code that uses the object was also user-created. Because the with statement is deprecated, I made my own, using eval to destructure the entire object and call the function that uses the destructured object. Below is a working example.
const vegetableColors = { corn: 'yellow', peas: 'green' };
function with2(obj, func) {
eval(`
var { ${Object.keys(obj).join(",")} } = obj;
(${func.toString()})()
`);
}
/*
with(vegetableColors) {
console.log(corn);
console.log(peas);
}
*/
with2(vegetableColors, function() {
console.log(corn);
console.log(peas);
})
Let me show you my solution to the problem. I don't agree with those who think that destructuring object properties into local scope without specifying their names is bad idea. For me, this feature, if implemented, would be helpful. This would make our code shorter, and improve code maintenance by making it easy to change property names without changing the processing code. After all, there is the extract() function in PHP that does the same thing. Are PHP developers wrong?
My solution is not ideal as it uses eval but it is one liner and it works. Perhaps in the future we will have a solution from JavaScript developers.
function extract(o)
{
var result = [];
for(var key in o)
if(o.hasOwnProperty(key)) {
var item = 'var ' + key + '=' + JSON.stringify(o[key]);
result.push(item);
}
return result.join(';');
}
var vegetableColors = { corn: 'yellow', peas: { unripe: 'green', ripe: 'yellow' } };
eval(extract(vegetableColors));
console.log(corn); // yellow
console.log(peas); // {unripe: "green", ripe: "yellow"}

Get element by ID returns null

const data = [{color : "red"},{color : "blue"}, {color : "green"} ];
function libraryRoot() {
load();
return (`<div id="appDiv">
${data.map(function(value){
return `<div><p>Color ${value.color} from libraryRoot</p>`
}).join("")}
</div>
`);
}
window.onload = libraryRoot;
function load() {
let a = document.getElementById("appDiv");
console.log(a);
}
let defaultLayout = libraryRoot();
document.getElementById("root").innerHTML = defaultLayout;
<div>
<div id="root"></div>
</div>
Hi Guys i modified the script as you guys suggested, but still the return value at the first instance prints null, and then it prints the div.can you guys help me where im going wrong.
All i wanted to do is i want to call the "appDiv" id and wirte a button funcion to it. like on click {//do something}.
updated Codepen Project
You won't be able to access the element until it's written to the DOM.
Notice the word document in document.getElementById(). It's a method of the document API.
DOM stands for Document Object Model.
If you want to modify it before then then split your string literal into different pieces. Assign specific variables to important parts of the element.
Then modify them. Concatenate them back into one string and add them to the DOM.
Your code could use some comments. It's a little unclear what you're trying to do.
Here I've modified your pen to show some different ways to handle each color in your data array. You can do some conditional logic and return different template strings based on that. You could easily pass data object properties to another javascript function using the onclick attribute.
Hopefully this helps you get closer to your goal
const data = [{color : "red"},{color : "blue"}, {color : "green"} ];
function alertFunction(color) {
alert(color);
}
function libraryRoot(){
return('<div id="appDiv">' +
data.map(function(value) {
var result = `<div><p>Color ${value.color} from libraryRoot</p>`;
if (result.indexOf("red") > -1) { // checking if the div includes red
return result;
} else if (`${value.color}` == "blue") { // checking if the objects color prop includes blue
return "<p>blue</p>";
} else {
return `<button onclick="alertFunction('${value.color}')">Button with function</button>`;
}
}).join("")
+ "</div>"
);
}
window.onload = libraryRoot;
document.getElementById("root").innerHTML = libraryRoot();
<div>
<div id="root"></div>
</div>
The first issue is that you are calling load() first, which is trying to access the appDiv node that doesn't exist yet. So rethink the order on that.
The second issue is that you never actually added the appDiv content to the DOM. You can use things like document.appendChild(libraryRoot()); or something like that, though I have never injected a string as a DOM node - I use createElement for that. The link has some examples.

Trying to make a function that takes in variable names as strings

New to Js, sorry if this is an obvious one.
I have some strings in my code that correspond to the names of variables. I'd like to put them into a function and have the function be able to make changes to the variables that have the same names as the strings.
The best example is where this 'string' is passed through from a data tag in html, but I have some other situations where this issue appears. Open to changing my entire approach too is the premise of my question is backwards.
<html>
<div data-location="deck" onClick="moveCards(this.data-location);">
</html>
var deck = ["card"];
function moveCards(location){
location.shift();};
Thanks!
A script should not depend on the names of standalone variables; this can break certain engine optimizations and minification. Also, inline handlers are nearly universally considered to be pretty poor practice - consider adding an event listener properly using Javascript instead. This will also allow you to completely avoid the issue with dynamic variable names. For example:
const deck = ["card", "card", "card"];
document.querySelector('div[data-location="deck"]').addEventListener('click', () => {
deck.shift();
console.log('deck now has:', deck.length + ' elements');
});
<div data-location="deck">click</div>
I think this can technically be done using eval, but it is good practice to think more clearly about how you design this so that you only access objects you directly declare. One example of better design might be:
container = {
obj1: //whatever this object is
...
objn:
}
function applyMethodToObject(object_passed){
container[object_passed].my_method();
}
I'm not sure I 100% follow what you're trying to do, but rather than trying to dynamically resolve variable names you might consider using keys in an object to do the lookup:
const locations = {
deck: ['card']
}
function moveCards (location) {
// if 'deck' is passed to this function, this is
// the equivalent of locations['deck'].shift();
locations[location].shift();
};
Here's a working demo:
const locations = {
deck: ['card 1', 'card 2', 'card 3', 'card 4']
};
function move (el) {
const location = el.dataset.location;
const item = locations[location];
item.shift();
updateDisplay(item);
}
// update the display so we can see the list
function updateDisplay(item) { document.getElementById('display').innerHTML = item.join(', ');
}
// initial list
updateDisplay(locations['deck']);
#display {
font-family: monospace;
padding: 1em;
background: #eee;
margin: 2em 0;
}
<div data-location='deck' onclick="move(this)">click to shift deck</div>
<div id="display">afda</div>
When you assign a value to an object in javascript you can access with dot or array notation. IE
foo = {};
foo.bar = "bar";
console.log(foo.bar);
console.log(foo["bar"]);
Additionally, global variables are added to the window object, meaning deck is available at window["deck"] or window[location] in your case. That means your moveCards function could do:
function moveCards(location) {
// perform sanity checks since you could change data-location="foo"
// which would then call window.foo.shift()
if (window[location]) {
window[location].shift();
}
}
That said, this probably isn't a great approach, though it's hard to say without a lot more context.

Categories