addClass to multiple distinct elements with jquery - javascript

I have some code that generates markup similar to this:
<div id="container">
<div data-number="123">Fred</div>
<div data-number="128">Wilma</div>
<div data-number="129">Barney</div>
<div data-number="123">Fred</div>
<div data-number="123">Fred</div>
<div data-number="129">Barney</div>
<div data-number="111">Dino</div>
<div data-number="008">Betty</div>
<div data-number="123">Fred</div>
<div data-number="123">Fred</div>
</div>
The container will have many duplicates.
The data tags are generated dynamically based on potentially thousands of data-ids. But, a particular view will likely not have more than a dozen or so unique items, so iterating over them shouldn't be a big deal.
What I want to do is add css classes to similar items in a predictable way.
<div class="unique-item-1" data-number="123">Fred</div>
<div class="unique-item-2" data-number="008">Betty</div>
<div class="unique-item-3" data-number="128">Wilma</div>
<div class="unique-item-4" data-number="129">Barney</div>
<div class="unique-item-5" data-number="111">Dino</div>
<div class="unique-item-1" data-number="123">Fred</div>
<div class="unique-item-1" data-number="123">Fred</div>
You'll notice that all the Fred divs (data-number 123) get the same class (unique-item-1) added.
The goal is to have CSS that will add colors or whatever, like so:
.unique-item-1 {color:red;}
.unique-item-2 {color:pink;}
.unique-item-3 {color:green;}
.unique-item-4 {color:black;}
.unique-item-5 {color:wheat;}
I've tried using jquery's "unique"... but I guess I don't know what unique means :)

Use data() attribute in jquery ,
var getArray = [];
$("#container").children("[data-number]").filter(function () {
var index = $.inArray($(this).data("number"), getArray);
if (index == -1) {
getArray.push($(this).data("number"));
$(this).addClass("unique-item-" + getArray.length);
} else {
$(this).addClass("unique-item-" + parseInt(index+1));
}
});
UPDATED DEMO

Related

How can I get the path to a specific DOM element iterating a HTML collection?

This is the issue: I want to have nested divs with paragraphs inside with different texts.
I want to be able to get the paragraph that contains certain word, for example "mate" I did the below HTML structure trying to obtain an HTML collection and iterate it, and then using javascript, try to use the includes method to get the paragraph than contains that word, and finally, try to find a way to get the full path from the uppermost div to this p.
<div class="grandpa">
<div class="parent1">
<div class="son1">
<p>I like oranges</p>
</div>
<div class="son2">
<p>yeeeey</p>
<p>wohoo it's saturday</p>
</div>
<div class="son3"></div>
</div>
<div class="parent2"></div>
<div class="parent3">
<div class="son1">
<p>your team mate has been killed!</p>
<p>I should stop playing COD</p>
</div>
<div class="son2"></div>
</div>
</div>
I actually don't know how to achieve it, but at least I wanted to get an HTML collection to iterate, but I'm not being able to get it.... When I use this:
const nodes = document.querySelector('.grandpa');
console.log(typeof nodes);
I don't get an HTML collection, instead if I console.log typeof nodes variable it says it is an object..
How can I iterate this DOM tree, capture the element that contais the word "mate", and obtain (this is what I really want to achieve) the path to it?
Thanks!
You can loop through every element, remove all children elements, then check whether the textContent includes the string you are looking for:
const allElements = document.body.querySelectorAll('*');
const lookFor = "mate";
var elem;
for (let i = 0; i < allElements.length; i++) {
const cur = allElements[i].cloneNode(true); //doesn't mess up the original element when removing children
while (cur.lastElementChild) {
cur.removeChild(cur.lastElementChild);
}
if (cur.textContent.includes(lookFor)) {
elem = cur;
break;
}
}
console.log(elem);
<div class="grandpa">
<div class="parent1">
<div class="son1">
<p>I like oranges</p>
</div>
<div class="son2">
<p>yeeeey</p>
<p>wohoo it's saturday</p>
</div>
<div class="son3"></div>
</div>
<div class="parent2"></div>
<div class="parent3">
<div class="son1">
<p>your team mate has been killed!</p>
<p>I should stop playing COD</p>
</div>
<div class="son2"></div>
</div>
</div>

How to retrieve all jQuery elements with an attribute that starts with a value? [duplicate]

This question already has answers here:
Find HTML based on partial attribute
(2 answers)
Closed 1 year ago.
<div data-start-one="one"></div>
<div data-start-one="one"></div>
<div data-start-two="two"></div>
<div data-start-three="three"></div>
How can I retrieve all elements that have a jQuery attribute that starts with data-start?
For example: $("div[data-start-*]")
There's no good way to do this. You'll have to iterate over all elements that might match what you want, then filter them out.
const starts = $('div').filter(function() {
return [...this.attributes].some(
attrib => attrib.name.startsWith('data-start')
);
});
console.log(starts.length);
console.log(starts[3]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div data-start-1="one"></div>
<div data-start-1="one"></div>
<div data-start-2="two"></div>
<div data-start-3="three"></div>
<div class="somethingElse"></div>
A much better approach would be to be able to select these elements using something else in common, like a class or another data attribute or a descendant of a container.
const starts = $('.container div');
console.log(starts.length);
console.log(starts[3]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
<div data-start-1="one"></div>
<div data-start-1="one"></div>
<div data-start-2="two"></div>
<div data-start-3="three"></div>
</div>
<div class="somethingElse"></div>

Container structure issue with JQuery - Containing containers

I'm finishing up a memory game for school and I'd really like the cards to flip with a CSS animation, which on it's own is pretty straight forward. However I'm pretty new to JavaScript and JQuery which is leading to some trouble with achieving the proper container structure I need to make the cards flip when they are clicked.
Presently the game pieces generate within the board as follows:
const generate=(cards)=>{
cards.forEach(function(card, i) {
$(".gameBoard")
.append($("<div>").addClass("front")//
.append($("<div>").addClass("back").append($("
<img>").attr("src", cards[i]))));
});
};
OR:
<div class="gameBoard>
<div class="front"></div>
<div class="back"><img src="cards"></div>
</div>
But in order for the animation to function properly both the front and back divs need to exist in the same container like this:
<div class="gameBoard>
<div class="flip">
<div class="front></div>
<div class="back"><img src="cards></div>
</div>
</div>
How can I add the div I need (.flip) but have it contain the front and back divs, not just append on to the other divs being generated within the .gameboard container.
Thanks.
It's much simpler to create your DOM using template literals rather than jQuery methods. That way you just describe the HTML as you're accustomed to.
const generate=(cards)=>{
cards.forEach(function(card, i) {
$(".gameBoard").append(`
<div class=flip>
<div class=front></div>
<div class=back><img src="${cards[i]}"</div>
</div>
`);
});
};
generate([
"https://dummyimage.com/180x120/f00/fff.png&text=one",
"https://dummyimage.com/180x120/0f0/fff.png&text=two",
"https://dummyimage.com/180x120/00f/fff.png&text=three",
]);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<div class=gameBoard></div>
You'll notice the ${cards[i]}, which lets you perform string interpolation by executing at runtime the code in the braces.
Here's a vanilla JS version.
const generate=(cards)=>{
var gb = document.querySelector(".gameBoard");
cards.forEach(card =>
gb.insertAdjacentHTML("beforeend", `
<div class=flip>
<div class=front></div>
<div class=back><img src="${card}"</div>
</div>
`)
);
};
generate([
"https://dummyimage.com/180x120/f00/fff.png&text=one",
"https://dummyimage.com/180x120/0f0/fff.png&text=two",
"https://dummyimage.com/180x120/00f/fff.png&text=three",
]);
<div class=gameBoard></div>
It also uses card instead of cards[i], and an arrow function for the callback.
And this one performs a single append.
const generate=(cards)=>{
document.querySelector(".gameBoard")
.insertAdjacentHTML("beforeend", cards.map(card =>
` <div class=flip>
<div class=front></div>
<div class=back><img src="${card}"</div>
</div>`).join(""));
};
generate([
"https://dummyimage.com/180x120/f00/fff.png&text=one",
"https://dummyimage.com/180x120/0f0/fff.png&text=two",
"https://dummyimage.com/180x120/00f/fff.png&text=three",
]);
<div class=gameBoard></div>

Jquery Text Swap - Multiple Use Issue

So I used a script that I found on Stack Overlow to swap text. It worked great initially but then I tried to use it again on the same page and I noticed an issue.
You can see the problem here: JsFiddle
The HTML
<div class="gallerycard">
<div id="textMessage"></div>
<div class="textContent">
<div class="girlname">ONE LEFT</div>
</div>
<div class="textContent">
<div class="newgirl">TWO LEFT</div>
</div>
<div class="girlimage"></div>
<div class="girlinfo">TEXT</div>
</div>
<div class="gallerycard">
<div id="textMessage"></div>
<div class="textContent">
<div class="girlname">ONE RIGHT</div>
</div>
<div class="textContent">
<div class="newgirl">TWO RIGHT</div>
</div>
<div class="girlimage"></div>
<div class="girlinfo">TEXT</div>
</div>
The Jquery
var cnt=0, texts=[];
// save the texts in an array for re-use
$(".textContent").each(function() {
texts[cnt++]=$(this).text();
});
function slide() {
if (cnt>=texts.length) cnt=0;
$('#textMessage').html(texts[cnt++]);
$('#textMessage')
.fadeIn('fast').animate({opacity: 1.0}, 800).fadeOut('fast',
function() {
return slide()
}
);
}
slide()
So, how do I keep them from merging?
You need two arrays, one for each,
give each one of the gallerycards different ids
and do it twice
var cnt=0, firstTexts=[], secondTexts=[];
// save the texts in an array for re-use
$('#firstID > .textContent').each(function() {
firstTexts[cnt++]=$(this).text();
});
cnt = 0;
// save the texts in an array for re-use
$('#secondID > .textContent').each(function() {
secondTexts[cnt++]=$(this).text();
});
and call slide twice with the relevant array and id
There are multiple problems based entirely on too much copy/paste without understanding the why.
Both target divs have the same id. You should never have two elements on the same page which share the same id. Now there is a quick and dirty way to clean this up and there is a flexible and effective way to clean this up. I went for the flexible solution and I'll explain how it works as best I can.
<div class="gallerycard" data-target="textMessageLeft">
<div id="textMessageLeft"></div>
<div class="textContent">
<div class="girlname">ONE LEFT</div>
</div>
<div class="textContent">
<div class="newgirl">TWO LEFT</div>
</div>
<div class="girlimage"></div>
<div class="girlinfo">TEXT</div>
</div>
<div class="gallerycard" data-target="textMessageRight">
<div id="textMessageRight"></div>
<div class="textContent">
<div class="girlname">ONE RIGHT</div>
</div>
<div class="textContent">
<div class="newgirl">TWO RIGHT</div>
</div>
<div class="girlimage"></div>
<div class="girlinfo">TEXT</div>
</div>
Notice I added a data-target element to the gallerycard containing the id of the div we want to place the text into. I also changed the ids on each target div to be unique. This is critical to make it all work, as is the data-target element matching those ids.
texts = {};
// save the texts in an array for re-use
$(".textContent").each(function () {
var target = $(this).parent().attr('data-target');
if (texts[target] == null) { texts[target] = []; }
texts[target].push($(this).text());
});
function slide(divId, cnt) {
if (cnt >= texts[divId].length) cnt = 0;
$('#' + divId).html(texts[divId][cnt++]);
$('#' + divId)
.fadeIn('fast').animate({
opacity: 1.0
}, 800).fadeOut('fast',
function () {
return slide(divId,cnt)
});
}
for (var t in texts)
{
slide(t, 0);
}
In the javascript I changed a lot to make this an expandable and flexible solution, rather than simply duplicating what was already there with two separate names.
First, we removed the counter and changed texts to an object ({} instead of []). From here I can use texts like a hash, which simplifies the rest of the script. The key of the hash is the value of the data-target from the container div of the message and content divs. Add as many content divs as you want under each parent and they'll all be found and associated correctly.
The texts from each textContent div are stored in an array, but we are using the push() function to eliminate the need for a counter variable - counters are fine for a single instance, but they get ugly with multiples.
I changed the slide function to accept two variables: divId and cnt. divId is how the slider knows which div to target and cnt allows the recursive call to keep a private counter which will not conflict with other instances of the slider function running simultaneously.
Finally, to again prevent duplication and allow further expansion, Instead of simply calling slide, we iterate through the hash to get the divId and call a slide instance for each divId we have. Go ahead and try expanding the number of panes or adding new textContent divs under one of the headers. It all works very smoothly now.
The fiddle is here: http://jsfiddle.net/AX4LC/4/

filtering and sorting divs

I have been looking for a robust and simple way to sort my casestudies but after a couple of hours and a search of stack overflow i could not find a way to filter casestudies the way I want.
Basically I will give each casestudy three categories (year produced, type of project and name) using css classes, for example the markup would look something like this
<div class="name1 home 2013"></div>
<div class="name2 work 2012"></div>
<div class="name3 home 2012"></div>
<div class="name4 charity 2012"></div>
<div class="name5 home 2010"></div>
<div class="name6 work 2007"></div>
Then I want to have buttons so you can choose which category you want to sort the casestudies by. So something like.
<div class="button" id="year">Sort by Year</div>
<div class="button" id="alpha">sort Alphabetically</div>
<div class="button" id="type">sort by type</div>
This is where I am getting stuck. What javascript function can i create so that if you click the button "sort by year" it will create a mark up that looks like this. eg sorting all the casestudies in to divs with casestudies of the same year.
<div>
<div class="name1 home 2013"></div>
</div>
<div>
<div class="name2 work 2012"></div>
<div class="name3 home 2012"></div>
<div class="name4 charity 2012"></div>
</div>
<div>
<div class="name5 home 2010"></div>
</div>
<div>
<div class="name6 work 2007"></div>
</div>
I would use data attributes to make the filtering easier.
<div class="name1 home" data-year="2013">2013</div>
<div class="name2 work" data-year="2012">2012</div>
<div class="name3 home" data-year="2012">2012</div>
<div class="name4 charity" data-year="2012">2012</div>
<div class="name5 home" data-year="2010">2010</div>
<div class="name6 work" data-year="2007">2007</div>
The using JQuery and array.map (could be replaced with a foreach if you want older browser support)
var studies = $('[data-year]')
studies.map(function(index, el) {
var $el = $(el)
year = $el.attr('data-year')
if($('#' + year).length == 0){
$(document.body).append(
$('<div>').attr('id', year)
.css('margin-bottom', '20px')
)
}
$('#' + year).append(el)
})
what this does is take all the elements with a data-year attribute, foreach element check to see if a div with the id of that elements year exists. If it doesn't create one and append it to the body. Then it appends the element into the year container.
see this jsfiddle

Categories