Select current item when tabbing off Select2 input - javascript

This has been the subject of the following similar SO Question and several github issues including:
Stack Overflow - jQuery Select2 Tag on Blur
#4578 - Tab and Esc should select the option
#4820 - TAB should select a result #4820
#3472 - Arrow keys do not change value of select
But the suggested solutions or questions have treated all blur events equally, regardless of how they were invoked. With most answers leveraging Automatic selection by setting selectOnClose.
Ideally, clicking off the dropdown (escaping) after merely hovering over options should not change the value:
How can you update the selection on tabout, but not other close events?
Here's an MCVE in jsFiddle and StackSnippets:
$('.select2').select2({});
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/css/select2.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/js/select2.js"></script>
<div class="form-control">
<label for="foods2">Select2</label>
<select id="foods2" class="select2" >
<option value="1">Apple</option>
<option value="2">Banana</option>
<option value="3">Carrot</option>
<option value="4">Donut</option>
</select>
</div>

This could be trivially handled by modifying the original source code on line 323 which treats tabs and esc keys identically:
if (key === KEYS.ESC || key === KEYS.TAB || (key === KEYS.UP && evt.altKey)) {
self.close();
evt.preventDefault();
}
But third party libraries should ideally be modified with a pull request. So we have a couple problems in creating a wrapper for this. Principally, detecting a tab keypress event is hard. If we catch it too late, another action might have superseded it or it might be fired on another field altogether.
The landscape of capturing tab events and persisting information seems to fall into two buckets:
Monitor before tab press and also after closed
Intercept tab press and modify synchronously
In either case, we must know that a tab key was the offending item that caused the menu to close.
If we listen for keypress events with tab, on an open input, they'll either occur from .select2-selection if there's no search or select2-search__field if search is enabled.
$("body").on('keydown', e => { if (e.keyCode === 9) console.log(e.target) });
However, if we setup as a delegated handler, as we have above, by the time the event bubbles up all the way up to "body", the menu has already closed, thus clearing the currently highlighted item and even our indicator of whether or not we started as open.
We can intercept before the menu closes by registering for the select2:closing event like this:
$("body").on('select2:closing', e => { console.log(e,e.target) });
However, select 2 doesn't persist the original event information and instead makes their own new jQueryEvent, so we don't yet know if we're closing due to a tab event (the body.keypress event fires afterward)
Solution
We'll monitor the select2:closing event and capture what we need to know. Next we need to attach a handler that listens for the subsequent firing of the initial click or a key stroke as the event pipeline is finished. We need to fire this once and only once for every close option. To do so we can use this extension $.fn.once. If it was raised by a tab, it'll update whatever value detected during closing. If not, that value and handler will disappear.
All told, it should look like this:
// monitor every time we're about to close a menu
$("body").on('select2:closing', function (e) {
// save in case we want it
var $sel2 = $(e.target).data("select2");
var $sel = $sel2.$element;
var $selDropdown = $sel2.$results.find(".select2-results__option--highlighted")
var newValue = $selDropdown.data("data").element.value;
// must be closed by a mouse or keyboard - listen when that event is finished
// this must fire once and only once for every possible menu close
// otherwise the handler will be sitting around with unintended side affects
$("html").once('keyup mouseup', function (e) {
// if close was due to a tab, use the highlighted value
var KEYS = { UP: 38, DOWN: 40, TAB: 9 }
if (e.keyCode === KEYS.TAB) {
if (newValue != undefined) {
$sel.val(newValue);
$sel.trigger('change');
}
}
});
});
$.fn.once = function (events, callback) {
return this.each(function () {
$(this).on(events, myCallback);
function myCallback(e) {
$(this).off(events, myCallback);
callback.call(this, e);
}
});
};
Working demo in jsFiddle and StackSnippets:
$('.select2').select2({});
// monitor every time we're about to close a menu
$("body").on('select2:closing', function (e) {
// save in case we want it
var $sel2 = $(e.target).data("select2");
var $sel = $sel2.$element;
var $selDropdown = $sel2.$results.find(".select2-results__option--highlighted")
var newValue = $selDropdown.data("data").element.value;
// must be closed by a mouse or keyboard - setup listener to see when that event is completely done
// this must fire once and only once for every possible menu close
// otherwise the handler will be sitting around with unintended side affects
$("html").once('keyup mouseup', function (e) {
// if close was due to a tab, use the highlighted value
var KEYS = { UP: 38, DOWN: 40, TAB: 9 }
if (e.keyCode === KEYS.TAB) {
if (newValue != undefined) {
$sel.val(newValue);
$sel.trigger('change');
}
}
});
});
$.fn.once = function (events, callback) {
return this.each(function () {
$(this).on(events, myCallback);
function myCallback(e) {
$(this).off(events, myCallback);
callback.call(this, e);
}
});
};
.form-control {
padding:10px;
display:inline-block;
}
select {
width: 100px;
border: 1px solid #aaa;
border-radius: 4px;
height: 28px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/css/select2.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/js/select2.js"></script>
<div class="form-control">
<label for="foods2">Select2</label>
<select id="foods2" class="select2" >
<option value="1">Apple</option>
<option value="2">Banana</option>
<option value="3">Carrot</option>
<option value="4">Donut</option>
</select>
</div>

Related

Disabling focused button does not move the focus to the next focusable item

As per title, I have a button that can be hit only 3 times and then it will disable (using jQuery) itself.
test.html
<div class="main">
<input class="one" type="text" />
<button class="two" >If you hit me I will disabling myself...</button>
<button class="three">...and focus should be moved to me!</button>
</div>
test.js
$('.two').on('keyup', function(event) {
$(event.target).attr('disabled', true);
});
Suppose the user is using the keyboard to do so, by hitting the Enter key
Why the focus does not move to the next button when the currently focused one gets disabled?
Here a link to a fiddle showing what I mean: https://jsfiddle.net/8dyk2b2m/
Edit 1
Suppose that:
You don't know what is the next focusable item but you want it to be focused
I have some cases where the next focusable item in not a sibling of the current one (next() does not work)
Edit 2
My DOM is generated on the fly, that's why I cannot manage case by case but I need a more general algorithm. The stranger thing to me still be that the browser does not manage to move the focus when I disable the field currently focused.
Edit 3
In a comment below, the linked solution from this StackOverflow question does not cover all the cases because the disable action prevent the keyup event to be triggered and - on the other side - the keydown event is to earlier, because when the button is hit a new section is created (obviously by another keydown handler somewhere else and no, I cannot modify that handler directly)
Ok, finally I get a good result. I will post here my answer just in case someone would like to do the same or to improve it:
utils.js
function setFocusToClosestTabbableField(target, forward) {
var promise = $timeout(function () {
// Find the focused element in case of the given target is not valid
var $focused = (target instanceof Element || target instanceof jQuery) ? $(target) : $(document.activeElement);
// Check if the element is visible and enabled
var isDisabled = $focused.is(':disabled');
var isHidden = $focused.is(':hidden');
if (isDisabled || isHidden) {
// If the focused element is disabled we have to enable it temporarily in order to find it
// in the list of the tabbable elements
if (isDisabled) {
$focused.attr('disabled', false);
}
// Retrieving now the list of tabbable elements and restore the status of the focused one if needed
var $tabbables = $(':tabbable');
if (isDisabled) {
$focused.attr('disabled', true);
}
// Find the index of the current focused element and retrieve the index of the next on tabbable
// in the list
var focusedIndex = $tabbables.index($focused);
var nextIndex = focusedIndex + ((forward == null || forward == true) ? 1 : -1);
if (nextIndex < 0 || nextIndex > $tabbables.length - 1) {
nextIndex = (forward == null || forward == true) ? 0 : $tabbables.length - 1;
}
// Get the next element focusable and put the focus on it
$focused = $($tabbables.get(nextIndex));
$focused.focus();
// If the field is disable force a keyup event because the browser engine prevents it
if (isDisabled) {
$focused.keyup();
}
}
// Return the focused element
return $focused;
}, 200);
return promise;
}
main.js
// Registering both keydown and keyup since the browser will prevent the second one if the
// focused field becomes disabled in a previously attache handler to the keydown event
$('body').on('keydown keyup', function() {
var key = event.keyCode | -1
var managedKeys = [
-1, // Manually triggered key event
9, // Tab
13, // Enter
32 // Space
];
// I check also Enter and Space since if I hit one of them while the focus is on a button and this
// button will get disabled, then I have to find the next tabbable field and put the focus on it
if (managedKey.indexOf(key) > -1) {
var $target = $(event.target);
setFocusToClosestTabbableField($target, !event.shiftKey);
}
});
NOTES
If someone want to discuss about my solution or want to improve it, please do not hesitate! Cheers!
you have to add focus to next element:
$('.two').on('keyup', function(event) {
$(event.target).attr('disabled', true);
$(event.target).next().focus();
});
.main * {
display: block;
margin: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="main">
<input class="one" type="text" />
<button class="two" >If you hit me I will disabling myself...</button>
<button class="three">...and focus should be moved to me!</button>
</div>
Sorry, I can't comment so answering it.
The fiddle is working for me, may be the browser doesnt know what to execute after the function execution.
If you have the focus injected in the javascript. at the end of the function execution it works.
But why to trigger focus, no answer, why it is not handled by browser.
like
$('.two').on('keyup', function(event) {
$(event.target).attr('disabled', true);
$('.three').focus();
});
You can use click and focus for this.
$('.two').on('click', function(event) {
$(event.target).attr('disabled', true);
$(this).next().focus();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="main">
<input class="one" type="text" />
<button class="two">If you hit me I will disabling myself...</button>
<button class="three">...and focus should be moved to me!</button>
</div>

Vanilla javascript Trap Focus in modal (accessibility tabbing )

This should be pretty simple but for some reason it isn't working, I'm getting the proper console.logs at the right time, but the focus isn't going to the correct place, please refer to my jsfiddle
https://jsfiddle.net/bqt0np9d/
function checkTabPress(e) {
"use strict";
// pick passed event of global event object
e = e || event;
if (e.keyCode === 9) {
if (e.shiftKey) {
console.log('back tab pressed');
firstItem.onblur=function(){
console.log('last a focus left');
lastItem.focus();
};
e.preventDefault();
}
console.log('tab pressed');
lastItem.onblur=function(){
console.log('last a focus left');
firstItem.focus();
};
e.preventDefault();
}
}
modal.addEventListener('keyup', checkTabPress);
I had to lock focus within a modal that we had used within a React component.
I added eventListner for KEY DOWN and collected Tab and Shift+Tab
class Modal extends Component {
componentDidMount() {
window.addEventListener("keyup", this.handleKeyUp, false);
window.addEventListener("keydown", this.handleKeyDown, false);
}
componentWillUnmount() {
window.removeEventListener("keyup", this.handleKeyUp, false);
window.removeEventListener("keydown", this.handleKeyDown, false);
}
handleKeyDown = (e) => {
//Fetch node list from which required elements could be grabbed as needed.
const modal = document.getElementById("modal_parent");
const tags = [...modal.querySelectorAll('select, input, textarea, button, a, li')].filter(e1 => window.getComputedStyle(e1).getPropertyValue('display') === 'block');
const focusable = modal.querySelectorAll('button, [href], input, select, textarea, li, a,[tabindex]:not([tabindex="-1"])');
const firstFocusable = focusable[0];
const lastFocusable = focusable[focusable.length - 1];
if (e.ctrlKey || e.altKey) {
return;
}
const keys = {
9: () => { //9 = TAB
if (e.shiftKey && e.target === firstFocusable) {
lastFocusable.focus();
}
if (e.target === lastFocusable) {
firstFocusable.focus();
}
}
};
if (keys[e.keyCode]) {
keys[e.keyCode]();
}
}
}
One of the problems is that you are using keyup instead of keydown. The keyup will only fire after the tab has already fired. However, making that change to your code results in the keyboard being trapped on one of the links. The code is flawed.
Here is some code that does what you want (using jQuery)
http://dylanb.github.io/javascripts/periodic-1.1.js
// Add keyboard handling for TAB circling
$modal.on('keydown', function (e) {
var cancel = false;
if (e.ctrlKey || e.metaKey || e.altKey) {
return;
}
switch(e.which) {
case 27: // ESC
$modal.hide();
lastfocus.focus();
cancel = true;
break;
case 9: // TAB
if (e.shiftKey) {
if (e.target === links[0]) {
links[links.length - 1].focus();
cancel = true;
}
} else {
if (e.target === links[links.length - 1]) {
links[0].focus();
cancel = true;
}
}
break;
}
if (cancel) {
e.preventDefault();
}
});
You can see a working version of this dialog here
http://dylanb.github.io/periodic-aria11-attributes.html
Click the text in one of the colored boxes to see the dialog pop up.
The e.preventDefault() has no effect on the keyup event (as the default browser action has already been fired)
Despite this, your example works. But only if there are links before and after the modal
If you change your HTML code with the following, adding one link before and one link after the modal; you will see that your focus is trapped in the modal:
other link
<div id="modal">
Link One
Link Two
</div>
other link
That's because there is no default browser action in such case, and then no action to prevent.
Trapping focus within a modal is very hard to do it on your own. If you're able to install third-party dependencies in your project, you can use the focus-trap package.
You can easily trap focus to any component with vanilla Javascript;
import { createFocusTrap } from 'focus-trap'
const modal = document.getElementById('modal')
const focusTrap = createFocusTrap('#modal', {
onActivate: function () {
modal.className = 'trap is-visible'
},
onDeactivate: function () {
modal.className = 'trap'
},
})
document.getElementById('show').addEventListener('click', function () {
focusTrap.activate()
})
document.getElementById('hide').addEventListener('click', function () {
focusTrap.deactivate()
})
or even React;
import React from 'react'
import ReactDOM from 'react-dom'
// Use the wrapper package of `focus-trap` to use with React.
import FocusTrap from 'focus-trap-react'
const Demo = () => {
const [showModal, setShowModal] = React.useState(false)
return (
<div>
<button onClick={() => setShowModal(true)}>show modal</button>
<FocusTrap active={showModal}>
<div id="modal">
Modal with with some{' '}
focusable elements.
<button onClick={() => setShowModal(false)}>
hide modal
</button>
</div>
</FocusTrap>
</div>
)
}
ReactDOM.render(<Demo />, document.getElementById('demo'))
I did a small write-up about the package here, which explains how to use it with either vanilla Javascript or React.
I thought I had solved trapping the focus on a modal by using tab, shift+tab, and arrow keys detection on keyup and keydown, focus, focusin, focusout on the first and last focusable elements inside the modal and a focus event for the window to set the focus back on the first focusable element on the form in case the focus "escaped" the modal or for situations like jumping from the address bar to the document using tab, but something weird happened. I had activated "Caret Browsing" in one of my browsers accidently, and that's when I realized all methods to trap focus failed miserably. I personally went on a rabbit whole to solve this for a modal. I tried focusin, focusout on the modal, matching focus-within pseudo classes, {capture: true} on the focus event from the modal and window, nothing worked.
This is how I solved it.
I recreated the modal to have a different structure. For the sake of simplicity, I am omitting a lot of things, like the aria attributes, classes, how to get all focusable elements, etc.
<component-name>
#shadow-root (closed)
<div class="wrapper">
<div class="backdrop"></div>
<div class="window>
<div tabindex="0" class="trap-focus-top"> </div>
<div class="content">
<div class="controls"><!-- Close button, whatever --></div>
<header><slot name="header"></slot></header>
<div class="body"><slot></slot></div>
<footer><slot name="footer"></slot></footer>
</div>
<div tabindex="0" class="trap-focus-bottom"> </div>
</div>
</div>
</component-name>
Search the contents div for focusable elements, to save the first and last one. If you find only one then that one will be first and last. If you find zero, then set the div for the body (.body) tabindex to "0" so that you have at least one element to set the focus on.
Before and after the content div we have two focusable divs, trap-focus-top and trap-focus-bottom, the first one when getting focus will jump the focus to the last focusable element detected on step one, and the second one will jump the focus to the first focusable element detected on step one. No need to capture any key events, just focus event on these elements. If you notice the non-breaking space on trap-focus elements, this is for mimicking content, because I noticed that the arrow keys went through these elements without firing any events when empty. When I realized this I added some content and everything worked, so I added a non-breaking space and styled the elements so that they do not occupy any space.
Capture all focus events from the window with the use capture flag set to true, so that every focus event whose target was different to the component (focus events inside the shadow-root wont't be captured with the actual target but the component itself) will result in the focus being set on the modal elements again.
Now there's another problem, let's say there's zero focusable elements on your modal outside of any controls, like a button to close the modal, then we set tabindex to 0 on the modal's body, your focus should go from the close button to the modal's body and vice versa, now, the caret browsing won't work on the content because the div.body will have the focus, not the actual content. This means I have to create another function that places the cursor at the beginning of the content whenever the body receives the focus.
startCursor = () => {
/* componentbody is a placeholder for the element with the actual content */
let text = componentbody.childNodes[0];
if (text) {
let range = new Range();
let selection = document.getSelection();
range.setStart(text, 0);
range.setEnd(text, 0);
selection.removeAllRanges();
selection.addRange(range);
componentbody.scrollTop = 0;/* In case the body has a scrollbar */
}
}
For anyone out there, this is what worked for me.

jquery select dropdown ignores keydown event when it's opened?

I'm trying to prevent backspace button to go one page back in every browser. For now I'm using this code:
$(document).on("keydown", function (e) {
if (e.which === 8 && !$(e.target).is("input, textarea")) {
e.preventDefault();
}
});
It works fine for everything except when a select field dropdown list is opened, this event is ignored and a backspace takes me one page back anyway. How can I solve this problem? Thank you for your answers.
Just for the info, this is Google Chrome specific since your code works fine in IE and FF.
If you really need this to work you could render a fake dropdown and than set select programmaticly.
You can change the size of dropdown to appear as it was open, something like this:
https://jsfiddle.net/yzr2cmqv/
<div clas="select-wrap">
<div class="fake-select"></div>
<select class="select">
<option value="1">one</option>
<option value="2">two</option>
<option value="3">three</option>
</select>
</div>
$(".fake-select").on("click", function () {
var numOfOpen = $("select.select option").size();
$(".select").attr("size", numOfOpen).css("overflow", "hidden");
$(this).hide();
});
$(".select").on("click", function () {
$(".select").attr("size", 1);
$(".fake-select").show();
});
Other than that I don't think you can do anything since Chrome events are not firing when dropdown is open.
Just add the select tag to your selector:
$(document).on("keydown", function (e) {
if (e.which === 8 && !$(e.target).is("input, textarea, select")) {
e.preventDefault();
}
});
You'll have to check for keydown event on $(select) right now, if you add
console.log(e.which)
console.log(e.target)
you'll notice that you won't get the keydown event when you click and press your keyboard while the select dropdown is active.
This will make it for your
$(document).on("keydown", $('select'), function (e) {
if (e.which === 8) {
e.preventDefault();
}
});

Select2 open dropdown on focus

I have a form with multiple text inputs and some select2 elements.
Using the keyboard to tab between fields works fine - the Select2 element behaves like a form element and receives focus when tabbing.
I was wondering if it is possible to open the dropdown when the Select2 element gets focus.
Here's what I've tried so far:
$("#myid").select2().on('select2-focus', function(){
$(this).select2('open');
});
But using this code makes the dropdown to open again after a selection is made.
Working Code for v4.0+ *(including 4.0.7)
The following code will open the menu on the initial focus, but won't get stuck in an infinite loop when the selection re-focuses after the menu closes.
// on first focus (bubbles up to document), open the menu
$(document).on('focus', '.select2-selection.select2-selection--single', function (e) {
$(this).closest(".select2-container").siblings('select:enabled').select2('open');
});
// steal focus during close - only capture once and stop propogation
$('select.select2').on('select2:closing', function (e) {
$(e.target).data("select2").$selection.one('focus focusin', function (e) {
e.stopPropagation();
});
});
Explanation
Prevent Infinite Focus Loop
Note: The focus event is fired twice
Once when tabbing into the field
Again when tabbing with an open dropdown to restore focus
We can prevent an infinite loop by looking for differences between the types of focus events. Since we only want to open the menu on the initial focus to the control, we have to somehow distinguish between the following raised events:
Doing so it a cross browser friendly way is hard, because browsers send different information along with different events and also Select2 has had many minor changes to their internal firing of events, which interrupt previous flows.
One way that seems to work is to attach an event handler during the closing event for the menu and use it to capture the impending focus event and prevent it from bubbling up the DOM. Then, using a delegated listener, we'll call the actual focus -> open code only when the focus event bubbles all the way up to the document
Prevent Opening Disabled Selects
As noted in this github issue #4025 - Dropdown does not open on tab focus, we should check to make sure we only call 'open' on :enabled select elements like this:
$(this).siblings('select:enabled').select2('open');
Select2 DOM traversal
We have to traverse the DOM a little bit, so here's a map of the HTML structure generated by Select2
Source Code on GitHub
Here are some of the relevant code sections in play:
.on('mousedown' ... .trigger('toggle')
.on('toggle' ... .toggleDropdown()
.toggleDropdown ... .open()
.on('focus' ... .trigger('focus'
.on('close' ... $selection.focus()
It used to be the case that opening select2 fired twice, but it was fixed in Issue #3503 and that should prevent some jank
PR #5357 appears to be what broke the previous focus code that was working in 4.05
Working Demo in jsFiddle & Stack Snippets:
$('.select2').select2({});
// on first focus (bubbles up to document), open the menu
$(document).on('focus', '.select2-selection.select2-selection--single', function (e) {
$(this).closest(".select2-container").siblings('select:enabled').select2('open');
});
// steal focus during close - only capture once and stop propogation
$('select.select2').on('select2:closing', function (e) {
$(e.target).data("select2").$selection.one('focus focusin', function (e) {
e.stopPropagation();
});
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.7/css/select2.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.7/js/select2.js"></script>
<select class="select2" style="width:200px" >
<option value="1">Apple</option>
<option value="2">Banana</option>
<option value="3">Carrot</option>
<option value="4">Donut</option>
</select>
Tested on Chrome, FF, Edge, IE11
For Version 3.5.4 (Aug 30, 2015 and earlier)
The current answer is only applicable to versions 3.5.4 and before, where select2 fired blur and focus events (select2-focus & select2-blur). It attaches a one-time use handler using $.one to catch the initial focus, and then reattaches it during blur for subsequent uses.
$('.select2').select2({})
.one('select2-focus', OpenSelect2)
.on("select2-blur", function (e) {
$(this).one('select2-focus', OpenSelect2)
})
function OpenSelect2() {
var $select2 = $(this).data('select2');
setTimeout(function() {
if (!$select2.opened()) { $select2.open(); }
}, 0);
}
I tried both of #irvin-dominin-aka-edward's answers, but also ran into both problems (having to click the dropdown twice, and that Firefox throws 'event is not defined').
I did find a solution that seems to solve the two problems and haven't run into other issue yet. This is based on #irvin-dominin-aka-edward's answers by modifying the select2Focus function so that instead of executing the rest of the code right away, wrap it in setTimeout.
Demo in jsFiddle & Stack Snippets
$('.select2').select2({})
.one('select2-focus', OpenSelect2)
.on("select2-blur", function (e) {
$(this).one('select2-focus', OpenSelect2)
})
function OpenSelect2() {
var $select2 = $(this).data('select2');
setTimeout(function() {
if (!$select2.opened()) { $select2.open(); }
}, 0);
}
body {
margin: 2em;
}
.form-control {
width: 200px;
margin-bottom: 1em;
padding: 5px;
display: flex;
flex-direction: column;
}
select {
border: 1px solid #aaa;
border-radius: 4px;
height: 28px;
}
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/select2/3.5.4/select2.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/select2/3.5.4/select2.js"></script>
<div class="form-control">
<label for="foods1" >Normal</label>
<select id="foods1" >
<option value=""></option>
<option value="1">Apple</option>
<option value="2">Banana</option>
<option value="3">Carrot</option>
<option value="4">Donut</option>
</select>
</div>
<div class="form-control">
<label for="foods2" >Select2</label>
<select id="foods2" class="select2" >
<option value=""></option>
<option value="1">Apple</option>
<option value="2">Banana</option>
<option value="3">Carrot</option>
<option value="4">Donut</option>
</select>
</div>
Something easy that would work on all select2 instances on the page.
$(document).on('focus', '.select2', function() {
$(this).siblings('select').select2('open');
});
UPDATE: The above code doesn't seem to work properly on IE11/Select2 4.0.3
PS: also added filter to select only single select fields. Select with multiple attribute doesn't need it and would probably break if applied.
var select2_open;
// open select2 dropdown on focus
$(document).on('focus', '.select2-selection--single', function(e) {
select2_open = $(this).parent().parent().siblings('select');
select2_open.select2('open');
});
// fix for ie11
if (/rv:11.0/i.test(navigator.userAgent)) {
$(document).on('blur', '.select2-search__field', function (e) {
select2_open.select2('close');
});
}
Probably after the selection is made a select2-focus event is triggered.
The only way I found is a combination of select2-focus and select2-blur event and the jQuery one event handler.
So the first time the element get the focus, the select2 is opened for one time (because of one), when the element is blurred the one event handler is attached again and so on.
Code:
$('#test').select2({
data: [{
id: 0,
text: "enhancement"
}, {
id: 1,
text: "bug"
}, {
id: 2,
text: "duplicate"
}, {
id: 3,
text: "invalid"
}, {
id: 4,
text: "wontfix"
}],
width: "300px"
}).one('select2-focus', select2Focus).on("select2-blur", function () {
$(this).one('select2-focus', select2Focus)
})
function select2Focus() {
$(this).select2('open');
}
Demo: http://jsfiddle.net/IrvinDominin/fnjNb/
UPDATE
To let the mouse click work you must check the event that fires the handler, it must fire the open method only if the event is focus
Code:
function select2Focus() {
if (/^focus/.test(event.type)) {
$(this).select2('open');
}
}
Demo: http://jsfiddle.net/IrvinDominin/fnjNb/4/
UPDATE FOR SELECT2 V 4.0
select2 v 4.0 has changed its API's and dropped the custom events (see https://github.com/select2/select2/issues/1908). So it's necessary change the way to detect the focus on it.
Code:
$('.js-select').select2({
placeholder: "Select",
width: "100%"
})
$('.js-select').next('.select2').find('.select2-selection').one('focus', select2Focus).on('blur', function () {
$(this).one('focus', select2Focus)
})
function select2Focus() {
$(this).closest('.select2').prev('select').select2('open');
}
Demo: http://jsfiddle.net/IrvinDominin/xfmgte70/
a bit late... but to share my code using select2 4.0.0
$("#my_id").select2();
$("#my_id").next(".select2").find(".select2-selection").focus(function() {
$("#my_id").select2("open");
});
Here is an alternate solution for version 4.x of Select2. You can use listeners to catch the focus event and then open the select.
$('#test').select2({
// Initialisation here
}).data('select2').listeners['*'].push(function(name, target) {
if(name == 'focus') {
$(this.$element).select2("open");
}
});
Find the working example here based the exampel created by #tonywchen
KyleMit's answer worked for me (thank you!), but I noticed that with select2 elements that allow for searching, trying to tab to the next element wouldn't work (tab order was effectively lost), so I added code to set focus back to the main select2 element when the dropdown is closing:
$(document).on('focus', '.select2', function (e) {
if (e.originalEvent) {
var s2element = $(this).siblings('select');
s2element.select2('open');
// Set focus back to select2 element on closing.
s2element.on('select2:closing', function (e) {
s2element.select2('focus');
});
}
});
The problem is, that the internal focus event is not transformed to jQuery event, so I've modified the plugin and added the focus event to the EventRelay on line 2063 of Select2 4.0.3:
EventRelay.prototype.bind = function (decorated, container, $container) {
var self = this;
var relayEvents = [
'open', 'opening',
'close', 'closing',
'select', 'selecting',
'unselect', 'unselecting',
'focus'
]};
Then it is enough to open the select2 when the focus occurs:
$('#select2').on('select2:focus', function(evt){
$(this).select2('open');
});
Works well on Chrome 54, IE 11, FF 49, Opera 40
I tried a number of these and finally came up with the following that works for me with Select2 4.0.1. element is the <select> element.
$.data(element).select2.on("focus", function (e) {
$(element).select2("open");
});
For me using Select2.full.js Version 4.0.3 none of the above solutions was working the way it should be.
So I wrote a combination of the solutions above.
First of all I modified Select2.full.js to transfer the internal focus and blur events to jquery events as "Thomas Molnar" did in his answer.
EventRelay.prototype.bind = function (decorated, container, $container) {
var self = this;
var relayEvents = [
'open', 'opening',
'close', 'closing',
'select', 'selecting',
'unselect', 'unselecting',
'focus', 'blur'
];
And then I added the following code to handle focus and blur and focussing the next element
$("#myId").select2( ... ).one("select2:focus", select2Focus).on("select2:blur", function ()
{
var select2 = $(this).data('select2');
if (select2.isOpen() == false)
{
$(this).one("select2:focus", select2Focus);
}
}).on("select2:close", function ()
{
setTimeout(function ()
{
// Find the next element and set focus on it.
$(":focus").closest("tr").next("tr").find("select:visible,input:visible").focus();
}, 0);
});
function select2Focus()
{
var select2 = $(this).data('select2');
setTimeout(function() {
if (!select2.isOpen()) {
select2.open();
}
}, 0);
}
I've had the problem which was two pronged:
1. In a form with multiple select2 elements, the dropdown won't open on tab, and you need to press space key to open it
2. Once you have made a selection, the tabindex won't be honored and you have to manually click on the next input field
While the usual suggestions worked, I came up with my own version, since a library script was doing the conversion of normal select to select2, and hence I had no control over this initialization.
Here is the code that worked for me.
Tab to open
$(document).on("focus", ".select2", function() {
$(this).siblings("select").select2("open");
});
Move to next on selection
var inputs = $("input,select"); // You can use other elements such as textarea, button etc.
//depending on input field types you have used
$("select").on("select2:close",function(){
var pos = $(inputs).index(this) + 1;
var next = $(inputs).eq(pos);
setTimeout( function() {
next.focus();
if (next.siblings(".select2").length) { //If it's a select
next.select2("open");
}
}, 500); //The delay is required to allow default events to occur
});
Hope this helps.
an important thing is to keep the multiselect open all the time. The simplest way is to fire open event on 'conditions' in your code:
<select data-placeholder="Choose a Country..." multiple class="select2-select" id="myList">
<option value="United States">United States</option>
<option value="United Kingdom">United Kingdom</option>
<option value="Afghanistan">Afghanistan</option>
<option value="Aland Islands">Aland Islands</option>
<option value="Albania">Albania</option>
<option value="Algeria">Algeria</option>
</select>
javascript:
$(".select2-select").select2({closeOnSelect:false});
$("#myList").select2("open");
fiddle: http://jsfiddle.net/xpvt214o/153442/
This worked for me using Select2 v4.0.3
//Initialize Select2
jQuery('.js-select').select2();
// Make Select2 respect tab focus
function select2Focus(){
jQuery(window).keyup(function (e) {
var code = (e.keyCode ? e.keyCode : e.which);
if (code == 9 && jQuery('.select2-search__field:focus').length) {
jQuery('.js-select').select2('open');
}
});
}
select2Focus();
Fork of Irvin Dominin's demo: http://jsfiddle.net/163cwdrw/
I tried these solutions with the select2 version 3.4.8 and found that when you do blur, the select2 triggers first select2-close then select2-focus and then select2-blur, so at the end we end up reopening forever the select2.
Then, my solution is this one:
$('#elemId').on('select2-focus', function(){
var select2 = $(this).data('select2');
if( $(this).data('select2-closed') ){
$(this).data('select2-closed', false)
return
}
if (!select2.opened()) {
select2.open()
}
}).on('select2-close', function(){
$(this).data('select2-closed', true)
})
Somehow select2Focus didn't work here with empty selection, couldn't figured out the issue, therefore I added manual control when after focus event auto open get's triggered.
Here is coffeescript:
$("#myid").select2()
.on 'select2-blur', ->
$(this).data('select2-auto-open', 'true')
.on 'select2-focus', ->
$(this).data('select2').open() if $(this).data('select2-auto-open') != 'false'
.on 'select2-selecting', ->
$(this).data('select2-auto-open', 'false')
I've tried a pretty ugly solution but it fixed my problem.
var tabPressed = false;
$(document).keydown(function (e) {
// Listening tab button.
if (e.which == 9) {
tabPressed = true;
}
});
$(document).on('focus', '.select2', function() {
if (tabPressed) {
tabPressed = false;
$(this).siblings('select').select2('open');
}
});
You can use this :
$(document).on('select2:open', () => {
document.querySelector('.select2-search__field').focus();
});

JavaScript click has different behavior than manual one

With prototype I'm listening for a click event on several checkboxes. On checkbox click I want to disable all <select> elements. I'm using prototype. So, I have this code:
$$('.silhouette-items input[type="checkbox"]').invoke('observe', 'click', function(event) {
var liItem = this.up('li.item');
if(this.checked) {
alert('checked');
liItem.removeClassName('inactive');
var selectItem = liItem.select('select');
for(i=0;i<selectItem.length;i++) {
selectItem[i].disabled=false;
if (selectItem[i].hasClassName('super-attribute-select')) {
selectItem[i].addClassName('required-entry');
}
}
} else {
alert('unchecked');
liItem.addClassName('inactive');
var selectItem = liItem.select('select');
for(i=0;i<selectItem.length;i++){
selectItem[i].disabled=true;
if (selectItem[i].hasClassName('super-attribute-select')) {
selectItem[i].removeClassName('required-entry');
}
}
}
calculatePrice();
});
When I manually click on the checkbox, everything seems to be fine. All elements are disabled as wanted.
However, I have also this button which on click event it fires one function which fires click event on that checkbox.
In Opera browser it works. In others, not. It's like Opera first (un)check and then executes event. Firefox first fires event, then (un)check element.
I don't know how to fix it.
The HTML:
<ul class="silhouette-items">
<li>
<input type="checkbox" checked="checked" id="include-item-17" class="include-item"/>
<select name="super_attribute[17][147]">(...)</select>
<select name="super_group[17]">(...)</select>
<button type="button" title="button" onclick="addToCart(this, 17)">Add to cart</button>
</li>
<!-- Repeat li few time with another id -->
</ul>
Another JS:
addToCart = function(button, productId) {
inactivateItems(productId);
productAddToCartForm.submit(button);
}
inactivateItems = function(productId) {
$$('.include-item').each(function(element) {
var itemId = element.id.replace(/[a-z-]*/, '');
if (itemId != productId && element.checked) {
simulateClickOnElement(element);
}
if (itemId == productId && !element.checked) {
simulateClickOnElement(element);
}
});
}
simulateClickOnElement = function(linkElement) {
fireEvent(linkElement, 'click');
}
Where fireEvent is a Magento function that triggers an event
Don't bother simulating a onclick if you can get away with not doing so. Having a separate function that can be called from within the event handler and from outside should work in your case.
var handler = function(){
//...
}
var nodeW = $('#node');
handler.call(nodeW);
Of course, this doesn't trigger all onclick handlers there might be but it is simpler so it should work all right. Points to note for when you use .call to call a function:
Whatever you pass as the first parameter is used as the this inside the call. I don't recall exactly what JQuery sets the this too but you should try passing the same thing for consistency.
The other parameters become the actual parameters of the function. In my example I don't pass any since we don't actually use the event object and also since I don't know how to emulate how JQuery creates that object as well.
replacing
fireEvent(linkElement, 'click');
with
linkElement.click();
works in firefox 5 and safari 5.1, so maybe the problem lies in the fireEvent() method.

Categories

Resources