I have a background in C++ but have some questions with what I'm trying to do in Javascript. I needed to make a pseudo class since Javascript doesn't have them. I basically want to print something different based on what selection is chosen. I only defined two "fruits" in this example but what I would be doing would use a lot more. Any help would be greatly appreciated. I know there are some flaws in this like the select does not link in any way to the fruit function.
<!DOCTYPE html>
<html>
<script>
function select_fruit(name)
{
var fruit_name = name;
}
function fruit(name, color, amt)
{
this.name = name;
this.color = color;
this.amt = amt;
}
apple = new fruit("apple", "red", 5);
orange = new fruit("orange", "orange", 1);
document.getElementById("name").innerHTML = name;
document.getElementById("color").innerHTML = color;
document.getElementById("amt").innerHTML = amt;
</script>
<body>
<select name="pick_these" onchange="fruit(this.value)">
<option value="apple">Apple</option>
<option value="orange">Orange</option>
</select><br />
<p>I want *amt *color *fruit</p><!--This should say I want 5 red apple, if it's selected on the dropdown-->
</body>
</html>
First of all, you are missing the <head> tag. That aside, you need to move your document.getElementById..... lines to the select_fruit() function, and change the onchange event to call select_fruit instead. Here's how I'd do what you're attempting:
HTML:
<select id="fruitlist">
<option value="apple">Apple</option>
<option value="orange">Orange</option>
</select>
<p id="result"></p>
JS:
window.onload = function() { // or just `(function() {` if script is after above HTML
var fruits = {
"apple":{
"color":"red",
"amt":5
},
"orange":{
"color":"orange",
"amt":1
}
}, result = document.getElementById('result');
document.getElementById('fruitlist').onchange = function() {
var selection = this.options[this.selectedIndex].value;
result.innerHTML = "I want "+fruits[selection].amt+" "
+fruits[selection].color+" "+selection;
};
}; // or `})();` if you used `(function() {` at the start
I'm going to go ahead and agree with cdhowie's comment about 125%.
Attempting to use JS like Java/C#/C++/ActionScript/etc is going to work.
You'll be able to eke something out, and it's going to do something functional, once you hack your way around it for a while.
But that's really, really not the way the language should be used.
If you've got a technical understanding of other languages, Crockford's "JavaScript: the Good Parts" is a great way to hit the ground running.
If you have limited/no understanding of how languages are parsed, or you're really feeling lost inside of JavaScript, compared to your comfort-zone, then Crockford's book is like a bootcamp for people who want a solid starting-point for grasping the language, at a low-level (using the language as-is, with no libraries and minimal use of design-patterns).
I would say that Stoyan Stefanov's "JavaScript Patterns" is a fantastic next-step, as it does exactly what it says on the label, and it does a great job of it.
Anyway, you've got quite a few problems, here.
The first is that your script is asking for elements and their values before they even exist.
JS/the DOM are not pre-compiled.
For the most part, until you get into client/server communication, or you're defining functions and using them elsewhere, what you see is what you get, in the order you put them down.
In this case, you're asking for an element where el.id === "name".
If you look above this line in your code, you'll notice that no element above it has that id.
The soluution here would be to make sure that your script is below the intended target (or to wrap everything in functions, and call the functions below the <select> so you know that it exists).
This brings us to the second issue: your elements have no IDs.
If you have no IDs on them, grabbing those elements by ID isn't going to work.
<select id="my-select-box"></select>
<script>
var select = document.getElementById("my-select-box");
</script>
If you put them the other way around, it won't work.
If you're missing an .id attribute, it won't work.
As for "classes", don't think of it as defining hard-classes and then creating instances.
Sometimes, that's useful/necessary.
Other times, all you need to care about is instances.
What I mean is: in C++, think of all of the times you might have been tempted to create a full-blown class, when really a simple struct might have done.
And what about a struct where all you really need is a single instance?
Now imagine giving that struct methods, without needing to do any other work (headers, etc).
<select id="fruit-list">
<option value="apples">apple</option>
<option value="oranges">orange</option>
<option value="bananas">banana</options>
</select>
<p id="fruit-output"></p>
var Fruit = function (name, colour, starting_amount) {
var public_interface = {
name : name,
colour : colour,
amount : starting_amount
};
return public_interface;
};
var apples = Fruit("apple", "red", 4),
oranges = Fruit("orange", "orange", 2),
bananas = Fruit("banana", "yellow", 1);
var fruit_basket = {
selected : "",
output_el : null,
apples : apples,
oranges : oranges,
bananas : bananas,
add : function (fruit, num) {
this[fruit].amount += num;
},
remove : function (fruit, num) {
this[fruit].amount -= num;
},
print : function () {
var fruit = fruit_basket.selected,
el = fruit_basket.output_el,
message = "I want " + fruit.amount
+ " " + fruit.colour
+ " " + fruit.name;
if (fruit.amount !== 1) { message += "s" }
if (el.hasOwnProperty("textContent")) {
el.textContent = message;
} else {
el.innerHTML = message;
}
},
select : function (el) {
var index = el.selectedIndex,
fruit = el.options[index].value;
fruit_basket.selected = fruit_basket[fruit];
},
set_output : function (el) {
fruit_basket.output_el = el;
}
};
var select_box = document.getElementById("fruit-list"),
output_el = document.getElementById("fruit-output");
fruit_basket.set_output(output_el);
select_box.addEventListener("change", function () {
fruit_basket.select(this);
fruit_basket.print();
});
This isn't the answer, but consider how much more straightforward this is than a C++ or Java solution, where you'd have to build classes for all of the above.
If this were more advanced, perhaps you'd consider separating your data from your view and your control in more obviously-separated ways.
But again, that doesn't mean that you need huge inheritance hierarchies, or lots of classes which do relatively little.
It's a very straightforward language, which supports construction/initialization, but you can also make a "singleton" just by inventing an object on the spot.
I'd suggest putting that research into it, if you intend to use the language seriously.
Related
I'm making a web app to track kill effects in a game that I play with some friends from school. In the game, there are multiple types of "assassins" with different effects sometimes happening on kill.
This sometimes gets confusing to keep track of so I'm trying to make something where you can input both types and it will give a result of the corresponding kill effects.
I've tried getting results for just one pair as a proof of concept but there are issues with the sheer amount (144 combinations!) and making a separate function for each is obviously not the way to go.
Is there a way I can make a list of combinations (ex: a = "Classic", v = "Poison", outcome = "Victim is eliminated, the attacker receives 2-day death countdown") then using this list, take the attacker and victim classes from variables and set a 3rd variable as whatever the outcome of that pair is?
The code used when I made a single test function was
function checkSelection(attackerClass, victimClass) {
// Check if the selectedAttacker is 'Classic' and the selectedVictim is 'Oxford'
if (attackerClass === 'Classic' && victimClass === 'Oxford') {
// Get the result element
const result = document.querySelector('#outcome');
result.textContent = 'No special effect';
}
}
let attackerSel = document.querySelector('#attackerSel')
let victimSel = document.querySelector('#victimSel')
attackerSel.addEventListener('change', function(){
var attacker = this.value;
return attacker;
})
console.log(attacker)
setInterval(checkSelection(attacker, victim), 5)
I think I've made a mistake with variable scope here, but I don't think it matters since I'm not going to make 144 different versions
What about using the attacker and victim values to build a keys in an object that'll give you the result you need? e.g.:
const outcomeMap = {
'Classic-Oxford': 'No special effect',
'Classic-Poison': 'Victim is eliminated, attacker receives 2 day death countdown',
'Piano-Oxford': 'The royal musical society takes out a bounty on attacker',
'Piano-Poison': 'Victim is poisoned, 2 day death countdown',
};
const attackerSel = document.querySelector('#attackerSel');
const victimSel = document.querySelector('#victimSel');
const outcomeEle = document.querySelector('#outcome');
function checkSelection() {
const attackerClass = attackerSel.value;
const victimClass = victimSel.value;
outcomeEle.textContent = outcomeMap[attackerClass + '-' + victimClass] || 'Not found';
}
attackerSel.addEventListener('change', checkSelection);
victimSel.addEventListener('change', checkSelection);
<form>
<select id="attackerSel">
<option value=""></option>
<option value="Classic">Classic</option>
<option value="Piano">Piano</option>
</select>
<select id="victimSel">
<option value=""></option>
<option value="Oxford">Oxford</option>
<option value="Poison">Poison</option>
</select>
</form>
<div id="outcome"></div>
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>
Context
In my quest to improve my JavaScript skillset; I've begun reading an enlightening book called Functional Javascript by Michael Fogus. The 1st chapter has already stretched me far beyond my current proficiency.
Below is my first attempt at closures and scope chain. Frustratingly the below has taken me north of 5 hours to get working.
I'd love to understand and learn from individuals who have a more excellent grasp than me, regarding:
Using the scope closure chain - ostensibly is the last return function always this complex; with almost the entire workings encapsulated here? Or am I missing a trick?
What is the best way to abstract/compose a means of cleaning up all of the return/console.log outputs into a clean display function, templates or method - apologies I am not sure how best to express that question.
What is the cleanest way of sharing findMin between the two f's? Whilst maintaining the composition? The i in i*(am)/m needs to move depending on which formula is required. Literally i(am)/m or r(i*m)/m.
function dash(formula) {
let m = 1000;
let target = 25;
return function(rate) {
return function(numAds) {
let i = 0;
onTarget = () => (rate*(numAds*m)/m)>target;
findMin = () => {
while ((i*(numAds*m)/m)<target) {
i += 0.01;
}
return `Corrected Using: ${i.toFixed(2)} Now: ${(i*(numAds*m)/m).toFixed(2)}`;
};
if (formula=='cpm') {
return (onTarget()) ? `Perfect First Time: ${(rate*(numAds*m)/m).toFixed(2)}` : findMin(i);
} else if (formula=='ad') {
return (onTarget()) ? `Perfect First Time: ${(rate*(numAds*m)/m).toFixed(2)}` : findMin(i);
};
};
};
};
let cpm = dash('cpm')(1.00)(7);
console.log(cpm);
let ads = dash('ad')(2.30)(13);
console.log(ads);
Whilst not addressing the three questions, I have acknowledged two suggestions notably i) using toFixed ii) making my formula elements more meangingful.
I don't know exactly what this is called or if its even possible. But basically I want to add stuff to an "incomplete" method. For example.
captureDog =()=>{
alert('caught dog');
}
captureCat =()=>{
alert('caught cat');
}
<select id="pet">
<option value="Cat">cat</option>
<option value="Dog">dog</option>
</select>
Essentially I have a whole bunch of capture methods (I will either hard-code write them myself or create another method that creates them). I want to essentially call a different method depending on what value is on the select (behaves as suffix of method). Some dummy code I have that is trying to do what I'm saying is below.
var selectField = document.getElementById("pet");
selectField.addEventListener("onchange", "capture"+ selectField.value ="()");
Preferably I would like this to be done with just plain javascript.
EDIT: Grammar
EDIT2: Fixed arrow Functions
I think something like this:
var selectField = document.getElementById("pet");
let X = {
cat: function(){
console.log("cat 123")
},
dog: function(){
console.log("dog 123")
}
}
selectField.addEventListener("change", x => {
let val = x.target.options[x.target.selectedIndex].value
X[val]();
});
fiddle: https://jsfiddle.net/5rnke6zy/10/
I have honestly spent the last week searching for this. I suspect I don't know the correct search terms to use, or maybe what I'm asking is impossible. It seems like a common use case, though, so it's probably the former.
I'm creating a page where a user can select their product from a drop-down menu to see software requirements. We have a ton of different possible products, and they all share similar requirements -- so we want to reuse values between them.
I'm very new to JavaScript, and have found template literals very helpful. Perhaps I overuse them because they are easy to understand, but I'm hoping to use them here as well.
To simplify this, I'm using "cups" instead of software/hardware as an example.
First, we have our various properties:
const color = {
blue: "Blue",
green: "Green"
};
const material = {
plastic: "Hard Plastic",
ceramic: "Glazed Ceramic",
};
const size = {
oz_12: "12 ounces",
oz_16: "16 ounces",
oz_32: "32 ounces"
};
Then I have a variable for each product, e.g.:
const simple = {
title: "Classic Cup Options",
colors: `${color.blue}`,
materials: `${material.ceramic}`,
sizes: `${size.oz_12} and ${size.oz_16}`
}
And, finally, a function that returns all my variables in the format I want:
function tableTemplate(data) {
return `<div class="cup-prod-table"><h4>${data.title}</h4>
<table class="cup-table">
<tbody>
<tr>
<td>Colors</td>
<td>${data.colors}</td>
</tr>
<tr>
<td>Materials</td>
<td>${data.materials}</td>
</tr>
<tr>
<td>Sizes</td>
<td>${data.sizes}</td>
</tr>
</tbody>
</table>
</div>`;
};
When I call tableTemplate(simple), I get an HTML table with all my properties filled in.
Then, I have a drop-down menu with each possible product. The menu value corresponds to the constant name, so:
<select id="demoSelect">
<option value="simple">Classic Cup</option>
<option value="travel">Traveler's Cup</option>
<option value="jumbo">Jumbo Cup</option>
</select>
Right now, I'm going through one by one and calling the tableTemplate function manually for each selection:
$("#demoSelect").on("change", function() {
var str = "";
$("#demoSelect option:selected").each(function() {
str += $(this).val();
});
if (str == "simple") {
$("#cupTables").prepend(tableTemplate(simple));
} else if (str == "travel") {
$("#cupTables").prepend(tableTemplate(travel));
} else if (str == "jumbo") {
$("#cupTables").prepend(tableTemplate(jumbo));
}
});
(And so on, but for like 25 product variations).
I'd like to be able to just do something like this, but can't
$("#demoSelect").on("change", function() {
var str = "";
$("#demoSelect option:selected").each(function() {
str += $(this).val();
});
$("#cupTables").prepend(tableTemplate(str));
});
But while I haven't found an answer on how to do it, my bumbling searches have taught me that I can't do that because I'm pointing to a value ("simple") instead of a reference (const simple = {}) (I think?)
Is there a workaround?
Would I have better luck using something other than template literals?
Here is a JSFiddle with an example: https://jsfiddle.net/cwow123/voq6dvt9/
Rather than storing your products just as variables in the general scope of things, you can key them under a parent object called products.
const products = {
simple: {
// ...
},
travel: {
// ...
},
jumbo: {
// ...
}
}
Now, you can access each product type by the name of the key it is stored under (which is a string):
const product = products[str]
$("#cupTables").prepend(tableTemplate(product))