Is there a way to use event.preventDefault with focusOut? If not, why?
Asked Answered
O

5

19

I would like to prevent the default event from a focusOut (or blur) event and keep the focus set to the specific input field.

This is what I have already tried:

  • using event.preventDefault() at the beginning and at the end of the function (for test purposes, not for style)

  • return false; to prevent propagation

  • setting the focus back to the element directly in the element (but I found that the focus still switches because the focus switch is executed after the .on() function executes. I know I could use setTimeout, but I’d like to avoid it and figure out the heart of the issue.)

  • using blur instead of focusOut

My code is this:

    _triggerEventHide: function(inst, eventType) {
        inst.$target.on(eventType, function(event) {
            event.preventDefault();
            if (!$.suggestBox.$divCont.hasClass($.suggestBox.mouseOverClassName)) {
                $.suggestBox.$divCont.css({display: 'none'});   //reposition Suggestbox
            } else {
                inst.target.focus(); 
            }
            event.preventDefault();
            return false;
        });
     }

Note that $target represents the jQuery-wrapped element and target represents the native DOM element.

I’ve done some research and have found already a few articles relating to my question, but none of them answer this exact question.

  1. preventDefault does not work on focus event – Here, the answer given is an alternate route to using any kind of event manipulation.

  2. Focus back textbox on focus out if does not have required value – Here, the solution was to use setTimeout() to set the focus back to the original element, which is all right, but I’d like to understand the heart of the issue, why preventDefault() is not working here.

  3. jQuery preventDefault() not working – I tried some possible solutions from this thread, such as using event.stopImmediatePropagation() and event.stop(), but all for naught.

I appreciate any time, knowledge and solutions (including attempts) contributed in this matter. I would really like to learn…

Here is a jsFiddle to show you the problem… feel free to play around with it and try different solutions.

Odoriferous answered 30/1, 2013 at 21:7 Comment(7)
onblur="this.focus()" on the unblurable element?Gustative
Try using a small timer to cheat the browser jsfiddle.net/D8qNSProsaism
In the else statement, I think the inst.target.focus(); should come before the return false;Pend
@ROY yes, my mistake. I had that in my original code, I copied it wrong in the question. It doesn't change anything.Odoriferous
@Vega Thanks for that; in my question I asked how to do this, without using a timeout function. And if I can't, then why is it not possible.Odoriferous
@MarcB This does not work for me either.Odoriferous
Only Internet Explorer has correctly designed event model for data entry (the onbeforedeactivate event). Standards based browsers can not do this robustly - the standards are flawed.Efta
O
25

After some more research I've found out that only certain events are 'cancelable'.

  • Quoted from http://help.dottoro.com/ljwolcsp.php

    You can check whether an event can be canceled with the cancelable property in all browsers except in Internet Explorer before version 9. Although the cancelable property exists in Firefox, it always returns true, regardless of the cancelable state of the event. There is no way to determine whether an event can be canceled in Internet Explorer before version 9.

    Note that the use of the preventDefault method and the returnValue property on a non-cancelable event does not cause an error. When an event handler returns false, the event will be canceled. You can use it instead of the preventDefault method and the returnValue property. See Example 2 below.

  • Each event in Javascript has a cancelable property that is defined as 'true' if the event can be prevented or 'false' if the event cannot be cancelled. Ref: http://help.dottoro.com/ljeosnqv.php

  • An example script from http://help.dottoro.com/ljeosnqv.php demonstrating this property can be found here on jsFiddle (to save space). Note the comments in the code about how Firefox always returns true for the cancelable property in the event object.

The general principal being:

 $('selector').on(eventType, function (event) {
    alert(('cancelable' in event));   //will return true
    alert(event.cancelable);      //will return true if event can be cancelled
                                  //NOTE: Firefox mistakenly always returns true
});
  • Events have a preset order of occuring depending on the situation. For example if a mouse button is clicked the order of eventTriggers would be: mousedown, mouseup, and click. Ref: www.w3.org/TR/DOM-Level-2-Events/events.html#Events-Event-initMouseEvent

  • Quoted from http://www.w3.org/TR/2003/NOTE-DOM-Level-3-Events-20031107/events.html#Events-flow-cancelation

    A 'cancelable event' is an event associated with a default action which is allowed to be canceled during the DOM event flow. At any phase during the event flow, the triggered event listeners have the option of canceling the default action or allowing the default action to proceed. In the case of the hyperlink in the browser, canceling the action would have the result of not activating the hyperlink. Not all events defined in this specification are cancelable events.

  • Sometimes the sequence of events has it that the DOM default action will occur before the event you are looking to cancel even shows up, in these cases, you cannot cancel the default action because it has already taken place. That is to say the default action occurs before the dispatch of the event.


MY SOLUTION:

That being said I did manage to find a solution to my problem and more/less this problem. Essentially I had an eventListener with an element for focusin already and I wanted this element to keep focus.

<div id='display'></div>
$('#idBox').on('focusin', function () {
  $('#div').append('focused');
  //do something
}

Initially I tried using a setTimeout event when I clicked off, but I found that wasn't good because it triggered my focusin event again. So what we need is an event that is dispatched before the DOM's default action of switching focus. I did find that there is an event that meets this requirement, but is only available to IE... typical. For your information it is: "onbeforedeactivate" and it is cancelable. However since cross browser compatibility is important, we should not use this eventTrigger. Rather I found that the event mousedown occurs before the DOM starts its focus-changing behaviours.

So what we could do is:

$(document).on('mousedown', function(event) { 
       target = event.target;     // the element clicked on
       myTarget = document.getElementById("idBox");   // the element to keep focus
       if (target !== myTarget) {   
           event.preventDefault();  //prevent default DOM action
           event.stopPropagation();   //stop bubbling
           return false;   // return
       }
});

There are many other ways of going about it. I personally don't like setting an eventListener to the entire document, but to demonstrate the basics it serves its purpose.


Footnote: A great article to read to learn more about event handling in Javascript is http://help.dottoro.com/ljtdqwlx.php An amazing resource I stumbled across.

Odoriferous answered 31/1, 2013 at 6:52 Comment(7)
The web standards are flawed, it is not possible to create high quality and robust UI using standards-based browsers.Efta
Thanks for putting together a really in depth answer!Declaratory
Glad you found it helpful :).Odoriferous
@Odoriferous great answer! Really late follow up here, but since this problem of "trapping focus" has eaten so much time for me, I wanted to share what I learned. After some recent research, the best thing i've been able to come up with is manually latching on to the capture phase of mousedown and click events, as well as binding a keydown listener for <TAB> and <SHIFT>+<TAB> events. Example here: jsfiddle.net/mikermcneil/e1Lqhkry/121 (see also w3.org/TR/wai-aria-practices/#trap_focus_div)Jasperjaspers
One other thing to add-- if you're looking to restore previous focus (i.e. before the modal popover was summoned), it is definitely possible to do so comprehensively nowadays. I ended up using a focusStack, which caches document.activeElement, as well as potentially the start and end indices of a text selection (i.e. to capture the text selection / caret position if the active element was a textarea or input), and alternately the DOM range if the active el was a contenteditable.Jasperjaspers
For the latter two cases I mentioned above, I had a lot of luck w/ Tim Down's modules: rangy (a standalone module for working with selected text and caret position within contenteditables) and rangyinputs (a JQuery plugin for working w/ selected text and caret position within textareas/inputs)Jasperjaspers
TLDR 'focusout' cannot be cancelled, so the next best option is preventing it from happening in the first placeUnavoidable
P
1

We know that it is the click function that cause the issue. So in the normal explorer like chrome, you can detact the mousedown event and then preventDefault.

But this doesn't work in ie, but we can use the beforedeactivate event instead http://msdn.microsoft.com/en-us/library/windows/apps/jj569321.aspx. It can be deteact in ie when the focus is changing. so just prevent it.

code would like this:

       $inp.on('blur', function(){

            self.hide();
        });

        isIE && $inp.on('beforedeactivate', function(evt){
            mousedown && evt.preventDefault();
        });

        $holder.on('mousedown', function(evt){
            evt.preventDefault();
            mousedown = 1;
        });
Precipitate answered 22/7, 2014 at 10:4 Comment(0)
E
0

I'm trying to achieve this behavior in a contenteditable element. I'd like to keep the cursor where it is while clicking outside buttons that shift the focus away from the contenteditable element.

As a solution, I intercept the focusout event and restore the original selection. Example in typescript:

editable_element.addEventListener('focusout',(e)=> {
    if(e.relatedTarget && e.relatedTarget.tagName=="BUTTON") 
    {
        let sel : any = App.SR.getSelection();
        let range : any = sel.getRangeAt(0);
        sel.removeAllRanges();
        sel.addRange(range);
    }
});
Erastianism answered 28/8, 2020 at 22:18 Comment(0)
A
0

This is a TypeScript or rather Angular solution but analogically could be used to solve the issue. My specific problem was that an input had a hide/show icon and upon clicking on the icon the input would loose focus. I needed to prevent this behavior as the hide/show icon appeared to be within the input and it did not make sense for the input to loose focus when clicking on the hide/show icon.

With this solution the focusin and focusout events can still be used freely for other purposes, they will trigger when expected.

//bind this method to a 'mousedown' event that happens before focuin/focusout/blur/focus events
  toggleShowPassword(event: Event, controlName: string): void {
    const input = document.getElementById(controlName);
    //when coming from outside, if the input did not have focus yet when clicking on the show/hide icon then focus it
    if (document.activeElement !== input) {
      setTimeout(() => {
        document.getElementById(controlName)?.focus();
      }, 1);
    } else {
//otherwise when clicking on the show/hide icon, stop the focusin/focusout event as the triggering 'mousedown' happens before them.
      event.preventDefault();
      event.stopPropagation();
    }

    this.showPassword = !this.showPassword;
  }
      <input
        id="password"
        name="password"
        formControlName="password"
        type="{{ showPassword ? 'text' : 'password' }}"
        (focusout)="onFocusOut()"
        (focusin)="onFocusIn()"
      />
 
      <icon
        [icon]="showPassword ? 'eyeClosed' : 'eyeOpened'"
        (mousedown)="toggleShowPassword($event, 'password')"
      ></icon>
 

Aside answered 17/8, 2023 at 19:19 Comment(0)
P
-1
inst.$target.on("blur", function() {
     if (!$.suggestBox.$divCont.hasClass($.suggestBox.mouseOverClassName)) {
        $.suggestBox.$divCont.css({'display': 'none'});   //reposition Suggestbox
        return true;
        } 
      inst.target.focus();
      return false;
         });

need to see your html. I don't think you are calling things correctly EX:

$.suggestBox I think should be $(".suggestBox") , but i cant tell without seeing your htnl

Pend answered 30/1, 2013 at 21:22 Comment(2)
I have tried this approach already; I didn't have any success.Odoriferous
jsfiddle.net/crislivinitup/gkhEB/28 Here you can see what I'm talking about... feel free to play around with it.Odoriferous

© 2022 - 2024 — McMap. All rights reserved.