I want to add the class name 'active' onClick to 'li a' & also remove any 'active' class present on the 'li a'. The current code is working properly if I click sequence from top elements, but when I click elements from bottom to top, it's not working.
component.html
<div class="container text-center">
<ul id="myList" class="pt-5">
<li class="p-3">
List 1
</li>
<li class="p-3">
List 2
</li>
</ul>
</div>
component.ts
linkActive(event) {
const activeClass = event.srcElement.classList.contains('active');
const classFound = document.querySelector('li a');
const hpn = classFound.classList.contains('active');
if (activeClass == true) {
if (hpn == true) {
classFound.classList.remove('active');
}
alert('true');
event.srcElement.classList.remove('active');
} else {
if (hpn == true) {
classFound.classList.remove('active');
}
alert('false');
event.srcElement.classList.add('active');
}
}
Please find the sample code : https://stackblitz.com/edit/angular-ivy-jf9xvp
Do not manipulate the DOM directly like this,
You can use ngClass to achieve the desired result:
template:
List1
.ts
public activeList: number;
...
public linkActive(listNumber: number) {
this.activeList = listNumber;
}
In general, as recommended in comments, do the heroes tutorial and try to understand how to use typescript.
Related
I've made a dark mode toggle button for my demo website that adds a class to change the background color. The button works, but I want to swap out the image the button is using depending on if the class is present. It's not working. I think I've messed up MutationObserver somehow, can anyone help?
Javascript
let buttonIMG = document.getElementById("darkButtonIMG");
const observer = new MutationObserver(darkImage);
observer.observe(buttonIMG, {
attributes: true
});
function darkImage() {
let buttonIMG = document.getElementById("darkButtonIMG");
let buttonSRC = buttonIMG.hasAttribute("dark");
if (buttonSRC === true) {
buttonIMG.setAttribute("src", "images/Sun_Icon.png");
} else {
buttonIMG.setAttribute("src", "images/Moon_Icon.png");
}
}
HTML
<nav>
<div class="row">
<button class="buttonHide" id="hamburgerBtn">☰</button>
<ul id="navOpen">
...
<li><button id="darkButton" type="button" class=""><img id="darkButtonIMG"src="images\Moon_Icon.png" alt="Dark mode icon"></button></li>
</ul>
</div> <!-- End of Navbar Row -->
</nav>
I want to swap out the image the button is using depending on if the
class is present.
I'm assuming that when you click the button you're adding class dark to it.
In your callback method you're checking for the presence of dark attribute, but you should check for the presence of a class instead.
let buttonSRC = buttonIMG.classList.contains("dark");
You need to setAttribute for check hasAttribute
let buttonIMG = document.getElementById("darkButtonIMG");
const observer = new MutationObserver(darkImage);
observer.observe(buttonIMG, {
attributes: true
});
function darkImage() {
let buttonIMG = document.getElementById("darkButtonIMG");
let buttonSRC = buttonIMG.hasAttribute("dark");
if (buttonSRC === true) {
buttonIMG.setAttribute("src", "images/Sun_Icon.png");
buttonIMG.removeAttribute("dark");
} else {
buttonIMG.setAttribute("src", "images/Moon_Icon.png");
buttonIMG.setAttribute("dark", "dark");
}
}
I'm working on a project where I need to implement some sort of drop zone where you can drag element from a list, then drop them in a zone where they can be dragged freely. I also would like to use a cdkDropList for the zone, because it provides all the tools for connecting lists.
I used this example as a reference for my implementation, but I am not able to make it work right.
When I drop an item in the zone, it does not drop where the cursor was, it just goes to the top left of my zone, like it was in a list.
When I drag an item in the zone, it either drags correctly to where I want it to be dropped, gets dropped near the drop point, or just goes back to the top left.
Here is my cdkDrag element, it differs from the example linked above because I absolutely need it to be in it's own component (I would like to apply some logic to it in the future), but it is essentially the same concept (cdkDrag div in cdkDropList div). I managed to route all the needed events to the parent element (the zone) using Outputs.
<div class="element-box"
cdkDrag
cdkDragBoundary={{boundary_name}}
(cdkDragDropped)="dragDroppedEventToParent($event)"
(cdkDragStarted)="dragStartedEventToParent($event)"
(cdkDragMoved)="dragMovedEventToParent($event)">
{{object_name}}
<div *cdkDragPlaceholder class="field-placeholder"></div>
</div>
Here is the logic for the drag element:
export class ElementBoxComponent implements OnInit {
#Input () object_name: string;
#Input () boundary_name: string;
#Input () itemSelf: any;
#Output () dragMovedEvent = new EventEmitter<CdkDragMove>();
#Output () dragStartedEvent = new EventEmitter<CdkDragStart>();
#Output () dragDroppedEvent = new EventEmitter<any>();
constructor() {}
ngOnInit(): void {
}
dragMovedEventToParent(event: CdkDragMove) {
this.dragMovedEvent.emit(event);
}
dragStartedEventToParent(event: CdkDragStart){
this.dragStartedEvent.emit(event);
}
dragDroppedEventToParent(event: CdkDragEnd){
this.dragDroppedEvent.emit({event, "self": this.itemSelf});
}
}
Here is my drop zone element, where I render the drag elements (you can see that I routed the Outputs to methods in my logic for the zone):
<div class="drop-container"
#cdkBoard
cdkDropList
[id]="'cdkBoard'"
[cdkDropListData]="itemsInBoard"
[cdkDropListConnectedTo]="connectedTo"
cdkDropListSortingDisabled="true"
(cdkDropListDropped)="itemDropped($event)">
<app-element-box *ngFor="let item of itemsInBoard; let i=index"
object_name="{{item.name}}"
boundary_name=".drop-container"
itemSelf="{{item}}"
style="position:absolute; z-index:i"
[style.top]="item.top"
[style.left]="item.left"
(dragMovedEvent)="elementIsMoving($event)"
(dragStartedEvent)="startedDragging($event)"
(dragDroppedEvent)="stoppedDragging($event)"></app-element-box>
<svg-container class="bin-container" containerId="bin-image-container" *ngIf="_binVisible" height=40>
<svg-image class="bin-icon" [imageUrl]="BIN_ICON_URL" height=40 width=40></svg-image>
</svg-container>
</div>
And here are the relevant methods in the TS file for my drop zone:
itemDropped(event: CdkDragDrop<any[]>) {
if (event.previousContainer === event.container) {
moveItemInArray(this.itemsInBoard, event.previousIndex, event.currentIndex);
} else {
copyArrayItem(
event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex
)
}
}
changePosition(event: CdkDragDrop<any>, field) {
const rectZone = this.dropZone.nativeElement.getBoundingClientRect();
const rectElement = event.item.element.nativeElement.getBoundingClientRect();
const top = rectElement.top + event.distance.y - rectZone.top;
const left = rectElement.left + event.distance.x - rectZone.left;
const out = (top < 0) || (left < 0) || (top > (rectZone.height - rectElement.height)) || (left > (rectZone.width - rectElement.width));
if (!out) {
event.item.element.nativeElement.style.top = top + 'px';
event.item.element.nativeElement.style.left = left + 'px';
} else {
this.itemsInBoard = this.itemsInBoard.filter((x) => x != event.item);
}
}
Again, the only differences are that my elements are encapsulated in their own components, and that the way I access the top and left style elements of the components are different (the code in the example did not work).
I know that the problem is the way I calculate the top and left variable, but I've been stuck on this for a week and cannot seem to find out what's wrong with it.
Here is a short demonstrative video if you want to better visualize what I am talking about.
Does anyone know what could be wrong with this ? I am open to any suggestions, thank you :)
It's difficut without a stackblitz know what is wrong
In this stackblitz I made two ckd-list
One cdkDropList (todoList) it's a typical "list", the other one is a dropZone (doneList). My elements are in the way
{label:string,x:number,y:number,'z-index':number}
The cdkDrag in the dropZone is in the way
<div cdkDrag class="item-box"
[style.top.px]="item.y"
[style.left.px]="item.x"
[style.z-index]="item['z-index']"
>
I choose that the todoList is connected to the dropZone, but the dropZone is not connected to anything. When I mover the elements of the dropZone if it's move away this one, simply add to the list
We need get the doneList as ElementRef
#ViewChild('doneList',{read:ElementRef,static:true}) dropZone:ElementRef;
And the neccesary functions
drop(event: CdkDragDrop<any[]>) {
if (event.previousContainer === event.container) {
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
} else {
transferArrayItem(
event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex,
);
event.item.data.y=(this._pointerPosition.y-this.dropZone.nativeElement.getBoundingClientRect().top)
event.item.data.x=(this._pointerPosition.x-this.dropZone.nativeElement.getBoundingClientRect().left)
this.changeZIndex(event.item.data)
}
this.posInside={source:null,x:0,y:0}
}
moved(event: CdkDragMove) {
this._pointerPosition=event.pointerPosition;
}
changeZIndex(item:any)
{
this.done.forEach(x=>x['z-index']=(x==item?1:0))
}
changePosition(event:CdkDragDrop<any>,field)
{
const rectZone=this.dropZone.nativeElement.getBoundingClientRect()
const rectElement=event.item.element.nativeElement.getBoundingClientRect()
let y=+field.y+event.distance.y
let x=+field.x+event.distance.x
const out=y<0 || x<0 || (y>(rectZone.height-rectElement.height)) || (x>(rectZone.width-rectElement.width))
if (!out)
{
field.y=y
field.x=x
}
else{
this.todo.push(field)
this.done=this.done.filter(x=>x!=field)
}
}
The .html like
<div class="wrapper">
<div
cdkDropList
#todoList="cdkDropList"
[cdkDropListData]="todo"
[cdkDropListConnectedTo]="[doneList]"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
<div
class="example-box"
*ngFor="let item of todo"
cdkDrag
[cdkDragData]="item"
(cdkDragMoved)="moved($event)"
>
{{ item.label }}
<div *cdkDragPlaceholder class="field-placeholder"></div>
</div>
</div>
<div
cdkDropList
#doneList="cdkDropList"
[cdkDropListData]="done"
class="drag-zone"
cdkDropListSortingDisabled="true"
(cdkDropListDropped)="drop($event)"
>
<ng-container *ngFor="let item of done">
<div
cdkDrag
class="item-box"
[style.top.px]="item.y"
[style.left.px]="item.x"
[style.z-index]="item['z-index']"
(cdkDragStarted)="changeZIndex(item)"
(cdkDragDropped)="changePosition($event, item)"
>
{{ item.label }}
<div *cdkDragPlaceholder class="field-placeholder"></div>
</div>
</ng-container>
</div>
</div>
I'm trying to make active tabs that show content depending on which tab you click.
I absolutely need the first tab to be active by default so i put the "active" class manually in the HTML so the base content is shown.
The issue is, i do that through an empty variable to hold the state but after you've already clicked once. Here is the code :
function homeLoaded() {
menuLoaded();
function menuLoaded() {
const sideMenuChildren = document.getElementById("sidemenu").childNodes;
let currentTab;
for(let i = 0; i < sideMenuChildren.length; i++) {
const tab = sideMenuChildren[i];
tab.addEventListener(
"click",
function() {
if(currentTab) {
sideMenuChildren[currentTab].classList.remove("liactive");
}
currentTab = i;
sideMenuChildren[currentTab].classList.add("liactive");
showContent(tab.id);
console.log("I clicked " + i);
}
);
}
}
let currentPage;
function showContent(menuName) {
const pageContainer = document.getElementById("content");
const element = pageContainer.querySelector("." + menuName);
if(currentPage) {
currentPage.classList.remove("selected");
}
currentPage = element;
element.classList.add("selected");
}
}
Here is the HTML :
<div id="main" style="background-image: url(./images/bg2.png);">
<div id="box">
<ul id="sidemenu">
<li id="profil" class="liactive">Profil</li>
<li id="parcours">Parcours</li>
<li id="contact">Contact</li>
</ul>
<div id="content">
<div class="profil selected"><p>Blabla</p></div>
<div class="parcours"><p>Blablabla</p></div>
<div class="contact"><p>Blablablabla</p></div>
</div>
<div id="bottomnav">
<div id="bottomnavcontent">
</div>
</div>
</div>
</div>
What this does sadly is that when i click another tab...it does not remove the first active tab, so i have 2 active tabs until i re-click the first tab to store it in the variable. I don't know how to fix this.
demo of working codeYou can get event inside the event listener function. There you can get the target.
So,first Remove active class of current tab. Then, set active class for target element.
i.e
tab.addEventListener(
"click",
function (ev) {
const currentab = document.querySelector(".liactive");
currentab.classList.remove("liactive")
ev.target.classList.add("liactive")
}
)
What I'm trying to do is hide text when ngState is true. When a certain element is clicked, that state is set to true. The [ngClass] should then add the hide class and hide the text. This first snippet is from the component.ts which outlines the boolean variable and the function which sets it to true.
export class MainMenuComponent implements OnInit {
ngState = false;
constructor() {
}
newGame(){
this.ngState = this.ngState === true ? false : true;
console.log(this.ngState);
}
}
This next snippet is the component html
<canvas id='sparkCanvas'></canvas>
<div class="menuBox">
<div class="title" [ngClass]="{'hide': ngState}">Dark Shards</div>
<div class="optContainer">
<ul>
<li *ngFor="let opt of opts" class="{{opt.class}}" [ngClass]="{'hide': ngState}" (click)="opt.f()">{{opt.n}}</li>
</ul>
</div>
</div>
and here is the hide class below
.hide{
opacity: 0;
}
When I replace [ngClass]="{'hide': ngState}" with [ngClass]="{'hide': true}"
It will then work as intended. What am I not understanding here?
Here is a link to my code with a working example:
https://stackblitz.com/edit/angular-fg48ro?file=src%2Findex.html
Try without Quote
<li *ngFor="let opt of opts" class="{{opt.class}}" [ngClass]="{hide: ngState}" (click)="opt.f()">{{opt.n}}</li>
EDIT
When i see your code, the issue is not related to angular, but with javascript context, you need to specifiy the context of this like
' f: this.newGame.bind(this),'
DEMO
I've created a mobile dropdown menu that toggles open and closed based on state. Once it's open, I would like the user to be able to close the dropdown by clicking anywhere outside the ul.
I'm setting the tabIndex attribute on the ul to 0, which gives the ul "focus". I've also added an onBlur event to the ul that triggers the state change (dropdownExpanded = false) that hides the ul.
<ul tabIndex="0" onBlur={this.hideDropdownMenu}>
<li onClick={this.handlePageNavigation}>Page 1</li>
<li onClick={this.handlePageNavigation}>Page 2</li>
<li onClick={this.handlePageNavigation}>Page 3</li>
</ul>
However, when I implement this fix, the onClick events that I have on each li element fail to fire.
I know something is going on with the event bubbling, but I am at a lose as to how to fix it. Can anyone help?
NOTE:
I know you can create a transparent div below the ul that spans the entire viewport and then just add an onClick even to that div that will change the state, but I read about this tabIndex/focus solution on Stack Overflow and I'd really like to get it working.
Here is a more complete view of the code (the dropdown is for users to select their home country, which updates the ui):
const mapStateToProps = (state) => {
return {
lang: state.lang
}
}
const mapDispatchToProps = (dispatch) => {
return { actions: bindActionCreators({ changeLang }, dispatch) };
}
class Header extends Component {
constructor() {
super();
this.state = {
langListExpanded: false
}
this.handleLangChange = this.handleLangChange.bind(this);
this.toggleLangMenu = this.toggleLangMenu.bind(this);
this.hideLangMenu = this.hideLangMenu.bind(this);
}
toggleLangMenu (){
this.setState({
langListExpanded: !this.state.langListExpanded
});
}
hideLangMenu (){
this.setState({
langListExpanded: false
});
}
handleLangChange(e) {
let newLang = e.target.attributes['0'].value;
let urlSegment = window.location.pathname.substr(7);
// blast it to shared state
this.props.actions.changeLang( newLang );
// update browser route to change locale, but stay where they are at
browserHistory.push(`/${ newLang }/${ urlSegment }`);
//close dropdown menu
this.hideLangMenu();
}
compileAvailableLocales() {
let locales = availableLangs;
let selectedLang = this.props.lang;
let markup = _.map(locales, (loc) => {
let readableName = language[ selectedLang ].navigation.locales[ loc ];
return (
<li
key={ loc }
value={ loc }
onMouseDown={ this.handleLangChange }>
{ readableName }
</li>
);
});
return markup;
}
render() {
let localeMarkup = this.compileAvailableLocales();
return (
<section className="header row expanded">
< Navigation />
<section className="locale_selection">
<button
className="btn-locale"
onClick={this.toggleLangMenu}>
{this.props.lang}
</button>
<ul
className={this.state.langListExpanded ? "mobile_open" : " "}
value={ this.props.lang }
tabIndex="0"
onBlur={this.hideLangMenu}>
>
{ localeMarkup }
</ul>
</section>
</section>
)
}
}
Try using onMouseDown instead of onClick.
The point is the onBlur is triggering a re-render which seems to lead the browser to do not follow up with the onClick: https://github.com/facebook/react/issues/4210
But if you check the onBlur event you can find some info about what's happening, event.relatedTarget is populated and you can use these info to detect when the onBlur is actually triggered by the onClick and chain whatever you need to do.
I just ran into this with an array of breadcrumb links, where an onBlur handler was causing a rerender, preventing the link click from working. The actual problem was that react was regenerating the link elements every time, so when it rerendered, it swapped the link out from under the mouse, which caused the browser to ignore the click.
The fix was to add key properties to my links, so that react would reuse the same DOM elements.
<ol>
{props.breadcrumbs.map(crumb => (
<li key={crumb.url}>
<Link to={crumb.url} >
{crumb.label}
</Link>
</li>
))}
</ol>