Show or hide offcanvas menu when router link is clicked - javascript

I have a vue electron app where the main menu template is loaded inside the App.vue file. I want to hide it when a <router-link> is clicked. It's an offcanvas menu so only the hamburger button is always displayed in all the components and the content will be showed only when the button is clicked. Is there any valid solution to manage this?
This is a sample of the menu markup that is inside the main view loaded inside the App.vue file
<template>
<div>
<nav v-if="isVisible">
<button #click.prevent="toggleMenu()"><i class="fas fa-bars"></i></button>
<li><router-link to="/"></router-link></li>
<li><router-link to="/link-one"></router-link></li>
</nav>
<router-view></router-view>
</div>
</template>
<script>
export {
methods: {
toggleMenu() {
this.isVisible = !this.isVisible;
}
}
}
</script>
At the moment the offcanvas content will remain on screen also after the router view will change.

use click.native?
Did a quick search on stackoverflow, looks like click on router-link will trigger click.native
https://stackoverflow.com/a/52230500/13703967
<template>
<div>
<nav v-if="isVisible">
<button #click.prevent="toggleMenu()"><i class="fas fa-bars"></i></button>
<li><router-link to="/" #click.native="toggleMenu"></router-link></li>
<li><router-link to="/link-one" #click.native="toggleMenu"></router-link></li>
</nav>
<router-view></router-view>
</div>
</template>
<script>
export {
methods: {
toggleMenu() {
this.isVisible = !this.isVisible;
}
}
}
</script>

Related

Dynamically show components according to route Vue.JS (v-show works but v-if does not)

So i need to conditionally render the header and footer based on the top level route. The following code works with v-show in statHeader and footerApp components but this is not what i want as v-show only hides the components using CSS.
v-if actually removes it from the DOM, but it only works once.
It doesnt update when the route changes.
From HOME to ADMIN : v-if hides the header and footer
but coming back to HOME: the header and footer still remains hidden and doesn't update.
New to Vue, help and insight understanding this concept would be very appreciated. Thank You.
<template>
<div id="app">
<!-- header -->
<statHeader v-show="checkComponent"/>
<!-- header -->
<!-- main content -->
<div id="content-scroll">
<router-view/>
<!-- footer -->
<footerApp v-show="checkComponent"/>
<!-- footer -->
</div>
<!-- main content -->
</div>
</template>
<script>
import footerApp from '#/components/Footer.vue'
import statHeader from '#/components/Header.vue'
export default {
name:"App",
data(){
return{
viewsList:['admin',],
}
},
components:{
statHeader,footerApp,
},
computed:{
checkComponent(){
if(this.$route.matched.some(({ name }) => this.viewsList.includes(name))){
return false;
}
else{
return true;
}
},
},
}
</script>

VueJs: How to reuse bootstrap modal dialog

I've created three simple buttons that will trigger three different bootstrap modal dialog. The modal dialogs are "Add Product", "Edit Product" and "Delete Product". Both the Add and Edit modal dialogs contain a form with two input elements, whereas the Delete modal dialog contains a simple text. I realise that my code becomes very messy and hard to maintain. Hence, I have the following question:
1) How do I reuse the modal dialog, instead of creating 3 separate dialogs?
2) How do I know which modal dialog has been triggered?
Update: I've developed a soultion where I will include conditional statements such as v-if, v-else-if and v-else to keep track of which button the user click. However, I still feel that there is a better solution to this. Can anyone help/advice me?
Below is my current code:
<template>
<div>
<b-button v-b-modal.product class="px-4" variant="primary" #click="addCalled()">Add</b-button>
<b-button v-b-modal.product class="px-4" variant="primary" #click="editCalled()">Edit</b-button>
<b-button v-b-modal.product class="px-4" variant="primary" #click="deleteCalled()">Delete</b-button>
<!-- Modal Dialog for Add Product -->
<b-modal id="product" title="Add Product">
<div v-if="addDialog">
<form #submit.stop.prevent="submitAdd">
<b-form-group id="nameValue" label-cols-sm="3" label="Name" label-for="input-horizontal">
<b-form-input id="nameValue"></b-form-input>
</b-form-group>
</form>
<b-form-group id="quantity" label-cols-sm="3" label="Quantity" label-for="input-horizontal">
<b-form-input id="quantity"></b-form-input>
</b-form-group>
</div>
<div v-else-if="editDialog">
<form #submit.stop.prevent="submitEdit">
<b-form-group id="nameValue" label-cols-sm="3" label="Name" label-for="input-horizontal">
<b-form-input id="nameValue" :value="productName"></b-form-input>
</b-form-group>
</form>
<b-form-group id="quantity" label-cols-sm="3" label="Quantity" label-for="input-horizontal">
<b-form-input id="quantity" :value="productQuantity">5</b-form-input>
</b-form-group>
</div>
<div v-else>
<p class="my-4">Are You Sure you want to delete product?</p>
</div>
</b-modal>
</div>
</template>
<script>
export default {
data() {
return {
productName: "T-Shirt",
productQuantity: 10,
addDialog: false,
editDialog: false,
deleteDialog: false
};
},
methods: {
addCalled() {
this.addDialog = true;
},
editCalled() {
this.editDialog = true;
this.addDialog = false;
this.deleteDialog = false;
},
deleteCalled() {
this.deleteDialog = true;
this.addDialog = false;
this.editDialog = false;
}
}
};
</script>
<style>
</style>
As already mentionned, I would have use slots and dynamic component rendering to accomplish what you're trying to do in a cleaner way.
See snippet below (I didn't make them modals as such but the idea is the same).
This way, you can have a generic modal component that deals with the shared logic or styles and as many modalContentsub-components as needed that are injected via the dedicated slot.
Vue.component('modal', {
template: `
<div>
<h1>Shared elements between modals go here</h1>
<slot name="content"/>
</div>
`
});
Vue.component('modalA', {
template: `
<div>
<h1>I am modal A</h1>
</div>
`
});
Vue.component('modalB', {
template: `
<div>
<h1>I am modal B</h1>
</div>
`
});
Vue.component('modalC', {
template: `
<div>
<h1>I am modal C</h1>
</div>
`
});
new Vue({
el: "#app",
data: {
modals: ['modalA', 'modalB', 'modalC'],
activeModal: null,
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button v-for="modal in modals" #click="activeModal = modal"> Open {{ modal }} </button>
<modal>
<template slot="content">
<component :is="activeModal"></component>
</template>
</modal>
</div>
Update
Now, You might think how will you close your modal and let the parent component know about it.
On click of button trigger closeModal for that
Create a method - closeModal and inside commonModal component and emit an event.
closeModal() {
this.$emit('close-modal')
}
Now this will emit a custom event which can be listen by the consuming component.
So in you parent component just use this custom event like following and close your modal
<main class="foo">
<commonModal v-show="isVisible" :data="data" #close- modal="isVisible = false"/>
<!-- Your further code -->
</main>
So as per your question
A - How do I reuse the modal dialog, instead of creating 3 separate dialogs
Make a separate modal component, let say - commonModal.vue.
Now in your commonModal.vue, accept single prop, let say data: {}.
Now in the html section of commonModal
<div class="modal">
<!-- Use your received data here which get received from parent -->
<your modal code />
</div>
Now import the commonModal to the consuming/parent component. Create data property in the parent component, let say - isVisible: false and a computed property for the data you want to show in modal let say modalContent.
Now use it like this
<main class="foo">
<commonModal v-show="isVisible" :data="data" />
<!-- Your further code -->
</main>
The above will help you re-use modal and you just need to send the data from parent component.
Now second question will also get solved here How do I know which modal dialog has been triggered?
Just verify isVisible property to check if modal is open or not. If isVisible = false then your modal is not visible and vice-versa

Bootstrap-Vue Modal reopens everytime except in ESC

I am currently writing a Vue component for a project.
I encountered a Problem where a Bootstrap-Vue modal will re open again instead of closing.
I am using Vue.js in Version 2.6.10 and Bootstrap 4.
<template>
<div>
<b-button v-b-modal.rating-modal #click="showModal()">
Click me
<b-modal ref="rating-modal" no-close-on-backdrop centered title="Rate" class="rating-modal" #ok="hideModal" #cancel="hideModal" #close="hideModal">
<div>
Content of the modal...
</div>
</b-modal>
</b-button>
</div>
</template>
<script>
export default {
name: "Rating",
components: {
,
},
methods: {
showModal() {
this.$refs['rating-modal'].show();
},
hideModal() {
this.$refs['rating-modal'].hide();
},
}
}
;
</script>
I expect it to close when I hit either cancel, ok or the cross in the header.
Ok, I resolved the Issue by myself. All I had to do is to move the b-modal tag out of the b-button tag like this:
<template>
<div>
<b-button v-b-modal.rating-modal #click="showModal()">
Click me
</b-button>
<b-modal ref="rating-modal" no-close-on-backdrop centered title="Rate" class="rating-modal" #ok="hideModal" #cancel="hideModal" #close="hideModal">
<div>
Content of the modal...
</div>
</b-modal>
</div>
</template>

On click outside of some element in vue without packages

I am wondering how can we hide all shown elements on click outside from those elements in vue:
<button #click="div1=true">Show div 1</button>
<div v-if="div1">DIV 1</div>
<button #click="div2=true">Show div 2</button>
<div v-if="div2">DIV 2</div>
How can I hide all divs besides div 1 if I click on div 1 or hide all divs on click on some random part of the page?
How does vuetify handle it?
Vue way is to use this.$el (DOM element of current component), you can change this.$el and use any other HTMLElement
<div class="modal" #click="handleClick">
<div class="modal-dialog">
<div class="modal-content">
...
</div>
</div>
And method should look like this
handleClick(e: Event): void {
if (e.target instanceof HTMLElement && !this.$el.contains(e.target)) {
this.$emit("hide"); //fires only on click outside
}
}
It's easy to detect click outside of the element if you know how event bubbling works in DOM.
Hiding other divs works perfectly well when you put state of every div into the components's state.
Here is an example: https://codesandbox.io/s/0480m38mww?fontsize=14&module=%2Fsrc%2FApp.vue
Learn more about Event Bubbling here - https://javascript.info/bubbling-and-capturing
I used this process so Home component would know that outside my Nav component has been clicked. It helps that I have my nav inside my Home Component.
My Home component template:
<div>
<nav-bar ref="nav"></nav-bar>
<div #click="useHideNavMethod()">
So I use ref="nav" to use methods from the Nav in my Home component. This means I can use the hideNav() method (which resides in Nav).
I have purposely put the nav outside of this div with the click method, so anywhere clicked other than Nav would initiate the useHideNavMethod() function.
In my Nav component template I have:
<nav id="nav">
which is referenced when I use ref="nav".
So inside my hideNav() I have this.styleNavLi.display = 'none'; so it would hide the nav bar.
So all I need to do to use that method in Home is to use:
useHideNavMethod() {
this.$refs.nav.hideNav();
}
Use div1 = !div1 for toggle view,
Or use div1 = true for one time only.
<template>
<div>
{{ div1 }}
<button #click="div1 = !div1">Show div 1</button>
<div v-if="div1">DIV 1</div>
{{ div2 }}
<button #click="div2 = !div2">Show div 2</button>
<div v-if="div2">DIV 2</div>
</div>
</template>
<script>
export default {
data() {
return {
div1: true, // set default value as you want
div2: false,
};
},
};
</script>

Set variable while navigating using angular

Im trying to create a navigation bar but i get stuck with this code.
What happens is when i click the menu it navigates to the right page. When i click again the variable is set. But is it possible to set the variable while navigating.
<div>
<nav class="{{active}}" >
<!-- When a link in the menu is clicked, we set the active variable -->
Home
Projects
Services
</nav>
<p ng-hide="active">Please click a menu item</p>
<p ng-show="active">You chose <b>{{active}}</b></p>
</div>
EDIT
I change the header page to this but i still can get it working.
<div>
<nav ng-controller="testController" class="{{active}}">
<!-- When a link in the menu is clicked, we set the active variable -->
<a href="#/" class="home" >Home</a>
<a href="#/second" class="second" >second</a>
<a href="#/third" class="third" >third</a>
</nav>
</div>
css
nav.home .home,
nav.second .second,
nav.third .third{
background-color:#e35885;
}
You can achieve that by listening to location changes and using the ng-class directive :
CSS :
.active, .active:focus{
color: red;
}
HTML :
<div ng-app="testApp">
<div ng-controller="testController">
<div>
<!-- When a link in the menu is clicked, we set the active variable -->
Home
second
third
</div>
</div>
</div>
Javascript :
var app = angular.module('testApp', []);
app.controller('testController', function ($scope, $location, $rootScope, $log) {
$scope.locationsDescriptions = {
'#/': 'Home',
'#/second': 'Second',
'#/third': 'Third'
}
$scope.isPageSelected = function (pageName) {
return $scope.active == pageName ? 'active' : '';
}
$scope.$on("$locationChangeSuccess", function (event, next, current) {
var location = this.location.hash.toLowerCase();
$scope.active = $scope.locationsDescriptions[location];
});
});

Categories