Tabindex not working in Chrome (React app) - javascript

I use react and react-modal to create an overlay over a website. This overlay contains various elements and also a form (overview below). I want to be able to guide the user through the form using TAB keys. I assigned tabindex=0 to the required elements to be tabbable in order of appearance.
My problem is: It does not work in Chrome (Version 61.0.3163.100) while it does work in Firefox. I read that this happens if any element up the DOM-tree is invisible or has height/width of 0. I made some styling changes to fix that but with no effect.
<div class="ReactModalPortal">
<div data-reactroot="" class="ReactModal__Overlay" style="position: fixed; top: 0px; left: 0px; right: 0px; bottom: 0px;">
<div class="ReactModal__Content" tabindex="-1" aria-label="Questionnaire" style="position: absolute; top: 0px; left: 0px; right: 0px; height: 100%; background: transparent none repeat scroll 0% 0%; overflow: auto;">
<!-- Some other stuff and nested elements -->
<div id="...">
<form>
<input tabindex="0">
<button tabindex="0">
</form>
</div>
</div>
</div>
As you can see one of the parent elements has tabindex="-1". When changing it through the inspect function in Chrome or programmatically with JS the problem still persists (or is it a difference if the element was rendered with this index initially?).
Update
I realized that something else was causing the issues. I was using the CSS attribute initial: all on the root node of my modal to fence my inner CSS from everything outside. For some reason this was preventing the tabindex from working. If you can help me understanding I will reward this wis the bounty. My workaround is just not using all: initial (it is not IE-compatible anyways but also there is no real good alternative I am aware of).

all: initial resets all CSS properties of the node with initial properties.
For display property, the initial value would be inline.
So, setting all: initial to the root div would set the display property to inline. An inline element does not have height or width, so these are 0x0.
This is also because the div contains only fixed, absolutely positioned elements.
React Modal checks if elements are focusable by running a loop through all the elements inside the modal. However, for an element to be focusable, it has to visible. For each element, we have to iterate till the body element to ensure it's visibility.
Here is the function that checks whether the element is visible.
function hidden(el) {
return (
(el.offsetWidth <= 0 && el.offsetHeight <= 0) || el.style.display === "none"
);
}
As you can see, our div would have no offsetHeight or offsetWidth and would be deemed as hidden. Therefore, the modal cannot not be focused.

I had the same issue and was not able to get other solutions working quicky, so I came up with brute force approach. Make a ref to the container element that holds the focusable elements that you wish to make tabbable.
const formRef = useRef();
<ReactModalTabbing containerRef={formRef}>
<form ref={formRef} onSubmit={handleSubmit} >
<input type="text" />
<input type="text" />
<input type="text" />
<input type="text" />
</form>
</ReactModalTabbing>
And this is the component
import React, { useState, useEffect } from 'react';
const ReactModalTabbing = ({ containerRef, children }) => {
const [configuredTabIndexes, setConfiguredTabIndexes] = useState(false);
const focusableElements = () => {
// found this method body here.
//https://zellwk.com/blog/keyboard-focusable-elements/
return [...containerRef?.current?.querySelectorAll(
'a, button, input, textarea, select, details, [tabindex]:not([tabindex="-1"]):not([type="hidden"]):not([disabled])'
)];
}
const isTabbable = (element) =>{
if(element.getAttribute('tabindex')){
return true;
}
return false;
}
const findElementByTabIndex = (tabIndex) => {
return containerRef?.current?.querySelector(`[tabindex="${tabIndex}"]`);
}
const moveFocusToTabIndex = (tabIndex) => {
findElementByTabIndex(tabIndex)?.focus();
}
const handleKeyDownEvent = (event) => {
if(!isTabbable(event.target)){
return;
}
const tabIndex = parseInt(event.target.getAttribute('tabindex'));
if(event.shiftKey && event.key === 'Tab'){
moveFocusToTabIndex(tabIndex - 1);
}else if(event.key === 'Tab'){ //should probably make sure there is no other modifier key pressed.
moveFocusToTabIndex(tabIndex + 1);
}
}
useEffect(() => {
if(!configuredTabIndexes && containerRef.current){
setConfiguredTabIndexes(true);
focusableElements().forEach((el, index) => el.setAttribute('tabindex', index + 1));
containerRef?.current?.addEventListener('keydown', handleKeyDownEvent);
}
});
return children;
}
export default ReactModalTabbing;

Related

How to remove or hide a div when checkbox is checked in React?

I am new to React and I am trying to build a simple todolist app.
Once a task is inserted, a div with an unchecked checkbox appears. Now, I'd like to hide or remove the div when the checkbox is clicked. I think I hooked up everything correctly but I miss, conceptually, what is the best way to do it: should I make a className attribute that changes upon clicking the checkbox? Should I modify the tasks array containing by removing the task that has been checked? Or, is there any other good way I could achieve this?
Thank you so much for your help.
https://scrimba.com/scrim/cof7e4dde89963549cc216a25 Here's the directly editable code
.insert {
margin: 0 auto;
display: block;
padding: 20px;
}
.enter--task {
border-radius: 5px;
}
.tasks {
width: 40%;
min-height: 100%;
color: black;
background-color: white;
margin-top: 20px;
border-radius: 15px;
line-height: 40px;
padding: 10px;
overflow-wrap: break-word;
}
.task--check {
margin: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
import React from "react"
export default function Insert () {
const [task, setTask] = React.useState ("") // settin state for the single task, e.g. the input.
const [taskArray, setTaskArray] = React.useState ([]) // setting state for the array of tasks.
const tasks = taskArray.map((tasks) => {
// {event.target.checked ? "hidden": "tasks"} this on className did not work.
return (
<div className="tasks">
<input
type="checkbox"
className="task--check"
onChange={handleCheck}
/>
{tasks}
</div>
)
}) // This is basically to return a series of paragraphs (soon divs) once enter or add task are clicked.
//How to let the task div disappear upon checking the checkbox?
function handleCheck() {
console.log(event.target.checked ? "checked": "unchecked")
}
function handleChange (event) {
setTask(event.target.value)
} // this is a function to register the value of the input. We use the setTask function to update the value of task to the value present in the input, which in this case it is our target
function handleSubmit (event) {
event.preventDefault()
setTaskArray(task ? [...taskArray,task] : [...taskArray]) // quite hyped! Problem: user can input empty tasks. Solution: ternary operator saying, if task is true (truthy in this case, meaning a string with some content), add it to the taskArray. Otherwise, just return taskArray without appending the falsy task (empty string).
setTask("")
}
// this function handles the event after clicking enter or add task. First, we use event.preventDefault(): this is to avoid that every time that we click enter or add task, the page refreshes and the input is attached to the url (try to remove and see what happens). Then we have to make another array to insert the new task. To do this, we spread the previous array (taskArray) and we add the new element, which is task. After that, we reset the value of task to an empty string - this is to show the placeholder again.
return (
<main className="insert">
<form onSubmit={handleSubmit}>
<input
type="text"
className="enter--task"
placeholder="Enter your task"
onChange={handleChange}
name="task"
value={task} // setting the value of my input equal to my state
/>
<button>Add task</button>
</form>
{tasks}
</main>
)
}
Create a state called checkState
const [checkState, setCheckState] = React.useState(false)
const handleCheck = (event) => {
setCheckState(event.checked);
}
<div className="tasks">
<input
type="checkbox"
checked={checkState}
onChange={handleCheck}
/>
{tasks}
</div>
{checkState && <div>Show div when checkbox checked</div>}
I guess it depends on what you want to achieve, in the classic todo list example, we would remove the task from the state. By hiding it, you would keep the reference in memory. I'm not sure to see a good use case for it.
If you want to remove, you could do it like this. Note that I'm using the index as key which is not recommended as the order of the items could change, instead you should add an id attribute to your tasks, and use it as the key. You will also use it to remove the task.
const tasks = taskArray.map((task, i) => {
return (
<div className="tasks" key={i}>
<input
type="checkbox"
className="task--check"
onChange={() => removeTask(i)}
/>
{task}
</div>
)
})
function removeTask(index) {
setTaskArray(prev => prev.filter((_,i) => i !== index))
}

Deeper understanding of React event bubbling / propagation and state management

I apologise in advance if this is a silly question. Although I have managed to get it to work, I would like to get a deeper understanding.
I am building a custom hamburger menu in react which closes whenever you click anywhere outside the unordered list or the hamburger icon itself.
I have seen answers here Detect click outside React component
And I have followed it but I couldn't understand why it wasn't working.
Firstly when it was just the hamburger icon and no click outside the menu to close option, it worked perfectly.
Then when I used the useRef hook to get a reference to the unordered list in order to only close the menu when the list is not clicked, it worked perfectly except for when I clicked the actual hamburger icon.
After a lot of amateur debugging I finally realised what was happening.
First when I opened the menu the state showMenu changed to true,
Then when I clicked the hamburger icon to close,
The parent wrapper element was firing first instead of the hamburger menu which is strange as during the bubbling phase I would expect the inner most element to fire first.
So the parent element would close the menu changing the state, causing the components to re-render. Then when the event would reach the actual icon the handleClick would once again toggle the state to true giving the impression that the hamburger click isn't working.
I managed to fix this by using event.stopPropogation() on the parent element.
But this seems very strange because I would not expect the parent element's click to fire first especially when Im using bubbling phase.
The only thing I can think of is because it is a native dom addeventlistener event it is firing first before the synthetic event.
Below is the code for the Mobile navigation which has the hamburger
The header component renders the normal Nav or the MobileNav based on screen width. I haven't put code for the higher order components to make it easier to go through, but I can provide all the code if needed:
//MobileNav.js
export default function MobileNav() {
const [showMenu, setShowMenu] = useState(false);
const ulRef = useRef();
console.log('State when Mobilenav renders: ', showMenu);
useEffect(() => {
let handleMenuClick = (event) => {
console.log('App Clicked!');
if(ulRef.current && !ulRef.current.contains(event.target)){
setShowMenu(false);
event.stopPropagation();
}
}
document.querySelector('#App').addEventListener('click', handleMenuClick);
return () => {
document.querySelector('#App').removeEventListener('click', handleMenuClick);
}
}, [])
return (
<StyledMobileNav>
<PersonOutlineIcon />
<MenuIcon showMenu={showMenu} setShowMenu={setShowMenu} />
{
(showMenu) &&
<ul ref={ulRef} style={{
backgroundColor: 'green',
opacity: '0.7',
position: 'absolute',
top: 0,
right: 0,
padding: '4em 1em 1em 1em',
}}
>
<MenuList/>
</ul>
}
</StyledMobileNav>
)
}
//MenuIcon.js
/**
* By putting the normal span instead of the MenuLine component after > worked in order to hover all div's
*/
const MenuWrap = styled.div`
width: 28px;
position: relative;
transform: ${(props) => props.showMenu ? `rotate(-180deg)` : `none` };
transition: transform 0.2s ease;
z-index: 2;
&:hover > div{
background-color: white;
}
`;
const MenuLine = styled.div`
width: 100%;
height: 2px;
position: relative;
transition: transform 0.2s ease;
background-color: ${(props) => props.showMenu ? 'white' : mainBlue};
&:hover {
background-color: white;
}
`;
const TopLine = styled(MenuLine)`
${(props) => {
let style = `margin-bottom: 7px;`;
if(props.showMenu){
style += `top: 9px; transform: rotate(45deg);`;
}
return style;
}}
`;
const MidLine = styled(MenuLine)`
${(props) => {
let style = `margin-bottom: 7px;`;
if(props.showMenu){
style += `opacity: 0;`;
}
return style;
}}
`;
const BottomLine = styled(MenuLine)`
${props => {
if(props.showMenu){
return `bottom: 9px; transform: rotate(-45deg);`;
}
}}
`;
export default function MenuIcon({showMenu, setShowMenu}) {
const handleMenuClick = (event) => {
console.log('Menu Clicked!');
console.log('State before change Icon: ', showMenu);
setShowMenu(!showMenu);
}
return (
<MenuWrap onClick={handleMenuClick} showMenu={showMenu}>
<TopLine onClick={handleMenuClick} showMenu={showMenu}></TopLine>
<MidLine onClick={handleMenuClick} showMenu={showMenu}></MidLine>
<BottomLine onClick={handleMenuClick} showMenu={showMenu}></BottomLine>
</MenuWrap>
)
}
Reading this article https://dev.to/eladtzemach/event-capturing-and-bubbling-in-react-2ffg basically it states that events in react work basically the same way as DOM events
But for some reason event bubbling is not working properly
See screenshots below which show how the state changes:
Can anyone explain why this happens or what is going wrong?
This is a common issue with competing event listeners. It seems you've worked out that the problem is that the click out to close handling and the menu button click to close handling are both triggered at the same time and cancel each other out.
Event listeners should be called in the order in which they are attached according to the DOM3 spec, however older browsers may not implement this spec (see this question: The Order of Multiple Event Listeners). In your case the click out listener (in the <MobileNav> component) is attached first (since you use addEventListener there, while the child uses the React onClick prop).
Rather than relying on the order in which event listeners are added (which can get tricky), you should update your code so that either the triggers do not happen at the same time (which is the approach this answer outlines) or so that the logic within the handlers do not overlap.
Solution:
If you move the ref'd element up a level so that it contains both the menu button and the menu itself you can avoid the overlapping/competing events.
This way the menu button is within the space where clicks are ignored so the outer click listener (the click out listener) won't be triggered when the menu button is clicked, but will be if the user clicks anywhere outside the menu or its button.
For example:
return (
<StyledMobileNav>
<PersonOutlineIcon />
<div ref={menuRef}>
<MenuIcon showMenu={showMenu} setShowMenu={setShowMenu} />
{ showMenu && (
<ul>
<MenuList/>
</ul>
)}
</div>
</StyledMobileNav>
)
Then use menuRef as the one to check for clicks outside of.
As an additional suggestion, try putting all the menu logic into a single component for better organization, for example:
function Menu() {
const [showMenu, setShowMenu] = React.useState(false);
// click out handling here
return (
<div ref={menuRef}>
<MenuIcon showMenu={showMenu} setShowMenu={setShowMenu} />
{ showMenu && (
<ul>
<MenuList/>
</ul>
)}
</div>
)
}

Close modal in vanilla js [duplicate]

I'd like to detect a click inside or outside a div area. The tricky part is that the div will contain other elements and if one of the elements inside the div is clicked, it should be considered a click inside, the same way if an element from outside the div is clicked, it should be considered an outside click.
I've been researching a lot but all I could find were examples in jquery and I need pure javascript.
Any suggestion will be appreciated.
It depends on the individual use case but it sounds like in this example there are likely to be other nested elements inside the main div e.g. more divs, lists etc. Using Node.contains would be a useful way to check whether the target element is within the div that is being checked.
window.addEventListener('click', function(e){
if (document.getElementById('clickbox').contains(e.target)){
// Clicked in box
} else{
// Clicked outside the box
}
});
An example that has a nested list inside is here.
You can check if the clicked Element is the div you want to check or not:
document.getElementById('outer-container').onclick = function(e) {
if(e.target != document.getElementById('content-area')) {
console.log('You clicked outside');
} else {
console.log('You clicked inside');
}
}
Referring to Here.
you can apply if check for that inside your click event
if(event.target.parentElement.id == 'yourID')
In Angular 6 and IONIC 3, I do same as here:
import {Component} from 'angular/core';
#Component({
selector: 'my-app',
template: `
<ion-content padding (click)="onClick($event)">
<div id="warning-container">
</div>
</ion-content>
`
})
export class AppComponent {
onClick(event) {
var target = event.target || event.srcElement || event.currentTarget;
if (document.getElementById('warning-container').contains(target)){
// Clicked in box
} else{
// Clicked outside the box
}
}
}
This working fine on web/android/ios.
It might be helpful for someone, Thanks.
Try this solution it uses pure javascript and it solves your problem. I added css just for better overview... but it is not needed.
document.getElementById('outer-div').addEventListener('click', function(){
alert('clicked outer div...');
});
document.getElementById('inner-div').addEventListener('click', function(e){
e.stopPropagation()
alert('clicked inner div...');
});
#outer-div{
width: 200px;
height: 200px;
position: relative;
background: black;
}
#inner-div{
top: 50px;
left: 50px;
width: 100px;
height: 100px;
position: absolute;
background: red;
}
<div id="outer-div">
<div id="inner-div">
</div>
</div>
I came up with a hack for this that's working well for me and that might help others.
When I pop up my dialog DIV, I simultaneously display another transparent DIV just behind it, covering the whole screen.
This invisible background DIV closes the dialog DIV onClick.
This is pretty straightforward, so I'm not going to bother with the code here. LMK in the comments if you want to see it and I'll add it in.
HTH!
closePopover () {
var windowBody = window
var popover = document.getElementById('popover-wrapper') as HTMLDivElement;
windowBody?.addEventListener('click', function(event){
if(popover === event.target) {
console.log("clicked on the div")
}
if(popover !== event.target) {
console.log("clicked outside the div")
}
})
}
}
I recently needed a simple vanilla JS solution which solves for:
Ignoring specific selectors including whether a parent contains one of these selectors
Ignoring specific DOM nodes
This solution has worked quite well in my app.
const isClickedOutsideElement = ({ clickEvent, elToCheckOutside, ignoreElems = [], ignoreSelectors = [] }) => {
const clickedEl = clickEvent.srcElement;
const didClickOnIgnoredEl = ignoreElems.filter(el => el).some(element => element.contains(clickedEl) || element.isEqualNode(clickedEl));
const didClickOnIgnoredSelector = ignoreSelectors.length ? ignoreSelectors.map(selector => clickedEl.closest(selector)).reduce((curr, accumulator) => curr && accumulator, true) : false;
if (
isDOMElement(elToCheckOutside) &&
!elToCheckOutside.contains(clickedEl) &&
!didClickOnIgnoredEl &&
!didClickOnIgnoredSelector
){
return true;
}
return false;
}
const isDOMElement = (element) => {
return element instanceof Element || element instanceof HTMLDocument;
}
In React you can use useClickOutside hook from react-cool-onclickoutside.
Demo from Github:
import { useClickOutside } from 'use-events';
const Example = () => {
const ref1 = React.useRef(null);
const ref2 = React.useRef(null);
const [isActive] = useClickOutside([ref1, ref2], event => console.log(event));
return (
<div>
<div ref={ref1} style={{ border: '1px dotted black' }}>
You are {isActive ? 'clicking' : 'not clicking'} outside of this div
</div>
<br />
<div ref={ref2} style={{ border: '1px dotted black' }}>
You are {isActive ? 'clicking' : 'not clicking'} outside of this div
</div>
</div>
);
};
Live demo

onClick remove borders and on next click add them again CSS,Preact

Hello using a table kind of like this
https://jsfiddle.net/vw19pbfo/24/
how could i make a trigger onClick that removes borders on first click and on second click add them back but that should only happen on the row that is being clicked on and not affect the other. I have tried to have a conditional css on the first and last <td> but that affected every border but i only want to affect the clicked one
function removeBorders(e){
var target = e.target || e.srcElement;
target.parentElement.classList.toggle('without-border');
};
Working JSFiddle: https://jsfiddle.net/andrewincontact/su86fhxo/9/
Changes:
1) to css:
.my-table-row.without-border td {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
2) to html:
onclick=removeBorders(event) instead onClick=this.removeBorders()
One way would be to check the <td> element's parent and add/remove a custom class, like so:
function removeBorders(e) {
var row = e.parentElement;
if (row.className.indexOf("has-borders") === -1) {
row.classList.add("has-borders");
} else {
row.classList.remove("has-borders");
}
};
Working JSFiddle: https://jsfiddle.net/d380sjrh/
I also changed onClick=this.removeBorders() to onclick="removeBorders(this);".
JSFiddle: https://jsfiddle.net/4qdstec7/
Use React's state to set and unset the selected class.
const Row = ({ children }) => {
const [selected, setSelected] = useState(false);
const onClick = e => setSelected(!selected);
return (
<tr
className={selected && 'selected'}
onClick={onClick}
>
{children}
</tr>
)
}
Developing in React requires a shift in thinking from traditional web development. Please take some time to read this excellent post from the React team.

Displaying Go To Top button when page becomes scrollable

Just wondering How I can do this in Angular 2/4 : This might be easy but I just can't figure out.
Here is my code:
Let me explain it, I have a component which scrolls me to the top of the page, when I am at the bottom. But the floating div i.e, little red arrow always stays visible even when page need not scroll.
In Html:
Each button is dynamically linked to div. So div displays when button is clicked
<div *ngFor="let sampledata of SAMPLEDATA; trackBy: trackId">
<button (click)="transmitInfo(sampledata ,0)" > </button>
<div *ngFor="let data of sampledata .data; trackBy: trackId" >
<button (click)="transmitInfo(data,1)" > </button>
</div>
<!-- This keeps on going -->
</div>
<div>
<div *ngIf="renderdata === 0"> {{Object Data}}</div>
<div *ngIf="renderdata === 1">{{Object Data}}</div>
<div *ngIf="renderdata === 2">{{Object Data}}</div>
</div>
<div id="scroolUpRight">
<img src="../../../content/images/scrollup.png" width="50px" height="50px" (click)="scrollToTop()">
</div>
Let's assume when a user clicks on button 2 or 3, 2nd or 3rd div is displayed based on button clicked, this div's are a huge data. Page automatically becomes scrollable when these are activated.
In Css:
#scroolUpRight {
position: fixed;
bottom: 4%;
right: 2%;
}
#scroolUpRight :hover {
cursor: pointer;
}
In my Component I have this to take me to the top of the page:
ngOnInit() {
this.renderdata = 0;
}
transmitInfo(data, type): void {
if (type === 1) { this.sampleData = data; this.renderdata = 1; }
if (type === 2) { this.dataData = data; this. renderdata = 2; }
}
scrollToTop() {
return window.scrollTo(0, 0);
}
Now I don't know if this works but I did this:
toogleScroolButton(): void {
if (window.screenY > 300 ) {
console.log('window length is 300 +');
}
}
But this is a function. How can I make a function or component that auto detects when page becomes scrollable and display this div, hide it when not scrollable.
Expected Result : Is to make this div visible once person starts to scroll.
Previous Knowledge:
I used Javascript and Jquery before to do the same. But how do I use
angular2,4 or higher for this? Reason I need this is to animate this div when
person starts to scroll.
I do accept recommendations to optimize the above code. Please do let me know if any.. ;)
This Worked. I need to get HostListener to get windows scroll even to see if I can scroll the page.
window.scrollY gives me the scroll page size which helps me in finding out if I am scrolling my page. If scrollY reaches to certain count I can say I am scrolling down i.e, I can trigger an *ngIf to true if I am scrolling bottom else I can make it false. Code Below :)
Add
import { HostListener } from '#angular/core';
export class BlaBlaBla {
//And this did the trick
activateGoTop : boolean;
OnNgInit :: activateGoTop = false /* added Silly Reference please put this in ngOnInit() { --- }*/
#HostListener('window:scroll',[])
onWindowScroll() {
if ( window.scrollY > 100 ) {
this.activateGoTop = true;
} else {
this.activateGoTop = false;
}
}
}
in Html:
//Gets activated when screenY is scrolled for more than 100px
<div id="scroolUpRight" *ngIf="activateGoTop">
<img src="../../../content/images/scrollup.png" width="50px" height="50px" (click)="scrollToTop()">
</div>
Hope this helps someOne .. ;)
You can use a simple *ngIf binding with your method:
<div *ngIf="scrollButton()">
Top <button>up button</button>
</div>
with scrollButton() method simple as that:
public scrollButton():boolean {
return window.screenY > 300;
}
The div will only get rendered if scrollButton() method returns true, this allows you to customize your top button render conditions easily, because you only need to return a boolean from it.

Categories

Resources