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

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.

Related

How to pass images has props in a vue js component (Configured with vite cause require doesnt work in vite)

Here is my parent component
<template>
<rooms-card
roomImage="../../assets/images/room3.jpg"
roomType="Duplex Room"
roomDescription="Sami double bed 1 guest room 3 windows"
roomPrice="$50/night"
/>
</template>
Here is the child component
<template>
<div class="m-5 px-6 py-4 shadow-xl border mb-14">
<div class="w-64 h-72 bg-red-700">
<img :src="roomImage" class="h-full"/>
</div>
<div class="">
<h1 class="font-semibold text-xl my-3">{{roomType}}</h1>
<p class="text-sm my-2 text-slate-500 w-44">{{roomDescription}}</p>
<p>Starting from<span class="font-semibold text-xl">{{roomPrice}}</span></p>
<button class="bg-pink-500 my-5 px-6 py-3 text-white">
Book now
</button>
</div>
</div>
</template>
<script setup>
// import { any } from "prop-types";
const props = defineProps({
roomImage:String,
roomType: String,
roomDescription:String,
roomPrice:String
})
console.log(props.roomImage)
</script>
NOTE: even if i use require(#/asset/images......) in my image tag src it wont work because for some reasons vite isn't configure to use requires
Just import the asset in <script> and use the variable as the src attribute.
From the docs
Importing a static asset will return the resolved public URL when it is served:
<script setup>
import RoomImage from "../../assets/images/room3.jpg";
</script>
<template>
<rooms-card
:roomImage="RoomImage"
roomType="Duplex Room"
roomDescription="Sami double bed 1 guest room 3 windows"
roomPrice="$50/night"
/>
</template>

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>

Vue how to customize global navbar at view level

Im super new to Vue.
i have a Vue-CLI app, which have a navbar and content.
Navbar is common to all pages, but i want to customize in each page whit some additional content.
Example:
Common-> home | about
View home -> home | about | your are in view home
View about -> home | about | your are in view about
router/index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from '../views/Home.vue';
import NavBar from '#/components/NavBar.vue';
Vue.use(VueRouter);
Vue.component('nav-bar', NavBar);
//...
components/navbar.vue
<template>
<div>
<b-nav-item to="/">home</b-nav-item>
<b-nav-item to="/about">about</b-nav-item>
{{customContent}}
</div>
</template>
<script>
export default {
name: 'NavBar',
props: {
customContent: {
type: String,
default: 'default Content',
},
},
};
</script>
App.vue
<template>
<div id="app">
<nav-bar />
<div class="container-fluid">
<router-view />
</div>
</div>
</template>
views/home.vue
<template>
<div class="row">
<div class="col-12">
<image-card :images="images"/>
</div>
</div>
</template>
<script>
//how can i customize here the navbar by adding for example 'your are in view home'???
</script>
Thanks so much!
There are a few ways in which you can solve this problem. I'll list two of them.
1. Update NavBar by $route
In this approach, the NavBar component already contains all of the possible combinations, and will display the relevant portion(s) depending on what $route contains.
Here's some pseudo code:
navbar.vue
<template>
<div class="navbar">
<div class="navbar-left>
APPNAME
</div>
<div v-if="name === 'landing'">
...
</div>
<div v-else-if="name === 'room'">
...
</div>
</div>
</template>
App.vue
<template>
<div id="app">
<NavBar :name="$route.name"/>
<main>
<router-view/>
</main>
</div>
</template>
In this example, the NavBar component is very rigid, and doesn't really lend itself to much reuse. However, it does encapsulate all the relevant code relating to the nav bar.
2. Extensible NavBar with slots
In this approach, the NavBar only provides the bare-minimum to create a nav bar. The rest of the route-specific elements are to be filled in by the views.
navbar.vue
<template>
<div class="navbar">
<div class="navbar-left">
<div class="navbar-brand">
APPNAME
</div>
<slot name="left"></slot>
</div>
<div class="navbar-right">
<slot name="right"></slot>
</div>
</div>
</template>
App.vue
<template>
<div id="app">
<router-view/>
</div>
</template>
landing.vue
<template>
<div>
<header>
<NavBar>
<template slot="right">
<span>
<div class="navbar-item">
<div class="buttons">
<button class="button" #click="...">Start Watching</button>
</div>
</div>
</span>
</template>
</NavBar>
</header>
<main>
...
</main>
</div>
</template>
This approach has a bit of repetition in terms of DOM elements, but gives you an extremely flexible NavBar that can be customized by each view.
The approach you want to use depends on what is important to you.
If strict encapsulation is what you want, then you may want to use approach 1, as all of the NavBar-related code is contained within a single file.
However, if you believe that there is a potential for reuse, or if you would like all view-related code to live in one place, then it makes sense to use slots instead and extend the NavBar as required by each view.
I use a breadcrumb to achieve a similar thing. Just an idea but Vue router allows you to add meta data to the current route which you always have access to
router.js
path: '/add',
name: 'add',
component: () => import(/* webpackChunkName: "add" */ '../../views/Add.vue'),
meta: {
breadCrumb: [
{ name: 'Add New' }
]
},
Notice the meta object attached to the route.. this will be used to describe the current view.
Breadcrumb.vue component
<template>
<div class="breadcrumb">
<ul class="d-flex m-0 p-0"
<li
v-for="(breadcrumb, idx) in breadcrumbList"
:key="idx">
{{ breadcrumb.name }}
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'Breadcrumb',
data () {
return {
breadcrumbList: []
}
},
mounted () { this.updateList() },
watch: { '$route' () { this.updateList() } },
methods: {
routeTo (pRouteTo) {
if (this.breadcrumbList[pRouteTo].link) this.$router.push(this.breadcrumbList[pRouteTo].link)
},
updateList () { this.breadcrumbList = this.$route.meta.breadCrumb },
formatPath(path) {
const newPath = path.replace(/\//g, " > ")
return newPath
}
}
}
</script>
And then you can import the breadcrumb into your navbar or where ever you would like to place it
<Breadcrumb class="breadcrumb" />
import Breadcrumb from '#/components/Breadcrumb.vue'
components: {Breadcrumb}
So basically the breadcrumb will always watch your current route and change the data based on the meta data you provide in your router.js file
You can access to router name like this:
<div v-if="this.$route.name == 'home'">
<HeaderTransparent />
</div>
<div v-else>
<HeaderWhite />
</div>

Trying to stop loading same component again and again

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.

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>