Search code examples
ractivejs

Ractivejs: how to collect values from dynamically added components?


Ref: this jsbin

My component has a select box:

var ListItem = Ractive.extend({
  template:`<select value='{{color}}'>
            <option disabled selected value=null>select color</option>
            <option value='red'>red</option>
            <option value='green'>green</option>
            <option value='blue'>blue</option>
            </select>`
})

And the main app can dynamically add components by clicking 'Add':

var ractive = new Ractive({
  el:'#container',
  template:`<p on-click='@this.add_comp()'>Add</p>
            <p>{{colors}}</p>
            {{#list}}
            <listItem />
            {{/list}}`,
  data:{ list: []},
  computed:{
    colors(){
      var items = this.findAllComponents('listItem')
      cols=[]
      for(var i=0; i<items.length; i++){
        cols[i] = items[i].get('color')
      }
      return cols
    }
  },
  components:{ listItem: ListItem },
  add_comp(){ this.push('list',{})}
})

I need to collect the values of the components' select boxes into the parent as an array, e.g. ['red','green','blue'] But the array is not updating when components are added and colors selected.

Any insight you can offer would be much appreciated. Thanks in advance.


Solution

  • You should "encapsulate" your components by using their own data. Values from outside the component is passed as arguments to the component and as data is binded, you will always use your "parent" data, not the data inside the component.

    So this is the component:

    var ListItem = Ractive.extend({
      data: { color: "" },
      template:`<select value='{{color}}'>
                <option value=null>select color</option>
                <option value='red'>red</option>
                <option value='green'>green</option>
                <option value='blue'>blue</option>
                </select>`
    })
    

    As you can see it has it's own data block...

    Now we "inject" the data from our parent:

    var ractive = new Ractive({
      el:'#container',
      template:`<p on-click='@this.add_comp()'>Add</p>
                <p>{{colors}}</p>
                {{#list}}
                <listItem color="{{this.color}}" />
                {{/list}}`,
      data:{ list: []},
      computed:{
        colors(){
          var items = this.get('list');
          cols=[]
          for(var i=0; i<items.length; i++){
            cols[i] = items[i].color;
          }
          return cols
        }
      },
      components:{ listItem: ListItem },
      add_comp(){ this.push('list',{ color: "" })}
    })
    

    As you can see in "parent" component we pass a local value to the "child" component which is binded. Once we change color property of child component, list property in "parent" component will get updated.

    Another important piece is that computed colors() property contains a depency to list property using this.get('list') inside.

    I hope this answers your question.

    Best regards, Andy