React implement active menu item in sidebar - reactjs

I'm using React version 16.6.3. I need some help to implement active menu item in sidebar. I need to do this thing: when the user clicks on any <li> in sidebar, the <li> must become active (for example, it gets class "active").
If the <li> is active and have treeview class, then, inside it, set to <ul> class "show" (if any)
Here's the code:
import React , { Component } from "react"
export default class Sidebar extends Component {
constructor(props) {
super(props);
this.state = { 'activeItem': 0 }
}
render() {
return (
<div className="main-sidebar">
<section className="sidebar">
<ul className="sidebar-menu tree">
<li className="nav-divider"></li>
<li className="header">PERSONAL</li>
<li>
<a href="#">
<span>Dashboard</span>
</a>
</li>
<li className="treeview">
<a href="#">
<span>Application</span>
</a>
<ul class="treeview-menu">
<li>Chat app</li>
<li>Project</li>
<li>Contact / Employee</li>
</ul>
</li>
<li className="treeview">
<a href="#">
<span>Application</span>
</a>
</li>
</ul>
</section>
</div>
)
}
}

The first question you should ask yourself is (if not always) how you store the data of the user selection. You might have multiple activeItem because this is a tree, ex. multiple branches can be open or close at the same time. You need to look for a data structure to hold that thinking first.
Here's a simple one, give each item an id/key, <li key="1-0-0"> and then you can track onChange event of this li and then inside onChange you can use a flat array structure to store the current state of this simple tree. For example, {'1-0-0': true }
There're lots of different ways to do data part, the above one is a simple idea to get you started. And after that, based on the captured data, you can then update the children attribute of each node, ex. <li className={ getNodeAttr('1-0-0')> assuming getNodeAttr is a utility function that give you back the class name string given the node name.

You should not bind active state on a navigation sidebar because if a user type a route directly in a browser the menu item won't be highlighted.
Instead, try to bind the active state to the route with NavLink (if you use react-router):
ref: https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/NavLink.js

Related

How to use isAccessibilityElement && accessibilityElementsHidden in ReactJS class to solve Voiceover issue

I have a Vertical menu that is styled as a dropdown.
on iPhone, when Voiceover is on, double tab will open the dropdown, swipe Right will go through menu items, and when gets to the last menu item, Swipe Right will close the dropdown and gives focus to the first link outside of Menu.
Now, dropdown menu is closed. and menu items are not visible. Normally, one single Left swipe should moves the focus from outside link to the dropdown header while Voiceover is ON.
but the problem is, it takes multiple Left Swipe equal to the number of menu items 4+ 1 to get from outside link to dropdown header. Voiceover is not announcing anything, I can only see on iPhone screen Voiceover text reading Manu item which is the same as <a role="menuitem">.
it looks like Voiceover still recognizes the menu items although the dropdown menu is closed.
If I do NOT open dropdown menu, 1 right swipe moves focus from dropdown header to outside link, and then 1 left swipe move focus from outside link to dropdown header. the problem happens when I open dropdown menu and swipe right through menu items.
I tried to resolve this by giving UL, Li , <a> the combination of these attributes but did not work.
When menu is closed:
aria-hidden=true, tabIndex=-1, display=none, visibility=hidden
but did not work.
I am wondering if I can implement isAccessibilityElement && accessibilityElementsHidden for React JS.
I see how the import command and code for React Native but nothing for React JS.
My Question is: when dropdown menu is closed, lets say focus is on outsideLink, how can I bring focus to dropdownHeader with one Left Swipe ?
How to force Voiceover to ignore menu items when dropdown menu is closed.
Note: my module is a Class in React JS.
Really appreciate any help.
import React, { Component } from 'react'
// help needed : how to import UISwipeGestureRecognizer for React JS
class App extends Component {
constructor(props){
super(props)
this.state = {
menuOpen: false,
}
this.closeMenu = this.closeMenu.bind(this);
this.toggleMenu = this.toggleMenu.bind(this);
this.handleSwipe = this.handleSwipe.bind(this);
}
componentDidMount () {
window.addEventListener('touchstart', handleSwipe);
}
closeMenu(event){
this.setState({menuOpen: false});
this.handleSwipe(event);
}
toggleMenu(event){
this.setState({menuOpen: !this.state.menuOpen});
}
handleSwipe(event){
const outsideLinkFocused = event.target.classList.contains('outsideLink');
const itemsArray = [...document.getElementsByClassName('item')];
if(outsideLinkFocused){
itemsArray.map(item => {
// need Help here
item.isAccessibilityElement = false;
item.accessibilityElementsHidden = true;
});
}
}
render() {
return (
<div className="Container">
<div className="NavContainer">
<button className="dropdownHeader">
Dropdown Header
</button>
<ul className="menu" role="menu">
<li className="item" role="none">
<a role=="menuitem" href="#"> item 1</a>
</li>
<li className="item" role="none">
<a role=="menuitem" href="#"> item 2</a>
</li>
<li className="item" role="none">
<a role=="menuitem" href="#"> item 3</a>
</li>
<li className="item" role="none">
<a role=="menuitem" href="#"> item 4</a>
</li>
</ul>
</div>
<div className="ArticleContainer">
<a className="outsideLink" href="#"> Outside Link </a>
</div>
</div>
)
}
}
This module is more complex and all the handlers work fine. I just wrote a short version here.

How to use UISwipeGestureRecognizer in ReactJS

I have a Vertical menu that is styled as a dropdown.
on iPhone, when Voiceover is on, double tab will open the dropdown, swipe Right will go through menu items, and when gets to the last menu item, Swipe Right will close the dropdown and gives focus to the first link outside of Menu.
Now, dropdown menu is closed. and menu items are not visible. Normally, one single Left swipe should moves the focus from outside link to the dropdown header while Voiceover is ON.
but the problem is, it takes multiple Left Swipe equal to the number of menu items 4+ 1 to get from outside link to dropdown header. Voiceover is not announcing anything, I can only see on iPhone screen Voiceover text reading Manu item which is the same as <a role="menuitem">.
it looks like Voiceover still recognizes the menu items although the dropdown menu is closed.
If I do NOT open dropdown menu, 1 right swipe moves focus from dropdown header to outside link, and then 1 left swipe move focus from outside link to dropdown header. the problem happens when I open dropdown menu and swipe right through menu items.
I tried to resolve this by giving UL, Li , <a> the combination of these attributes but did not work.
When menu is closed:
aria-hidden=true, tabIndex=-1, display=none, visibility=hidden
but did not work.
I am wondering if I can implement UISwipeGestureRecognizer for React JS.
I see how the import command and code for React Native but nothing for React JS.
My Question is: when dropdown menu is closed, lets say focus is on outsideLink, how can I bring focus to dropdownHeader with one Left Swipe ?
How to force Voiceover to ignore menu items when dropdown menu is closed.
Note: my module is a Class in React JS.
Really appreciate any help.
import React, { Component } from 'react'
// help needed : how to import UISwipeGestureRecognizer for React JS
class App extends Component {
constructor(props){
super(props)
this.state = {
menuOpen: false,
}
this.closeMenu = this.closeMenu.bind(this);
this.toggleMenu = this.toggleMenu.bind(this);
this.handleSwipe = this.handleSwipe.bind(this);
}
componentDidMount () {
window.addEventListener('touchstart', handleSwipe);
}
closeMenu(event){
this.setState({menuOpen: false});
this.handleSwipe(event);
}
toggleMenu(event){
this.setState({menuOpen: !this.state.menuOpen});
}
handleSwipe(event){
const outsideLinkFocused = event.target.classList.contains('outsideLink');
const dropdownHeader = document.getElementsByClassName('dropdownHeader')[0];
const leftSwipe = UISwipeGestureRecognizer.direction == 'left'; // I need help with leftSwipe condition
if(outsideLinkFocused && leftSwipe){
dropdownHeader.focus();
}
}
render() {
return (
<div className="Container">
<div className="NavContainer">
<button className="dropdownHeader">
Dropdown Header
</button>
<ul className="menu" role="menu">
<li className="item" role="none">
<a role=="menuitem" href="#"> item 1</a>
</li>
<li className="item" role="none">
<a role=="menuitem" href="#"> item 2</a>
</li>
<li className="item" role="none">
<a role=="menuitem" href="#"> item 3</a>
</li>
<li className="item" role="none">
<a role=="menuitem" href="#"> item 4</a>
</li>
</ul>
</div>
<div className="ArticleContainer">
<a className="outsideLink" href="#"> Outside Link </a>
</div>
</div>
)
}
}
This module is more complex and all the handlers work fine. I just wrote a short version here.

Angular 9 - Add removed li to another ul - Eventbinding

Hello everyone =) I have started to practice Angualar 9 and I really like the component approach. In order to practice property and eventbinding, i started a project of deckbuilding component with two lists. English is not my main language but I will try to explain clearly.
IMPORTANT : I really want to practice this tool, so could you, in the answer, explain your reasoning ? If you have solution tracks, i can search by myself too in order to learn.
GOAL :
I have two lists (CardlistComponent and DecklistComponent) and each time i click on one LI in CardListComponent (or DecklistComponent), this same element is removed but added to the other list.
COMPONENTS :
DeckbuilderComponent which contains my two other components (the two lists)
CardlistComponent (the first list)
DecklistComponent (the second list)
ISSUE :
I can remove the element in CardlistComponent, but I can't add the same element to the DecklistComponent. I guess the problem is the way I use arrays in Javascript.
DeckbuilderComponent (HTML) (Parent component)
<app-cardlist></app-cardlist>
<app-decklist
(cardAdded)="onCardAdded($event)">
</app-decklist>
*DeckbuilderComponent (TS) * (Parent component) I guess one of the problems is here because I duplicated the logic of add an item in the parent AND in DecklistComponent
#Output() cardAdded = new EventEmitter<{name: string}>();
decklist: Card[] = [
new Card('CardC'),
new Card('CardD')
]
onCardAdded(element : HTMLLIElement){
this.decklist.push({
name: element.textContent
})
}
CardlistComponent
#Output() cardRemoved = new EventEmitter<{cardName: string}>();
cardlist: Card[] = [
new Card('CardA'),
new Card('CardB')
]
removeCard(indexCard: number, element: HTMLLIElement){
this.cardlist.splice(indexCard, 1)
this.cardRemoved.emit({
cardName: element.textContent
});
DecklistComponent this list has to recieve the element removed in the first list
#Input() cardName: {name: string};
decklist: Card[] = [
new Card('CardC'),
new Card('CardD')
]
onCardAdded(element : HTMLLIElement){
this.decklist.push({
name: element.textContent
})
}
Here is the HTML of my two components just in case.
DecklistComponent (HTML)
<div class="container">
<div class="col-xs-12">
<ul class="list-group">
<li
*ngFor="let card of decklist; let indexCard=index"
class="list-group-item"
#cardName
>
<a href="#" class="list-group-item list-group-item-action" >{{ card.name }}</a>
</li>
</ul>
</div>
CardlistComponent (HTML)
<div class="container">
<div class="col-xs-12">
<ul class="list-group">
<li *ngFor="let card of cardlist; let indexCard=index" class="list-group-item">
<a href="#"
(click)="removeCard(indexCard, card)"
class="list-group-item list-group-item-action">{{ card.name }}</a>
</li>
</ul>
</div>
If you want something else I didn't mention, don't hesitate to tell me and have a nice day full of code =D
You can design DeckbuilderComponent as Container Component and CardlistComponent, DecklistComponent as Presentational Components
source
Container Components: These components know how to retrieve data from
the service layer. Note that the top-level component of a route is
usually a Container Component, and that is why this type of components
where originally named like that
Presentational Components - these components simply take data as input
and know how to display it on the screen. They also can emit custom
events
<deck-list (remove)="removeAndMoveTo($event, deckList, cardList)" [items]="deckList"></deck-list>
<card-list (remove)="removeAndMoveTo($event, cardList, deckList)" [items]="cardList"></card-list>
<div class="container">
<div class="col-xs-12">
<ul class="list-group">
<li *ngFor="let card of cardlist; let indexCard=index" class="list-group-item">
<a href="#"
(click)="removeCard(card)"
class="list-group-item list-group-item-action">{{ card.name }}</a>
</li>
</ul>
</div>
class CardlistComponent {
#Output("remove") removeCardEmitter: EventEmitter<Card>;
constructor() {
this.removeCardEmitter = new EventEmitter();
}
removeCard(card: Card){
this.removeCardEmitter.emit(card);
};
}
class DeckbuilderComponent {
cardList: Card[];
decklist: Card[];
constructor() {
this.cardList = [
new Card('CardA'),
new Card('CardB')
];
this.decklist = [
new Card('CardC'),
new Card('CardD')
];
}
removeAndMoveTo(card: Card, sourceList: Card[], targetList: Card[]) {
this.sourceList = this.sourceList.filter(pr => pr.id === card.id);
this.targetList.push(card);
}
}
Of course they are many solution to move element from one list to another. You can as well have one list and each card has some information to which group it belongs. Then value input binder would look like [items]="list.filter(pr => pr.isInCardList).
Another single-list approach might use rxjs. One observable which gets split with partition operator

add active class to menu item clicked add remove from others

I want to add isActive class to an item menu when user click on this item, and remove isActive class from all others items.
I am trying to compare the id, this is the angularJS code:
$rootScope.isActive = function(idVal, event){
return idVal === event.target.id;
}
This is a part from Menu Html code:
<ul class="sidebar-nav">
<li>
<a ui-sref="" id='101' ng-class="{active: isActive($event, 101)}">
<span class='glyphicon glyphicon-ban-circle glyph-sidebar'></span>
Rules
</a>
<ul class='dropdown sidebar-nav-dropdown' >
<li>
Transaction Mapping
</li>
<li>
File Setup
</li>
<li>
Code Setup
</li>
</ul>
</li>
<li>
<a href="#" id='102' ng-class="{active: isActive($event, 102)}">
<span class='glyphicon glyphicon-ban-circle glyph-sidebar'></span>
Administrative Rules
</a>
<ul class='dropdown sidebar-nav-dropdown'>
<li>
<a ui-sref="admin.mapping-rules">Transaction Mapping</a>
</li>
<li>
<a ui-sref="admin.mapping-rules">File Setup</a>
</li>
<li>
<a ui-sref="admin.mapping-rules">Code Setup</a>
</li>
</ul>
</li>
</ul>
Thanks,
First of all, you shouldn't use the root scope. You should use the scope of the controller associated to that view.
Second, your code doesn't make much sense. $event can be used as a parameter of a function called... to react to an event:
ng-click="doSomething($event)"
But with ng-class, there is no $event.
All you need to have in your scope is the ID (or name, or whatever identifies a menu item) of the selected menu item:
$scope.selectedMenuItem = null;
When an item is clicked, you need to change the selected menu item:
ng-click="selectMenuItem(101)"
$scope.selectMenuItem(item) {
$scope.selectedMenuItem = item;
}
Then to associated a css class with the selected menu item, you simply need
ng-class="{active: selectedMenuItem === 101}"
That said, if all your links navigate to a given router state, you don't even need that selectedMenuItem. All you need is to add the active class if the current router state is the one the that the link allows navigating to (assuming $state is on your scope):
ng-class="{active: $state.includes('admin.mapping-rules')}

AngularJS - Change class on list when clicked

I am just getting started with Angular and am still trying to get a feel for how it works. I have 2 questions about a list with ng-repeat. I will post my code first.
HTML
<div class="row">
<div class="span2">
<ul class="nav nav-pills nav-stacked">
<li ng-repeat="class in classes | orderBy:orderProp" ng-class="activeClass">
<a ng-click="loadRoster(class)">{{class.class_name}}</a>
</li>
</ul>
</div>
<div class="span2">
<ul class="nav nav-pills nav-stacked">
<li ng-repeat="student in students | orderBy:orderProp">
<a ng-click="enterPassword(student)">{{student.student_name}}</a>
</li>
</ul>
</div>
</div>
Controller
function ClassListCtrl($scope, $http) {
$scope.loadedList=[];
$scope.students=[];
$scope.activeClass='';
$scope.orderProp = 'alphabetical';
$http.get('data/class.json').success(function(data) {
$scope.classes = data;
});
$scope.loadRoster=function(classlist){
$scope.selectedClass=classlist;
$scope.activeClass='active';
if($scope.loadedList[classlist.class_id]== undefined){
$http.get('data/class_'+classlist.class_id+'.json').success(function(data){
$scope.loadedList[classlist.class_id]=data;
$scope.students=data;
});
}
$scope.students=$scope.loadedList[classlist.class_id];
}
$scope.studentClick=function(student){
}
}
Ok, this pulls the data just fine. I can click on a class and it loads the students in that class. To cut down on AJAX calls, I store those that are clicked and if that class is clicked again, it will fetch it from memory instead of making the call. This is fine as class rosters are static for the most part.
I am trying to have it load a default class roster (the first alphabetically) and mark just that list item as active.
Secondly, when they click a list item, I want to change that list item to active, and change the class of the other ones back to nothing. I am not sure how to do that either. As it stands, none are active now, but when I click one, all become active.
in Html ng-class, if class_id matches $scope.activeClass set class of li to "active"
<li ng-repeat="class in classes | orderBy:orderProp" ng-class="{'active':class.class_id == activeClass}">
in Controller when $scope.loadRoster is called set $scope.activeClass to class_id of the class passed to function
$scope.loadRoster=function(classlist){
$scope.selectedClass=classlist;
$scope.activeClass=classlist.class_id;
for your default active class just set $scope.activeClass to class_id of the class

Resources