Return a custom component from a method

export default {
    components: {
        'def-input': TextField,
        'def-select': Select,
        'def-button': Button
    },
    props: {
        'title': String,
        'fields': Array,
        'btnAction': Object
    },
    data: function () {
        return {
            arrElem: []
        }
    },
    template: `
        <form @submit.prevent class="ui-form">
            <div class="uif-title">
                <h3>{{ title }}</h3>
            </div>
            <div class="uif-row" v-for="indx in fields.length" :id="'uifRow' + indx" :key="indx" v-html="arrElem[indx]"></div>
            <div class="uif-action">
                <def-button
                    :unique-id="btnAction.uniqueId"
                    :use-style="btnAction.useStyle"
                    :variant="btnAction.variant"
                    :action="btnAction.action"
                    :value="btnAction.value"
                    :icon="btnAction.icon" />
            </div>
        </form>
    `,
    beforeMount: function () {
        this.fields.forEach((fields) => {
            const rowFields = [];
            let elemPos, elemRow, elemType, elemProps;

            for (const field in fields) {
                elemPos = field.split("_")[0];
                elemRow = field.split("_")[1];
                elemType = field.split("_")[2];
                elemProps = fields[field];

                rowFields[elemPos] = this.returnComponent(elemType, elemProps);
            }

            this.arrElem[elemRow] = rowFields;
        });
    },
    methods: {
        returnComponent(type, props) {
            const componentByType = {
                input: `<def-input
                    label="${props.label}"
                    uniqueId="${props.name}"
                    class="${props.class}"
                    mask="${props.mask}"
                    placeholder="${props.placeholder}"
                    size="${props.size}"
                    maxlength="${props.maxlength}"
                    useStyle="${props.useStyle}"
                    attrs="${props.attrs}"></def-input>`,
                select: `<def-select
                    label="${props.label}"
                    uniqueId="${props.name}"
                    options="${props.options}"
                    useStyle="${props.useStyle}"
                    attrs="${props.attrs}"></def-select>`
            }

            return componentByType[type];
        }
    }
}

I have the following component, its a formularie with x number of ‘uif-row’, the variable fields is an object with all the fields this formularie will have.
I am trying to fill the ‘uif-row’ with its respective fields but all i get in is…
<def-input label="Cotação" uniqueid="id_cotacao" class="undefined" mask="int" placeholder="undefined" size="undefined" maxlength="undefined" usestyle="undefined" attrs="undefined"></def-input>

and not the component TextField.
Is there a way to do this?

There are so many questions.
How come you can read elemRow in a loop but then use it later outside the loops scope?
How come you can use the index of a loop over the length of the fields array and it matches the index of the arrElem object, which again used the mentioned elemRow value as key?
How come you are expecting elemRow[indx] to be a “component” when it is actually an array of “components”?

This is not easy to maintain code. I hope somebody gives you some constructive feedback later during code-review.

Just for the sake of making your code work you might want to adjust it in the following way:

export default {
    components: {
        'def-input': TextField,
        'def-select': Select,
        'def-button': Button
    },
    props: {
        'title': String,
        'fields': Array,
        'btnAction': Object
    },
    data: function () {
        return {
            arrElem: []
        }
    },
    template: `
        <form @submit.prevent class="ui-form">
            <div class="uif-title">
                <h3>{{ title }}</h3>
            </div>
            <div class="uif-row" v-for="(rowFields, indx) in arrElem" :id="'uifRow' + indx" :key="indx">
               <div v-for="(field, pos) in rowFields" :key="field.name">
                  <def-input
                    v-if="field.type === 'input'"
                    :label="field.props.label"
                    :uniqueId="field.props.name" ... ></def-input>
                  <def-select
                    v-if="field.type === 'select'"
                    :label="field.props.label"
                    :uniqueId="field.props.name" ... ></def-select>
               </div>
            </div>
            <div class="uif-action">
                <def-button
                    :unique-id="btnAction.uniqueId"
                    :use-style="btnAction.useStyle"
                    :variant="btnAction.variant"
                    :action="btnAction.action"
                    :value="btnAction.value"
                    :icon="btnAction.icon" />
            </div>
        </form>
    `,
    beforeMount: function () {
        this.fields.forEach((fields) => {
            const rowFields = [];
            let elemPos, elemRow, elemType, elemProps;

            for (const field in fields) {
                elemPos = field.split("_")[0];
                elemRow = field.split("_")[1];
                elemType = field.split("_")[2];
                elemProps = fields[field];

                rowFields[elemPos] = {
                  type: elemType,
                  props: elemProps
                }

            this.arrElem[] = rowFields;
        });
    }
}
1 Like

You can’t mount components using v-html. That directive’s purpose is to render raw html. It looks like you want dynamic components.

1 Like