jQuery: Loop iterating through numbered classes? - javascript

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');
});
});

Related

Toggle many classes

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>

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.

Getting html code from another action and manipulate the code throught javascript

I have a cshtml view file with the following code:
<ul id="ul1">
<li id="ul1li1"></li>
<li id="ul1li2"></li>
<li id="ul1li3"></li>
</ul>
And an action "x" which returns me html code with which is several divs one after the other.
something like :
<div class="xdiv"></div>
<div class="xdiv"></div>
<div class="xdiv"></div>
<div class="xdiv"></div>
<div class="xdiv"></div>
Id like to use javascript function in order to put the divs I get from the action "X", into the lis in a circular ascending order.
I couldn't find the right solution, and so far my javascript function is something like this :
function fillLis()
{
var i = 0;
divs = #Url.Action("X");
for(ind in divs){
div = divs[ind];
i = i%3 + 1;
var currentli = document.getElementById('ul1li'+i);
currentli.innterHTML += div.innerHTML;
}
};
Because of some bad html coding, I have to do it that way.
This isn't doing what you think:
divs = #Url.Action("X");
It's just going to resolve to a string (and a syntax error), such as:
divs = /Home/X;
What you want to do is make an AJAX request to that action. (Note: Since you're using ASP.NET, I'm assuming that jQuery is an option.) Something like this:
$.get('#Url.Action("X")', function (data) {
// update your UI
});
In that callback function, data will contain the response from the server. Which, in this case, should be the HTML. At this point it looks like you want to loop over the div elements. So you can probably wrap it in a jQuery object and loop over that. Something like this:
$.get('#Url.Action("X")', function (data) {
var divs = $('div', data);
// at this point "divs" is a list of div HTML elements in the response
});
Your existing code should work on the divs array, perhaps with some minor adjustments through debugging. But essentially that's how you'd get the HTML elements you're looking for from the server in order to use those elements in your client-side code.
Something like that?
Array.prototype.forEach.call(#Url.Action("X"), function(xdiv, i) {
document.getElementById("ul1li"+i).appendChild(xdiv);
});
If not then please update your question with more detailed informations about what you're trying to do.
Some quick notes about your code:
function fillLis()
{
var i = 0;
divs = #Url.Action("X");
for(ind in divs){
div = divs[ind];
// This will always be equal to 1
i = i%3 + 1;
// You say you want to "put the divs [...] into the list", but it seems like you're trying to transfer only the content of your divs
var currentli = document.getElementById('ul1li'+i);
currentli.innterHTML += div.innerHTML;
}
};

Searching items causes lags

I have following problem. Let's say I have DOM like this.
<div class="results">
<div class="result">
<div class="title">Aaa</div>
</div>
<div class="result filtered-out">
<div class="title">Aab</div>
</div>
<div class="result">
<div class="title">Aac</div>
</div>
<div class="result">
<div class="title">Aad</div>
</div>
<div class="result">
<div class="title">Aae</div>
</div>
</div>
and an input field like this
<input type="text" id="search">
And now I try to filter the results with a simple function defined by this
var searchBox = $(this);
searchBox.keyup(function(){
var searchBox = $(this);
var items = $(".results .result:not(.filtered-out)");
items.each(function(){
var title = $(this).find(".title").html();
if(title.toLowerCase().indexOf(searchBox.val().toLowerCase())!== -1)
$(this).show();
else
$(this).hide();
});
});
So the problem is that the list of results is quite long something between 100 and 200 elements and whenever I type something into the search input the code executes very long. Maybe around 2-3 seconds. Is there any other approach to solve this "lag"? Thank you for any advices!
EDIT Maybe something like delayed script execution or asynchronous script execution (like in ajax)?
It's generally not a good idea to use the DOM as a datasource, it's not meant for it and is therefore slow. Personally I would recommend using a small MVVM library or something similar so you don't have to manually manage the DOM yourself.
I've used Vue.js below, but you could just as well use any similar solution. Keeping your data in the code will allow you to operate on it a lot faster since you don't have to re-request it all the time and you avoid doing a lot of work for modifications. All operations below are done on 1000 objects:
var items = [];
for (var i = 0; i < 1000; i++) {
items.push({
title: 'Item #' + i
});
}
var v = new Vue({
el: '#list',
data: {
items: items,
input: ""
},
computed: {
filteredItems: function() {
var value = ("" || this.input).trim().toLowerCase();
if (!value.length) return this.items;
return this.items.filter(function(item) {
return item.title.toLowerCase().indexOf(value) !== -1;
});
}
}
});
ol {
list-style: none;
padding: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/0.12.16/vue.min.js"></script>
<div id="list">
<input placeholder="Search" v-model="input" />
<ol>
<li v-repeat="filteredItems">{{title}}</li>
</ol>
</div>
When searching the dom through many elements it is recommended to use javascript as opposed to jQuery if speed is what you are after. jQuery has it's purpose but for large amounts of dom searching using javascripts getElementById or querySelector / querySelectorAll is going to be much much faster. If you check this jsPerf example you can see that the jQuery selector operates roughly 94% slower than the comparable getElementById.
You should try using some logging to figure out which part is taking the longest. If you find that it's the items selector (with the psuedo-not), you could try to optimize that, however I don't see anything about the filtered-out class so I'm not sure exactly what that does.
Here's some simple optimizations though:
var searchBox = $(this);
searchBox.keyup(function(){
var $searchBox = $(this);
var searchBoxVal = $searchBox.val().toLowerCase();
var items = $(".results .result:not(.filtered-out)");
items.each(function(){
var $item = $(this);
var title = $item.find(".title").html();
if (title.toLowerCase().indexOf(searchBoxVal) !== -1)
$item.show();
else
$item.hide();
});
});
My guess the lag is because you are performing the search based on the DOM elements, and at the same time manipulating them with hiding/ showing.
I suppose the DOM is populated from some data source? If so it'll be better to perform the search/ filter from that data source, then use the filtered data set to populate the DOM again. (And even if you don't have the data source at first, you can build one by reading the original DOM)

how to repeat same Javascript code over multiple html elements

Note: Changed code so that images and texts are links.
Basically, I have 3 pictures all with the same class, different ID. I have a javascript code which I want to apply to all three pictures, except, the code needs to be SLIGHTLY different depending on the picture. Here is the html:
<div class=column1of4>
<img src="images/actual.jpg" id="first">
<div id="firsttext" class="spanlink"><p>lots of text</p></div>
</div>
<div class=column1of4>
<img src="images/fake.jpg" id="second">
<div id="moretext" class="spanlink"><p>more text</p></div>
</div>
<div class=column1of4>
<img src="images/real.jpg" id="eighth">
<div id="evenmoretext" class="spanlink"><p>even more text</p></div>
</div>
Here is the Javascript for the id="firsttext":
$('#firstextt').hide();
$('#first, #firsttext').hover(function(){
//in
$('#firsttext').show();
},function(){
//out
$('#firsttext').hide();
});
So when a user hovers over #first, #firsttext will appear. Then, I want it so that when a user hovers over #second, #moretext should appear, etc.
I've done programming in Python, I created a sudo code and basically it is this.
text = [#firsttext, #moretext, #evenmoretext]
picture = [#first, #second, #eighth]
for number in range.len(text) //over here, basically find out how many elements are in text
$('text[number]').hide();
$('text[number], picture[number]').hover(function(){
//in
$('text[number]').show();
},function(){
//out
$('text[number]').hide();
});
The syntax is probably way off, but that's just the sudo code. Can anyone help me make the actual Javascript code for it?
try this
$(".column1of4").hover(function(){
$(".spanlink").hide();
$(this).find(".spanlink").show();
});
Why not
$('.spanlink').hide();
$('.column1of4').hover(
function() {
// in
$(this).children('.spanlink').show();
},
function() {
// out
$(this).children('.spanlink').hide();
}
);
It doesn't even need the ids.
You can do it :
$('.column1of4').click(function(){
$(this); // the current object
$(this).children('img'); // img in the current object
});
or a loop :
$('.column1of4').each(function(){
...
});
Dont use Id as $('#id') for multiple events, use a .class or an [attribute] do this.
If you're using jQuery, this is quite easy to accomplish:
$('.column1of4 .spanlink').hide();
$('.column1of4 img').mouseenter(function(e){
e.stopPropagation();
$(this).parent().find('.spanlink').show();
});
$('.column1of4 img').mouseleave(function(e){
e.stopPropagation();
$(this).parent().find('.spanlink').hide();
});
Depending on your markup structure, you could use DOM traversing functions like .filter(), .find(), .next() to get to your selected node.
$(".column1of4").hover(function(){
$(".spanlink").hide();
$(this).find(".spanlink, img").show();
});
So, the way you would do this, given your html would look like:
$('.column1of4').on('mouseenter mouseleave', 'img, .spanlink', function(ev) {
$(ev.delegateTarget).find('.spanlink').toggle(ev.type === 'mouseenter');
}).find('.spanlink').hide();
But building on what you have:
var text = ['#firsttext', '#moretext', '#evenmoretext'];
var picture = ['#first', '#second', '#third'];
This is a traditional loop using a closure (it's better to define the function outside of the loop, but I'm going to leave it there for this):
// You could also do var length = text.length and replace the "3"
for ( var i = 0; i < 3; ++i ) {
// create a closure so that i isn't incremented when the event happens.
(function(i) {
$(text[i]).hide();
$([text[i], picture[i]].join(',')).hover(function() {
$(text[i]).show();
}, function() {
$(text[i]).hide();
});
})(i);
}
And the following is using $.each to iterate over the group.
$.each(text, function(i) {
$(text[i]).hide();
$([text[i], picture[i]].join(', ')).hover(function() {
$(text[i]).show();
}, function() {
$(text[i]).hide();
});
});
Here's a fiddle with all three versions. Just uncomment the one you want to test and give it a go.
I moved the image inside the div and used this code, a working example:
$('.column1of4').each(function(){
$('div', $(this)).each(function(){
$(this).hover(
function(){
//in
$('img', $(this)).show();
},
function(){
//out
$('img', $(this)).hide();
});
});
});
The general idea is 1) use a selector that isn't an ID so I can iterate over several elements without worrying if future elements will be added later 2) locate the div to hide/show based on location relational to $(this) (will only work if you repeat this structure in your markup) 3) move the image tag inside the div (if you don't, then the hover gets a little spazzy because the positioned is changed when the image is shown, therefore affecting whether the cursor is inside the div or not.
EDIT
Updated fiddle for additional requirements (see comments).

Categories

Resources