How to select a specific <span> with the same class names? - javascript

I am attempting to extract some data from this block of HTML.
<div class="group-capitalization-content field-group-html-element">
<h2><span>Capitalization</span></h2>
<div class="item">
<div class="label-inline">Issued & Outstanding: </div>
<span class="number">242906121</span>
</div >
<div class="item">
<div class="label-inline">Reserved for Issuance: </div>
<span class="number">51423534</span >
</div>
</div>
I'm using an npm module called cheerio to scrape data from HTML. Thus I have the following code to try and get the "numbers".
var data = $('.group-capitalization-content .item .number').text();
Running this code results in: 24290612151423534, which is both results appended together.
How do I select the individual numbers here / separate them?

If the above example is all you need, use .eq().
First items's text() is
$('.group-capitalization-content .item .number').eq(0).text();
...and second is
$('.group-capitalization-content .item .number').eq(1).text();
If you have a more complex case and you need to store the data in an array, for later, I'd use $.map() (#Sushanth's answer) - probably a bit more specific, to rule out the possibility of other .numbers in the page:
let data = $.map(
$('.group-capitalization-content .item .number'),
function(e){ return $(e).text() }
);

You could you map method and target the .number class, which will spit out the contents into an array.
let data = $.map($('.number'), function(elem) {
return $(elem).text();
});
console.log(data);

If you don't know ahead of time how many there will be you can loop through them with an .each
$('.group-capitalization-content .item .number').each(function() {
$(this).text(); // Save this to a var or do something with it
});

How about each function to go through each of your results, like:
$('.group-capitalization-content .item .number').each(function(index, element) {
//your individual item code goes here
console.log( index + ": " + $( this ).text() );
});

i usually get all the dom elements and then loop over them to do my necessary functions. here is a working fiddle. https://jsfiddle.net/bbmtk7tm/
var data = $('.group-capitalization-content .item .number');
for(var i=0; i<data.length;i++)
{
console.log(data[i].innerHTML);
}

Related

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)

jQuery.each() class example Appending Div Text to Li

$EACH DEMO
anyhow iam getting the text of "div class": and printing in console.,
and iam trying to append the same text in "li > button", which is not happening, not sure where iam going wrong,
html :
<div class="productDescription">Red</div>
<div class="productDescription">Orange</div>
<div class="productDescription">Green</div>
<li><button>1</button></li>
<li><button>2</button></li>
<li><button>3</button></li>
JS:
//step - 1
$.each($('.productDescription'), function() {
var classTxt = $(this).text();
console.log(classTxt);
});
//outputs: Red Orange Green
//step - 2 now im trying to append the text to li
var liBtn = $(this).find('li').next('button');
console.log("Text of Button - "+ classTxt);
console.log(liBtn)
for (var i = liBtn.length; i >= 0; i++) {
liBtn[i]
};
Appreciate Your Help, Thanks
There are several errors in your code. classTxt is undefined outside of the each handler's context and the second this refers to window object and not to the .productDescription elements.
I'd suggest using the .append() method's callback function:
// cache the collection for better performance
var $p = $('.productDescription');
$('li button').append(function (index) {
return $p.eq(index).text();
});
http://jsfiddle.net/46yo7etz/
You could also use the .text() method:
$('li button').text(function(index, currentTextContent) {
// using indices for selecting
// the corresponding `.productDescription` element
return currentTextContent + $p.eq(index).text();
});
Please note that your fiddle's markup is invalid. li element should be child of an ul/ol element.
$('li > button').text(function() {
return $('.productDescription').eq( $('li > button').index( this ) ).text();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="productDescription">Red</div>
<div class="productDescription">Orange</div>
<div class="productDescription">Green</div>
<li><button>1</button></li>
<li><button>2</button></li>
<li><button>3</button></li>
$("button").each(function(i){
$(this).text( $(".productDescription").eq(i).text() );
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="productDescription">Red</div>
<div class="productDescription">Orange</div>
<div class="productDescription">Green</div>
<ul>
<li><button>1</button></li>
<li><button>2</button></li>
<li><button>3</button></li>
</ul>
http://jsfiddle.net/simply_simpy/uqmpvb7e/

Collapse HTML if no content found

I want to be able to remove HTML elements if they contain no content.
Let's say we have some markup and are targeting all 'collapse' classes:
<div class='collapse'>[CONTENT?]</div>
If there is some content then don't do anything.
But if there is no content - no string characters or whitespace - then remove the div element completely.
This is easy to implement in the simple cases but with nested content it's slightly more more tricky.
Here is a demo, if you try removing the [CONTENTX?] strings and then seeing what the HTML structure is you'll notice that it doesn't work completely.
If a div only has other divs with no content then that should be treated as no characters or whitespace.
If we remove all [CONTENTX?] strings then we should see no HTML structure.
What ways are there to handle this?
jsFiddle: http://jsfiddle.net/97udq/
HTML:
<div id='container'>
<div class='collapse'>
[CONTENT1?]
</div>
<div class='collapse'>
[CONTENT2?]
<div class='collapse'>
[CONTENT3?]
<div class='collapse'>[CONTENT4?]</div>
<div class='collapse'>[CONTENT5?]</div>
</div>
</div>
</div>
Javascript:
$(function(){
// function
collapse();
// Show HTML structure
alert($('#container').html());
});
function collapse(){
// Loop thru all collapse elements
$('.collapse').each(function(){
// Check for pure whitespace
if($(this).html().replace(/\s+/g, '').length==0){
// Nothing to see, so remove.
$(this).remove();
}
});
}
CSS:
.collapse{
height:20px;
border:1px solid red;
}
I think this does the job;
It just uses text() instead of html();
Here's the documentation.
This one adds the trim(), but I thik that's not what you want.
function collapse(){
$('.collapse').each(function(){
if($(this).text().length==0){
$(this).remove();
}
});
}
Here's another way of accomplishing what you want. It recurses down the DOM pruning nodes from the bottom up. Hope this helps.
function prune(root) {
$.each($(root).children(), function(){
prune($(this));
});
if($(root).html().replace(/\s+/g, '').length==0 && $(root).hasClass("collapse")){
$(root).detach();
}
}
Code integrated into your JSFiddle
You need to recreate the .each() loop, but reversed. Just like that :
function collapse(){
var el = $('.collapse');
for(var i = el.length - 1; i >= 0; i--){
if(el[i].innerHTML.replace(/\s+/g, '').length==0){
$(el[i]).remove();
}
}
}
It will remove the childrens first, then check for parent.
Here a fiddle : http://jsfiddle.net/97udq/5/
EDIT :
I missunderstood your question, here's the right solution :
function collapse(){
$('.collapse').each(function(){
var $this = $(this)
var clone = $this.clone();
clone.children().remove();
if(clone.html().replace(/\s+/g, '').length==0){
$this.children().appendTo($this.parent());
$this.remove()
}
})
}
Basicly, you clone the current div, remove its children and then check if there is some text. If there's none, you append his children to his parent
Fiddle : http://jsfiddle.net/97udq/9/

jQuery to loop through elements with the same class

I have a load of divs with the class testimonial and I want to use jquery to loop through them to check for each div if a specific condition is true. If it is true, it should perform an action.
Does anyone know how I would do this?
Use each: 'i' is the postion in the array, obj is the DOM object that you are iterating (can be accessed through the jQuery wrapper $(this) as well).
$('.testimonial').each(function(i, obj) {
//test
});
Check the api reference for more information.
try this...
$('.testimonial').each(function(){
//if statement here
// use $(this) to reference the current div in the loop
//you can try something like...
if(condition){
}
});
It's pretty simple to do this without jQuery these days.
Without jQuery:
Just select the elements and use the .forEach() method to iterate over them:
const elements = document.querySelectorAll('.testimonial');
Array.from(elements).forEach((element, index) => {
// conditional logic here.. access element
});
In older browsers:
var testimonials = document.querySelectorAll('.testimonial');
Array.prototype.forEach.call(testimonials, function(element, index) {
// conditional logic here.. access element
});
Try this example
Html
<div class="testimonial" data-index="1">
Testimonial 1
</div>
<div class="testimonial" data-index="2">
Testimonial 2
</div>
<div class="testimonial" data-index="3">
Testimonial 3
</div>
<div class="testimonial" data-index="4">
Testimonial 4
</div>
<div class="testimonial" data-index="5">
Testimonial 5
</div>
When we want to access those divs which has data-index greater than 2 then we need this jquery.
$('div[class="testimonial"]').each(function(index,item){
if(parseInt($(item).data('index'))>2){
$(item).html('Testimonial '+(index+1)+' by each loop');
}
});
Working example fiddle
you can do it this way
$('.testimonial').each(function(index, obj){
//you can use this to access the current item
});
jQuery's .eq() can help you traverse through elements with an indexed approach.
var testimonialElements = $(".testimonial");
for(var i=0; i<testimonialElements.length; i++){
var element = testimonialElements.eq(i);
//do something with element
}
divs = $('.testimonial')
for(ind in divs){
div = divs[ind];
//do whatever you want
}
I may be missing part of the question, but I believe you can simply do this:
$('.testimonial').each((index, element) => {
if (/* Condition */) {
// Do Something
}
});
This uses jQuery's each method: https://learn.jquery.com/using-jquery-core/iterating/
You can do this concisely using .filter. The following example will hide all .testimonial divs containing the word "something":
$(".testimonial").filter(function() {
return $(this).text().toLowerCase().indexOf("something") !== -1;
}).hide();
With a simple for loop:
var testimonials= $('.testimonial');
for (var i = 0; i < testimonials.length; i++) {
// Using $() to re-wrap the element.
$(testimonials[i]).text('a');
}
Without jQuery updated
document.querySelectorAll('.testimonial').forEach(function (element, index) {
element.innerHTML = 'Testimonial ' + (index + 1);
});
<div class="testimonial"></div>
<div class="testimonial"></div>
You could use the jQuery $each method to loop through all the elements with class testimonial.
i => is the index of the element in collection and val gives you the object of that particular element and you can use "val" to further access the properties of your element and check your condition.
$.each($('.testimonal'), function(i, val) {
if(your condition){
//your action
}
});
In JavaScript ES6 .forEach()
over an array-like NodeList collection given by Element.querySelectorAll()
document.querySelectorAll(".testimonial").forEach((el, idx) => {
el.style.color = "red";
console.log(`${idx} Element ${el.tagName} with ID #${el.id} says: ${el.textContent}` );
});
<p class="testimonial" id="1">This is some text</p>
<div class="testimonial" id="2">Lorem ipsum</div>
$('.testimonal').each(function(i,v){
if (condition) {
doSomething();
}
});
More precise:
$.each($('.testimonal'), function(index, value) {
console.log(index + ':' + value);
});

How to select consecutive elements that match a filter

Given this example:
<img class="a" />
<img />
<img class="a" />
<img class="a" id="active" />
<img class="a" />
<img class="a" />
<img />
<img class="a" />
(I've just used img tags as an example, that's not what it is in my code)
Using jQuery, how would you select the img tags with class "a" that are adjacent to #active (the middle four, in this example)?
You could do it fairly easily by looping over all the following and preceding elements, stopping when the filter condition fails, but I was wondering if jQuery could it natively?
Here's what I came up with in the end.
// here's our active element.
var $active = $('#active');
// here is the filter we'll be testing against.
var filter = "img.a";
// $all will be the final jQuery object with all the consecutively matched elements.
// start it out by populating it with the current object.
var $all = $active;
for ($curr = $active.prev(filter); $curr.length > 0; $curr = $curr.prev(filter)) {
$all = $all.add($curr);
}
for ($curr = $td.next(filter); $curr.length > 0; $curr = $curr.next(filter)) {
$all = $all.add($curr);
}
For a follow up question, I could see how this could easily be generalised by making it into a function which takes two arguments: an initial element, and a filter string - can anyone point me in the right direction to find out how to extend the jQuery object to add such a function?
Edit: I've since found that the each() function would do this rather well for some purposes. In my own case it doesn't work as cleanly, since I want a single jQuery object for all those elements, but here's how you could use each for a different purpose (hiding consecutive ".a" elements, in this example:)
$('#active')
.nextAll()
.each(hideConsecutive)
.end()
.prevAll()
.each(hideConsecutive)
;
function hideConsecutive(index, element) {
var $e = $(element);
if (!$e.is(".a")) {
return false; // this stops the each function.
} else {
$e.hide('slow');
}
}
--
Edit: I've put this together into a plugin now. Take a look at http://plugins.jquery.com/project/Adjacent if you're interested.
I believe looping is your best bet. But you could try, each active, and then move before and after until the condition breaks, which if the set is large enough would be faster.
The below code will add two new functions, nextConsecutive() and prevConsecutive(). They should do what you want.
$.each( ['prev', 'next'], function(unusedIndex, name) {
$.fn[ name + 'Consecutive' ] = function(matchExpr) {
var $all =
(name == 'prev')
? $(this).prevAll()
: $(this).nextAll();
if (!matchExpr)
return $all;
var $notMatch = $($all).not(matchExpr).filter(':first');
if ($all.index($notMatch) != -1)
return $allConsecutive = $all.slice(0, $all.index($notMatch));
return $all;
};
});
The tilde (~) is the siblings selector:
$('#active ~ img.a').hide();
#Prestaul
$('#active ~ img.a')
would only select the following siblings, and would include the non-consecutive siblings too. Docs: http://docs.jquery.com/Selectors/siblings#prevsiblings
This is another way to do it, though the sibling selector answer is pretty cool:
var next = $('#active').next('.a');
var prev = $('#active').prev('.a');
Edit: I re-read your requirements and this isn't quite what you want. You could use nextAll and prevAll, but those, too, would not stop at the IMGs without the class name.

Categories

Resources