Programmatically Collapse Bootstrap Navbar in Typescript - javascript

I'm using Bootstrap's navbar and I can get it to collapse successfully by using data-toggle and data-target on each li element.
This SO answer shows a way to do this without having to alter each li:
https://stackoverflow.com/a/42401686/279516
This is my navbar with two of the li elements:
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item" data-toggle="collapse" data-target=".navbar-collapse.show">
<a class="nav-link" href="#" routerLink="/servers">Servers</a>
</li>
<li class="nav-item" data-toggle="collapse" data-target=".navbar-collapse.show">
<a class="nav-link" href="#" routerLink="/servers">Variables</a>
</li>
I'm close to getting this done in my Angular 8 Typescript file:
export class AppComponent implements OnInit {
ngOnInit(): void {
const navbarItems = document.querySelectorAll('.navbar-nav>li');
navbarItems.forEach(navbarItem => {
navbarItem.addEventListener('click', () => {
const navbar = document.querySelector('.navbar-collapse').collapse('hide');
})
});
}
}
The issue is the last line:
Property collapse does not exist on type element.
First, what should I do to get this to work?
Second, is there a better way?
I've tried casting navbar as different types of HTML elements, but that doesn't work either.

You can do it more the Angular way. Like this:
<button class="navbar-toggler" type="button" (click)="showMenu=!showMenu">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" [ngClass]="{'show':showMenu}">
...
</div>

I was able to get it to work by removing 'show' from the class list, shown on the last line here.
(I'm still not sure if this is a good approach, but it's working.)
export class AppComponent implements OnInit {
ngOnInit(): void {
const navbarItems = document.querySelectorAll('.navbar-nav>li');
navbarItems.forEach(navbarItem => {
navbarItem.addEventListener('click', () => {
const navbar = document.querySelector('.navbar-collapse');
navbar.classList.remove('show');
})
});
}
}

Related

Invoking JS Interop Function for DOM element located in submenu in Blazor

I am trying to invoke a JS function that creates a modal on user click through JS Interop in Blazor. The thing is, the function is using a onclick event for a DOM element that is inside a Blazor submenu. When that DOM Element is outside the submenu in the side navbar, it works fine. When I put that element inside the submenu, Blazor complains that it cannot set property 'onclick' of null
Here is my navbar HTML and JS Interop code
#inject IJSRuntime JS;
<div class="top-row pl-4 navbar navbar-dark">
<a class="navbar-brand" href="">Test</a>
<button class="navbar-toggler" #onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="#NavMenuCssClass" #onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="/" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" #onclick="()=>expandSubNav = !expandSubNav">
<span class="oi oi-fork" aria-hidden="true"></span> AccountPage
</NavLink>
#if (expandSubNav)
{
<ul class="nav flex-column" id="submenu">
<li class="nav-item px-3">
<a class="expand-menu" href="page">
<span>element1</span>
</a>
</li>
<li class="nav-item px-3">
<a class="expand-menu" href="page">
<span>element2</span>
</a>
</li>
<li class="nav-item px-3">
<a class="expand-menu" href="page">
<span>element3</span>
</a>
</li>
<li class="nav-item px-3">
<a class="expand-menu">
<div id="addBtn" ><span class="oi oi-plus" aria-hidden="true"></span> Add Element</div>
</a>
</li>
</ul>
}
</li>
</ul>
</div>
<div id="myModal" class="modal">
<!-- Modal content -->
<div class="modal-content">
<h1 class="modal-title">
<b>Add Element Here</b>
</h1>
<input id="addelementtext" type="text" />
<button id="add">Add</button>
<button type="button" class="btn btn-defaultcloseissue" id="closebtn" data-dismiss="modal" onclick="">Close</button>
</div>
</div>
</div>
</div>
#code { private bool collapseNavMenu = true;
private bool expandSubNav;
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("createModal");
}
}
}
I am trying to use onclick with this id element
<li class="nav-item px-3">
<a class="expand-menu">
<div id="addBtn" ><span class="oi oi-plus" aria-hidden="true"></span> Add Element</div>
</a>
</li>
It is inside the submenu it created. When I put it outside the submenu into the main nav menu, the JS function does not complain and it works fine.
I have also tried doing this
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
if (expandSubNav)
{
await JS.InvokeVoidAsync("createModal");
}
}
}
This stops the error but clicking the element does nothing. How can I go about fixing this? I am trying to research about the Blazor LifeCycle as I feel this is a rendering issue but I am having a hard time understanding what I can do.
Here is also the JS function that I am trying to call
window.createModal = function createModal() {
var modal = document.getElementById("myModal");
// Get the button that opens the modal
var btn = document.getElementById("addBtn");
var closebtn = document.getElementById("closebtn");
// When the user clicks the button, open the modal
btn.onclick = function () {
modal.style.display = "block";
}
// When the user clicks anywhere outside of the modal, close it
window.onclick = function (event) {
if (event.target == modal) {
modal.style.display = "none";
}
}
closebtn.onclick = function (event) {
modal.style.display = "none";
}
}
Your first error, cannot set property 'onclick' of null, happens because the subnav portion is not expanded when the navbar is first rendered, so the addBtn element isn't found in the DOM yet when you call var btn = document.getElementById("addBtn");
The second issue is because your if() statement only runs once, on first render, while expandSubnav is false. So your await JS.InvokeVoidAsync("createModal"); line will never get executed.
If you notice in the #code block there's a method for toggling the collapseNavMenu:
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
If you did something similar, but for expandSubnav, you could use that as an opportunity to hook in your createModal code. You only want to do it once, so you'd probably want to keep track of that too. Something like:
private bool modalCreated = false;
private void ToggleSubNav()
{
expandSubnav = !expandSubnav;
if(!modalCreated)
{
await JS.InvokeVoidAsync("createModal");
modalCreated = true;
}
}
And then hook that into your NavLink:
<NavLink class="nav-link" #onclick="ToggleSubNav">
<span class="oi oi-fork" aria-hidden="true"></span> AccountPage
</NavLink>

How do I update Angular styling using ElementRef?

I am new to Angular I am working on a bottom that updates the styling when pressed. Here is a copy of the HTML page.
<nav>
<button #toggleNavigation aria-expanded="false">
<mat-icon>menu</mat-icon>
</button>
<div class="sidebar-sticky collapse navbar-collapse">
<ul>
<side-menu-item *ngFor="let item of navItems" [hidden]="item.isHidden"
[finished]="item.finished" [name]="item.displayName"
[routeURL]="item.routeURL"
[ngClass]="{'mt-auto': item.name === 'Summary'}">
</side-menu-item>
</ul>
</div>
</nav>
When the button is pressed, this is what I would like it to update to.
<nav>
<button #toggleNavigation aria-expanded="true">
<mat-icon>menu</mat-icon>
</button>
<div class="sidebar-sticky collapse navbar-collapse show">
<ul>
<side-menu-item *ngFor="let item of navItems" [hidden]="item.isHidden" [finished]="item.finished" [name]="item.displayName" [routeURL]="item.routeURL" [ngClass]="{'mt-auto': item.name === 'Summary'}"></side-menu-item>
</ul>
</div>
</nav>
Clicking the button will display a navigation list. As you see in the code the styling is updated from class="sidebar-sticky collapse navbar-collapse" to class="sidebar-sticky collapse navbar-collapse show". Originally, I using a bootstrap.js file to handle this but it interfered with styling associated with another program. In my .ts file this is what I have
import { Component, ElementRef, Renderer2, ViewChild } from '#angular/core';
#Component({
selector: 'app-side-menu',
templateUrl: './side-menu.component.html',
styleUrls: ['./side-menu.component.scss']
})
export class NavMenuComponent {
#ViewChild('toggleNavigation', {static: false}) toggleNavigation: ElementRef;
constructor() {
onToggleNavigation() {
}
}
Any help would be greatly appreciated.
I would do this with [ngClass].
Try:
<nav>
<button #toggleNavigation aria-expanded="true" (click)="onToggleNavigation()">
<mat-icon>menu</mat-icon>
</button>
<div class="sidebar-sticky collapse" [ngClass]="{'show': !collapseSideNav}">
<ul>
<side-menu-item
*ngFor="let item of navItems"
[hidden]="item.isHidden"
[finished]="item.finished" [name]="item.displayName"
[routeURL]="item.routeURL"
[ngClass]="{'mt-auto': item.name === 'Summary'}"
(click)="collapseNavigation()"
>
</side-menu-item>
</ul>
</div>
</nav>
collapseSideNav: boolean = true; // can default it to whatever you like
onToggleNavigation() {
this.collapseSideNav = !this.collapseSideNav;
}
collapseNavigation() {
if (!this.collapseSideNav) {
this.collapseSideNav = true;
}
}
Use [ngClass] for this:
<nav>
<button [attr.aria-expanded]="collapsed" (click)="collapsed = !collapsed">
click
</button>
<div [ngClass]="{'show' : collapsed}" class="sidebar-sticky collapse navbar-collapse">
<ul>
content
</ul>
</div>
</nav>
https://stackblitz.com/edit/angular-3rn8no?file=src%2Fapp%2Fapp.component.ts

How to show and hide side menu in angular2

In my application i have to implement hide and show side menu. By default the page menu is open while clicking the toggle menu i have to hide the side menu. How can i implement this.
what i have is:
app.component.html, nav.component.html
<div class="menu-toggler sidebar-toggler">
<span></span>
</div>
<ul>
<li class="nav-item ">
<a class="nav-link nav-toggle">
<i class="icon-diamond"></i>
<span class="title">Name</span>
<span class="arrow"></span>
</a>
</li>
</ul>
Myservice.ts
export class GlobalService {
public collapse;
constructor() { }
setValue(val: boolean) {
this.collapse = val;
}
getValue() {
return this.collapse;
}
EDIT
app.component.html
<div *ngIf="!toggle()"class="menu-toggler sidebar-toggler">
<span></span>
</div>
app.component.ts
import { GlobalService } from "path";
export class AppComponent {
toggle() {
this.globalService.setValue(false);
}
}
how can i hide this list(in nav.html) while clicking menu toggle (app.compnent.html)? Any help will really appreciable. i am new to angular.
If use of service is not the priority then you can simply maintain simple variable to do this task.
Your app.component.ts
export class AppComponent {
showMenu : boolean = true;
}
Your app.component.html
<div (click)="showMenu = !showMenu" class="menu-toggler sidebar-toggler"><span></span>
</div>
<ul *ngIf="showMenu">
<!-- used showMenu to hide/show -->
<li class="nav-item ">
<a class="nav-link nav-toggle">
<i class="icon-diamond"></i>
<span class="title">Name</span>
<span class="arrow"></span>
</a>
</li>
</ul>
hope this helps ...
For this ,
You can make a CommonService to store the state of menu or and use that Service to make toggle you menu.
You can also use #Input #Output , in case you are having parent child relation between components.
Method will depend on how is your project/file structure.
You can create a service and preferably make a static variable inside to get and set the visibility state of the menu. By this you could directly set and get the variable by using ComponentName.variableName.
to play with the visibility you could use(Sorry if there is any syntax errors)
1> Set the document.getelementbyid("idofelement").display= none or block
2>use *ngIf="someboolean" where you should set the boolean in your ts file

How to hide elements based on user in FireBase and Angular

I'm starting to use Firebase with AngularJS.
Coming from a php/serverside rendered pages.
I dont't get how we're supposed to hide parts of an app to some users.
I have basically 3 levels of users (guests / members / admins)
I could hide with a ng-show based on user, but this only hides client-side.
Data is still sent to the user
Real life example:
The menu items are different based on user level.
I was thinking about using ngshow and check for the uuid , but then again, is exposing the admins uuid a good idea? sounds terrible to me.
Then I thought about putting the menu inside a database and requesting the elements.
Not all users would access all items, but this means a lot of 'unauthorised access attempts on purpose'
What is the correct way of handling this? I feel like I've missed something important about client-only apps relying on Firebase services.
Look at userStatus method in service layer and it usage in other layers.
Service layer :
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { AngularFireAuthModule, AngularFireAuth } from 'angularfire2/auth';
import * as firebase from 'firebase/app';
#Injectable()
export class AuthService {
user: Observable<firebase.User>;
constructor(private fireAuth: AngularFireAuth) {
}
loginGoogle() {
this.fireAuth.auth.signInWithPopup(new firebase.auth.EmailAuthProvider())
.catch(function (error) {
alert('Please try again');
});
}
logout() {
this.fireAuth.auth.signOut();
}
userStatus() {
return this.fireAuth.authState;
}
}
Navbar Component :
import { Component, OnInit } from '#angular/core';
import { AuthService } from '../../services/auth.service';
import { Observable } from 'rxjs/Observable';
import * as firebase from 'firebase/app';
#Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit {
user: Observable<firebase.User>;
constructor(private authservice: AuthService) {
this.user = this.authservice.userStatus();
}
ngOnInit() {
}
login() {
this.authservice.loginGoogle();
}
logout() {
this.authservice.logout();
}
}
And here is Navbar view, where you want to hide or show the elements based on if user is authenticated :
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
<a class="navbar-brand" routerLink="/">Firebase</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
<ul class="navbar-nav mr-auto">
<li class="nav-item ">
<a class="nav-link" routerLink="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" *ngIf="(user | async)?.uid" routerLink="/listings">Listings</a>
</li>
<li class="nav-item">
<a class="nav-link" *ngIf="(user | async)?.uid" routerLink="/add-listing">Add Listing</a>
</li>
</ul>
<ul class="navbar-nav navbar-right">
<li class="nav-item">
<a class="nav-link" *ngIf="!(user | async)?.uid" (click)="login()">Login</a>
<a class="nav-link" *ngIf="(user | async)?.uid" (click)="logout()">Logout</a>
</li>
</ul>
<div *ngIf="(user | async)?.uid">
<img src="{{(user | async)?.photoURL}}" style="width:30px;height:30px;">
<br> Email: {{(user | async)?.email}}
<br> Name: {{(user | async)?.displayName}}
</div>
</div>
</nav>

More efficient way of listening for jQuery hasClass method?

I'm trying to listen if an element with the id of dropdown has the class open. If #dropdown has .open, add class badge-border to class badge. If #dropdown doesn't have .open, remove class badge-border from class badge. Here is my code:
HTML:
<!doctype html>
<body>
<nav class="navbar navbar-default">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<li>Language (EN)
</li>
<li>Currency ($)
</li>
<li><i class="fa fa-user"></i> My Account
</li>
<li class="dropdown" id="dropdown"> <i class="fa fa-shopping-cart"><span class="badge">36</span></i> My Cart
<ul class="dropdown-menu" id="dropdown-menu">
<li>Action
</li>
<li>Another action
</li>
<li>Something else here
</li>
<li role="separator" class="divider"></li>
<li>Separated link
</li>
</ul>
</li>
</ul>
</div>
<!-- /.navbar-collapse -->
</div>
<!-- /.container-fluid -->
</nav>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script>
window.jQuery || document.write('<script src="js/vendor/jquery-1.11.3.min.js"><\/script>')
</script>
</body>
JS:
$(document).ready(function () {
var interval = setInterval(changeBadge, 1);
function changeBadge() {
if ($('#dropdown').hasClass('open')) {
console.log('add class border');
// clearInterval(interval);
$(".badge").addClass("badge-border");
} else {
console.log('remove class border');
$(".badge").removeClass("badge-border");
}
};
});
The above way works to accomplish my goals, but I have a feeling that listening to an event every 1 millisecond isn't the best way to do it.
To make more sense of all this, you can view the demo here: http://jsfiddle.net/obliviga/qt7o3u6e/2/
You will need to maximize the viewport until the navbar is not in mobile mode, then click the shopping cart dropdown to see the badge's border added.
EDIT:
The reason why I implemented the way I did is because when the user clicks out of the cart dropdown, it should close, and the styles should revert to how they were before.
Consider using Mutation Observers intended exactly to track DOM changes, including changes of attribute values.
Note that IE supports Mutation Observers since version 11. For IE 10 and older, a fallback is needed.
EDIT: The reason why I implemented the way I did is because when the
user clicks out of the cart dropdown, it should close, and the styles
should revert to how they were before.
Updated , Added click event attached to document which removes .open class from #dropdown if present , calls handler of #dropdown a click event with this set to #dropdown a , with handler remove .badge-border class from .badge element which should revert styles of .badge to properties initially set at css.
Try attaching click event to selector $("#dropdown a") , calling .delay(1) on $(this).parents("#dropdown") within handler , .queue() function to check for open class , .toggleClass() to set $(this).parents("#dropdown") class on $(this).parents("#dropdown") with second argument Boolean $(this).parents("#dropdown")
$(document).ready(function () {
function changeBadge(e) {
$(this).parents("#dropdown")
.delay(1, "check")
.queue("check", function () {
$(".badge").toggleClass("badge-border", $(this).hasClass("open"));
}).dequeue("check")
}
$("#dropdown a").click(changeBadge);
$(document).on("click", function (e) {
var elem = $("#dropdown");
if ($(e.target).is("#dropdown *")) {
return false
} else {
if (elem.is(".open") || $(".badge").hasClass("badge-border")) {
elem.removeClass("open");
changeBadge.call(elem.find("a.dropdown-toggle")[0])
}
}
})
});
jsfiddle http://jsfiddle.net/qt7o3u6e/6/

Categories