Why isn't the background colour changing? (JS) - javascript

I'm new to programming, and stackoverflow, so I apologize if my question isn't in the correct format.
I'm working with code which was partially written by my professor, with directions included from him in comments. My goal is to get the javascript to write CSS in order to change the background colour of the page. I think the problem is with the second function (writeCSS), but I can't figure out how to fix it.
Here's the code I've got. Sorry for the massive block of code, I don't want to leave out anything that might be needed.
Any other general tips or criticism would be great!
/* Declare and initialize global variables
-------------------------------------------------- */
const pageBg = document.querySelector('html');
const sliders = document.getElementsByTagName('input');
let rgb = [0, 0, 0];
/* Event handlers for range sliders
-------------------------------------------------- */
for (var i = 0; i < sliders.length; i ++) {
// Loop through the three range inputs and for each one, add an onchange event listener
sliders[i].onchange = function() {
// If an input range slider is moved, grab it’s id attribute value
let whichSlider = this.getAttribute('id');
// …also, grab the numerical value that it is set to
let sliderValue = this.value;
// Declare a new variable to hold the new RGB value that calls a function that updates the global rgb variable, passing in what slider was moved (whichSlider), and its value (sliderValue)
newRgb = changeRgb(whichSlider, sliderValue);
// Call a function that builds a new CSS background-color property (as a string), passing it the updated RGB array (newRgb)
let newCSS = writeCSS(newRgb);
// Directly change the background-color of the page using the new CSS rgb value
pageBg.style.backgroundColor = newCSS;
};
};
/* Functions
-------------------------------------------------- */
// STEP 1: Write a function called changeRgb() that accepts two parameters, channel and value
function changeRgb(channel, value) {
// STEP 2: Build a switch based on the value of the channel parameter (red, green, or blue) (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch)
switch(channel) {
// STEP 3: Inside each case, update the appropriate global rgb array element (0, 1, or 2)
case "red":
rgb[0] = value;
break;
case "green":
rgb[1] = value;
break;
case "blue":
rgb[2] = value;
break;
}
// STEP 4: Return the updated rgb array back to the event handler
return rgb;
}
// STEP 5: Write a new function called writeCSS() that accepts one parameter, the updated rgb array
function writeCSS(newRgb) {
// STEP 6: Declare a new local variable called newColor that will contain the new string that will be used to update the CSS background-color property in the following format: rgb(0,0,0) - initialize it with the start of the string, 'rgb('
let newColor = "rgb(";
// STEP 7: Create a while loop that iterates through the array passed into this function, called newRgb (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/while)
let i = 0;
while(i < newRgb.length) {
// STEP 8: For each element of the array, add to the string newColor, the red, green, and blue values, each followed by a comma
newColor += newRgb[i];
newColor += ",";
i++;
}
// STEP 9: Slice off the last comma from the string contained by the variable, newColor - we don’t need it (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice)
newColor = newColor.slice(0, -1);
// STEP 10: Finish off the newColor string by adding the closing ')'
newColor += ")";
// STEP 11: Return the string newColor back to the event handler
return newColor;
}
// STEP 12: Move the contents of this script element into a separate .js file and add a script element to the head of this html file to connect it - don't forget the defer attribute!
I tried to play around with the writeCSS function, as when I try to call it in the console, I get this error: VM717:1 Uncaught ReferenceError: newColor is not defined at :1:1
When the sliders are moved, the background stays black.
I'm at a loss, because I thought let newColor = "rgb("; is defining it.

It looks like your code works, though there are some funny things about the code. Using the .onchange property of an element to assign a function feels a bit old school instead of .addEventListener. All these formal loops instead of using array methods. Grabbing the attribute id from the element every iteration instead of assigning it once in a scope that can be accessed by the function. Using a switch statement instead of using an object as a dictionary to lookup / assign the related values. Having to slice off the last comma especially when it's only three variables that could be pieced together. Probably lots of things that serve to teach the syntax instead of being best practice. I'm also probably nowhere near as accredited as your professor so take my thoughts with a grain of salt.
If you're experiencing issues it could be with your html or the environment you're running the js in. I'm not sure what your project structure looks like. If you have an interest in making a github repo I'd always be happy to review the project.
For reviewing working code you could always check out code review for the feedback others get, or getting feedback of your own.
I did my own take on the color changing idea. I was thinking about packaging it in a way that I might be able to pack it into a component into the future. I have css manage the variables, and just change the css variable based on the slider update. I used the html data attribute to track which input affects which rgb value. I decided to listen to the input event instead of change event, because it gave a more pleasing cosmetic effect.
Good luck out there 👍
const get = (str, parent = document) => [...parent.querySelectorAll(str)];
const container = get(".color-area")[0];
const setUpdate = el => {
const rgb = el.dataset.rgb;
const property = `--${rgb}`;
el.addEventListener("input", () => {
const val = el.value;
container.style.setProperty(property, val);
});
};
get("input", container).forEach(setUpdate);
.color-area {
--r: 0;
--g: 0;
--b: 0;
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: rgb(var(--r), var(--g), var(--b));
}
<div class="color-area">
<input data-rgb="r" type="range" min="0" max="255" value="0" />
<input data-rgb="g" type="range" min="0" max="255" value="0" />
<input data-rgb="b" type="range" min="0" max="255" value="0" />
</div>

Related

JS calling ID by variable

Hi All
First data:
let NewDivForGame_0 = document.createElement('div');
let NewDivForGame_1 = document.createElement('div');
let NewDivForGame_2 = document.createElement('div');
and so on...12
Next:
NewDivForGame_0.id = 'Key';
NewDivForGame_1.id = 'string_1';
NewDivForGame_2.id = '1a1';
and so on...12
Next: append.
Next:
for (i=0;i<=12;i++){
document.getElementById("NewDivForGame_"+i.id).style.width ="35px"; //ERROR
document.getElementById("NewDivForGame_"+[i].id).style.height= "35px"; //ERROR
document.getElementById("NewDivForGame_"+[i].id).style.backgroundColor = "blue";
console.log('Create size div run #'+i);
It doesn't work. Help me please. Please write a solution.
tried:
1)document.getElementById("NewDivForGame_"+i.id).style.width = "35px"; //ERROR
2)document.getElementById("NewDivForGame_"+[i].id).style.width = "35px"; //ERROR
3)
let DetectPer = "NewDivForGame_";
document.getElementById(DetectPer+i.id).style.width = "35px"; //ERROR
It doesn't work. Help me please. Please write a solution.
Another example, maybe not so short as then one of #mplungjan, but it shows how it can be done differently.
If You want to create elements you can use simple for loop to do it, but then you need to add them to DOM as a child of other DOM element.
In example below I've added first 'div' as a child of body, second as child of first and so on.
Because all elements references where stored in newDivForGame array we can use it to change style properties using simple for loop.
{
const newDivForGame = [];
for (let i = 0; i < 12; ++i) {
newDivForGame.push(document.createElement('div'));
newDivForGame[i].id = `key${i}`;
document.body.appendChild(newDivForGame[I]);
}
for (const elem of newDivForGame) {
elem.style.width = '35px';
elem.style.height = '35px';
elem.style.background = 'blue';
}
}
You cannot build your selectors like that - it is wishful thinking.
To do what you are trying you would need eval, or worse:
window["NewDivForGame_"+i].id
Neither which are recommended
Why not access them using querySelectorAll, here I find all elements where the id starts with NewDivForGame
document.querySelectorAll("[id^=NewDivForGame]").forEach(div => {
div.style.width ="35px";
div.style.height= "35px"; //ERROR
div.style.backgroundColor = "blue";
})
or use css and a class
.blueStyle {
width: 35px;
height: 35px;
background-color: blue;
}
and do
NewDivForGame.classList.add("blueStyle")
or
document.querySelectorAll("[id^=NewDivForGame]").forEach(div => div.classList.add("blueStyle"))
The main problems with your code are these lines:
for (i=0;i<=12;i++){
document.getElementById("NewDivForGame_"+i.id).style.width ="35px"; //ERROR
document.getElementById("NewDivForGame_"+[i].id).style.height= "35px"; //ERROR
document.getElementById("NewDivForGame_"+[i].id).style.backgroundColor = "blue";
console.log('Create size div run #'+i);
...
From what I can tell, you're attempting to access your variables by expecting your browser to evaluate the result of concatenating a string with a number.
Aside from that, you're attempting to access the id property from i, which as it stands, is a number. The number primitive does not have an id property, but based on your code it seems you might have been mixing it up with your string/eval assumption.
Lastly, your line of [i] was actually creating an array with the number i being the single and only element. Arrays likewise do not have an id property.
(Un)Fortunately, Javascript doesn't work this way. At least not exactly that way; in order for the browser or container to do what you expect, there's a few methods that could be used, but I'm only going to reference two; the dreaded eval(), which I won't get into due it being a dangerous practice, and an object literal definition. There are of course other ways, such as the other existing answer(s) here, but:
// Here, we define an object literal and assign it properties for each div manually.
let gameDivs = {
NewDivForGame_0: document.createElement('div'),
NewDivForGame_1: document.createElement('div'),
// etc
};
// And then assign the id values sort of like you do in your question;
gameDivs.NewDivForGame_0.id = 'Key';
gameDivs.NewDivForGame_1.id = 'string_1';
gameDivs.NewDivForGame_2.id = '1a1';
// etc
for (i=0;i<=12;i++){
// Before finally using square bracket notation to target the
// properties by a dynamic name;
document.getElementById(gameDivs["NewDivForGame_"+i].id).style.width ="35px";
document.getElementById(gameDivs["NewDivForGame_"+i].id).style.height= "35px";
document.getElementById(gameDivs["NewDivForGame_"+i].id).style.backgroundColor = "blue";
console.log('Create size div run #'+i);
}
Of course, you don't even need to select them by their id if you have the reference to them, which you do:
for (i=0;i<=12;i++){
// Before finally using square bracket notation to target the
// properties by a dynamic name;
gameDivs["NewDivForGame_"+i].style.width ="35px";
gameDivs["NewDivForGame_"+i].style.height= "35px";
gameDivs["NewDivForGame_"+i].style.backgroundColor = "blue";
console.log('Create size div run #'+i);
}
This example assumes the divs are appended to the document.
This methodology uses square brackets on an object literal. As you may, or may not be aware, square brackets should be used when accessing an object's property in a dynamic way, such as what you were trying to do with string concatenation earlier.
Due to the way objects behave, you could even go so far as to generate the divs with a for-loop and then add them to the object:
let gameDivs = {};
for (let i = 0; i<=12; i++) {
let gameDiv = document.createElement('div');
gameDivs["NewDivForGame_"+i] = gameDiv;
// append them too;
document.body.appendChild(gameDiv);
}
Of course, I have no idea what pattern you're using for creating element ids, but in general the above would work.

toggle visibility of a layer (the state property) in Acrobat not working

I'm trying to toggle layer visibility in an Acrobat pdf by setting the state property to either true or false. It works fine in a proof of concept pdf, but it's not working here. I feel like this is a scope issue, because I'm getting into the switch statement but the states aren't toggling. I feel like I'm not referencing STARS in displayRating correctly with respect to the document. Any ideas? It seems like the result of the splice suddenly isn't the same as what that item is in the LAYERS array once it's pushed into STARS. Thanks!
// document javascript
var STARS = [];
function init(){
var LAYERS = this.getOCGs();
var l = LAYERS.length;
for(var i = 0;i<l;i++){
if(/^(STAR_)/i.test(LAYERS[i].name)==true){
STARS.push(LAYERS.splice(i,1,null));
};
}
}
function displayRating(r){
// called from a button in the pdf
switch(parseInt(r)){
case 3:
STARS[0].state = STARS[1].state = STARS[2].state = true;
break;
case 2:
STARS[0].state = STARS[1].state = true;
STARS[2].state = false;
break;
case 1:
STARS[0].state = true;
STARS[1].state = STARS[2].state = false;
break;
default:
STARS[0].state = STARS[1].state = STARS[2].state = false;
}
}
init();
Note: In the Acrobat SDK pdf, the state property is described on page 520.
Solved: It turns out the splice statement was causing the problems. It was recording the null value into the LAYERS array and then pushing null into the STARS array. I thought it would return the value from the splice first, and then fill it with null after, but that wasn't the case. Solution was to just push the matched value without the splice.

Gradually changing opacity with JavaScript doesn't work

I have this element:
<div id="newDimention">
<div class="newDimention">
</div>
</div>
I'm trying to change its opacity with javascript:
let newDimention=document.getElementById('newDimention');
setTimeout(()=>{
setDimention();
newDimention.innerText="You've uncovered the third dimension."
newDimention.style.color="purple";
newDimention.style.fontSize='30px';
newDimention.style.marginTop='30px';
newDimention.style.opacity="0";
})
const setDimention = () => {
for (var i = 0,b=14; i <= 500; i++) {
setTimeout(()=>{
//document.getElementById("newDimention").style.opacity=String(Math.round(i/50)/10);
newDimention.style.opacity=String(Math.round(i/50)/10);
},i*b)
}
}
I tried without converting to a string, tried accessing by the class, id. Devtools clearly show that String(Math.round(i/50)/10) gradually increases each time as it should be. But newDimention.style.opacity remains '0' each time.
Then once String(Math.round(i/50)/10)==='1', newDimention.style.opacity changes to '1' instantly. So it remains '0' for some reason until i===500, then suddenly changes to '1'. I don't have any other functions manipulating this element. And if I remove the line newDimention.style.opacity=String(Math.round(i/50)/10); the opacity stays at '0', so this line is supposed to change the opacity of this element.
Why is this happening?
While writing this question I realized that I used var instead of let in the for loop, so when the functions got fired eventually after setTimeout, they used i===500, the latest value. Changing it to let fixed it:
const setDimention = () => {
for (let i = 0,b=14; i <= 500; i++) {
setTimeout(()=>{
//document.getElementById("newDimention").style.opacity=String(Math.round(i/50)/10);
newDimention.style.opacity=String(Math.round(i/50)/10);
},i*b)
}
}
From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let:
"let allows you to declare variables that are limited to a scope of a block statement, or expression on which it is used, unlike the var keyword, which defines a variable globally, or locally to an entire function regardless of block scope".

Retrieve a value dynamically from a group of arrays using a parameter passed to a function

I am working on an educational tool. I want to retrieve a value from one of a group of arrays based on a parameter passed to a function. Specifically, this goal of this code is to change the label text on a group of radio-buttons based on selections by a user.
Here's my code:
//arrays
var bodyplan = ['Anguilliform', 'Compressiform', 'Depressiform', 'Filiform', 'Fusiform', 'Globiform', 'Sagittiform', 'Taeniform']
var caudalshape = ['Continuous', 'Emarginate', 'Forked', 'Lunate', 'Rounded', 'Truncate']
var mouthposition = ["Inferior", "Jawless", "Subterminal", "Superior", "Terminal"]
//function
function changelabels(opt1, opt2){
var i = opt2
var c = 1
var index = 0
while (i>=c){
document.getElementById("rb" + c + "lbl").innerHTML = (opt1[index])
c = c + 1
index = index + 1}}
The call to the function is made in a switch statement that is quite lengthy. For example here is one of the calls:
case 4:
changelabels("caudalshape", 6)
The code I created above does change the labels, but not in the desired way. For the above case instead of my radio-buttons being labeled "Continuous", "Emarginate", "Forked" and so on, they are labeled "c", "a", "u", "d" and so on. In other words the function I wrote is not pulling values from the array. It is simply selecting letters from the parameter word passed tot he function according to the index.
Any help would be appreciated. I'm a relative novice at Javascript. I've been trying to tackle this problem for several hours both in playing with code and in searching these forums.
This is because you're operating on the string "caudalshape" rather than the array which is pointed to by the variable named "caudalshape".
You can fix this by passing the array rather than the string;
changelabels(caudalshape, 6)
Unrelated to your problem; you should really start adding your own semi-colons to your code. Yes, it's optional in JavaScript, but it's not in many others, and it's a pain to adjust to when the time comes to learn another language (personal experience). For more info, see Do you recommend using semicolons after every statement in JavaScript?
Unless I'm missing something in your implementation, you want to pass in the caudalshape variable rather than the string:
case 4:
changelabels(caudalshape, 6)
1st there are some redundant code, which i am removing for your refrence. and also your missing semicolon everywhere.
//arrays
var bodyplan = ['Anguilliform', 'Compressiform', 'Depressiform', 'Filiform', 'Fusiform', 'Globiform', 'Sagittiform', 'Taeniform'];
var caudalshape = ['Continuous', 'Emarginate', 'Forked', 'Lunate', 'Rounded', 'Truncate'];
var mouthposition = ["Inferior", "Jawless", "Subterminal", "Superior", "Terminal"];
//function
function changelabels(opt1, opt2){
var index = 0;
while (index < opt2){
document.getElementById("rb" + (index+1) + "lbl").innerHTML = opt1[index];
index = index + 1;
}
}
Here are the correction: : the parameter you are passing is string, you should pass array variable
case 4:
changelabels(caudalshape, 6);

Why does this function work for literals but not for more complex expressions?

I am having some insidious JavaScript problem that I need help with. I am generating HTML from a JSON structure. The idea is that I should be able to pass a list like:
['b',{'class':'${class_name}'}, ['i', {}, 'Some text goes here']]
...and get (if class_name = 'foo')...
<b class='foo'><i>Some text goes here.</i></b>
I use the following functions:
function replaceVariableSequences(str, vars) {
/* #TODO Compiling two regexes is probably suboptimal. */
var patIdent = /(\$\{\w+\})/; // For identification.
var patExtr = /\$\{(\w+)\}/; // For extraction.
var pieces = str.split(patIdent);
for(var i = 0; i < pieces.length; i++) {
if (matches = pieces[i].match(patExtr)) {
pieces[i] = vars[matches[1]];
}
}
return pieces.join('');
}
function renderLogicalElement(vars, doc) {
if (typeof(doc[0]) == 'string') {
/* Arg represents an element. */
/* First, perform variable substitution on the attribute values. */
if (doc[1] != {}) {
for(var i in doc[1]) {
doc[1][i] = replaceVariableSequences(doc[1][i], vars);
}
}
/* Create element and store in a placeholder variable so you can
append text or nodes later. */
var elementToReturn = createDOM(doc[0], doc[1]);
} else if (isArrayLike(doc[0])) {
/* Arg is a list of elements. */
return map(partial(renderLogicalElement, vars), doc);
}
if (typeof(doc[2]) == 'string') {
/* Arg is literal text used as innerHTML. */
elementToReturn.innerHTML = doc[2];
} else if (isArrayLike(doc[2])) {
/* Arg either (a) represents an element
or (b) represents a list of elements. */
appendChildNodes(elementToReturn, renderLogicalElement(vars, doc[2]));
}
return elementToReturn;
}
This works beautifully sometimes, but not others. Example from the calling code:
/* Correct; Works as expected. */
var siblings = findChildElements($('kv_body'), ['tr']);
var new_id = 4;
appendChildNodes($('kv_body'),
renderLogicalElement({'id': new_id},
templates['kveKeyValue']));
/* Incorrect; Substitutes "0" for the expression instead of the value of
`siblings.length` . */
var siblings = findChildElements($('kv_body'), ['tr']);
var new_id = siblings.length; // Notice change here!
appendChildNodes($('kv_body'),
renderLogicalElement({'id': new_id},
templates['kveKeyValue']));
When I trap out the first argument of renderLogicalElement() using alert(), I see a zero. Why is this?? I feel like it's some JavaScript type thing, possibly having to do with object literals, that I'm not aware of.
Edit: I have this code hooked up to the click event for a button on my page. Each click adds a new row to the <tbody> element whose ID is kv_body. The first time this function is called, siblings is indeed zero. However, once we add a <tr> to the mix, siblings.length evaluates to the proper count, increasing each time we add a <tr>. Sorry for not being clearer!! :)
Thanks in advance for any advice you can give.
If new_id is 0, doesn't it mean that siblings.length is 0? Maybe there is really no sibling.
Perhaps siblings.length is actually 0? Try debugging further (e.g. with Firebug)
OK, I fixed it. As it turns out, I was modifying my source JSON object with the first function call (because in JS you are basically just passing pointers around). I needed to write a copy function that would make a new copy of the relevant data.
http://my.opera.com/GreyWyvern/blog/show.dml/1725165
I ended up removing this as a prototype function and just making a regular old function.

Categories

Resources