This code currently works, and when each div is clicked the background color and font size will change. In addition, the formatting for one of the other two divs which was already clicked will be removed. The problem is that this will end up requiring a lot of code, what I imagine is far more than is needed. I'm wondering how to repeat less. It is not such a big deal in this example, with only three divs, but my actual project will need many, many more.
I tried including multiple divs, so it would look like this;
document.querySelector(".div2, .div1").classList.remove("styles");
but that did not seem to work.
const div1 = document.querySelector(".div1");
const div2 = document.querySelector(".div2");
const div3 = document.querySelector(".div3");
function makeBigDiv1 () {
document.querySelector(".div1").classList.add("styles");
document.querySelector(".div2").classList.remove("styles");
document.querySelector(".div3").classList.remove("styles");
}
div1.addEventListener("click", makeBigDiv1);
function makeBigDiv2 () {
document.querySelector(".div2").classList.add("styles");
document.querySelector(".div1").classList.remove("styles");
document.querySelector(".div3").classList.remove("styles");
}
div2.addEventListener("click", makeBigDiv2);
function makeBigDiv3 () {
document.querySelector(".div3").classList.add("styles");
document.querySelector(".div1").classList.remove("styles");
document.querySelector(".div2").classList.remove("styles");
}
div3.addEventListener("click", makeBigDiv3);
.div1 {
width:500px;
height:100px;
border: 2px solid black;
}
.div2 {
width:500px;
height:100px;
border: 2px solid black;
}
.div3 {
width:500px;
height:100px;
border: 2px solid black;
}
.styles {
font-size: 50px;
background-color: grey;
}
<div class="div1">One</div>
<div class="div2">Two</div>
<div class="div3">Three</div>
Well as I mentioned the code works, but would just become prohibitively verbose I feel if applied to a large project. I'm relatively new to this and want to write DRY - don't repeat yourself - code. Thanks!
If you want the three divs to have the shared style, you can style them all at once. You can also make a lot of your click functionality reusable. This is what I would do:
const elements = document.querySelectorAll("div")
function attachClickHandler(className) {
return () => {
document.querySelector(`.${className}`).classList.add('styles');
document.querySelectorAll(`div:not(.${className})`).forEach(element => { element.classList.remove('styles') });
}
}
elements.forEach(element => {
element.addEventListener("click", attachClickHandler(element.className))
})
<html>
<head>
<style>
.div1, .div2, .div3 {
width:500px;
height:100px;
border: 2px solid black;
}
.styles {
font-size: 50px;
background-color: grey;
}
</style>
</head>
<body>
<div class="div1">One</div>
<div class="div2">Two</div>
<div class="div3">Three</div>
<script>
</script>
</body>
</html>
In context you would probably not want to add event listeners to every div so you could just add a class to all divs you want to make selectable and find all by class name instead of find all of type div. This would also allow you to add the base styling for the shared class instead of to all 3 divs.
You can do this quite easily just by looping thru the divs. here is an example. There is some optimization you can do but you get the idea
const div = document.querySelectorAll('.div');
for (var i = 0; i < div.length; i++) {
div[i].addEventListener('click', function(event) {
for (var j = 0; j < div.length; j++) {
// remove styles class from all the div classes
div[j].classList.remove("styles");
}
// add styles class only to the clicked item
this.classList.add("styles");
});
}
.styles {
font-size: 50px;
background-color: grey;
}
<div class="div">One</div>
<div class="div">Two</div>
<div class="div">Three</div>
this.classList.add("styles"); The this refers to the clicked item
Here are some changes:
Use the same className for each block, and give it a specific name (i.e. box).
Same for the added className, make it clear. (i.e. is-selected).
Don't duplicate functions for the same action and use forEach instead to loop through each box.
// Get all boxes
const boxes = document.querySelectorAll('.box');
// For each box
[...boxes].forEach(box => {
// Attach an event click listener
box.addEventListener('click', () => {
// Add the `is-selected` className to the clicked one
box.classList.add('is-selected');
// Remove the `is-selected` className to all the others
[...boxes].filter(el => el !== box).forEach(box => {
box.classList.remove('is-selected');
})
});
});
.box {
width: 500px;
height: 100px;
border: 2px solid black;
}
.box.is-selected {
font-size: 50px;
background-color: grey;
}
<div class="box">One</div>
<div class="box">Two</div>
<div class="box">Three</div>
Related
So I'm trying to implement a set of functions on my website with multiple div objects in it, so that when I click on Div A, it sets the text color of the page to red through Class A, and when i click on Div B, it sets the text to green through Class B, and so on and so forth.
My issue is that the other classes don't unset when clicking multiple objects and one class overrides the others, so the color of the text won't switch anymore.
I've been looking for solutions and trying to use addClass() and removeClass(), but it doesn't work for some reason. Here is a snippet of my code here
$(function() {
$('.one').click(function() {
$("h1").addClass('onetxt');
$("h1").removeClass('twotxt, threetxt');
});
});
$(function() {
$('.two').click(function() {
$("h1").addClass('twotxt');
$("h1").removeClass('onetxt, threetxt');
});
});
$(function() {
$('.three').click(function() {
$("h1").addClass('threetxt');
$("h1").removeClass('onetxt, twotxt');
});
});
h1 {
text-align: center;
}
div {
height: 65px;
width: 65px;
margin: 20px auto;
border-style: solid;
}
/*style info, ignore above here*/
.one {
background-color: red;
}
.onetxt {
color: red;
}
.two {
background-color: green;
}
.twotxt {
color: green;
}
.three {
background-color: blue;
}
.threetxt {
color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1>Sample Text</h1>
<div class="one"></div>
<div class="two"></div>
<div class="three"></div>
Any help on this would be greatly appreciated, and if you need more info, ask me in the replies, thank you!
EDIT: Here's a JSFiddle link demonstrating the code that i currently have, my intention is for all three of the DIV elements to change the top text's color when selected in any order, with using the classes if possible.
It seems like the main issue is that removeClass doesn't support multiple class selectors (like 'onetxt, twotxt'). Also you aren't removing all possible classes depending on the order of clicks.
Here's a solution that might work. I've written some helper functions which hopefully clarify what's going on.
const targets = 'h1, p'
const classmap = {
one: 'onetxt',
two: 'twotxt',
three: 'threetxt'
}
const allclasses = Object.values(classmap);
function clearSelection() {
allclasses.forEach(function(clz) { $(targets).removeClass(clz) });
}
function addSelection(sel) {
$(targets).addClass(classmap[sel]);
}
$(function() {
$('.one').click(function(){
clearSelection();
addSelection('one')
});
});
$(function() {
$('.two').click(function(){
clearSelection();
addSelection('two')
});
});
$(function() {
$('.three').click(function(){
clearSelection();
addSelection('three')
});
});
Here's a vanilla DOM API solution based on classList.toggle(className: string, force: boolean). The second parameter controls whether toggle works as remove or add.
const classes = ['one', 'two', 'three'];
classes.forEach(clazz => {
document.querySelector(`.${clazz}`).addEventListener('click', () => {
classes.forEach(cl => document.querySelector('h1').classList.toggle(`${cl}txt`, cl === clazz));
})
})
h1 {
text-align: center;
}
div {
height: 35px;
width: 35px;
margin: 5px auto;
border-style: solid;
}
/*style info, ignore above here*/
.one {
background-color: red;
}
.onetxt {
color: red;
}
.two {
background-color: green;
}
.twotxt {
color: green;
}
.three {
background-color: blue;
}
.threetxt {
color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1>Sample Text</h1>
<div class="one"></div>
<div class="two"></div>
<div class="three"></div>
There is no .removeClass() overload supports multiple argument, it only remove the first class. .removeClass() with no argument remove all the classes. This maybe the one you need.
I tweak your code a little bit, including:
Rename some class name. .color-palette is the container of all color selection. .color is individual color choice box.
Add data-color-name attribute to .color. The attribute will be used for sample-text css class assignment.
Simplify the click event with a single, event-delegate handler. I try to decouple the add/remove class logic with the actual color name. This way if you have more color boxes to add, you do not need to copy a new set of function.
Define custom css property. E.g. (--color-1, --color-2). The same property is used for color box background and sample text font color. You don’t have to maintain colors in two different place.
$('.color-palette').on('click', '.color', function(e) {
$("#sample-text").removeClass().addClass($(e.currentTarget).data('color-name'));
});
/* maintain the color choices here */
:root {
--color-1: red;
--color-2: green;
--color-3: blue;
}
h1 {
text-align: center;
}
.color {
height: 65px;
width: 65px;
margin: 20px auto;
border-style: solid;
}
/* style info, ignore above here */
/* color palette style */
.color.color-1 {
background-color: var(--color-1);
}
.color.color-2 {
background-color: var(--color-2);
}
.color.color-3 {
background-color: var(--color-3);
}
/* sample text style */
#sample-text.color-1 {
color: var(--color-1);
}
#sample-text.color-2 {
color: var(--color-2);
}
#sample-text.color-3 {
color: var(--color-3);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1 id="sample-text">Sample Text</h1>
<div class="color-palette">
<div class="color color-1" data-color-name="color-1"></div>
<div class="color color-2" data-color-name="color-2"></div>
<div class="color color-3" data-color-name="color-3"></div>
</div>
I've got a simple text button with an image of an arrow next to it. I'm wanting the arrow image to move when someone hovers over the button.
I currently have this working in one instance with JS 'document.getElementById...', but I have several buttons across my site that I'd like to have the same behavior. My first thought would be to use a class instead of an id, and use the same functions.
For whatever reason, document.getElementsByClassName doesn't work - even in one instance.
Here's a simpler version to demonstrate - View on Codepen: https://codepen.io/sdorr/pen/JxYNpg
HTML
<HTML>
hover over me
<div id="block"></div>
hover over me
<div class="block"></div>
CSS
* {
margin: 0;
padding: 0;
}
.button {
color: #000000;
text-decoration: none;
background-color: cyan;
margin: 0;
display: block;
width: 300px;
padding: 20px;
text-align: center;
}
#block {
width: 30px;
height: 30px;
background-color: red;
}
.block {
width: 30px;
height: 30px;
background-color: green;
}
JS
function move() {
document.getElementById("block").style.marginLeft = "35px";
}
function moveBack() {
document.getElementById("block").style.marginLeft = "0px";
}
function moveAlt() {
document.getElementsByClassName("block").style.marginLeft =
"35px";
}
function moveBackAlt() {
document.getElementsByClassName("block").style.marginLeft =
"0px";
}
First off, why isn't the behavior with a class working but an id works fine?
Secondly, would a class solve this issue and be scalable across all buttons with the same two functions (onmouseover / onmouseout)?
If not, any ideas on a solution? I currently have a solution I found using jQuery that does work, but when hovering over one button, all arrow images move across the site. I don't necessarily mind this behavior because only one button is really in view at a time - but I'm trying to learn JS and solve problems with my own solutions!
I greatly appreciate your desire to learn on your own and not rely on premade solutions. Keep that spirit and you will go places!
When it comes to getElementsById, we know this should work for one element, since the function returns a single Element.
However, what does getElementsByClassName return?
(see: https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName)
It returns an HTMLCollection which you can iterate over to change an single element's style.
So, to get this to work with JavaScript you need to write a function that will be able to identify the particular div.block you want to move. But, this puts you back to where you started, needing some particular identifier, like an id or a dataset value to pass to the function.
Alternately, based on the HTML structure you provide, you could look for nextElementSibling on the a that get's clicked. But I would set up an eventListener rather than adding a JS function as a value to the onmouseenter property.
const btns = document.getElementsByTagName('a');
/*** UPDATE forEach is a NodeList method, and will fail on HTMLCollection ***/
/* this fails -> Sorry! ~~btns.forEach(button=>{~~
/* the following will work
/**********/
for (let i = 0; i < btns.length; i++){
btns[i].addEventListener('mouseenter', function(e) {
//we pass e to the function to get the event and to be able to access this
const block = this.nextElementSibling;
block.style.marginLeft = "35px";
})
btns[i].addEventListener('mouseleave', function(e) {
const block = this.nextElementSibling;
block.style.marginLeft = "0";
})
}
But with siblings, there is a CSS-only solution.
We can use the Adjacent Sibling Selector combined with the :hover state selector and no JavaScript is needed, if we are just moving back and forth.
.button:hover+.block {
margin-left: 35px;
}
See the Snipped Below
* {
margin: 0;
padding: 0;
}
.button {
color: #000000;
text-decoration: none;
background-color: cyan;
margin: 0;
display: block;
width: 300px;
padding: 20px;
text-align: center;
}
.block {
width: 30px;
height: 30px;
background-color: green;
}
.button:hover+.block {
margin-left: 35px;
}
hover over me
<div class="block"></div>
hover over me
<div class="block"></div>
As Vecta mentioned, getElementsByClassName returns an array-like. You'll need to do something like this to get the first element:
function moveAlt() {
document.getElementsByClassName("block")[0].style.marginLeft = "35px";
}
function moveBackAlt() {
document.getElementsByClassName("block")[0].style.marginLeft = "0px";
}
However a better solution might be to use document.querySelector, which operates similarly to jQuery's $() syntax:
function moveAlt() {
document.querySelector(".block").style.marginLeft = "35px";
}
function moveBackAlt() {
document.querySelector(".block").style.marginLeft = "0px";
}
I am creating a list of profiles which will be displayed based on a given category. The setup makes it inconvenient to use a container element to wrap the list items, so I'm using display:inline-flex on each item instead of a flex container with the usual justify-this and align-that.
The issue is that the first element in the row appears to have a space to the right of it, and I'm not sure why.
I'd like to display all the elements evenly, in this case 4 to a row with identical spacing, without nesting them in a parent container if possible.
// simple function to repeat html elements
$(document).ready(function() {
let a = $('.a')[0];
const repeats = 11;
let count = 0;
while (count < repeats) {
$('body').append($(a).clone())
count++;
}
//$( 'body' ).append( html );
});
.a {
background-color: red;
box-sizing: border-box;
border: 1px solid green;
display: inline-flex;
height: 25px;
width: 25%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
<body>
<div class="a"></div>
</body>
</html>
Actually now the first element (with space to the right) is one you declared in your html. Remove it from there and use instead:
// simple function to repeat html elements
$(document).ready(function() {
let a = $('<div class="a"></div>');
const repeats = 12;
let count = 0;
while (count < repeats) {
$('body').append($(a).clone())
count++;
}
//$( 'body' ).append( html );
});
.a {
background-color: red;
box-sizing: border-box;
border: 1px solid green;
display: inline-flex;
height: 25px;
width: 25%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
<body>
</body>
</html>
A look at the dev tools inspector reveals a bit of invisible code between the first and second items:
When I delete those lines in the inspector, the gap is removed and all boxes line up as intended.
So it's an issue with your script appending elements. I'm not sure how you want to handle that (e.g., is the script only for this demo? is the problem only in this IDE? is removing the first element an option?), so I won't get into solutions.
Current codepen: http://codepen.io/anon/pen/rVNpvg
function opcWizardry() {
var $activeBox = $('.box.active');
$activeBox.prevAll('.box').addClass('done');
$activeBox.nextAll('.box').addClass('hidden');
}
opcWizardry();
.box {
border: solid 1px #bbb;
margin: 5px;
padding: 5px;
}
.box.done {
background: #bbb;
}
.box.active {
background: green;
}
.box.hidden {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="box">box1</div>
<div class="box">box2</div>
<div class="box active">box3</div>
<div class="box">box4</div>
<div class="box">box5</div>
I'm currently working on the best way to do this and would love input.
I'm writing a function that will loop through this set of elements that have a class name of box. One of the elements in that list will have a class name of active. For all the elements prior to the one with the active class, I want to set a class name of done. For all of the elements after the one with the active class, I want to set a class name of hidden.
The way I've done it seems unnecessarily iterative and I'm wondering if someone can provide an elegant method.
var $activeBox = $('.box.active');
$activeBox.prevAll('.box').addClass('done');
$activeBox.nextAll('.box').addClass('hidden');
I am trying to make a Tic-Tac-Toe game and I am currently working on the aspect of selecting the boxes themselves, but while using JQuery the :not selector doesn't seem to be working.
function main(){
//Functions
$('.cell:not(.block)').click(function(){
$(this).addClass(color);
$(this).addClass('block');
if(color=='g'){color='r';}else{color='g';}
});
//Variables
var color = 'g';
}
$().ready(main);
html {
background-color:black;
color:white;
text-align:center;
}
.cell {
border: 1px solid white;
margin:1px;
width:30%;height:30%;
}
.g {background-color:lime;}
.r {background-color:red;}
#board {height:500px;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<header>Tic Tac Toe</header>
<div id='board'>
<div class='cell'></div>
<div class='cell'></div>
<div class='cell'></div>
</div>
That isn't how jQuery selects elements.
When you run $('selector'), the selector is evaluated immediately, against the current state of the DOM. Your three elements are found because none of them have .block, and click handlers are bound to all three elements.
There are several ways of fixing this:
If you want the selector to be dynamically evaluated, you need to use on to delegate the event to one of the containing elements. The event on the specific child element will bubble up to the containing element's handler and be tested each time against the selector. This is the most expensive option, and probably the least desirable; you shouldn't be relying on jQuery selectors for this kind of logic:
$('.board').on('click', '.cell:not(.block)', function () {
// ...
});
Alternatively, the simplest and cheapest option is to simply check for .block in the click handler:
$('.cell').click(function () {
if ($(this).hasClass('block')) return;
//...
Finally, you can unbind the click handler at the same time you add the .block class
$('.cell').click(function () {
$(this).unbind( "click" );
// ...
Since you are changing the class after already have made the selection it would count as a dynamic selector and you need to use .on() for that.
function main() {
//Functions
$('#board').on('click', '.cell:not(.block)', function() {
$(this).addClass(color).addClass('block');
color = color == 'g' ? 'r' : 'g';
});
//Variables
var color = 'g';
}
$().ready(main);
html {
background-color: black;
color: white;
text-align: center;
}
.cell {
border: 1px solid white;
margin: 1px;
width: 30%;
height: 30%;
}
.g {
background-color: lime;
}
.r {
background-color: red;
}
#board {
height: 500px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<header>Tic Tac Toe</header>
<div id='board'>
<div class='cell'></div>
<div class='cell'></div>
<div class='cell'></div>
</div>