pass template from a .vue file to another .vue file - vuejs2

I am trying to follow this tutorial. I am getting a Modal using below code.
<modal
v-model="showModal"
:animation-duration="animationDuration"
:close-on-click-away="closeOnClickAway"
>
<p slot="header">Confirmation needed</p>
<p slot="content">Do you want to continue?</p>
<template slot="actions">
<div class="ui black deny button"
#click="showModal=false">
No
</div>
<div class="ui positive right labeled icon button"
#click="confirm">
Yes
<i class="checkmark icon"></i>
</div>
</template>
</modal>
Here I would like to place below portion of the code in another .vue file named ModalBody.vue.
<p slot="header">Confirmation needed</p>
<p slot="content">Do you want to continue?</p>
<template slot="actions">
<div class="ui black deny button"
#click="showModal=false">
No
</div>
<div class="ui positive right labeled icon button"
#click="confirm">
Yes
<i class="checkmark icon"></i>
</div>
</template>
Then I am trying to access that file like below
<modal
v-model="showModal"
:animation-duration="animationDuration"
:close-on-click-away="closeOnClickAway"
>
<modal-body></modal-body>
</modal>
But it is not working. Even I am not getting any error in console.
UPDATE
Parent.vue
<template>
<modal
v-model="showModal"
:animation-duration="animationDuration"
:close-on-click-away="closeOnClickAway"
>
<modal-body></modal-body>
</modal>
</div>
</template>
<script>
import ModalBody from './ModalBody';
export default {
components: { ModalBody },
}
</script>

use import
<template>
<!-- html code -->
<modal-body v-show="showModal"></modal-body>
</template>
<script>
import modalBody from './ModalBody'
export default {
components: {
childComponent
},
data: () => ({ showModal: false })
}
</script>
UPDATE:
I committed mistakes, but your noticed her.
your updated code have mistakes too
tag </div>
<modal> - is a component
so
Parent.vue
<template>
<modal
v-model="showModal"
:animation-duration="animationDuration"
:close-on-click-away="closeOnClickAway"
>
<modal-body></modal-body>
</modal>
</template>
<script>
import ModalBody from './ModalBody';
import modal from './modal';
export default {
components: {
ModalBody,
modal
}
/* component code */
}
</script>
important!
If you want to insert another component or element into the component, then inside this component there must be a <slot></slot>. see
also see my example app: https://codesandbox.io/s/3xyx386q65
open 'components/setProps/setProps.vue'

Related

PrimeVue Toast displaying html tags

How can I implement displaying a link in a primevue toast message? I cannot use v-html directive and triple brackets do not work. Does anybody has another idea how to solve it?
A hacky way is to extends Toast component:
Here a codesandbox : https://codesandbox.io/s/extend-primevue-toast-o5o1c?file=/src/CustomToastMessage.vue
1. On your component
Import your custom toast component where you need to call this.$toast:
<template>
<div>
<CustomToast />
<CustomToast position="top-left" group="tl" />
<CustomToast position="bottom-left" group="bl" />
<CustomToast position="bottom-right" group="br" />
<div class="card">
<Button #click="test" label="test" />
</div>
</div>
</template>
<script>
import CustomToast from "./CustomToast.vue";
export default {
components: {
CustomToast,
},
data() {
return {
messages: [],
};
},
methods: {
test() {
this.$toast.add({
severity: "success",
summary: "Test",
detail: "<b>Test Bold</b>",
});
},
},
};
</script>
2. CustomToast.vue (extend primevue toast)
<template>
<Teleport to="body">
<div ref="container" :class="containerClass" v-bind="$attrs">
<transition-group name="p-toast-message" tag="div" #enter="onEnter">
<CustomToastMessage
v-for="msg of messages"
:key="msg.id"
:message="msg"
#close="remove($event)"
/>
</transition-group>
</div>
</Teleport>
</template>
<script>
import Toast from "primevue/toast/Toast.vue";
import CustomToastMessage from "./CustomToastMessage.vue";
export default {
extends: Toast,
components: {
CustomToastMessage,
},
};
</script>
3. CustomToastMessage (extend primevue toastmessage)
Add v-html where you want to have html
<template>
<div
:class="containerClass"
role="alert"
aria-live="assertive"
aria-atomic="true"
>
<div class="p-toast-message-content">
<span :class="iconClass"></span>
<div class="p-toast-message-text">
<span class="p-toast-summary">{{ message.summary }}</span>
<div class="p-toast-detail" v-html="message.detail"></div>
</div>
<button
class="p-toast-icon-close p-link"
#click="onCloseClick"
v-if="message.closable !== false"
type="button"
v-ripple
>
<span class="p-toast-icon-close-icon pi pi-times"></span>
</button>
</div>
</div>
</template>
<script>
import ToastMessage from "primevue/toast/ToastMessage.vue";
export default {
extends: ToastMessage,
};
</script>
There is an easiest solution.
Just implement your own template.
Example:
<Toast :position="toastPosition">
<template #message="slotProps">
<span :class="iconClass"></span>
<div class="p-toast-message-text">
<span class="p-toast-summary">{{slotProps.message.summary}}</span>
<div class="p-toast-detail" v-html="slotProps.message.detail" />
</div>
</template>
</Toast>

Vue - display/create component from a function

One thing that I have been struggling to figure out how to do better is modals. Currently, I am registering the modal component on each Vue that needs it. However, this feels rather sloppy, as I am having to register the component several times. Even using mix-ins just does not feel like an elegant solution. What would be optimal to be able to do is to mimic JavaScript's alert() method on the Vue instance. For example, be able to call this.ShowModal(Header, Body)
However, from my understanding, there is no way to accomplish this
Take for example my Modal example. You could have a modal template like this:
<script type="text/x-template" id="modal-template">
<transition name="modal">
<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>
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
default footer
<button class="modal-default-button" #click="$emit('close')">
OK
</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
</script>
Then you would have to reference the component over and over again like this
<template>
<button #click="displayModal">Display the Modal Alert</button>
<modal v-if="showModal" #close="showModal = false">
<h3 slot="header"> This is a good header </h3>
<p>
Look at me I am the body! You have seen me {{displayCount}} times!
</p>
</modal>
</template>
<script>
components: {modal},
data: {
showModal: false,
displayCount: 0
},
methods: {
displayModal(){
this.displayCount++
this.showModal = true;
}
}
</script>
If you wanted to reuse the component for several messages from within the parent you would then have to add several more variables to store things such as the header and body. You could put some of the logic into a mixin but you would still have to have the clutter of adding the modal component and possibly the mixin.
This brings us to the question. Is there a way to create a function in the Vue instance that would allow for us to dynamically create a Modal component and fill in the slots with arguments passed to the function? e.g. this.ShowModal("This is a good header", "Look at me I am the body!")
Use Vue.extend() create a "modal" constructor and create a instance,you can mount it to DOM dynamically by $mount or other ways
In Modal example:
modal.vue:
<template>
<div>
{{message}} //your modal content
</div>
</template>
<script>
export default {
name: 'modal',
data(){
return {
message: '',
}
},
methods:{
/************/
close () {
/****this.$destroy()****/
}
}
}
</script>
modal.js:
import myModal from 'modal.vue'
import Vue from 'vue'
const modalConstructor = Vue.extend(myModal)
const modal = (options,DOM)=>{
const modalInstance = new modalConstructor({data:options})
modalInstance.vm = modalInstance.$mount() //get vm
const dom = DOM || document.body // default DOM is body
dom.appendChild(modalInstance.vm.$el) // mount to DOM
return modalInstance.vm
}
export default modal
now you can create a Modal component by a function like this:
import showModal from 'modal.js'
showModal({message:"..."})

Dialog not apper using v-show

Im trying to popup a login dialog but the problem i cannot make appear even I set it 'true'. below is my two components that appear on main layout.
login components:
<template>
<div v-show="true">
<q-dialog>
<q-card>
<q-card-section>
<q-form class="q-gutter-md" style="width: 400px">
<h4 class="text-h4 text-primary q-my-auto text-weight-medium">Login</h4>
<q-input
filled
:error="state.error"
label="Your email *"
lazy-rules
:rules="[ required, email ]"
/>
<q-input
filled
v-model="state.doc.password"
:error-message="state.error ? 'Incorrect email or password' : 'Field is Required'"
:error="state.error"
:type="state.showPassword ? 'text' : 'password'"
label="Password *"
lazy-rules
:rules="[ required ]">
</q-input>
</div>
</q-form>
</q-card-section>
</q-card>
</q-dialog>
</div>
</template>
<script>
//import part here...
export default defineComponent({
setup () {
//other function here...
})
</script>
other components that call login component as dialog:
<template>
<LoginComponents />
</template>
<script>
import { defineComponent } from 'vue'
import LoginComponents from 'components/LoginComponents'
export default defineComponent({
components: {
LoginComponents
}
})
</script>
q-dialog needs to know it should be shown.
There are examples of how to use that component here.
https://quasar.dev/vue-components/dialog
<q-dialog v-model="isShownWhenThisIsTrue">

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>

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.