Sebastian Sulinski
Sebastian Sulinski

Reputation: 6045

VueJs: Component slot as property

I've read the documentation and I'm not sure if what I'm trying to achieve is actually doable. What I'm after is the component's property that contains html tags, which obviously produce invalid html - hence I was wondering if there is a way of passing html into the component and sending it further with emitted event. The most suitable way would be to have named slot, but I don't think I can associate content of a named slot with internal property.

The component I work on is a simple confirmation dialog. The trigger component would wrap two slots - one for label and the other for message I'd like to dispatch with the event to the dialog component.

My trigger component looks like this at the moment:

<template>
    <a :class="cssClass" @click="clicked()">
        <slot></slot>
    </a>
</template>
<script>
    export default {
        props: {
            id: {
                type: String,
                required: true
            },
            route: {
                type: String,
                required: true
            },
            message: {
                type: String,
                required: true
            },
            cssClass: {
                type: String,
                required: false
            }
        },
        mounted() {
            window.EventHandler.listen('confirm-dialog-' + this.id + '-called', () => {
                window.location.reload(true);
            });
        },
        methods: {
            clicked() {
                window.EventHandler.fire('top-confirm', {
                    id: 'confirm-dialog-' + this.id,
                    message: this.message,
                    url: this.route
                });
            }
        }
    };
</script>

and when added to the html:

<call-dialog
    route="{{ route('subscriber.destroy', $subscriber->id) }}"
    css-class="alert"
    id="{{ $subscriber->id }}"
    message="Are you sure you wish to remove '{{ $subscriber->email }}'?<br />There is no undo!"
>
   <i class="fa fa-trash"></i> Remove
</call-dialog>

As you can see, at the moment I'm passing the message using message prop, but this particular one contains <br /> tag, which produces invalid html.

Any idea how to tackle this?

NOTE

I think I've found the way of doing it with named slots:

<template>
    <a :class="cssClass" @click.prevent="clicked()">
        <slot name="label"></slot>
    </a>
</template>
<script>
    export default {
        props: {
            id: {
                type: String,
                required: true
            },
            route: {
                type: String,
                required: true
            },
            cssClass: {
                type: String,
                required: false
            }
        },
        mounted() {
            window.EventHandler.listen('confirm-dialog-' + this.id + '-called', () => {
                window.location.reload(true);
            });
        },
        methods: {
            message() {
                 return this.$slots.message[0].context.message;
            },
            clicked() {
                window.EventHandler.fire('top-confirm', {
                    id: 'confirm-dialog-' + this.id,
                    message: this.message(),
                    url: this.route
                });
            }
        }
    };
</script>

And now simply passing and additional named slot slot="message"

<call-dialog
    route="{{ route('subscriber.destroy', $subscriber->id) }}"
    css-class="alert"
    id="{{ $subscriber->id }}"
>
    <span slot="label"><i class="fa fa-trash"></i></span>
    <span slot="message">Are you sure you wish to remove '{{ $subscriber->email }}'?<br />There is no undo!</span>
</call-dialog>

Upvotes: 1

Views: 1861

Answers (2)

Linus Borg
Linus Borg

Reputation: 23968

You should use v-html inside of the call-dialog component to insert the raw HTML from the message prop into an element.

<div v-html="message"></div>

https://v2.vuejs.org/v2/api/#v-html

Upvotes: 0

chamberlainpi
chamberlainpi

Reputation: 5241

I've had to do something similar for a custom button component.

Vue.prototype.$getSlotText = function( name = 'default' ) {
    const htmlElements = this.$slots[name];
    if(!htmlElements || !htmlElements.length) return '';

    return htmlElements.reduce((fullText, {tag, text}) => {
        return fullText + (tag === 'br' ? '\n' : text || '');
    }, '');
}

Essentially, this would access the HTML-Elements on the "default" slot (by default).

  • If none found, returns '' blank string.
  • If some found, iterates over each Element, concatenates it's text property if found, or \n if it's a <br/> tag.

Note: The .reduce(...) method is used for the iteration / concatenation. Alternatively a .map(...) call with a similar arrow-function, followed by a .join('') would probably result the same thing.

Upvotes: 0

Related Questions