Les Nightingill
Les Nightingill

Reputation: 6156

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.

Upvotes: 0

Views: 139

Answers (2)

Tipul07
Tipul07

Reputation: 141

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

Upvotes: 1

paulie4
paulie4

Reputation: 502

AFAIK, Ractive dependencies are based on data keypaths, so that's my guess as to why calling this.findAllComponents('listItem') does not add a dependency for it. I tried adding a call to this.get('list'), but that makes the computed get called before the new component is added. I know you can defer an observer until after the template rendering happens, so you might need to use an oberserver instead of a computed.

Upvotes: 0

Related Questions