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

Prevent select2:open when clearing selection #3320

Closed
kartik-v opened this issue May 6, 2015 · 81 comments
Closed

Prevent select2:open when clearing selection #3320

kartik-v opened this issue May 6, 2015 · 81 comments

Comments

@kartik-v
Copy link
Contributor

kartik-v commented May 6, 2015

Prevent catching the click (and disallow opening of the select or triggering the select2::open) when the clear indicator is pressed to clear the selection when allowClear is true.

This behavior worked fine in 3.5.2 - not sure if there is a reason for this.

Use case: When allowClear is true and one clicks the selection clear indicator -- the value gets cleared but it also triggers the dropdown to open again when it is not necessary.

@kartik-v kartik-v changed the title Prevent select from opening when clearing selection Prevent select2::open when clearing selection May 6, 2015
@sectus
Copy link

sectus commented May 25, 2015

This feature "makes" weird solutions to prevent it: http://stackoverflow.com/questions/29618382/disable-dropdown-opening-on-select2-clear

@kartik-v
Copy link
Contributor Author

I did manage to implement this functionality by trapping the select2:opening event and aborting the open and also using the select2:unselect to cleanup/reset vars set at open if needed.

@kreintjes
Copy link

I can confirm this issue.

I think this issue should be considered a bug, since when a user wants to clear the select, he does not immediately want to select another value. And if he does, he would simply click on the element anywhere but the clear button.

@kreintjes
Copy link

@kartik-v Could you please explain what you do exactly? I tried cancelling the opening event using e.preventDefault(). This works, but I do get a JavaScript error TypeError: args is undefined.

My code as I now have it:

  $field.on 'select2:unselecting', ->
      $(this).data('unselecting', true)
    $field.on 'select2:opening', (e) ->
      if $(this).data('unselecting')
        $(this).removeData('unselecting')
        e.preventDefault()

@kevin-brown What is the reason for the new behaviour? Is it possible to restore the old behaviour, potentially as an option?

@kevin-brown
Copy link
Member

This works, but I do get a JavaScript error TypeError: args is undefined.

Known issue, tracking at #3431.

What is the reason for the new behaviour?

Based on the commit (ffed370), there wasn't any major reason for it.

Is it possible to restore the old behaviour, potentially as an option?

I'm open to the idea of it, though it would have to be done in a way such that it didn't break backwards compatibility.

@kartik-v
Copy link
Contributor Author

kartik-v commented Jun 4, 2015

Could you please explain what you do exactly? I tried cancelling the opening event using e.preventDefault(). This works, but I do get a JavaScript error TypeError: args is undefined.

Yes its similar to yours (probably yours is better) - and more of an hack - which you can check on my Yii2 Select2 widget demo pages - but I am also facing the args undefined message as you mentioned (probably only when I have change events on the input) and related to #3431.

At other times I am getting an error cannot set property prevented of undefined in select2.full.js Line 5161.

// initialize a variable like this (better if this variable is unique for each Select2 input)
var isClearClicked = false;
// trap these events
$field.on('select2:opening', function(e) {
    if (window['isClearClicked']) {
        e.preventDefault();
        window['isClearClicked'] = false;
    }
}).on('select2:unselect', function(e) {
     window['isClearClicked'] = true;
});

@kartik-v
Copy link
Contributor Author

kartik-v commented Jun 4, 2015

I modified the hack to the following code combining all the thoughts in this thread (with some additions) and it seems to work without errors (if you can forgive the flicker of open and close of the dropdown) - so still a dirty hack... till we get this solved...

var $el = $('#select-id');
$el.on('select2:opening', function(e) {
    if ($el.data('unselecting')) {    
        $el.removeData('unselecting');
        setTimeout(function() {
            $el.select2('close');
        }, 1);
    }
}).on('select2:unselecting', function(e) {
    $el.data('unselecting', true);
});

@kartik-v
Copy link
Contributor Author

kartik-v commented Jun 9, 2015

Another side effect of clear - #3452 - we probably need additional events.

@NovaFocus
Copy link

The following works for me:

            $("select").on("select2:unselecting", function (e) {
                $(this).select2("val", "");
                e.preventDefault();
            });

@SlyDave
Copy link

SlyDave commented Sep 14, 2015

Is it possible to restore the old behaviour, potentially as an option?
I'm open to the idea of it, though it would have to be done in a way such that it didn't break backwards compatibility.

I agree with others, this is odd default behaviour - it doesn't match the default behaviour of the standard multiselect HTML element on Windows (Chrome, FF and standard Office and Windows UI tested) or MAC, and in our focus group test of our new UI, it confused and frustrated users (as they had to do an additional click to access options below the select2 element after they had cleared all the selected items from the multiselect)

Further to this is is a change in behaviour from Select2 v3, so yet another incompatibility - with no evidence supported reason for the change :(

I would like to see the default behaviour changed to match v3, or at the very least a option for it.

@NovaFocus solution doesn't appear to work when the select is configured for multiselect

@Sector95
Copy link

Here's another hack for those that can't stand the quick flicker that shows up in kartik's solution (full credit to him for his solution being a springboard to this solution):

var select2Obj = $('select');
select2Obj.select2();

/* The following mess is a result of Select2's behavior of triggering the
 * opening event twice upon unselecting an item. */
select2Obj.on('select2:unselecting', function(e) {
    $(this).data('unselecting1', true);
    $(this).data('unselecting2', true);
});
select2Obj.on('select2:open', function(e) {
    var unselecting1 = $(this).data('unselecting1');
    var unselecting2 = $(this).data('unselecting2');

    if(unselecting1 || unselecting2) {
        $(this).select2('close');

        if(unselecting1) {
            $(this).data('unselecting1', false);
        } else {
            $(this).data('unselecting2', false);
        }
    }
});
// End of mess.

@kevin-brown kevin-brown changed the title Prevent select2::open when clearing selection Prevent select2:open when clearing selection Oct 16, 2015
@althaus
Copy link

althaus commented Oct 19, 2015

👍 for making it to not open the dropdown.

Maybe an option like "autoOpenOnClear" could solve the issue. Or people could trigger the opening theirselves with the unselecting event?

@Hlsgs
Copy link

Hlsgs commented Oct 27, 2015

@SectorNine50 your solution works, but disables the normal opening of the dropdown for one click after unselecting from the dropdown list, so i came up with this, witch i think works better because it prevents the open event from firing in the first place:

var select2Obj = $('select');
select2Obj.select2();

select2Obj.on('select2:unselecting', function(e) {
    $(this).on('select2:opening', function(e) {
        e.preventDefault();
    });

});
select2Obj.on('select2:unselect', function(e) {
     var sel = $(this);
     setTimeout(function() {
       sel.off('select2:opening');
     }, 1);
});

Obviously, replace " sel.off('select2:opening'); " with your own code for taht event if you're using it
.
Enjoy :)

@Sector95
Copy link

@Hlsgs Odd, it doesn't have that behavior on my end... Both of the unselecting "variables" should be unset immediately after deselecting an object. Does one remain set to true after deselecting on your end?

That said, what you came up with is a good solution as well!

@Hlsgs
Copy link

Hlsgs commented Oct 28, 2015

@SectorNine50 unfortunately, i don't know how to watch for variables that are not defined straight in js. btw, how would one watch for unselecting1 and unselecting2 in your code?

regardless, i think this behaviour is because unselecting by clicking the "x" in teh selection actually reopens the dropdown but unselecting from the dropdown does not. so after unselecting we're left with one more round of preventing teh dropdown from opening.

i've dug a little deeper here, and decided to fix select2.js as per here 6be96cf and use preventdefault() as TypeError: args is undefined is a select2 issue and this is going to be in future releases anyway.
after that, i wanted to go with @kreintjes 's solution as it seemed teh most to the point to me, but that didn't work i think because the open event gets fired twice(witch you accounted for by using two variables, correct?)
so i made a hybrid of your and his solution like this

$(this).on('select2:unselecting', function(e) {
    $(this).data('unselecting1', true);
    $(this).data('unselecting2', true);
});
$(this).on('select2:opening', function(e) {
    var unselecting1 = $(this).data('unselecting1');
    var unselecting2 = $(this).data('unselecting2');

    if(unselecting1 || unselecting2) {

        e.preventDefault();

        if(unselecting1) {
            $(this).data('unselecting1', false);
        } else {
            $(this).data('unselecting2', false);
        }
    }
});

This produced exactly the same results as your vanilla solution, and after deselecting from the dropdown the opening of it gets blocked for a round.

In the end i went back to my solution, as it doesn't rely on setting variable and, by hooking into the :unselect event to restore opening functionality it disregards wether the dropdown was actually opened or not. And the timeout accounts for the opening event firing more than once(or not).

Considering all this, i should be fine with this solution even if in future updates open fires only once and/or the developer prevents the opening of the dropdown in the original code. Correct?

EDIT:
Actually it seems that the timeout i added has nothing to do with the open event firing more that once as i fixed that as per 2acf9f3 and it seems the code doesnt work without it anyway.
I'm starting to despise my feeble understanding of js :)
Please can someone explain why this works:

    $(this).on('select2:unselecting', function(e) {
        $(this).on('select2:opening', function(event) {
            event.preventDefault();
        });
    });
    $(this).on('select2:unselect', function(e) {
         var sel = $(this);
         setTimeout(function() {
           sel.off('select2:opening');
         }, 100);
    });

and this doesn't:

    $(this).on('select2:unselecting', function(e) {
        $(this).on('select2:opening', function(event) {
            event.preventDefault();
        });
    });
    $(this).on('select2:unselect', function(e) {
         var sel = $(this);
         sel.off('select2:opening');
    });

@Sector95
Copy link

unfortunately, i don't know how to watch for variables that are not defined straight in js. btw, how would one watch for unselecting1 and unselecting2 in your code?

Within your debugger, you should be able to dig into the parent variable $(this) and find the data object, which should contain the unselected variables.

Considering all this, i should be fine with this solution even if in future updates open fires only once and/or the developer prevents the opening of the dropdown in the original code. Correct?

If future versions only fire open once or not at all, you'll have to change the code, since one (or both) variable(s) will still remain set, meaning that the next attempt to open will be blocked.

I'm not entirely sure what your patch changes, honestly. I'll have to dig a little more into the core code to get an understanding.

@straps
Copy link

straps commented Nov 16, 2015

+1 for this...great component anyway

@Geczy
Copy link

Geczy commented Nov 27, 2015

                $(this).select2("val", "");
                e.preventDefault();

Works great, thanks

@sirNemanjapro
Copy link

Still no solution?

@kevin-brown
Copy link
Member

The planned solution is to add a select2:clear event that will contain the originalEvent, so we can just call stopPropagation() on that to prevent the opening. That would make the solution similar to #3209 (comment).

@kevin-brown kevin-brown added this to the 4.1.0 milestone Dec 7, 2015
@Hlsgs
Copy link

Hlsgs commented Jul 16, 2019

Please don't close this if unresolved.

@gorj-tessella
Copy link

Still not fixed in 4.0.8

@kevin-brown
Copy link
Member

Yup, this can be done natively. I followed the guidance at #3320 (comment) and made a jsbin for everyone.

https://jsbin.com/duyisusawo/edit?html,js,output

$("select").on("select2:clear", function (evt) {
  $(this).on("select2:opening.cancelOpen", function (evt) {
    evt.preventDefault();
    
    $(this).off("select2:opening.cancelOpen");
  });
});

I'm going to turn this ticket into a documentation ticket, so this snippet can be added to the documentation for others to use.

@kartik-v
Copy link
Contributor Author

Thanks. Is there a reason this feature cannot be included within the plugin by default instead of one needing to write this code for every select2 plugin render?

@kevin-brown
Copy link
Member

Is there a reason this feature cannot be included within the plugin by default instead of one needing to write this code for every select2 plugin render?

Yes, there's actually a pretty solid reason: maintenance.

The snippet takes a few minutes to copy and install globally (you don't need to copy/paste it for each one, it'll only affect <select> boxes with Select2 initialized). If we were to bring this in natively to Select2, we would have to add yet another boolean option which would have to work with the internal event model to accomplish the same goal. Inevitably that would introduce complex, hard-to-test bugs with other supported options, which then means I have to hunt those down and add further tests for it.

The external event system exists for others to be able to integrate with Select2 and allow it to do things which are not natively supported. This is an awesome example of one of those cases.

@gorj-tessella
Copy link

Another boolean option is not needed if the default behavior were to not open the dialogue on clear, which seems much more sensible especially for multiselect. If I click clear I mean clear, not clear and open just so I have to close it. As it stands I have to double-check that every single time I setup a select2 control (often dynamically, on many pages) I also ensure this event hook is attached.

@stale
Copy link

stale bot commented Oct 29, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the status: stale label Oct 29, 2019
@stale stale bot closed this as completed Nov 5, 2019
@wildlyinaccurate
Copy link

I'm surprised that this is a "feature". We switched to select2 recently and testing feedback was that this issue is a bug. Kind of disappointing that I have to wrangle events just to get around this behaviour.

@handpc
Copy link

handpc commented Feb 6, 2020

The following works for me:

            $("select").on("select2:unselecting", function (e) {
                $(this).select2("val", "");
                e.preventDefault();
            });

Simple and worked!
Thank you! You save my time :)

@nafetsrybak
Copy link

Yup, this can be done natively. I followed the guidance at #3320 (comment) and made a jsbin for everyone.

https://jsbin.com/duyisusawo/edit?html,js,output

$("select").on("select2:clear", function (evt) {
  $(this).on("select2:opening.cancelOpen", function (evt) {
    evt.preventDefault();
    
    $(this).off("select2:opening.cancelOpen");
  });
});

I'm going to turn this ticket into a documentation ticket, so this snippet can be added to the documentation for others to use.

Hello, yes it worked for me, but unfortunately it still sends AJAX request after unselecting the value(
How can i solve this problem?

@kevin-brown
Copy link
Member

@nafetsrybak that sounds like a different issue and possibly a bug, any chance you can open a new ticket about it?

gmcharlt pushed a commit to Koha-Community/Koha that referenced this issue Aug 24, 2020
We want to restore the previous behaviour and keep the options closed when
clear is clicked

select2/select2#3320 (comment)

Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
@Martin12350
Copy link

Martin12350 commented Feb 15, 2021

This is definitely not feature, but bug. If we will ignore fact that clear means clear and not adding as people mentioned before, here is picture what will happen if somebody clear multiple selects without @NovaFocus fix.
bug

Unfortunatelly even with @NovaFocus fix the problem is not solved. It works only for selects that are simple, not multiple. In that case when somebody wanna remove only one option, he will delete all options except the last one.

Update: for those who wants clear single and multiple without dropdown here is my little trick (still need some trick when removing by backspace):

$('select').on('select2:unselecting', function (e) {
	if(e.params.args.originalEvent===undefined) {
		e.preventDefault();
		$(this).select2("val", "");
	}
});

@drj613
Copy link

drj613 commented Feb 17, 2021

Yup, this can be done natively. I followed the guidance at #3320 (comment) and made a jsbin for everyone.
jsbin.com/duyisusawo/edit?html,js,output

$("select").on("select2:clear", function (evt) {
  $(this).on("select2:opening.cancelOpen", function (evt) {
    evt.preventDefault();
    
    $(this).off("select2:opening.cancelOpen");
  });
});

I'm going to turn this ticket into a documentation ticket, so this snippet can be added to the documentation for others to use.

This works for me for preventing the dropdown from re-opening when you click clear, but prevents the dropdown from opening again at all after that.


This solution worked best for me:

https://stackoverflow.com/a/35807365

$('#my-select').select2({
    allowClear: true
}).on('select2:unselecting', function() {
    $(this).data('unselecting', true);
}).on('select2:opening', function(e) {
    if ($(this).data('unselecting')) {
        $(this).removeData('unselecting');
        e.preventDefault();
    }
});

@reimerdes
Copy link

Yup, this can be done natively. I followed the guidance at #3320 (comment) and made a jsbin for everyone.

https://jsbin.com/duyisusawo/edit?html,js,output

$("select").on("select2:clear", function (evt) {
  $(this).on("select2:opening.cancelOpen", function (evt) {
    evt.preventDefault();
    
    $(this).off("select2:opening.cancelOpen");
  });
});

I'm going to turn this ticket into a documentation ticket, so this snippet can be added to the documentation for others to use.

Works for me.
If I use this on a select2 combobox the dropdown will show an directly close again. Is there a way to prevent this?

@joker-777
Copy link

I came across this particular code snippet

        // Related with https://github.com/select2/select2/issues/3320
        $(sallowClearelect).on('select2:unselecting', function() {
          $(this).data('unselecting', true);
        }).on('select2:open', function() {
          if ($(this).data('unselecting')) {
            $(this).select2('close').removeData('unselecting');
          }   
        });

which links to this ticket. Unfortunately this hack influences the behavior of the select2 tags plugin negatively. Usually, it would just start removing the last letter of the previous tag when using the backspace key. With this "hack" it removes the whole "tag" :(

@MaheryLala
Copy link

MaheryLala commented Feb 22, 2023

After digging for a bit I found a solution and changed a few things, idk if this'd work for y'all

$(".select2").on("select2:unselecting", function () {
        $(this).on("select2:opening", function (ev) {
            ev.preventDefault();
            $(this).off("select2:opening");
        });
    });

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