I have a XUL tree in my Firefox extension. When I double click on a row, the row collapses or expands. How can I disable this behavior (so that collapsing and expanding can only occur by clicking on the plus/minus signs)? I'd like to reserve double clicking for something else.
I've tried to disable the default action and event propagation like below, but this didn't work. The event handler was executed but did not prevent the collapsing.
tree.addEventListener("dblclick", onTreeDoubleClick, true);
onTreeDoubleClick = function(event) {
event.preventDefault();
event.stopPropagation();
}
The tree gets the event before you do. AFAIK the only way to get it earlier is to register a capturing event handler on the parent element of the tree:
tree.parentNode.addEventListener("dblclick", onTreeDoubleClick, true);
Of course, that assumes that the parent element doesn't contain element other than the <tree>, otherwise you catch the events on these elements as well - but you can always put the <tree> element into its own <box> element just for that.
Related
We have custom context menus that replace the browser menus on right click. We would like to be able to abort (hide) the context menu in the same way that the default context menu is hidden by the browser- a click anywhere outside the menu that does not register as a click. The solution should apply to both bound events and default browser actions, but not impede the ability for other events, ie. hover from firing.
An example:
In firefox, right click on this page to open the context menu. Hover over the
Questions-Tags-Users-Badges-Unanswered
at the top of this page. Even though the context menu is open, highlighting still occurs. Now, click on a text area on this page, like the search box at the top. The context menu will hide, but your cursor will not focus the text box (unless you click it again with the menu closed).
How can we emulate this behaviour in JavaScript?
Rejected options we've considered:
Use a transparent div over the whole page. Problem: This can capture clicks anywhere, but breaks hover events and hover css.
Check for a context-menu-open variable in each click handler, and assign handlers to all links, and input elements to detect the context menu open state, which closes the context menu, unset the open state and prevents the handlers default. Problem: Very sloppy code and a maintenance nightmare.
Consider a variation of rejected option #2, where you have a single event listener on the document.
document.addEventListener('click', function (e) {
if (contextMenuOpen) {
e.preventDefault();
e.stopPropagation();
}
}, true);
For more information about that true, look up useCapture and event flow.
Guest was on the right track with event capture, but the solution had a few glitches. This is a more robust solution that solves the following problems:
Don't immediately close the menu in the right click event that fires right after context menu.
Don't let text fields get focused when the context menu is open- the focus event fires first and is not caught by a capture click event. We need to setup a capture handler on focus too.
Deal with the problems created by having a focus and click handler, which both fire.
To do this, you need two capture event handlers:
document.addEventListener('focus', function(e){eDocumentFocusCapture(e)}, true);
document.addEventListener('click', function(e){eDocumentClickCapture(e)}, true);
// If the context menu is open, ignore the focus event.
eDocumentFocusCapture = function(e){
if(RowContextMenuIsOpen){
console.log('focus event sees the menu open: cancel focus, but leave menu be. Click will take care of closing it.');
e.stopImmediatePropagation();
e.preventDefault();
e.target.blur(); // tell the clicked element it is not focused, otherwise you can't focus it until you click elsewhere first!
}
}
eDocumentClickCapture = function(e){
// A right click fires immediatly after context menu is fired,
// we prevent the menu from closing immediately by skipping one right click event
if(RowContextMenuWasJustOpened===true && e.button===2){
console.log('right click self bypass');
RowContextMenuWasJustOpened=false;
return;
}
if(this.protected.RowContextMenuIsOpen){
console.log('click event sees menu open, I will close it.');
this.HideRowContextMenu();
e.stopImmediatePropagation();
e.preventDefault();
}
}
I have an input field that brings up a custom drop-down menu. I would like the following functionality:
When the user clicks anywhere outside the input field, the menu should be removed.
If, more specifically, the user clicks on a div inside the menu, the menu should be removed, and special processing should occur based on which div was clicked.
Here is my implementation:
The input field has an onblur() event which deletes the menu (by setting its parent's innerHTML to an empty string) whenever the user clicks outside the input field. The divs inside the menu also have onclick() events which execute the special processing.
The problem is that the onclick() events never fire when the menu is clicked, because the input field's onblur() fires first and deletes the menu, including the onclick()s!
I solved the problem by splitting the menu divs' onclick() into onmousedown() and onmouseup() events and setting a global flag on mouse down which is cleared on mouse up, similar to what was suggested in this answer. Because onmousedown() fires before onblur(), the flag will be set in onblur() if one of the menu divs was clicked, but not if somewhere else on the screen was. If the menu was clicked, I immediately return from onblur() without deleting the menu, then wait for the onclick() to fire, at which point I can safely delete the menu.
Is there a more elegant solution?
The code looks something like this:
<div class="menu" onmousedown="setFlag()" onmouseup="doProcessing()">...</div>
<input id="input" onblur="removeMenu()" ... />
var mouseflag;
function setFlag() {
mouseflag = true;
}
function removeMenu() {
if (!mouseflag) {
document.getElementById('menu').innerHTML = '';
}
}
function doProcessing(id, name) {
mouseflag = false;
...
}
I was having the exact same issue as you, my UI is designed exactly as you describe. I solved the problem by simply replacing the onClick for the menu items with an onMouseDown. I did nothing else; no onMouseUp, no flags. This resolved the problem by letting the browser automatically re-order based on the priority of these event handlers, without any additional work from me.
Is there any reason why this wouldn't have also worked for you?
onClick should not be replaced with onMouseDown.
While this approach somewhat works, the two are fundamentally different events that have different expectations in the eyes of the user. Using onMouseDown instead of onClick will ruin the predictability of your software in this case. Thus, the two events are noninterchangeable.
To illustrate: when accidentally clicking on a button, users expect to be able to hold down the mouse click, drag the cursor outside of the element, and release the mouse button, ultimately resulting in no action. onClick does this. onMouseDown doesn't allow the user to hold the mouse down, and instead will immediately trigger an action, without any recourse for the user. onClick is the standard by which we expect to trigger actions on a computer.
In this situation, call event.preventDefault() on the onMouseDown event. onMouseDown will cause a blur event by default, and will not do so when preventDefault is called. Then, onClick will have a chance to be called. A blur event will still happen, only after onClick.
After all, the onClick event is a combination of onMouseDown and onMouseUp, if and only if they both occur within the same element.
Replace on onmousedown with onfocus. So this event will be triggered when the focus is inside the textbox.
Replace on onmouseup with onblur. The moment you take out your focus out of textbox, onblur will execute.
I guess this is what you might need.
UPDATE:
when you execute your function onfocus-->remove the classes that you will apply in onblur and add the classes that you want to be executed onfocus
and
when you execute your function onblur-->remove the classes that you will apply in onfocus
and add the classes that you want to be executed onblur
I don't see any need of flag variables.
UPDATE 2:
You can use the events onmouseout and onmouseover
onmouseover-Detects when the cursor is over it.
onmouseout-Detects when the cursor leaves.
onFocus / onBlur are events that don't bubble. There are however focus events that do bubble. These being focusin and focusout.
Now to the solution: We wrap both the input and our dropdown into a div-element and set the tabindex of that div to -1 (so that it can recieve focus / but does not appear in the tab order). We now add an eventlistener for focusin and focusout to this div. And since these events do bubble a click on our input element will trigger our divs focusin event (which opens the drop-down)
The neat part now is that a click on our dropdown will also trigger the focusin event on our div (so we basically maintain focus which means: focusout/blur never fires and our dropdown stays open)
You can try this out with the code snippit below (the dropdown only closes on loss of focus - but if you want it to close when clicking on the dropdown aswell just uncomment the one line of JS)
const container = document.getElementById("container")
const dropDown = document.getElementById("drop-down")
container.addEventListener("focusin", (event) => {
dropDown.classList.toggle("hidden", false)
})
container.addEventListener("focusout", (event) => {
dropDown.classList.toggle("hidden", true)
})
dropDown.addEventListener("click", (event) => {
console.log("I - the drop down - have been clicked");
//dropDown.classList.toggle("hidden", true);
});
.container {
width: fit-content;
}
.drop-down {
width: 100%;
height: 200px;
border: solid 1px black
}
.hidden {
display: none
}
<div class="container" id="container" tabindex="-1">
<input id="input" />
<div class="drop-down hidden" id="drop-down" > Hi I'm a drop down </div>
</div>
there arises however one issue if you want to add your dropdown into the tabbing order, have buttons in your dropdown or in general have an element in the dropdown, that can recieve focus. Because then a click will give the element in the dropdown focus first. This triggers our container div to lose focus which closes the dropdown so the focus event can't bubble further and therefore can't trigger the focusin on our container.
We can solve this issue by expanding the focusout eventlistener a bit.
The new eventlistener is as follows:
container.addEventListener("focusout", (event) => {
dropDown.classList.toggle("hidden", !container.matches(":hover"))
})
We basically say: "don't you close that dropDown if someone is hovering over it" (This solution only considers mouse-use; but in that case this is fine, because the problem this tries to fix only ever occured when using a mouse, when tabbing onto/through the dropDown everything worked fine from the start)
change onclick by onfocus
even if the onblur and onclick do not get along very well, but obviously onfocus and yes onblur. since even after the menu is closed the onfocus is still valid for the element clicked inside.
I did and it worked.
An ideal solution I found to work for me was to simply add a timeout in my onBlur function. I used 250ms, that provided smooth behaviour for my blur event and allowed my onClick to fire before the onBlur. I used this example as a reference https://erikmartinjordan.com/onblur-prevents-onclick-react
You can use a setInterval function inside your onBlur handler, like this:
<input id="input" onblur="removeMenu()" ... />
function removeMenu() {
setInterval(function(){
if (!mouseflag) {
document.getElementById('menu').innerHTML = '';
}
}, 0);
}
the setInterval function will remove your onBlur function out from the call stack, add because you set time to 0, this function will be called immediately after other event handler finished
I'm implementing an interactive tutorial for a js-heavy web application. I highlight some container and expect the user to click on some element inside it. At the same time, I want to prevent the user from doing anything else, e.g. clicking on a different link.
The main problem is that I don't want to unbind any events - when the tutorial's closed, the application must work like it did before.
I started with registering a handler on all the containter's descendant elements:
element.on("click.tutorialLock", function(e){
e.stopPropagation();
e.stopImmediatePropagation();
e.preventDefault();
}
Then I set its "priority", so that it executes before any other events:
handlers = element.data("events").click;
our = handlers.pop();
handlers.splice(0, 0, our);
Finally, when I want to unlock some element, I just disable the event on it:
elementToEnable.off(".tutorialLock")
That works, but is very heavy. I tried registering the event only on elements which have some custom event handlers defined, but it omits anchors and other basic elements. Maybe you could come up with some good idea?
I would get the active parent element and pass it into a function which would disable every event other than the events in parent
$('.className').live ('click', function (e)
{
if (!$(this).parents('#targetParent').length))
return false; // same as e.preventDefault() & e.stopPropogation()
});
Hope this is similar to what you want
I have a div that has an onclick that hides the div.
If I have a link inside the div, the link does not work, because the onclick steals the click.
The desired result: if a click is made anywhere inside the div BUT the link, that the div gets hidden. If a click is made ON the link, then I want the link to open in a _blank window, and the div to remain visible.
Is there a way to deal with this with javascript?
document.getElementById('yourlinksid').onclick = function(e){
// ... pop your window ...
/* the following prevents the event from "bubbling up"
to the div's event handler */
if(!e.stopPropagation) {
e.cancelBubble = true;
return;
}
e.stopPropagation();
};
Verification:
http://jsfiddle.net/kSTNT/4/
DEMO
Inside the click handler for the link, you'll want to call event.stopPropagation, or set e.cancelBubble to true—whichever your browser prefers. This will prevent the event from bubbling to your div.
document.getElementById("thelink").onclick = function (e) {
window.open();
if (e.stopPropogation)
e.stopPropogation();
if (e.cancelBubble != null)
e.cancelBubble = true;
};
If you control the links, the easiest way is to add onclick="event.stopPropagation();" (or whatever the cross-browser version of that is) to each link, which stops the div from seeing the event without preventing the default effect of the link from taking place.
Alternatively, if the links only contain text, then you can check the tag name of the event's target in the div's click event to see whether it is a link, but if you have something more complex then you need to walk the dom tree from the event target to the div to verify that no link exists.
I don't understand why the jQuery blur handler isn't working in the most simple case. I'm literally creating a div 100px by 100px and setting a blur event on it, but it's not firing (JSFiddle):
<div id="test">this is a test</div>
$(document).ready(function() {
$('#test').bind('blur', function() {
alert('blur event!');
});
});
Is my understanding of blur wrong? I expect the blur event to fire when I click anywhere that is not the div...right?
According to jQuery's documentation:
In recent browsers, the domain of the event has been extended to
include all element types. An element can lose focus via keyboard
commands, such as the Tab key, or by mouse clicks elsewhere on the
page.
I've tried it on the latest Chrome and Firefox on Mac.
From the W3C DOM Events specification:
focus
The focus event occurs when an element receives focus either via a pointing device or by
tabbing navigation. This event is valid for the following elements: LABEL, INPUT, SELECT,
TEXTAREA, and BUTTON.
blur
The blur event occurs when an element loses focus either via the pointing device or by
tabbing navigation. This event is valid for the following elements: LABEL, INPUT, SELECT,
TEXTAREA, and BUTTON.
The jQuery docs state browsers extended the events to other elements, which I'm guessing means blur and focus are aliases for the more generic DOMFocusIn and DOMFocusOut events. Non-input elements aren't eligible to receive those by default though, and an element has to somehow gain focus before losing it - a blur still won't fire for every click outside the div.
This SO question mentions that giving an element a tabindex would allow that, and seems to work for me in Chrome after modifying your jsFiddle. (Albeit with a fairly ugly outline.)
As far as I knew, blur happens on inputs that had the focus, either way you say
I expect the blur event to fire when I click anywhere that is not the div...right?
Not exactly, the blur event only happens for an element that had the focus first
So in order for a blur event to occur, you would first have to give focus to the div, how is the div getting focus first?
If you are really try to determine if there was a click outside of your div, you need to attach a click handler to the document, and then check to see where your click came from.
var div_id = "#my_div";
var outsideDivClick = function (event) {
var target = event.target || event.srcElement;
var box = jQuery(div_id);
do {
if (box[0] == target) {
// Click occured inside the box, do nothing.
return;
}
target = target.parentNode;
} while (target);
}
jQuery(document).click(outsideDivClick);
Just remember that this handler will be run for EVERY click on the page. (in the past if i ha to use something like this, i attach the handler when I need it, and remove it when I no longer need to look for it)
A can't "blur" because that would involve the div having focus in the first place. Non-input elements like a and textarea can have focus, which is what jQuery's documentation refers to.
What you need is the "mouseout" or "mouseleave" event (mouseleave doesn't bubble, mouseout does), which will be fired when the cursor leaves the div. If you need to have clicks, I would attach a "click" event to the body, as well as the div and stopping the event propagation on only the div:
$("div").click(function(e) {
return false; // stop propagation
});
Or, if you're really determined, you can fake the appearance of a div with a and some CSS rules :)
If you want something to happen while you move your mouse over the box, you could use the mouseover event.