Javascript half works locally, fully works on code pen - javascript

Been racking my brains trying to figure this out. I've tried adding $( document ).ready(function() { and closing it again around the script but not joy.
It's a simple filter. Supposed to show / hide div elements depending on the button. The script adds 'active' to the button clicked and removes it from the other buttons.
It works exactly as intended on Code Pen. However, when running it locally or on a server, the filter works fine, 'active' is added to the button clicked, but 'active' is not removed from the last button and keeps stacking (ie if I click snowdonia 4 times, the class 'active' is applied 4 times. If I then click tryfan, active is applied correctly but not removed from the previous button)
The code I'm using is below. Any help would be much appreciated. The Javascript is EXACTLY as it appears in my filter.js file
filterSelection("all")
function filterSelection(c) {
var x, i;
x = document.getElementsByClassName("filterDiv");
if (c == "all") c = "";
for (i = 0; i < x.length; i++) {
w3RemoveClass(x[i], "show");
if (x[i].className.indexOf(c) > -1) w3AddClass(x[i], "show");
}
}
function w3AddClass(element, name) {
var i, arr1, arr2;
arr1 = element.className.split(" ");
arr2 = name.split(" ");
for (i = 0; i < arr2.length; i++) {
if (arr1.indexOf(arr2[i]) == -1) {element.className += " " + arr2[i];}
}
}
function w3RemoveClass(element, name) {
var i, arr1, arr2;
arr1 = element.className.split(" ");
arr2 = name.split(" ");
for (i = 0; i < arr2.length; i++) {
while (arr1.indexOf(arr2[i]) > -1) {
arr1.splice(arr1.indexOf(arr2[i]), 1);
}
}
element.className = arr1.join(" ");
}
var header = document.getElementById("myBtnContainer");
var btns = header.getElementsByClassName("btn-filter");
for (var i = 0; i < btns.length; i++) {
btns[i].addEventListener("click", function() {
var current = document.getElementsByClassName("active");
if (current.length > 0) {
current[0].className = current[0].className.replace(" active", "");
}
this.className += " active";
});
}
.filterDiv {
display: none;
/* Hidden by default */
}
/* The "show" class is added to the filtered elements */
.show {
display: block;
}
.btn-filter {
background-color: #fff;
border-radius: 0;
padding-right: 5px;
padding-left: 5px;
padding-top: 4px;
padding-bottom: 4px;
text-transform: uppercase;
font-family: 'Montserrat';
font-size: 14px;
font-weight: 600;
color: #91227e;
border: 2px;
border-style: solid;
border-color: #91227e;
}
.active,
.btn-filter:hover {
background-color: #91227e;
color: #fff;
}
<div id="myBtnContainer">
<button class="btn-filter active" onclick="filterSelection('all')"> Show all</button>
<button class="btn-filter" onclick="filterSelection('snowdon')"> Snowdon</button>
<button class="btn-filter" onclick="filterSelection('tryfan')"> Tryfan</button>
<button class="btn-filter" onclick="filterSelection('glyder')"> Glyder</button>
</div>
Here are the scripts I have linked in the html file:
And here a link to a working version in codepen: https://codepen.io/dan-anderton-the-sasster/pen/vYgWdER

This is not an answer, but more of a suggestion. But with your localhost environment are you also including the necessary files which are being used in the codepen example?
IE: jquery?
I cannot tell from the example you have provided, but a link to the codepen would be useful.
TRY - you may need to tweak slightly to fit your html, as I cannot view this:
var btns = document.getElementsByClassName("btn-filter");
var i;
for (i = 0; i < btns.length; i++) {
btns[i].addEventListener("click", function() {
this.classList.toggle("activated");
var content = this.nextElementSibling;
if (content.style.display === "block") {
content.style.display = "none";
} else {
content.style.display = "block";
}
});
}

Turns out there was a CSS conflict with the active tag between my code and bootstrap. Changing the class to be applied to the current button solved the problem. So changing it from active to active-filter in this case and then styling accordingly.
Thanks everyone for looking at it for me.

Related

How to add hover effect upon mouseover to all divs on a page?

I have a 16x16 grid of small squares. I have added a permanent "hover" effect to make the very first box turn red when I put my mouse over it. However, I want to add the same effect to all of the boxes on the page. I can't figure out how to do it - I have tried to add an event listener to the whole page and used target.nodeName and target.NodeValue, but to no avail. I have included the working version where the fix box turns red on mouseover.
var n=16; //take grid column value as you want
const bigContainer = document.querySelector('.bigContainer')
for(var i = 1; i < n; i++) {
bigContainer.innerHTML+='<div class="row">';
for(j = 0; j < n; j++) {
bigContainer.innerHTML+='<div class="smallBox">';
}
}
const smallBox = document.querySelector('.smallBox');
smallBox.addEventListener('mouseover', () => {
smallBox.classList.add('permahover');
});
.smallBox {
border: 1px solid black;
width: 20px;
height: 20px;
display: inline-block;
}
.permahover {
background: red;
}
h1 {
text-align: center;
}
.bigContainer {
text-align: center;
}
<h1>Etch-a-Sketch Assignment - The Odin Project</h1>
<div class="bigContainer">
</div>
The immediate problem you are having is that this is only querying, and subsequently adding an event listener to, one element.
const smallBox = document.querySelector('.smallBox');
smallBox.addEventListener('mouseover', () => {
smallBox.classList.add('permahover');
});
In the above portion of your code, querySelector only returns the first matching element. You may be looking for querySelectorAll here which returns a NodeList of matching elements.
You have two options (perhaps others if you want to restructure your code further). The naive approach is to, in fact, query for all of the cells and add event listeners to each of them.
var n=16; //take grid column value as you want
const bigContainer = document.querySelector('.bigContainer')
for(var i = 1; i < n; i++) {
bigContainer.innerHTML+='<div class="row">';
for(j = 0; j < n; j++) {
bigContainer.innerHTML+='<div class="smallBox">';
}
}
const smallBoxes = document.querySelectorAll('.smallBox');
[...smallBoxes].forEach(smallBox => {
smallBox.addEventListener('mouseover', () => {
smallBox.classList.add('permahover');
});
})
.smallBox {
border: 1px solid black;
width: 20px;
height: 20px;
display: inline-block;
}
.permahover {
background: red;
}
h1 {
text-align: center;
}
.bigContainer {
text-align: center;
}
<h1>Etch-a-Sketch Assignment - The Odin Project</h1>
<div class="bigContainer">
</div>
Another option is to use event delegation as you identified. Here is how you can leverage that. Note: this approach is a bit tricker for an aggressive event like "mouseover" as you may get false positive targets (like the outer container for example).
var n=16; //take grid column value as you want
const bigContainer = document.querySelector('.bigContainer')
for(var i = 1; i < n; i++) {
bigContainer.innerHTML+='<div class="row">';
for(j = 0; j < n; j++) {
bigContainer.innerHTML+='<div class="smallBox">';
}
}
bigContainer.addEventListener('mouseover', e => {
var target = e.target
if (target !== bigContainer) {
target.classList.add('permahover')
}
})
.smallBox {
border: 1px solid black;
width: 20px;
height: 20px;
display: inline-block;
}
.permahover {
background: red;
}
h1 {
text-align: center;
}
.bigContainer {
text-align: center;
}
<h1>Etch-a-Sketch Assignment - The Odin Project</h1>
<div class="bigContainer">
</div>
You need to use a delegation event, because all the small boxes don't exist on the page when the page is loaded (You can figure out in the inspector element that only your first box has the event listener).
So you listen the whole container (because it is always on the page on load)
bigContainer.addEventListener('mouseover', () => {
// Code for checking if we hovered a small div & if yes applying the style
});
...and then do a comparaison with the event.target (which will be the small div hovered)
if (event.target.matches('.smallBox')) {
event.target.classList.add('permahover');
}
var n=16; //take grid column value as you want
const bigContainer = document.querySelector('.bigContainer')
for(var i = 1; i < n; i++) {
bigContainer.innerHTML+='<div class="row">';
for(j = 0; j < n; j++) {
bigContainer.innerHTML+='<div class="smallBox">';
}
}
const smallBox = document.querySelector('.smallBox');
bigContainer.addEventListener('mouseover', () => {
if (event.target.matches('.smallBox')) {
event.target.classList.add('permahover');
}
});
.smallBox {
border: 1px solid black;
width: 20px;
height: 20px;
display: inline-block;
}
.permahover {
background: red;
}
h1 {
text-align: center;
}
.bigContainer {
text-align: center;
}
<h1>Etch-a-Sketch Assignment - The Odin Project</h1>
<div class="bigContainer">
</div>
You can use forEach method to loop through all boxes and add eventListener on each one.
If all of them have .smallBox class you can do it like this:
const smallBoxes = document.querySelectorAll('.smallBox');
smallBoxes.forEach(box => box.addEventListener('mouseover', () => {
smallBox.classList.add('permahover');
}))
I hope it helped you!
let smallBoxes = document.querySelectorAll('.smallBox');
[...smallBoxes].forEach(el => {
el.addEventListener('mouseover', e => e.target.classList.add('permahover'));
});
you should set the eventlistener to your DOM and ask if the trigger element are one of your elements which are that specific class. So you can handle every element with that class.
var n = 16; //take grid column value as you want
const bigContainer = document.querySelector('.bigContainer')
for (var i = 1; i < n; i++) {
bigContainer.innerHTML += '<div class="row">';
for (j = 0; j < n; j++) {
bigContainer.innerHTML += '<div class="smallBox">';
}
}
document.addEventListener('mouseover', function(e) {
if (e.target && e.target.className == 'smallBox') {
var target = e.target;
target.classList.add('permahover');
}
});
Working js fiddle: https://jsfiddle.net/nwukf205/
hope i could help you :)
if you got questions just ask
Have you tried the :hover selector? Not sure if you want specify any dynamic actions here, but it's easy to do basic stuff.
https://www.w3schools.com/cssref/sel_hover.asp
a:hover {
background-color: yellow;
}
I haven't tried your example myself but something similar to this has been answered here:
Hover on element and highlight all elements with the same class

How can i remove an event listener from multiple elements in JavaScript?

I'm working on a card game where the user has to select a card from a set of 4. If it is an Ace then they win if not then they lose. But I'm having some trouble removing the event listener of click from the set of cards after the first card has been clicked.
for(var i = 0; i < card.length; i++)
{
card[i].addEventListener("click",display);
}
function display()
{
this.setAttribute("src","CardImages/" + deck[this.id] + ".jpg");
this.setAttribute("class","highlight");
if(firstGo == 0)
{
firstGo++;
firstCard = this;
this.removeEventListener("click",display);
console.log("card" + deck[this.id]);
}
else
{
alert("You've already selected a card");
this.removeEventListener("click",display);
}
}
You are adding click events using a loop because you have multiple cards.
for(var i = 0; i < card.length; i++) {
card[i].addEventListener("click", display);
}
but you're removing the event listeners using
this.removeEventListener("click",display);
which will only remove the listener on the card you clicked. If you want to remove the listener on other cards too, you should also remove them in a loop.
function display() {
this.setAttribute("src","CardImages/" + deck[this.id] + ".jpg");
this.setAttribute("class","highlight");
if (firstGo == 0) {
firstGo++;
firstCard = this;
// this.removeEventListener("click",display);
for (var i = 0; i < card.length; i++) {
card[i].removeEventListener("click", display);
}
console.log("card" + deck[this.id]);
} else {
alert("You've already selected a card");
// this.removeEventListener("click",display);
for (var i = 0; i < card.length; i++) {
card[i].removeEventListener("click", display);
}
}
}
Here's a working demo.
var cards = document.getElementsByClassName("card");
for (var i = 0; i < cards.length; i++) {
cards[i].addEventListener("click", display);
}
function display() {
this.classList.add("highlight");
for (var i = 0; i < cards.length; i++) {
cards[i].removeEventListener("click", display);
}
}
.card {
float: left;
padding: 50px 40px;
border: 1px solid rgba(0,0,0,.2);
margin: 5px;
background: white;
}
.card:hover {
border: 1px solid rgba(0,0,255,.4);
}
.card.highlight {
border: 1px solid rgba(0,200,0,.5);
}
<div class="card">1</div>
<div class="card">2</div>
<div class="card">3</div>
<div class="card">4</div>
I'm not sure what your card array looks like, but I filled in the rest on a codepen and it seems to be successfully removing the eventListener. Is your card array referencing specific DOM elements like this for example?
var a = document.getElementById('A');
var b = document.getElementById('B');
var c = document.getElementById('C');
var card = [a, b, c];

javascript change color after each event

I have three elements, the first element clicked needs to change, lets say to red. no matter what element is clicked. The second element clicked needs to turn green then last element clicked needs to turn blue. When these elements are clicked a second time they need to turn back to white.
The first element is not a problem but how do I move on to change the other elements?
css
.container {
background-color: #ffffff;
border: 1px solid blue;
border-radius: 10px;
width: 100px;
height: 50px;
}
.red { background-color: #ff0000; }
.green { background-color: #00ff00; }
.blue { background-color: #0000ff; }
html
<div class='container' id='1' onclick='changeColor(1);'></div>
<div class='container' id='2' onclick='changeColor(2);'></div>
<div class='container' id='3' onclick='changeColor(3);'></div>
javascript
function changeColor(whichOne)
{
var element = document.getElementById(whichOne);
if ( whichOne == 1 || whichOne == 2 || whichOne == 3 )
{
element.classList.toggle("red");
}
}//end
The general process for something like this is:
Use an Array to hold the values you want to cycle through and a counter to indicate the position of the next value to use.
When you need to apply a value, pull it from the Array using the counter as the index.
After using a value, increment the counter so it indicates the next value in the Array. If the counter has reached the end of the Array, reset it back to 0.
Here is an example:
var valuesToUse = ['classA', 'classB', 'classC'],
nextIndex = 0;
function applyValue(target) {
var value = valuesToUse[nextIndex];
nextIndex = (nextIndex + 1) % valuesToUse.length;
// use `value` on `target`
}
Here is this idea applied to your problem via either cycling through classnames or through color values in JavaScript.
http://jsfiddle.net/teTTR/1/
var colors = ['#ff0000', '#00ff00', '#0000ff'],
nextColor = 0;
var classes = ['red', 'green', 'blue'],
nextClass = 0;
var elms = document.querySelectorAll('.color-changer'),
len = elms.length,
i = 0;
for (; i < len; i++) {
elms[i].addEventListener('click', changeColor);
}
elms = document.querySelectorAll('.class-changer');
len = elms.length;
i = 0;
for (; i < len; i++) {
elms[i].addEventListener('click', changeClass);
}
function changeClass(event) {
var elm = event.currentTarget,
currentClass = hasClass(elm, classes);
if (currentClass) {
elm.classList.remove(currentClass);
} else {
elm.classList.add(classes[nextClass]);
nextClass = (nextClass + 1) % classes.length;
}
}
function changeColor(event) {
var element = event.currentTarget;
if (element.style.backgroundColor) {
element.style.backgroundColor = '';
} else {
element.style.backgroundColor = colors[nextColor];
nextColor = (nextColor + 1) % colors.length;
}
}
function hasClass(elm, classes) {
var len,
i;
if (isArray(classes)) {
len = classes.length;
i = 0;
for (; i < len; i++) {
if (elm.classList.contains(classes[i])) {
return classes[i];
}
}
return false;
}
return elm.classList.contains(classes) ? classes : false;
}
function isArray(item) {
return Object.prototype.toString.call(item) === '[object Array]';
}

Javascript for-loop to control button click states

I'm new to JavaScript but moving over from ActionScript, so I'm using a lot of AS3 logic and not sure what's possible and not.
I have a series of 5 dots for an image slider nav. The dots are just CSS styled dots, so I'm trying to make it so I can control the colors using element.style.backgroundColor.
Here's my script:
function btnFeatured(thisBtn) {
btnFeatured_reset();
for (i = 1; i <= 5; i++) {
if (thisBtn === document.getElementById("dotFeat" + i)) {
document.getElementById("dotFeat" + i).style.backgroundColor = "#ffae00";
}
}
}
function btnFeatured_reset() {
for (i = 1; i <= 5; i++) {
document.getElementById("dotFeat" + i).style.backgroundColor = "#969696";
}
}
Seems to work just fine, but when I click the dot, it turns orange (ffae00) and then immediately turns back to gray (969696).
And just in case, here's the style I'm using for the dots:
#featured-nav a {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 8px;
background-color: #969696;
border-bottom: none;
margin: 0 14px;
}
#featured-nav a:hover {
background-color: #ffae00;
border-bottom: none;
}
And my html:
Change the HTML to
test
test
test
test
test
and the JS:
function btnFeatured(thisBtn) {
for (i = 1; i <= 5; i++) {
var state = parseInt(thisBtn.id.slice(-1),10) == i,
elem = document.getElementById("dotFeat" + i);
elem.style.backgroundColor = (state ? "#ffae00" : "#969696");
}
return false;
}
FIDDLE
Even better would be to not use inline JS, but proper event handlers.

How to add multiple divs with appendChild?

I am trying to make a chessboard using javascript and creating 64 divs with it.
The problem is, that it creates only the first div.
Here is the code:
div {
width: 50px;
height: 50px;
display: block;
position: relative;
float: left;
}
<script type="text/javascript">
window.onload=function()
{
var i=0;
var j=0;
var d=document.createElement("div");
for (i=1; i<=8; i++)
{
for (j=1; j<=8; j++)
{
if ((i%2!=0 && j%2==0)||(i%2==0 && j%2!=0))
{
document.body.appendChild(d);
d.className="black";
}
else
{
document.body.appendChild(d);
d.className="white";
}
}
}
}
</script>
As t-j-crowder has noted, the OP's code only creates one div. But, for googlers, there is one way to append multiple elements with a single appendChild in the DOM: by creating a documentFragment.
function createDiv(text) {
var div = document.createElement("div");
div.appendChild(document.createTextNode(text));
return div;
}
var divs = [
createDiv("foo"),
createDiv("bar"),
createDiv("baz")
];
var docFrag = document.createDocumentFragment();
for(var i = 0; i < divs.length; i++) {
docFrag.appendChild(divs[i]); // Note that this does NOT go to the DOM
}
document.body.appendChild(docFrag); // Appends all divs at once
The problem is, that it creates only the first div.
Right, because you've only created one div. If you want to create more than one, you must call createElement more than once. Move your
d=document.createElement("div");
line into the j loop.
If you call appendChild passing in an element that's already in the DOM, it's moved, not copied.
window.onload=function()
{
var i=0;
var j=0;
for (i=1; i<=8; i++)
{
for (j=1; j<=8; j++)
{
if ((i%2!=0 && j%2==0)||(i%2==0 && j%2!=0))
{
var d=document.createElement("div");
document.body.appendChild(d);
d.className="black";
}
else
{
var d=document.createElement("div");
document.body.appendChild(d);
d.className="white";
}
}
}
}
Although what T.J. Crowder writes works fine, I would recommend rewriting it to the code below, using a documentFragment, like Renato Zannon suggested. That way you will only write to the DOM once.
window.onload = function() {
var count = 5,
div,
board = document.getElementById('board'),
fragment = document.createDocumentFragment();
// rows
for (var i = 0; i < count; ++i) {
// columns
for (var j = 0; j < count; ++j) {
div = document.createElement('div');
div.className = (i % 2 != 0 && j % 2 == 0) || (i % 2 == 0 && j % 2 != 0) ? 'black' : 'white';
fragment.appendChild(div);
}
}
board.appendChild(fragment);
};
#board {
background-color: #ccc;
height: 510px;
padding: 1px;
width: 510px;
}
.black,
.white {
float: left;
height: 100px;
margin: 1px;
width: 100px;
}
.black {
background-color: #333;
}
.white {
background-color: #efefef;
}
<div id="board"></div>
function crt_dv(){
dv=document.createElement('div'),document.body.appendChild(dv)
};
crt_dv(),dv.className='white';crt_dv(),dv.className='black';
Also use: for(i=0;i<2;i++)

Categories

Resources