How to apply CSS class to child component with two root nodes? - javascript

I created a custom alert component using Vue 3 with Vuetify 3 with two root nodes
<template>
<v-alert type="warning" title="Contains button to display dialog" />
<v-dialog>dialog goes here</v-dialog>
</template>
The v-alert component contains a button to toggle the state of the v-dialog component.
In my parent component ( the consuming one ) I would like to apply a CSS class to the child component ( my custom alert component )
<MyAlert class="mb-8" />
<div>Main content goes here</div>
The problem is that this doesn't work, I get the warning
[Vue warn]: Extraneous non-props attributes (class) were passed to component but could not be automatically inherited because component renders fragment or text root nodes
Which makes sense because it doesn't know which component should have this class, both, either alert or dialog or none.
A quick fix would be to apply the class mt-8 to the div below. But my alert component is conditional so I would have to use an if-statement to check if I should apply a margin-top or not.
Reproduction link: https://play.vuetifyjs.com/#eNp9UstuwjAQ/BXLFy4krvpShVIE4swXYA4hGHCJH7I3QRHKv3cdB0ih7SmZnfXseD2rM51bm9aVoBOagVC2zEFMuSYkq5Pc2u63AyqXukeIl828FA5IUebef3KqNskHp4TdOrayni7xDCmMBqGB7I3w5CCcyFjgemE2UA4gzszYwAtCXzhpgXgBVaClsgaHn4kTO9KSnTOKjPASoyt18ReplC0OstyGe2JLxqIcCtExjQfQhE2/vNG4h3PwwnvCczohXSXUUCBgTg8A1k8Yq7Q97tPCKDZDjrlKg1Qi2Ro1e0mf09c3vKuHYT0VXiUbZ05eOJzI6XggDnLX/DWgp1H3KX2Psn0pSD5IMdSvhUuc0FtcuvvX913vD+933IP/MLPlusVdllIf/d0aC9+tcLWOLdeH+DVw3ZtBYwVG6pQ7LfUeUwUSylBZYJIwK55sKgCjCRiCDlGhwW9emtAbE4hasdLHLIJbBC9puzYNAkfbMY33R7vdsun6G20jDc0=
Is it possible to tell Vue it should use the applied class for the inner v-alert component?

So, you are trying to assign classes to a component, but your component contains fragments.
Wrap your Child.vue component tags:
<template>
<div>
<v-alert type="warning" title="Contains button to display dialog" />
<v-dialog>
dialog goes here
</v-dialog>
</div>
</template>

Yes you can!
First thing you need to do is to specify the
inheritAttrs: false
option in your alert component:
<script>
export default {
inheritAttrs: false,
}
</script>
and than you can apply class attribute to the desired component using
:class="$attrs.class"
But you must know, that now every other attribute must be specified in code using $attrs, because your component would not apply them by default anymore
More on this topic can be found here Vue Docs

Related

Using the same component in a child and parent in Vue

I have a VueJS project - which in one view there are parent and child components that are both using the same component called deleteModal.
When I trigger the modal from the child (to show it), it triggers both the child and parent modals (except no data is passed to the modal triggered by the parent). As I understand it, this is because I have used the component twice - in the parent and child - example below. To note, it works as expected from the paren
I've researched and tried a few things: setting a key value for each of the components, changing the components ref name among other things. I have also tried using v-show to only show component just before I trigger the parent model however, this solution is not ideal.
How can I only trigger the modal from the child?
Parent Component
<template>
<div>
<childCompt ref="childCompt" />
<deleteModal
ref="deleteModal"
#deleteTriggerAPI="deleteAPIParent"
/>
</div>
<template>
Child Component - childCompt
<template>
<div>
<deleteModal
ref="deleteModal"
#deleteTriggerAPI="deleteAPIChild"
/>
</div>
<template>
My old answear is not good at all. I personally to show and hide element using jquery in vue. For me right now this is best solution but maybe i don't know some best.
If you want use only vue i using also variable passing to child from parent which will support visable of your modal.
We pass variable with ":" and register event with "#".
<template>
<childComponent :isModalOpen="isModalOpen" #onModalClose="isModalOpen=false">
<template>
export default {
name:"parent",
data: () => {
isModalOpen: false
}
}
In child we catch this by using props. We need to define type of varialbe we pass. Different between props and data is that in props we cannot change value in child component.
export default {
name: "child",
props: {
isModalOpen: Boolean
}
}
And you can use this variable to show or hide modal. Also in child component we can create button to close modal and we emit event to parent in order to change variable value.
To do this we using this.$emit('eventName');
More information right here: https://forum.vuejs.org/t/passing-data-back-to-parent/1201
You could try globally defining the component,
ie, in main.js
Vue.component('deleteModal',deleteModal)

Recommended way of conditionally rendering custom Vue components

in my parent render function, I am rendering a list of form inputs. They can be number, slider, text, etc, and for each, I have a custom Vue component, i.e. FormInputSlider. I receive some data from an API, and then have an array of these different inputs to render. Doing a huge if/else block in my parent's render code seems unmaintainable, so what is the best/standard way of rendering a FormInputSlider component when I encounter a "form-input-slider" in the list iteration? I'm coming from a React understanding of the world for what it's worth.
If you've the component names, you could just use the built-in component tag to render the component dynamically :
<component :is="dynamicName" />
instead of :
<template v-if="dynamicName==='abc'">
<abc/>
</template>
<template v-elseif="dynamicName==='xyz'">
<xyz/>
</template>
....

Vuejs: weird class render

I've got the following set-up and weird render of class and name.
<component v-for="item in List"
:item="item"
:class="item.class"
:name="item.name"
</component>
let component = Vue.component('component', {
props: ['item'],
template: `<li :class="item.class">
{{ item.name }}
</li>`
}),
data: function (){
return {
List: [
{key:0, class:'someClass', name:'someName'},
]
}
},
})
Render:
<li class="someClass someClass" name="someName"> someName </li>
Where on earth comes double "someClass"? Why 'name' item from 'List' is an attribute here as it wasn't even used and v-bound as attribute? Is 'name' a somewhat reserved attribute?
Thanks beforehands :)
The problem is here:
<component v-for="item in List"
:item="item"
:class="item.class"
:name="item.name">
</component>
In this section you're binding the class to item.class and the attribute name to item.name. Vue will apply both of these to the outer element of the corresponding component.
Within that component you're then adding :class="item.class", which adds the class again.
To fix this just remove the :class="item.class" and :name="item.name" from that first template.
<component v-for="item in List"
:item="item">
</component>
It is also a little mysterious that you have List defined within the data of component but you seem to be using it within the scope of the parent component's template.
Update based on a comment:
A class can be set on the outer element in two ways. It can either come from the component itself, inside its template, or it can be set from the parent template.
Which one you choose depends on what the class does and which of the two components is responsible for that. Neither one is necessarily right or wrong.
Usually a component is responsible for controlling its own styling but a common exception to that is classes that control layout.
For example, if you have a button component then classes that decide whether the button should be red, green or blue would typically be managed by the component itself. However, if that button needs to be right-aligned within its parent container then the button component probably doesn't need to know about that. That alignment decision is controlled by the parent container and it can set a class on the button without the button needing to get involved.

Trigger method in sub component inside slot, $refs not working

I have the following template in a specific modal component:
<template>
<my-base-modal ref="BaseModal" :width="1000">
<template v-slot:header>Details</template>
<template v-slot:body>
<detail-card ref="DetailCard"></detail-card>
</template>
</my-base-modal>
</template>
It creates a base modal and overwrites the slots for header and body.
The body slot is filled with a sub component which needs to load some data.
I tried to use the following method to open and load the content of this modal:
open (id) {
this.$refs.DetailCard.load(id)
this.$nextTick(() => {
this.$refs.BaseModal.open()
})
}
But this.$refs.DetailCard is always undefined. I suspect this is because the reference DetailCard is defined inside the body slot of the <base-modal> component?
How am I supposed to trigger a function on the <detail-card> component in this example, without using EventBus or passing some props into it?
My suspicion is that at runtime, the DOM is not rendered until the BaseModal's open method is invoked. Therefore, this.$refs.DetailCard will returned undefined since the body slot of your component has not rendered with the nested component.
As you have mentioned in the comments, the fix can be as easy as ensuring that the DOM is already rendered, e.g. using v-show instead of v-if.

Vue.js - How to create child component's DOM element outside of it's parent component's DOM element

I'm trying to create a child component, but instead of putting it's DOM element inside parent's as it is by default, I want to put it somewhere else.
I want to create child component of modal component, but there is a issue because the modal component is overflow: hidden, and I need the child component to be visible outside of the modal. The best solution I've found is how Vuetify do it with their v-menu component (link below). They simply not create child component's DOM element as a child node of it's parent component's DOM element, but instead they put it somewhere else.
I read their code on github, but I'm quite confused with the implementation, and found nothing in Vue documentation about this.
https://vuetifyjs.com/en/components/menus
Child:
<template>
<div>
I'm CHILD
<slot></slot>
</div>
</template>
Parent:
<template>
<div>
<Child>
<span>I'm parent</span>
</Child>
</div>
</template>

Categories