kaan_atakan
kaan_atakan

Reputation: 4047

How can I pass class and style attributes on a vue component to a different element like $attrs?

I have a single file component called confirm-document that looks something like this:

Sandbox

<template>
  <v-dialog>
    <template v-slot:activator="{ on }">
      <v-btn v-bind="$attrs" :class="activatorClass" v-on="on">{{
        title
      }}</v-btn>
    </template>
    <v-card>
      <v-card-title>{{ title }}</v-card-title>
      <v-card-text><slot></slot></v-card-text>
    </v-card>
  </v-dialog>
</template>

<script>
export default {
  name: "ConfirmDocument",
  props: {
    title: String,
    activatorClass: {},
  },
};
</script>

So when I then use this component like:

<ConfirmDocument
    class="useless-class"
    activator-class="mt-4 ml-n4"
    title="Consent"
> Document Content </ConfirmDocument>

Sandbox

The classes get applied to the v-dialog, which ends up as an invisible div with nothing inside and both the activator and modal attached as sibling nodes.

Since this is mainly a wrapper component to provide a consistent UI, I actually only need for the activator to be positionable. So I want to pass the class and style props to the v-activator.

The activator-class prop that I have declared actualy works fine. But I am very curious if there a way to change the element to which the component's class and style attributes are bound, so that I can use class instead?

Upvotes: 4

Views: 8480

Answers (3)

Momin Bin Shahid
Momin Bin Shahid

Reputation: 836

This is fixed in Vue.js v3 ref

For Vue.js v2, you can try this

Check v-bind and attrs computed property below

<template>
  <v-dialog>
    <template v-slot:activator="{ on }">
      <v-btn v-bind="attrs" v-on="on">{{
        title
      }}</v-btn>
    </template>
    <v-card>
      <v-card-title>{{ title }}</v-card-title>
      <v-card-text><slot></slot></v-card-text>
    </v-card>
  </v-dialog>
</template>

<script>
export default {
  name: "ConfirmDocument",
  inheritAttrs: false, // <- added just for safety
  props: {
    title: String,
  },
  computed: {
    attrs() {
      const attrs = { ...self.$attrs }

      // adding class and style for `v-bind`
      attrs.class = self.$vnode.data.staticClass
      attrs.style = self.$vnode.data.staticStyle
            
      return attrs
    }
  }
};
</script>

Explanation - In Vue.js version 2.x.x, $attrs does not include class and style (ref) but there are many scenarios in which we wanted to pass on all the props along with class and style into another component so, $attrs should have class and style in it which they did add in version 3 of vue.js. There is a detailed discussion on this topic on github do check it out for more details.

For version 2, what we wanted to achieve is that class and style can be passed to another component. We can pull it off by getting the class [as string] and style [as object] from the current component node i.e. vnode and pass it on to the other component using v-bind.

Now, you can pass props along with class and style into another component inside ConfirmDocument directly from the parent (or caller) component

Upvotes: 6

Andrei Gătej
Andrei Gătej

Reputation: 11934

I think you can use the inheritAttrs: false property. What it does it to make sure that attributes are not applied automatically to the root element and it lets you choose where to apply them instead.

<template>
  <v-dialog>
    <template v-slot:activator="{ on }">
      <v-btn v-bind="buttonAttrs" >Read {{ title }}</v-btn>
    </template>
    <v-card>
      <slot></slot>
    </v-card>
  </v-dialog>
</template> 

<script>
export default {
  inheritAttrs: false,
  props: {
    title: {type: String},
  },
  computed: {
    buttonAttrs () {
      // select which attrs to apply
      const { title, ...rest } = this.$attrs;

      return rest;
    }
  }
}
</script>

A working (and a bit cluttered) example can be found here.

Upvotes: 2

RWAM
RWAM

Reputation: 7018

What's about simple using props to handle this?

<template>
  <v-dialog>
    <template v-slot:activator="{ on }">
      <v-btn :class="btnClass" v-on="on">Read {{ title }}</v-btn>
    </template>
    <v-card>
      <slot></slot>
    </v-card>
  </v-dialog>
</template> 

<script>
export default {
   props: {
      btnClass: { type: String },
      title: { type: String }
   }
}
</script>

and using the component:

<confirm-document 
  btn-class="mt-0 mb-0"
  title="Privacy Policy"
>
  Lorem ipsum dolor sit amet
</confirm-document>

Upvotes: 1

Related Questions