VueJS close Hamburger Menu on Route Change - javascript

I have a simple VueJS App with a Navigation Bar from Bootstrap:
<template>
<header id="header">
<nav class="navbar mynavbar navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="logo" href="index.html"><img src="images/logo.png" alt=""></a>
</div>
<div class="collapse navbar-collapse" id="navbar-collapse">
<ul class="nav navbar-nav navbar-right">
<li><router-link to="/home"><a>Home</a></router-link></li>
<li><router-link to="/about"><a>About Us</a></router-link></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container -->
</nav>
</header>
</template>
Now I want to ensure that when I change the route, the Bootstrap Menu gets closed. What is the best way to accomplish this?

Instead of adding event handlers to every router-link, you can simply watch the $route property for changes:
<script>
export default {
watch: {
'$route' () {
$('#navbar-collapse').collapse('hide');
}
}
}
</script>

Without Bootstrap and jQuery, can be easily achieved using class toggle and watch route changes.
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false"
#click="toggledNav = !toggledNav"
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
Script:
export default {
data () {
return {
toggledNav: false
}
},
watch: {
'$route' () {
this.toggledNav = false
}
}
}

You could give this a try:
<template>
<header id="header">
<nav class="navbar mynavbar navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="logo" href="index.html"><img src="images/logo.png" alt=""></a>
</div>
<div class="collapse navbar-collapse" id="navbar-collapse">
<ul class="nav navbar-nav navbar-right">
<li><router-link #click.native="closeMenu()" to="/home"><a>Home</a></router-link></li>
<li><router-link #click.native="closeMenu()" to="/about"><a>About Us</a></router-link></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container -->
</nav>
</header>
</template>
<script>
export default {
methods: {
closeMenu() {
$('#navbar-collapse').collapse('hide');
}
}
}
</script>

With vue3 and bootstrap5, adding this to the router-link element:
data-bs-toggle="collapse" data-bs-target=".navbar-collapse"
did not work for me: the menu would close without the link being followed.
So, I added this to router/index.js:
router.beforeEach(() => {
document.getElementById('navbarCollapse').classList.remove('show');
})
with navbarCollapse being the id of the div holding the menu/nav items.
It seemed to do the trick.
If necessary you can adjust the hamburger button state: its class list would need to contain "collapsed" and aria-expanded set to "false".
This gets corrected though when the user clicks the button again, so I didn't opt to write that code.

If you are using bootstrap-vue and vue-router.
<script>
export default {
watch: {
'$route' () {
const element = document.querySelector("#nav-collapse");
let isShown = element.classList.contains("show");
if(isShown){
this.$root.$emit('bv::toggle::collapse', 'nav-collapse')
}
}
}
}
</script>
<template>
<b-navbar toggleable="lg" type="" class="p-0">
<b-navbar-toggle target="nav-collapse" id="navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</b-navbar-toggle>
<b-collapse id="nav-collapse" is-nav>
<div class="nav-wrapper w-100">
<ul class="app-navigation__list">
<router-link to="/whatever" tag="li" exact class="app-navigation__list__item">
<a href="">
<span class="link-text">Home</span>
</a>
</router-link>
<router-link to="/whatever" tag="li" exact class="app-navigation__list__item">
<a href="">
<span class="link-text">About</span>
</a>
</router-link>
<router-link to="/whatever" tag="li" exact class="app-navigation__list__item">
<a href="">
<span class="link-text">Store</span>
</a>
</router-link>
</ul>
</div>
</b-collapse>
</b-navbar>
</template>

I know this is an old question but my answer might help someone.
do this in your router/index.js
const router = createRouter({}); //P.S I am using Vue3
then add this before each route entering like so.
router.beforeEach(() => {
$('.navbar-collapse').collapse('hide'); //Be sure to import jquery
});

Because this is such a simple task, I try to make it as simple as my simple website.
Vue.mixin((()=> {
let store = Vue.observable({
indexMenu: false,
})
return{
computed: {
menu: {
get() {
return store.indexMenu
},
set(val) {
store.indexMenu = val
}
}
},
watch: {
'$route' () {
this.menu = false
}
}
}})())

router.beforeEach((from,to,next) => {
$('.navbar-collapse').collapse('hide'); //Be sure to import jquery
next();
});
This works

Related

How can I create dynamic menu with submenu in vue 2?

I have a problem and if you can help me I would be grateful, because I am working with vue for 3 weeks.
I try to create a menu with submenu in vue 2 by two weeks and I can't succeed. the menu is created , but when I go to submenu is won't create it. the code is next:
<script>
import image from "./assets/img/logo-just-escape.png"
import axios from "axios"
export default {
data(){
const lang = localStorage.getItem('lang')
return {
imagel: image,
lang: lang,
languages:null,
menu:null,
submenu:null
}
},
async created(){
try {
var res =await axios.get(`lng`);
this.languages=res.data;
const valmenu="titles/value?language="+this.lang;
res=await axios.get(valmenu);
this.menu=res.data;
this.submenu=this.getSubMnu(this.menu);
console.log(this.submenu)
}catch(e){
console.error(e);
}
},
methods: {
handleChange(event){
localStorage.setItem('lang',event.target.value);
window.location.reload();
},
getSubMnu(value){
let temp='';
let sbmenu=[];
let variable=null;
for(temp in value){
if('2'==value[temp].subtitle){
variable=value[temp];
let temp1=value[temp].link+"\/"+this.menu[temp].id;
variable.link=temp1;
sbmenu.push(variable);
}
}
return sbmenu;
}
}
}
</script>
<template>
<div id="app" class="bg-dark">
<div>
<nav class="navbar fixed-top navbar-expand-lg navbar-dark bg-dark fixed-top">
<a class="navbar-brand" href="/">
<img width="100" height="50" :src="imagel"/>
</a>
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
<div v-for="menuButton in this.menu" :value="menuButton.title" :key="menuButton.id">
<li class="nav-item" v-if="menuButton.subtitle ===0">
<router-link :to="menuButton.link" ><span class="nav-link">{{menuButton.title}}</span></router-link>
</li>
<li class="nav-item dropdown" v-if="menuButton.subtitle ===1" style="color: white;">
<span class="nav-link dropdown-toggle" data-toggle="dropdown" aria-expanded="false">{{menuButton.title}}</span>
<div v-for="tempor in this.submenu" class="dropdown-menu dropdown-menu-right bg-dark" :value="tempor.title" :key="tempor.id">
<span class="nav-link">{{tempor.title}}</span>
<router-link :to="tempor.link" #click.name><span class="nav-link">{{tempor.title}}</span></router-link>
</div>
</li>
</div>
<li class="nav-item">
<select class="selectpicker form-control" v-model="lang" data-width="fit" #change="handleChange($event)">
<option v-for="langu in languages" :value="langu.language_short" :key="langu.id">
{{langu.language}}
</option>
</select>
</li>
</ul>
</div>
</nav>
<div class="divmargin" >
<router-view/>
</div>
</div>
</div>
</template>
vue.runtime.esm.js?2b0e:619 [Vue warn]: Error in render: "TypeError: Cannot read property 'submenu' of undefined"
found in
---> at src/App.vue
If someone can give me some answer to this I will be very grateful.
Thank you,
Vlad.

Bootstrap Navbar Active Links

I am using Visual Studio 2017 with the built in MVC template, I added to bootstrap.css to create a navbar that is along the left side of the screen on sm/md/lg and across the top on xs. The navbar looks like so
<div id="sidenav" class="col-xs-12 col-sm-3 col-lg-2 navbar navbar-default navbar-fixed-left nav-stacked">
<div class="col-xs-12 container">
<!--Header-->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
#Html.ActionLink("Menu", "Index", "Home", "Home", new { #class = "navbar-brand" })
</div>
<!--Content-->
<div class="navbar-collapse">
<ul class="nav navbar-nav">
<li class="active">Home</li>
<li>Elements</li>
</ul>
</div>
</div>
I added to bootstrap.js the following and made no other changes:
$('.navbar-nav li a').click(function (e) {
$('.navbar-nav li.active').removeClass('active');
var $this = $(this);
if (!$this.hasClass('active')) {
$this.addClass('active');
}
e.preventDefault();
});
The problem seems to be with the e.preventDefault(), if I include it, the active list item will change, but navigation doesn't happen. If I remove it, the active item will clear, the clicked item will change color, navigation happens, but then the correct link will go back to normal and the original active link is active again.
Probably something small and stupid but what do I have wrong or what am I missing, thank you.

Prevent Bootstrap collapse navbar from closing on mobile when scrolling

I have a vertical navbar that becomes collapsible when viewed in mobile devices. I want my users to continue to see the navbar even after they scroll, and only have it close by clicking on an item or on the nav toggle.
I've tried the following (from a post on stack overflow :) ) :
$('.sidebar-navbar-collapse').on({
"show.bs.collapse": function() { this.closable = false;console.log('in show nav'); },
"click": function() { this.closable = true; console.log('in click to hide nav'); },
"hide.bs.collapse": function() { return this.closable;console.log('in hide nav, closable:', this.closable); }
});
But while I see the output for the shown & click, I don't see it for the hide event when it is closed by scrolling.
HTML:
<div class="col-md-4 col-sm-4 sidebar-nav">
<div class="navbar" role="navigation">
<h1 class='hidden-xs' style="border-bottom: 1px solid #ddd;">Topics</h1>
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".sidebar-navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<span class="visible-xs navbar-brand">Topics</span>
</div>
<div class="navbar-collapse collapse sidebar-navbar-collapse">
<ul class="nav navbar-nav" style="list-style:none; font-size: 16pt !important; font-family: תArial, sans-serif;" lang="HE">
<li>
Privacy
</li>
<li>
Report
</li>
</ul>
<div class="clear"></div>
</div>
</div>
Here is a js fiddle:
https://jsfiddle.net/bv773t0a/
Thank you!
Rightly pointed out by #Karan3112
You have something conflicting with Bootstrap Javascript within your main.js. Try:
jQuery(".collapse.navbar-collapse.in").removeClass("in");

Image not resizing Bootstrap 3

I am currently using the Bootstrap 3 framework to create a prototype, I have a logo image in my header of my website that doesn't seem to be resizing at all. I have given it the image-responsive class but it doesn't affect it.
I think the column may not be holding the image content. I have created a fiddle (with a sample header image off google)
http://jsfiddle.net/pocockn/52VtD/10157/
<!-- navbar and logo -->
<div class="navbar navbar-default" role="navigation">
<div class = "row nav-row">
<div class="col-md-10">
<div class="navbar-header">
<a class="navbar-brand" href="#"><img src ="http://slant.investorplace.com/files/2013/08/pandora-stock-logo.png" class="img-responsive"></a>
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle Navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
</div> <!-- end of col md 8 -->
<div class="col-md-2">
<div class="navbar-collapse collapse">
<ul class="nav nav-stacked">
<li>ROOMS</li>
<li>PROFILE</li>
<li>CONTACT</li>
</ul>
</div> <!-- end of col md 4 -->
</div><!-- end of row -->
</div><!-- end of container -->
Thanks!
Demo Try This
I Added class "logo" to img tag and "remove_pad" to a tag
.logo{
width:100%;
height:100%;
}
.remove_pad{
padding:0px !importnat;
}
One way is to override directly your img with css :
Fiddle : http://jsfiddle.net/62uz5odq/
.navbar-brand > img {
width:auto;
height:100%;
}

Angularjs - data binding not working with controller

I am developing an app that uses bootstrap (v3.2.0) with angularjs (v1.2.26), I want to create a set of columns that are changable via a dropdown.
I have got the dropdown data binding ok, but I am failing to change the bootstrap responsive column classes when the user selects a different number of columns in the dropdown.
This fiddle shows my code so far.
The html...
<div data-ng-app="">
<div class="container-fluid">
<nav class="navbar navbar-default" role="navigation">
<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"> <span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button> <span class="navbar-brand navbar-collapse-header">Header</span>
</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 data-ng-controller="ColumnController" id="columnDropDown" class="dropdown"> Columns: {{columnLayout.value}} <span class="caret"></span>
<ul class="dropdown-menu" role="menu">
<li data-ng-repeat="layout in columnLayouts"> <a data-ng-click="changeLayout(event)" href="#" class="portlet-column-number">{{layout.text}}</a>
</li>
</ul>
</li>
</ul>
</div>
<!-- /.navbar-collapse -->
</div>
<!-- /.container-fluid -->
</nav>
</div>
<div class="row">
<div data-ng-controller="ColumnController" data-ng-class="getLayoutClass()">
<div class="panel panel-default">
<div class="panel-heading mo-product-header">Header 1</div>
<div class="panel-body">More content</div>
<div class="panel-footer">Layout class is {{getLayoutClass()}}</div>
</div>
</div>
<div data-ng-controller="ColumnController" data-ng-class="getLayoutClass()">
<div class="panel panel-default">
<div class="panel-heading mo-product-header">Header 2</div>
<div class="panel-body">Some other content</div>
<div class="panel-footer">Layout class is {{getLayoutClass()}}</div>
</div>
</div>
<div data-ng-controller="ColumnController" data-ng-class="getLayoutClass()">
<div class="panel panel-default">
<div class="panel-heading mo-product-header">Header 3</div>
<div class="panel-body">Some content</div>
<div class="panel-footer">Layout class is {{getLayoutClass()}}</div>
</div>
</div>
</div>
</div>
and js...
function ColumnController($scope) {
$scope.columnLayouts = [{
value: 3,
text: "Reset",
layoutClass: "col-sm-6 col-md-4"
}, {
value: 1,
text: "1",
layoutClass: "col-sm-12"
}, {
value: 2,
text: "2",
layoutClass: "col-sm-6"
}, {
value: 3,
text: "3",
layoutClass: "col-sm-4"
}];
$scope.changeLayout = function () {
$scope.columnLayout = this.layout;
}
$scope.getLayoutClass = function(){
return $scope.columnLayout.layoutClass;
}
$scope.columnLayout = $scope.columnLayouts[0];
}
UPDATE:
Ok so it turns out that I should have only added the 'ColumnController' to one element and in this case that element works best as the wrapper for all other elements.
However, I think to further improve this i will probably create a separate controller for navigation and then broadcast messages about change, in this case I could have the ColumnController listen for messages about column changes and repsond to them to change the model driving the column layout.
The problem is that you put too many data-ng-controller="ColumnController". You don't need to add it to every tag where you use angular binding {{}} or ngRepeat. You need only one on the wrapping container. For example:
<div data-ng-app="" data-ng-controller="ColumnController">
...
</div>
Demo: http://jsfiddle.net/pykm0tkv/25/

Categories