onFocus bubble in React - html

jsfiddle : https://jsfiddle.net/leiming/5e6rtgwd/
class Sample extends React.Component {
onInputFocus(event) {
console.log('react input focus')
}
onSpanFocus(event) {
console.log('react span focus')
// event.stopPropagation()
}
render() {
return ( <span onFocus = {this.onSpanFocus}>
react input: <input type="text"
onFocus = {this.onInputFocus} />
</span> )
}
}
ReactDOM.render( < Sample / > ,
document.getElementById('container')
);
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
<div>
<span onfocus="(function(){console.log('normal span')})()">
normal input:<input type="text" onfocus="(function(){console.log('normal input focus')})()">
</span>
</div>
jsfiddle : https://jsfiddle.net/leiming/5e6rtgwd/
Using React, onFocus in <input/> will bubble which is not same as usual HTML5.
Could anyone give me the refer doc why focus bubbles with React?

focus events do not bubble, so you're correct that the behavior in React differs from that of the DOM. The DOM has a focusin event that does bubble; here's a demonstration:
<div>
<span onfocus="(function(){console.log('span focus')})()">
onfocus: <input type="text"
onfocus="(function(){console.log('input focus')})()">
</span>
</div>
<div>
<span onfocusin="(function(){console.log('span focusin')})()">
onfocusin: <input type="text"
onfocusin="(function(){console.log('input focusin')})()">
</span>
</div>
Looking through the React source code, it seems this was intentional; the code checks for whether or not the browser supports the focus event with capturing, and implements it via the focus event with ReactEventListener.trapCapturedEvent instead of ReactEventListener.trapBubbledEvent. This is necessary because React implements its synthetic event system using event delegation, and so needs to use either capturing or bubbling for all its event handling. The article linked to in the comment explains how this works:
The problem is that these events do not bubble up. A focus or blur event on a link fires only on the link itself, and not on any ancestor element of the link.
This is an ancient rule. A few events, most notably focus, blur, and change, do not bubble up the document tree. The exact reasons for this have been lost in the mist of history, but part of the cause is that these events just don't make sense on some elements. The user cannot focus on or change a random paragraph in any way, and therefore these events are just not available on these HTML elements. In addition, they do not bubble up.
...
Except when you use event capturing.
...
One of the most curious conclusions of my event research is that when you define event handlers in the capturing phase the browser executes any and all event handlers set on ancestors of the event target whether the given event makes sense on these elements or not.
It seems pretty likely that the React team decided to simply make the event always bubble (which, to be honest, is what I expected from the DOM spec as well until I read your question). The browser implementations don't seem to be consistent; one issue comment mentions that focus events bubble in Firefox, but I was not able to reproduce that on a recent version. However, using an onfocusin attribute or using addEventListener("focusin", ...) also didn't work in FF. So it's possible that this was simply an attempt at normalizing the events across browsers.
All that said, it does seem there is perhaps a bug where the .bubbles property on a SyntheticFocusEvent is false instead of true.

Related

HTML issue on making the whole div clickable (angular)

Sample HTML Code:
<div class="first-div" (click)="onClickCustomCard(d)">
Some text..
Images...
<input type="checkbox">
</div>
so my problem is that when i try to click the "input type checkbox" it also trigger the onCLickCustomCard(). Is there a way to not auto trigger the onC;ickCustomCard() when i click the input?
Thanks for helping guys...
You do this by stopping the propagation of events when you click on the input element.
Events can bubble up in the dom tree, meaning that every parent element also receives the event, in your case the click event.
The stopPropagation() method of the Event interface prevents further propagation of the current event in the capturing and bubbling phases.
Edit, some notes that are not directly related to your question: It's not good practice to add click events to divs, because this doesn't make them focusable via keyboard. So you can only click them with your mouse but not tab to them and click them with space or return key. If you use a button or link this behaviour is already implemented for you.
function onClickCustomCard() {
console.log('clicked Card')
}
document.querySelector('.first-div input').addEventListener('click', function(e) {
e.stopPropagation();
});
<div class="first-div" onclick="onClickCustomCard()">
Some text..
Images...
<input type="checkbox">
</div>

Making a clickable <div> accessible through tab structure?

So I am working on a project that requires a <div> with an onclick event. I've got the main functionality working, but there is one problem. I need the onclick event to happen when the user tabs to the <div> and presses enter. I added a tabindex to the <div> which allows it to gain focus, but nothing happens when the user presses enter (or any other key).
Can anyone help me with this? Or is what I want not even possible?
Here is a jsfiddle demonstrating my problem.
http://jsfiddle.net/logiwan992/suwq7r09/
Thank you in advance for any help.
I note the question is tagged WCAG and "accessibility".
The answer to your question is therefore "don't do that." The other answers on this page will all work fine, for everyone except the people who need it to work, i.e. those using screenreaders or other assistive technology. None of the javascript-based solutions here are WCAG compliant.
What you want is a <button>. That gives you your tabindex and keyboard control for free.
You can also force a <div> to work like a <button> by adding ARIA markup (though it's better and easier to just use the tag that already does what you need it to do.)
If absolutely necessary, a good introduction to using ARIA for pseudo-buttons is here:
https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_button_role
The gist of it is, you need to add the role="button" attribute, and manage the state of the aria-pressed attribute manually (by capturing key events and clicks in javascript; other answers have covered this pretty thoroughly so I won't repeat the details)
It is perfectly fine to have a <div> work like a button provided you specify the right ARIA tags, roles, and keyboard events. That's the whole point of ARIA.
I do agree, though, that when possible you should use the native html elements. That's the first rule of ARIA use - http://www.w3.org/TR/aria-in-html/#notes-on-aria-use-in-html. But I understand that's not always possible.
There was a mention of using focus(). That's incorrect. Focus() is used to move the focus to the object. It's not used to handle an event. Now perhaps they meant onFocus(), which is an event triggered when the object receives focus, but that's still not the right event to trap for. You don't want a button (whether implemented as a <div> or a <button>) to perform its function just because you tabbed to it. The user has to click or press enter/space on it.
Please look at the authoring practices which define the keyboard behavior for a button, http://www.w3.org/TR/wai-aria-practices/#button, as well as the section that talks about keyboard events, http://www.w3.org/TR/wai-aria-practices/#focus_tabindex. In particular, note that you should not rely on keypress. Not all browsers send that event.
Anytime you press a key, three possible events might happen: Keydown, keypress, keyup. Keydown and keyup are supported on all browsers and have access to event.keyCode. Keypress is supported on some browser and has access to event.charCode.
There's a significant different between keyCode and charCode, especially if you're trying to implement shortcut keys such as Ctrl+/. Many non-US keyboards have special keys in different places on the keyboard and you get different keyCodes for them. But that's a topic for another discussion.
The "onclick" attribute has a specific behavior on links, because it can be triggered with the enter key.
See the following WCAG failure:
http://www.w3.org/TR/WCAG20-TECHS/F59.html
You have to take care of the "role" of the element.
The following page illustrates how you can make an "accessible link" from a "span":
https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_link_role
Also, as already said, best way is relying on a button/a element when possible.
Try to bind the keypress event on the said div and detect if Enter key was pressed (which has code number 13).
var div = document.getElementsByTagName('div');
div[0].addEventListener('keypress', function(e) {
if(e.keyCode == 13) {
alert('div focused');
}
});
Working JSFiddle
You can, alternatively, use jQuery also:
jQuery(function($) {
$('div').on('keypress', function(e) {
if(e.keyCode == 13) {
alert('div focused');
}
});
});
Working JSFiddle
Use the same event handler for both events. If the event is keypress verify the key pressed is the Enter key before executing.
var divButton = document.querySelector('#div-button');
divButton.addEventListener('click', activate);
divButton.addEventListener('keypress', activate);
function activate(e) {
if (e.type === 'keypress' && e.keyCode == 13) {
alert('activated the div');
}
};
div {
outline: 1px solid black;
}
div:focus {
outline: 1px solid red;
}
<div id="div-button" tabindex="0">
<h1>This is my div!</h1>
</div>

jQuery change event fired twice when giving focus to another control inside the change callback

A weird bug caused me a lot of headaches recently, and I've been able to dumb it down to the simplest form possible. See this fiddle : http://jsfiddle.net/PgAAb/
<input type="text" id="foo" placeholder="Change me!"><br>
<input type="text" id="bar" size="30" placeholder="Dummy control to switch focus">
$('#foo').change(function() {
console.log('Changed!');
$('#bar').focus();
});
Basically, when you change the first textbox and use the mouse to click elsewhere in the document, the change event fires, as usual. However, if you change the value, and hit the enter key to trigger the change, the event fires twice.
I've noticed the bug is only with Chrome. Firefox does not trigger the event twice, and IE does not even support the enter key to trigger change on an input.
I guess that happens because of the focus switching inside the event callback. Is there any way around this?
The focus() on other control in your change eventhandler call the change event in chrome because it unfocus "blur" your current control if the value is different.
This bug is not new, you can take a look at this bug ticket on jQuery : http://bugs.jquery.com/ticket/9335
You can work around this by disabling the change eventhandler before to remove the focus on your control.
Here a little exemple of what I want to say:
$('#foo').change(changeHandler);
function changeHandler() {
console.log('Changed!');
$(this).off('change').blur().on('change', changeHandler);
$('#bar').focus();
}
Also, you can workaround this bug with just blur your input on Enter key:
jQuery('input').keydown(function(e){
if(e.keyCode==13) jQuery(this).blur();
});

Make an HTML element non-focusable

Is it possible to make an HTML element non-focusable?
I understand that a list of elements that can receive focus can be defined and that a user can navigate through these elements by pressing a Tab key. I also see that it is up to the browser to control this.
But maybe there is a way to make certain elements non-focusable, say I want a user to skip a certain <a> tag when pressing a Tab.
unfocusable
A negative value means that the element should be focusable, but should not be reachable via sequential keyboard navigation.
See also: developer.mozilla.org
To completely prevent focus, not just when using the tab button, set disabled as an attribute in your HTML element.
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<input class="form-control" type="text"> Click this, you can see it's focusable.
<input class="form-control" type="text" readonly> Click this, you can see it's focusable.
<input class="form-control" type="text" readonly tabindex="-1"> Click this, you can see it's focusable. Not tab'able.
<input class="form-control" type="text" disabled> Click this, you can see it's <strong>not</strong> focusable.
In order to make an prevent an element from taking focus ("non-focusable"), you need to use Javascript to watch for the focus and prevent the default interaction.
In order to prevent an element from being tabbed to, use tabindex=-1 attribute.
Adding tabindex=-1 will make any element focusable, even div elements. This means when a user clicks on it, it would likely get a focus outline, depending on the browser..
You would ideally, want this:
/**
* #this {HTMLElement}
* #param {FocusEvent} event
* #return {void}
*/
function preventFocus(event) {
if (event.relatedTarget) {
// Revert focus back to previous blurring element
event.relatedTarget.focus();
} else {
// No previous focus target, blur instead
this.blur();
// Alternatively: event.currentTarget.blur();
}
}
/* ... */
element.setAttribute('tabindex', '-1');
element.addEventListener('focus', preventFocus);
For safe typechecking, you can perform if (event.relatedTarget instanceof HTMLElement) instead if (event.relatedTarget).
TabIndex is what your looking for: http://www.w3schools.com/jsref/prop_html_tabindex.asp.
When you set a tabIndex value to -1 you will skip it when tabbing through your form.
In case you are looking for a global solution:
Link
document.body.addEventListener('focusin', (e) => {
if (e.target.classList.contains('__nofocus')) {
e.relatedTarget ? e.relatedTarget.focus() : e.target.blur();
}
});
It should work for anchors, buttons and anything else that can receive focus by default. Don't forget to set tabindex="-1" as well as the element would be unpassable by Tab-key navigation.
For the element you do not want to be focused on tab, you have to put the tabindex as a negative value.
I used focusable="false", because tabindex="-1" was not working in IE.
Making a focusable-by-default HTML element a non-focusable one isn't possible without JavaScript.
After diving into focus-related DOM events, I've came up with the following implementation (based on the #ShortFuse's answer, but fixed some issues and edge cases):
// A focus event handler to prevent focusing an element it attached to
onFocus(event: FocusEvent): void {
event.preventDefault();
// Try to remove the focus from this element.
// This is important to always perform, since just focusing the previously focused element won't work in Edge/FF, if that element is unable to actually get the focus back (became invisible, etc.): the focus would stay on the current element in such a case
const currentTarget: any | null = event.currentTarget;
if (currentTarget !== null && isFunction(currentTarget.blur))
currentTarget.blur();
// Try to set focus back to the previous element
const relatedTarget: any | null = event.relatedTarget;
if (relatedTarget !== null && isFunction(relatedTarget.focus))
relatedTarget.focus();
}
// Not the best implementation, but works for the majority of the real-world cases
export function isFunction(value: any): value is Function {
return value instanceof Function;
}
This is implemented in TypeScript, but could be easily adjusted for plain JavaScript.

mootools event listener disappears after element.innerHTML is changed

I putting together a page that will display a set of stored values. I am using mootools and AJAX calls to update the values without needing to refresh the page each time the user selects a new item from the drop down menus.
the HTML each line looks something like:
<div class="selections">
<input class="checkbox selector" type="checkbox" CHECKED />
<span class="b_name">
<select class="b_n selector">
<!-- options -->
</select>
</span>
<span class="b_level">
<select class="b_l selector">
<!-- options -->
</select>
</span>
<span class="values">
<!-- the values -->
</span>
</div>
In the head I have set up an event listener like:
$$('.selector').addEvent('change', function(event){changeValues(this);});
My problem is that when the "b_name" select changes I have to update the list of options in the "b_level" select. I accomplish that by getting a list of the possible options from my database through a PHP script on another page and replacing "b_level"'s innerHTML. Once I do that, the event listener attached to "b_l selector" no longer works.
I tried to resolve this issue by explicitly attaching an event listener to "b_l selector" each time "b_name" changes like so:
row.getElement('.b_l').addEvent('change', function(event){changeValues(row.getElement('.b_l'));});
where 'row' is the html element 'div.selections'.
It still isn't working and I have no idea what's going on. Can anyone offer a suggestion as to how I can get this resolved? or perhaps a better way to do what I'm doing.
This is how JavaScript works, it's not a bug.
What you need to use is Element Delegation - you attach an event to the parent element, in the same time specifying the element that the event should be delegated to.
Here's a basic example of Element Delegation in action: http://jsfiddle.net/oskar/ENR3E/
And the documentation: http://mootools.net/docs/more/Element/Element.Delegation
When you set innerHTML on an element, the element's contents are completely cleared and replaced with a new set of elements -- the ones parsed from the innerHTML property. Any events set on the old elements will not apply to the new ones.
jQuery provides a solution to this problem with live() events. I found a solution here that apparently achieves the same with mootools.
Your approach is correct, there's probably just a bug in your addEvent() code. The reason the event handler disappears when you replace the innerHTML is straightforward enough - you are removing the elements that the handlers are on, so the handlers are removed as well. But your approach to re-add the handler should work.
I think it's possible that it's a scoping issue. What happens if you reference the div explicitly, like this:
row.getElement('.b_l').addEvent('change', function(event){
{
changeValues($$('div.selections .b_l'));
});