Search code examples
laravelvue.jsvuejs2vue-componentvuex

VueJs 2 click doesn't seem to work


I'm not getting any errors and it's compiling, so I'm not sure what I'm doing wrong. I've searched on the subject with no success.

I have BulkExpenses.vue which pulls and shows some expense records, then BulkExpense.vue is a nested component that displays each record. I want to click the trash icon and have it emit the expense ID back to the parent component to run a delete script. I'm doing this in another project and it's working perfectly, so I'm not sure what I'm doing wrong here.

There's also a Lookup component which is a TypeAhead for pulling trips to connect to the Expense, but that's working.

BulkExpenses.vue

<template>
    <div>
        <form action="#" @submit.prevent="createBulkExpense()" class="publisher bt-1 border-fade bg-white" autocomplete="off">
            <table>
                <thead>
                    <tr>
                        <td><input v-model="bulk_name" type="text" placeholder="Name"></td>
                        <td><input id="bulk_expensed_at" type="text" name="bulk_expensed_at" placeholder="Date Expense Incurred"></td>
                        <td><input id="bulk_type_id" type="text" name="bulk_type" placeholder="Type"></td>
                        <td>
                            <lookup
                                source="/api/lookup/trip"
                                placeholder="Search your trips"
                                filter-key="name"
                                :start-at="3"
                                v-on:selected-link="onSelectedTrip"
                                :modellink="modellink">
                            </lookup>
                        </td>
                        <!-- <td><input id="bulk_tags" type="text" name="bulk_tags" placeholder="Tags"></td> -->
                        <td><input id="bulk_vendor" type="text" name="bulk_vendor" placeholder="Vendor"></td>
                        <td><input id="bulk_method" type="text" name="bulk_method" placeholder="Payment Method"></td>
                        <td><input id="bulk_total" type="text" name="bulk_total" placeholder="Total Amount"></td>
                        <td><input id="bulk_paidby_user_id" type="text" name="bulk_paidby" placeholder="Paid By"></td>
                        <td><input id="bulk_status" type="text" name="bulk_status" placeholder="Payment Status"></td>
                        <td><input id="bulk_notes" type="text" name="bulk_notes" placeholder="Notes"></td>
                    </tr>
                </thead>
                <tbody>
                    <expense v-for="expense in expenses"
                        :key="expense.id"
                        :expense="expense"
                        @expense-deleted="deleteExpense($event)">
                    </expense>
                </tbody>
            </table>
        </form>
    </div>
</template>
<script>
    // import CommentsManager from './CommentsManager.vue';
    var axios = require("axios");
    import lookup from './Lookup.vue';
    import expense from './BulkExpense.vue';
    export default {
        components: {
            lookup, expense
        },
        data: function() {
            return {
                modellink: {
                   "name": "n/a",
                   "description": "",
                   "id": null,
                   "model": "n/a"
                },
                bulk_trip: {
                   "name": "n/a",
                   "description": "",
                   "id": null
                },
                selectName: "",
                bulk_name: "",
                bulk_expensed_at: "",
                bulk_type: "",
                bulk_tags: "",
                bulk_vendor: "",
                bulk_method: "",
                bulk_total: "",
                bulk_paidby: {
                    name: "",
                    id: ""
                },
                bulk_status: "",
                bulk_notes: "",
                expense: {
                        id: 1,
                        name: "",
                        expensed_at: "",
                        type: {
                            id: "",
                            name: ""
                        },
                        trip: {
                            id: "",
                            name: ""
                        },
                        tags: [],
                        vendor: "",
                        method: "",
                        total: "",
                        paidby: {
                            id: "",
                            name: ""
                        },
                        status: {
                            id: "",
                            name: ""
                        },
                        notes: ""
                    },
                expenses: [
                    {
                        id: 1,
                        name: "",
                        expensed_at: "",
                        type: {
                            id: "",
                            name: ""
                        },
                        trip: {
                            id: "",
                            name: ""
                        },
                        tags: [],
                        vendor: "",
                        method: "",
                        total: "",
                        paidby: {
                            id: "",
                            name: ""
                        },
                        status: {
                            id: "",
                            name: ""
                        },
                        notes: ""
                    }
                ]
            };
        },        
        created() {
            this.fetchExpenses();
        },
        methods: {
            // onSelectedLink: function (talink) {
            //     // alert(JSON.stringify(talink.description, null, 4));
            //     this.modellink = talink
            // },
            onSelectedTrip: function (talink) {
                // alert(JSON.stringify(talink.description, null, 4));
                this.bulk_trip = talink
                this.modellink = talink
            },
            fetchExpenses() {
                axios.get('/api/expense').then((res) => {
                    //alert(JSON.stringify(res.data[0], null, 4));
                    this.expenses = res.data;
                });
            },

            createExpense() {
                axios.post('/api/expense', {name: this.expense.name, vessel_id: Laravel.vesselId, expensed_at: this.expense.expensed_at })
                    .then((res) => {
                        this.expense.content = '';
                        // this.expense.user_id = Laravel.userId;
                        // this.task.statuscolor = '#ff0000';
                        this.edit = false;
                        this.fetchExpenses();
                    })
                    .catch((err) => console.error(err));
            },
            deleteExpense(expense) {
                console.log(expense.id);
                alert(expense.id);
                axios.delete('/api/expense/' + expense.id)
                    .then((res) => {
                        this.fetchExpenses()
                    })
                    .catch((err) => console.error(err));
            },
        }
    }
</script>

BulkExpense.vue

<template>
    <tr>
        <td><input v-model="expense.name" type="text" name="name"></td>
        <td><input v-model="expense.expensed_at"></td>
        <td v-if="expense.type"><input v-model="expense.type.name"></td>
        <td>
            <trip-select  v-bind:tripId="expense.trip_id" selectName="trip_id"></trip-select>
        </td>
        <td><input v-model="expense.vendor"></td>
        <td><input v-model="expense.method"></td>
        <td><input v-model="expense.total"></td>
        <td v-if="expense.paidby"><input v-model="expense.paidby.name" ></td>
        <td v-if="expense.status"><input v-model="expense.status.name" ></td>
        <td><input v-model="expense.notes"></td>
        <td>
            <a class="text-lighter hover-light"  v-on:click="deleteExpense" href="#"><i class="fas fa-trash"></i></a>
        </td>
    </tr>
</template>
<script>
    import TripSelect from './TripSelect.vue';
    import typeahead from './Typeahead.vue';
    export default {
      name: 'expense',
      components: { TripSelect, typeahead },
      props: {
            bulk_name: {
                type: String,
                required: false
            },
            expense: {
                required: true,
                type: Object,
                default: function () {
                  return {
                        id: 1,
                        name: "",
                        expensed_at: "",
                        type: {
                            id: "",
                            name: ""
                        },
                        trip: {
                            id: "",
                            name: ""
                        },
                        tags: [],
                        vendor: "",
                        method: "",
                        total: "",
                        paidby: {
                            id: "",
                            name: ""
                        },
                        status: {
                            id: "",
                            name: ""
                        },
                        notes: ""
                  }
                }
            }
        },
        data() {
            return {
            }
          },
          methods: {
            deleteExpense() {
                alert(this.expense.id)
                this.$emit('expense-deleted', {
                    'id': this.expense.id,
                });
            }
          }
    }
</script>

Solution

  • While it may not directly answer your question, this will for sure work. Also, this solves a fundamental problem you have which is an example of an anti-pattern.

    Instead of creating another method inside of your component that emits to the parent, just emit from the click handler in the child. Also, just send the expense back and forth, so you're not property-juggling:

    No need to declare $event as you can already have it by default if you want it.

    <expense v-for="expense in expenses" :key="expense.id" :expense="expense" @expense-deleted="deleteExpense"></expense>
    

    Then in your click handler, just emit the action and pass the expense back:

    <a class="text-lighter hover-light"  
        @click="$emit('expense-deleted', expense)" 
        href="#">
        <i class="fas fa-trash"></i>
    </a>
    

    This will address those fundamental problems above and likely your issue in the process.

    Also I see you mixing v-on:click and @click, but they're the same thing, one is just shorthand for the other. I'd suggest just @click for cohesion.

    Finally, here's an MCVE for this approach.

    Edit

    Just in case you're curious, you can delete it from your existing collection after your promise resolves by doing this:

    this.expenses.slice(this.expenses.findIndex(o => o.id === expense.id), 1)
    

    Since our expense will always exist in our collection, we never have to worry about checking for existence, so you can use a one liner.

    Or you can replace the entire array, but you'll need to use Vue.set for this:

    Vue.set(this, 'expenses', this.expenses.filter(o => o.id !== expense.id))