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>
Related
Is there a way to get the content of an ordered list item's number?
var list = document.getElementById('list');
list.style.listStyleType = 'upper-roman';
<ol class="list" id="list">
<li class="list__item">apple</li>
<li class="list__item">banana</li>
<li class="list__item" id="target">orange</li>
<li class="list__item">pear</li>
</ol>
That will produce a list of items like this.
I. apple
II. banana
III. orange
IV. pear
Is there a way to get the III string of text of the #target list item?
EDIT:
Roman characters here are just an example. I'd like the ability to access to the content provided by any of the list-style-type options.
The only way I can think of doing this is the following:
1) Get the index of the item (e.g. 3)
2) Have a function like this: https://stackoverflow.com/a/9083076/1324321
3) Run the index through the function
I created a jsfiddle here which can display the chosen selection to the user. Although javascript is not holding this as a string, I first find the index of the selected list item, then I recreate a list of that one item with the "start" attribute being set to that index.
Here is the HTML:
<ol>
<li>first</li>
<li>second</li>
<li id="active">third</li>
<li>Fourth</li>
</ol>
<br/>
<br/>
<div id='selected'>
</div>
And the JQuery:
$(document).ready(function() {
var intt = $('li').index($('#active')) + 1;
$('#selected').html('<ol start="' + intt + '"><li></li></ol>');
});
JSFIDDLE
You could use the start attribute and iterate all list elements.
var list = document.getElementById('list'),
start = list.start || 0;
list.style.listStyleType = 'upper-roman';
Array.prototype.forEach.call(list.getElementsByTagName('li'), function (a, i) {
if (a.id === 'target') {
console.log(i + start);
console.log(a.innerHTML);
}
});
<ol class="list" id="list" start="5">
<li class="list__item">apple</li>
<li class="list__item" >banana</li>
<li class="list__item" id="target">orange</li>
<li class="list__item">pear</li>
</ol>
I have a li tag that looks like :
<li class="item maybe" data-selected="1"></li>
<li class="item confused" data-selected="2"></li>
<li class="item why" data-selected="3"></li>
I then have three other elements that look like :
<li class="display"></li>
<li class="display"></li>
<li class="display"></li>
I'm trying to on select of one of the elements append the class to my 2nd list of elements.
So if I selected the li tag with data-selector="1" then my first li tag in my display list will have the class maybe added.
This is what i've tried so far, but i'm getting undefined when I console log my var out :
if ($('.item').attr('data-selected') == 1) {
var itemClassAdd = $('.item').find("[data-selected='1']").attr('class');
console.log(itemClassAdd);
$(".display").addClass(itemClassAdd);
}
Thanks!
I perform action in a click event for convenience:
$("li.item").click(function(){
var index = $(this).data("selected");
$("li.display").eq(index - 1).addClass($(this).attr("class"));
});
If you don't need class item:
$("li.item").click(function(){
var index = $(this).data("selected");
var c = $(this).attr("class").replace("item ", "");
$("li.display").eq(index - 1).addClass(c);
});
The class and the attribute are on the same element, so you use a compound selector, not find:
var itemClassAdd = $('.item[data-selected="1"]').attr('class');
But note that
if ($('.item').attr('data-selected') == 1) {
will only branch of the first element matching .item has data-selected="1", which may or may not be what you want.
Live Example:
if ($('.item').attr('data-selected') == 1) {
var itemClassAdd = $('.item[data-selected="1"]').attr('class');
$(".display").addClass(itemClassAdd);
}
.maybe {
color: green;
}
<ul>
<li class="item maybe" data-selected="1">maybe</li>
<li class="item confused" data-selected="2">confused</li>
<li class="item why" data-selected="3">why</li>
</ul>
<ul>
<li class="display">d1</li>
<li class="display">d2</li>
<li class="display">d3</li>
</ul>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Explaining by example:
<li data-owner="1"></li>
<li data-id="1"></li>
<li data-id="2"></li>
(insert here)
<li data-owner="2"></li>
I want to insert between data-owner="1" and data-owner="2", and insert last - just above data-owner="2".
This do my first requirement:
$('li[data-owner="1"]').after('<li data-id="3"></li>');
But this will insert here:
<li data-owner="1"></li>
(insert here)
<li data-id="1"></li>
<li data-id="2"></li>
<li data-owner="2"></li>
Is there a way to make it insert after one element, and then move down until it finds another element and insert before that? Or find the next element li[data-owner] after a specific element li[data-owner="2"] and insert before that?
I cannot use $('li[data-owner="2"]').before('<li data-id="3"></li>'); because I do not know the specific value of data-owner of the element I want to insert before.
You are going to want to find the next list element with a data id, and then place your insertion before that element. This can be done using nextAll. The example is broken out to show the steps clearer as opposed to being simply chained.
var owner = 1;
var currentOwner = $('li[data-owner='+owner+']');
var nextOwner = currentOwner.nextAll('li[data-owner]:first');
nextOwner.before('<li data-id="3">(insert: li data-id="3")</li>');
//1-liner: $('li[data-owner=1']').nextAll('li[data-owner]:first').before('<li data-id="3">(insert)</li>')
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<li data-owner="1">data-owner="1"</li>
<li data-id="1">data-id="1"</li>
<li data-id="2">data-id="2"</li>
<li data-owner="2">data-owner="2"</li>
Something along these lines?
<ul>
<li data-owner="1">a</li>
<li data-id="1">b</li>
<li data-id="2">c</li>
<li data-owner="2">d</li>
</ul>
javascript:
var owner_flag = 0;
jQuery("li[data-owner]").each(function(){
if(owner_flag == 1)
{
owner_flag == 0;
jQuery(this).before('<li data-id="3">next</li>');
}
if(jQuery(this).attr("data-owner") == "1")
{
owner_flag = 1;
}
});
https://jsfiddle.net/u7m5mkL6/
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 …?)