Modal component in VueJS - vue.js

I am building a web app. I have few components that are modals (that show data about customers, lessons, ...).
I search a way to show one of the components easily.
And if possible doing lazy loading.
What's the best way to perform this?

Check out conditional rendering, specifically v-if. This would only load the modal if the button is clicked for example.
https://v2.vuejs.org/v2/guide/conditional.html#v-if
Single page component:
<template>
<div>
<div
v-if="showModal"
class="modal">
Stuff
</div>
<button #click="toggleModal">
Toggle Modal
</button>
</div>
</template>
<script>
export default {
data () {
return {
showModal: false
}
},
methods: {
toggleModal() {
this.showModal = !this.showModal
}
},
}
</script>

Related

Prevent Bootstrap-Vue Modal From Opening

I am using the Bootstrap-Vue modal and want to stop it from opening at times. I'm not sure with how to block the default behavior.
<b-img
ref='cal-modal-button'
id='cal-modal-button'
class="cal-icon"
v-bind:src="imagePath + calimage"
v-b-modal.date-time-modal
>
</b-img>
And then the stripped down modal is set up as such:
<b-modal id="date-time-modal" name="header-modal" ref="date-time-modal" hide-footer title="Set Date and Time">
...
</b-modal>
Is there a way to prevent it from popping up without using JQuery?
From the documentation you can cancel modal by using show event :
<template>
// ...
<b-modal #show="onShow" ... />
</template>
<script>
export default {
// ...
data:() => ({
modalDisabled:true
}),
methods: {
onShow(bvModalEvt) {
if(this.modalDisabled) {
bvModalEvt.preventDefault()
}
}
}
}
</script>
show event reference :
https://bootstrap-vue.js.org/docs/components/modal/#comp-ref-b-modal-events
Always emits just before modal is shown. Cancelable
BvModalEvent object. Call bvModalEvt.preventDefault() to cancel show

How to include a local JS file into Vue template

I want to import a JS file to be run along with a template in browser. I tried this, but it didn't work because I need everything loaded before my script can run.
Let me show you the problematic vue file:
<template>
<div id="canvaspage">
<canvas id="canvas"></canvas>
<div id="buttonlist">
<h5>Select your action:</h5>
<div class="col">
<button id="btn1">JS file custom action 1</button>
<button id="btn2">JS file custom action 2</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'CanvasPage'
}
</script>
...
See that canvas and buttons on template? I want to interact with it using pure JS.
Here is an example of what the JS file is trying to do:
let canvas = document.getElementById('canvas')
let button1 = document.getElementById('btn1')
let button2 = document.getElementById('btn2')
canvas.addEventListener('click', () => {
console.log('Canvas clicked')
})
button1.addEventListener('click', () => {
console.log('Button 1 clicked')
})
button2.addEventListener('click', () => {
console.log('Button 2 clicked')
})
If I try the solution linked above, what happens is that 'canvas', 'button1' and 'button2' are all null, because JS cannot find them. How can I make it work on Vue?
I don't see a reason- in this example- why you want to do anything in external js file, why not just interact with dom the vue way - I mean, proper way? Vue can destroy or replace your element with any v-if or rerender action. You can always link to your elements with this.$refs if you want to interact with DOM directly which is lots better than querySelector thingy. But anyway, here's a dummy example:
// external js file - ./extfile.js
export function canvasClick(...args) {
console.log('canvas clicked with: ', args);
}
export function button1Click(...args) {
console.log('button1 clicked with: ', args);
}
export function button2Click(...args) {
console.log('button2 clicked with: ', args);
}
// vue component
<template>
<div id="canvaspage">
<canvas id="canvas" #click="canvasAction"></canvas>
<div id="buttonlist">
<h5>Select your action:</h5>
<div class="col">
<button id="btn1" #click.prevent="button1Action">JS file custom action 1</button>
<button id="btn2" #click.prevent="button2Action">JS file custom action 2</button>
</div>
</div>
</div>
</template>
<script>
import { canvasClick, button1Click, button2Click } from './extfile';
export default {
name: 'CanvasPage',
methods: {
canvasAction(event) { canvasClick(event, this) },
button1Action(event) { button1Click(event, this) },
button2Action(event) { button2Click(event, this) },
}
}
</script>
Objects managed by Vue are create/destroyed according to Vue' lifecycle. This means that any external code you use to query vue-managed elements should be somewhat coupled to Vue's lifecycle.
This means that, ideally, you should use Vue itself to add the behaviour you want. You should, for instance, add this new function you want into a Vue component. This guarantees a simpler design.
Alternative: If the Vue components are from third-parties, perhaps from another team which you can't count on, you could hook those event listeners to the document and check the target's id attribute instead of hooking the event listeners directly to the canvas element (which may be destroyed by Vue and the hooks lost).
document.body.addEventListener('click', (event) => {
switch (event.target.id) {
case 'canvas':
console.log('Canvas clicked');
break;
case 'btn1':
console.log('Button 1 clicked');
break;
case 'btn2':
console.log('Button 2 clicked');
break;
}
}, true);
This code makes it very obvious that if you have more than one element in the DOM with those IDs, all of them will trigger the code.
Demo:
const CanvasComponent = Vue.component('canvas-component', {
template: `#canvas-component`,
});
const BlankComponent = Vue.component('blank-component', {
template: `<div><h3>Now click back to canvas and see that the listeners still work.</h3></div>`,
});
var router = new VueRouter({
routes: [{
path: '/',
component: {template: '<div>Click one link above</div>'}
},{
path: '/blank',
component: BlankComponent,
name: 'blank'
},
{
path: '/canvas',
component: CanvasComponent,
name: 'canvas'
}
]
});
var app = new Vue({
el: '#app',
router: router,
template: `
<div>
<router-link :to="{name: 'canvas'}">canvas</router-link> |
<router-link :to="{name: 'blank'}">blank</router-link>
<router-view></router-view>
</div>
`
});
document.body.addEventListener('click', (event) => {
switch (event.target.id) {
case 'canvas':
console.log('Canvas clicked');
break;
case 'btn1':
console.log('Button 1 clicked');
break;
case 'btn2':
console.log('Button 2 clicked');
break;
}
}, true);
<script src="//unpkg.com/vue#2.6.9/dist/vue.min.js"></script>
<script src="//unpkg.com/vue-router#3.1.3/dist/vue-router.min.js"></script>
<div id="app">
<canvas-component></canvas-component>
</div>
<template id="canvas-component">
<div id="canvaspage">
<canvas id="canvas"></canvas>
<div id="buttonlist">
<h5>Select your action:</h5>
<div class="col">
<button id="btn1">JS file custom action 1</button>
<button id="btn2">JS file custom action 2</button>
</div>
</div>
</div>
</template>

Is there a way, to "emit" a built in event, by hard coding it in Vue instance?

I would like to link two components with each other in my Vue project.
I use two-way binding for that, so I have a parent, and two child components.
The concept:
We see a carousel in the left side of the screen, and we see an accordion in the right side. I built the carousel and the accordions with v-for from a database file.
When I click in some of the accordion it drops down, and I need a reaction from a carousel component, to slide exactly there, where I clicked in the accordion.
Like:
carousel: banana, apple, house
accordion: banana, apple house
So when im clicking in the apple accordion button, I need the slider to go to the where are the apple is displayed, and reverse.
As I said, I already bind the two components to each other, so when I'm clicking one of the accordion buttons like #click="onShowStart(index)", I get that index in the another child too, and it's changing dynamically vica-versa by sliding or clicking. So the indexes are already linked and its dynamic.
My problem is I don't know how to trigger an event, like #sliding-start from vue instance in the watch field. So I watch the "actualPosition" prop in my component, and when its changed (from 3 to 1 for example), I would like to start a sliding event to the new value of the actualPosition.
So i need something like:
this.$emit('sliding-start', actualPosition);
I've been sitting at this problem for days, but I think my whole thinking is wrong. But before i believe this, im asking you first.
Here is my code for the Parent component:
<div class="row">
<carousel :actualPosition="actualPosition" class="col bg-dark" #sendTheCarouselPosition="updateAccordion($event)"></carousel>
<accordion :actualPosition="actualPosition" class="col bg-dark" #sendTheAccordionlPosition="updateCarousel($event)"></accordion>
</div>
<script>
export default {
data() {
return {
actualPosition: null,
}
},
methods:{
updateAccordion: function (updatedAccordion){
this.actualPosition = updatedAccordion;
},
updateCarousel: function(updatedSlider){
this.actualPosition = updatedSlider
}
},
}
</script>
My Accordion component:
<template>
<div role="tablist">
<b-card no-body class="mb-1" v-for="(item, index) in dataForProject">
<b-card-header header-tag="header" class="p-1" role="tab">
<b-button block href="#" v-b-toggle="'accordion-' + index" variant="info" #click="onShowStart(index)" >{{ item.title }}</b-button>
</b-card-header>
<b-collapse :id="'accordion-' + index" visible accordion="my-accordion" role="tabpanel">
<b-card-body>
<div>
<h1>data from Carousel sibling: {{ actualPosition }}</h1>
</div>
<b-card-text>{{ item.content }}</b-card-text>
</b-card-body>
</b-collapse>
</b-card>
</div>
</template>
<script>
import myDataBase from '../data2'
export default {
props:['actualPosition'],
watch:{
actualPosition: function () {
},
},
data() {
return {
dataForProject: myDataBase,
}
},
methods:{
onShowStart: function (accordionIndex) {
this.$emit('sendTheAccordionlPosition', accordionIndex);
},
},
}
</script>
And my Carousel component:
<template>
<div>
<p class="mt-4 text-white">
data from Accordion sibling: {{ actualPosition }}
</p>
<b-carousel
id="carousel-1"
:interval="0"
controls
indicators
background="#ababab"
img-width="1024"
img-height="480"
style="text-shadow: 1px 1px 2px #333;"
ref="slider"
#sliding-start="onSlideStart"
#sliding-end="onSlideEnd"
>
<b-carousel-slide v-for="(item, index) in dataForProject" :id="index" >
<img
slot="img"
class="d-block img-fluid w-100"
width="1024"
height="480"
:src="item.image_url"
alt="image slot"
>
</b-carousel-slide>
</b-carousel>
</div>
</template>
<script>
import myDataBase from '../data2'
export default {
props:['actualPosition'],
watch: {
actualPosition: function () {
},
},
data() {
return {
//slide: 0,
dataForProject: myDataBase,
}
},
methods: {
onSlideStart(slide) {
this.$emit('sendTheCarouselPosition', slide);
},
onSlideEnd(slide) {
},
}
}
</script>
I can get this done by two ways.
1 - Global EventBus
I will create an eventBus and register events on it from any file and listen it anywhere -
import { EventBus } from '#/eventBus'
// simply import it to component which need listen the event
//Register Event where you have your methods - like In your COMP_B_TWO
EventBus.$on('changeValue', () => { this.doSomething() })
// Emit event from another component
EventBus.$emit('changeValue')// Like directly from your COMP_A_TWO
To know how to create a eventBus follow this - Global Event Bus Vue
2 - Use state management - Vuex Follow this link - Vuex
Basically, it will have centralized store for all the components in an application. Whenever you wish to update state you will update to store. And all the other component using that state will react accordingly
Okay, so my whole concept was wrong, and i was wrong about this.
I should have used the v-model for this whole thing. And nothing more.
I added the v-model to the target tags, and my problem is solved.

Vue Launch Modal with Message

I am trying to launch a modal window, much like a normal alert()
I am using bootstrap-vue BModal
How to generate Modal class from code and launch it
or, add modal in the root app.vue and call it from child classes.
I found an example to but wasn't able to replicate that - https://codesandbox.io/embed/4l3w20zomw
I think you need to use show(), hide(), and toggle() component methods and here's Link, but the difference here you will call show() method to mounted() hook it will call showModal method in mounted cycle so when application is hosted you will see modal like alert, example
<template>
<div>
<b-modal ref="myModalRef" hide-footer title="Using Component Methods">
<div class="d-block text-center">
<h1>Any Content here</h1>
</div>
</b-modal>
</div>
</template>
<script>
export default {
methods: {
showModal() {
this.$refs.myModalRef.show()
},
hideModal() {
this.$refs.myModalRef.hide()
}
},
mounted() {
this.showModal();
}
}
</script>

How to add/remove class on body tag when open/close modal in vuejs

I have a modal in one of my pages and I want to add a class “active” on body when I open the modal, so I can make the body overflow hidden (no scroll).
Is there a way to toogle a class on the body tag when I click from one component? I can't figure it out...
I use routes
<template>
<div id="app">
<Header />
<router-view/>
<Footer />
</div>
</template>
Thx in advance
The correct way of doing this in Vue is to communicate between components, in this case it might not be a simple parent/child communication, so you might want to create an Event Bus.
By using this approach the modal's code is has minimum effects on the rest of your application, it only dispatches events that you can subscribe to from any other component.
Note: In this case you won't add the class on your body tag (because you can't mount Vue on body), but you may just add it to your root div to have a similar result.
const eventBus = new Vue();
Vue.component('modal', {
props: ['isOpen'],
template: `
<div class="modal" v-if="isOpen">This is a modal</div>
`,
});
Vue.component('wrapper', {
template: `
<div>
<modal :isOpen="isModalOpen"></modal>
<button #click="toggleModal">toggle modal</button>
</div>
`,
data() {
return {
isModalOpen: false,
}
},
methods: {
toggleModal() {
this.isModalOpen = !this.isModalOpen;
eventBus.$emit('toggleModal', this.isModalOpen);
}
}
});
new Vue({
el: "#app",
data: {
active: false,
},
created() {
eventBus.$on('toggleModal', (isModalOpen) => {
this.active = isModalOpen;
});
},
})
.active {
background: grey;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app" :class="{active}">
<wrapper></wrapper>
</div>
This should help
document.body.className += 'active'