count immediate child elements using jquery with a variable - javascript

This question is similar to this one (but not exact):
Count immediate child div elements using jQuery
I have a bunch of lists that represent christmas presents for different people.
As an example, this is a list of presents for one person. Note that new people can be added
<ul id = "presents">
<li id = "person1" >
<ul id = "presentsforperson1">
<li id = "present1" >
<ul id = "present1info">
<li> Present 1 </li>
<li> A New Pair of Shoes </li>
<li> $19.99 </li>
</ul>
</li>
<!-- More presents for person 1 can be added here dynamically-->
</ul>
</li>
<!-- More people can be added here -->
</ul>
Unlike the question posted, I have the object presentsforperson1 in a variable called drop (droppable). I was wondering what the correct syntax is for finding the number of presents person 1 has (in this case 1) with this variable?
This is what I have tried so far:
var numberofPresents = $(drop + "ul > li").length; //drop holds li#presentsforperson1 .droppable
var numberofPresents = $(dropid + "ul > li").length; //dropID is $(drop).attr("id");

If the person id is the id of the ul containing their presents then simply:
$('#personId > li').length
will do it.
Update
Or if the id is in a variable:
$('#' + personId).children('ul').length
You obviously could append the child selector to the selector string eg:
$('#' + personId + ' > ul').length
But personally I find the version with the .children call more readable.

Assuming your drop variable references a jQuery object:
var quantity = drop.children().length;
If it has the raw DOM element:
var quantity = $(drop).children().length;
If it is merely a string with the ID, with no ID selector syntax:
var quantity = $('#' + dropid).children().length;
Or you can do concatenation like you were:
var quantity = $('#' + dropid + " > li").length;

You can do something as follows (give your lis that represent a present a css class):
var presents = $('ul[id="person1"]').children('li.presentClass');
alert(presents.length);

Related

How do I select elements whose class name starts with a specific word?

My goal, for example, is to select elements that have a class starting with the word "child-1".
var childs = document.querySelectorAll("#child-" + i);
This code can only select the class name "child-x".
<li class="child-1-5">
<li class="child-1-8">
<li class="child-1-9">
How do I select elements that begin with Child1?
This should work:
var childs = document.querySelectorAll("li[class^='child-1']");
If you'd like to select the first number dynamically with your parameter, just escape to the variable:
var i = 1;
var childs = document.querySelectorAll("li[class^='child-" + i + "']");
<li class="child-1-5">5
<li class="child-1-8">8
<li class="child-1-9">9
You can use CSS wildcards for that:
const children = document.querySelectorAll('[class^="child-"], [class*=" child-"]');
console.log(children);
<li class="child-1-5">
<li class="child-1-8">
<li class="child-1-9">
<li class="some classes child-1-10 to make noise">
MDN reference

Count of nested ul-li children over multiple levels doesn't always work

I was trying to show the count of all children elements over multiple nested levels beside all li elements which are dynamically generated.
Eg:
Z(8)
A(4)
B
C(2)
D
E
F(2)
G
H
What i am getting now:
Code I used:
Sample HTML:
<li class="li-item">
B R(None)<span class="count-item" data-cnt="2">[2]</span>
<ul class="sub-parent-ul 237">
<li class="li-item">
B R(None)<span class="count-item" data-cnt="3">[3]</span>
<ul class="sub-parent-ul 246">
<li class="li-item">
Bhu Rik(None)<span class="count-item" data-cnt="3">[3]</span>
<ul class="sub-parent-ul 258">
<li class="li-item">Kai Hiwatari(None)<span class="count-item"></span></li>
<li class="li-item">
B R(None)<span class="count-item" data-cnt="2">[2]</span>
<ul class="sub-parent-ul 263">
<li class="li-item">
Bhu Rik(None)<span class="count-item" data-cnt="1">[1]</span>
<ul class="sub-parent-ul 264">
<li class="li-item">B R(None)<span class="count-item"></span></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
JQuery:
$(".sub-parent-ul").each(function() {
// reusabilty
var context = $(this);
// count and populate
var count = context.children().length;
context.prev("a").children().text('['+count+']');
context.prev("a").children().attr('data-cnt',count);
});
$(".sub-parent-ul").each(function() {
var context2 = $(this);
// count and populate
var child_count = context2.children().length;
//check for inner ul
var sub_count = 0;
context2.children('li').each(function () {
var context3 = $(this);
if(context3.children('a').children('span').attr('data-cnt') !== undefined){
sub_count += +context3.children('a').children('span').attr('data-cnt');
}
if(context2.hasClass('G52436')){
console.log(context3.children('a').children('span').attr('data-cnt'));
console.log(context3.children('a').children('span').html());
console.log(context3.children('a').children('span'));
}
});
// final count and populate
var tot_count = child_count+sub_count;
context2.prev("a").children().text('['+tot_count+']');
context2.prev("a").children().attr('data-cnt',tot_count);
});
It works for some levels but not everywhere. I am not sure where i am wrong. What's weird is, I have kept some console logs in the calculation logic which will get the complete count of child count. The js object shows the perfect count which i need, but when i select the attr from the object, it shows different value and that is driving me nuts.
As you can see in the screenshot, the object shows the count as 2 but the value of attribute returns 1. It looks like it is ignoring the data-cnt of immediate child and getting the next child's data-cnt in some cases.
Can someone please identify the issue?
The code doesn't seem to be using recursion, but rather tries to enumerate a set depth (context,context2,context3).
One solution would be to use recursion within the function itself, the other is to use find instead of children to search multiple levels for all the children:
$(".sub-parent-ul").each(function() {
var context = $(this),
children = context.find('li'),
count = children.length,
a = context.prev("a").children();
a.text('['+count+']');
a.data('cnt', count);
});
Not entirely sure it's exactly according to your goal, but the outcome for the example html can be seen in this fiddle

create simple js object and store them into js array

Inside html dom I have following structure
<div id="myTitles">
<ul>
<li>
Title:<title>A</title><br>
Second title: <secTitle>B</secTitle><br>
Third title: <thirdTitle>3</thirdTitle>
</li>
<hr>
<li>
Title:<title>B</title><br>
Second title: <secTitle>C</secTitle><br>
Third title: <thirdTitle>9</thirdTitle>
</li>
</ul>
</div>
Number of list elements inside ul of myTitles div is unknown on runtime. So I'm taking var nrOfTitles = $('#myTitles ul li').length; to determine how many is there.
Now, I want to create some simple javascript object which will hold those values (stored inside this semantic tags (title, secTitle, thirdTitle)) and store them inside js array.
var items = $("#myTitles > ul > li");
var arr = [];
items.each(function(){
arr.push([$(this).find("title").text(),$(this).find("secTitle").text(),$(this).find("thirdTitle").text()])
})
With your current combination of <li>s it would contain:
[["A","B","3"],["B","C","9"]]
Or you can store the properties as objects:
var items = $("#myTitles > ul > li");
var arr = [];
items.each(function(){
arr.push({title:$(this).find("title").text(),secTitle:$(this).find("secTitle").text(),thirdTitle:$(this).find("thirdTitle").text()})
})
Then you would get
[{title:"A",secTitle:"B",thirdTitle:"3"},{title:"B",secTitle:"C",thirdTitle:"9"}]
You can use $.fn.map method to conveniently create an array of objects:
var obj = $('#myTitles ul li').map(function() {
return {
title: $(this).find('title').text(),
secTitle: $(this).find('secTitle').text(),
thirdTitle: $(this).find('thirdTitle').text()
};
}).get();
alert(JSON.stringify(obj, null, 4));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="myTitles">
<ul>
<li>
Title:<title>A</title><br>
Second title: <secTitle>B</secTitle><br>
Third title: <thirdTitle>3</thirdTitle>
</li>
<hr>
<li>
Title:<title>B</title><br>
Second title: <secTitle>C</secTitle><br>
Third title: <thirdTitle>9</thirdTitle>
</li>
</ul>
</div>
But since you are using custom tags in HTML, remember to create them for IE8, if you plan so support this browser, like document.createElement('title');.

jQuery: How to specify element in this case?

I have a function that adds game results to a schedule.
I want to have the score of a winning team in yellow - add ".winner" class to (<%=score1%> or <%=score2%>).
I wonder what is the correct way to specify needed element, using jQuery.
My current code isn't working. All elements get ".winner" class, regardless of whether a team won or not.
addGame: function(gameInfo) {
var sHtml = window.JST["schedule/gamelist/inner_row"](gameInfo);
var tableRow = $("<tr>").append(sHtml);
tableRow.data("id", gameInfo.id);
return tableRow;
}
addNewTable: function(date, gameInfos, number) {
...
...
for (var ind = 0; ind < gameInfos.length; ++ind) {
gameInfo = gameInfos[ind].attributes;
var element = this.addGame(gameInfo);
$("#schedule_mytable_tbody" + number).append(element);
if (gameInfo.score1 > gameInfo.score2)
$("li:nth-child(5)").find("p:first-child").addClass("winner");
else
$("li:nth-child(5)").find("p:last-child").addClass("winner");
}
My template looks like this.
window.JST["schedule/gamelist/inner_row"] = _.template(
'<td width="668px">\
<ul>\
<li class="schedule_..."></li>\
<li class="schedule_..."></li>\
<li class="schedule_...."></li>\
<li class="schedule_..."></li>\
<li class="schedule_boxscore">\
<p><%=score1%></p>\
<p><%=score2%></p>\
</li>\
...
Rendered HTML part:
<li style="font-size:16px" class="schedule_boxscore">
<p class="winner">2</p>
<p class="winner">10</p>
</li>
So, because of the "for" loop, all paragraphs get the ".winner" class, not just <p class="winner">10</p>...

JQuery conditional click events

I have an unordered list of elements organized in rows. When a user clicks on a row, I want the text in the row to be appended into a separate text field. The issue with my current code is that I if the user clicks multiple boxes, all of the associated text with each of those boxes will be appended into the textfield. I would like to append the text from only the last row element that the user clicked.
Here is my javascript:
function clickEvents() {
// Day List Selector
$('#DC_id_1').click(function() {
$('#whenTextField').attr('value', 'Today');
});
$('#DC_id_3').click(function() {
$('#whenTextField').attr('value', 'Tomorrow');
});
$('#DC_id_5').click(function() {
$('#whenTextField').attr('value', 'Later');
});
// Time List Selector
$('#DC_id_37').click(function() {
var day = $('#whenTextField').attr('value');
$('#whenTextField').attr('value', day + ', Right Now');
});
$('#DC_id_39').click(function() {
var day = $('#whenTextField').attr('value');
$('#whenTextField').attr('value', day + ', Morning');
});
$('#DC_id_41').click(function() {
var day = $('#whenTextField').attr('value');
$('#whenTextField').attr('value', day + ', Midday');
});
$('#DC_id_43').click(function() {
var day = $('#whenTextField').attr('value');
$('#whenTextField').attr('value', day + ', Afternoon');
});
$('#DC_id_45').click(function() {
var day = $('#whenTextField').attr('value');
$('#whenTextField').attr('value', day + ', Evening');
});
}
Basically, I think I want to use an "if" statement to control the clicking in the Time List Selector elements list.
example:
if (DC_id_37 is clicked) {
append('text');
}
else if (DC_id_39 is clicked) {
append('some other text');
Here is the associated HTML:
<ul id="dayList">
<li id="DC_id_1">
Today
</li>
<li id="DC_id_3">
Tomorrow
</li>
<li id="DC_id_5">
Later
</li>
</ul>
<ul id="timeList">
<li id="DC_id_37">
Right Now
</li>
<li id="DC_id_39">
Morning
</li>
<li id="DC_id_41">
Midday
</li>
<li id="DC_id_43">
Afternoon
</li>
<li id="DC_id_45">
Evening
</li>
</ul>
<textField id="whenTextField">
*Note I just created this HTML by hand, as I'm building the web app in Dashcode, and its putting out some very ugly HTML
Actual HTML created by Dashcode:
<ul id="timeList">
<li>
<div id="foo"></div>
<div id="DC_id_37">Right Now</div>
<div></div>
</li>
<li>
<div id="foo2"></div>
<div id="DC_id_39"></div>
<div></div>
</li>
</ul>
Instead of simply appending the new value, you need to replace the old value. I would do it this way:
// Based on the HTML you posted, we only need two click functions.
$("#dayList > li > div:eq(1)").click(function() {
var newDate = $(this).text();
var currentValues = $("#whenTextField").attr("value").split(", ");
// Initialize this as an empty string in case the user has not yet selected
// a time.
var time = "";
// Get the previous time, if one has already been appended.
if (currentValues.length == 2) {
time = ", " + currentValues[1];
}
// Update the value of the text field.
$("#whenTextField").attr("value", newDate + time);
});
$("#timeList > li > div:eq(1)").click(function() {
// Get the current date value.
var date= $("#whenTextField").attr("value").split(", ")[0];
// Get the text from the 'li' element that raised the event.
var time = $(this).text();
// Update the value of the text field, keeping the previously selected date.
$("#whenTextField").attr("value", date + ", " + time);
});
This approach also saves the selected time if a user later changes the selected date.
Update:
I updated my example to match the second nested <div /> element under each <li /> using the :eq(n) selector (the selector is zero-based). This solution assumes that the target <div /> elements will always be the second one, based on your comment.
You have a few other options, too:
You could use the :parent selector to affect only <divs /> that contain text (or any child elements).
$("#timeList > li > div:parent").click(function() { });
Or, you could use the Attribute Starts With Selector to affect only <div /> elements with IDs that start with "DC_id_".
$("#timeList > li > div[id^='DC_id_']").click(function() { });
I'm not sure which solution performs the best. Personally, I would recommend going with the third option (the Attribute Starts With selector) since generated IDs are usually more predictable than an element's index or contents.
I believe this is what you're looking for:
$('#DC_id_37').click(function() {
var day = $('#whenTextField').attr('value').split(",")[0];
$('#whenTextField').attr('value', day + ', Right Now');
});
The .split(",")[0] will grab the first part of what's in the text box.
You could simplify your code with something like this:
$('#dayList li').click(function() {
$('#whenTextField').attr('value',$(this).text());
});
$("#timeList li").click(function() {
var day = $('#whenTextField').attr('value').split(",")[0];
$('#whenTextField').attr('value', day + ', '+$(this).text());
});
Roughly
html: (adjust to taste, prolly have to do some css)
<div class="appointment">
<div class="options">
<ul class="day">
<li>Today</li>
<li>Tomorrow</li>
<li>Later</li>
</ul>
<ul class="time">
<li>Right Now</li>
<li>Morning</li>
<li>Midday</li>
<li>Afternoon</li>
<li>Evening</li>
</ul>
</div>
<input class="when" type="text" />
</div>
jquery
$('.appointment').each(function(){
var self = $(this)
, whenField = self.find('.when')
, day = 'choose a day'
, time = 'choose a time';
self.delegate('.options li', 'click', function(e){
var li = $(this), ul = li.closest('ul');
if(ul.hasClass('day')) {
day = li.text();
} else if(ul.hasClass('time')) {
time = li.text();
}
whenField.val(day + ' - ' + time);
});
});
Reusable on a page, so you can have more appointment boxes on a page.
I personally avoid using element ID's like the plague, better abstract it and shorten code substantially if you generalize.

Categories