Selecting a value and assigning as a variable is not working - javascript

For a Hangman game, I have some topics (eg:cities and animals).
When the user selects one of the topics, the outcome should be one of the chosen topic's random item. eg: London or Zebra etc.
Currently I only have random letter of selected topic.
const cities = ["New York", "London", "Berlin"]
const animals = ["Alligator", "Alpaca", "Zebra"]
const topicsEl = document.querySelector("#topics")
function randomTopic(){
return topicsEl.value[Math.floor(Math.random()*topicsEl.value.length)]
}
topicsEl.addEventListener("change", function(){
console.log(randomTopic());
})
<div class="select">
<label for="topics">Choose a topic:</label>
<select id="topics">
<option value=cities>Cities</option>
<option value=animals>Animals</option>
</select>
</div>

You seem to have issues getting a random value of a list depending on a selection.
Currently, you are selecting a random letter of topicsEl.value instead of a random element of the associated topic's list.
You need to determine the list to choose from depending on topicsEl.value. Dynamically this can be achieved if that value can be used as a key (e.g. for a dictionary), but this can also be done statically.
But doing it statically would result in duplicate code, e.g. in an if-else-if ladder growing for each new topics list:
function randomTopic() {
if (topicsEl.value === "cities") {
// ... (use citites)
} else if (topicsEl.value === "animals") {
// ... (use animals)
} // Etc. for every new topic
}
Doing it dynamically allows for abstracting away the list selection, keeping the function simple. As suggested before, a dictionary can be used for this.
For example, your dictionary's properties could each be a topic list, and your option values should then match their corresponding property's name:
const topics = {
cities: [/*...*/],
animals: [/*...*/]
};
const topicsEl = document.querySelector("#topics");
function randomTopic() {
const list = topics[topicsEl.value];
// ...
}
Selecting a random item of that list works analogous to how you are currently selecting a random letter:
function randomTopic() {
const list = topics[topicsEl.value];
return list[Math.floor(Math.random() * list.length)];
}
Personally, I find such random selections more readable if the index generation is in a separate function. Example:
const edibles = ["cheese", "ham", "bread", "banana", "peanuts"];
console.log(randomEdible());
function randomEdible() {
return edibles[randomInt(0, edibles.length - 1)];
}
function randomInt(max = 100, min = 0) {
if (max < min) {
[max, min] = [min, max]; // Swap
}
return Math.floor(Math.random() * (max - min + 1) + min); // +1 to include max
}

In your existing code, topicsEl.value is going to be the string "cities" or the string "animals" (because those are the value of each of the options in your <select> box.). These are not the global variables you defined in your javascript, they're just strings contained in the HTML.
You then, in randomTopic(), access that string as an array, which Javascript interprets as you wanting two treat it as an array of the characters within that string. This is why you're getting a random letter from the word: "animals"[0] is the letter a, "animals"[1] is the letter n, and so on.
What you're trying to do is choose a random item from the array variables you've named "cities" and "animals", but your functions don't try to touch those variables, they're only acting on the strings contained in the DOM.
So you need to add a step, to get from the string value from the <select> to the array you're trying to access.
You've defined the two arrays as global variables; in theory those can be accessed as window.cities or window.animals, so you could do window[topicsEl.value] which would return the array you're trying to access.... it's not great practice to depend on the window global, though, so I'd encourage you to switch that pair of separate variables in to an object for easier access:
const topics = {
cities: ["New York", "London", "Berlin"],
animals: ["Alligator", "Alpaca", "Zebra"]
}
const topicsEl = document.querySelector("#topics")
function randomTopic() {
// find the desired array:
let topicArr = topics[topicsEl.value]
// return a random element from that array:
return topicArr[Math.floor(Math.random() * topicArr.length)]
}
topicsEl.addEventListener("change", function() {
console.log(randomTopic());
})
<div class="select">
<label for="topics">Choose a topic:</label>
<select id="topics">
<option value=cities>Cities</option>
<option value=animals>Animals</option>
</select>
</div>

Related

Can't get mutator logic to correctly add the proper amount of items into a cart

I'm trying to make an add to cart function that first checks if the item being added is already in the cart. If it's in the cart, update its quantity property. If not in the cart, add the entire object to the cart. I think my problem is I'm getting the logic wrong inside my "ADD_ITEM_TO_CART" mutator function.
This is my store with some console.logs() from when I click "addToCart()"
state: {
checkoutCart: [],
},
actions: {
cartAdd({ commit }, payload) {
commit("ADD_ITEM_TO_CART", payload);
},
},
mutations: {
ADD_ITEM_TO_CART(state, payload) {
//CONSOLE.LOG()'s
console.log("state.checkoutCart[0]", state.checkoutCart[0]);
// eslint-disable-next-line
console.log("state.checkoutCart[0].item", state.checkoutCart.item);
console.log("state.checkoutCart", state.checkoutCart);
//IF ITEM ALREADY IN checkoutCart, UPDATE IT'S QUANTITY
if (state.checkoutCart.includes(payload.item)) {
state.checkoutCart.quantity += payload.quantity;
console.log("Item already in cart");
}
//IF ITEM NOT IN checkoutCart, UPDATE THE QUANTITY PROPERTY AND ADD ITEM TO CART
else {
payload.item.quantity = payload.quantity;
state.checkoutCart.push(payload);
}
https://i.imgur.com/rjOOljN.png
I thought this code would work, but it ALWAYS executes the ELSE condition and adds to cart like the
if (state.checkoutCart.includes(payload.item))
isn't being recognized or working at all.
https://i.imgur.com/LLB790Z.png
VueX devtools shows the same thing. An "item" object inside an object inside an array.
I also tried:
ADD_ITEM_TO_CART(state, payload) {
console.log("add_item_to_cart"); <---ONLY PART THAT SHOWS UP IN CONSOLE.LOG() WHEN EXECUTED
//LOOP THROUGH ALL ARRAY ENTRIES TO GAIN ACCESS TO state.checkoutCart.item
for (let i = 0; i < state.checkoutCart.length; i++) {
console.log("i=", i);
console.log("state.checkoutCart.item", state.checkoutCart.item);
//IF ITEM ALREADY IN checkoutCart, UPDATE IT'S QUANTITY
if (state.checkoutCart[i].item.includes(payload.item)) {
state.checkoutCart.quantity += payload.quantity;
console.log("Item already in cart");
return;
};
}
//IF ITEM NOT IN checkoutCart, UPDATE THE QUANTITY PROPERTY AND ADD ITEM TO CART
payload.item.quantity = payload.quantity;
state.checkoutCart.push(payload);
},
because I figured I needed to loop through all the array entries. BUT the for loop doesn't even run, and with this code nothing gets added to the cart at all.
I can't figure out what I'm doing wrong here. Can somebody help? Is my syntax wrong? Or is my logic? Am I accessing the arrays/objects incorrectly? How do I write the "ADD_ITEM_TO_CART" mutator function correctly? I've literally spent all day on this and my brain is shutting down.
EDIT:
https://i.imgur.com/bkU8YSo.png
PAYLOAD
<div v-for="item in items"> <--ACTUALLY PROP FROM PARENT COMPONENT BUT SAME IDEA
<p>
Qty
<select v-model="quantity">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
</p>
<p>
<button type="button" #click="addToCart()">
Add to Cart
</button>
</p>
</div>
let quantity = ref("1");
const addToCart = () => {
console.log("addToCart Running");
store.dispatch("cartAdd", { item: item.value, quantity: quantity.value });
};
That is because your if condition is not checking for what you think.
Array.prototype.includes checks if a value is in the array but there are two cases:
the value is a primary type (string, number, boolean, ...). It compares by value.
the value is an object. Then it compares by reference.
So here, you are checking if the reference of your item object is already included in the array. But it's not, since it's a new object.
Solution: check if there is an object with the same values, not reference.
You can use the some method, and you have to write a condition that checks if two items are equals.
Here is an example if your items have an id:
if (state.checkoutCart.some(item => item.id === payload.item.id))
The problem is indeed inside ADD_ITEM_TO_CART mutation.
As Kapcash has pointed out, two objects having the same properties and the same values are not the same.
In other words, .includes() checks for identity, not equality. To better understand this, consider this example:
const a = { foo: 'bar' }
const b = [{ foo: 'bar' }]
const c = [a]
const d = [{ ...a }]
console.log(b.includes(a)) // false
console.log(c.includes(a)) // true
console.log(d.includes(a)) // false
To get past this, use Kapcash's answer.
I'll just mention the standard way of dealing with this problem is using unique identifiers on objects (e.g: uuids).
Once you fix the above, it's still not going to work, because you'll run into the following problem: inside the if you're attempting to alter state.checkoutCart's quantity. And an array does not have a quantity property.
The proper logic to achieve the desired functionality is (assuming the unique identifier on checkoutCart members is item._id, from the pictures you posted):
ADD_ITEM_TO_CART(state, payload) {
// find the index of a cart element having same `item._id` value as the payload
const index = state.checkoutCart.findIndex(
(el) => el.item._id === payload.item._id
)
if (index > -1) {
// if the index is higher than `-1`, an element was found
// create a copy, update its quantity and then
// replace the original element with the copy
const copy = { ...state.checkoutChart[index] }
copy.quantity += payload.quantity
state.checkoutCart.splice(index, 1, copy)
} else {
// no element found, add payload to `checkoutCart`
state.checkoutCart.push(payload)
}
}
Side note: None of your items should contain a quantity property. That's the cart's responsibility, not the item's. By adding it to the item you end up with two sources of truth which you'd need to keep in sync. You don't want this type of problem.

I want to generate unique ID

I want to generate unique ID in JavaScript. I have tried uuid npm package, it's good but it's not what I want.
For example what I get as a result from generating unique ID from uuid package is
9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d
Is there any way to make specific format as I want.
For example I want my ID to look like this
XZ5678
In this example format is two uppercase letters and 4 numbers.
That's it, I'm looking for answer and thank you all in advance.
If you're just looking to generate a random sequence according to that pattern, you can do so relatively easily. To ensure it's unique, you'll want to run this function and then match it against a table of previously generated IDs to ensure it has not already been used.
In my example below, I created two functions, getRandomLetters() and getRandomDigits() which return a string of numbers and letters the length of the argument passed to the function (default is length of 1 for each).
Then, I created a function called generateUniqueID() which generates a new ID according to the format you specified. It checks to see if the ID already exists within a table of exiting IDs. If so, it enters a while loop which loops until a new unique ID is created and then returns its value.
const existingIDs = ['AA1111','XY1234'];
const getRandomLetters = (length = 1) => Array(length).fill().map(e => String.fromCharCode(Math.floor(Math.random() * 26) + 65)).join('');
const getRandomDigits = (length = 1) => Array(length).fill().map(e => Math.floor(Math.random() * 10)).join('');
const generateUniqueID = () => {
let id = getRandomLetters(2) + getRandomDigits(4);
while (existingIDs.includes(id)) id = getRandomLetters(2) + getRandomDigits(4);
return id;
};
const newID = generateUniqueID();
console.log(newID);
Not sure why you want this pattern but you could do like this:
const { floor, random } = Math;
function generateUpperCaseLetter() {
return randomCharacterFromArray('ABCDEFGHIJKLMNOPQRSTUVWXYZ');
}
function generateNumber() {
return randomCharacterFromArray('1234567890');
}
function randomCharacterFromArray(array) {
return array[floor(random() * array.length)];
}
const identifiers = [];
function generateIdentifier() {
const identifier = [
...Array.from({ length: 2 }, generateUpperCaseLetter),
...Array.from({ length: 4 }, generateNumber)
].join('');
// This will get slower as the identifiers array grows, and will eventually lead to an infinite loop
return identifiers.includes(identifier) ? generateIdentifier() : identifiers.push(identifier), identifier;
}
const identifier = generateIdentifier();
console.log(identifier);
They way of what you are suggesting be pretty sure you are going to get duplicates and collitions, I strongly suggest go with uuid.
Also probably you can find this useful: https://www.npmjs.com/package/short-uuid
Or if you want to continue with your idea this could be helpful: Generate random string/characters in JavaScript

For loop a number to create buttons inside template literal

Getting a bit stuck with this one.
I'm looping through an object (dataLayer.Tests) and I'm displaying the values of my content, in a DIV. Here's an example of how this object looks:
I'm doing this by looping through my object with forEach. (And in this example, I'm just console.logging my result result3).
The problem I'm having, is that within my forEach, I want to display create/display buttons, depending on what the number is in the totalVariants key/value.
So for example, if the totalVariants === 2, I want to create 2 buttons. If it is one, I want to create 1 button.
I know I need to for loop through this particular value, but I'm not sure how to do this, within a template literal.
Here's my code.
dataLayer.Tests.forEach((element, index, array) => {
let result3 = '';
let numberOfVariants = element.totalVariants;
if (numberOfVariants >= 1) {
for (i = 0; i < numberOfVariants; i++) {
console.log("The number is ", i + 1);
}
result3 += `
<div class="CRO-variant-result">
<p>Date Launched: ${element.date}</p>
<p>Test ID: ${element.id}</p>
<p>Test Description: ${element.name}</p>
<p>Variant Active: ${element.variant}</p>
<p>Total Variants: ${element.totalVariants}</p>
${(function () {
for (i = 0; i < element.totalVariants; i++) {
return `<button>${i}</button>`
}
})()}
</div>
`
console.log("result3", result3);
};
});
I've seen solutions which include .map and object.keys, but this doesn't seem to work/return anything. (Probably as I just need to loop through a number and not array etc.
Any ideas/pointers, would be appreciated.
Basically, I'm not sure how to loop through a number, within a template literal and return x number of elements.
Thanks,
Reena
numberOfVariants is an number, not an object, so one way you could do this is create a new incrementing array of that length (Array.from(Array(numberOfVariants).keys()) does this nicely) and then map over that array as you're doing.
${Array.from(Array(numberOfVariants).keys()).map(i => (
`<button value="${i}">${i}</button>`
)).join('')}
I'm not quite sure what you want to appear inside the button (maybe the integer of the current number as you increment)?

How to Compare Values from 2 Arrays to Filter Out Elements

I'm having a really difficult time figuring out this problem. I have two arrays (cars and active_filters). The cars array contains html li tags with attributes such as car type, number of seats, price, etc. The active_filters array contains objects related to which filters the user interacted and will be used for which cars to display or hide.
Example: When looking at active filters for id data-type, the value property includes a list of all car codes that the user is wanting to display. If a cars data-type is not found or equal to one of these car codes, my goal is to hide these cars by adding class type to each car. So in this case, these cars should have class filtered-out-by-car-type added.
I'm noticing the performance is pretty slow with what I currently have below. Also, I'm stuck on how to access the specific type from active_filters associated with the car that is found. Appreciate any help! Thanks so much!!
Cars:
<li data-partner-code="EZ" data-type="XXAR" data-seats="2" data-bags="2" data-prepaid="Y" data-transmission="Automatic" data-unlimited-miles="Y" data-price="84.81" class="listing">
<li data-partner-code="AV" data-type="SFAR" data-seats="4" data-bags="2" data-prepaid="N" data-transmission="Automatic" data-unlimited-miles="Y" data-price="125.54" class="listing">
<li data-partner-code="BU" data-type="CCAR" data-seats="4" data-bags="2" data-prepaid="N" data-transmission="Automatic" data-unlimited-miles="N" data-price="65.42" class="listing">
<li data-partner-code="BU" data-type="CCAR" data-seats="4" data-bags="2" data-prepaid="N" data-transmission="Automatic" data-unlimited-miles="N" data-price="65.42" class="listing">
<li data-partner-code="FX" data-type="MCAR" data-seats="2" data-bags="1" data-prepaid="N" data-transmission="Automatic" data-unlimited-miles="N" data-price="32.00" class="listing">
Active Filters:
{id: "data-seats", value: "1", type: "filtered-out-by-seats"}
{id: "data-bags", value: "2", type: "filtered-out-by-bags"}
{id: "data-partner-code", value: "ET,EY,BU", type: "filtered-out-by-company"}
{id: "data-type", value: "IFAR,SFAR,PGAR,RFAR,FFAR,XXAR", type: "filtered-out-by-car-type"}
Current Code:
cars.forEach(function(car)
{
var found = active_filters.find(function(filter) {
var filter_value = filter.value.split(","); // getting unique value
for (var i=0; i<filter_value.length; i++)
{
if (car.attr(filter.id) === filter_value[i])
{
return car; // Why is this returning active_filters element instead of car?
}
}
});
// Todo: How can I access active_filters.type associated to the car that was found?
if (!found)
{
car.addClass(active_filters.type); // ?
}
});
Your requirements aren't completely clear to me, but the problem seems tailor-made for basic array operations, so perhaps I can offer a framework for solving it that you can fill in with the details.
First, it seems that the basic idea is to filter to cars array to a subset. It turns out there's an Array method just for that called, naturally, filter
const filtered_cars = cars.filter(car => {
// This function will get passed each car in turn. Return
// true if the car should be kept in the list, false otherwise.
});
Now, for each car, I think you want to check all of the active filters. If any of the active filters match, the car should be kept. Again, there's an Array method, in this case it's some.
const filtered_cars = cars.filter(car => {
return active_filters.some(filter => {
// Within this function, we have a car variable, and a filter
// variable. We want to return true if the car matches the
// filter, false otherwise
});
});
It appears from your description that each active filter may have multiple
values, separated by commas. We need to extract those into an array. We can also extract the appropriate car attribute for the filter.
const filtered_cars = cars.filter(car => {
return active_filters.some(filter => {
const values = filter.value.split(",");
const attribute = car.getAttribute(filter.id);
// Return true if the attribute is in the set of values
});
});
Once again we can make use of some
const filtered_cars = cars.filter(car => {
return active_filters.some(filter => {
const values = filter.value.split(",");
const attribute = car.getAttribute(filter.id);
return values.some(value => {
return value === attribute;
});
});
});
At this point, we have reduced the cars array to only those elements that match the filters, but we haven't added the type to those cars that didn't make the filter. To do that, we have to replace the first some with a forEach to ensure that we loop through all the filters instead of stopping at the first match. Also we want to either add or remove the special class as appropriate.
const filtered_cars = cars.filter(car => {
let keep_car = false;
active_filters.forEach(filter => {
const values = filter.value.split(",");
const attribute = car.getAttribute(filter.id);
const matched = values.some(value => {
return value === attribute;
});
car.classList.toggle(filter.type, !matched);
keep_car = keep_car || matched;
});
return keep_car;
});
Like I said, I'm not sure if that code is exactly what you want, as I may have misunderstood the problem. But hopefully it's enough to get you past the current road block.

Checking an input against a specific array string

I am trying to create a quiz that randomly selects a question from pool of questions in an array, which is answered in an input box that is to be checked against the corresponding answer string in the array. I used math.floor(math.random() *); to get my random number. The random number is intended to be used to find both the question and answer, which are arranged in order to correspond to one another, e.g. ['question1','question2'] and ['answer1','answer2'].
I am having difficulty with trying to get my input to properly be checked against the corresponding answer value from the array. I am fairly novice at Javascript, so I am not sure as to how to do this. I tried using the document.getElementById command to compare the two. I suspect it has something to do with the fact that ansNum doesn't get the value of questNum because of the fact that questNum is only given its value inside the generateQuiz function. (I realize ansNum is likely redundant, but I was just playing around to see if anything would happen)
Javascript:
const questions = ['What do young Roman males wear?','Who is the Roman god of the smith?','Who is the 6th king of Rome?'];
const answers = ['toga praetexta','vulcan','servius tullius'];
function getQuestNum() {
questNum = Math.floor(Math.random() * 3);
};
function getAnsNum() {
ansNum = questNum();
}
function generateQuiz() {
getQuestNum();
document.getElementById("question").innerHTML = questions[questNum];
};
function checkCorrect() {
getAnsNum();
if (answer[ansNum] = document.getElementById("input").innerHTML) {
document.getElementById("verification").innerHTML = "Correct!";
}
};
Codepen Link
An image of the code
Based on your code, I fixed it with some changes. It is not the best way to do this i think. I posted the js part here.
const questions = ['What do young Roman males wear?','Who is the Roman god of the smith?','Who is the 6th king of Rome?'];
const answers = ['toga praetexta','vulcan','servius tullius'];
var questNum;
function getQuestNum() {
questNum = Math.floor(Math.random() * 3);
};
function getAnsNum() {
ansNum = questNum;
}
function generateQuiz() {
getQuestNum();
document.getElementById("question").innerHTML = questions[questNum];
};
function checkCorrect() {
getAnsNum();
if (answers[ansNum] = document.getElementById("input").value) {
document.getElementById("verification").innerHTML = "Correct!";
}
};
First you need a global variable questNum then you can use it in all of your functions.
The function getAnsNum() is redundant, at least i think so, just use questNum in your checkCorrect() function.
For getElementByID function, insert an ID attribute to your input
<input id="input" type="text" name="input">
For input, if you want to take the value of the input field, use document.getElementById("input").value instead of innerHTML.
If you not sure about any result, console.log it or use Chrome dev debug tool to check the result. In the checkCorrect function, your array name should be answers instead of answer.
Shorter ver:
const questions = ['What do young Roman males wear?','Who is the Roman god of the smith?','Who is the 6th king of Rome?'];
const answers = ['toga praetexta','vulcan','servius tullius'];
var questNum;
function getQuestNum() {
questNum = Math.floor(Math.random() * 3);
};
function generateQuiz() {
getQuestNum();
document.getElementById("question").innerHTML = questions[questNum];
};
function checkCorrect() {
if (answers[questNum] = document.getElementById("input").value) {
document.getElementById("verification").innerHTML = "Correct!";
}
};
It would be simpler to create an array of objects that each contain both a question and an answer - and create a function that generates your random number and returns the object at the corresponding index.
Then you'll have access to everything you need without worrying about whether or not you can maintain access to the original randomly selected number, or matching up indices between two different arrays.

Categories

Resources