This question is related to Javascript event handling and flow control, but it is one step beyond. The question that remains unanswered is: when an event is fired and control is returned to the browser, could the browser decide to handle other events first (fired by other scripts or user action) (A), or will it always handle my event directly (B)?
The question is important, because in case (B) you can rely on the fact that nothing has been changed between firing the event and the event handler, while (A) gives no guarantees whatsoever.
My first guess is (B), how else could stopPropagation() and preventDefault() work? But giving it a second thought, it is no hard evidence.
A real-life example of this problem. I am modifying a rich text editor (hallo), and I want it to have these specs:
clicking on an editable text (#txt) will activate the editor, and clicking outside #txt will deactivate it. hallo uses blur and focus events on #txt to achieve this.
Activating the editor opens a toolbar, mousedown on the toolbar (but not on a button) will set a flag that prevents the blur event on #txt to deactivate the editor. The toolbar will return focus to #text.
mousedown on a toolbar button should also prevent deactivating the editor, but it should first wait till the click event, perform its action and then return focus to #txt. Some actions are immediate (bold or italic), while others need extra user input (selecting from a dropdown).
Some of these buttons open a dialog.
...And I want all these elements (editor, toolbar, dialog) to be modular
and easily extendable.
Now in most cases when you close a dialog you want the focus to return to #txt. But in the case where a dialog is opened and you click somewhere else on the page, the editor will close and call the toolbar, including the dialog to close as well. If in this case the dialog would return focus to the editor, it would activate the editor again.
As far as I understand now, the event handling order is at least deterministic. It is not possible that some event gets a delay while others are processed earlier. This is what is meant by 'synchronous'. The exception is of course events like loading a file.
From the perspective of a program component, say the dialog, the situation can be quite unpredictable. It can bind a handler to the open event, and then call dialog("open"), but anything can happen between the call and the handler, if only because the editor has an event handler on the same event.
So my conclusion is 1) yes it is predictable but 2) it needs a sophisticated architecture to implement this.
In general the event model is synchronous and reentrant, meaning that if an event handler causes another event - the second event will be executed synchronously and only after completion will the first event continue executing.
This seems to be what you're trying to describe, in which case (B) is guaranteed.
Relevant source: http://www.w3.org/TR/DOM-Level-3-Events/#event-flow
Related
In Chrome (85) and FF (80), clicking on a tabbable element gives it focus. Of course, one can focus by tabbing to the element too, and the focus events that come from either seem to be identical (view dump in browser console: the focus event is quite big):
document.getElementById('main').addEventListener('focus', console.log)
focus here
This becomes problematic for the following: I'm implementing a double-click behavior, where one must select an item with one click, then click again to follow it. I'd also like users to be able to use tab + enter or tab + single-click to follow through.
While I'd strongly prefer to use the default browser behavior to do most of the work, given all I'm adding is one extra click, this seems to be impossible to do without listening for keystrokes. This is because if indeed the two above events are indistinguishable, then one can't tell the difference between:
a tab followed immediately by click, and
a single click that generates both a focus and click event.
Event order can't be trusted, and unfortunately FF has focus first before click anyways.
Is there something in the focus or click events that I'm overlooking that I could use to check where that event is coming from?
I have a form that I create within a Bootbox (itself a layer on top of Bootstrap's modal). Once it is created, I call
$('#myForm').validate()
on it.
The desirable part of the behaviour is that now as the user tabs through the forms, they can see immediate feedback that they've messed up one of the fields. Entered a non-email address before hitting "tab" or clicking elsewhere? Warning and error message. Great!
But the undesirable thing is that the blur eats other events. So, if a user instead chooses to hit "cancel" (knowing full well that the form is invalid), the click on the cancel is not heard and its handler will not fire. A second click (now that the focus is off the form altogether) and cancel fires as expected. I don't want the users to have to click twice.
I saw a technique for disabling the blur altogether, but then the desirable behaviour described above is then lost, which I do not want either. Ideally, a "well, I blurred, but I also know what the user was clicking" is what I'm after.
[EDIT TO ADD:]
It has come to my attention that this is confusing, so let me try a different approach to the question: is it possible to be working on a form (that has had .validate() called on it), then click on any other clickable item on the whole page and have its click handler successfully fire?
[EDIT AGAIN:]
Based on Sparky's demo, I wired up a warning to show below the inputs on blur. So, to see the problem in action, fire up this fiddle: https://jsfiddle.net/bd1fhpu3/
Then click into one of the input fields BEFORE clicking "just a button". Click in an upper sliver of "just a button" to make sure that the warning labels have pushed it out of the cursor's hotspot on release of the mouse button.
Anyhow, this just explains what the problem is. Solving it is probably not within the scope of the question anymore, but it seemed worth sharing in case anybody else comes across this. To solve, I will either need to override Bootbox's click event to be a mousedown event instead of a click (then you don't need to press and release), or I can "Band-Aid"(tm) the solution by avoiding relocating the button. Maybe a static-height dialog that's tall enough to accomodate the form and any possible warnings. Definitely somewhat of a "hack" like this because you just never know if you're going to do other things later that make the problem appear again. But it'd be a quick and easy temp fix.
You cannot use the built in onfocuout function since the JavaScript cannot possibly know the different between focusing out to another field and focusing out to the cancel button.
You'll have to completely disable onfocusout and write some custom event handlers.
Your code is sparse, so my answer will be very generic.
$('#myForm').validate({
// other options here
onfocusout: false; // disable default blur event
});
Then write an event handler that programmatically triggers validation on the form when they focus to an input, and will do nothing when they focus over to anything, like a cancel button.
$('#myForm input').on('focus', function() {
$('#myForm').valid(); // trigger validation on the form
});
However, this will trigger validation on the entire form. If you only want to trigger validation on the one field losing focus, then you're back to square one, because the event only knows focus was lost, not where it's going.
Just for posterity, here's an "answer" such as it is. Maybe it will help somebody SOME day; you just never know.
There is no code that is operating "incorrectly" and therefore there is not really a fix nor code sample to provide. Here's what was happening:
User is in focus in field
User presses mouse button down (but the release action hasn't happened yet)
Blur event fires, which causes form error warnings to appear below the fields. This also had the net effect of pushing the "cancel" button down the Y axis of the screen
User releases the mouse button, which would normally complete the "click" handler.
HOWEVER, in #4, since the "cancel" button is no longer under the cursor, the click event listener bound to the button does not fire.
The demo, for as long as it lives on the internet, will be in a comment below. However, providing a code sample to go with it is pointless. When the demo dies, I will remove the comment.
Possible Steps (workarounds/hacks) that I elected NOT to pursue thus far:
Modify the dialog such that the button does not move when mousedown event fires, meaning the "click" event is completed successfully
Provide custom code that substitutes the mousedown event for the click event on the button. However, this causes a loss of fairly common UI convention
Force user to press ESC to cancel, rather than clicking a button
[credit to Tieson T. via comment] One possible option, if you can use some custom styles on the error messages, is to hide them with visibility: none rather than display: none. The former would hide the text but still reserve the vertical space of the text, which the latter doesn't (hence why your modal is expanding). This would keep the button from possibly shifting during the focusout event. You would wind up with extra whitespace, but you could knock some of that off by removing the bottom margin on the form-group (assuming you're using it)
I'm sure there are other workarounds, but that's a starting point.
I am using plain JavaScript (no JQuery etc.) to display a semi-modal dialog, which contains a small FORM
The way that I have things set up at the moment, the same handler will show a different dialog if the user clicks on a different part of the page, but if the user clicks on an INPUT field in my dialog, the click propogates/bubbles through to the handler, and the INPUT loses focus - the only way to type into it is to TAB into it, which is not exactly ideal!
Any suggestions on how to avoid this?
You can attach a click handler to your dialog's main element, and stop propagation at that point:
theDialogMainElement.addEventListener("click", function(e) {
e.stopPropagation();
}, false);
That way, clicking within the dialog doesn't propagate to the click handler on your page that's interfering.
If you need to support old versions of IE (IE8 and earlier) that don't have addEventListener and stopPropagation, see this other answer for a cross-browser event hookup function (which supplies stopPropagation as well).
You can call a .focus() on the specific input element you are referring to.
http://www.w3schools.com/jsref/met_html_focus.asp
In short
Is there a way in which, when listening to a native event, I can detect if the event was somehow used by CKEditor before it propagated to my listener, or prevent it from propagating at all?
Use case
I'm listening to the keyup event using jQuery, to detect when escape is pressed. When it is, the user is prompted if they want to discard changes, and the CKEditor instance is destroyed and its element removed from the DOM.
$('body').on('keyup', function(e){
if(e.which==27){
CKEDITOR.instances.myDiv.destroy();
$('#myDiv').remove();
}
});
The problem here is that CKEditor allows the user to interact with certain UI elements using the escape key. For instance to close a dialog window or drop-down list.
So my event should only execute its code if CKEditor did not already use the event to close a UI element of its own.
Attempt
I tried to listen to the dialogShow and dialogHide events to detect if a dialog window is open, and my action should thus be ignored. This didn't work for two reasons:
CKEditor handles the event first, so by the time the event propagates to my listener, no dialog windows are open and my code is executed.
Even if it would work, it wouldn't for drop-down lists as they do not trigger the dialog* events.
Ideas
I don't know enough about the workings of CKEditor to come up with a solution, but I think I'm looking for something along the lines of:
A setting in CKEditor to prevent event propagation: CKEDITOR.instances[0].noEventPropagation = true
An indication in the original event object: if(event.CKEditorWasHere){/*do nothing*/}
A plugin providing functionality that I can use.
Worst case scenario: A setTimeout in the dialogHide event which I'll use to suppress my own events for a short time.
So
Maybe I'm completely overlooking something. This seems to me like a common problem which should have a simple solution.
Thanks for your time.
IE fires the change event on a select menu when using the arrows to navigate the menu. This is not the case in non-IE browsers. Non-IE browsers only fire the event when clicking on the option, or pressing enter after navigating to the item with the arrows. Is there a way program around this? I need the event to not fire when navigating with the keys.
I would add my own change event listener and handle things that way if I could. Without knowing all of the details it's hard to say but I'd look there first because the event cannot be cancelled. Outside of that approach, IE is going to fire the event when the value changes so not much you can do about that. Here is a link to the change/onchange in IE. It actually says this in the doc.
To invoke this event, do one of the following:
Choose a different option in a select object using mouse or keyboard navigation.
Alter text in the text area and then navigate out of the object.
It stinks but one of those you have to account for when using a select field.
The solution that worked for my situation was the following.
bind to the blur event instead of the change event.
This introduced another issue, where when I initially load the page the select fires a change event, and I need the code in my blur binding to take effect. Binding to change and having it trigger blur caused massive recursion. The solution was to create an init function that ran at startup.
initData : function(){
var t = this,
formSelects = 'select';
jQuery.each(formSelects, function(){
// do my code here that normally happens in blur.
})
}