How can I create iterative scalable arguments in a HTML element? - javascript

<!DOCTYPE html>
<html>
<body>
<h2>What Can JavaScript Do?</h2>
<p>JavaScript can change HTML attribute values.</p>
<p>In this case JavaScript changes the value of the src (source) attribute of an image.</p>
<button onclick="document.getElementById('bulb0').src='pic_bulbon.gif'">Turn on the light</button>
<img id="bulb0" src="pic_bulboff.gif" style="width:50px">
<button onclick="document.getElementById('bulb0').src='pic_bulboff.gif'">Turn off the light</button>
<p></p>
<button onclick="document.getElementById('bulb1').src='pic_bulbon.gif'">Turn on the light</button>
<img id="bulb1" src="pic_bulboff.gif" style="width:50px">
<button onclick="document.getElementById('bulb1').src='pic_bulboff.gif'">Turn off the light</button>
<p></p>
<button onclick="document.getElementById('bulb2').src='pic_bulbon.gif'">Turn on the light</button>
<img id="bulb2" src="pic_bulboff.gif" style="width:50px">
<button onclick="document.getElementById('bulb2').src='pic_bulboff.gif'">Turn off the light</button>
<p></p>
<button onclick = turnOn()>Turn All lights ON</button>
<button onclick = turnOff()>Turn All lights OFF</button>
<button onclick = turnRandomOn()>Turn Any light ON</button>
<script>
function turnOn() {
document.getElementById('bulb0').src='pic_bulbon.gif';
document.getElementById('bulb1').src='pic_bulbon.gif';
document.getElementById('bulb2').src='pic_bulbon.gif';
console.log('All lights were turned on');
}
function turnOff() {
document.getElementById('bulb0').src='pic_bulboff.gif';
document.getElementById('bulb1').src='pic_bulboff.gif';
document.getElementById('bulb2').src='pic_bulboff.gif';
console.log('All lights were turned off');
}
function turnRandomOn() {
let random = Math.floor(Math.random() * 3);
console.log(random);
turnOff();
if (random == 0) {
document.getElementById('bulb0').src='pic_bulbon.gif';
} else
if (random == 1) {
document.getElementById('bulb1').src='pic_bulbon.gif';
} else
if (random == 2) {
document.getElementById('bulb2').src='pic_bulbon.gif';
}
}
</script>
</body>
</html>
Hello there! I'm on my first steps learning JS. I practiced with an interesting self-invented exercise that I just finished, the task consist in creating N-lamps that you can switch On/Off with a button for each lamp, I also added the idea of turning all on/off with just one button and the same for setting on/off a random lamp.
The code I shared works fine but I know is not scalable at all since I had to put the lamps by hand. My question is the following:
How can I improve this code to select an HTML element defined by a recursive variable? What I'm trying to say is, for example, instead having a line with the object "document.getElementById('bulb0')", how could I change it for something like "document.getElementById('bulbN')"(where N is variable).
I know it is with an iterative loop but every time I try defining the argument it throws me an error. I tried these two ways:
getElementById('"' + bulb + N + '"').innerHTML
Defining the argument ('"' + bulb + N + '"') as a separated variable and later adding it with the same procedure
I will truly appreciate your help, even more if you can share the code
PS: The exercise can be found here https://www.w3schools.com/js/tryit.asp?filename=tryjs_intro_lightbulb

you can do it using template literal strings.
function turnOn() {
for(let i of [0,1,2]) {
document.getElementById(`bulb${i}`).src='pic_bulbon.gif';
}
console.log('All lights were turned on');
}
function turnOff() {
for(let i of [0,1,2]) {
document.getElementById(`bulb${i}`).src='pic_bulboff.gif';
}
console.log('All lights were turned off');
}
Now, what you are doing wrong?
getElementById('"' + bulb + N + '"').innerHTML
// '"' + bulb + N + '"' -> bulb is taken as variable, not like string.
// instead of this you can just use "bulb" + N
getElementById("bulb" + N).innerHTML
e.g.
let N = 0
console.log("bulb" + N)
// now here, bulb is taken as a variable
// this will give you an error. because bulb variable is not defined
// console.log('"' + bulb + N + '"') throw an error
// if you define bulb variable,
{
const bulb = 'bulb'
console.log('"' + bulb + N + '"')
console.log(bulb + N)
// you can see '"' + bulb + N + '"' !== bulb + N
}
if you need to know more details:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals

There are several techniques you can use which I've commented on in the code but I'll mention here with some links to the documentation.
Use a loop as you suggested to build some HTML using template strings. Push each string into an array, and then join that array into one string and add it to the element that contains the lamps: lampsContainer.
Attach event listeners to the lampsContainer, and the buttons. When they're clicked they'll call a handler. With regards to lampsContainer we're using event delegation - attaching one listener to a container that catches events from its child elements as they "bubble up" the DOM.
If the lampsContainer is clicked we first check to see if the element that we clicked on is a button, and then we get that button's lamp element using previousElementSibling. We toggle the class on that lamp on/off.
The other buttons, as mentioned, have their own handlers. They either turn off (remove the on class) or turn on (add the on class) on all of the lamps. The random button simply toggles one random lamp from the lamp node list on/off.
// Initialise the lamp number,
// and an array to store the HTML string
const n = 4;
const html = [];
// Using a template string create a series of
// sections each containing a button, and a "lamp"
// element. Push each string into the array
for (let i = 0; i < n; i++) {
const str = `
<section class="lampContainer">
<div class="lamp"></div>
<button>lamp${i + 1}</button>
</section>
`;
html.push(str);
}
// Cache the elements for the lamps container,
// and the other buttons
const lampsContainer = document.querySelector('.lampsContainer');
const toggleAllOn = document.querySelector('.toggleAllOn');
const toggleAllOff = document.querySelector('.toggleAllOff');
const random = document.querySelector('.random');
// Add the HTML string to the lampsContainer element
lampsContainer.innerHTML = html.join('');
// Add some listeners to the lamps container, and the buttons
lampsContainer.addEventListener('click', handleLamps);
toggleAllOn.addEventListener('click', handleToggleAllOn);
toggleAllOff.addEventListener('click', handleToggleAllOff);
random.addEventListener('click', handleRandom);
// When an element in the lamps container is clicked
// check that it was a button, and then find its lamp
// element. Toggle the lamp `on` class.
function handleLamps(e) {
if (e.target.matches('button')) {
const lamp = e.target.previousElementSibling;
lamp.classList.toggle('on');
}
}
// Find all the lamps in the lamps container,
// and add an `on` class
function handleToggleAllOn() {
const lamps = lampsContainer.querySelectorAll('.lamp');
lamps.forEach(lamp => lamp.classList.add('on'));
}
// Find all the lamps in the lamps container,
// and add an `on` class
function handleToggleAllOff() {
const lamps = lampsContainer.querySelectorAll('.lamp');
lamps.forEach(lamp => lamp.classList.remove('on'));
}
// Find all the lamps in the lamps container,
// find a random lamp from that node list,
// and toggle its `on` class
function handleRandom() {
const lamps = lampsContainer.querySelectorAll('.lamp');
const rnd = Math.floor(Math.random() * lamps.length);
lamps[rnd].classList.toggle('on');
}
.lampContainer { display: flex; flex-direction: row; width:20%; align-items: center; }
.lamp { margin: 0.25em; border: 1px solid #dfdfdf; width: 20px; height: 20px; background-color: white; }
.on { background-color: yellow; }
button, .lamp { border-radius: 5px; }
button { text-transform: uppercase; }
button:hover { cursor: pointer; background-color: lightgreen; }
.buttonsContainer { margin-top: 1em; }
<section class="lampsContainer"></section>
<section class="buttonsContainer">
<button class="toggleAllOn">Toggle all on</button>
<button class="toggleAllOff">Toggle all off</button>
<button class="random">Toggle random on/off</button>
</section>

Use Document.createElement() to create element with any iterator
Then assign attributes. Then simply add that to DOM 👍
Extending it further you can even customize each element while creation base on your conditions

Related

Is it possible to make these functions into one function?

I'm trying to get this program to add a special character when the assigned button for the character is pressed. The problem is, I'm going to have a lot of functions. Can I somehow just make one function that I can use for all of the buttons?
//These are buttons
var aa = document.querySelector('#aa')
var oo = document.querySelector('#oo')
var uu = document.querySelector('#uu')
var khii = document.querySelector('#khii')
//This is the text box
var textBox = document.querySelector('#typeIn')
//Functions to add a character into the text box
function addAa() {
textBox.innerHTML += "ā";
}
function addOo() {
textBox.innerHTML += "ō";
}
function addUu() {
textBox.innerHTML += "ū";
}
function addKhii() {
textBox.innerHTML += "χ";
}
//Telling the buttons to call on the functions when clicked
aa.onclick = addAa
oo.onclick = addOo
uu.onclick = addUu
khii.onclick = addKhii
Additionally: why does this not work?
var aa = document.querySelector('#aa')
var textBox = document.querySelector('#text')
function addLetter(a) {
textBox.innerHTML += a
}
aa.onclick = addLetter("ā")
This just adds the character once into the text box. Clicking on the button then does nothing. Why does it do that?
Yes you can do it with only one function. Pass the character as parameter to the function. Like that:
version with addEventListener (prefered)
const btns = document.querySelectorAll('button');
const textBox = document.querySelector('#typeIn');
btns.forEach(b => {
b.addEventListener('click', e => {
textBox.innerHTML += e.target.getAttribute('data-char')
})
});
#typeIn {
margin:10px;
padding: 10px;
color: white;
min-height:40px;
background: gray;
}
<button data-char="aa">aa</button>
<button data-char="X">X</button>
<button data-char="ō">ō</button>
<button data-char="ū">ū</button>
<div id="typeIn"></div>
Generally try to avoid onclick events and use eventListener instead.
Version onclick Event
const textBox = document.querySelector('#typeIn');
function add(what) {
textBox.innerHTML += what;
}
#typeIn {
margin:10px;
padding: 10px;
color: white;
min-height:40px;
background: gray;
}
<button onclick="add('aa')">aa</button>
<button onclick="add('X')">X</button>
<button onclick="add('ō')">ō</button>
<button onclick="add('ū')">ū</button>
<div id="typeIn"></div>
You could do something like this:
Add (for example) data-value attributes to each of your buttons
<button data-value="A">A</button>
<button data-value="B">B</button>
<button data-value="C">C</button>
Grab all these buttons, and add "click" event listeners to each, like so:
document
.querySelectorAll('button') // Use appropriate class name here
.forEach(button =>
button
.addEventListener("click", (e) =>
console.log(e.target.dataset.value) // Do whatever you want here
)
)
Modify the listener function's body as per your needs
Here's a link to a JsFiddle that I've created for demo.

javascript changing text of multiple elements while hovering only one element

Having an issue with replacing text in multiple elements while hovering only one element
I tried doing document.getElementsbyClassName() but it didnt seem to work
function replace(text) {
var display = document.getElementById('head');
display.innerHTML = "";
display.innerHTML = text;
}
function revert(text) {
var display = document.getElementById('head');
display.innerHTML = "";
display.innerHTML = text;
}
<h2 id="head" onmouseover="replace('oh no!! the heading is gone')" onmouseout="revert('e8')"> e8 </h2>
<p id="pp"> :) </p>
I can replace the header but not the paragraph.
Personally I'd recommend a more extensible route revolving around data attributes.
The benefit to this approach is that you don't need to modify the JavaScript for each new item. You simply add attributes to the HTML elements themselves.
See the examples below - comments in the code.
If each element has it's own hover and replace information:
const elems = document.querySelectorAll('[data-replace]');
elems.forEach(elem => {
elem.addEventListener("mouseover", function() {
this.dataset["original"] = this.innerHTML; // store the text as data-original
this.innerHTML = this.dataset["replace"]; // update the html to be data-replace
});
elem.addEventListener("mouseout", function() {
this.innerHTML = this.dataset["original"]; // revert back to data-original
});
});
<h2 id="head" data-replace="'oh no!! the heading is gone'">e8</h2>
<p id="pp" data-replace="Something else"> :) </p>
If one item being hovered affects others, check out the example below instead.
You can group items by giving them the same data-group attribute. The one with the data-replace attribute is the one that triggers the replacement and defines the text.
const elems = document.querySelectorAll('[data-replace]');
elems.forEach(elem => {
//Get all elements that have the same data-group as the item being hovered
let group = document.querySelectorAll(`[data-group='${elem.dataset.group}']`);
elem.addEventListener("mouseover", function() {
group.forEach(e => { //For all group elements
e.dataset.original = e.innerHTML; //Store the original text
e.innerHTML = this.dataset.replace; //Replace the current text
});
});
elem.addEventListener("mouseout", function() {
group.forEach(e => e.innerHTML = e.dataset.original); //Rever to original text
});
});
<h2 data-group="group1" data-replace="Hello World">heading</h2>
<p data-group="group1">p</p>
<h2 data-group="group2" data-replace="Goodbye World">heading 2</h2>
<p data-group="group2">p 2</p>
Once option would be to pass the mouseover/mouseout event into the function. then you could use one function for multiple places.
<h2 id="head" onmouseover="replace(this, 'oh no!! the heading is gone', 'pp', 'moused over the heading')" onmouseout="replace(this, 'e8', 'pp', ':)')"> e8 </h2>
<p id="pp"> :) </p>
then use one function that will update the content of whatever element is passed to it.
function replace(e, text, secondElmID, secondElmText) {
// update the first element
e.innerHTML = "";
e.innerHTML = text;
// update the second element if is exists
var secondElm = document.getElementById(secondElmID);
if (secondElm) {
secondElm.innerHTML = secondElmText;
}
}
You just need to make sure that you reference both the elements that need to be affected and change both of them when one of them initiates the event.
Now, for few notes:
Don't use inline HTML event attributes.
Don't use .getElementsByClassName().
Don't use .innerHTML when you aren't setting any HTML as it has security and performance implications. Use .textContent instead.
let head2 = document.getElementById("head2");
let display = document.getElementById("pp");
let origHead2 = null;
let origPP = null;
head2.addEventListener("mouseover", function(){ replace("oh no!! the heading is gone"); });
head2.addEventListener("mouseout", revert);
function replace(text) {
// Store the original values before changing them
origHead2 = head2.textContent;
origPP = pp.textContent;
// Set the new values to what was passed into the function
head2.textContent = text;
display.textContent = text;
}
function revert() {
// Set the values back to the originals
head2.textContent = origHead2;
display.textContent = origPP;
}
<h2 id="head2"> e8 </h2>
<p id="pp"> :) </p>
You can proceed by grouping the elements which have the hover and replace text functionality under the same class and give each one a data-txt (where you can change txt part per your requirements) that holds the text that will be shown on hover and also will hold the old text each time an element gets hovered.
Here's a demo :
/**
* txtChange: the elements to be changed (their text) on mouse enter.
* doTextChange: a function that handles changing the text back and forth (replace and return to the original) for each element on both mouseenter and mouseleave events.
**/
const txtChange = [...document.querySelectorAll('.text-change')],
doTextChange = () => {
/** loop through the elements **/
txtChange.forEach(el => {
/** save the old text **/
const oldTxt = el.textContent;
/** replace the old text with the one in the data-txt **/
el.textContent = el.dataset.txt;
/** store the old text in the data-txt so that we return to the original text on mouse leave **/
el.dataset.txt = oldTxt;
});
};
/** cycle through the elements and attach the events **/
txtChange.forEach(el => {
/** the "doTextChange" can handle both the events **/
el.addEventListener('mouseenter', doTextChange);
el.addEventListener('mouseleave', doTextChange);
});
/** for demo purposes **/
.text-change {
margin: 35px 0;
padding: 8px;
background-color: #eee;
transition: all .4s 0s ease;
}
.text-change:hover {
background-color: #c0c0c0;
}
<!-- the hoverable elements share the same class "text-change" and each one has its own "data-txt" -->
<h2 class="text-change" data-txt="new text !">e8</h2>
<p class="text-change" data-txt="yet another new text">:)</p>

both buttons trigger one script

I have a button that triggers a script on a webpage. One instance works. When I try to add a second button/script, both buttons trigger the second script only. I know (think?) it's because the var I'm defining for the buttons are not unique to their individual scripts, but every way I attempt I break the whole thing.
button {
display: block;
cursor: pointer;
margin-left: 10px;}
button:after {
content: " (off)";
}
button.on:before {
content: "✓ ";
}
button.on:after {
content:" ";
}
.frac span {
-webkit-font-feature-settings: "frac" 1;
font-feature-settings: "frac" 1;
}
.onum span {
-webkit-font-feature-settings: "onum" 1;
font-feature-settings: "onum" 1;
}
Html:
<button name="frac" id="frac">Fractions</button>
<button name="onum" id="onum">Oldstyle Numbers</button>
This text is supposed change OT features when the buttons are pressed.
JS:
<script> var btn = document.getElementById("frac"),
body = document.getElementById("textA"),
activeClass = "frac";
btn.addEventListener("click", function(e){
e.preventDefault();
body.classList.toggle(activeClass);
btn.classList.toggle('on');
}); </script>
<!-- onum -->
<script> var btn = document.getElementById("onum"),
body = document.getElementById("textA"),
activeClass = "onum";
btn.addEventListener("click", function(f){
f.preventDefault();
body.classList.toggle(activeClass);
btn.classList.toggle('on');
}); </script>
The variance between the scripts/buttons are some of the changes from different things I've done, but I've gone mostly back to the beginning so it's simpler.
In javascript, every variable that you declare is inherently available across the entire page. So, putting them in separate tags will have no effect.
So essentially, your second variable btn is actually overwriting the first one. Rename the second variable to say, btn2.
Or, as an alternative, change the line
btn.classList.toggle('on')
to
this.classList.toggle('on')
this within the click handler will always point to the current button being clicked.
You can do it in fewer lines of code
// you create the array of buttons
let butons = Array.from(document.querySelectorAll("button")),
// you define the _body
_body = document.getElementById("textA")
// for every button in the buttons array (map is an iterator)
butons.map((btn) =>{
//you define the activeClass to be the name attribute of the button
let activeClass = btn.getAttribute("name");
// everytime you click the button
btn.addEventListener("click", (e) =>{
/*this was in your code. I don't know why you need it
e.preventDefault();*/
//you toggle the activeClass & the on class
_body.classList.toggle(activeClass);
btn.classList.toggle("on");
})
})
button {
display: block;
cursor: pointer;
margin-left: 10px;
}
button:after {
content: " (off)";
}
button.on:before {
content: "✓ ";
}
button.on:after {
content: " ";
}
/* I'm using color to visualize the change */
.frac span {
color: red;
}
.onum span {
color: green;
}
<button name="frac" id="frac">Fractions</button>
<button name="onum" id="onum">Oldstyle Numbers</button>
<p id="textA">The variance between the <span>scripts/buttons</span> are some of the changes from different things I've done, but I've gone mostly back to the beginning so it's simpler.</p>

Randomize the layout of a set of existing images with JavaScript

I have a grid of images (baseball and football teams), with names associated with them (like captions).
What I'm trying to do is randomize the placement of the images with the names so they appear at different spots of the grid.
I can get the images and names to display (the names come from a textarea and are stored into a names array) just fine, but as soon as I hit the random button, the img.src disappears, leaving me with blank spots.
The names still randomize though and work fine.
This is my code so far:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>sports</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<div class="container-fluid">
<h1>Sports Random</h1>
<br>
<div class="container">
<div class="row" id="parent">
</div>
<div class="row">
</div>
<textarea id="textarea"></textarea>
<div id="buttonGroup">
<button id="random">Random</button>
<button id="baseball">Baseball</button>
<button id="football">Football</button>
<button id="reset">Reset</button>
</div>
</div>
<br>
</div>
<script src="js/main.js"></script>
</body>
</html>
JavaScript:
bGroup.addEventListener("click", function images(e) {
tDisplay.innerHTML = "";
for (var i = 1; i < names.length; i++) {
var newDiv = document.createElement("div");
var newImg = document.createElement("img");
var userName = document.createElement("p");
newDiv.className = "col-sm-3 col-md-3 col-lg-2"
newDiv.appendChild(newImg);
newDiv.appendChild(userName);
userName.textContent = names[Math.random() * i | 0];
if (e.target.id === "baseball") {
newImg.src = "images\\baseball\\team" + i + ".jpg";
} else if (e.target.id === "football") {
newImg.src = "images\\football\\team" + i + ".gif";
}
tDisplay.appendChild(newDiv);
};
});
// random the images
random.addEventListener("click", function random() {
for (var i = 0; i < tDisplay.children.length; i++) {
tDisplay.appendChild(tDisplay.children[Math.random() * i | 0]);
tDisplay.getElementsByTagName("p")[Math.random() * i | 0].textContent =
names[Math.random() * i | 0];
}
});
Right now the buttons on my page are in a div group (bGroup) and then I delegate events depending on which button is clicked.
I had this working when I had separate functions for "baseball" and "football" images; just trying to reduce code.
The random button is included in the button group but I kept it separate just for the sake of organization; kind of wondering if that is good practice or not?
If possible I would like any answers strictly in JavaScript.
Interference
The cause of the problem is that your Random button has its own "click" event listener whilst being a child of bGroup which also has a "click" event listener.
The exact process that results in empty clones is kind of irrelevant, but could be examined (if interested) by use console.log().
When you click Random, you trigger both the randomization of the contents of tDisplay and the initialization/creation of <div><img><p></p></div> children.
You have two options:
Either stopPropagation() of the "click" event in the unique listener attached to Random.
Or include the randomization functionality in the function triggered by a "click" on bGroup.
I have built a simplified version of your code below, using the second option stated above.
Since the randomization code Math.random() * i | 0 is used many times, I've created a function to return the random result of any number parsed through it.
The const declarations at the top of the JS are to store values that will never change and that are repeatedly used throughout the script.
The const declaration creates a read-only reference to a value. It does not mean the value it holds is immutable, just that the variable identifier cannot be reassigned. For instance, in the case where the content is an object, this means the object's contents (e.g., its parameters) can be altered.
I've used let instead of var where suitable, since it's less leaky.
let allows you to declare variables that are limited in scope to the block, statement, or expression on which it is used. This is unlike the var keyword, which defines a variable globally, or locally to an entire function regardless of block scope.
I chose to use forEach() instead of a for loop to iterate through names, but retained the for loop to handle the randization.
Each has its own merits.
The arrow function called by forEach() uses two arguments; n and i, where n is the name on each loop, and i is the index of the name (a loop counter).
I also chose to use querySelectorAll() instead of getElementsByTagName() for brevity.
Those changes have benefits, but the only significant change is in moving the "click" handling for Random into the handler for clicks on bGroup.
Be aware that the randomization may not change the display every time (the nature of random), but keep clicking and you'll see that it does in fact work; a longer names Array (and thus more images) would likely randomize more noticeably.
const tDisplay = document.querySelector( ".container" ),
bGroup = document.querySelector( "#buttonGroup" ),
names = [ "foo", "bar", "baz", "qux" ];
function randIndex( i ) {
return ( Math.random() * i | 0 );
}
bGroup.addEventListener( "click", function( evt ) {
let trg = evt.target.id;
if ( trg !== "random" ) {
tDisplay.innerHTML = "";
names.forEach( ( n, i ) => {
let newDiv = document.createElement( "div" ),
newImg = document.createElement( "img" ),
userName = document.createElement( "p" );
//newDiv.className = "col-sm-3 col-md-3 col-lg-2";
newDiv.appendChild( newImg );
newDiv.appendChild( userName );
userName.textContent = names[ randIndex( i ) ];
if ( trg === "sports" ) {
newImg.src = "https://lorempixel.com/100/70/sports/" + i;
} else if ( trg === "cats") {
newImg.src = "https://lorempixel.com/100/70/cats/" + i;
}
tDisplay.appendChild( newDiv );
} );
} else {
for ( let i = 0; i < tDisplay.children.length; i++ ) {
tDisplay.appendChild( tDisplay.children[ randIndex( i ) ] );
tDisplay.querySelectorAll( "p" )[ randIndex( i ) ].textContent =
names[ randIndex( i ) ];
}
}
});
body {
font-family: sans-serif;
}
h1, p {
margin: .5em 0;
}
#buttonGroup {
margin-bottom: 1em;
}
.container div {
display: inline-block;
margin-right: 1em;
}
<h1>Images Random</h1>
<div id="buttonGroup">
<button id="random">Random</button>
<button id="sports">Sports</button>
<button id="cats">Cats</button>
</div>
<div class="container"></div>

making a JavaScript character selector

I’m trying to make a character selector, which select each character separately every time button pressing. But its not working at all
<!DOCTYPE html>
<html lang="en">
<head>
<title>HELLO WORLD</title>
</head>
<body>
<center>
<br />
<p id="temp">ABCDEFGHIJKLMNOPQRSTUVWXYZ</p>
<br />
<input type="button" onclick="selector()" value="SELECT" />
<script>
var x = document.getElementById("temp").innerHTML;
var i = 0;
function selector() {
x.charAt(i).style.backgroundColor = "red";
i++;
}
</script>
</center>
</body>
</html>
The primary issue is that you can only apply styling to HTML elements, not individual characters that make up the text content of an element.
This is why you are getting the "undefined" error that you are... backgroundColor can't be set on undefined, which refers to the return value of the style property, which doesn't exist on individual characters.
So first, you must wrap the character(s) to be highlighted in another element (a <span> is the best choice here) and then you can have the contents of the <span> be highlighted.
You weren't exactly clear on whether each click of the button should highlight only the next character or if the highlighting should be extended to include the next character, so I have solutions for both of those below. See comments inline for detailed explanations:
Solution #1 (highlight single character at a time)
// Get DOM reference to paragraph (not contents of paragraph)
var x = document.getElementById("temp");
// Get DOM reference to button so we can wire it up to
// an event handler in JS (not via inline event handling attributes).
var btn = document.getElementById("btn");
// Set up event handler:
btn.addEventListener("click", selector);
var i = 0;
function selector() {
// Get the character to be highlighted
var char = x.textContent.charAt(i);
// Set the contents of the paragraph to a new string that has the particular character
// wrapped with a <span> that is set to use a predetermined class that does the actual
// highlighting.
x.innerHTML = x.textContent.replace(char, "<span class='highlight'>" + char + "</span>");
// Increment i until we've hit the 26th value, then reset to 0
i < 25 ? i++ : i = 0;
}
.highlight { background-color:red; }
<p id="temp">ABCDEFGHIJKLMNOPQRSTUVWXYZ</p>
<br>
<!-- There is no closing tag for input elements! -->
<input type="button" id="btn" value="SELECT">
Solution #2 (extend highlighting to include next character)
// Get DOM reference to paragraph (not contents of paragraph)
var x = document.getElementById("temp");
// Get DOM reference to button so we can wire it up to an event handler in JS (not via inline event
// handling attributes).
var btn = document.getElementById("btn");
// Set up event handler:
btn.addEventListener("click", selector);
var i = 0;
function selector() {
// Get the character to be highlighted
var char = x.textContent.charAt(i);
// Set the contents of the paragraph to a new string that encloses all the characters
// up to and including the current one in a <span> that is set to use a predetermined
// class that does the actual highlighting.
x.innerHTML = "<span class='highlight'>" + x.textContent.replace(char, char + "</span>");
// Increment i until we've hit the 26th value, then reset to 0
i < 25 ? i++ : i = 0;
}
.highlight { background-color:red; }
<p id="temp">ABCDEFGHIJKLMNOPQRSTUVWXYZ</p>
<br>
<!-- There is no closing tag for input elements! -->
<input type="button" id="btn" value="SELECT">
Here's one possible implementation
Create a list of characters in the HTML element by using string#split.
Wrap each of these characters inside a span tag. This is easy to do using the map function. We want to check if these are alphabetical characters so we use the test function.
We then need to find the number of characters in the original string. We can do that by stripping the new string of all of it's span tags. Set the initial index to the first character, which in JavaScript is zero.
Call an event listener. This could be for example keydown, which listens for keypresses.
All of our characters have now been wrapped with a char class. To find a particular one, use document.querySelectorAll, and pass in [index]
In the event that we cycle through the string, we will remove the styling for the last character in the list. Otherwise, naturally, the previous character will be converted back to normal.
var chars = document.getElementById("par").innerHTML.split('');
var wrapped = chars.map( c => /[a-z]/i.test(c) ? "<span class='char'>" + c + "</span>" : "").join('');
var numLetters = wrapped.replace(/<span class='char'>/g, '').replace(/<\/span>/g, '').length;
document.getElementById("par").innerHTML = wrapped;
var index = 0;
document.addEventListener("keydown", function() {
document.querySelectorAll(".char")[index].style.color = "red";
if (index == 0) {
document.querySelectorAll(".char")[numLetters - 1].style.color = "black";
}
if (index > 0) {
document.querySelectorAll(".char")[index - 1].style.color = "black";
}
index = index == numLetters - 1 ? 0 : index + 1
});
<p id="par">This is a paragraph</p>
You need to put all character into a span tag, and change the span background color.
var i = 0;
function selector() {
if (document.getElementById("temp_" + i))
document.getElementById("temp_" + i).style.backgroundColor = "red";
i++;
}
<p id="temp">
<span id='temp_0'>A</span>
<span id='temp_1'>B</span>
<span id='temp_2'>C</span>
<span id='temp_3'>D</span>
</p>
<button onclick='selector();'>Test</button>

Categories

Resources