I have a checkbox element with the ID "fullscreen." Clicking the checkbox puts the user into fullscreen mode. However, if the user exits the fullscreen mode using f11, the checkbox remains checked.
I am looking for a script which does as follows:
If the checkbox "fullscreen" is checked, and an "onfullscreenchange"
event occurs
Then, uncheck checkbox with the ID "fullscreen."
This is what I have right now:
It does not work--
var el = document.getElementById('fullscreen');
if (el.checked == true){
document.onfullscreenchange = function ( event ) {
el.checked = false;
};
}) );
How do I do this? Please help!
You can use onfullscreenchange event listener with vendor prefixed to listen to fullscreen change and do whatever required, something like.
document.addEventListener("webkitfullscreenchange", function (event) {
var el = document.getElementById('fullscreen');
if(document.webkitFullscreenElement === null) {
elem.checked = false
}
});
Alternatively
["fullscreenchange", "webkitfullscreenchange", "mozfullscreenchange", "msfullscreenchange"].forEach(
prefixed => document.addEventListener(prefixed, function() {
var el = document.getElementById('fullscreen');
el.checked = false;
})
);
I am asking after reading and following these answers.
jQuery prevent multiple clicks until animation is done
how to prevent click queue build up, using toggle in jquery? tried using bind/unbind('click')
Despite having e.stopPropagation(); and if ($element.is(':animated')){return false;} in place fast clicks bubble.
Here is my fiddle. Slow clicks work fine, fast clicks make it fail. What am I doing wrong please? How can I discard all fast clicks while the menu items are animated?
Your code was solid, however, you were adding the class BEFORE checking for animation.
I simply moved your animation check up, and prevented the click from even changing the class.
// jQuery 1.11.0 on DOM ready
var $hamburgerMenuButton = $('#burgerButton');
var $navTitle = $('.navigation-item');
var $score = $('.score');
$hamburgerMenuButton.click(function(e) {
e.stopPropagation();
if ( !$hamburgerMenuButton.hasClass('open')) {
console.log('hamburgerMenuButton does NOT have class open');
if ( $navTitle.is(':animated') ) {
$score.append('<br>animating ');
return false;
}
$hamburgerMenuButton.addClass('open');
console.log('class open ADDED to hamburgerMenuButton');
$score.append('<br>class open ADDED ');
var delay = 0;
$navTitle.each(function(){
$(this).delay(delay).animate({
'margin-left':'0px'
},500,'easeOutQuint');
delay += 33;
}); // animation end
$score.append('<br>clicked ');
} // if end
else {
console.log('hamburgerMenuButton does HAVE class open');
if ( $navTitle.is(':animated') ) {
$score.append('<br>animating ');
return false;
}
$hamburgerMenuButton.removeClass('open');
console.log('class open REMOVED from hamburgerMenuButton');
$score.append('<br>class open REMOVED ');
var delay = 0;
$navTitle.each(function(){
$(this).delay(delay).animate({
'margin-left':'10em'
},500,'easeOutQuint');
delay += 33;
}); // animation end
$score.append('<br>clicked again');
} // else end
}); // $hamburgerMenuButton click end
https://jsfiddle.net/gregborbonus/b2tw65hf/3/
I've got a weird bug happening on a music library site that I'm working on. The intended functionality (take a form of checkboxes, dynamically change them into selectable words that highlight and "check" the associated checkbox on click, then automatically update the songs below based on the highlighted tags) works fine -- but when you click a selected tag to remove it, it does the correct functionality with the data below and the highlight is removed, but all other selected tags are getting "display: none" added to them.
Here, I think, is the function causing the weird issue:
// Given an label "$label," if it hasn't been selected, then
// highlight the label area and set the "checked" value of the
// appropriate checkbox input to true; if it is already selected,
// remove the highlight and set the "checked" value of the appropriate
// checkbox to "false"
function highlightAndCheck( $label )
{
var id = $label.attr("id"),
$checkbox = $label.prev(),
val = $checkbox.attr("value");
if( id === val )
{
if( $label.hasClass("clicked") )
{
$checkbox.prop("checked", false);
$label.removeClass("clicked");
} else
{
$checkbox.prop("checked", true);
$label.addClass("clicked");
}
}
}
Here's the full jQuery code for the page. I can provide more code if anything is confusing, but I hope the labeling, etc. are straightforward:
$(function() { //on document ready
var $categoriesAndTags = $("div#categories-and-tags"),
$tagCategory = $categoriesAndTags.find("div.tag-category"),
$searchButton = $categoriesAndTags.find("input#public-user-tag-search-submit");
// First, hide the checkboxes and search button, since we don't need them in the dynamic version
$tagCategory.addClass("tag-spinner-skin")
.find("input[type=checkbox]").hide();
$tagCategory.find("br").hide();
$searchButton.hide();
// Make it so then clicking on the text of a tag makes the hidden select box "select"
$tagCategory.find("label").each(function(){
$(this).on("click",function(){
var $this = $(this);
highlightAndCheck( $this );
searchByTags();
//While the unwanted "display:none" bug is happening, use this to make them re-appear on next click
$this.siblings("label").show();
});
});
// Make the search update automatically when a select box is clicked or unclicked.
var tagIDs = getUrlVarValArray( "tagID" );
$tagCategory.find("label").each(function(){
var $this = $(this),
id = $this.attr("id");
if( tagIDs.indexOf( id ) > -1 )
{ highlightAndCheck( $this ); }
});
});
function searchByTags( tags )
{
data = $("form#tag-select input").serialize()
if( data.length > 0 )
{
data += "&search=search";
}
$.ajax({
url: "songs/",
data: data,
type: "GET",
success: function(data){
// DO THIS if we successfully do the Ajax call
$newSongPlayers = $(data).find("div#songs-area");
$("div#songs-area").replaceWith( $newSongPlayers );
$.getScript("javascripts/public.js");
}
});
}
// Given an label "$label," if it hasn't been selected, then
// highlight the label area and set the "checked" value of the
// appropriate checkbox input to true; if it is already selected,
// remove the highlight and set the "checked" value of the appropriate
// checkbox to "false"
function highlightAndCheck( $label )
{
var id = $label.attr("id"),
$checkbox = $label.prev(),
val = $checkbox.attr("value");
if( id === val )
{
if( $label.hasClass("clicked") )
{
$checkbox.prop("checked", false);
$label.removeClass("clicked");
} else
{
$checkbox.prop("checked", true);
$label.addClass("clicked");
}
}
}
function getUrlVarValArray( needle )
{
var results = [],
hash,
hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
for(var i = 0; i < hashes.length; i++)
{
hash = hashes[i].split('=');
if( needle.length > 0 )
{
if( hash[0] === needle )
{
results[ results.length ] = hash[1]; // array[ array.length ] is a faster way to apend than array.push() in small arrays
}
}
}
return results;
}
Thanks in advance for your help! If it's helpful to login and see this in context, please go to the test site and use username: stackoverflowuser; password: HelpMeFigureThisOut -- once you're logged in, click on "View Songs"and the jQuery is referencing the tags at the top of the page.
Look at the following code in the public.js file:
$("html").on("click", function(event){
if(!$(event.target).is('.options-button')
&& !$(event.target).is('input.add-new-playlist')
&& !$(event.target).is('button.submit-new-playlist')
&& !$(event.target).is('button.add-song-to-playlist')
&& !$(event.target).is('button.playlist-popup-button')
)
{
if(!$(event.target).is('.clicked') && !$(event.target).is('.clicked > div') )
{ $(".clicked").hide().removeClass("clicked"); }
}
});
This handler gets executed because the click event propagates from the <label> element to the <html> element. It executes after the click handler on the <label> element, which removes the "clicked" class from the <label> element. Since the <label> element is the event.target and no longer has the "clicked" class, the following line is executed:
$(".clicked").hide().removeClass("clicked");
That line hides all the labels that still have the "clicked" class.
On my current project we have some modal panes that open up on certain actions. I am trying to get it so that when that modal pane is open you can't tab to an element outside of it. The jQuery UI dialog boxes and the Malsup jQuery block plugins seem to do this but I am trying to get just that one feature and apply it in my project and it's not immediately obvious to me how they are doing that.
I've seen that some people are of the opinion that tabbing shouldn't be disabled and I can see that point of view but I am being given the directive to disable it.
This is just expanding on Christian answer, by adding the additional input types and also taking into consideration the shift+tab.
var inputs = $element.find('select, input, textarea, button, a').filter(':visible');
var firstInput = inputs.first();
var lastInput = inputs.last();
/*set focus on first input*/
firstInput.focus();
/*redirect last tab to first input*/
lastInput.on('keydown', function (e) {
if ((e.which === 9 && !e.shiftKey)) {
e.preventDefault();
firstInput.focus();
}
});
/*redirect first shift+tab to last input*/
firstInput.on('keydown', function (e) {
if ((e.which === 9 && e.shiftKey)) {
e.preventDefault();
lastInput.focus();
}
});
I was finally able to accomplish this at least somewhat by giving focus to the first form element within the modal pane when that modal pane is open and then if the Tab key is pressed while focus is on the last form element within the modal pane then the focus goes back to the first form element there rather than to the next element in the DOM that would otherwise receive focus. A lot of this scripting comes from jQuery: How to capture the TAB keypress within a Textbox:
$('#confirmCopy :input:first').focus();
$('#confirmCopy :input:last').on('keydown', function (e) {
if ($("this:focus") && (e.which == 9)) {
e.preventDefault();
$('#confirmCopy :input:first').focus();
}
});
I may need to further refine this to check for the pressing of some other keys, such as arrow keys, but the basic idea is there.
Good solutions by Christian and jfutch.
Its worth mentioning that there a few pitfalls with hijacking the tab keystroke:
the tabindex attribute might be set on some elements inside the modal pane in such a way that the dom order of elements does not follow the tab order. (Eg. setting tabindex="10" on the last tabbable element can make it first in the tab order)
If the user interacts with an element outside the modal that doesn't trigger the modal to close you can tab outside the modal window. (Eg. click the location bar and start tabbing back to the page, or open up page landmarks in a screenreader like VoiceOver & navigate to a different part of the page)
checking if elements are :visible will trigger a reflow if the dom is dirty
The document might not have a :focussed element. In chrome its possible to change the 'caret' position by clicking on a non-focussable element then pressing tab. Its possible that the user could set the caret position past the last tabbable element.
I think a more robust solution would be to 'hide' the rest of the page by setting tabindex to -1 on all tabbable content, then 'unhide' on close. This will keep the tab order inside the modal window and respect the order set by tabindex.
var focusable_selector = 'a[href], area[href], input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]';
var hide_rest_of_dom = function( modal_selector ) {
var hide = [], hide_i, tabindex,
focusable = document.querySelectorAll( focusable_selector ),
focusable_i = focusable.length,
modal = document.querySelector( modal_selector ),
modal_focusable = modal.querySelectorAll( focusable_selector );
/*convert to array so we can use indexOf method*/
modal_focusable = Array.prototype.slice.call( modal_focusable );
/*push the container on to the array*/
modal_focusable.push( modal );
/*separate get attribute methods from set attribute methods*/
while( focusable_i-- ) {
/*dont hide if element is inside the modal*/
if ( modal_focusable.indexOf(focusable[focusable_i]) !== -1 ) {
continue;
}
/*add to hide array if tabindex is not negative*/
tabindex = parseInt(focusable[focusable_i].getAttribute('tabindex'));
if ( isNaN( tabindex ) ) {
hide.push([focusable[focusable_i],'inline']);
} else if ( tabindex >= 0 ) {
hide.push([focusable[focusable_i],tabindex]);
}
}
/*hide the dom elements*/
hide_i = hide.length;
while( hide_i-- ) {
hide[hide_i][0].setAttribute('data-tabindex',hide[hide_i][1]);
hide[hide_i][0].setAttribute('tabindex',-1);
}
};
To unhide the dom you would just query all elements with the 'data-tabindex' attribute &
set the tabindex to the attribute value.
var unhide_dom = function() {
var unhide = [], unhide_i, data_tabindex,
hidden = document.querySelectorAll('[data-tabindex]'),
hidden_i = hidden.length;
/*separate the get and set attribute methods*/
while( hidden_i-- ) {
data_tabindex = hidden[hidden_i].getAttribute('data-tabindex');
if ( data_tabindex !== null ) {
unhide.push([hidden[hidden_i], (data_tabindex == 'inline') ? 0 : data_tabindex]);
}
}
/*unhide the dom elements*/
unhide_i = unhide.length;
while( unhide_i-- ) {
unhide[unhide_i][0].removeAttribute('data-tabindex');
unhide[unhide_i][0].setAttribute('tabindex', unhide[unhide_i][1] );
}
}
Making the rest of the dom hidden from aria when the modal is open is slightly easier. Cycle through all the
relatives of the modal window & set the aria-hidden attribute to true.
var aria_hide_rest_of_dom = function( modal_selector ) {
var aria_hide = [],
aria_hide_i,
modal_relatives = [],
modal_ancestors = [],
modal_relatives_i,
ancestor_el,
sibling, hidden,
modal = document.querySelector( modal_selector );
/*get and separate the ancestors from the relatives of the modal*/
ancestor_el = modal;
while ( ancestor_el.nodeType === 1 ) {
modal_ancestors.push( ancestor_el );
sibling = ancestor_el.parentNode.firstChild;
for ( ; sibling ; sibling = sibling.nextSibling ) {
if ( sibling.nodeType === 1 && sibling !== ancestor_el ) {
modal_relatives.push( sibling );
}
}
ancestor_el = ancestor_el.parentNode;
}
/*filter out relatives that aren't already hidden*/
modal_relatives_i = modal_relatives.length;
while( modal_relatives_i-- ) {
hidden = modal_relatives[modal_relatives_i].getAttribute('aria-hidden');
if ( hidden === null || hidden === 'false' ) {
aria_hide.push([modal_relatives[modal_relatives_i]]);
}
}
/*hide the dom elements*/
aria_hide_i = aria_hide.length;
while( aria_hide_i-- ) {
aria_hide[aria_hide_i][0].setAttribute('data-ariahidden','false');
aria_hide[aria_hide_i][0].setAttribute('aria-hidden','true');
}
};
Use a similar technique to unhide the aria dom elements when the modal closes. Here its better
to remove the aria-hidden attribute rather than setting it to false as there might be some conflicting
css visibility/display rules on the element that take precedence & implementation of aria-hidden
in such cases is inconsistent across browsers (see https://www.w3.org/TR/2016/WD-wai-aria-1.1-20160721/#aria-hidden)
var aria_unhide_dom = function() {
var unhide = [], unhide_i, data_ariahidden,
hidden = document.querySelectorAll('[data-ariahidden]'),
hidden_i = hidden.length;
/*separate the get and set attribute methods*/
while( hidden_i-- ) {
data_ariahidden = hidden[hidden_i].getAttribute('data-ariahidden');
if ( data_ariahidden !== null ) {
unhide.push(hidden[hidden_i]);
}
}
/*unhide the dom elements*/
unhide_i = unhide.length;
while( unhide_i-- ) {
unhide[unhide_i].removeAttribute('data-ariahidden');
unhide[unhide_i].removeAttribute('aria-hidden');
}
}
Lastly I'd recommend calling these functions after an animation has ended on the element. Below is a
abstracted example of calling the functions on transition_end.
I'm using modernizr to detect the transition duration on load. The transition_end event bubbles up
the dom so it can fire more than once if more than one element is transitioning when the modal
window opens, so check against the event.target before calling the hide dom functions.
/* this can be run on page load, abstracted from
* http://dbushell.com/2012/12/22/a-responsive-off-canvas-menu-with-css-transforms-and-transitions/
*/
var transition_prop = Modernizr.prefixed('transition'),
transition_end = (function() {
var props = {
'WebkitTransition' : 'webkitTransitionEnd',
'MozTransition' : 'transitionend',
'OTransition' : 'oTransitionEnd otransitionend',
'msTransition' : 'MSTransitionEnd',
'transition' : 'transitionend'
};
return props.hasOwnProperty(transition_prop) ? props[transition_prop] : false;
})();
/*i use something similar to this when the modal window is opened*/
var on_open_modal_window = function( modal_selector ) {
var modal = document.querySelector( modal_selector ),
duration = (transition_end && transition_prop) ? parseFloat(window.getComputedStyle(modal, '')[transition_prop + 'Duration']) : 0;
if ( duration > 0 ) {
$( document ).on( transition_end + '.modal-window', function(event) {
/*check if transition_end event is for the modal*/
if ( event && event.target === modal ) {
hide_rest_of_dom();
aria_hide_rest_of_dom();
/*remove event handler by namespace*/
$( document ).off( transition_end + '.modal-window');
}
} );
} else {
hide_rest_of_dom();
aria_hide_rest_of_dom();
}
}
I have just made few changes to Alexander Puchkov's solution, and made it a JQuery plugin. It solves the problem of dynamic DOM changes in the container. If any control add it to the container on conditional, this works.
(function($) {
$.fn.modalTabbing = function() {
var tabbing = function(jqSelector) {
var inputs = $(jqSelector).find('select, input, textarea, button, a[href]').filter(':visible').not(':disabled');
//Focus to first element in the container.
inputs.first().focus();
$(jqSelector).on('keydown', function(e) {
if (e.which === 9) {
var inputs = $(jqSelector).find('select, input, textarea, button, a[href]').filter(':visible').not(':disabled');
/*redirect last tab to first input*/
if (!e.shiftKey) {
if (inputs[inputs.length - 1] === e.target) {
e.preventDefault();
inputs.first().focus();
}
}
/*redirect first shift+tab to last input*/
else {
if (inputs[0] === e.target) {
e.preventDefault();
inputs.last().focus();
}
}
}
});
};
return this.each(function() {
tabbing(this);
});
};
})(jQuery);
For anyone coming into this more recently like I was, I've taken the approaches outlined above and I've simplified them a bit to make it a bit more digestible. Thanks to #niall.campbell for the suggested approach here.
The code below can be found in this CodeSandbox for further reference and for a working example
let tabData = [];
const modal = document.getElementById('modal');
preventTabOutside(modal);
// should be called when modal opens
function preventTabOutside(modal) {
const tabbableElements = document.querySelectorAll(selector);
tabData = Array.from(tabbableElements)
// filter out any elements within the modal
.filter((elem) => !modal.contains(elem))
// store refs to the element and its original tabindex
.map((elem) => {
// capture original tab index, if it exists
const tabIndex = elem.hasAttribute("tabindex")
? elem.getAttribute("tabindex")
: null;
// temporarily set the tabindex to -1
elem.setAttribute("tabindex", -1);
return { elem, tabIndex };
});
}
// should be called when modal closes
function enableTabOutside() {
tabData.forEach(({ elem, tabIndex }) => {
if (tabIndex === null) {
elem.removeAttribute("tabindex");
} else {
elem.setAttribute("tabindex", tabIndex);
}
});
tabData = [];
}
I have a canvas which listens for mouse down event... but I wanted to make it more detailed by listening on the buttons and if they were double clicks or not.
I have this:
canvas.addEventListener("mousedown", what_button, false);
Then a function named what_button:
function what_button(e){
//check which button on the mouse
//was it a double click ?
}
Is this possible in JavaScript?
I don't think that the 'mousedown' event is able to anticipate whether or not a second mouse click will occur. You'll have to bind to both 'click' and 'dblclick' and then override the behavior if a double-click occurred...
Inside the handler, the e.button property tells you which button was clicked:
0 => left
1 => middle
2 => right
This works for me:
var dblclick;
var timeout;
$( document ).on( 'click', function ( e ) {
if ( !timeout ) {
timeout = setTimeout( function () {
timeout = 0;
handler( dblclick ? 'dblclick' : 'click' );
dblclick = false;
}, 200 );
}
});
$( document ).on( 'dblclick', function ( e ) {
dblclick = true;
});
// this function receives either 'click' or 'dblclick'
// and performs the corresponding action
function handler ( type ) {
alert( type );
}
Live demo: http://jsfiddle.net/f73tY/1/
I use a delay value of 200. I have found that (at least on my machine) a value of 100 does not detect a double-click.
Use click/dblclick:
var button = document.getElementById('myButton');
button.ondblclick = function() {
doubleClick = true;
};
You can even add an normalClick = false then use that information in your code.