Vue.js - How to add a event handler to a Bootstrap element - javascript

I have an app where I'm using Bootstrap 5. In this app, I have an Offcanvas component. I need to do some work when this component closes. In an attempt to do this, I wanted to hook into the hide.bs.offcanvas event. My Vue item looks like this:
<template>
<div>
...
<div class="offcanvas offcanvas-bottom overlay-offcanvas" tabindex="-1" id="myOffCanvas" ref="myOffCanvas" aria-labelledby="overlayOffCanvasLabel" data-bs-backdrop="false">
<div class="offcanvas-header bg-secondary text-white">
<h6 class="offcanvas-title" id="overlayOffCanvasLabel">Hello</h6>
<button data-bs-dismiss="offcanvas">x</button>
</div>
<div class="offcanvas-body">
Hello, world
</div>
</div>
</div>
</template>
<script>
import { Offcanvas } from 'bootstrap';
export default {
data() {
myOffCanvas: null
},
methods: {
onOffcanvasHidden() {
alert('Good bye!');
}
},
mounted() {
this.myOffCanvas = new Offcanvas(this.$refs.myOffCanvas);
}
}
</script>
How do I call onOffcanvasHidden when the offcanvas elements is closed? In other words, how do I wire-up an event handler to the hide.bs.offcanvas event from Vue.js?

Related

How to implement Bootstrap Modal for page preload

I want to show a spinner modal in a vue 3 app when the page preloader gets all the data from the server and when it finishes I want to hide the spinner.
My preloader:
async preload(to) {
Spinner.showSpinner();
let data = await Manager.getFilteredList({
filter: [],
order: {},
pageinfo: { pagenum: 1, pagelength: globalVars.EVENT_PER_PAGE },
}).finally(() => Spinner.hideSpinner());
to.params.list= data.list;
to.params.totalCount = data.totalcount;
return to;
}
Spinner.js
import { Modal as BSModal } from 'bootstrap';
class Spinner {
showSpinner() {
let modal = BSModal.getInstance(document.getElementById('mdlSpinner'));
if (!modal) {
modal = new BSModal(document.getElementById('mdlSpinner'));
}
modal.show();
}
hideSpinner() {
const modal = BSModal.getInstance(document.getElementById('mdlSpinner'));
modal.hide();
}
}
export default new Spinner();
App.vue
<template>
<div class="home">
<div class="modal" tabindex="-1" id="mdlSpinner">
<div
class="spinner-border text-info"
role="status"
style="width: 3rem; height: 3rem"
>
<span class="visually-hidden">Loading...</span>
</div>
</div>
<div id="login-teleport" />
<Navbar />
<div class="cam-container">
<div class="sidebar">
<Sidemenu />
</div>
<div class="content">
<router-view />
<div id="popup-teleport" />
</div>
</div>
</div>
</template>
This way on the page I get an error:
TypeError: Illegal invocation
at Object.findOne (bootstrap.esm.js:1017)
at Modal._showElement (bootstrap.esm.js:2928)
at eval (bootstrap.esm.js:2851)
at execute (bootstrap.esm.js:275)
at eval (bootstrap.esm.js:2557)
at execute (bootstrap.esm.js:275)
at executeAfterTransition (bootstrap.esm.js:281)
at Backdrop._emulateAnimation (bootstrap.esm.js:2627)
at Backdrop.show (bootstrap.esm.js:2556)
at Modal._showBackdrop (bootstrap.esm.js:3032)
And also I guess if it would even work my spinner would still show because it is permanently coded into App.vue (?).
What would be the ideal implementation of a Spinner which I could use in preloaders and also in vue templates - for example when I click on a button to fetch data.
Thank you for your answers in advance!

vue button related event not fired

In:
https://codesandbox.io/s/upbeat-hodgkin-qjt61?file=/src/components/EditCategory.vue
the modal is shown as expected upon long click over a category:
but clicking OK does not fire the close event:
<template>
<div>
<p v-longclick="() => longClicked()" #click="longClicked()">
{{ taskItemLocal["name"] }}
</p>
<div v-if="this.showModal" #close="closeModal()">
<transition name="modal">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header"> Edit Category </slot>
</div>
<div class="modal-body">
<slot name="name"> Edit Name </slot>
</div>
<div class="modal-body">
<slot name="delete"> Delete Category </slot>
</div>
<div class="modal-footer">
<slot name="footer">
<!-- default footer -->
<!-- EVENT NOT FIRING -->
<button class="modal-default-button" #click="$emit('close')">
OK
</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
</div>
</div>
</template>
closeModal() is not called; changing showModal "directly" also fails.
You have dispatch event to parent but in parent component you have not done any thing with "close" event. Here, in GenericItem.vue I have made event listener with #close="closeBox($event)" . Here, it will trigger method of closeBox
GenericItem.vue
Changes on Template
<edit-category
v-if="editCategoryStatus"
:taskItem="taskItemLocal"
#close="closeBox($event)"
/>
Add one closeBox method
closeBox() {
this.editCategoryStatus = !this.editCategoryStatus;
},
Add editCategoryStatus on data
data() {
return {
editCategoryStatus: true,
taskItemLocal: {
type: Object,
default: {},
},
};
If you want to listen to an event within the component that emitted that event, you use the instance $on method:
mounted() {
this.$on("close", () => {
this.closeModal();
});
}
The template event handler #close="closeModal()" is used to listen to events from parent. It has no effect within the child component.
The working codesandbox: https://codesandbox.io/s/loving-kirch-vrhwn?file=/src/components/EditCategory.vue .
You could just make your button like this. You made this more complicated than it should be
<button class="modal-default-button" #click="showModal = false">
Also, there is this example from the official docs
here.

How to change the data value of the parent file from the sub file in vue.js?

I have a brand new app in vue.js. I want to change the data value of the app.vue using the checkbox checked used in the child component how I will do this the code I' using in both the files is:-
component file:-
<template>
<div>
<h3>Edit the user</h3>
<p>Local: {{ $route.query.local }}</p>
<p>Number is:- {{ $route.query.q }}</p>
<label><input type="checkbox" #click="change()">customizer</label>
</div>
</template>
<script>
export default{
props:["changeParentStatus"],
methods:{
change() {
this.active = !this.active
console.log(this.active)
this.changeParentStatus(this.active);
}
}
}
</script>
app.vue file
<template>
<div :class="[{ 'active': active }]">
<div class="row">
<div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3">
<h1>Routing</h1>
<hr>
<app-header></app-header>
<router-view :changeParentStatus="changeStatus"></router-view>
</div>
</div>
</div>
</template>
<script>
import AppHeader from "./components/Header.vue";
export default {
data(){
return{
active: true
}
},
methods:{
changeStatus(active){
this.active=active
}
},
components:{
appHeader: AppHeader
}
}
</script>
<style>
.active{
background: black;
}
</style>
How will I change the value of the app.vue data active to true or false by changing the checkbox used in the component file. Can anybody please help me to do this.
Folder structure is:-
src---> app.vue
components---->users----->userEdit.vue (component file)
you can pass a function as an attribute to the child component
<app-header :changeParentStatus="changeStatus"></app-header>
parent
callback
<script>
import AppHeader from "./components/Header.vue";
export default {
data(){
return{
active: true
}
},
methods:{
changeStatus(active){
this.active=active
}
},
components:{
appHeader: AppHeader
}
}
</script>
now call "changeParentStatus" from child change function
<script>
export default{
props:["changeParentStatus"],
methods:{
change() {
this.active = !this.active
console.log(this.active)
this.changeParentStatus(this.active);
}
}
}
</script>
Looks like you want to pass the checkbox value from child to parent. For this define event on parent component and emit that event from child component.
In Parent handle event like this:
<router-view #changeParentStatus="changeStatus"></router-view>
The ChangeStatus is event/method so we defined using v-on: (or #shorthand)
Next, emit event from child component:
<label>
<input type="checkbox"
#click="$emit('changeParentStatus', $event.target.checked)">
customizer</label>
More info: Vue.JS Guide

Passing functions as props to child components

Assuming I have a component named Modal which I want to reuse across my application and in the meanwhile I also want to dynamically bind functions to its Yes button, how can I pass a function to the #click event of the Yes button in my Modal as a prop. For instance:
//data tags are used for fast markup here only
<tamplate>
<div>
<div :id="`${id}`" data-modal>
<div data-modal-title>
{{title}}
</div>
<div data-modal-body>
{{body}}
</div>
<div data-modal-footer>
<button #click="//event to be passed//" data-modal-button-ok>Yes</button>
<button data-modal-button-cancel>Yes</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'modal',
props: [
'id',
'title',
'body',
// event?
]
}
</script>
and when using this modal, how should the event be passed?
<Modal id="x" title="y" body="x" //event="????"//></Modal>
You should $emit an event (name, say, yes) at the modal:
<button #click="$emit('yes')" data-modal-button-ok>Yes</button>
Or, use a method:
<button #click="handleYesClick" data-modal-button-ok>Yes</button>
methods: {
handleYesClick() {
this.$emit('yes');
}
}
And listen to it in the parent using:
<modal ... v-on:yes="someCodeToExecute"></modal>
or (shorthand):
<modal ... #yes="someCodeToExecute"></modal>
Demo:
Vue.component('modal', {
template: "#modal",
name: 'modal',
props: ['id', 'title', 'body']
})
new Vue({
el: '#app',
data: {},
methods: {
methodAtParentYes() {
alert('methodAtParentYes!');
},
methodAtParentCancel() {
alert('methodAtParentCancel!');
}
}
})
<script src="https://unpkg.com/vue"></script>
<template id="modal">
<div>
<div :id="`${id}`" data-modal>
<div data-modal-title>
{{title}}
</div>
<div data-modal-body>
{{body}}
</div>
<div data-modal-footer>
<button #click="$emit('yes')" data-modal-button-ok>Yes</button>
<button #click="$emit('cancel')" data-modal-button-cancel>Cancel</button>
</div>
</div>
</div>
</template>
<div id="app">
<modal id="1" title="My Title" body="Body" #yes="methodAtParentYes" #cancel="methodAtParentCancel"></modal>
</div>
There are two ways to do this.
The first way is you could pass the method down as a prop, just like you would pass anything else and then in the Modal component just call that prop right in the click handler.
Parent.vue
<template>
<Modal id="x" title="y" body="x" :handleYes="handleYes"></Modal>
</template>
<script>
methods: {
handleYes () {
// do something
}
}
</script>
Modal.vue
<button #click="handleYes()">Yes</button>
The other way is to use $emit. So in Modal.vue you would define a method to emit an event and then listen for that event in the parent and do call the method there.
Modal.vue
<template>
<button #click="emitEvent">Yes</button>
</template>
<script>
methods: {
emitEvent () {
this.$emit('userClickedYes')
}
}
</script>
Parent.vue
<template>
<Modal id="x" title="y" body="x" #userClickedYes="handleYes"></Modal>
</template>
<script>
methods: {
handleYes () {
// do something
}
}
</script>

How to use multiple references to the same vueJs component?

I have a php page person.php that includes 2 vueJs components: person-details.vue and phones.vue. Each of these components include the same third component notify-delete. This notify-delete component includes a bootstrap modal dialog to confirm a deletion action of the parent component (person or phone).
I use props to set a message in the modal confirmation dialog and $refs to show it.
Problem:
When this props is set the msg props from the component person and the dialog is shown, the message is correctly set. However when I set from the phones component, the dialog shows the message set by person. as if person is constantly overriding the value of the msg props.
here is a sample of the code:
person.php:
<person-details details="<?= json_encode($person) ?>"></person-details>
<phones details="<?= json_encode($phones) ?>"></phones>
Person-details.vue:
<template>
<notify-delete ref="modalDeletePerson" :entity="'person'" :msg="deleteMsg" #confirmed="deleteMe"></notify-delete>
<button type="button" #click="confirmDelete">Delete this person</button>
</template>
<script>
export default {
data () {
return {
deleteMsg: ''
}
},
methods: {
confirmDelete() {
this.deleteMsg = 'Are you sure you want to delete this person?'
this.$refs.modalDeletePerson.show()
},
deleteMe() {
// delete person
}
}
}
</script>
</template>
Phones.vue:
<template>
<notify-delete ref="modalDeletePhone" :entity="'phone'" :msg="deleteMsg" #confirmed="deleteMe($event)"></notify-delete>
<button type="button" #click="confirmDelete">Delete this phone</button>
</template>
<script>
export default {
data () {
return {
deleteMsg: ''
}
},
methods: {
confirmDelete() {
this.deleteMsg = 'Are you sure you want to delete this phone?'
this.$refs.modalDeletePhone.show()
},
deleteMe() {
// delete phone
}
}
}
</script>
Notify-delete.vue:
<template>
<div id="modalDelete" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
{{msg}}
</div>
<div class="modal-footer">
<button type="submit" data-dismiss="modal" #click="confirm">Delete</button>
<button type="button" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['entity', 'msg'],
methods: {
show() {
$('#modalDelete').modal('show')
},
confirm() {
this.$emit('confirmed')
}
}
}
</script>
Any idea how I can have two distinguish instances of the same component?
The problem is here
show() {
$('#modalDelete').modal('show')
}
You are rendering two modals with the same id, then using jQuery to show them. Specifically, $('#modalDelete') will contain two elements. I expect the modal method just picks the first one and shows it.
Try
<div ref="modal" class="modal fade" tabindex="-1" role="dialog">
and
show() {
$(this.$refs.modal).modal('show')
}
This should give each instance of the Notify-delete.vue component it's own reference.

Categories

Resources