I'm trying to create a help message that will disappear when the user either clicks the toggle button to display the help message or clicks away by clicking elsewhere on the page. The solution appears to be to look at the relatedTarget property of the onblur event and prevent the onblur handler from running when the relatedTarget is the button to toggle the help message. This seems to work in Chrome, but in Firefox and Safari, the relatedTarget property is set to the container div rather than the button, which makes it impossible to distinguish between the toggle button click and a "click away".
I've created a simple demonstrator that illustrates the problem:
let openState = false;
let contentDiv = document.getElementById("show-hide-content");
let accordionDiv = document.getElementById("accordion");
let showHideButton = document.getElementById("show-hide-toggle");
function setOpenState(state) {
openState = state;
if(openState) {
contentDiv.style.visibility = "visible";
contentDiv.focus();
}
else {
contentDiv.style.visibility = "hidden";
}
}
function toggleVisibility(override) {
if (typeof override === "boolean") {
setOpenState(override);
}
else {
setOpenState(!openState);
}
}
function buttonClickHandler(event) {
toggleVisibility();
}
function contentBlurHandler(event) {
if(!accordionDiv.contains(event.relatedTarget)) {
toggleVisibility(false);
}
}
showHideButton.onclick = buttonClickHandler;
contentDiv.onblur = contentBlurHandler;
.parent {
display: flex;
width: 100vw;
height: 100vh;
flex-direction: row;
background-color: #409958;
}
.accordion {
background-color: #28a7c9;
}
.show-hide-content {
visibility: hidden;
background-color: #c91e63;
}
<h1>Show/Hide Test</h1>
<div class="parent">
<div class="drawer">
<div class="accordion" id="accordion">
<button class="show-hide-toggle" id="show-hide-toggle">Show/Hide</button>
<div class="show-hide-content" id="show-hide-content" tabindex="-1">
<p>This is some content that should be shown or hidden depending on the toggle.</p>
</div>
</div>
<div class="spacer">
</div>
</div>
</div>
This code works correctly in Chrome. However, in Firefox, clicking the "Show/Hide" button displays the hidden content, but doesn't hide it when the button is clicked again. As far as I can tell, this is because the onblur handler is hiding the div, and then the onclick handler is toggling it open again, even though I'm checking onblur.relatedTarget to ensure that it's not anywhere inside the drawer.
What is the correct way to detect click-away in Firefox?
The problem is that clicking the <button> doesn't focus it on some browser/OS combinations (notably on macOS except in Chrome), so onblur's event.relatedTarget is null as nothing on the page receives focus. If you SHIFT+TAB from the <div id="show-hide-content">, you'll see that relatedTarget is set as you expect, as the button does receive focus in this scenario.
I would run the code to hide the div off a small timeout, to give the click handler a chance to run first.
In the example below I implemented this suggestion and added some logging to make it easier to see what's going on:
let openState = false;
let contentDiv = document.getElementById("show-hide-content");
let accordionDiv = document.getElementById("accordion");
let showHideButton = document.getElementById("show-hide-toggle");
function setOpenState(state) {
openState = state;
if(openState) {
contentDiv.style.visibility = "visible";
contentDiv.focus();
}
else {
contentDiv.style.visibility = "hidden";
}
}
function buttonClickHandler(event) {
console.log("click, setting openState to", !openState);
setOpenState(!openState);
}
function contentBlurHandler(event) {
console.log("blur, relatedTarget is", event.relatedTarget);
setTimeout(function() {
console.log("blur, after timeout, openState is", openState);
setOpenState(false);
}, 100);
}
showHideButton.onclick = buttonClickHandler;
showHideButton.onmousedown = function() {console.log("button mousedown"); };
contentDiv.onblur = contentBlurHandler;
showHideButton.onfocus = function(ev) { console.log("button onfocus"); }
.parent {
display: flex;
width: 100vw;
height: 100vh;
flex-direction: row;
background-color: #409958;
}
.accordion {
background-color: #28a7c9;
}
.show-hide-content {
visibility: hidden;
background-color: #c91e63;
}
<h1>Show/Hide Test</h1>
<div class="parent">
<div class="drawer">
<div class="accordion" id="accordion">
<button class="show-hide-toggle" id="show-hide-toggle">Show/Hide</button>
<div class="show-hide-content" id="show-hide-content" tabindex="-1">
<p>This is some content that should be shown or hidden depending on the toggle.</p>
</div>
</div>
<div class="spacer">
</div>
</div>
</div>
This happened to me as well on both Safari and Firefox.
I had an use case where I needed to show clear icon/button only when user focused input (with input having value of course). And clicking that icon would make a blur on input and I had check if new focused element is contained within input but since relatedTarget was null I could not check it.
I added dummy span around my clear button and relatedTarget wasnt't null anymore:
<span aria-hidden="true" tabindex="-1" style="outline: none;">
<button>X</button>
</span>
For thoses who just need a specific value from the target, remember, if your application's design and size allows this possibility, that you can just hardcode the value:
onBlur={()=>function(value)}
For a lot of case it could just work, before an automatic, better scaling solution occurs you have this possibility.
Related
I have an app that needs to be fully navigable by keyboard. When I click on the header of a div (generated by a javascript/jquery function), an event listener is triggered. I was able to highlight the headers with tab by adding the attributes role="button" and tabindex="0", but it's still only clickable by mouse. The ARIA documentation is kind of hard to follow and I don't have much time left.
HTML
<section id="js-item-root">
<div class="item">
<h2 class="item-header js-item-header" id="1" role="button" tabindex="1">Title of the First Div</div>
<p>This is a paragraph</p>
</div>
<div class="item">
<h2 class="item-header js-item-header" id="2" role="button" tabindex="2" >Title of the Second Div</div>
<p>This is also a paragraph</p>
</div>
</section>
CSS
.item {
border: solid black 2px;
margin: 15px;
padding: 15px;
}
.item-header {
border-bottom: solid black 1px;
background-color: lightgrey;
padding: 5px 0;
}
Javascript/Jquery:
function handleHeaderClick() {
$('#js-item-root').on('click', '.js-item-header', function(event) {
event.preventDefault();
console.log(this.id)
}
}
how to I get the console.log to work when I highlight the header with the tab key and press enter?
//Progress update detailed below
I was able to get my code working right by trading my <div>s for <button>s and setting the width to width: 100%, but I still want to learn a way to make a div ACT like a button. I tried creating a new function that sawpped the 'click' for a 'keypress', but that didn't work. Is there something else I'm missing?
Sometimes using a button element isn't possible - e.g. when you need to use block level elements such as headings and divs inside the button. For those times I use the following snippet to get back what you lose from using a real button:
// --------------------------------------------
// Allow role=button to behave like a real <button> el.
// by triggering the click on spacebar/enter keydown
// --------------------------------------------
document.querySelectorAll('div[role="button"]').forEach(el => {
el.addEventListener('keydown', e => {
const keyDown = e.key !== undefined ? e.key : e.keyCode;
if ( (keyDown === 'Enter' || keyDown === 13) || (['Spacebar', ' '].indexOf(keyDown) >= 0 || keyDown === 32)) {
// (prevent default so the page doesn't scroll when pressing space)
e.preventDefault();
el.click();
}
});
});
I have several boxes of cards on one page, these boxes can come dynamically in different, not upper right corner has a text for the click to open the accordion type content, for each class I have to do an action as below, I think of something Regardless of the number of classes.
*new
I do not know how to explain it, I'll try a summary:
Change the text of only one div when clicking, because when I click on the item in the box it changes all the other texts of the
Other boxes.
$('.change-1').click(function () {
var $mudartxt = $('.mudartexto');
if ($mudartxt.text() == 'expandir')
$mudartxt.text('ocultar');
else {
$mudartxt.text('expandir');
}
});
You need to find the current clicked item.
For that you can use the event object
$('.change-1').click(function (e) {
// Get current target as jquery object
var $target = $(e.currentTarget);
// Find mudartexto in current target.
var $mudartxt = $target.find('.mudartexto');
if ($mudartxt.text() == 'expandir')
$mudartxt.text('ocultar');
else {
$mudartxt.text('expandir');
}
});
.change-1 {
display:inline-block;
width:200px;
height:50px;
text-align: center;
background-color:#dfdfdf;
clear: both;
float: left;
margin-top:10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="change-1">
<div class="mudartexto">expandir</div>
</div>
<div class="change-1">
<div class="mudartexto">expandir</div>
</div>
<div class="change-1">
<div class="mudartexto">expandir</div>
</div>
<div class="change-1">
<div class="mudartexto">expandir</div>
</div>
If you are asking how to change text of an element, inside a clicked box, this should do it.
$('.change-1').click(function () {
var $mudartxt = $(this).find('.mudartexto');
if ($mudartxt.text() == 'expandir')
$mudartxt.text('ocultar');
else {
$mudartxt.text('expandir');
}
});
I have a form that includes a Submit button. After the submit button is clicked, a confirmation popup appears. Once the confirm condition is met, an HTTP post takes place. However, sometimes that post can take a while. As such, I would like to display a loading gif after the confirm condition is met, up until the post response comes back, whereby the page is already reloading.
I would suggest using an element as some sort of modal then setting the css property visibility to and from hidden and visible.
Then you can add event handlers to your button presses and requests to hide or show this element.
See the example below:
const requestButton = document.querySelector('.request');
const yes = document.querySelector('.yes');
const no = document.querySelector('.no');
const confirmation = document.querySelector('.confirmation');
const loading = document.querySelector('.loading');
const content = document.querySelector('.content');
requestButton.addEventListener('click', event => {
confirmation.style.visibility = 'visible';
});
yes.addEventListener('click', event => {
// instead of a setTimeout, you'd actually make a request using `fetch` or jquery ajax
loading.style.visibility = 'visible';
confirmation.style.visibility = 'hidden';
window.setTimeout(() => {
// the code in this callback would be the same code you'd put in your `success` callback
// i.e. this code should run when the request finishes
loading.style.visibility = 'hidden';
const p = document.createElement('p');
p.innerText = 'Loaded!';
content.appendChild(p);
}, 2000);
});
no.addEventListener('click', event => {
confirmation.style.visibility = 'hidden';
});
.hidden-center {
position: absolute;
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
top: 0;
left: 0;
visibility: hidden;
}
.modal {
background-color: lightgray;
padding: 3em;
}
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
<div class="content">
<button class="request">load a thingy?</button>
</div>
<div class="confirmation hidden-center">
<div class="modal">
Are you sure?
<div>
<button class="yes">Yes</button>
<button class="no">No</button></div>
</div>
</div>
<div class="loading hidden-center">
<div class="modal">
<div>Loading...</div>
<i class="fa fa-spinner fa-spin fa-3x"></i>
</div>
</div>
EDIT:
Reading the comments above, it looks like your application is not a single page application (I just assume nowadays). Instead of attaching an event listener to the button click, you need to attach the event listener to the form like so:
document.querySelector('#my-form-id').addEventListener('submit', event => {/*code to show loading*/})
Also, you probably don't need to add an event listener for the request coming back because the new document will replace the current one.
I was wondering if there was a way to have a centered item shift smoothly when its width changes?
In my case, I have a piece of text on the left that stays the same, and the piece of text on the right will change depending on what page you are on.
<div id="title-container">
<h1 class="inline-header">example.</h1>
<h1 id="title-category" class="inline-header">start</h1>
</div>
The total width of this will change as a result, and it will shift abruptly.
Here is a jsfiddle demonstrating the problem.
https://jsfiddle.net/sm3j26aa/3/
I've currently worked around it by just fixing the left side using relative positioning and translates, but if I can get the smooth transition, I would rather do that.
Thanks for any help!
Instead of fading just the right portion in and out, you'll need to fade the entire line.
Also, there is no need for individual functions for each word change. Just have one function that accepts the new word as a parameter.
Lastly, don't use inline HTML event attributes to set up event handlers. It:
creates spaghetti code that is more difficult to read
creates anonymous wrapper functions that alter the this binding
within the function
doesn't follow W3C DOM Even Standards
Instead set up your event handlers in JavaScript.
var $titleContainer = $('#title-container');
var $titleCategory = $('#title-category');
$("button").click(function(){ change(this.textContent); })
function change(text) {
$titleContainer.fadeOut(300, function() {
$titleCategory.text(text);
$titleContainer.fadeIn(600);
})
}
#title-container, #button-container { text-align: center; }
.inline-header { display: inline-block; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="title-container">
<h1 class="inline-header left">example.</h1>
<h1 id="title-category" class="inline-header">start</h1>
</div>
<div id="button-container">
<button>Sample</button>
<button>Hello</button>
<button>SampleX2</button>
</div>
var $titleCategory = $('#title-category');
function changeToSample() {
$titleCategory.fadeOut(300, function() {
$titleCategory.text('sample');
$titleCategory.fadeIn(600);
document.getElementById('title-container').style.marginLeft = `calc(50% - 14em/2)`;
})
}
function changeToHello() {
$titleCategory.fadeOut(300, function() {
$titleCategory.text('hello');
$titleCategory.fadeIn(600);
document.getElementById('title-container').style.marginLeft = `calc(50% - 12em/2)`;
})
}
function changeToDoubleSample() {
$titleCategory.fadeOut(300, function() {
$titleCategory.text('samplesample');
$titleCategory.fadeIn(600);
document.getElementById('title-container').style.marginLeft = `calc(50% - 20em/2)`;
})
}
#title-container {
margin-left: calc(50% - 12em/2);
transition: .2s;
}
#button-container {
text-align: center;
}
.inline-header {
display: inline-block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="title-container">
<h1 class="inline-header">example.</h1>
<h1 id="title-category" class="inline-header">start</h1>
</div>
<div id="button-container">
<button onclick="changeToSample();">Sample</button>
<button onclick="changeToHello();">Hello</button>
<button onclick="changeToDoubleSample();">SampleX2</button>
</div>
I am Trying to use jQuery.one() to disable a button that shows an image of a tree after it is clicked. The showImage function works fine, but not .one.
Can I not use javascript inside of a jquery event handler?
html:
<div class="grove">
<button id="plant" onclick="showImage()">Plant Orange Tree</button>
</div>
<div id="orange-tree-template">
<div class="orange-tree">
<h2>Tree Name</h2>
<h3>etc...</h3>
css:
.display-tree-big{
background: url('../images/tree_big.jpg');
background-repeat: no-repeat;
height:1000px;
width:1000px;
border: solid black 2px;
}
#orange-tree-template { visibility: hidden; }
javascript
function showImage() {
var img = document.getElementById('orange-tree-template');
img.style.visibility = 'visible';
}
$(document).ready(function() {
$("#plant").one( "click", function() {
document.getElementById("#plant").disabled = true;
});
});
A couple of issues.
You have an inline onclick handler assigned to the button. This will not respect the jQuery.one method, so because of error 2 you could continue to click the button and showImage will be called.
The function assigned to the click event via jQuery.one() is being called, however the statement document.getElementById("#plant") should not contain #, thus the button was not disabled.
jQuery(function($) {
$("#plant").one("click", function() {
// yes of course you can use JavaScript
document.getElementById('orange-tree-template').style.visibility = 'visible';
this.disabled = true;
});
});
#orange-tree-template {
visibility: hidden;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<button id="plant">Plant Orange Tree</button>
<div id="orange-tree-template">
TEST
</div>
Also the jQuery.one() method does not disable anything, it just executes a callback at most once per element per event type.