Search code examples
javascriptvue.jsvuex

pass method value to another component


i have a method in my Component IncomeList, which contains the method sumValue. This method just added different numbers together and output one value, like 3+5 = 8. Same in my other Component OutputList, the same logic is used but the method is called sumValueOutput. Now i want to use both values in another component, which is called WinTotal. I tried something with props and vuex, but till now i have no working product and i also dont know how to start. Thanks for helping!

IncomeList:

<template>
<div class="container-income">
    <button class="btn btn-info" @click="showModal">show modal</button>
    <div class="hidden-container-income" id="test123">
    <input type="text" class="income-input" placeholder="What needs to be done" v-model="newIncome" @keyup.enter="addincome">
    <input type="text" class="value-input" placeholder="€" v-model="newIncomeValue" @keyup.enter="addValue">
    <transition-group name="fade" enter-active-class="animated fadeInUp" leave-active-class="animated fadeOutDown">
        <income-item v-for="income in incomesFiltered" :key="income.id" :income="income"
                   @removedIncome="removeincome">
        </income-item>
    </transition-group>

    <div class="extra-container">
        <div><label><input type="checkbox" style="display: none" :checked="!anyRemaining" @change="checkAllincomes"></label></div>
        <div>{{ remaining }} elements</div>
    </div>
    <div class="sum-container">
        <div><label> Total Income: </label></div>
        <div>{{ sumValue }} €</div>
    </div>
  </div>
</div>
    </template>
    <script>
    import IncomeItem from './IncomeItem'
    export default {
    name: 'income-list',
    components: {
        IncomeItem,
    },
    data () {
        return {
            newIncome: '',
            newIncomeValue: '',
            idForincome: 3,
            incomes: [
                {
                    'id': 1,
                    'title': 'Finish Vue Screencast',
                    'value': 300,
                    'completed': false,
                    'editing': false,
                },
                {
                    'id': 2,
                    'title': 'Take over world',
                    'value': 315,
                    'completed': false,
                    'editing': false,
                },
                {
                    'id': 3,
                    'title': 'Excellent',
                    'value': 313,
                    'completed': false,
                    'editing': false,
                },
            ]
        }
    },
    computed: {
        remaining() {
            return this.incomes.filter(income => !income.completed).length
        },
        anyRemaining() {
            return this.remaining != 0
        },
        incomesFiltered() {
            return this.incomes
        },
        sumValue() {
            return this.incomesFiltered.reduce((a, c) => a + c.value, 0)
        },
    },
    methods: {
            addincome() {
                if (this.newIncome.trim().length == 0) {
                    return
                }
                this.incomes.push({
                    id: this.idForincome,
                    title: this.newIncome,
                    value: this.newIncomeValue,
                    completed: false,
                })
                this.newIncome = ''
                this.newIncomeValue = ''
                this.this.idForincome++
            },

        removeincome(id) {
            const index = this.incomes.findIndex((item) => item.id == id)
            this.incomes.splice(index, 1)
        },
        checkAllincomes() {
            this.incomes.forEach((income) => income.completed = event.target.checked)
        },
        clearCompleted() {
            this.incomes = this.incomes.filter(income => !income.completed)
        },
        finishedEdit(data) {
            const index = this.incomes.findIndex((item) => item.id == data.id)
            this.incomes.splice(index, 1, data)
        },
        //Same for Value
        addValue() {
            if (this.newIncomeValue.trim().length == 0) {
                return
            }
            this.incomes.push({
                id: this.idForincome,
                title: this.newIncome,
                value: this.newIncomeValue,
                completed: false,
            })
            this.newIncome = ''
            this.newIncomeValue = ''
            this.this.idForincome++
        },
        showModal () {
            if (document.getElementById('test123').style.display == 'none' ) {
                document.getElementById('test123').style.display = 'block';
            }
            else {
                
                
document.getElementById('test123').style.display = 'none'
            }
        },
    },
};
                               </script>

OutputList:

<template>
<div class="container-output1">
    <button class="btn btn-info1" @click="showModal">show modal</button>
    <div class="hidden-container-output1" id="test1231">
        <input type="text" class="output-input1" placeholder="What needs to be done" v-model="newOutput" @keyup.enter="addoutput">
        <input type="text" class="value-input1" placeholder="€" v-model="newOutputValue" @keyup.enter="addValue">
        <transition-group name="fade" enter-active-class="animated fadeInUp" leave-active-class="animated fadeOutDown">
            <output-item v-for="output in outputsFiltered" :key="output.id" :output="output"
                         @removedoutput="removeOutput">
            </output-item>
        </transition-group>

        <div class="extra-container1">
            <div><label><input type="checkbox" style="display: none" :checked="!anyRemaining" @change="checkAlloutputs"></label></div>
            <div>{{ remaining }} elements</div>
        </div>
        <div class="sum-container1">
            <div><label> Total Output: </label></div>
            <div>{{ sumValueOutput }} €</div>
        </div>
    </div>
</div>
   </template>

   <script>
import OutputItem from './OutputItem'
export default {
    name: 'output-list',
    components: {
        OutputItem,
    },
    data () {
        return {
            newOutput: '',
            newOutputValue: '',
            idForOutput: 3,
            outputs: [
                {
                    'id': 1,
                    'title': 'Finish Vue Screencast',
                    'value': 300,
                    'completed': false,
                    'editing': false,
                },
                {
                    'id': 2,
                    'title': 'Take over world',
                    'value': 315,
                    'completed': false,
                    'editing': false,
                },
                {
                    'id': 3,
                    'title': 'Excellent',
                    'value': 311,
                    'completed': false,
                    'editing': false,
                },
            ]
        }
    },
    computed: {
        remaining() {
            return this.outputs.filter(output => !output.completed).length
        },
        anyRemaining() {
            return this.remaining != 0
        },
        outputsFiltered() {
            return this.outputs
        },
        sumValueOutput() {
            var outputValue = this.outputsFiltered.reduce((a, c) => a + c.value, 0);
            return outputValue;
        },
    },
    methods: {
        addOutput() {
            if (this.newOutput.trim().length == 0) {
                return
            }
            this.outputs.push({
                id: this.idForOutput,
                title: this.newOutput,
                value: this.newOutputValue,
                completed: false,
            })
            this.newOutput = ''
            this.newOutputValue = ''
            this.this.idForOutput++
        },

        removeOutput(id) {
            const index = this.outputs.findIndex((item) => item.id == id)
            this.outputs.splice(index, 1)
        },
        checkAlloutputs() {
            this.outputs.forEach((output) => output.completed = event.target.checked)
        },
        clearCompleted() {
            this.outputs = this.outputs.filter(output => !output.completed)
        },
        finishedEdit(data) {
            const index = this.outputs.findIndex((item) => item.id == data.id)
            this.outputs.splice(index, 1, data)
        },
        //Same for Value
        addValue() {
            if (this.newOutputValue.trim().length == 0) {
                return
            }
            this.outputs.push({
                id: this.idForOutput,
                title: this.newOutput,
                value: this.newOutputValue,
                completed: false,
            })
            this.newOutput = ''
            this.newOutputValue = ''
            this.this.idForOutput++
        },
        showModal () {
            if (document.getElementById('test1231').style.display == 'none' ) {
                document.getElementById('test1231').style.display = 'block';
            }
            else {
                document.getElementById('test1231').style.display = 'none'
            }
        }

    }
}
            </script>

Solution

  • There are two ways I can think of immediately that should work for you:

    1) Pass data using a Bus

    In Vue you can pass data between a child component and its parent fairly easily. In the child component you emit the data you want to send, as an event:

    this.$emit('event-name', payload)
    

    ...and, in the parent, you listen for the event, and do something with the data:

    <child-component @event-name="doSomething($event)" />
    

    But passing data between two components becomes trickier. You can pass data from a component to component through a child-parent-child strategy, but that becomes tedious. Instead, what you can do is create a Bus: an instance of Vue which you import in both child components and emit events directly between the two.

    A Bus is easy to make; requiring all but two lines of code:

    // Bus.js
    import Vue from "vue";
    
    export const Bus = new Vue();
    
    

    So in your child component(s), (IncomeList and OutputList) you import the Bus:

    import { Bus } from "@/Bus";
    

    ...and emit an event with the value of sumValue (in IncomeList)

    sumValue() {
        const sumVal = this.incomesFiltered.reduce((a, c) => a + c.value, 0);
        Bus.$emit("sumvalue-change", sumVal);
        return sumVal;
    }
    

    ...and sumOutputValue (in OutputList):

    sumValueOutput() {
        const outputValue = this.outputsFiltered.reduce((a, c) => a + c.value, 0);
        Bus.$emit("sumvalueoutput-change", outputValue);
        return outputValue;
    }
    

    Then in WinTotal.vue, you import the Bus and listen for these events, in the component's created hook:

    created() {
        Bus.$on("sumvalue-change", (sumvalue) => (this.sumValue = sumvalue));
        Bus.$on("sumvalueoutput-change", (sumvalueoutput) => (this.sumValueOutput = sumvalueoutput));
    }
    

    You can now use the sumValue and sumValueOutput in WinTotal however you want, and they'll be automatically updated whenever they change in either IncomeList or OutputList.

    emit-demo-working

    Here's a sandbox demonstrating this solution.

    HOWEVER, there's a downside to this method. Say your App.vue looks like this:

    // App.vue
    <template>
      <div id="app">
        <IncomeList />
        <OutputList />
        <WinTotal />
      </div>
    </template>
    

    In this case both IncomeList and OutputList are rendered before WinTotal, so the initial values of sumValue and sumValueOutput are emitted before WinTotal has even begun listening for these events. Thus, WinTotal never receives the initial values of sumValue and sumValueOutput (see below).

    emit-demo-broken

    As you can see, WinTotal isn't updated initially with the values, but when they change, they are updated fine. A workaround is to put <WinTotal /> before <IncomeList /> and <OutputList />, but that's not always feasible. Sure you could get around this in other ways, but it'd likely be tedious to implement, and not worth the trouble. So, here's an alternative.

    2) Passing data using VueX

    A simpler solution would be to just use VueX. I'm assuming you know a bit about VueX already, since you've mentioned that you've tried something with it previously, but if there's anything unclear, let me know.

    Assuming VueX is set up already, in your store, create values in state for sumValueIncome and sumValueOutput, and mutations that update them:

    import Vue from "vue";
    import Vuex from "vuex";
    
    Vue.use(Vuex);
    
    export default new Vuex.Store({
      state: {
        sumValueIncome: null,
        sumValueOutput: null
      },
      mutations: {
        updatesumValueIncome(state, newvalue) {
          state.sumValueIncome = newvalue;
        },
        updatesumValueOutput(state, newvalue) {
          state.sumValueOutput = newvalue;
        }
      },
      actions: {},
      modules: {}
    });
    

    Then in IncomeList, you update sumValueIncome like in the previous solution (but with VueX, of course):

    sumValueIncome() {
      const sumValueIncome = this.incomesFiltered.reduce((a, c) => a + c.value, 0);
      this.$store.commit("updatesumValueIncome", sumValueIncome);
      return sumValueIncome;
    }
    

    ...and in OutputList:

    sumValueOutput() {
      const outputValue = this.outputsFiltered.reduce((a, c) => a + c.value, 0);
      this.$store.commit("updatesumValueOutput", outputValue);
      return outputValue;
    }
    

    In WinTotal, you can use the mapState helper to create computed properties with the dynamically updated values of sumValue and sumValueOutput:

    <template>...</template>
    <script>
    import { mapState } from "vuex";
    
    export default {
      name: "win-total",
      computed: mapState(["sumValueIncome", "sumValueOutput"]),
    };
    </script>
    

    ...and you're all set!

    vuex-demo

    Here's a sandbox demonstrating this solution.