jquery sortable: sorted item triggers reordering - javascript

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.

Related

materialize collapsible: how to determine which section is open?

I have a materialize collapsible which works as expected. Something similar to:
<ul class="collapsible">
<li>
<div class="collapsible-header">Title1</div>
<div class="collapsible-body" />
</li>
<li>
<div class="collapsible-header">Title2</div>
<div class="collapsible-body" />
</li>
</ul>
In a later process, when pressing a button I need a javascript function to modify its behavior depending on which section is open.
How can I determine which section is open?
I guess one possibility would be to store in a hidden element the index of the section when it is selected but I don't know how to do it.
Materializecss add an active class to an open collapsible item by itself. So you can use it to understand which collapsible item is open.
You can use this jquery code :
$(document).on("click","ul.collapsible li", function(){
var elem = document.querySelectorAll("ul.collapsible li");
var index = "none"
for (var i = 0; i < elem.length; i++) {
if (elem[i].className == "active") {
index = i;
}
document.getElementById("show").innerHTML = index;
}
})
This code show index of opened collapsible item for you.
Here is complete example : jsFiddle

jquery sorting function and prepend/insertAfter?

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());
});

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/

How to append <ul><li> element in between list of parent elements for some specific condition

I have a list of elements where i want to show max 5 elements and add show more button if total elements is more than 5. Show/Hide part is done but I am stuck to customize this list using jquery.
For Example here is a list of brands which having total 13 items.
<ul class="search-filter" id="attributeLevel1Facet">
<li>brand1</li>
<li>brand2</li>
<li>brand3</li>
<li>brand4</li>
<li>brand5</li>
<li>brand6</li>
<li>brand7</li>
<li>brand8</li>
<li>brand9</li>
<li>brand10</li>
<li>brand11</li>
<li>brand12</li>
<li>brand13</li>
</ul>
I want to make this list like this using jquery only if total item is more than 5
<ul class="search-filter" id="attributeLevel1Facet">
<li>brand1</li>
<li>brand2</li>
<li>brand3</li>
<li>brand4</li>
<li>brand5</li>
<li class="search-opt hide">
<ul class="search-opt">
<li>brand6</li>
<li>brand7</li>
<li>brand8</li>
<li>brand9</li>
<li>brand10</li>
<li>brand11</li>
<li>brand12</li>
<li>brand13</li>
</ul>
</li>
</ul>
<c:if test="${fn:length(***) gt 5}">
<a data-role="more-options" class="more-option" title="more-option">More Options</a>
</c:if>
I want to change the first list to second list using jquery if items > 5. If $("#attributeLevel1Facet > li").length > 5 then it should wrap it with another <ul><li> element and add more-option button (second list above).
Please help me.
You can use something like this,
$("#attributeLevel1Facet > li").filter(function() {
return $(this).index() > 4;
}).wrapAll("<li class='search-opt hide'><ul class='search-opt'></ul></li>");
Fiddle
In the above code, filter will return the li elements whose index is greater than 4. Then you can wrap them with any elements.
You can check the length of the li elements and then create a new ul like
var $lis = $('#attributeLevel1Facet li');
if ($lis.length > 5) {
var $ul = $('<ul class="search-opt"/>').append($lis.slice(5));
$ul.wrap('<li class="search-opt hide" />').parent().appendTo('#attributeLevel1Facet')
}
Demo: Fiddle
Check this -> jQuery load first 3 elements, click "load more" to display next 5 elements
From the answer i think you can get what you want
You can try something like this
if($('li').length > 5){
var element='<li class="search-opt hide">'+
'<ul class="search-opt">';
$('li:nth-child(5)').after(element)
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<ul class="search-filter" id="attributeLevel1Facet">
<li>brand1</li>
<li>brand2</li>
<li>brand3</li>
<li>brand4</li>
<li>brand5</li>
<li>brand6</li>
<li>brand7</li>
<li>brand8</li>
<li>brand9</li>
<li>brand10</li>
<li>brand11</li>
<li>brand12</li>
<li>brand13</li>
</ul>

Ordering a list of elements without losing the event handler

I have this list :
<ul>
<li id="6">
list 6: somethings
</li>
<li id="2">
list 2: somethings
</li>
<li id="4">
list 4: somethings
</li>
<li id="5">
list 5: somethings
</li>
<li id="0">
list 0: somethings
</li>
</ul>
and I'd like (with Javascript/jQuery) order these elements by the id (ASC) keeping the event handler for each element.
Is it possible? How can I do it?
You could just assign the ID's into an array and use sort():
var a = [];
$("ul li").attr('id',function(i,e){
a.push(e);
});
$.each(a.sort(),function(i,e){
$("#"+e).appendTo('ul');
});
You are never removing them from the list, just moving them around. Click handler stays intact:
http://jsfiddle.net/niklasvh/nVLqR/
This should work fine. Using detach preserves any events associated with the element. You then use a custom sort function to compare the id attributes of each li element.
var ul = $("ul");
var li = ul.children("li");
li.detach().sort(function(a, b) {
var compA = $(a).prop("id");
var compB = $(b).prop("id");
return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
});
ul.append(li);
See an example fiddle here. A click event is attached to the li with ID "6". After the list has been reordered, that click event is still handled.
I think to order them you'll had to remove and add them back into the DOM... and therefore you'll certainly lose the event handler. Are you in control of the handler, can you rebind or use live() instead?
The alternative would be to absolutely position the li elements and use the css position properties (top, right, bottom, left) to move them around, this will keep them in the same order in the DOM, but render them in your desired order.t
This example work for me:
var mylist = $('.item').detach().sort(function (a, b) {
return $(a).find(selector).html() > $(b).find(selector).html();
});
$("#container").html(mylist);
or if you want to sort with other informations:
var mylist = $('.item').detach().sort(function (a, b) {
return $(a).find(selector).attr('data-title')() > $(b).find(selector).attr('data-title');
});
$("#container").html(mylist);

Categories

Resources