I have a vue component with 2 slots (both are filled) and i need to get the position relatively to the page (using getBoundingClientRect method for example), but i can't get access to watchable slot even with refs. Is there any way to do that without this.$el.querySelector method?
<template>
<div class="watchable-container">
<slot name="watchable" ref="refWatchable"></slot>
<div class="bubble-container">
<div class="bubble">
<slot name="content">
</slot>
</div>
<div class="bubble-tail"></div>
</div>
</div>
</template>
<script>
export default {
name: "BubblePopover",
mounted() {
console.log(this.$slots)
console.log(this.$refs);
},
}
</script>
The this.$slots member should work.
this.$slots.watchable
// or
this.$slots.watchable.$el
Try using nextTick in your mounted function to give slot time to render.
mounted() {
nextTick(()=>{
// your code here
})
}
You can also add an ID from your slot content add access it by getElementById
Another options is to use the composition API plugin and then access content from the ref.
Related
I'm trying to initialise IntersectionObserver in each page of my website built with Nuxt3.
Therefore, I want to access each HTML element that has a specific CSS class. However, on page change, I noticed that via onMounted hook the detected elements are from the previous page.
Here a easy to reproduce example:
app.vue
<template>
<div>
<NuxtPage />
</div>
</template>
pages/index.vue
<script setup lang="ts">
onMounted(() => {
console.group("index.vue");
console.log(document.querySelector("#container"));
console.groupEnd();
});
</script>
<template>
<div id="container">
<h1>INDEX</h1>
<NuxtLink to="/work">
Go to work
</NuxtLink>
</div>
</template>
pages/work.vue
<script setup lang="ts">
onMounted(() => {
console.group("work.vue");
console.log(document.querySelector("#container"));
console.groupEnd();
});
</script>
<template>
<div id="container">
<h1>WORK</h1>
<NuxtLink to="/">
Go to index
</NuxtLink>
</div>
</template>
Simply, the result in the console always come from the previous DOM. Here the steps:
Load the page on index.vue, you see the right element in the console.
Go to work.vue using the link.
See the console showing the exact same result as previously, yet with an added empty class attribute on #container
My question is, why does onMounted hook doesn't show the right DOM on page change?
I tried to set the page transition to the default mode:
definePageMeta({
pageTransition: {
mode: 'default',
},
});
Nothing changed.
NOTE: I am using nuxt version: 3.0.0-rc.9
For this kind of usage, you should use template refs: https://vuejs.org/guide/essentials/template-refs.html#accessing-the-refs
Otherwise, Vue will not behave as expected. More details can be found in this other answer.
I have an array sheets initialised in data, has an item pushed to it when a button is pressed
data() {
return {
sheets: []
};
}
And in the html I'm trying to add a Card component for each element in this array and pass the data as a prop, but none of the components get rendered and there is no error message. I also tried putting the v-for directly on the component but it has the same result
<div id="sheets">
<template v-for="c in sheets">
<Card :info="c"/>
</template>
</div>
Meanwhile if I do something like this, it displays all the data in the array correctly, so I dont understand whats going wrong here
<div id="sheets">
<template v-for="c in sheets">
<span>{{c}}</span>
</template>
</div>
--
Solution
In my component the data from prop was being manipulated in the created() function, it works after I did this in the mounted() function instead
Make sure you have following things done correctly:
Imported your Card component
Passing and accessing the props
There are no conflicts in the variable names and id names
Next thing you need to know and is a must while using v-for is adding :key to the element which acts as unique id and lets Vue detect that each element is unique inside the v-for. One thing to be noted while using :key is that, you cannot use :key on the <template> tag.
Adding a validation using v-if would be a good idea, so v-for only executes if the array is not empty.
<div id="sheets">
<template v-if="sheets.length">
<Card v-for="(sheet,index) in sheets" :key="index" :info="sheet" />
</template>
</div>
Edit 1: As mentioned by Michal in the comments section that using index can lead to difficulties while debugging issues. You can use either :key="sheet" or if sheet is object including some unique id in it, then use :key="sheet.id" and get rid of the index
// use this if sheet is not an object
<div id="sheets">
<template v-if="sheets.length">
<Card v-for="sheet in sheets" :key="sheet" :info="sheet" />
</template>
</div>
OR
// use this if sheet is an object having some unique id in it
<div id="sheets">
<template v-if="sheets.length">
<Card v-for="sheet in sheets" :key="sheet.id" :info="sheet" />
</template>
</div>
As :key is not mandatory, It should work without that as well. I just created a working fiddle below. Please have a look and try to find out the root cause.
Vue.component('card', {
props: ['info'],
template: '<span>{{ info }}</span>',
});
var app = new Vue({
el: '#app',
data: {
sheets: [],
count: 0
},
methods: {
create() {
this.count++;
this.sheets.push('Sheet' + this.count);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="create">Create a Card!</button>
<template v-for="c in sheets">
<Card :info="c"></Card>
</template>
</div>
I'm a bit new to the component's world and trying to figure out a thing, how the parent child relationship works in components. I've seen some examples of some component libraries where they have some parent child components to be defined and those are used as the child components. For example, table and tr:
<my-table> <!-- Parent -->
<my-tr> </my-tr> <!-- Child -->
</my-table>
Now, I assume, that child works for parent via slots. So the parent should be defined something like this:
<template>
<div>
<slot></slot>
</div>
</template>
Now the parent element can have multiple <my-tr> as well. And slot should be rendering all of those. However, I am trying to a similar thing but a little more complex than that.
I am trying to create a slider with this approach. Where there is a my-slider component and my-slider-item components used to define inside my-slider component. And then I want to control the visibility of the child components defined in the parent component slot by modifying it's properties.
It should be looking like this:
<my-slider>
<my-slider-item>Item 1</my-slider-item>
<my-slider-item>Item 2</my-slider-item>
<my-slider-item>Item 3</my-slider-item>
</my-slider>
my-slider component
<template>
<div class="my-slider">
<slot></slot>
</div>
</template>
my-slider-item component
<template>
<div class="my-slider__item">
<slot></slot>
</div>
</template>
Now how can I know in the parent that how many <my-slider-item> are defined in the parent slot and based on that I want to control the visibility of the child 1 at a time as it is going to work as the slider.
I'm not sure but I missing some basic concept here which I was not getting after looking at tons of example since yesterday. If anyone can please help here? Thanks a lot!
The parent-child relationship is actually established by importing the child component into the parent component, and including the child in the parent's 'components' option.
I created an example scenario with simple Parent and Child component definitions in order to show a standard relationship implementation. Built with Vue 2 and the Vue CLI.
MySlider.vue (parent)
<template>
<div class="my-slider">
<h4>My Slider</h4>
<my-slider-item v-for="(item, index) in sliderItems" :key="index" :sliderItem="item" />
</div>
</template>
<script>
import MySliderItem from './MySliderItem.vue'
export default {
components: {
MySliderItem
},
data() {
return {
sliderItems: [
{
name: 'Slider Item 1'
},
{
name: 'Slider Item 2'
},
{
name: 'Slider Item 3'
}
]
}
}
}
</script>
MySliderItem.vue (child)
<template>
<div class="my-slider-item">
<h5>{{ sliderItem.name }}</h5>
</div>
</template>
<script>
export default {
props: {
sliderItem: {
type: Object,
required: true
}
}
}
</script>
I am trying to compose a component inside another like this:
<prompt :users="users">
...
<dataset v-for="ds in users" :user="user"></dataset>
...
</prompt>
But apparently I'm not registering it properly:
[Vue warn]: Unknown custom element: <dataset> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
(found in root instance)
Here's how I'm trying to register it:
app.js
Vue.component('prompt', {
props: ['userdata', 'users'],
template: '#prompt-template',
components: {
'dataset': {
props: ['userdataset', 'user'],
template: '#dataset-template',
}
}
});
Finally, the templates:
<template id="dataset-template">
<li>{{ user}}</li>
</template>
<template id="prompt-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 name="body">
</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>
</template>
Are there any steps I'm missing? I can't figure out how the component isn't being registered.
The problem is that you use the dataset component as a slot within the prompt component. While the Vue vm tries to figure out the component tree it will recognize the dataset component which it does not know. Subcomponents are used in the component template but not within slots. You have to register the dataset component within the Vue vm like you did for the prompt component. Try this
Vue.component('promp', { ... })
Vue.component('dataset', { ... })
It also make sense to register the components on the same level since the templates of the components are also registered on the same level (next to each other).
Compare it to the example you mentioned in another answers comment: Here the subcomponent axis-label is only used within the template of the parent polygraph component. This is valid since now the component is in contract to figure out it sub components not the vue-vm.
In other words:
It should be possible to pass components into the slot of any component A which are not subcomponents of A. Therefore all components passed to slots of a component should be available to the vue vm.
Thumb rule could be
if a component does not appear within another components template, it is not a subcomponent.
I have actually 2 global components one for Admin and other one for Modal. The Admin component have a child comp called Page and the Page comp have others childs. I want to pass content directly to Page comp via slots. Like this:
app.js
new Vue({
el: '#app',
components: { Admin, Modal }
})
Admin.vue
<template>
<div>
<page>
<slot></slot>
</page>
</div>
</template>
export default {
components: { Page }
}
Page.vue
<template>
<div>
<page-header>
<slot name="page-header">
<h1 class="page-title">
<slot name="page-title">
Page Title
</slot>
</h1>
</slot>
</page-header>
<page-body>
<slot>
Page Body
</slot>
</page-body>
<page-footer>
<slot name="page-footer">
Page Footer
</slot>
</page-footer>
</div>
</template>
export default {
components: {
pageHeader,
pageBody,
pageFooter
}
}
index.html
<admin>
<div slot="page-header">
Header Test
</div>
Body Test
<div slot="page-footer">
Footer Test
</div>
</admin>
I don't need to use Page as global component, Please any idea?? Hope you understand what I'm looking for...
Thanks
I don't think slots are designed to do this. If you need data to be persisted between parent and children then use either props as #Belmin Bedak suggested. When you pass a prop to a component it will be available to all its children.
If you need to persist state on front-end I strongly recommend using Vuex as source of data for all components ( only use if it becomes more complex to have data scattered across components ).