jquery sorting function and prepend/insertAfter? - javascript

I have a function to sort list items based on the date and class of the list item. It's late and I am tired and frustrated, so not sure what I am missing here.
The function should first determine if the class is .pinned and sort them by date and insert them to the top of the list.
Then it should look at the date of the item (found in data-date) and if it is earlier than today, add the class .past sort them by date, and then insert them after the pinned items.
Lastly, it should sort the remaining items and insert them after the last item with the class of .past
here is the code:
//sort todo list-items by type and then date
function sortTodo() {
var itemDate = "";
var date = new Date();
$(".todo-list-item").each(function() {
var itemDate = new Date($(this).data("date"));
if($(this).hasClass('pinned')) {
$(".todo-list-item.pinned").sort(function(a,b) {
return date > itemDate;
}).each(function() {
$(".todo-list").prepend(this);
})
} else if(date > itemDate) {
$(this).addClass("past");
$(".todo-list-item.past").sort(function(a,b) {
return date > itemDate;
}).each(function() {
$(".todo-list").prepend(this);
})
} else {
$(".todo-list-item").not(".pinned, .past").sort(function(a,b) {
return date > itemDate;
}).each(function() {
$(".todo-list").append(this);
})
}
});
}
$(document).ready(function() {
sortTodo();
});
CODE PEN DEMO
as of now, the script not placing pinned items to the top as desired, unless I add a new pinned item, and is not sorting by date after an items class changes or is removed.

The reason that the pinned item is not at the top of your list of todos on first page load is because it is the first item in your list to get prepended to the todo list.
When the page loads, the first item is pinned so it gets prepended to the list. Then it keeps looping through the list until it finds the items whose date > itemDate and puts it before the pinned item.
To see what I mean: try changing your html by putting the pinned item as the last item in your unordered list.
*Sorry - I don't have enough rep to leave a comment...

Your code is sorting and moving elements in three separate steps once per item (!). This messes up the order of the overall result.
Just sort once and append different sections of the sorted list one after another (jQuery .filter() is useful for this). When you're through, all items will have been re-ordered.
Note about Array#sort: It does not work when you return true or false. It expects you return numbers less than, equal to or greater than 0. Luckily, subtracting dates from one another gives you exactly these kinds of numbers.
The following sorts all items across all lists on the page.
// helper
function appendToParent() { this.parentNode.appendChild(this); }
$(function() {
var $items = $(".todo-list-item").sort(function(a, b) {
return new Date($(a).data("date")) - new Date($(b).data("date"));
});
// append items by priority
$items.filter(".pinned").each(appendToParent);
$items.filter(".pinned.past").each(appendToParent);
$items.filter(":not(.pinned)").each(appendToParent);
$items.filter(":not(.pinned).past").each(appendToParent);
});
.past { font-style: italic; }
.pinned { color: red; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
List 1
<ul class="todo-list">
<li class="todo-list-item past" data-date="2017-05-03">Past Item 5</li>
<li class="todo-list-item" data-date="2017-06-04">Item 4</li>
<li class="todo-list-item pinned" data-date="2017-06-02">Item 1 (pinned)</li>
<li class="todo-list-item" data-date="2017-06-03">Item 3</li>
<li class="todo-list-item pinned past" data-date="2017-05-03">Past Item 2 (Pinned)</li>
</ul>
List 2
<ul class="todo-list">
<li class="todo-list-item past" data-date="2017-05-03">Past Item 5</li>
<li class="todo-list-item" data-date="2017-06-04">Item 4</li>
<li class="todo-list-item pinned" data-date="2017-06-02">Item 1 (pinned)</li>
<li class="todo-list-item" data-date="2017-06-03">Item 3</li>
<li class="todo-list-item pinned past" data-date="2017-05-03">Past Item 2 (Pinned)</li>
</ul>
Toggling the .past class is easy, too, but I've left it out of the sample code above because at some point in the future all items here would end up being "past", which would defy the purpose of the sample code.
$items.each(function () {
$(this).toggleClass("past", new Date( $(this).data("date") ) < Date.now());
});

Related

Shift list element order in several <ul> at once if one of <li> changes position

I am using drag and drop functionality to allow users to order elements on a page. I have several <ul> elements with <li> elements inside of them (all <ul> contain 3 <li> elements) where each unordered list corresponds to a month, so
<ul class="dragable" id="month-june">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<ul class="dragable" id="month-july">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<ul class="dragable" id="month-august">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<!-- etc.. -->
I want to somehow sort all of the lists once .drop() event occurs (basically users drop item in place after dragging it). I'm changing list positions in dom so they are always ordered there, for example if Item 3 from august is moved between item 1 and item 2 in july it will look like this:
<ul class="dragable" id="month-july">
<li>Item 1</li>
<li>Item 3</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
Now I need to figure out how to push Item 3 from july down to augusts unordered list and also push down all other items after it. This should have vice versa effects if for example Item 1 from june is draged into july between item 2 and item 3, in this case everything above it should shift left. Therefore I need to have 3 items in all list at any given time.
Here is image further showing it, that hopefully explains it better: (Consider middle section as initial and than arrows show where item is dragged and what happens to lists before and after it depending on position)
Could this be done without using ids or classes, but relying on next and previous elements (unordered lists), as I don't know exactly what months follow what in this case.
Here is very simple js fiddle with drag and drop behaviour: DEMO
Updates on Drop
$(".sortable-queue").sortable({
revert: true,
scroll: false,
connectWith: ".connected-sortable",
placeholder: "item-dragable-placeholder",
receive: function (event, ui) {
var target = $(event.target).parent().parent();
var source = ui.sender.parent().parent();
var children = target.parent().children();
var start = children.index(source);
var end = children.index(target);
// bubble up?
if (start < end) {
for (var i = start; i < end; i++) {
$(children[i]).find("ul").append($(children[i + 1]).find("li").first().detach())
}
}
// bubble down?
else if (start > end) {
for (var i = start; i > end; i--) {
$(children[i]).find("ul").prepend($(children[i - 1]).find("li").last().detach())
}
}
// same pulldown
else
return;
}
}).disableSelection();
All it does is identify the div wrappers for the source and target uls. It then uses this to figure out if the target ul is above or below the source ul.
If it is below, it steps through all the wrappers from source to target, grabbing the first li from the succeeding wrapper and adding it to its end.
If it is above, the same thing happens, the only difference being it's picked from the end and added to the start.
If the source and target wrapper is the same, we don't need to do anything.
Fiddle - https://jsfiddle.net/ajdw2u0b/
Updates When Dragging
var source;
$(".sortable-queue").sortable({
revert: true,
scroll: false,
connectWith: ".connected-sortable",
placeholder: "item-dragable-placeholder",
start: function(event, ui) {
source = ui.item.parent().parent().parent();
},
over: function (event, ui) {
var target = $(event.target).parent().parent();
var children = target.parent().children();
var start = children.index(source);
var end = children.index(target);
// same pulldown
if (start === end) {
console.log(start)
return;
}
// bubble up?
else if (start < end) {
for (var i = start; i < end; i++) {
$(children[i]).find("ul").append($(children[i + 1]).find("li:not(.ui-sortable-helper):not(.item-dragable-placeholder)").first().detach())
}
}
// bubble down?
else if (start > end) {
for (var i = start; i > end; i--) {
$(children[i]).find("ul").prepend($(children[i - 1]).find("li:not(.ui-sortable-helper):not(.item-dragable-placeholder)").last().detach())
}
}
source = target;
}
}).disableSelection();
The logic is pretty much the same. The only differences are that
You do the updates on over (when you drag your item over a drop target).
You have to maintain the source wrapper value instead of getting it from the ui object, since you'll be changing it once you hover over a drop target.
Note that you have to exclude the element being dragged and the place holder when picking an element to detach.
Fiddle - https://jsfiddle.net/f4655x9n/

jQuery Sortable: storing values in an multipledimensional array

I have a sortable list (jQuery UI) formatted as something like this:
<ul class="baseList">
<li id="3">Item 1
<ul class="childList">
<li id="68">Child 1 of Item 1</li>
<li id="69">Child 2 of Item 1</li>
<li id="70">Child 3 of Item 1</li>
</ul>
</li>
<li id="8">Item 2
<ul class="childList">
<li id="81">Child 1 of Item 2</li>
<li id="83">Child 2 of Item 2</li>
</ul>
</li>
</ul>
What I am trying to achieve is to get an array variable that consists out of something like this:
var entireList = [];
entireList = [[3,[68, 69, 70], 8, [81, 83]]]
So I can post that variable to PHP to process it in the database.
I cant seem to figure out how I can solve this in javascript. What I have so far is the following:
var childList = $('.childList, .baseList').sortable({
placeholder: "ui-state-highlight",
opacity: 0.6,
update: function(event, ui){
var childArray = $('.childList').sortable('toArray');
var parentsArray = $('.baseList').sortable('toArray');
for(p in parentsArray)
{
postChildData[parentsArray[p]] = childArray;
}
console.log(postChildData);
}
});
Now that works for halve, my result is:
[3: Array[3], 8: Array[3]]
Comes down to th point that it only takes the children of the first <li> element.
Can someone here help me to get an array like the one i've written above (entireList)?
Instead of using childArray in your for loop use this:
$('#' + parentsArray[p] + ' > ul').sortable('toArray');
it may not be proper css, but give the second list a different class name, i gave it childList2 in this example. If you do not want to modify the ul you could bump the differentiation up a level to the parent li as well
var childArray = [];
var childArray2 = [];
$.each(childList, function(children) {
childArray.push(children);
});
$.each(childList2, function(children2) {
childArray2.push(children2);
});
If that doesnt work I should stop guessing without a way to test it on my own. You
Ok, I finally found the solution:
I needed to create a new Array, within there I needed to store the Parent ID and also the array with childs.
The way to do that is replace the for loop with the following:
for(p in parentsArray)
{
postListData[p] = Array(parentsArray[p], $('#' + parentsArray[p] + ' > ul').sortable('toArray'));
}

jquery sortable: sorted item triggers reordering

I have a list of items that represent div layers. When I sort one of these list items, I want their respective div layers to be sorted aswell.
list: these items are sortable
<ul id="sortable">
<li id="1">Div 1</li>
<li id="2">Div 2</li>
<li id="3">Div 3</li>
</ul>
div layers: these divs will be reordered
<div id="div_container">
<div id="div1">Div 1 item</div>
<div id="div2">Div 2 item</div>
<div id="div3">Div 3 item</div>
</div>
example: when li#1 moves to the second place, then div#1 goes to the second position automatically
init
$('#sortable').sortable();
This code could be what you want if I got what you are asking for:
http://jsfiddle.net/NsawH/84/
var indexBefore = -1;
function getIndex(itm, list) {
var i;
for (i = 0; i < list.length; i++) {
if (itm[0] === list[i]) break;
}
return i >= list.length ? -1 : i;
}
$('#sortable').sortable({
start: function(event, ui) {
indexBefore = getIndex(ui.item, $('#sortable li'));
},
stop: function(event, ui) {
var indexAfter = getIndex(ui.item,$("#sortable li"));
if (indexBefore==indexAfter) return;
if (indexBefore<indexAfter) {
$($("#div_container div")[indexBefore]).insertAfter(
$($("#div_container div")[indexAfter]));
}
else {
$($("#div_container div")[indexBefore]).insertBefore(
$($("#div_container div")[indexAfter]));
}
}
});
This code is portable since it does not use element ID's, however you should parametrize the sortable selector to be able to use them on any two lists eg. if you are binding to the sortable after init.
The code is jQuery dom modification friendly since it uses selector indexes and not node dom indexes. You will see on JSFiddle that i made the div_container a sortable , and it syncs back to the list.
Bind the change event of the sortable (if you want real-time updates) or stop (to just read off the end state), and manually reorder the divs accordingly.

detecting the last list item in jquery/javascript

I have a set of tab (main-tabs) on a website and each tab has another set of tabs (sub-tabs).
I want to use arrow keys on a keyboard to navigate the tabs, instead of a mouse.
These tabs are just HTML list items <li>
When I reach the last sub-tab with the arrow key, I want it to go back to the next main tab so it can display its own sub-tabs, and carry on the navigation inside it.
My question is, how can I detect, in jQuery/javascript, when I've reached the last list item (tab) using the arrow keys i.e. the right arrow key?
Many Thanks
You might be able to use either the :last or :last-child selectors in jQuery. Depending on how your <li> tags are nested, you might also have to use the children() function along with it.
For example, let's say you have the following markup:
<ul id="nav">
<li>List item</li>
<li>List item with sub items
<ul>
<li>Sub list item</li>
</ul>
</li>
</ul>
This would select the last top-level <li>
$('ul#nav > li:last').css('border', '1px solid red');
This would select the last <li> traversing the DOM downward. In this case it's the <li> with text "Sub list item"
$('ul#nav li:last').css('border', '1px solid red');
This would select any <li> tags that are the last child of their parent
$('ul#nav li:last-child').css('border', '1px solid red');
var maintabs = $('.maintab'),
active_maintab_eq = 0,
active_subtab_number = 1;
$(document).keyup( function(e){
if (e.which == 39) {
// right arrow key pressed
if ( active_subtab_number == maintabs.eq(active_maintab_eq).find('li').length ) {
// go to next main-tab
// and reset active sub-tab counter
++active_maintab_eq;
active_subtab_number = 1;
} else {
++active_subtab_number;
}
}
});
Some thing like this, I guess.
You can use .length to find out if a jQuery selector found anything:
var nextSubTab = $(currentSubTab).next("li");
if (nextSubTab.length == 0) {
// oops, end of this tab, switch to next tab
}

using '.each' method: how do I get the indexes of multiple ordered lists to each begin at [0]?

I've got multiple divs, each with an ordered list (various lengths). I'm using jquery to add a class to each list item according to its index (for the purpose of columnizing portions of each list). What I have so far ...
<script type="text/javascript">
/* Objective: columnize list items from a single ul or ol in a pre-determined number of columns
1. get the index of each list item
2. assign column class according to li's index
*/
$(document).ready(function() {
$('ol li').each(function(index){
// assign class according to li's index ... index = li number -1: 1-6 = 0-5; 7-12 = 6-11, etc.
if ( index <= 5 ) {
$(this).addClass('column-1');
}
if ( index > 5 && index < 12 ) {
$(this).addClass('column-2');
}
if ( index > 11 ) {
$(this).addClass('column-3');
}
// add another class to the first list item in each column
$('ol li').filter(function(index) {
return index != 0 && index % 6 == 0;
}).addClass('reset');
}); // closes li .each func
}); // closes doc.ready.func
</script>
... succeeds if there's only one list; when there are additional lists, the last column class ('column-3') is added to all remaining list items on the page. In other words, the script is presently indexing continuously through all subsequent lists/list items, rather than being re-set to [0] for each ordered list.
Can someone please show me the proper method/syntax to correct/amend this, so that the script addresses/indexes each ordered list anew?
many thanks in advance.
shecky
p.s. the markup is pretty straight-up:
<div class="tertiary">
<h1>header</h1>
<ol>
<li>a link</li>
<li>a link</li>
<li>a link</li>
</ol>
</div><!-- END div class="tertiary" -->
This will iterate over each OL, but once at a time:
// loop over each <ol>
$('ol').each(function(olIndex){
// loop over each <li> within the given <ol> ("this")
$(this).find('li').each(function(liIndex){
// do your <li> thing here with `liIndex` as your counter
});
});
As for all that stuff in the middle, you might be able to improve it with some nicer selectors:
$('ol').each(function(){
$(this).find('li')
.filter(':lt(6)').addClass('column-1') // <li> 1-5
.filter(':first').addClass('reset').end().end() // <li> 1
.filter(':gt(5):lt(12)').addClass('column-2') // <li> 6-11
.filter(':first').addClass('reset').end().end() // <li> 6
.filter(':gt(11)').addClass('column-3') // <li> 12+
.filter(':first').addClass('reset'); // <li> 12
});
Of course if we're making columns here, maybe we should be getting these counts dynamically?
$('ol').each(function(){
var $lis = $(this).find('li');
var len = $lis.size();
var colLen = Math.ceil(count / 3);
// and so on with the filter stuff with
});
$('ol').each(function(){
$(this).find('li').each(function(index){
// assign class according to li's index ... index = li number -1: 1-6 = 0-5; 7-12 = 6-11, etc.
if ( index <= 5 ) {
$(this).addClass('column-1');
}
if ( index > 5 && index < 12 ) {
$(this).addClass('column-2');
}
if ( index > 11 ) {
$(this).addClass('column-3');
}
}).filter(function(index) {
return index != 0 && index % 6 == 0;
}).addClass('reset'); // Closes li each and filter
}); // closes ol each

Categories

Resources