Toggle sidebar from Vue method? - javascript

In a <b-table> I would like to create an action on each items so I have a button:
<b-table :items="data" :fields="fields">
<template v-slot:cell(actions)="data">
<b-button v-on:click="doIt(data.index)">Do It</b-button>
</template>
</b-table>
Then I have a Form in a sidebar
<b-sidebar id="do-it-form" title="Do it" right>
...
</b-sidebar>
In my methods I would like to respond to the action:
methods: {
doIt(id) {
sidebar.form.id = id
sidebar.show().onSubmit(() => {
axio...
refresh(<b-table>)
})
}
}
Of course, this last part is not valid. On Bootstrap Vue manual I didn't find how to interact from Vue to Bootstrap components. Any clue?

You can emit an event on $root, which can be used to toggle the sidebar. The second parameter being the id of the sidebar you wish to open.
this.$root.$emit('bv::toggle::collapse', 'my-sidebar-id')
<b-collapse> and <b-sidebar> listens for the same event, which is why it says collapse in the event.
new Vue({
el: '#app',
methods: {
openSidebar() {
this.$root.$emit('bv::toggle::collapse', 'my-sidebar')
}
}
})
<link href="https://unpkg.com/bootstrap#4.5.2/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap-vue#2.17.1/dist/bootstrap-vue.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.17.1/dist/bootstrap-vue.js"></script>
<div id="app">
<b-sidebar id="my-sidebar" right>
My sidebar
</b-sidebar>
<b-btn #click="openSidebar">
Open sidebar
</b-btn>
</div>
Alternatively you can bind a boolean property to the v-model on the sidebar and set the boolean to true when you want to open the sidebar.
new Vue({
el: '#app',
data() {
return {
isSidebarOpen: false
}
},
methods: {
openSidebar() {
this.isSidebarOpen = true
}
}
})
<link href="https://unpkg.com/bootstrap#4.5.2/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap-vue#2.17.1/dist/bootstrap-vue.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.17.1/dist/bootstrap-vue.js"></script>
<div id="app">
<b-sidebar v-model="isSidebarOpen" right>
My sidebar
</b-sidebar>
<b-btn #click="openSidebar">
Open sidebar
</b-btn>
</div>

Also, you can use one of the sidebar built-in public methods show, hide or toggle. All you need is add a reference to your sidebar
<b-sidebar ref="mySidebar" id="do-it-form">
...
</b-sidebar>
and then in any of your methods where/when it's needed, you can simply call any of them
this.$refs.mysidebar.show();
this.$refs.mysidebar.hide();
this.$refs.mysidebar.toggle();

You can also just assign a boolean value to the visible prop on the b-sidebar component and toggle the boolean value as you like.
<b-sidebar ref="mySidebar" id="do-it-form" :visible="showSidebar">
...
</b-sidebar>
And toggling it:
data: {
showSidebar: false, //starts off invisible
},
methods: {
toggleSidebar(){
this.showSidebar = !this.showSidebar
}
}
This approach at first glance is not the best in an approach where you have other components updating the sidebar visiblity. This use case is for situations when all updates to the sidebar visibility are made from a central store by using a central boolean value.
e.g.
const state = {
showSidebar: null
}
const mutations: {
toggleSidebar(state, payload){
if (payload) { //payload incase you want to force a value and not just toggle
state.showSidebar = payload;
} else {
state.showSidebar = !state.showSidebar;
}
}
}
And in your components:
computed: {
showSidebar(){
return this.$store.state.showSidebar
}, //starts off invisible
},
methods: {
toggleSidebar(){
this.$store.commit("toggleSidebar");
}
}
Your updated sidebar component would look like this:
<b-sidebar ref="mySidebar" id="do-it-form" :visible="showSidebar" #change="updateSidebar">
...
</b-sidebar>
And the method:
methods: {
updateSidebar(value){
this.$store.commit("toggleSidebar", value);
}
}

Related

Vue.js - How to prevent re-render whole component?

I want to prevent my whole component (a modal) re-render. When user login, my web application shows a modal including some messages. Once user clicks next, the content in this modal changes. However, the modal will shows the pop-up animation again. The modal use same modal, but change the content.
This is absolute how Vue works, however, if the firstPage changes, the modal pop-up again... How could I only re-render the content part, not the whole modal?
<template>
<div>
<b-modal v-model="modalShow">
<p v-if="firstPage">Hello</p>
<p v-else>{{content}}</p>
</b-modal>
</div>
</template>
<script>
export default {
data() {
return {
modalShow: false,
}
},
computed() {
content() {
return this.$store.state.content
},
firstPage() {
return this.$store.state.firstPage
}
}
}
</script>
The re-render issue is unlikely to be related to the children, could you eloborate the example more? As you can see in the example, content of the children changes without opening the modal second time, it should be related to unwanted change on the "modalShow", how and where are you changing this data.
window.onload = () => {
new Vue({
el: '#app',
data() {
return {
modalShow: false,
firstPage: true,
}
},
methods: {
toggle() {
this.modalShow = !this.modalShow;
setTimeout(() => this.firstPage = !this.firstPage, 300);
}
}
})
}
<link href="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.css" rel="stylesheet"/>
<link href="https://unpkg.com/bootstrap#4.5.3/dist/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.js"></script>
<script src="https://unpkg.com/babel-polyfill/dist/polyfill.min.js"></script>
<div id="app">
<div>
<b-button #click="toggle">Launch demo modal</b-button>
<b-modal v-model="modalShow">
<p class="my-4" v-if="firstPage">1st Component</p>
<p class="my-4" v-else>2nd Component</p>
</b-modal>
</div>
</div>
After reading your question I imagine you have an login button.. than you can do following - add a click event to your button like this:
<b-button #click="login = !login> LOGIN </b-button> //use this if you want to switch your modal each time on click
<b-button #click="login = false"> LOGIN </b-button> //use this if you only want to change it one time after click
Here you are switching your boolean if it's firstly true after clicking your button it will be false.
Than you have to define your property in your data return like this:
data() {
return {
login: true,
}
}
and afterwards you can check it in your template, like you have done it before:
<p v-if="login == true">Login is true</p>
<p v-if="login == false">Login is false</p>
<!-- OR -->
<p v-if="login == true">Login is true</p>
<p v-else>Login is false</p>
You can also add a method to your click event and check it there if you don't want to have code like login = !login in your template!
Please let me know if this helps you!

How to 2-way bind props with local data of a component in Vue 3?

Can anyone tell me how I can bind the prop of a component to a data property of its own? For example, consider I have a component called ModalComponent
<template> // ModalComponent.vue
<div v-if="showModal">
...
<button #click="showModal = !showModal">close the modal internally</button>
</div>
</template>
<script>
export default {
props: {
show: {
type: Boolean,
default: false
},
},
data() {
return {
showModal: false,
}
}
}
</script>
<style>
</style>
Now consider I am using this modal as a reusable component from inside a parent component using an external button to open up the pop up for the modal.
<template>
<button #click="showChild = !showChild">click to open modal</button>
<ModalComponent :show="showChild" />
</template>
<script>
export default {
components: {ModalComponent},
data() {
return {
showChild: false,
}
}
}
</script>
<style>
</style>
How do I make it so that every time the parent clicks on the button, the modal pops up by binding the local showModal to the prop show ? And when the modal is closed internally by the local close button, if I click on the parent button again it will re-pop up? (I am using Vue 3, just in case)
Any help would be appreciated. Thanks.
The perfect solution for this case is using v-model instead of passing a prop :
in child component define modelValue as prop and emit its value to the parent using $emit function:
<template> // ModalComponent.vue
<div v-if="modelValue">
...
<button #click="$emit('update:modelValue',!modelValue)">close the modal internally</button>
</div>
</template>
<script>
export default {
props: {
modelValue: {
type: Boolean,
default: false
},
},
emits: ['update:modelValue'],
}
</script>
in parent component just use v-model to bind the value :
<template>
<button #click="showChild = !showChild">click to open modal</button>
<ModalComponent v-model="showChild" />
</template>
<script>
export default {
components: {ModalComponent},
data() {
return {
showChild: false,
}
}
}
</script>

Toggle button in dynamic/recursive structure

I'm looping an array of element and I'd want to recursively display that element with given template
and then inside that template use button with toggle to show/hide deeper level template of Childs of given element (Child is also an Element)
<div v-for="(element in elements)">
<MyTemplate :element="element"></MyTemplate>
</div>
Here's my template:
<template>
<div>element.Name</div>
<button #click="toggleSomehow">
// I'd want to display it under that button when he's "showing"
<MyTemplate :element="element.Child"></MyTemplate>
</button>
</template>
But I'm not really sure how to do that SHOW/HIDE button without binding it with some property or array, but I'd rather want to avoid it because everything has to be kind of dynamic
You should add toggleable data to your MyComponent component like visible
See example below
Vue.component('my-template', {
template: '#my-template',
props: {
element: {
type: Object,
required: true
}
},
data() {
return {
visible: false
}
},
methods: {
toggleVisible() {
this.visible = !this.visible
}
}
});
new Vue({
el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script type="text/x-template" id="my-template">
<div>
<div>{{element.name}}</div>
<button #click="toggleVisible" v-if="element.child">toggle</button>
<my-template v-if="visible" :element="element.child" />
</div>
</script>
<div id="app">
<my-template :element="{name: 'test', child: {name: 'child test'}}" />
</div>

Call method from another vue.js component through this.$refs

I have a sibling element which i want to trigger.
i've try this solution
<b-img #click="callFileLoader"/>
<b-file type="file" ref="fileUploader"></b-file>
...
methods:{
callFileLoader () {
this.$refs.fileUploader.click()
}
}
Got: Uncaught TypeError: this.$refs.fileUploader.click is not a function
b-file documentation
After some debugging i found a way to access that input using this statement :
this.$refs.fileUploader.$el.firstChild
which is <input> element that could be clickable.
new Vue({
el: "#app",
data() {
return {
file: null,
file2: null
}
},
methods: {
callFileLoader() {
this.$refs.fileUploader.$el.firstChild.click();
}
}
});
<script src="https://unpkg.com/vue#2.5.17/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.0.0-rc.11/dist/bootstrap-vue.min.js"></script>
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#2.0.0-rc.11/dist/bootstrap-vue.css" />
<div id='app'>
<div>
<!-- Styled -->
<b-file v-model="file" ref="fileUploader" :state="Boolean(file)" placeholder="Choose a file..."></b-file>
<div class="mt-3">Selected file: {{file && file.name}}</div>
<button class="btn btn-primary" #click="callFileLoader">load</button>
</div>
</div>
If it is sibling component, it is not recognized in sibling component via v-ref approach.
Either try to access it via parent component or Vue root, if there is no parent component where it is nested:
this.$root.$refs.fileUploader.click()
Or use this.$root.$emit() in sibling component b-img, to trigger event, and place event listener in events of b-file component to catch emited event and trigger click
so in b-img would be:
methods:{
callFileLoader () {
this.$root.$emit('file-uploader-click');
}
}
and in b-file would be:
events:{
'file-uploader-click' : function() {
this.click();
}
}
Instead of placing events method, you try to place v-on:event-name="action" within an component:
VueTools chrome extension is very useful to check correct reference name generated by VueJs

Vue js : Call a child method from the parent component

I tried to make communicate to components with vuejs and vuex :
First component is a menu
Second component, inside the first, is an hamburgerButton.
In this very current case, when I click the hamburgerButton, this one is animated and the menu too. With this code, after the button click, the menu is animated with vueTransition and the hamburgerButton too. Note that i use vuex to manage a menuIsOpen state.
My problem is when i click an item of the menu, I would like fired the hamburgerButton animation.
hamburgerButtonanimation method, call animButtonHandler(), is encapsulated in a #click event. Actually i understand why it doesn't work right know, but I don't perceive how to handle this method to the click of a Parent element (item of the menu). So my question is, how can I access a method to a child component from a parent component ? Or is there an another workaround methodology to achieve this ?
parent component - menu.vue :
<template>
<div class="menu">
<!-- import hamburgerButton component -->
<hamburger-button></hamburger-button>
<transition v-on:enter="enterMenuHandler" v-on:leave="leaveMenuHandler">
<div class="menu_wrapper" v-if="this.$store.state.menuIsOpen">
<ul class="menu_items">
<li class="menu_item" #click="$store.commit('toggleMenu')">
<router-link class="menu_link" to="/">home</router-link>
<router-link class="menu_link" to="/contact">contact</router-link>
</li>
</ul>
</div>
</transition>
</div>
</template>
<script>
import hamburgerButton from "hamburgerButton.vue";
export default {
components: {
'hamburger-button': hamburgerButton,
},
methods: {
enterMenuHandler(el, done){
TweenLite.fromTo(el, 0.5, {
opacity: '0',
},{
opacity: '1',
onComplete: done
});
},
leaveMenuHandler(el, done){
TweenLite.to(el, 0.5, {
opacity: '0',
onComplete: done
});
},
}
}
</script>
child component : hamburgerButton.vue :
<template>
<div class="hamburgerButton" #click="animButtonHandler()">
<div class="hamburgerButton_inner" ref="hamburgerButtonInner">
<i class="hamburgerButton_icon></i>
</div>
</div>
</template>
<script>
export default {
methods: {
animButtonHandler (){
// toggle the state of menu if button clicked
this.$store.commit('toggleMenu');
const isOpen = this.$store.state.menuIsOpen === true;
// anim the button
TweenLite.to(this.$refs.hamburgerButtonInner, 0.5, {
rotation: isOpen ? "43deg" : '0',
});
},
}
}
</script>
store.js (imported in the main.js) :
let store = new Vuex.Store({
state : {
menuIsOpen : false,
},
mutations: {
toggleMenu(state) {
state.menuIsOpen = !state.menuIsOpen
}
}
});
I have added basic Example of event bus. you can now compare it with and do changes like wise.
if find any difficulties please comment.
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/vue#2.1.10/dist/vue.min.js"></script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div id="app">
<h2>event bus</h2>
<button #click="callChildAnimateMethod"> Button On Parent Call Child </button>
<childcmp><childcmp />
</div>
<script>
var EventBus = new Vue();
Vue.component('childcmp', {
template: `<div>child demo - {{ message }}</div>`,
data: function() {
return {
message: 'hello'
}
},
mounted: function() {
// listen for event
EventBus.$on('animate', this.animButtonHandler);
},
destroyed: function(){
// remove listener
EventBus.$off('animate', this.animButtonHandler);
},
methods: {
animButtonHandler: function() {
console.log('this is child method');
this.message = 'I am changed.'
}
}
});
new Vue({
el: '#app',
data: function() {
return {
}
},
methods: {
callChildAnimateMethod: function() {
EventBus.$emit('animate');
}
}
});
</script>
</body>
</html>
Update
you need to define EventBus
eventbus.js
import Vue from 'vue';
const EventBus = new Vue();
export default EventBus;
parent component - menu.vue
import EventBus from './eventbus.js'
... your code
child component : hamburgerButton.vue :
import EventBus from './eventbus.js'
... your code
now EventBus will be available to your code.
Since you wanted to know how to imtegrate event bus with your code, here it is:
Create an event bus which is just an empty vue instance
Add it in you main.js file or outsource it in a spererate file;
main.js
export const EventBus = new Vue();
menu.vue
<template>
<div class="menu">
<!-- import hamburgerButton component -->
<hamburger-button></hamburger-button>
<transition v-on:enter="enterMenuHandler" v-on:leave="leaveMenuHandler">
<div class="menu_wrapper" v-if="this.$store.state.menuIsOpen">
<ul class="menu_items">
<li class="menu_item" #click="toggleMenu">
<router-link class="menu_link" to="/">home</router-link>
<router-link class="menu_link" to="/contact">contact</router-link>
</li>
</ul>w
</div>
</transition>
</div>
</template>
<script>
import hamburgerButton from "hamburgerButton.vue";
import {EventBus} from './path/to/main.js' //or a path to file where you exported your EventBus
export default {
components: {
'hamburger-button': hamburgerButton,
},
methods: {
toggleMenu(){
this.$store.commit('toggleMenu');
EventBus.$emit('animate-hamburger-btn');
},
enterMenuHandler(el, done){
TweenLite.fromTo(el, 0.5, {
opacity: '0',
},{
opacity: '1',
onComplete: done
});
},
leaveMenuHandler(el, done){
TweenLite.to(el, 0.5, {
opacity: '0',
onComplete: done
});
},
}
}
</script>
set up a event listener on the event bus in the created hook and perform the animation on every animate-hamburger-btn event
hamburgerButton.vue
<template>
<div class="hamburgerButton" #click="animButtonHandler()">
<div class="hamburgerButton_inner" ref="hamburgerButtonInner">
<i class="hamburgerButton_icon></i>
</div>
</div>
</template>
<script>
import {EventBus} from './path/to/main.js' //or a path to file where you exported your EventBus
export default {
created(){
EventBus.$on('animate-hamburger-btn', () => {
this.animateBtn();
});
}.
methods: {
animButtonHandler (){
// toggle the state of menu if button clicked
this.$store.commit('toggleMenu');
this.animateBtn();
},
animateBtn(){
const isOpen = this.$store.state.menuIsOpen === true;
// anim the button
TweenLite.to(this.$refs.hamburgerButtonInner, 0.5, {
rotation: isOpen ? "43deg" : '0',
});
}
}
}
</script>

Categories