How to obtain children components inside Vue.js app

593 Views Asked by At

So, I have an app with multiple child components. Basically a spreadsheet.

I want to be able to calculate the sum of the components when any cell changes. I've figured out a way to store all the values of the cells by caching them when a change event is propagated. But, is this the best way? Is there a better way to dynamically grab the children? I understand props are the way to send data down, but how do I pull data up?

This is the HTML:

<html>

<head>

</head>

<body>

<span id="calculator">
<template v-for="i in 5">
<cell v-bind:index="i" v-on:total="total"></cell>
</template>
{{ subtotal }}

{{ cells }}
</span>

<script src="vue.js"></script>
<script src="app.js"></script>

</body>

</html>

And the app.js:

Vue.component( 'cell', {
  template: "<input v-model='value' v-on:change='total' size='10' type='text'/>",
  props: {
    index: Number
  },
  data: function() {
      return {
        value: 0
      };
  },
  methods: {
    total: function() {
      console.log( "Value is now: " + this.value + " for index: " + this.index )
      this.$emit( 'total', this.value, this.index )
    }
  }
});

var app = new Vue( {
  data: {
    subtotal: 0,
    cells: []
  },
  el: "#calculator",
  methods: {
    total: function( value, indexPlusOne )  {
      var index = indexPlusOne-1;
      var v =  parseInt( value );
      Vue.set( this.cells, index, v);
      console.log( "Inside the total function: " + v + " " + index );
      this.subtotal = 0;
      for( var i = 0; i < this.cells.length; i++ ) {
        if( this.cells[i] ) {
          this.subtotal += this.cells[i];
        } 
      }
    }
  }
});
1

There are 1 best solutions below

0
On BEST ANSWER

I understand props are the way to send data down, but how do I pull data up?

The best way is to use v-model for your custom cell component to pull data up.

Ref: https://v2.vuejs.org/v2/guide/components.html#Form-Input-Components-using-Custom-Events

As explained in the link above, <input v-model="something"> is a syntactic sugar for:

<input v-bind:value="something" v-on:input="something = $event.target.value">

So, your ideal solution would be like:

<cell v-model="item" v-for="item in all_cell_items"></cell>

From within the cell component, you may pass value back to parent (root) component by: this.$emit("input", newValue). The parent component (root) remains clean, and you may simply use a computed property for subTotal.

But this will not work if you have a plain list of integers, like this.cells = [1,2,3,4] and attempt to pass the values to cell component using v-model. You will get the following error:

[Vue warn]: : You are binding v-model directly to a v-for iteration alias. This will not be able to modify the v-for source array because writing to the alias is like modifying a function local variable. Consider using an array of objects and use v-model on an object property instead.

If you are ok to modify your this.cells to array of objects, then you have a clean way of doing it like:

<cell v-model="item.price" :label="item.name" v-for="item in all_items"></cell>

Here is a working jsFiddle for this example: https://jsfiddle.net/mani04/9b7n3qmt/