I've created dialog windows; and I want them to be closed whenever the uses clicks anywhere else than the dialog window buttons (hyperlinks).
I was creating one large "overlay" div (0 opacity) at the back of dialog window to catch the clicks, but that becomes quite problematic when the user wants to clck anything at the back (like another hyperlink) as well as closing the dialog at the same time. Since there is overlay, it becomes activated (to close the dialog) and the clicked hyperlink is not catched.
Shortly, I need a solution for this situation where I'll close the dialog windows whenever user does anything except clicking the dialog window buttons.
I hope it's clear; thank you.
This is being caused by event bubbling. It's a shame that 2 people downvoted #Lilith2k3 answer because he wasn't wrong and #Xotic750 had way too complex a solution. You do need an event handler on your body, but you need simply to filter out clicks from the your dialog.
You need two onclick() handlers. One in your body to close the dialog, and the other in your dialog to cancel event bubbling. See below
function dlgClickHandler(e) {
// do dialog stuff, then...
e.cancelBubble = true; if (e.stopPropagation) e.stopPropagation(); // cancel event bubbling so body click handler not activated
}
function bodyClickHandler(e) {
// close dlg
}
This is also the reason you can't do this simply by comparing the ID of the dialog, because the click may have come from one of the children (eg. your OK, cancel buttons).
Working DEMO
Without jQuery DEMO
I've wrapped the functions in a module pattern to make it a neater component, and although I've used jQuery in the first example (which I suspect you are not) the technique pre-dates jQuery.
One of the reasons I suspect you're not using jQuery, is because if you were you'd probably already have stumbled across one of the many jQuery popup plugins for handing dialogs like this. If you've not tried jQuery take a look, it might help you out in many other ways too.
This is a very basic demonstration. We have a yellow div on the screen that represents your dialog. If you click anywhere in the div then it remains visible, you could fill this div with more HTML and event handlers to do as you wish. Click anywhere outside of the div and the div will become hidden. Note: I am not cleaning up any event handlers, which you will want to do.
Please see the answer by cirrus, where he actually gives an explanation about event propagation and why you will need it in your solution, which I haven't done here. He also gives you a solution using vanilla javascript and jquery, which I don't. He also demonstrates the javascript module pattern where I have not. I wouldn't have been able to bring you this answer without his constructive critisism and tuition, which prompted me to come back here and improve on my original poor, time constrained answer. Good luck.
CSS
.box {
width:300px;
height:100px;
position: absolute;
top: 30%;
left: 30%;
background-color:yellow;
border:2px solid;
}
#message {
position: absolute;
right: 50%;
bottom: 50%;
}
#button1 {
position: absolute;
right: 0;
bottom: 0;
}
#button2 {
position: absolute;
right: 4em;
bottom: 0;
}
HTML
<div id="box" class="box">
<div id="message"></div>
<button id="button1">
<img src="http://img856.imageshack.us/img856/3817/ticklf.png" alt />Ok</button>
<button id="button2">
<img src="http://img822.imageshack.us/img822/1917/crossn.png" alt />Cancel</button>
</div>
JavaScript
function dontBubble(evt) {
evt.stopPropagation();
}
function hideBox(evt) {
box.hidden = true;
}
function messgage() {
document.getElementById("message").textContent = "I'm ignoring you";
}
document.getElementById("box").addEventListener("click", dontBubble, false);
document.getElementById("button1").addEventListener("click", messgage, false);
document.getElementById("button2").addEventListener("click", hideBox, false)
document.addEventListener("click", hideBox, false)
;
On jsfiddle
At best, you would bind a click event to $("body"), which checks, where the user clicked and in case the user clicked not within the dialog, you could unbind the event and close the dialog.
Related
I'm trying to toggle a button to either submit on click or reveal an input field depending on if the user clicks the document to hide/collapse the input field. The issue is that after the user dismisses the input field the submit button doesn't return to a toggle behavior; clicking the button to again open the input field results in form submission.
I need the button, when clicked, to trigger a search field to slide in. Once the input field is visible, the button will submit instead of toggle, unless, the user clicks on the document to hide the field. Currently, the initial click will open the input field, clicking on the document will collapse the field. The second click after either previous action will result in 'submit'.
I believe, based on my little understanding, that the preventDefault() function isn't occurring on the second click or, .off('click') needs to be toggled somehow?
HTML:
<form action="" id="sSearch" class="collapsed">
<input type="search" id="sSearch-Field" placeholder="Search">
<button type="submit">
Open
</button>
</form>
JS:
$('#sSearch button').click(function (event) {
event.preventDefault();
$('#sSearch').removeClass('collapsed');
$('#sSearch button').off('click');
});
$(document).click(function (event) {
$target = $(event.target);
if(!$target.closest('#sSearch').length && $('#sSearch').not('.collapsed')) {
$('#sSearch').addClass('collapsed');
}
});
I don't understand why the preventDefault() function doesn't work on the second click? Based on what I've seen in similar questions, the off() and unbind() functions should reset the click and default behavior. Is this true?
CodePen Example
There's already an answer here, but it doesn't explain why your code isn't working. From your question it is clear you are specifically trying to understand the problem, so let's solve it! :-)
You asked:
... the off() and unbind() functions should reset the click and default behavior. Is this true?
In this case, yes, that is pretty much what happens. More generally, off() will remove an event handler. In this case it removes the click handler that was added to the button.
So where's the problem? The first time that button handler runs, it removes itself from handling any future clicks. That means you'll never be able to display the search input again (which is what the handler normally does). If you click on the document to hide the search input, and then need to show it again, you can't. The handler which does that is no longer listening for clicks on your button!
Let's trace how that happens:
If the second click is on the document:
first handler is not attached to any events, will not fire;
second handler does fire;
!$target.closest('#sSearch').length will evaluate true;
$('#sSearch').not('.collapsed') will evaluate true
test passes, search field is hidden;
GOOD! This is what you want;
Now say you want to show the search input again, so you click the button:
first handler is not attached to any events, will not fire;
second handler does fire;
!$target.closest('#sSearch').length will evaluate false;
test fails, second handler does nothing;
neither handler did anything, default action was not prevented - the form will submit, just like a normal HTML form without any Javascript at all;
BAD! You this is not what you wanted.
Side note: $('#sSearch').not('.collapsed') works, but for readability and consistency it should probably be $('#sSearch').not('.collapsed').length, just like the first condition.
So, how to get the behaviour you want?
To stick with the approach you are using, you would need to re-attach the first event handler when the search field is hidden, so that the page goes back to the initial state, just like it is at page load.
To do that, I would extract the code into a function so that you can call it from 2 places, to avoid having to duplicate it:
// New function we can call whenever we need it
function showSearch (event) {
event.preventDefault();
$('#sSearch').removeClass('collapsed');
$('#sSearch button').off('click');
}
// Call our function when button clicked
$('#sSearch button').click(showSearch);
$(document).click(function (event) {
$target = $(event.target);
if(!$target.closest('#sSearch').length && $('#sSearch').not('.collapsed')) {
$('#sSearch').addClass('collapsed');
// Re-attach the function to button clicks, so page goes back to
// the initial state, before any button clicks
$('#sSearch button').click(showSearch);
}
});
Here's a working JSFiddle (sorry I switched from CodePen, I'm more used to it).
Alternatively, while the approach you've taken works fine, I'd maybe opt for something a little simpler. Rather than adding or removing event handlers based on user activity, I'd instead track the current state of the form and use that to work out what to do when a click happens.
We need some kind of variable to track the state ... but if you take a step back, you already have such a variable! The collapsed class tells us the current state of the page. You can use jQuery's .hasClass() method to check if the class is present or not, and that will tell us the state the page is currently in:
For clicks on the button, if the input currently has the collapsed class, you want to display it, so you prevent the submission and remove the class. If the input does not have that class, you don't do anything, and allow the submission to happen.
$('#sSearch button').on('click', function(event) {
if ($('#sSearch').hasClass('collapsed')) {
event.preventDefault();
$('#sSearch').removeClass('collapsed');
}
});
For clicks not on the button, when the input is shown, you want to hide it; when the input is hidden you do nothing.
$(document).on('click', function (event) {
$target = $(event.target);
if(!$target.closest('#sSearch').length && !$('#sSearch').hasClass('collapsed')) {
$('#sSearch').addClass('collapsed');
}
});
Here's another JSFiddle, using this different approach.
You can add a flag variable to determine if the input is shown or not. Also, you need to use .on("click") instead of .click() to handle live changes.
Here is the code:
let readySubmit = false;
$('#sSearch').on("click", "button", function(event) {
if (readySubmit == false) {
event.preventDefault();
$('#sSearch').removeClass('collapsed');
$('#sSearch button').off('click');
$('#sSearch button').html("Submit");
readySubmit = true;
}
});
$(document).click(function(event) {
$target = $(event.target);
if (!$target.closest('#sSearch').length && $('#sSearch').not('.collapsed')) {
$('#sSearch').addClass('collapsed');
$('#sSearch button').html("Open");
readySubmit = false;
}
});
#sSearch input {
position: absolute;
right: 40px;
width: 167px;
height: 38px;
padding-left: 0.75rem;
padding-right: 0.75rem;
border: 1px solid #ced4da;
transition-property: width, height, padding-left, padding-right;
transition-duration: 0.25s;
transition-timing-function: linear;
}
button {
transition: all 0.25s linear;
}
#sSearch.collapsed input {
width: 0;
padding-left: 0;
padding-right: 0;
border: none;
transition: all 0.25s linear;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form action="" id="sSearch" class="collapsed">
<input type="search" id="sSearch-Field" placeholder="Search">
<button type="submit">
Open
</button>
</form>
I'm catching the contextmenu event using jQuery like this:
$(document.body).on("contextmenu", function(e){
//do stuff here
});
So far, so good. Now I want to execute some code when it closes but I can't seem to find a correct solution for this.
Using something like the following would catch some of the cases, but not nearly all:
$(document.body).on("contextmenu click", function(e){});
It wouldn't be executed when:
the browser loses focus
an option in the contextmenu is chosen
the user clicks anywhere in the browser that's not on the page
note: I'm not using a jQuery context menu, I'm just using it to catch the event.
Following code may help you. jsfiddle
var isIntextMenuOpen ;
$(document).on("contextmenu", function(e){
isIntextMenuOpen = true;
});
function hideContextmenu(e){
if(isIntextMenuOpen ){
console.log("contextmenu closed ");
}
isIntextMenuOpen = false;
}
$(window).blur(hideContextmenu);
$(document).click(hideContextmenu);
I needed to detect when a context menu closes and so I came up with a solution.
Fiddle: https://jsfiddle.net/kexp0nmd/1/
var premenuelem;
var TempContextMenuCloseHandler = function(e) {
console.log('closed!');
//console.log(e);
window.removeEventListener('keyup', TempContextMenuCloseHandler, true);
window.removeEventListener('mousedown', TempContextMenuCloseHandler, true);
window.removeEventListener('focus', TempContextMenuCloseHandler, true);
var focuselem = document.getElementById('tempfocus');
if (focuselem === document.activeElement) premenuelem.focus();
focuselem.style.display = 'none';
};
var TempContextMenuHandler = function(e) {
console.log('open!');
//console.log(e);
premenuelem = document.activeElement;
var focuselem = document.getElementById('tempfocus');
focuselem.style.display = 'block';
focuselem.focus();
window.addEventListener('keyup', TempContextMenuCloseHandler, true);
window.addEventListener('mousedown', TempContextMenuCloseHandler, true);
window.addEventListener('focus', TempContextMenuCloseHandler, true);
};
window.addEventListener('contextmenu', TempContextMenuHandler, true);
html, body { min-height: 100%; }
<textarea></textarea>
<div id="tempfocus" tabIndex="-1" style="left: 0; bottom: 0; height: 50px; width: 100%; background-color: #CCCCCC; display: none; position: fixed; outline: none;"></div>
Tested and verified working as of May 2020 on Edge, Firefox 76, and Chrome 80 for both mouse and keyboard. Mobile/touch support unknown.
The key aspect of this solution is using an element that has a tabIndex on it. By showing and moving focus to that element (focus stealing) before the context menu appears causes Edge and Chrome to send a focus change event when the user later closes the context menu. I made the background of the div gray so it could be seen - in production, make it a transparent background and style it up however you want.
The keyup handler catches the release of the Escape/Enter key for when the keyboard closes the context menu. The mousedown handler catches mousedown events in Firefox only.
As far as I can tell, there is no way to know for certain what option a user selected or even if they did, in fact, select an option. At the very least, it allows for consistent detection of context menu open/close across all major browsers.
The textarea in the example is just there to give something else to play with for focus handling.
While this solution involves temporary focus stealing, it is the cleanest, cross-browser solution until browser vendors and the W3C add an 'exitcontextmenu' event or some such to the DOM.
One minor bug I just ran into: Showing the context menu and switching away to another application closes the context menu but does not fire the closed event right away. However, upon switching back to the web browser, the event fires and the close handler runs. Adding a 'blur' capture to the window might solve that but then I'd have to re-test everything and it might break something (e.g. fire blur on opening the context menu). Not worth fixing for the extremely rare occasion this might happen AND the handler still fires - it's just visibly delayed.
On WebKit browsers (I tested on Chrome and Safari on Mac), button element behaves weird:
Wen in this fiddle http://jsfiddle.net/5ReUn/3/ you do the following:
Press left mouse button while the cursor is over HTML button text
Move the cursor (while pressed) to an area of the button without text
Release the mouse button
Then the click event on the button element is not fired!
HTML is very simple:
<button id="button">Click</button>
And the CSS is not sophisticated at all:
button {
display: block;
padding: 20px;
}
JS for click catching:
button = document.getElementById('button');
button.addEventListener('click', function() {
console.log('click');
});
Many visitors to my site complain that buttons are not clickable. They don't realize they are moving the cursor while clicking.
Has anybody found a workaround for this?
It turns out to be a bug in WebKit.
An quick non-JavaScript solution is to wrap the text in a SPAN element and make it click-through:
<button>
<span>Click Me</span>
</button>
Example CSS:
span {
display: block;
padding: 20px;
pointer-events: none; // < --- Solution!
}
Since the bug appears only in WebKit, browsers that don't support pointer-events can be ignored.
If you don't find another solution, you can use the mousedown event instead of the click event.
This is not the best solution since it behaves different than normal. Normally the user is expecting the action to happen when the finger is released from the mouse button. Not when the finger is holding the mouse button down.
Here you are trying to drag instead of clicking it. So, you may want to try mousedown event instead.
//This is same as click event
var down = false;
var button = document.getElementById('button');
var info = document.getElementById('info')
button.addEventListener('mousedown', function() {
down = true;
});
button.addEventListener('mouseup', function() {
if(down){
info.innerHTML += "click ";
down = false;
}
});
jsfiddle
You could add a div around the button (with like a margin of what, 20px?) with the same js/jquery bound to it. That way, if they would missclick(lol) the script would still fire. However, I noticed that indeed if you drag your mouse off the button while it's pressed, the button will not do anything (doesn't just happen on your page, i.e. try it with Facebook-login buttons or even the buttons on this website).
I have a div and I have a link inside this div. I want the user to be able to click anywhere in the div and they go to the page that's in the link. The link can be changed to a <span>/<p>, that's not important. There are the following conditions, however:
I cannot use css to make the <a> tags into display: block;
If I use onClick I want the user to be able to use the middle-mouse button (or right click, new tab) to open the page in a new tab if they so desire. I currently do not have this functionality if I use onClick and javascript.
I want it to be valid CSS i.e. I don't want to have <a><div> .... </div></a>
That pretty much sums up the problem I have. I've tried reading around but most people seem to solve the problem with either onclick, display: block or putting images, or forcing the user to open the page in a new window. I do not want any of these solutions, I just want a div to work as a normal link (if possible).
Many thanks
You could try wrapping the A tag with your DIV instead of vice-versa.
Instead of:
<div><a>link</a></div>
Try:
<a><div>link</div></a>
So, maybe it's not valid mark up (before HTML5) but the fact that it is now acceptable by HTML5 standards seems like hindsight that this is at least a good way to accomplish what you need.
The middle-mouse/right click functions are part of how the browser interacts with an anchor, not part of your code. There's nothing you can do in code to make it work different. If changing the anchor to take up the whole space (act like a DIV) isn't possible, I don't see how you're going to do this in exactly the same way.
You could approximate it by detecting which mouse button is pressed with event.which and doing different things based on which mouse button is clicked. It won't be the browser interface that the user is interacting with and some of the options may not be possible, but you could make it close.
$('#myDiv').mousedown( function(e) {
var href = $(this).find('a:first').attr('href');
if (e.which == 1) {
window.parent.location = href;
}
else if (e.which == 2) {
window.open(href);
}
else {
...popup a menu with the various options...
}
});
// prevent all clicks on the actual anchor from bubbling to the DIV handler
$('#myDiv a').click(function(e) {
e.stopPropagation();
});
One way that I have approached this in the past is to wrap the div inside of the tag.
So, try something like this
<div style="min-width: 20px; min-height: 20px;">test</div>
Sure you can make <a>tags into display: block; but be careful you need to add height: inherit; if you don't want to set a fixed height
Update:
onclick event can't handle right click and middle click but onmousedown can!
Example:
$("a").mousedown(function(e){
switch(e.which)
{
case 1: //left click
$(this).attr("target", "_self"); //this tab
break;
case 2: case 3: // right and middle click
$(this).attr("target", "_blank"); //new tab
e.preventDefault();
break;
}
});
I have a checkbox on a page that is disabled until some criteria is met.
In an effort to give the user some information, I'd like to have a 'tool tip' display when the user tries to click on the disabled checkbox. The problem I'm having is that I can't get the onClick event to trigger on the checkbox.
Here is some sample code:
<script>
function notify() {
alert("Hello");
}
</script>
<input type="checkbox" onclick="notify();" id="thisOneWorks"/>
<input type="checkbox" onclick="notify();" id="thisOneDoesnt" disabled/>
When the checkbox is enabled, the onClick event will fire.
When the checkbox is disabled, the onClick event will not fire.
My question is: How can I execute a function when a disabled checkbox is clicked?
I was looking through StackOverflow yesterday and found this solution in a question somewhere, but I now I can't find it again. When I find it, I'll link back to it.
The Fix
In order to capture clicks on a disabled checkbox, you can overlay a div above the disabled checkbox, and the div will receive all the onClick events (demo here):
<style type="text/css">
.checkboxWrapper {
position: relative;
}
.checkboxOverlay {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
</style>
<script type="text/javascript">
function notify() {
alert("Hello");
}
</script>
<span class="checkboxWrapper">
<input type="checkbox" disabled/>
<div class="checkboxOverlay" onclick="notify();"></div>
</span>
This places the div over the checkbox.
Internet Explorer
There's a bug in Internet Explorer, where the div is forced beneath the checkbox, and so the div can't receive click events because the checkbox blocks it. I've read that this happens because Internet Explorer treats the checkbox as an ActiveX control, and ActiveX controls get placed above all other elements.
In order to get around this Internet Explorer bug, we need to place a background on the div. I'm not sure why, but that causes the div to pop to the top. We can just create a transparent image and use it as the background for the div. I created a 1x1 transparent gif and set it as the background on the checkboxOverlay div:
.checkboxOverlay {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: url(img/transparent.gif) repeat;
}
Now it will work in Internet Explorer.
$('.mycontainer').delegate(':checkbox:disabled', 'click', function(e) {
//do stuff
});
Try wrapping each input in a div and attaching the onclick to the div. On a side note, you'd probably want the tooltip to show on hover instead of on click. Take a look at YUI's event utility if you're not using a library.
If you don't want to wrap the checkbox in a div, then consider not diabling checkbox and modifying the built-in click event on the checkbox:
document.getElementById("checkboxId").addEventListener("click", function (event) {
event.preventDefault();
//The code you want to be run when the checkbox is clicked
});
In Angular 4+
.html ->
<label class="round-check" (click)="validate()">
<input type="checkbox" [(ngModel)]="item.isChecked" [disabled]="true">
<span> {{item.text}}</span>
</label>
.ts ->
validate(){
this._toastr.warning('your warning msg')
}
Using jQuery it is simple: $('#thisOneDoesnt').trigger('click'); Live demo here. Using clean javascript doesn't work: document.getElementById('thisOneDoesnt').click(); - i don't know why, but i'm working on it.
EDIT
In JavaScript You can enable, click and disable checkbox. Live demo here.
document.getElementById('thisOneDoesnt').disabled = false;
document.getElementById('thisOneDoesnt').click();
document.getElementById('thisOneDoesnt').disabled = true;