Trying to stop loading same component again and again - vue.js

About the code
Using Laravel 5.8 and Vue.js components.
Issue Details
There are 3 tabs. When I click component 1, it loads. Then I click Component 2, It loads. So far everything is okay.
When I go back to Component 1, it loads the component again. Here the problem is that there is captcha which gets the response code again from server because the component is loading everytime on clicking the tab.
Can i stop loading the component loading after first time? I meant, I just want to reuse the html which was loaded.
Main Component
<template>
<div class="container-fluid">
<b-tabs pills card vertical>
<b-tab title="Main" active>
<profile></profile>
</b-tab>
<b-tab title="Comp 1" #click="setComponent('comp1')">
<component :is = "comp1"></component>
</b-tab>
<b-tab title="Comp 2" #click="setComponent('comp2')">
<component :is = "comp2"></component>
</b-tab>
</b-tabs>
</div>
</template>
<script>
export default {
methods: {
setComponent(name) {
if(name == "comp1") {
this.comp1 = name;
}
else {
this.comp2 = name;
}
}
},
data() {
return {
comp1: '', comp2: ''
}
}
}
</script>
Component - Comp 1
<template>
<div class="container">
Comp 1 rendered
<vue-recaptcha
:sitekey="My Site Key">
</vue-recaptcha>
</div>
</template>
Component - Comp 2
<template>
<div class="container">
Comp 2 Rendered
<vue-recaptcha
:sitekey="My Site Key">
</vue-recaptcha>
</div>
</template>

You can adds keep-alive, getting the components are not destroyed re-create
<div class="container-fluid">
<keep-alive>
<b-tabs pills card vertical>
<b-tab title="Main" active>
<profile></profile>
</b-tab>
<b-tab title="Comp 1" #click="setComponent('comp1')">
<component :is = "comp1"></component>
</b-tab>
<b-tab title="Comp 2" #click="setComponent('comp2')">
<component :is = "comp2"></component>
</b-tab>
</b-tabs>
</keep-alive>
</div>
After that you will have two new hooks in both child components, activated and deactivated.

Related

Passing props dynamically to a component inside a v-for loop

I have a v-for loop that iterates through an array of meetings (meetings between sellers and potential buyers of used cars) and prints a card for each meeting, a basic display of who the meeting is with, what car it is about and the scheduled date. Now, I implemented a button that when clicked, opens a dialog with a Google Maps component that shows the marker for the agreed location of the meeting.
My problem is that no matter what card I click on, the dialog will always display the location of the LAST card, regardless of which has been clicked. I would think that since Im calling the component INSIDE the v-for loop it would pass props dynamically for each card, on each iteration, but that does not seem to be the case.
Here is the HTML:
<div
v-for="meeting in meetings"
:key="meeting.did"
class="col-12 col-md-6 col-lg-3 q-pa-md q-mx-xl"
>
<q-card class="my-card homeCard q-pa-md">
<q-dialog class="mapDialog flex column" v-model="mapDialog">
<MeetMapComponent
:key="componentKey"
:mapDiv="mapDiv"
:mapData="meeting.address"
:buyerName="meeting.name"
/>
</q-dialog>
<q-card-section
class="tipCardImage flex row justify-end"
:style="`background-image: url(${meeting.car.carImg})`"
>
<router-link
:to="`/user/meet/edit/${meeting.did}`"
style="text-decoration: none"
>
<q-icon
#click="fetchMeeting(meeting.did)"
name="fa-solid fa-pencil editNameIcon q-mb-sm q-ml-sm"
></q-icon>
</router-link>
<q-icon
name="fa-solid fa-trash editNameIcon q-mb-sm q-ml-sm"
#click="triggerDelete(meeting.did)"
></q-icon>
</q-card-section>
<q-card-section>
<div class="cardTitle">
<span>Encuentro Con</span> {{ truncateString(meeting.name, 30) }}
</div>
<div class="tipCardText">
<span>Agendado para el </span>
<p>{{ truncateString(meeting.date, 120) }}</p>
</div>
<div class="flex row justify-end">
<q-btn
#click="mapDialog = true"
class="text-white cardButton"
:class="{ cardButtonMobile: $q.screen.lt.md }"
>Ver UbicaciĆ³n</q-btn
>
</div>
</q-card-section>
</q-card>
</div>
And here is the code for the MeetMapComponent:
<template>
<div class="meetMapContainer">
<div ref="mapDiv" style="width: 100%; height: 500px" />
<h5 class="text-center text-white">{{ props.mapData.address }}</h5>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useAuthStore } from "stores/auth";
import { storeToRefs } from "pinia";
import { Loader } from "#googlemaps/js-api-loader";
const props = defineProps({
mapData: Object,
buyerName: String,
});
const GOOGLE_MAPS_API_KEY = "...";
const loader = new Loader({ apiKey: GOOGLE_MAPS_API_KEY });
const mapDiv = ref(null);
async function mapRender() {
await loader.load();
const map = new google.maps.Map(mapDiv.value, {
mapTypeId: "roadmap",
center: props.mapData.coordinates,
zoom: 13,
});
console.log(map);
new google.maps.Marker({
position: props.mapData.coordinates,
map,
title: `Encuentro con ${props.buyerName}`,
});
}
mapRender();
</script>
I will help you as much as I understand. You use the mapDialog variable to open the dialogue. But even if this variable is used in v-for, its reference does not change. For this reason, when you want to open a modal, all modals may be opened and the last one may appear because it is the last one opened. Please check the dom.
I think this method can solve the problem.
in script
const meetings = [
{
did: 'some value',
address: 'some address',
name: 'some name',
// add modal flag
showMapModal: false
}
]
template
<div
v-for="meeting in meetings"
:key="meeting.did"
class="col-12 col-md-6 col-lg-3 q-pa-md q-mx-xl"
>
<q-card class="my-card homeCard q-pa-md">
<q-dialog class="mapDialog flex column" v-model="meeting.showMapModal">
<MeetMapComponent
:key="componentKey"
:mapDiv="mapDiv"
:mapData="meeting.address"
:buyerName="meeting.name"
/>
</q-dialog>
</q-card>
</div>

Show different component conditionally if parent tag contains active class - Vue Bootstrap

I want to customize tab component's title part on Vue Bootstrap. If the tab is active, I want to show different type of tab item. Of course we can solve it by using css classes by setting .active class. But In my situation, I want to display a different component if that tab is active.
I believe this can be done using 'v-if' but I dont know how to get its tag's classes contains active class. If tab is active, I want to render a spinner component, else I only want to render name of that tab.
Do you have any idea?
<b-tabs fill>
<b-tab title="First">
<template #title v-if="active">
<b-spinner type="grow" small variant="success"></b-spinner> <b>Active Title</b>
</template>
<template #title v-else>
<b>Title</b>
</template>
</b-tab>
<b-tab title="Second">
<template #title v-if="active">
<b-spinner type="grow" small variant="success"></b-spinner> <b>Second Title</b>
</template>
<template #title v-else>
<b>Title</b>
</template>
</b-tab>
You could bind a property to <b-tabs> v-model, which will contain the index of the active tab. Then use this to check inside your v-if whether the tab is active or not.
new Vue({
el: '#app',
data() {
return {
currentTab: 0
}
}
});
<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.js"></script>
<script src="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.js"></script>
<div id="app" class="p-4">
<b-tabs v-model="currentTab" fill>
<b-tab title="First">
<template #title v-if="currentTab === 0">
<b-spinner type="grow" small variant="success"></b-spinner> <b>Active Title</b>
</template> Tab 1 content
</b-tab>
<b-tab title="Second">
<template #title v-if="currentTab === 1">
<b-spinner type="grow" small variant="success"></b-spinner> <b>Second Title</b>
</template> Tab 2 content
</b-tab>
</b-tabs>
</div>

VueJs Lazy initialization of tab's component just once

I'm trying to make the bootstrap-vue tabs initialization lazy and though it works if i set the lazy attribute on true, it render the component every time i'm visiting a specific tab:
BtabsWrapper.vue:
<b-tabs
:lazy="true"
>
<b-tab
v-for="(tab, index) in tabs"
:key="'li-tab-' + index"
:title="tab.title"
:href="'#' + tab.id"
>
<slot
:name="tab.id"
/>
</b-tab>
</b-tabs>
I need more of a lazy initialization of each tab(just once) rather than re rendering the tab's component every time the user visits it. Any ideas?
If you wrap the content of each tab in a v-if and change that condition once when they're loaded you should get your desired outcome.
And in your case if you're using a v-for, you can utilize the index in your v-for in the includes part as visitedTabs.includes(index)
window.onload = () => {
new Vue({
el: '#app',
data() {
return {
visitedTabs: []
}
},
methods: {
onInput(value) {
if(!this.visitedTabs.includes(value)){
this.visitedTabs.push(value)
}
}
}
})
}
body {
padding: 1em;
}
<link href="https://unpkg.com/bootstrap#4.3.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://unpkg.com/bootstrap-vue#2.0.4/dist/bootstrap-vue.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.0.4/dist/bootstrap-vue.js"></script>
<div id="app">
<b-card clas no-body>
<b-tabs card #input="onInput">
<b-tab title="First tab">
<div v-if="visitedTabs.includes(0)">
Hello World
</div>
</b-tab>
<b-tab title="Second tab">
<div v-if="visitedTabs.includes(1)">
<b-input />
</div>
</b-tab>
<b-tab title="Third tab">
<div v-if="visitedTabs.includes(2)">
<b-checkbox />
</div>
</b-tab>
</b-tabs>
</b-card>
</div>

VueJS Modal component inside component

I have a component like this:
<test></test>
I declare this as follows:
Vue.component('test', {
data: {
showModal: true
},
methods: {
displayComponentModalDialog: function() {
this.showModal = true;
}
},
template: `<button #click='displayComponentModalDialog'>test</button>`
});
The <test></test> component is then placed somewhere inside the <div id="#app"> wrapper.
var app = new Vue({
router,
el: '#app',
// etc.
})
Now, I want to display another component inside the test component. So in this case I want a dialog to appear after I click the button in test component. I am not able to achieve this.
What I did is adding a new component:
Vue.component('dialog', {
template: '#dialog-template'
});
And then the following code, although I do not know for sure where to put it.
<!-- template for the modal component -->
<script type="text/x-template" id="dialog-template">
<transition name="dialog">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
default header
</slot>
</div>
<div class="modal-body">
<slot name="body">
default body
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
<button class="btn btn-primary" #click="$emit('close')">
OK
</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
</script>
<!-- use the modal component, pass in the prop -->
<dialog v-if="showModal" #close="showModal = false">
<h3 slot="header">header</h3>
<p slot="body">
test
</p>
</dialog>
I tried putting this code inside the <test></test> but doesn't work. If I put it inside the template attribute in the component structure, it complains about only one root element.
So it is clear I miss some basic conception how this actually works in VueJS. Someone can help me clarify? Thanks.
As far as I can see your component indeed doesn't have a root tag. Templates have to have a root tag.
This is NOT a valid Vue template:
<div>
<h1>Stuff</h1>
</div>
<h2>Other stuff</h2>
This IS a valid Vue template:
<div>
<div>
<h1>Stuff</h1>
</div>
<h2>Other stuff</h2>
</div>
Note that in the second version we have a single root element for the template, a <div>, whereas in the first one we do not.
You have both a <script></script> and a <dialog></dialog> in your component template.
if you want to add another component in your test component . you can use slot on it.
You can refer to this documentation: https://v2.vuejs.org/v2/guide/components-slots.html
Example:
//component 1
<template>
<div id="modal">
// do something for your modal here.
<slot></slot> // reserve area for your another html or component.
</div>
</template>
// component 2
<template>
<your-modal-component>
<another-component>
</your-modal-component>
</template>

bootstrap-vue v-tabs inside v-card renders 'undefined'

Using bootstrap-vue I have a working example using b-tabs and some other of it's components.
However, on one page (.vue) when I try to wrap the block within a tag the page always renders that card and its contents as 'undefined'
I use the sample from here.
Without the v-card I see:
and with it I see:
The code literally follows the sample referenced in the page above and in my main.ts I have the following
import {Card} from 'bootstrap-vue/es/components'
import { Button } from 'bootstrap-vue/es/components';
import { Collapse } from 'bootstrap-vue/es/components';
import { Alert } from 'bootstrap-vue/es/components';
import { Tabs } from 'bootstrap-vue/es/components';
Vue.use(Tabs);
Vue.use(Alert);
Vue.use(Collapse);
Vue.use(Button);
Vue.use(Card);
new Vue({
router,
store,
components: {
bCard: Card,
bButton: Button,
bCollapse: Collapse,
bAlert: Alert,
bTabs: Tabs,
pagination
},
render: h => h(App)
}).$mount("#app");
Can anyone point me in the right direction even to see how to isolate any underlying problem please?
EXTRA CODE ADDED BELOW
Vue (lang="ts") component which does not display correctly:
<template>
<div class="container-fluid">
<div class="row ml-1">
<h4>Complaint Reference: {{ complaint.ComplaintReference }}</h4>
<div class="col-12"><p>Enter the details of the complaint and click Save</p></div>
<div class="col-12">
<b-alert variant="success"
dismissible
:show="showDismissableAlert"
#dismissed="showDismissableAlert=false">
Your changes have been saved
</b-alert>
</div>
</div>
<div class="row">
<!-- the next line is a separate vue component that uses the same approach and which renders correctly -->
<tabs-view></tabs-view>
<div class="col-md-12">
<b-card no-body> <!-- THIS IS WHAT BREAKS! -->
<b-tabs card>
<b-tab title="Details" active>
<p> in first tab</p>
</b-tab>
<b-tab title="Contact">
<p> in second tab</p>
</b-tab>
<b-tab title="Description">
<p> in third tab</p>
</b-tab>
<b-tab title="Outcome">
<p> in final tab</p>
</b-tab>
</b-tabs>
</b-card> <!-- THIS IS WHAT BREAKS! -->
</div>
</div>
<div class="clearfix"></div>
</div>
</template>
<script lang="ts">
import {Component, Prop, Vue} from "vue-property-decorator";
import {Complaint} from "#/components/complaints/Complaint";
import TabsView from '../shared/Tabs.vue'
#Component({
components: {
tabsView: TabsView
}
})
export default class EditComplaintComponent extends Vue {
complaint: Complaint = new Complaint("");
baseUri: string = "api/Complaints/GetByReference/?id=";
showDismissableAlert: Boolean = false;
}
</script>
<style scoped>
</style>
and then another non-TS vue component consumed on the broken one which works correctly with b-tabs inside b-card
<template>
<div>
<b-card title="Card Title"
img-src="https://lorempixel.com/600/300/food/5/"
img-alt="Image"
img-top
tag="article"
style="max-width: 20rem;"
class="mb-2">
<!--<p class="card-text">-->
<!--Some quick example text to build on the card title and make up the bulk of the card's content.-->
<!--</p>-->
<b-button href="#" variant="primary">Go somewhere</b-button>
<b-card no-body>
<b-tabs card>
<b-tab title="first" active>
<br>I'm the first fading tab
</b-tab>
<b-tab title="second" >
<br>I'm the second tab content
</b-tab>
<b-tab title="disabled" disabled>
<br>Disabled tab!
</b-tab>
</b-tabs >
</b-card>
</b-card>
</div>
</template>
<script>
export default {
components: { }
}
</script>
<style scoped>
</style>
I ran into a similar issue with bootstrap-vue 2.21.2. Ended up being that I had a component that wasn't marked up with #Component. After adding it in all my components went from 'undefined' text to display the proper content. See https://github.com/bootstrap-vue/bootstrap-vue/issues/5985#issuecomment-765558938.
Upgrade to the latest version of BootstrapVue to get around this bug.
I had a parent component missing the #Component decorator (in my case it was App.vue) that was causing the undefined behaviour. Make sure that all components have this decorator applied.