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.
Related
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
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)
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>
....
<el-tree :data="Project">
<span class="custom-tree-node" slot-scope="{ node, data }" v-contextmenu:contextmenu>
<el-input
placeholder="Please input"
v-model.trim="data.label"
#blur="saveOrDiscard(node, data)"
></el-input>
</span>
</el-tree>
I have used method as below, where I am updating id of treenode (which is from ajax call inserted id. I have simply changed to clearly explain problem).
But data of tree (which I have given as Project) is not updating. Next time it shows 0 value (which I have set as deault).
public saveOrDiscard(node: TreeNode<any, TreeData>, data: TreeData) {
//Following 456 is not updating in tree data.
node.data.id = 456;
}
Scoped Slots and Slot Props
el-tree is a slotted component and, by default, slots only have access to the same instance properties as the rest of the containing template. So for example, the slot does not have access to :data.
If you must access this data in the parent template, you need to use slot props and also explicitly bind the data in the slot's template. So in the el-tree component template, you bind the data to the slot like this:
<slot :data="data"></slot>
Then in the parent template, you can access data via the v-slot directive like this:
<template v-slot:default="slotProps">
{{ slotProps.data }}
</template>
The default argument refers to the slot name, which is named "default" if none is specified.
Here is a fiddle showing this behavior.
*You can read more about slots & slot props here.
Binding blur event as following worked for me.
#blur="() => saveOrDiscard(node, data)"
instead of following.
#blur="saveOrDiscard(node, data)"
I created a component for displaying some information in a nice looking way. This component is just a wrapper for the content, which is rendered inside the parent component. The parent component implements the child component this way:
<my-component v-for="item in items" :key="item.id">
<template slot="header">
{{ item.title }}
</template>
<template slot="meta">
<div>
<template v-if="typeof item.additionalData != 'undefined'">
{{ item.additionalData.text }}
</template>
</div>
</template>
</my-component>
Its working fine, until I want to change the data. items is a variable in the parent component and at render time, the data is parsed in the right way. When I change something inside of items after rendering, the child component doesn't recognize it. The reason is because the item.additionalData is added through an AJAX call after the component was already rendered.
The docs say
Everything in the parent template is compiled in parent scope; everything in the child template is compiled in child scope.
but it seems like this is only true at render time.
Am I not able to use my component this way or is there a solution for that?
From the docs:
Due to the limitations of modern JavaScript (and the abandonment of
Object.observe), Vue cannot detect property addition or deletion.
Since Vue performs the getter/setter conversion process during
instance initialization, a property must be present in the data object
in order for Vue to convert it and make it reactive.
So I suspect the fact that your item.additionalData field is initially undefined means that it's getting missed by Vue later on when it's added. The best solution is to make item.additionalData defined, but set to null or false. If that's not possible, consider using Vue.set(object, key, value) as outlined here.
I had a similar problem with adding a search icon to the item template.
The way I resolved it was to make (in terms of your example) additionalData a computed property which is then fully reactive. This is the basic form:
computed: {
additionalDataText() {
return item.additionalData ? item.additionalData.text : null
}
}
I'm unclear if your code shows the parent component or the child component.
As it is, the simplest change you can make requires the computed property to receive item as a parameter like so:
computed: {
additionalDataText() {
return function (item) {
return item.additionalData ? item.additionalData.text : null
}
}
}
and your template would be
<my-component v-for="item in items" :key="item.id">
<template slot="header">
{{ item.title }}
</template>
<template slot="meta">
<div>
<template v-if="additionalDataText(item)">
{{ additionalDataText(item) }}
</template>
</div>
</template>
</my-component>
This technique is from Unirgy's answer to this question: Can I pass parameters in computed properties in Vue.Js. There's a bit of controversy there about this method, but I tried it in my project and it seems to work.
Note, in the referenced question the accepted answer suggests using a method, but this does not give you the reactivity you require.