Summary: The purpose of this is to display a 3-part slider that users can click through, and they can use the index on each slide to filter the cards on that slide to a specific topic. The filters are working fine, but there's a problem in the JavaScript when it populates the indices: it's populating an index for all 3 cards, and showing that large index on each slide. Instead, the index for each slide needs to be unique, and only contain the hashtags from the cards in that same slide. I really want to avoid duplicating code for the different slides.
HTML
The following HTML has 3 (li) slides. Each slide contains a visible index (.hashtag-list), and one or more article cards (.item). Each (.item) besides the first one contains a hidden input with one or more hashtag values.
<li class="trend-cards">
<div class="card-items">
<div class="item trendingtopiccardblock">
<div class="hashtag-list"></div>
</div>
<div class="item">
<input class="tags" type="hidden" value="TopicA,TopicB"/>
</div>
<div class="item">
<input class="tags" type="hidden" value="TopicC"/>
</div>
</div>
</li>
<li class="trend-cards">
<div class="card-items">
<div class="item trendingtopiccardblock">
<div class="hashtag-list"></div>
</div>
<div class="item">
<input class="tags" type="hidden" value="TopicC, TopicD"/>
</div>
<div class="item">
<input class="tags" type="hidden" value="TopicA,TopicC,TopicD"/>
</div>
</div>
</li>
<li class="trend-cards">
<div class="card-items">
<div class="item trendingtopiccardblock">
<div class="hashtag-list"></div>
</div>
<div class="item">
<input class="tags" type="hidden" value="TopicA, TopicD"/>
</div>
<div class="item">
<input class="tags" type="hidden" value="TopicB,TopicC,TopicD"/>
</div>
</div>
</li>
JavaScript
The following jQuery pulls the values from the .tags classes, stores them in an array, removes duplicates, sorts them, and then populates the HTML in a callback. (Ignore the countryButtons and countries array, as that's not relevant.)
populateHashtagList: function() {
var $cards = $(".card-items .tags");
var list = [];
var $countryButtons = $('.card-filtering li .country-filtering');
var countries = [];
$countryButtons.each(function() {
countries.push(this.firstChild.data.replace("#", "").toLowerCase());
});
//Get tag values, set to lowercase and store in List array
$cards.each(function() {
var tags = getTags($(this).val());
$(tags).each(function (index, value) {
var tagValue = value.toLowerCase();
if($.inArray(tagValue, countries) === -1) list.push(value);
});
});
//Remove duplicates from the array
var uniqueTags = [];
$.each(list, function(i, el){
if($.inArray(el, uniqueTags) === -1) uniqueTags.push(el);
});
uniqueTags.sort();
function getTags(parameter) {
var arr = parameter.split(',');
return arr;
}
//Populate hash-tag List
var hashtagList = $('.hashtag-list');
populateHashtagList();
function populateHashtagList(callback) {
$.each(uniqueTags, function(i, el){
var htmlToInsert = '<span class="active">' + el + '</span>';
hashtagList.append(htmlToInsert);
});
if(typeof callback == "function")
callback();
}
}
What I've tried
Isolating the function using a $(".trend-cards").each function. This resulted in the same large list, but it was tripled on each slide.
Adding more specific paths to the .tags selectors, which changed nothing.
Using a parent selector once the .tags variable is set, and calling the remainder of the function from there. No hashtags populate in this case.
I appreciate any feedback and input on this. I want to learn how to do this better in the future. Thank you very much!
Wrapping this code in .each() function is the best solution here. You said you tried that and you probably forgot to specify parent element for cards and hashtag-list selectors.
Here is a working example: https://jsfiddle.net/k3oajavs/
$(".trend-cards").each(function(){
var $cards = $(".card-items .tags", this);
// ...
var hashtagList = $('.hashtag-list', this);
});
Related
I have a page that includes a section that's generated from a csv file (in Jekyll, using liquid). In this section, I have individual divs with an image and description. I need to find each div that fits a specific description.
<div class="row">
{% for item in site.data.items %}
<div class="col-md-3 item {{ item.test }} {{ item.industry }} {{ item.shape }}">
<img src="source here">
<br/>
<p>{{ item.name }}</p>
<p>{{ item.description }}</p>
</div>
{% endfor %}
</div>
I assigned classes to each div, and pulled the classes from the csv. This generates a class name of something like "item sticky food pasta".
Now I want to filter the displayed items when a user selects options on the page. I created an array, selectedOptions:
var selectedOptions = [];
Hide the other items:
$(".item").hide();
Push the selected options to the array:
$('input[name="filter-input"]:checked').each(function(i){
selectedOptions.push($(this).val());
});
And loop through the options to display only the items that have the classes selected:
for (var i = 0; i < selectedOptions.length; i++) {
$('.item').each(function() {
if ($(this).hasClass(selectedOptions[i])){
$(this).show();;
}
});
}
This works great, if I wanted every item that has any of the classes. However, what I want is a cumulative effect: I want only the items that have BOTH food AND pasta as a class.
How can I show every item that has all the classes? I know I can use something like $('.classa.classb'), but I'm not sure how to use that with the array I have here.
Thank you!
You can use querySelectorAll to do the dirty work for you in finding all the elmeents that match. Just build up a selector string.
const wrapper = document.querySelector(".wrapper");
document.querySelector("fieldset").addEventListener("change", function () {
const cbs = Array.from(document.querySelectorAll('input[type="checkbox"]:checked'));
const selectedClasses = cbs.map(cb => cb.value).join("");
const matchedElems = wrapper.querySelectorAll(selectedClasses);
console.log(matchedElems);
});
<div class="wrapper">
<div class="one">1</div>
<div class="two">2</div>
<div class="three">3</div>
<div class="one two">1 2</div>
<div class="one three">2 3</div>
<div class="two three">1 3</div>
<div class="one two three">1 2 3</div>
</div>
<form>
<fieldset>
<legend>Filters</legend>
<input type="checkbox" value=".one" id="cb1" /><label for="cb1">1</label>
<input type="checkbox" value=".two" id="cb2" /><label for="cb2">2</label>
<input type="checkbox" value=".three" id="cb3" /><label for="cb3">3</label>
</fieldset>
</form>
Consider the following.
$(".item").hide();
$('input[name="filter-input"]:checked').each(function(i){
$(".row > .item." + $(this).val()).show();
});
This will hide all items and then iterate each of the selected. It will show each of the elements that has the Class selected. The Selector does all the work.
E.G. If the user selects food and pasta, all the row that have food and pasta as a child will be shown.
$(".row > .item.pasta").show();
This will be done for each checked item.
You can also bank them up in an Array as you suggested. E.G.:
selectedOptions = ["food", "pasta"];
You can simply use .join(), yet you may want to add . to each so it can be a Class selector.
$(".item").hide();
var selectedOptions = [];
$('input[name="filter-input"]:checked').each(function(i){
selectedOptions.push($(this).val());
});
$.each(selectedOptions, function(k, v){
selectedOptions[k] = "." + v;
});
$(selectedOptions.join(", ")).show();
This will result in something like:
$(".food, .pasta").show();
You might notice how this is a lot of extra code to accomplish the same thing.
My Function does not work when i call it.
When I Query All Elements And loop over them. I cant get any effect on the UI.
I need to add attribute hidden from all .question and remove .hidden from the one whose index is passed to the Js function. classes when i call the function.
Here is the HTML.
<div class="col-md-12 mb-12 question" hidden="hidden" id="D1">
1
</div>
<div class="col-md-12 mb-12 question" hidden="hidden" id="D2">
2
</div>
<div class="col-md-12 mb-12 question" hidden="hidden" id="D3">
3
</div>
<div class="col-md-12 mb-12 question" hidden="hidden">
4
</div>
Calling the lines in the If condition alone without the loop works.
What could i not be doing right here.
function hideothersexcept(index){
var ALLQNS = $('.question');
for (i = -1; i < ALLQNS.length; i++) {
if (index == i) {
$('#' + getid(index)).removeAttr('hidden')
} else {
ALLQNS[index].setAttribute("hidden", "hidden");
}
}
}
function getid(elm) {
var ALLQNS = $('.question');
k = ALLQNS[elm].getAttribute("id");
return k;
}
Try following code its more clean and succinct then manually looping through all divs and more importantly it works :)
function hideothersexcept(index){
$('.question').each(function(elIndex, el){
if(elIndex == index){
$(el).removeAttr('hidden');
}else{
$(el).attr('hidden', 'hidden');
}
});
}
hideothersexcept(1);
It will hide all other dive except div containing number 2.
NOTE: indexing is zero-based :)
Please let me know if it does not work.
Below is part of some code on an html page that lists shopping cart products. Using JavaScript/jQuery, I need to be able to loop through the li items and get the div "data-" values for each. The issue that I am having is that there are no IDs for the div that has the data- value (). I only see the div for "CategoryContent".
<div class="Block CategoryContent Moveable Panel" id="CategoryContent">
<ul class="ProductList ">
<li class="Odd">
<div class="ProductImage QuickView" data-product="577">
<img src="http://cdn3.example.com/products/577/images/1731/2311-.jpg?c=2" alt="Sweater Vest V-Neck Cotton" />
</div>
<div class="ProductDetails">
Sweater Vest V-Neck Cotton
</div>
<em class="p-price">$45.04</em>
<div class="ProductPriceRating">
<span class="Rating Rating0">
<img src="http://cdn3.example.com/templates/custom/images/IcoRating0.png?t=" alt="" style="" />
</span>
</div>
<div class="ProductActionAdd" style="display:;">
Choose Options
</div>
</li>
</ul>
</form>
</div>
So, there is only one li item here, on a typical page, there are up to 9. My goal is to use JavaScript to get the data-product values and then use that to look up a better image thumbnail and have it replaced. So, how do I get the value set for data-product?
Quite easy:
// Loop through all list items of ul.ProductList:
$("ul.ProductList li").each(function (index, element) {
// Find the element with attribute data-product:
$dp = $(element).find("[data-product]");
// Get the value of attribute data-product:
var product = $dp.attr("data-product");
// Now set the high quality thumbnail url:
var url = "/images/hq/" + product + ".png"; // Note that this is just an example
// Here you can use $(element) to access to current li (and the img):
$(element).find('.ProductImage img').attr('src', url);
});
You can use this:
$("#CategoryContent div[data-product]").each(function(){
alert($(this).attr('data-product'));
});
Pure JS:
var divs = document.querySelectorAll('#CategoryContent div[data-product]');
var index = 0, length = divs.length, prodIds = [];
for ( ; index < length; index++) {
prodIds.push(divs[index].getAttribute('data-product'));
}
Fiddle: http://jsfiddle.net/we5q7omg/
You could use the class name to get the data-product
var products = document.getElementsByClassName("ProductImage");
for(var i=0; i<products.length;i++)
{
console.log(products[i].getAttribute("data-product"));
}
Using JQuery, you can get the elements with the data-product attribute by simply calling
$('[data-product]')
// Or if you only want the data-product elements within the UL.
$('ul').find('[data-product]')
From there you can simply do pull the products from the elements. For Example:
var products = $('[data-product]').map(function() {
return $(this).data('product');
});
I need to loop through a list of divs. If a div in that list has the class name of "active", than I need to save the contents of the <p></p> tag of the specific div to a variable. I then need to place the contents of that variable in a the value of a hidden input element on a form. For example, here is some example HTML:
<div class="names">
<div class="one active">
<p>A</p>
</div>
<div class="two active">
<p>B</p>
</div>
<div class="three">
<p>C</p>
</div>
<div class="four active">
<p>D</p>
</div>
<div class="five">
<p>E</p>
</div>
<div class="six active">
<p>F</p>
</div>
</div>
<form action="form.php" method="POST">
<input type="hidden" name="list" id="list" value="">
</form>
Since four of the divs contain the "active" class, I need to save the content that is in each paragraph tag to a variable to be inserted into the value of the hidden field. In this example, the value of the field would be A, B, D, F.
I thought about doing something like this:
var userSelection = function() {
$('.names div').each(function () {
if($(this).hasClass('active')) {
return $(this).text();
}
});
};
$('#list').val(userSelection);
First off, that code doesn't work and I am also not even sure if that's the best way to go about solving my problem. Second, if anyone has a better idea of how to accomplish what I need, I would love to hear it.
I would use map() to get an array of the text:
var textArr = $('.names div.active').map(function() {
return $(this).text();
}).get();
From there, you could use join() to get a string you could write to the DOM:
var textString = textArr.join(', ');
Full, compressed code:
var userSelection = function() {
return $('.names div.active').map(function() {
return $(this).text();
}).get().join(', ');
};
$('#list').val(userSelection());
Alternative to Jason P's answer is to modify what you've got to return an array of the results, as you're calling a function into a variable that has multiple results:
var userSelection = function() {
var output = [];
$('.names div').each(function () {
if($(this).hasClass('active')) {
output.push( $(this).text().trim() );
}
});
return output;
};
See JSFiddle.
Working fiddle: http://jsbin.com/uQEJIkU/3/edit
$(document).ready(function() {
values = []
$("div.active > p").each( function() {
values.push($(this).text())
})
})
What i am trying to do is, from my ajax call get the first result and put it into the .portfolio--active div and then remove this first item from the ajax results then loop through the rest of the items in the .portfolio--active.
The looping is working perfectly. The problem I am having is with the .portfolio--active. I just don't understand how i am meant to output data without it being in a loop or someway of referencing the function name. for example: <ul data-bind="foreach: items"> reefers to this: hutber.portfolio.ko.self.items = ko.observableArray([]); without it being in a l
Markup
<section>
<h2>portfolio</h2>
<div class="portfolio--active">
<!--<img alt="" src="/img/clients/vw.jpg">-->
<img alt="" data-bind="attr: {src: '/img/clients/' + headline.id+'.jpg'}">
<h3>Volkswagen.co.uk</h3>
<date>Febuary, 2012 - Zone Ltd.</date>
<p>Lorem text</p>
<tags><i title="jQuery" class="icon-rss upsideLeft"></i><i title="jQuery" class="icon-html5 upsideLeft"></i></tags>
</div>
<div class="portfolio--options">
<ul data-bind="foreach: items">
<li data-bind="attr: {'data-id': $data.id}">
<img alt="" data-bind="attr: {src: '/img/clients/' + $data.id+'.jpg'}">
<h4 data-bind="text: title"></h4>
</li>
</ul>
</div>
</section>
JS
hutber.portfolio.ko = {
init: function(){
ko.applyBindings(new hutber.portfolio.ko.portfolioViewModel());
},
portfolioViewModel: function(){
hutber.portfolio.ko.self = this;
hutber.portfolio.ko.self.items = ko.observableArray([]);
hutber.portfolio.ko.self.headline = ko.observableArray([]);
$.getJSON('/portfolio/json').done(function(info){
//build headline item
hutber.portfolio.ko.self.headline(info.slice(0,1));
//remove first item in array only leave none headline items
info = info.slice(1,info.length);
//update items with updated info
hutber.portfolio.ko.self.items(info)
});
}
};
You can reference the [0] index of the array in your bindings, but in your case it seems like maybe you should make headline just an observable and just do an info.shift() to remove and return the first item in the array to set the value of headline. Then, you can just set items as info without doing any slicing.
$.getJSON('/portfolio/json').done(function(info){
//build headline item
hutber.portfolio.ko.self.headline(info.shift());
//update items with updated info
hutber.portfolio.ko.self.items(info)
});