Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example "Select2 Drag and Drop Sorting" in Select2 4.0 #3004

Closed
Hemmils opened this issue Feb 5, 2015 · 19 comments
Closed

Example "Select2 Drag and Drop Sorting" in Select2 4.0 #3004

Hemmils opened this issue Feb 5, 2015 · 19 comments

Comments

@Hemmils
Copy link

Hemmils commented Feb 5, 2015

Hello Select2 gods,

I'm looking for the "Select2 Drag and Drop Sorting" example in Select2 4.0.
It's not on the example page.

Is it still possible in 4.0?

@kevin-brown
Copy link
Member

We have brought the old onSortStart and onSortEnd methods over to Select2 4.0. It wasn't used as often as most of the things we brought over, and I had no clue how it needed to be implemented.

If you happen to know what elements need to exist and how the sorting should happen, I'd be interested in making a decorator for the selectionAdapter that added support.

@Hemmils
Copy link
Author

Hemmils commented Feb 5, 2015

Thanks for the quick reply.
Sadly I got no clue.
Maybe this is the problem: The sorting is only available when Select2 is attached to a hidden input field.
And there is no hidden input field in 4.0, right.

@kevin-brown
Copy link
Member

The sorting is only available when Select2 is attached to a hidden input field.

This mostly had to do with the fact that sorting a <select> requires moving the <option> elements around, which isn't always favorable. I still have no idea how I would want to handle this, though if you know of any other sortable dropdown plugins that we can look at, that would be useful.

@Hemmils
Copy link
Author

Hemmils commented Feb 9, 2015

Selectize.js has a drag_drop plugin, but it requires JQuery UI.
http://brianreavis.github.io/selectize.js/

@Dahkon
Copy link

Dahkon commented Sep 4, 2015

What is the correct way to add sortable to select2 4.0 ?
I can find an exemple to make it work.

@andrewbaldock
Copy link

If you're trying to use jQueryUI Sortable per the 3.5.3 instructions, this will make dragging work for Select2 v4:

$("ul.select2-selection__rendered").sortable({
  containment: 'parent'
});

As mentioned above by @kevin-brown the select2 'value' accessed via select2('val') is not yet wired to be aware of the order change. So until that is addressed you will need to roll your own 'sorted value getter', but it's doable via the dom.

jsFiddle with examples

UPDATE: the sort order of the selections gets hosed when you add or remove from the selection. So maybe something can be done with the 'sorter' option...

@WRidder
Copy link

WRidder commented Nov 25, 2015

I was wondering whether there are plans to natively support the custom ordering of a multiple select component?

@lucasvg
Copy link

lucasvg commented Feb 22, 2016

Me too. Is there any example of this done? I'm having such a hard time trying to accomplish this...

@joker-777
Copy link

This comment #1190 (comment) offers a possible solution. You only have to make sure that the options to choose from are ordered in the correct way initially.

@ambroiseRabier
Copy link

ambroiseRabier commented Aug 17, 2016

okay this work, i used it like this (whit jqueryUI sortable installed and require.js):

    function select2_sortable($select2, pCallback){
        var ul = $select2.next('.select2-container').first('ul.select2-selection__rendered');
        ul.sortable({
            placeholder : 'ui-state-highlight',
            forcePlaceholderSize: true,
            items       : 'li:not(.select2-search__field)',
            tolerance   : 'pointer',
            stop: function() {
                $($(ul).find('.select2-selection__choice').get().reverse()).each(function() {
                    var id = $(this).data('data').id;
                    var option = $select2.find('option[value="' + id + '"]')[0];
                    $select2.prepend(option);
                });
                pCallback();
            }
        });
    }

and then

select2_sortable($("#YourSelectElement"), onOptionsChange);

edit: my select2 is already created when i use this function, so no need to rebuild it..

@cfxd
Copy link

cfxd commented Apr 21, 2017

@ambroiseRabier you're a lifesaver, thank you!

I just had to remove the onOptionsChange and pCallback param/callback and this worked like a charm.

@litlife
Copy link

litlife commented May 5, 2018

Method .first() does not accept any arguments.
I change code to:

function select2_sortable($select2, pCallback){
var ul = $select2.next('.select2-container').find('ul.select2-selection__rendered');
ul.sortable({
placeholder : 'ui-state-highlight',
forcePlaceholderSize: true,
items : 'li:not(.select2-search__field)',
tolerance : 'pointer',
stop: function() {
$($(ul).find('.select2-selection__choice').get().reverse()).each(function() {
var id = $(this).data('data').id;
var option = $select2.find('option[value="' + id + '"]')[0];
$select2.prepend(option);
});
pCallback();
}
});
}

@seaders
Copy link

seaders commented Jul 25, 2018

select2.find('option...') doesn't seem to work with jQuery any more, either FYI. You need to do something like,

$(x_path + ' option').filter(function () { return $(this).html() === text; })

I'm not using JQuery UI, anyway, I'm using this with JQuery EasyUI (a totally different beast!), so below is the drag'n'drop working with that. It's not the most elegant, and I had to do a few things after delays because easyui seems to throw in arbitrary animations every now and again that I needed to wait for them to end, but it certainly works well enough.

function select2_sortable(select_id){
    var x_path = '#' + select_id;
    var $select = $(x_path);

    var ul_xpath = '#s2id_' + select_id + ' ul.select2-choices';
    $(ul_xpath).css({'min-height': '200px'});

    var li_xpath = ul_xpath + ' li';
    var $li = $(li_xpath);
    var $indicator = $('<li class="select2-search-choice indicator"> &nbsp; </li>').appendTo('body');

    $li.draggable({
        proxy:'clone',
        deltaX: 0,
        deltaY: 20,
        onStartDrag: function() {
            var proxy = $(this).draggable('proxy');
            proxy.css('z-index', 9999);
            $(this).hide();
        },
        onEndDrag: function() {
            endDrag($indicator, $(this));
        },
        onStopDrag: function() {
            endDrag($indicator, $(this));
        }
    });

    $li.droppable({
        accept: li_xpath,
        onDragOver:function(){
            $indicator.css({ display:'block' }).insertAfter(this);
        },
        onDragLeave:function(e, source){
            endDrag($indicator);
        },
        onDrop:function(e, source){
            $(source).insertAfter(this);
            endDrag($indicator, $(source));

            after_delay(function() {
                $.each($(li_xpath + ' div').get().reverse(), function () {
                    var text = $(this).text();
                    var $option = $(x_path + ' option').filter(function () {
                        return $(this).html() === text; });
                    $select.prepend($option);
                });
            });
        }
    });
}

function endDrag($indicator, $dragger) {
    $indicator.hide().appendTo('body');
    if($dragger) {
        $dragger.removeAttr('style');
        $dragger.show();
    }
}

function after_delay(fn) {
    setTimeout(fn, 10);
}

@pedrofurtado
Copy link
Contributor

We don't have immediate plans to provide this. We are focused to fix some major UI bugs (that are majority of issues and PR's). But if you open a PR with unit tests, I will be glad to review and approve if everything is ok 👍

@petr-panek
Copy link

petr-panek commented Oct 8, 2018

I rewrote the code from @ambroiseRabier .
My code is more clean, don't use $(ul).find to find selected options. Instead of that it use passed ui element, to find selected options and original select element that needs to be updated. This version works properly if you have more Select2 instances in the web page.

(function ($) {
$.fn.select2Sortable = function () {
    this.next().children().children().children().sortable({
        containment: 'parent', stop: function (event, ui) {
            var originalSelectElement = ui.item.parent().parent().parent().parent().prev();
            ui.item.parent().children('[title]').each(function () {   //Select2 li elementy
                var title = $(this).attr('title');
                console.log(title);
                var originalOptionElem = $(originalSelectElement).children('[value="' + title + '"]');
                originalOptionElem.detach();
                $(originalSelectElement).append(originalOptionElem)
            });
            $(originalSelectElement).change();
        }
    });
};

})(jQuery);

@danielbachhuber
Copy link

If your option label isn't the same as the ID, #3004 (comment) doesn't work.

Here's what I used, for an already instantiated selectEl element:

var selectEl = $('select').select2();
selectEl.next().children().children().children().sortable({
	containment: 'parent', stop: function (event, ui) {
		ui.item.parent().children('[title]').each(function () {
			var title = $(this).attr('title');
			var original = $( 'option:contains(' + title + ')', selectEl ).first();
			original.detach();
			selectEl.append(original)
		});
		selectEl.change();
	}
});

@elshobokshy
Copy link

Thank you @danielbachhuber with some tweaking this works!

@JoomBall
Copy link

Small improvement to find the exact text

<option value="1">Hello Word</option>
<option value="2">Hello</option>
var selectEl = $('select').select2({width: '100%'});

selectEl.next().find('ul').sortable({
	containment: 'parent', stop: function (event, ui) {
		ui.item.parent().children('[title]').each(function () {
			var title = $(this).attr('title');
			$('option:contains(\'' + title + '\')', selectEl).filter(function(){
				if ($(this).text() === title) {
					var original = $(this);
					original.detach();
					selectEl.append(original);
				}
			});
		});
		selectEl.change();
	}
});

@kamyargerami
Copy link

Small improvement to find the exact text

<option value="1">Hello Word</option>
<option value="2">Hello</option>

var selectEl = $('select').select2({width: '100%'});

selectEl.next().find('ul').sortable({
containment: 'parent', stop: function (event, ui) {
ui.item.parent().children('[title]').each(function () {
var title = $(this).attr('title');
$('option:contains('' + title + '')', selectEl).filter(function(){
if ($(this).text() === title) {
var original = $(this);
original.detach();
selectEl.append(original);
}
});
});
selectEl.change();
}
});

Thank you. it works for my case perfectly :)))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests