How to implement Bootstrap Modal for page preload - javascript

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!

Related

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

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?

Logic behind loading Views with vuex dependencies vueJS

I have an API call that loads on my App.vue that populates my vuex store's state. The App.vue by default loads a Home.vue that displays images based on the store's state. The images load, but throw a lot of console errors before the state is populated. I'm not sure what the best logic is, in general, to delay loading VIEWS before the data they depend on is finished loading. Existing answers make sense for components but I can't bind data to the router-view, and don't think passing it in params makes sense. I'm a n00b.
//App.vue
<template>
<div id="app">
<div id="nav">
<div id="nav-logo"></div>
<div id="nav-links">
<router-link to="/">Home</router-link>
<router-link to="/pokemon">Pokemon</router-link>
</div>
</div>
<router-view></router-view>
</div>
</template>
<script>
export default {
created(){
this.init();
},
methods:{
init(){
this.$store.dispatch('fetchPokemon', 'gen1');
}
}
}
</script>
//Home.vue
<template>
<div>
<div id="pokemonAFront">
<img :src="pokeImg[randomA].sprites.front_default" alt="">
</div>
<div id="pokemonABack">
<img :src="pokeImg[randomA].sprites.back_default" alt="">
</div>
</div>
</template>
<script>
export default {
data(){
return{
randomA: Math.floor(Math.random()* 20),
},
computed:{
pokeImg(){
return this.$store.state.pokemon
}
}
</script>
//store/index.js
state:{
pokemon:[]
},
mutations: {
SET_POKEMON(state, pokemon){
state.pokemon = pokemon;
}
},
actions: {
fetchPokemon(context, currentGen){
context.commit('SET_POKEMON', pokeData.fetchPokemon(currentGen)) //api call
}
}
What logic works best for delaying loading views/components when their vuex dependencies haven't loaded yet?
The simplest is to render the dom only after the pokeImg gets populated from API:
<div v-if="pokeImg.length > 0">
<div id="pokemonAFront">
<img :src="pokeImg[randomA].sprites.front_default" alt="">
</div>
<div id="pokemonABack">
<img :src="pokeImg[randomA].sprites.back_default" alt="">
</div>
</div>

Bind element inside a for loop Vue not working properly

In the following Vue Component I want to loop through dwarfs array. And as long as I am in the current component, everything is fine (TEST) and also all the following properties are correct.
Currenct_Component.vue :
<template>
<div>
<h2>Stamm: {{ tribeName }}</h2>
<div class="card-container">
<div class="card" style="width: 18rem;" v-for="dwarf in dwarfs" :key="dwarf.name">
<!-- TEST -->
<p>{{dwarf}}</p>
<!-- CHILD COMPONENT -->
<app-modal
:showModal="showModal"
:targetDwarf="dwarf"
#close="showModal = false"
#weaponAdded="notifyApp"
/>
<!-- <img class="card-img-top" src="" alt="Card image cap">-->
<div class="card-body">
<h3 class="card-title" ref="dwarfName">{{ dwarf.name }}</h3>
<hr>
<ul class="dwarf-details">
<li><strong>Alter:</strong> {{ dwarf.age }}</li>
<li><strong>Waffen:</strong>
<ul v-for="weapon in dwarf.weapons">
<li><span>Name: {{ weapon.name }} | Magischer Wert: {{ weapon.magicValue }}</span></li>
</ul>
</li>
<li><strong>Powerfactor:</strong> {{ dwarf.weapons.map(weapon => weapon.magicValue).reduce((accumulator, currentValue) => accumulator + currentValue) }}</li>
</ul>
<button class="card-button" #click="showModal = true"><span class="plus-sign">+</span> Waffe</button>
</div>
</div>
</div>
<button id="backBtn" #click="onClick">Zurück</button>
</div>
</template>
<script>
import Modal from './NewWeaponModal.vue';
export default {
data() {
return {
showModal: false,
}
},
components: { appModal : Modal },
props: ['tribeName', 'dwarfs'],
methods: {
onClick() {
this.$emit('backBtn')
},
notifyApp() {
this.showModal = false;
this.$emit('weaponAdded');
}
},
}
</script>
But when I bind the element dwarf to the Child Component <app-modal/> it changes to the next dwarf in the array dwarfs (TEST) - (So as the result when i add a new weapon in the modal-form it gets added to the second dwarf...):
Child_Component.vue :
<template>
<div>
<div class="myModal" v-show="showModal">
<div class="modal-content">
<span #click="$emit('close')" class="close">×</span>
<h3>Neue Waffe</h3>
<!-- TEST -->
<p>{{ targetDwarf }}</p>
<form>
<input
type="text"
placeholder="Name..."
v-model="weaponName"
required
/>
<input
type="number"
placeholder="Magischer Wert..."
v-model="magicValue"
required
/>
<button #click.prevent="onClick">bestätigen</button>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
weaponName: '',
magicValue: '',
}
},
props: ['showModal', 'targetDwarf'],
methods: {
onClick() {
if(this.weaponName !== '' &&
Number.isInteger(+this.magicValue)) {
let newData = {...this.dwarf};
newData['weapons'] = [
...this.dwarf['weapons'],
{
"name": this.weaponName,
"magicValue": Number.parseInt(this.magicValue)
},
];
this.$http.post("https://localhost:5019/api", newData)
.then(data => data.text())
.then(text => console.log(text))
.catch(err => console.log(err));
this.$emit('weaponAdded');
} else {
alert('You should fill all fields validly')
}
},
}
}
</script>
It looks like you have the <app-modal/> component inside of the v-for="dwarf in dwarfs" loop, but then the control for showing all of the modal components created by that loop is just in one variable: showModal. So when showModal is true, the modal will show each of the dwarfs, and I'm guessing the second dwarf's modal is just covering up the first one's.
To fix this, you could move the <app-modal /> outside of that v-for loop, so there's only one instance on the page, then as part of the logic that shows the modal, populate the props of the modal with the correct dwarf's info.
Something like this:
<div class="card-container">
<div class="card" v-for="dwarf in dwarfs" :key="dwarf.name">
<p>{{dwarf}}</p>
<div class="card-body">
<button
class="card-button"
#click="() => setModalDwarf(dwarf)"
>
Waffe
</button>
</div>
</div>
<!-- Move outside of v-for loop -->
<app-modal
:showModal="!!modalDwarfId"
:targetDwarf="modalDwarfId"
#close="modalDwarfId = null"
#weaponAdded="onDwarfWeaponAdd"
/>
</div>
export default {
//....
data: () => ({
modalDwarfId: null,
)},
methods: {
setModalDwarf(dwarf) {
this.modalDwarfId = drawf.id;
},
onDwarfWeaponAdd() {
//...
}
},
}
You could then grab the correct dwarf data within the modal, from the ID passed as a prop, or pass in more granular data to the modal so it's more "dumb", which is the better practice so that the component isn't dependent on a specific data structure. Hope that helps
Courtesy of #Joe Dalton's answer, a bit alternated for my case:
<div class="card" style="width: 18rem;" v-for="dwarf in dwarfs" :key="dwarf.name">
...
<button class="card-button" #click="setModalDwarf(dwarf)"><span class="plus-sign">+</span> Waffe</button>
<div>
<app-modal
:showModal="showModal"
:targetDwarf="currentDwarf"
#close="showModal = false"
#weaponAdded="notifyApp"
/>
<script>
import Modal from './NewWeaponModal.vue';
export default {
data() {
return {
showModal: false,
currentDwarf: null,
}
},
components: { appModal : Modal },
props: ['tribeName', 'dwarfs'],
methods: {
setModalDwarf(dwarf) {
this.currentDwarf = dwarf;
this.showModal = true;
},
...
}
</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.

Laravel+vue - Props is undefined

so I am new to vue, but getting up to speed. However, I cannot make the props thing to work - it always remains undefined in my child component.
The idea of the below is to create a app-wide notification modal window to display notifications.
This is my app.js
require('./bootstrap');
import ModalNotification from './components/Modal-Notification.vue';
const app = new Vue({
el: '#app',
data : {
formInputs: {},
formErrors: {},
showModal: false
},
components: {ModalNotification}
});
This is my Modal-Notification.vue
<template>
<transition name="modal">
<div class="modal-mask" #click="$emit('close')">
<div class="modal-wrapper">
<div class="modal-container" #click.stop>
<!-- <div class="modal-header">
<slot name="header">
NOTIFICATION
</slot>
</div> -->
<div class="modal-body">
<slot name="body">
Bla bla
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
<button class="modal-default-button btn btn-success" #click="$emit('close')">
OK
</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
</template>
<script>
export default {
name: 'ModalNotification',
data: function() {
return {
};
},
props: ['showModal'],
mounted: function() {
console.log(this);
document.addEventListener("keydown", (e) => {
console.log(this.showModal);
if (this.showModal && e.keyCode == 27) {
this.$emit('close');
}
});
},
methods: {
}
}
</script>
And the relevant part of app.blade.php
<div class="container-fluid" id="app">
<button #click="showModal = true" class="btn btn-default">MODAL</button>
<modal-notification v-if="showModal" #close="showModal = false" :showModal="false">
<p slot="body" id="notification-message">hehe</p>
</modal-notification>
<div id="wrapper">
#yield('sidebar')
#yield('content')
</div>
</div>
I've tried everything out there, except switching to Browserify, babel and such stuff, but I don't think it should be needed - webpack should work just fine.
Please help, if you can.
You have mistake in the following snippet of code:
<modal-notification v-if="showModal" #close="showModal = false" :showModal="false">
<p slot="body" id="notification-message">hehe</p>
</modal-notification>
:showModal="false" is basically shortcut of v-bind:showModal="false", which tries to search vue instance variable in the value of attached HTML property(documentation). as you are passing false which is not a vue data variable, it is just passing null in showModal props.
If you want to pass only false, change the code to following:
<modal-notification v-if="showModal" #close="showModal = false" showModal="false">
<p slot="body" id="notification-message">hehe</p>
</modal-notification>
Edited
I think it is magic of camelCase-vs-kebab-case:
HTML attributes are case-insensitive, so when using non-string templates, camelCased prop names need to use their kebab-case (hyphen-delimited) equivalents:
You need to pass : show-modal="false"
<modal-notification v-if="showModal" #close="showModal = false" show-modal="false">
<p slot="body" id="notification-message">hehe</p>
</modal-notification>

Categories

Resources