JQuery sortable lists and fixed/locked items
Is it possible to lock list items in JQuery sortable list in a way that those items will stay in that particular place in the list.
For example,
consider this pseudo list with locked items...
item A
item B(locked)
item C(locked)
item D
item E
item F
item G(locked)
So, I'd like to have the items B,C and G to be fixed in a way that if user drag and drop item D at the start of the list, the item A "jumps" over fixed/locked items B an开发者_如何转开发d C with following results...
item D
item B(locked)
item C(locked)
item A
item E
item F
item G(locked)
I've been searching for something like this without luck. Is it possible..?
Here's a hopefully bug-free version, updating as you drag. It's generating the current desired positions of the items when sorting starts, which means you should be able to change the classes whenever you need, refresh the widget's list items and you're good to go.
It also uses the sortable's built-in items
property to prevent dragging the fixed items and to sort out any sorting problems at the top and the bottom of the list.
I tried to move the fixed items around, but that resulted in horribly buggy behaviour, especially when there are multiple fixed items in groups. The final solution detaches all fixed items from the list, adds a helper element to the front, then re-inserts the fixed elements to their desired position, which seems to fix all bugs.
Try the demo here: http://jsfiddle.net/PQrqS/1/
HTML:
<ul id="sortable">
<li>oranges</li>
<li class="static">apples</li>
<li>bananas</li>
<li>pineapples</li>
<li>grapes</li>
<li class="static">pears</li>
<li>mango</li>
</ul>
CSS:
.static { color:red; }
li { background-color:whitesmoke; border:1px solid silver; width:100px; padding:2px; margin:2px; }
Javascript:
$('#sortable').sortable({
items: ':not(.static)',
start: function(){
$('.static', this).each(function(){
var $this = $(this);
$this.data('pos', $this.index());
});
},
change: function(){
$sortable = $(this);
$statics = $('.static', this).detach();
$helper = $('<li></li>').prependTo(this);
$statics.each(function(){
var $this = $(this);
var target = $this.data('pos');
$this.insertAfter($('li', $sortable).eq(target));
});
$helper.remove();
}
});
I extended the jQuery.Ui.sortable
:
Overview
jQuery.Ui.sortable
widget extension with fixed
feature. This feature allows user to fix elements in the list.
With the .fixedsortable()
constructor you construct a .sortable()
class which extended with the features. You can use the original methods and the extended as well.
Code
https://gist.github.com/3758329#file_fixedsortable.js > fixedsortable.js
Example
http://jsfiddle.net/omnosis/jQkdb/
Usage
General:
To use, add the fixed
property to the sortable list optios:
$("#list").fixedsortable({
fixed: (value)
})
the value can be:
- integer example:
3
- array of integers example :
[1,2,5]
- a html element or a list of html elements
- a css selector
- jquery object
HTML:
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script> //the jquery
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"></script> //the original jquery-ui
<script type="text/javascript" src="https://raw.github.com/gist/3758329/91749ff63cbc5056264389588a8ab64238484d74/fixedsortable.js"></script> //the extended sortable
...
<ul id="sortable1">
<li>oranges</li>
<li class="static">apples</li>
<li>bananas</li>
<li>pineapples</li>
<li>grapes</li>
<li class="static">pears</li>
<li>mango</li>
</ul>
<ul id="sortable2">
<li>bananas</li>
<li foo="asd">oranges</li>
<li foo="dsa">apples</li>
<li>pineapples</li>
<li>grapes</li>
<li>pears</li>
<li>mango</li>
</ul>
<ul id="sortable3">
<li>bananas</li>
<li>oranges</li>
<li>apples</li>
<li>pineapples</li>
<li>grapes</li>
<li>pears</li>
<li>mango</li>
</ul>
Javascript
$(function() {
$("#sortable1").fixedsortable({
fixed: "> .static"
});
$("#sortable2").fixedsortable({
fixed: $("li[foo]").css("background","red")
});
$("#sortable3").fixedsortable({
fixed: 2
})
});
Notes:
If you insist to use the .sortable
instead of .fixedsortable
you can use this https://gist.github.com/3758329#file_sortable.js instead of the jquery.ui library. This is a complete replacement of the jQuery.ui
, but i don't recommend to use this because of later updates.
i have been working on this more than 12 hours :( i am insane..
Check this out: Forcing an item to remain in place in a jQuery UI Sortable list
Also, I've implemented the above solution with multiple fixed elements here: http://jsfiddle.net/enBnH/12/ (obsolete, see below) It's rather self-explanatory, i think.
EDIT:
I've automated the process for generating the lockto values as well as adding ID's to those li
s with the class "fixed" (note that i have to add an ID so we can reference it)
See the COMPLETE solution HERE: http://jsfiddle.net/enBnH/44/
EDIT
Okay, after a gazillion errors with the above, i just rewrote the dang thing myself: http://jsfiddle.net/thomas4g/GPMZZ/15/
NOTE: The above does work, but @DarthJDG's answer seems a lot nicer to me. I'm leaving mine up on the offchance someone might prefer how mine behaves (i've learned not to delete stuff just beceause there's a better version :P )
Using the items parameter you can achieve what you want like this:
$("#mytable tbody").sortable({items: 'tr.sortable'});
Only rows with a .sortable
CSS class can be sorted now.
If you want to lock only the 1st row you can do this:
$("#mytable tbody").sortable({items: 'tr:not(:first)'});
The possibilities are endless...
This is based on @DarthJDG code. However it wasn't retrieving all the id's and the sorting wasn't working with the table. So I managed to update his solution which works with both list and tables and keeps the id in the array.
Javascript:
var fixed = '.static'; //class which will be locked
var items = 'li'; //tags that will be sorted
$('ul').sortable({
cancel: fixed,
items: items,
start: function () {
$(fixed, this).each(function () {
var $this = $(this);
$this.data('pos', $this.index());
});
},
change: function () {
var $sortable = $(this);
var $statics = $(fixed, this).detach();
var tagName = $statics.prop('tagName');
var $helper = $('<'+tagName+'/>').prependTo(this);
$statics.each(function () {
var $this = $(this);
var target = $this.data('pos');
$this.insertAfter($(items, $sortable).eq(target));
});
$helper.remove();
}
});
Demo: http://plnkr.co/edit/hMeIiRFT97e9FGk7hmbs
Connected sortables and fixed items
I ran into the problem when we have several connected sortables. The code suggested by @sarunast and @DarthJDG has erroneous behavior when dragging items from one list to another. Therefore, I have modified it a little, and now you can drag items from different lists with saving positions in both of them.
javascript:
let connected = '.soratble';
let fixed = '.static';
let newParentContainer;
//wrap the code suggested by @sarunast and @DarthJDG into the function
//code was modified a little
function sortingAroundFixedPositions(container) {
let sortable = $(container);
let statics = $(fixed, container).detach();
let tagName = statics.prop('tagName');
let helper = $('<' + tagName + '/>').prependTo(container);
statics.each(function() {
let target = this.dataset.pos;
let targetPosition = $(tagName, sortable).eq(target);
if (targetPosition.length === 0) {
targetPosition = $(tagName, sortable).eq(target - 1)
}
$(this).insertAfter(targetPosition);
});
helper.remove();
}
$('ul').sortable({
connectWith: connected,
cancel: fixed,
start: function() {
$(fixed, connected).each(function() {
this.dataset.pos = $(this).index();
});
},
change: function(e, ui) {
sortingAroundFixedPositions(this);
if (ui.sender) {
newParentContainer = this;
}
if (newParentContainer) {
sortingAroundFixedPositions(newParentContainer);
}
},
update: function(e, ui) {
newParentContainer = undefined;
}
});
demo: http://plnkr.co/edit/blmv4ZjaWJFcjvO2zQH0
Just use the "Include/Exclude" items selectors. Here is the link: https://jqueryui.com/sortable/#items
oh no! gist link is broken. here is code dump from https://gist.github.com/peterh-capella/4234752
code accessed Jan 6, 2016
//this code is created to fix this problem: http://stackoverflow.com/questions/4299241/
(function( $, undefined ) {
$.widget("ui.fixedsortable", $.ui.sortable, {
options: $.extend({},$.ui.sortable.prototype.options,{fixed:[]}),
_create: function() {
var o = this.options;
this.containerCache = {};
this.element.addClass("ui-sortable");
//Get the items
$.ui.sortable.prototype.refresh.apply(this,arguments);
if( typeof this.options.fixed == "number") {
var num = this.options.fixed
this.options.fixed = [num];
}
else if( typeof this.options.fixed == "string" || typeof this.options.fixed == "object") {
if(this.options.fixed.constructor != Array) {
var selec = this.options.fixed;
var temparr = [];
var temp = $(this.element[0]).find(selec);
var x = this;
temp.each(function() {
var i;
for(i=0;i<x.items.length && x.items[i].item.get(0) != this;++i) {}
if(i<x.items.length) temparr.push(i);
});
this.options.fixed = temparr;
}
}
//Let's determine if the items are being displayed horizontally
this.floating = this.items.length ? o.axis === 'x' || (/left|right/).test(this.items[0].item.css('float')) || (/inline|table-cell/).test(this.items[0].item.css('display')) : false;
//Let's determine the parent's offset
this.offset = this.element.offset();
//Initialize mouse events for interaction
$.ui.sortable.prototype._mouseInit.apply(this,arguments);
},
_mouseCapture: function( event ) {
this._fixPrev = this._returnItems();
return $.ui.sortable.prototype._mouseCapture.apply(this,arguments);
},
_mouseStart: function( event ) {
for(var i=0;i<this.options.fixed.length;++i) {
var num = this.options.fixed[i];
var elem = this.items[num];
if(event.target == elem.item.get(0)) return false;
}
return $.ui.sortable.prototype._mouseStart.apply(this,arguments);
},
_rearrange: function(event, i, a, hardRefresh) {
a ? a[0].appendChild(this.placeholder[0]) :
i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction == 'down' ? i.item[0] : i.item[0].nextSibling));
this._refix(i);
//Various things done here to improve the performance:
// 1. we create a setTimeout, that calls refreshPositions
// 2. on the instance, we have a counter variable, that get's higher after every append
// 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
// 4. this lets only the last addition to the timeout stack through
this.counter = this.counter ? ++this.counter : 1;
var self = this, counter = this.counter;
window.setTimeout(function() {
if(counter == self.counter) self.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
},0);
},
_refix: function(a) {
var prev = this._fixPrev;
var curr = this._returnItems();
var Fixcodes = this.options.fixed;
var NoFixed = [];
var Fixed = [];
var Mixed = []
var post = [];
for(var i=0;i<Fixcodes.length;++i) {
var fix_index = Fixcodes[i];
var fix_item = prev[fix_index];
var j = 0;
for(j=0;j<curr.length && curr[j].item.get(0) != fix_item.item.get(0);++j) {}
curr.splice(j,1);
Fixed.push(fix_item);
}
for(var i=0;i<curr.length;++i) {
if(curr[i].item.get(0) != this.currentItem.get(0)) {
NoFixed.push(curr[i]);
}
}
var fix_count = 0;
var nofix_count = 0;
for(var i=0;i<Fixed.length + NoFixed.length;++i) {
if(Fixcodes.indexOf(i) >= 0) {
Mixed.push(Fixed[fix_count++]);
}
else {
Mixed.push(NoFixed[nofix_count++]);
}
}
var parent = this.currentItem.get(0).parentNode;
var allchild = parent.children;
for(var i=0;i<Mixed.length;++i) {
parent.removeChild(Mixed[i].item.get(0));
parent.appendChild(Mixed[i].item.get(0));
}
},
_returnItems: function(event) {
this.containers = [this];
var items = [];
var self = this;
var queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]];
var connectWith = $.ui.sortable.prototype._connectWith.apply;
if(connectWith) {
for (var i = connectWith.length - 1; i >= 0; i--){
var cur = $(connectWith[i]);
for (var j = cur.length - 1; j >= 0; j--){
var inst = $.data(cur[j], 'sortable');
if(inst && inst != this && !inst.options.disabled) {
queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
this.containers.push(inst);
}
};
};
}
for (var i = queries.length - 1; i >= 0; i--) {
var targetData = queries[i][1];
var _queries = queries[i][0];
for (var j=0, queriesLength = _queries.length; j < queriesLength; j++) {
var item = $(_queries[j]);
item.data('sortable-item', targetData); // Data for target checking (mouse manager)
items.push({
item: item,
instance: targetData,
width: 0, height: 0,
left: 0, top: 0
});
};
};
return items;
},
value: function(input) {
//console.log("test");
$.ui.sortable.prototype.value.apply(this,arguments);
}
});
})(jQuery);
And dumping rest of his answer, just in case
dependencies
https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js
Script
function randomColor() { //for a little fun ;)
var r = (Math.floor(Math.random()*256));
var g = (Math.floor(Math.random()*256));
var b = (Math.floor(Math.random()*256));
return "#" + r.toString(16) + g.toString(16) + b.toString(16)
}
$(function() {
$("#sortable1").fixedsortable({
fixed: "> .static", //you can use css selector
sort: function() { //you can add events as well, without getting confused. for example:
$(".static").css("background",randomColor()) //change the fixed items background
},
change: function(event,ui) {
$(ui.item[0]).css("border","2px solid "+randomColor()) //change the captured border color
},
stop: function(event,ui) {
$(ui.item[0]).css("border","2px solid #777"); //change the back the css modifications
$("#sortable1 > li.static").css("background","#aaa");
}
});
$("#sortable2").fixedsortable({ //you can use jQuery object as selector
fixed: $("li[foo]").css("background","red")
});
$("#sortable3").fixedsortable({
fixed: [2,4], //you can use array of zero base indexes as selector
update: function(event, ui) {
alert($(this).fixedsortable('toArray')) //the fixedsortable('toArray') also works
}
})
$("#sortable4").fixedsortable({
fixed: 5 //you can fix a single item with a simple integer
})
});
HTML
<body>
<div style="width:120px;float:left;">
<ul id="sortable1">
<li><a href="#">oranges</a></li>
<li class="static"><a href="#">apples</a></li>
<li><a href="#">bananas</a></li>
<li><a href="#">pineapples</a></li>
<li><a href="#">grapes</a></li>
<li class="static"><a href="#">pears</a></li>
<li><a href="#">mango</a></li>
</ul>
<ul id="sortable2">
<li>bananas</li>
<li foo="asd">oranges</li>
<li foo="dsa">apples</li>
<li>pineapples</li>
<li>grapes</li>
<li>pears</li>
<li>mango</li>
</ul>
</div>
<div style="width:120px;float:left;">
<ul id="sortable3">
<li id="fru_1">bananas</li>
<li id="fru_2">oranges</li>
<li id="fru_3" style="background:#f4f">apples</li>
<li id="fru_4">pineapples</li>
<li id="fru_5" style="background:#faaba9">grapes</li>
<li id="fru_6">pears</li>
<li id="fru_7">mango</li>
</ul>
<ul id="sortable4">
<li>bananas</li>
<li>oranges</li>
<li>apples</li>
<li>pineapples</li>
<li>grapes</li>
<li style="background:#dada00">pears</li>
<li>mango</li>
</ul>
</div>
</body>
CSS
ul {margin:10px;}
ul#sortable1 > li, ul#sortable2 > li, ul#sortable3 > li, ul#sortable4 > li {
display:block;
width:100px;
height:15px;
padding: 3px;
background: #aaa;
border: 2px solid #777;
margin: 1px;
}
ul#sortable1 > li.static {
opacity:0.5;
}
Maybe this will help to someone: use methods "disable" and "enable". Example HTML:
<ul class="sortable">
<li>You can move me</li>
<li data-state="lifeless">You can't move me.</li>
</ul>
Script:
$('#sortable').sortable();
$('#sortable').mousedown(function() {
if($(this).data('state')=='lifeless') $('#sortable').sortable('disable');
else $('#sortable').sortable('enable');
});
Live example here: https://jsfiddle.net/ozsvar/0ggqtva5/2/
There is a slightly better way to solve this. You need to use a grid instead of a list and then you can fix the position of an element by declaring where the element should be arranged with css:
.fixed-element {
grid-column-start: 1;
grid-column-end: 1;
grid-row-start: 1;
grid-row-end: 1;
}
精彩评论