Toggle many classes - javascript

so thanks for you all that help me. You made me think what i was doing, so i short my code in what i thought would be good, but before the code was long but working now, short but not works with the first panel even that there is no errors.So maybe a loop?
So the html goes like this:
<div class="container">
<div class="down sound">
<img id="batman" class="image-1-panel active" src="flash.svg">
<img class="image-2-panel notactive" src="http://cdn.playbuzz.com/cdn/3c096341-2a6c-4ae6-bb76-3973445cfbcf/6b938520-4962-403a-9ce3-7bf298918cad.jpg">
<p class="image-3-panel notactive">Bane</p>
<p>Joker</p>
<p>Alfred</p>
</div>
And then it repeats for 3 more containers like that one. On css for the active and not active i have:
.notactive{
visibility: hidden;
position: relative;
}
.active{
position: absolute;
}
And in js:
document.querySelector('#batman').addEventListener('click', batman);
function batman(){
document.querySelector('.image-1-panel').classList.toggle('.notactive');
document.querySelector('.image-1-panel').classList.toggle('.active');
document.querySelector('.image-2-panel').classList.toggle('.active');
document.querySelector('.image-2-panel').classList.toggle('.notactive');
}
But nothing happens... I also have a for loop for sounds that is working still good, but the panels don´t move. Can someone share some light here? I thought in a loop and try it following the function but also didn´t work so clearly I am missing something, maybe wrong element I am trying to catch?
Above i have my question without the edit where you can understand what i mean.
Thanks for your help
I have 4 panels. In each panel, there will be 3 images/text one beyond the other. So it will work that when I press panel 1, panel 2,3 and 4 will turn to show an image. Press panel 2, and panel 1,3 and 4 will turn to show different image then when pressed the panel one. And so one for the rest of the panels.
So i start and i create a function for the first panel. It works, but there is any way i can make it simple? Here is the code:
function guessWho(){
document.querySelector('.image-1-panel').classList.toggle('notactive');
document.querySelector('.image-1-panel').classList.toggle('active');
document.querySelector('.image-2-panel').classList.toggle('active');
document.querySelector('.image-2-panel').classList.toggle('notactive');
document.querySelector('.image-3-panel').classList.toggle('active');
document.querySelector('.image-3-panel').classList.toggle('notactive');
document.querySelector('.image-4-panel').classList.toggle('active');
document.querySelector('.image-4-panel').classList.toggle('notactive1');
document.querySelector('.image-5-panel').classList.toggle('active');
document.querySelector('.image-5-panel').classList.toggle('notactive');
document.querySelector('.image-6-panel').classList.toggle('active');
document.querySelector('.image-6-panel').classList.toggle('notactive2');
document.querySelector('.image-7-panel').classList.toggle('active');
document.querySelector('.image-7-panel').classList.toggle('notactive');
document.querySelector('.image-8-panel').classList.toggle('active');
document.querySelector('.image-8-panel').classList.toggle('notactive3');
}
Any way I can put this simple? I don´t want to use any framework, so it has to be pure js. Thank you

Use a loop.
for (var i = 0; i <= 8; i++) {
document.querySelector(`.image-${i}-panel`).classList.toggle('notactive');
document.querySelector(`.image-${i}-panel`).classList.toggle('active');
}

Given that querySelector takes a string, you could build that string up inside of a loop and use the iterator inside of the string. Something like
for(i = 1; i <= 8; i++) {
document.querySelector('.image-' + i + '-panel').classList.toggle('notactive');
}
would toggle the 'notactive' class for everything that has image-x-panel.

Solution 1
for (var i = 1; i <= 8; i++) {
document.querySelector(`.image-${i}-panel`).classList.toggle('notactive');
document.querySelector(`.image-${i}-panel`).classList.toggle('active');
}
Solution 2
Add some CSS class to your HTML like CLASSNAME
And use try this
document.querySelector(`CLASSNAME`).classList.toggle('notactive');
document.querySelector(`CLASSNAME`).classList.toggle('active');

You can create a list of the new states you want your classes to be modified by. In this case, you have an object that looks like this:
{a:false,i:""}
where a is whether it's active, and i is your post class increment, like "notactive2".
Then you create a nested loop where you iterate over each number twice, and check the position in your list of states to determine what things will be added to your base "active" class.
var activeClassesList = [{a:false,i:""},{a:true,i:""},{a:true,i:""},{a:false,i:""},{a:true,i:""},{a:false,i:""},{a:true,i:""},{a:false,i:"1"},{a:true,i:""},{a:false,i:""},{a:true,i:""},{a:false,i:"2"},{a:true,i:""},{a:false,i:""},{a:true,i:""},{a:false,i:"3"}];
var imageClassPrefix = ".image-";
var imageClassPostfix = "-panel";
var start =1;
var base = 8;
for (var i=0;i<2;i++)
{
var count = 1;
for (var j=start;j<(start+base);j++) {
var modifier = activeClassesList[j-1];
var cls = (modifier.a ? "" : "not") + "active" + modifier.i;
document.querySelector(imageClassPrefix + String(count) + imageClassPostfix).classList.toggle(cls);
count++;
}
start = start + base;
}

Another approach might be to just focus on the results, so we can remove the first selectors for each image panel since they are immediately being overwritten.
That leaves the second queries for each selector. This can be simplified by combining multiple selectors inside a queryselectorAll for items with the same class being toggled.
function guessWho(){
document.querySelector('.image-1-panel').classList.toggle('active');
document.querySelector('.image-2-panel').classList.toggle('notactive');
document.querySelectorAll('.image-3-panel, .image-5-panel, .image-7-panel, .image-7-panel').forEach((f)=>{f.classList.toggle('notactive');})
document.querySelector('.image-4-panel').classList.toggle('notactive1');
document.querySelector('.image-6-panel').classList.toggle('notactive2');
}
guessWho();
.allpanels > div {display: inline-block;border:solid 1px black;height:50px;width:50px;margin:8px;}
.active { background-color:red;}
.notactive { background-color:lightsteelblue;}
.notactive1 { background-color:green;}
.notactive2 { background-color:orange;}
<div class="allpanels">
<div class="image-1-panel">1</div>
<div class="image-2-panel">2</div>
<div class="image-3-panel">3</div>
<div class="image-4-panel">4</div>
<div class="image-5-panel">5</div>
<div class="image-6-panel">6</div>
<div class="image-7-panel">7</div>
</div>

Related

Create for loop with changing image

I haven't been able to find my specific case on here yet so I thought I'd ask. I'm trying to make a very simple Tamagotchi in Javascript for a school project. Musts are that I apply DOM manipulation, use a loop, use an array(or an object), and use a function.
My idea was to make an array with all the 'emotions' as images and then a for loop to slowly count them down. Giving the impression that the mood of the Tamagotchi gets worse as time passes.
This is the code I have so far, it's not a lot:
var imgArray = ["super.png", "blij.png", "neutraal.png", "meh.png", "verdrietig.png", "dood.png"] //Array with the images
for (var i = 0; i < imgArray.length; i++)
{
//for loop that counts down array
//Here I want a function that changes the image according to the array number
}
Sorry for the bad formatting, this is my first time on here :)
This is what I have in the body:
<h1>Tamagotchi</h1>
<button id="feed">Give food</button>
<button id="play">Entertain</button>
<button id="walk">Walk</button>
<div id="tamagotchi"></div>
I'd also then like the buttons that you see above to add points to make the Tamagotchi feel better (so in the for loop the array automatically keeps ++i but I'd like the button to --i, so subtract one point) imgArray[0] is the happiest and imageArray[5] is the saddest.
I hope this wasn't too vague, please let me know if I need to better explain anything!
Here is some draft so you can start from something. I've created a function allowing you to improve the state of the tamagoshi.
For you now :
Making a function to decrease it
Make them to be displayed as image and not strings
Make it prettier using css rules
If you get trouble with the code, Stack Overflow will help. SO is not made to write code from scratch, but to fix bug and coding issues.
// Array with the images
const imgArray = [
'super.png',
'blij.png',
'neutraal.png',
'meh.png',
'verdrietig.png',
'dood.png',
];
let tamagoshiState;
// Pointer to the div where you are going to insert the picture
const tamagoshiFeel = document.getElementById('tamagotchi');
// Function that can change the state of the tamagoshi
function setTamagoshiState(i) {
tamagoshiState = i;
tamagoshiFeel.innerHTML = imgArray[i];
}
// Change the tamagoshi state in a positive way
function improveTamagoshiState() {
// Tamagoshi can't be happier
if (tamagoshiState === 0) {
return;
}
setTamagoshiState(tamagoshiState - 1);
}
// Initialize the tamagoshi state at very sad
setTamagoshiState(imgArray.length - 1);
#tamagotchi {
margin-top: 2em;
}
<h1>Tamagotchi</h1>
<button id="feed" onclick="improveTamagoshiState()">Give food</button>
<button id="play" onclick="improveTamagoshiState()">Entertain</button>
<button id="walk" onclick="improveTamagoshiState()">Walk</button>
<!-- How the tamagochi feels -->
<div id="tamagotchi">
</div>

How to create a search that filters by class name

I currently have a website that has multiple items. They are all individual div items. They all have one class name in common with several other class names as tags to help separate them (some tags are common among multiple div items)
I already have buttons set up that use data-filter=".exampleclass" and data-filter=".exampleclass2" etc. which work perfectly for sorting based on the class names. I am now trying to make a search bar where a user could type in the class name so I don't have to make buttons for them all.
document.getElementById("boxsearch").oninput = function() {
var matcher = new RegExp(document.getElementById("boxsearch").value, "gi");
for (var i = 0; i < document.getElementsByClassName("portfolio-item").length; i++) {
if (matcher.test(document.getElementsByClassName("category")[i])) {
document.getElementsByClassName("portfolio-item")[i].style.display = "inline-block";
} else {
document.getElementsByClassName("portfolio-item")[i].style.display = "none";
}
}
}
http://jsfiddle.net/kc2ot8ua/
I dont have the jquery file included so the buttons dont work (they work on my end) I just dont know how to use the search bar to search the class names.
This is the closest I could find to what I am trying to achieve: http://jsfiddle.net/mttgj1tt/5/
Filtering elements based on regular expression matches with one of their class names is an inefficient way to filter elements. Typically you'd build an index and use that with a more optimised search algorithm.
You might use one class to select the target set of elements, then loop over them and get their classList, then loop over those looking for matches, there's an example below. But this will also test other class names that have nothing to do with filtering or sorting (e.g. in the example below, "box" is used for display only, but elements are filtered by it anyway).
A better idea might be to add the filter and sorting values as a data- attribute, then they can be isolated from other side effects. I'd also suggest building an index of subject elements so you can find the ones you want first, then hide them.
Multiple getElementByClassName calls are expensive and unnecessary (particularly in a for loop). The example does one call per keyup.
function filterOnClass(baseClass, s) {
let re = new RegExp(s.trim(), 'i');
document.querySelectorAll('.' + baseClass).forEach(node => {
let cNames = Array.from(node.classList);
// Show all if search string is blank
if (s.trim() == '') {
node.classList.remove('hide');
// Otherwise, filter
} else if (cNames.some(cName => re.test(cName))) {
node.classList.add('hide');
} else {
node.classList.remove('hide');
}
});
}
.box {
height: 50px;
width: 100px;
border: 1px solid blue;
}
.hide {
display: none;
}
<input id="searchInput" onkeyup="filterOnClass('box', this.value)"><br>
<div class="box foo">foo</div>
<div class="box foo bar">foo bar</div>
<div class="box fum bar">fum bar</div>
<div class="box fum">fum</div>

Can bPopup trigger and target divs be assigned dynamically?

We have a list of paired divs dynamically built in a ColdFusion page (internshipHandleX, internshipHiddenX, etc.) by looping over a query and adding the current row, eg:
<div id="internshipHidden#internship.currentrow#" class="hidden pop-up">
that we want to bind together as modal windows and corresponding triggers. Using this code:
for (var row = 1; row <= totalInternships; row ++){
var thisHandle = "#internshipHandle" + row;
var thisHidden = "#internshipHidden" + row;
$(thisHandle).click(function(e){
e.preventDefault();
$(thisHidden).bPopup({modalColor:"black"});
});
}
We apparently link all of the internshipHandle(s) to the last internshipHidden.
What am I missing? Is there a better way to make modal windows out of dynamically created css-hidden divs (that is, within the skeleton framework? I REALLY don't want to start over using bs.)
DreamWeaver is not happy about me putting functions in loops but all the cool kids tell me not to listen to it.
Edit:tryed the same thing with the jqueryUI dialog and had the same problems. I'd love an explanation. Thanks!
Welcome to Javascript. You just encountered a closure in its natural habitat.
In order to have the row variable working the way you expect it to, you need to pass it in its own scope. This can be done using a closure. You may want to dig deeper into that topic, but for now, here is a fix for your problem:
var totalInternships = 2;
for (var row = 1; row <= totalInternships; row++){
var bindInternship = function(rowIndex) {
$("#internshipHandle" + rowIndex).click(function(e){
e.preventDefault();
alert('pop-up #' + rowIndex);
//$("#internshipHidden" + rowIndex).bPopup({modalColor:"black"});
});
};
bindInternship(row);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<button type="button" id="internshipHandle1">pop-up 1</button>
<div id="internshipHidden1" class="hidden pop-up">
<button type="button" id="internshipHandle2">pop-up 2</button>
<div id="internshipHidden2" class="hidden pop-up">
Note: I commented the line for the bPopup. Uncomment it, remove the alert and you're good to go.

HTML comment if without javascript

I know that I can display/hide content whether the browser is IE or not or even the version of IE. I was wondering if I can use other expressions too such as
<!--[if 1 == 0]-->
This should be hidden
<!--[endif]-->
The reason behind this is that I'm sending auto generated E-Mails and for me it would be easier to insert such comments in the template E-Mail instead of creating multiple templates.
if you have a template system, then make this in your template. Anyway when you render the template you calculate the condition, but instead of printing "0 == 1" or "0 == 0", use the template's ability to print or not to print the following paragraph
I know this would look like a long answer but I just wanted to divide the code into small functions each does its own job -kind of-, first select each element with a class name of hasComment in an array using querySelectorAll then pass this array to updateHTML() function, loop through its element and call returnComment() function for each item in the array.
The returnComment() function first call hasComment() function on the element passed to it, and using .replace() to get the exact string. Function hasComment() loop through the child nodes of the element and if the nodeType of the child node is 8 it then it's a comment, we return the text between the comment <!-- and -->.
This .replace(/\[|\]/ig, ''); omits the brackets to get value of either show or hide which according to it we "hide" or "show" the child .contentDiv div.
JS Fiddle
var commentDivs = document.querySelectorAll('.hasComment');
updateHTML(commentDivs);
function updateHTML(arr) {
for (var i = 0; i < arr.length; i++) {
var childDiv = arr[i].querySelector('.contentDiv'),
showIt = returnComment(arr[i]);
if (showIt == 'show') {
childDiv.style.display = 'block';
console.log('Div-' + (i + 1) + ': shown');
} else if (showIt == 'hide') {
childDiv.style.display = 'none';
console.log('Div-' + (i + 1) + ': hidden');
}
}
}
function returnComment(element) {
var comment = hasComment(element);
comment = comment.replace(/\[|\]/ig, '');
return comment;
}
function hasComment(element) {
for (var i = 0; i < element.childNodes.length; i++) {
if (element.childNodes[i].nodeType == 8) {
return element.childNodes[i].data;
}
}
}
<div class="hasComment">
<!--[hide]-->
<div class="contentDiv">Div -1: This should be hidden</div>
</div>
<hr>
<div class="hasComment">
<!--[hide]-->
<div class="contentDiv">Div -2: Again, This should be hidden</div>
</div>
<hr>
<div class="hasComment">
<!--[show]-->
<div class="contentDiv">Div -3: But this should be shown</div>
</div>
----------
Notes:
Wrapping the all contents of each .hasComment elements making controlling the content easier.
The above solution only work on the very top level of .hasComment element children, so if you have other comments inside .contentDiv these comments won't be affected.Demo Fiddle
You could probably use [if 1==0] for "templating" like in your code then use eval() or more complex regex to check upon it, but IMHO I think using show and hide look easier and mostly less bugs as you this over and over through your document.
More details about nodeType:
https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
https://developer.mozilla.org/fr/docs/Web/API/Node/nodeType
http://www.w3schools.com/xml/dom_nodetype.asp
Since you are developing for email clients, no this isn't possible. You need to figure out how different clients can be targeted. Then set the display property via CSS to whatever is affected.
Ideally, your emails shouldn't need any kind of crazy logic like this. It is a smell that your email is bad. Not to mention, anything you put in the email itself is viewable, all someone needs to do is turn off HTML rendering or view the source.

jQuery: Loop iterating through numbered classes?

I looked at the post jQuery: Loop iterating through numbered selectors? and it didn't solve my problem, and didn't look like it was truly an answer that works.
I have a list of <h3> tags that are titles to questions, and there are answers below in a <p>. I created classes for each Q & A like so:
<h3 class="sec1">Question:</h3><p class="view1">Answer...</p>
<h3 class="sec2">Question:</h3><p class="view2">Answer...</p>
<h3 class="sec3">Question:</h3><p class="view3">Answer...</p>
I used the following jQuery loop to reduce redundacy for my 21 questions.
$(document).ready(function () {
for (var i = 1; i < 21; i++) {
var link = ".sec" + i;
var content = ".view" + i;
$(link).click(function () {
$(content).toggle("fast");
});
}
});
But it isn't working for all Q & A sets, only the last one. i.e.: It works for the first set if I set the max value to 2 (only looping once). Please advise. Thanks
While I agree with #gaffleck that you should change your approach, I think it is worth while to explain how to fix the current approach.
The problem is that the click function does not get a copy of the content variable but instead has a reference to that same variable. At the end of the loop, the value is .view20. When any element is clicked it read that variable and gets back .view20.
The easiest way to solve this is to move the code into a separate function. The content variable within this function is a new variable for every call of the function.
function doIt(i){
var link = ".sec" + i;
var content = ".view" + i;
$(link).click(function () {
alert(content);
});
}
$(document).ready(function () {
for (var i = 1; i < 21; i++) {
doIt(i);
}
});
http://jsfiddle.net/TcaUg/2/
Notice in the fiddle, if you click on a question the alert has the proper number. Optionally, you could make the function inline, though I find the separate function in most cases to be a bit cleaner.
http://jsfiddle.net/TcaUg/1/
A much easier way to do this, would be this:
$(document).ready(function(){
$("h3").click(function(){
$(this).next("p").toggle("fast");
});
});
This is also safer in that you can add/remove questions and answers in the future and you won't have to update the function.
Wrap your questions in a more logical structure to create a proper scope for your questions-block:
<div id="questions">
<div class="question">
<h3 class="sec1">Question:</h3><p class="view1">Answer...</p>
</div>
<div class="question">
<h3 class="sec2">Question:</h3><p class="view2">Answer...</p>
</div>
<div class="question">
<h3 class="sec3">Question:</h3><p class="view3">Answer...</p>
</div>
</div>
Now iterate through it like this:
$(function() {
$('#questions .question h3').click(function(){
$(this).parent().find('.answer').toggle('fast');
});
});

Categories

Resources