I have a classic root structure represented in HTML by ULs (and Lis ofc.). What I need to do is that when I click on any of my LI items (or in fact element as it contains only s) I want to get its text and text of its LIs parents.
Heres my sample of Unordered List (already wrapped):
<ul>
<li class='firstLevel'><a href='#'>1</a>
<ul>
<li><a href='#'>11</a>
</li>
<li><a href='#'>12</a>
</li>
</ul>
</li>
<li class='firstLevel'><a href='#'>2</a>
<ul>
<li><a href='#'>21</a>
<ul>
<li><a href='#'>211</a>
</li>
<li><a href='#'>212</a>
</li>
</ul>
</li>
<li><a href='#'>22</a>
<ul>
<li><a href='#'>221</a>
<ul>
<li><a href='#'>2211</a>
</li>
<li><a href='#'>2212</a>
</li>
</ul>
</li>
<li><a href='#'>222</a>
<ul>
<li><a href='#'>2221</a>
</li>
<li><a href='#'>2222</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li class='firstLevel'><a href='#'>3</a>
</li><br>
<li class='firstLevel'><a href='#'>4</a>
<ul>
<li><a href='#'>41</a>
</li>
<li><a href='#'>42</a>
</li>
</ul>
</li>
</ul>
For example when I click 3, I get only 3, 'cause it has no parent. But if I click on 2222 element, I should get a 2222, 222, 22 and 2 output.
My thoughts were: First, I should get a level of ULs clicked, I've done this with this code:
$("li").click(function (e) {
var cnt = $(e.target).parents('ul').length;
});
In a cnt variable is stored level of plunge - so when I click on 2222 element, cnt = 4. (4 because root alredy returns a 1, not a zero.)
Next step is to get the texts of this parent elements. I tried to approach this elements with jQuerys .eq() function but it doesn't work correctly. Heres my test:
$("li").click(function (e) {
var cnt = $(e.target).parents('ul').length;
vat outputString = '';
cnt--;
for (var i = 0; i < cnt; i++) {
outputString += $(e.target).parents('li').eq(cnt).text();
outputString += '###'; //texts separator
}
});
But this approach returns all of LI item text (including his descendants) so it returns whole tree-text structure.
To summarize my question - What functions/how should I continue to get the desired output?
Heres a jsfiddle example: http://jsfiddle.net/F548m/1/
.text will always return the combined text of all descendants of an element. What you want seems to be the text of the a child of li element, not the text of the li element itself.
So with this in mind, this should do what you want:
$("li").click(function (e) {
var text = $(this).parents('li').map(function() {
return $(this).children('a').text());
}).get();
text.unshift($(this).children('a').text();
var outputString = text.join('###');
});
DEMO
This gets all the li ancestors of the clicked element and maps them to their a children's text value. Then it adds the text value of the clicked elements a children to the begnning of the array. The order of the values is from the clicked element up to root.
If you want it the other way round, i.e. the clicked element's value last, the code actually becomes a bit simpler:
$("li").click(function (e) {
var text = $(this).parents('li').addBack().map(function() {
return $(this).children('a').text());
}).get();
var outputString = text.join('###');
});
I think .parents() can help you
Check demo
You need to get the text of the a
var str=$(this).find("a:first").text();
$.each($(this).parents("li"), function (i, v) {
str+=", "+$(this).find("a:first").text();
});
alert(str);
e.stopPropagation();
http://jsfiddle.net/F548m/3/
you should use jQuery.closest() function.
Related
I'm wondering if anyone could explain to me what is going on here?
var testNo = $("#test").text()
$("ul li a").click(function() {
$("#test").text($(this).text());
console.log(testNo)
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li>
1
<ul>
<li>
<a href="#" >1</a>
</li>
<li>
2
</li>
<li>
<a href="#" >3</a>
</li>
</ul>
</li>
</ul>
It is strange to me why this wouldn't update the list item as you click using this method. However, if you change the javascript to;
$("ul li a").click(function() {
var testNo = $("#test").text()
$("#test").text($(this).text());
console.log(testNo)
})
It shows the value as one behind, but set the following as;
$("ul li a").click(function() {
$("#test").text($(this).text());
var testNo = $("#test").text()
console.log(testNo)
})
and it has the desired behaviour.
Is there a way to achieve what this last code snippet gives me, but by using the first code format?
Let me know something doesn't make sense,
thanks
You need to update the testNo variable text, then only it will show the latest text. Try:
var testNo = $("#test").text()
$("ul li a").click(function() {
testNo = $(this).text();
$("#test").text($(this).text());
console.log(testNo)
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<ul>
<li>
1
<ul>
<li>
<a href="#" >1</a>
</li>
<li>
2
</li>
<li>
<a href="#" >3</a>
</li>
</ul>
</li>
</ul>
It's all about when you get the value of the $('#test').text() -- because you then go on to change the source value.
Your code does two things: (1) It reads #test and stores its value as testNo, and (2) it gets the text from the clicked anchor tag and puts that value into #test. So two things change: the var testNo and #test.
If you save the value of #test as var testNo, then change the value of #test before displaying testNo, they will have different values.
I think what you want to do is this:
$("ul li a").click(function() {
var testNo = $(this).text()
$("#test").text(testNo);
console.log(testNo)
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li>
1
<ul>
<li>
<a href="#" >1</a>
</li>
<li>
2
</li>
<li>
<a href="#" >3</a>
</li>
</ul>
</li>
</ul>
In your first code testNo is set globally and take the value at page load in the second and 3th it is set locally so the value changes at each click event ,
In the second the value is incremented before the text is change so it captures the previous altered value, the 3th the value is saved after the text is saved so testNo is display the updated value
Is it possible to get the .nextUntil() to work on split lists, or get the same functionality?
So I am trying to implement the ever so popular shift select for my items, and since they are ordered in a list in my application I want to be able to select across <ul> borders.
I have the following set of DOM elements:
<ul class="current">
<li class="item">first</li>
<li class="item clicked">second</li>
<li class="item">third</li>
<li class="item">fourth</li>
</ul>
<ul class="later">
<li class="item">fifth</li>
<li class="item selected">sixth</li>
<li class="item">seventh</li>
</ul>
And using something like this:
$('li.clicked').nextUntil('li.selected');
I'd like a list containing the following elements
[ <li class="item">third</li>,
<li class="item">fourth</li>,
<li class="item">fifth</li> ]
However all I get is the elements leading up to the split </ul>. Is there any way of doing this? I have also tried to first selecting all items with $('.item')and then using .nextUntil() on them without any luck.
Is this what you are looking for?
$('li').slice($('li').index($('.clicked'))+1,$('li').index($('.selected')));
For reference
Jquery.Index
Jquery.Slice
Edit
So if you do
$('li')
you will get an array of all elements 'li' getting:
[<li class="item">first</li>,
<li class="item clicked">second</li>,
<li class="item">third</li>,
<li class="item">fourth</li>,
<li class="item">fifth</li>,
<li class="item selected">sixth</li>,
<li class="item">seventh</li>]
Since it is an array you can slice him to get an sub array you just need two positions, where to start and here to finish.
//start
$('li').index($('.clicked'))+1 // +1 because you dont want to select him self
//end
$('li').index($('.selected'))
For better preformance you should before create an array with all li so it will not search all dom 3 times for the array of 'li'
var array = $('li');
var subarray = array.slice(array.index($('.clicked'))+1,array.index($('.selected')));
Assuming these lists cannot be merged into one, it is impossible using the nextUntil method. This is because of how jQuery performs traversing. According to the documentation,
Get all following siblings of each element up to but not including the element matched by the selector, DOM node, or jQuery object passed.
fifth is not a sibling of the clicked element, but rather it is a child of the sibling of the element's parents.
I came up with two possible solutions.
Solution 1: Combine NEXT and PREV traversals
Assuming that .clicked is always in the first list and .selected is always in the second list, combining prevAll() with nextAll() should do the trick. This assumes that the order is the same.
var siblings = $("li.clicked").nextAll()
Get all siblings of the current element AFTER the element itself.
var distantSiblings = $("li.selected").prevAll();
Get all distant siblings after the first element, but before the second one.
siblings.push(distantSiblings);
Combine them into two and then iterate over each element.
var siblings = $("li.clicked").nextAll()
var distantSiblings = $("li.selected").prevAll();
siblings.push(distantSiblings);
siblings.each(function() {
$(this).addClass("blue");
});
.blue { color: blue; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="current">
<li class="item">first</li>
<li class="item clicked">second</li>
<li class="item">third</li>
<li class="item">fourth</li>
</ul>
<ul class="later">
<li class="item">fifth</li>
<li class="item selected">sixth</li>
<li class="item">seventh</li>
</ul>
http://jsfiddle.net/r15z10o4/
Note:
You will notice that the above code works, however it might not be the optimal solution. This is only confirmed to work for your example above. There may also be a less verbose solution.
Solution 2 (Find index of all list items)
Another idea is to find the index of all items, and collect the elements that are sandwiched between those two indices. You will then want to use the 'slice' selector to get the range in between.
var items = $(".item");
var clicked = $(".clicked");
var selected = $(".selected");
var clickIndex = items.index(clicked);
var selectIndex = items.index(selected);
$("li").slice(clickIndex + 1, selectIndex).addClass("blue");
var clicked = $(".clicked");
var selected = $(".selected");
var clickIndex = $("li").index(clicked);
var selectIndex = $("li").index(selected);
$("li").slice(clickIndex+1, selectIndex).addClass("blue");
.blue { color: blue; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="current">
<li class="item">first</li>
<li class="item clicked">second</li>
<li class="item">third</li>
<li class="item">fourth</li>
</ul>
<ul class="later">
<li class="item">fifth</li>
<li class="item selected">sixth</li>
<li class="item">seventh</li>
</ul>
You can do it manually by selecting all these items at once, and using loops.
Consider the parent element, let's say "container":
<div id="container">
<ul class="current">
<li class="item">first</li>
<li class="item clicked">second</li>
<li class="item">third</li>
<li class="item">fourth</li>
</ul>
<ul class="later">
<li class="item">fifth</li>
<li class="item selected">sixth</li>
<li class="item">seventh</li>
</ul>
</div>
Now, you can select all these items:
var $items = $("#container > ul > li.item"); // or simply $("#container .item");
And iterate through them:
var $items = $(".item"), $result = $(), found = false;
for (var i = 0; i < $items.length; i++)
{
$currentItem = $items.eq(i);
if ($currentItem.is('.clicked')) {
found = true;
continue;
}
if ($currentItem.is('.selected'))
break;
if (found)
$result = $result.add($currentItem);
}
console.log($result);
Here is the working JSFiddle demo.
In any case it feels like you will need to define groups of li.
I think the easiest is to create a function getting a list of lis that you can request any way you want then to filter the el you are interested in.
function elRange(elList, start, end){
// we do not use indexOf directly as elList is likely to be a node list
// and not an array.
var startI = [].indexOf.call(elList, start);
var endI = [].indexOf.call(elList, end);
return [].slice.call(elList, startI, endI + 1);
}
// request the group of *ordered* elements that can be selected
var liList = document.querySelectorAll('ul.current > li, ul.later > li');
var selectionEnd = document.querySelector('.selected');
[].forEach.call(liList, function(li){
li.addEventListener('click', function(){
var selected = elRange(liList, li, selectionEnd);
selected.forEach(function(el){
el.classList.add('red');
})
});
});
.selected {
color: blue;
}
.red {
color: red;
}
<ul class="current">
<li class="item">first</li>
<li class="item">second</li>
<li class="item">third</li>
<li class="item">fourth</li>
</ul>
<ul class="later">
<li class="item">fifth</li>
<li class="item selected">sixth</li>
<li class="item">seventh</li>
</ul>
I have the following list:
<ul>
<li class="item">One</li>
<li class="item">Two</li>
<li class="item">Three
<ul>
<li class="item">Something Original</li>
<li class="item selected">Something</li>
</ul>
</li>
<li>Four
<ul>
<li class="item">I want this selected next</li>
<li class="item">Good</li>
</ul>
</li>
</ul>
Using jQuery, how do I find the next li with the class="item" since it is wrapped in a different container. Obviously I cannot do $(".selected").next(".item") so how else can I do it?
JSFiddle: http://jsfiddle.net/q3f6v7zz/
Since the li elements are nested and you know that you want the next appearing li with a particular class, you can use .index() and do something like this
var $li = $('.item'); // <--- get the list of all lis with class .item
var index = $li.index($('.selected')); // <--- find the index of the one with .selected amongst all the lis
console.log($li.eq(index+1).html()); // <--- index+1 because you need the next appearing li after selected
If you want to move the selected class on keydown something like this should do
var $li = $('.item');
$(document).on('keydown', function(e) {
if (e.keyCode == 40) {
var index = $li.index($('.selected'));
$li.eq(index).removeClass('selected');
index = (index+1) % $li.length; // <--- to rotate the values from 0 to count of li.item elements
$li.eq(index).addClass('selected');
}
});
var $li = $('.item');
$(document).on('keydown', function(e) {
if (e.keyCode == 40) {
var index = $li.index($('.selected'));
$li.eq(index).removeClass('selected');
index = (index+1) % $li.length;
$li.eq(index).addClass('selected');
}
});
.selected {
background: green;
color: white;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<ul>
<li class="item">One</li>
<li class="item">Two</li>
<li>Three
<ul>
<li class="item">Something</li>
<li class="item selected">Something Else</li>
</ul>
</li>
<li>Four
<ul>
<li class="item">I want this selected next</li>
<li class="item">Good</li>
</ul>
</li>
</ul>
You can get the index of the selected element within all lis, and then increment that index to get the next one.
$("ul").on("click", "li.item.selected", function() {
var all_li = $("li.item");
var selected_index = all_li.index(this);
var next_li = all_li.eq((selected_index + 1) % all_li.length);
$(this).removeClass("selected");
next_li.addClass("selected");
});
.item.selected {
background-color: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li class="item">One</li>
<li class="item">Two</li>
<li class="item">Three
<ul>
<li class="item selected">Something</li>
</ul>
</li>
<li>Four
<ul>
<li class="item">I want this selected next</li>
<li class="item">Good</li>
</ul>
</li>
</ul>
I used the modulus so it will wrap around at the end.
Not sure what you are exactly looking for but you can use $(Element").parent().parent().find("li");
So in other words .parent() may be what you are looking for there is also .sibling() to find or you may want $('li').closest('ul').find('li')
which will go up the tree to find the nearest ul to the one you are looking for
https://api.jquery.com/closest/
You may also use:
Vanilla JS to do something similar to what was discussed by others with $index if it makes more sense to you:
Again this isn't as efficient but that is basically what JQuery is doing:
var myLis = document.getElementsByTagName('li');
var wantedIndex;
for(var i = 0;i<myLis.length; i++){
if(myLis[i].className === "active"){
wantedIndex = i+1; //gets the li which is next when selecting all lis
}
}
Please take a look at this FIDDLE. I have two pairs of unordered lists, each of which is inside a div element.pricing-table. The following code can find the li with the same classes, get the max height and set the height of all of them to the same. But I want to limit it to getting the max-height of each pair of lists inside each div element.
I think this line is giving me problem because it is getting all the lists with the same classes in the document:
var elems = $('.pricing-table ul li.' + elem.className),
I don't think I can use $(this) and update it like $(this +elem.className). Any suggestions?
Jquery script:
$(document).ready( function(){
$('.pricing-table ul li').each(function(i, elem) {
var elems = $('.pricing-table ul li.' + elem.className),
heights = $.map(elems, function(li) {
return $(li).height();
}),
max = Math.max.apply(null, heights);
elems.height(max);
});
});
HTML
<div class="pricing-table">
<ul>
<li class="heading">Bronze</li>
<li class="year">2003<p>(Text)..........</li>
<li class="package">Starter package</li>
<li class="location">Africa (Text).......)</li>
<li class="description">Text............ </li>
</ul>
<ul class="feature">
<li class="heading">Silver</li>
<li class="year">2004</li>
<li class="package">Intermediate package</li>
<li class="location">Asia</li>
<li class="description">Text............ </li>
</ul>
</div>
<div class="pricing-table">
<ul>
<li class="heading">Bronze</li>
<li class="year">2003<p>(Text)..........</li>
<li class="package">Starter package</li>
<li class="location">Africa (Text).......)</li>
<li class="description">Text............ </li>
</ul>
<ul class="feature">
<li class="heading">Silver</li>
<li class="year">2004</li>
<li class="package">Intermediate package</li>
<li class="location">Asia</li>
<li class="description">Text............ </li>
</ul>
</div>
You’d need to get only the li that are descendants of your current .pricing-table element, so you’ll have to iterate over the latter first:
$(document).ready(function () {
$('.pricing-table').each(function (i, e) {
$(e).find('ul li').each(function (i, elem) {
var elems = $(e).find('ul li.' + elem.className),
heights = $.map(elems, function (li) {
return $(li).height();
}),
max = Math.max.apply(null, heights);
elems.height(max);
});
});
});
… or something like that. http://jsfiddle.net/p3sfy/3867/
(Still kinda ugly, since it will iterate over the li multiple times, so that’s rather just a “quick fix” – but I don’t wanna think about anything more sophisticated here before I have not first heard a convincing argument why this data is not marked up using tables in the first place …?)
i have this following html structure usilg ul and li.
<ul class="treeview" id="productTree">
<li class="collapsable lastCollapsable">
<div class="hitarea collapsable-hitarea lastCollapsable-hitarea"></div>
<span id="top1" class="">top1</span>
<ul>
<li class="collapsable lastCollapsable">
<span class="">mod1</span>
<ul>
<li class="last">
<span>bottom1</span>
</li>
</ul>
</li>
</ul>
</li>
<li class="collapsable lastCollapsable">
<span id="top2" class="">top2</span>
<ul>
<li class="collapsable lastCollapsable">
<span class="">mid2</span>
<ul>
<li class="last">
<span>bottom2</span>
</li>
</ul>
</li>
</ul>
</li>
</ul>
the website allows user to add more data under this structure and am using jquery treeview to show the tree structure dynamically.
Now i need to save this whole ul-li structure into a js object for future use in the website. how do i achieve this? the last node("bottom1 and bottom2 here") has a class "last" if that helps.
as we can add data dynamically we can be sure how much levels of ul li is there at the end when user clicks "save"
You can use recursive function to save a tree object;
function save(obj_ul, tree){
var obj_lis = obj_ul.find("li")
if (obj_lis.length == 0) return;
obj_lis.each(function(){
var $this = $(this);
if($this.parent("ul").get(0) == obj_ul.get(0))
{
tree.push({
name : $this.find('> span').text(),
child : save($this.find("ul").first(), [])
});
}
});
return tree;
}
console.log(save($('#productTree'), []));
If you want to reprouce the same thing verbatim, as a string of HTML elsewhere on the site, you could just do this? Then .append() or .prepend() treeview where you like.
var treeview = $('#productTree').parent().html()
Assuming you want JSON:
function save(){
var tmp = [];
$('#productTree li.collapsable').each(function(){
var $this = $(this),
$spans = $this.find('span'),
o = [];
$spans.each(function(){
o.push($(this).text())
})
tmp.push(o);
});
return tmp;
}
You could also use map() to accomplish the same thing, too.
EDIT: Updated, assuming your text will live inside a span. This will create an array of arrays, each containing the text from the spans inside each of your list-items.