wellhellothere
wellhellothere

Reputation: 629

Using $emit from child component to send data to the parent component doesn't work with Vue

I'm writing a "tabs" component.

I am trying to send data using the sendTabsData() method in my code, and for some reason it's not working using $emit.

This is my tab component code:

<template>
    <div id="tabs">
        <div id="tabs-cont">
            <div class="tabbable-panel">
                <div class="tabbable-line">
                    <ul class="nav nav-tabs" role="tablist"> 
                    </ul>
                    <div class="tab-content">
                        <slot></slot>
                    </div>
                </div>
            </div>
        </div> 
    </div> 
</template>

<script>
export default {
    name: 'DashboardTabs',
    props: {
        tabs: {
            type: Object,
            required: true,
            default: {}
        }
    },
    data() {
        return {
            displayed:      '',
            tabName:        '',
            $tabsContainer: null,
            navTabs:        null, 
            tabContent:     null
        }
    },
    created() {
    },
    mounted() {
        this.init();
    },
    watch: {
    },
    methods: {
        init: function() {
            // Fetch UI elements 
            this.$tabsContainer = $('#tabs');
            this.navTabs        = this.$tabsContainer.find('.nav-tabs');
            this.tabContent     = this.$tabsContainer.find('.tab-content');

            this.createTabs(this.tabs);
        },
        // Create tabs by result
        createTabs: function(tabs) {
            if ( this.tabs !== undefined || this.tabs.length > 0 ) {
                this.setTabsBtns(tabs);
            }
        },
        // 
        tabClick: function(e) {
            this.changeDisplayNames(e);         
            this.sendTabsData();
        },
        // Change display names
        changeDisplayNames: function(e) {

            let liItem = e['target']['dataset']['displaytable'];

            if (liItem) {
                // Update variable table name
                this.displayed = liItem;
                // Remove the '$' and 'Table' text
                let tableCategory   = this.displayed.replace('Table', '');
                tableCategory       =  tableCategory.replace('$', '');
                // Update "global" variable of the current table
                this.tabName        = tableCategory.charAt(0).toUpperCase() + tableCategory.slice(1);
            }
        }, 
        // Set tabs li btns 
        setTabsBtns: function(tabs) {

            for (let key in tabs) {
                this.navTabs.append(`<li class="nav-item" @click="this.tabClick"> <a href="#tab_default_${key}" class="nav-link" data-toggle="tab" data-displayTable="${tabs[key]}"> ${tabs[key]} </a> </li>`);
            }

            this.navTabs.find('li:first a').addClass('active');
            this.displayed  = this.tabs[0];
            this.tabName    = this.tabs[0];
        }, 
        // Set tabs content divs - not relevant if you don't want to load extra data to the DOM
        setTabsContent: function(tabs) {

            for (let key in tabs) {
                this.tabContent.append(`<div class="tab-pane" id="tab_default_${key}"> <table class="aTable hover display" id="${tabs[key]}"> </table> </div>`);
            }

            this.tabContent.find('.tab-pane').first().addClass('active');
        },
        hideTabs: function() {
            this.$tabsContainer.slideUp('slow');
        },
        showTabs: function() {
            this.$tabsContainer.slideDown('slow');
        },
        // Send tab data to parent
        sendTabsData: function(){
            let data = {
                'tabDisplayed': this.displayed,
                'tabName':      this.tabName            
            };
            this.$emit('tabsData', data);
        }
    }
};
</script>   

and this is the parent component:

<script>
    // import ProgressBar      from "../components/ProgressBar.vue";
    // import _                from 'lodash';
    import DashboardForm    from "../components/DashboardForm.vue";
    import DashboardTabs    from "../components/DashboardTabs.vue";

    export default {
        name: 'testcomponent',
        components: {
            // ProgressBar,
            DashboardForm, 
            DashboardTabs
        },
        // mixins: [GF],
        data() {
            return {
            };
        },
        created() {
        },
        mounted() {
        },
        destroyed() {
        },
        watch: {
        },
        methods: {
            // Submit form
            formSubmit: function(data) {
                console.log('Submit form here');
                console.log(data);
            }, 
            // Tabs data 
            fetchTabsData: function(data) {
                console.log('tab was clicked');
                console.log(data);
            }
        }
    }
</script>

<!-- Template -->
<template>

    <div id="testcomponent">

        <!-- DashboardForm -->
        <DashboardForm 
            :useGroupFilter="true"
            :useDateRange="true"
            @submit="formSubmit"
        />

        <!-- Main Data Container -->
        <div id="data-container">
            <div id="chart-container"></div>
            <!-- Dashboard Tabs and Tab Container -->
            <DashboardTabs
                :tabs="{ 0: 'potato', 1: 'banana' }"
                @tabsData="fetchTabsData"
            >
                <!-- Datatable -->
                <!-- <DashboardDatatable/> -->
            </DashboardTabs>


        </div> 
        <!-- END data-container -->

    </div>
</template>

as you can see, in my parent component, i have a submit emit that fetches data (and works fine).

The only difference between the tabs component and the form component is that the tab component generates the <li>s that have the click event bined to.

So just to be clear with my question:

How do I send data from a child component to a parent component using $emit when in this case - the element that has the click event bind to it (onClick ==> send/listen the data to the parent component) is dynamically self-generated and not "static".

EDIT: using v-for doesn't seem to get the requested result:

<li class="nav-item" @click="this.tabClick" v-for="(tab,index) in tabs">
<a :href="`#tab_default_${index}`" class="nav-link" data-toggle="tab" :data-displayTable="`${tab}`"> ${tab} </a>
</li>

I get the error:

Error in render: "TypeError: Cannot read property 'tabClick' of undefined"

Seems like it's not letting me to add a @click

Edit2:

tryed using @click.native="this.tabClick" in v-for - doesn't result any errors but my dummy console.logs do not appear. click event doesn't run prroperly.

Upvotes: 3

Views: 1351

Answers (1)

JosselinTD
JosselinTD

Reputation: 621

Your component emit a tabs event (this.$emit('tabs', data)), so, your parent should listen to the tabs event : @tabs="tabsData" instead of submit

Upvotes: 3

Related Questions