How to create Ractive's subcomponents dynamically and change them programmatically

1.4k views Asked by At

I can instantiate (sub)components manually using tags, but I don't know how to do it dynamically, or, how to insert and remove different components in the same area using tags.

Today I instantiate each (sub)component this way:

Ractive.load( '/templates/global/example.html' ).then( function ( Example )
{
       ractive.components.example = new Example( { el : 'aside' } );
});

But the new (sub)component can't see the data of it's parent instance in mustache, only his own data.

2

There are 2 answers

4
martypdx On BEST ANSWER

Here's a dynamic component:

Ractive.components.dynamic = Ractive.extend({
    template: '<component/>',
    components: {
        component: function() {
            return this.get('name');
        }
    },
    oninit: function(){
        this.observe('name', function(){
            this.reset();
        }, { init: false});
    }
});

Just pass in the name of the component it should implement:

<dynamic name='{{name}}'/>

See it in action below

Ractive.components.a = Ractive.extend({ template: 'I am A {{foo}}' });
Ractive.components.b = Ractive.extend({ template: 'I am B {{foo}}' });
Ractive.components.c = Ractive.extend({ template: 'I am C {{foo}}' });

Ractive.components.dynamic = Ractive.extend({
    template: '<component/>',
    components: {
        component: function() {
            return this.get('name');
        }
    },
    oninit: function(){
        this.observe('name', function(){
            this.reset();
        }, { init: false});
    }
});


var r = new Ractive({
    el: document.body,
    template: '#template',
    data: {
        foo: 'foo',
        list: ['a', 'b', 'c'],
        name: 'a'
    }
});
<script src="http://cdn.ractivejs.org/latest/ractive.js"></script>
<script id='template' type='text/ractive'>
   
    {{#each list}}
    <input type='radio' name='{{name}}' value='{{.}}'>{{.}}
    {{/each}}
    <br>
    <dynamic name='{{name}}'/>
       
</script>

0
Vikram On

Would like to upvote @martypdx's answer more than once. Inspired by his answer, I came up with something I'd like to share. It adds the following things:

  1. Create multiple instances of a type of Component (Ractive.extend)

  2. Component instances are alive and can receive messages from external listeners even if they have been unrendered.

Here is my fiddle: https://jsfiddle.net/vikikamath/z5otgzpL/9/

// Inspired by http://stackoverflow.com/a/31080919/405117
var mapper = {
 'A': function(instanceId){
   var handleAData;
   return Ractive.extend({ 
     template: 'I am A this is my data: <ul>{{#datas}}<li>{{.}}</li>{{/datas}}</ul>',
        data: {
         datas:[]
        },
        onrender: function() {
         handleAData = function(txt){
           this.push('datas', txt);
          }.bind(this);
          
         r.on(instanceId, handleAData);
        },
        onunrender: function() {
         r.off(instanceId, handleAData);
        }
   });
  }
  ,'B': function(instanceId){
   var handleBData;
   return Ractive.extend({ 
     template: 'I am B this is my data: <ul>{{#datas}}<li>{{.}}</li>{{/datas}}</ul>',
        data: {
         datas:[]
        },
        onrender: function() {
         handleBData = function(txt){
           this.push('datas', txt);
          }.bind(this);
          
         r.on(instanceId, handleBData);
        },
        onunrender: function() {
         r.off(instanceId, handleBData);
        }
   });
  }
  ,'C': function(instanceId){
   var handleCData;
   return Ractive.extend({ 
     template: 'I am C this is my data: <ul>{{#datas}}<li>{{.}}</li>{{/datas}}</ul>',
        data: {
         datas:[]
        },
        onrender: function() {
         handleCData = function(txt){
           this.push('datas', txt);
          }.bind(this);
          
         r.on(instanceId, handleCData);
        },
        onunrender: function() {
         r.off(instanceId, handleCData);
        }
   });
  }
  ,'D': function(instanceId){
   var handleDData;
   return Ractive.extend({ 
     template: 'I am D this is my data: <ul>{{#datas}}<li>{{.}}</li>{{/datas}}</ul>',
        data: {
         datas:[]
        },
        onrender: function() {
         handleDData = function(txt){
           this.push('datas', txt);
          }.bind(this);
          
         r.on(instanceId, handleDData);
        },
        onunrender: function() {
         r.off(instanceId, handleDData);
        }
   });
  }
}

/* arbitrarily select a component */
function pickRandomComponent() {
 return String.fromCharCode(Math.floor(Math.random() * Object.keys(mapper).length) + 65);
}

var DynamicComponent = Ractive.extend({
    template: '<component/>',
    components: {
        component: function() {
            return this.get('name');
        }
    },
    oninit: function(){
        this.observe('name', function(){
            this.reset();
        }, { init: false});
    }
});


var r = new Ractive({
    el: 'main',
    template: '#template',
    components: {
     dummy: Ractive.extend({ template: 'Welcome message' }),
     dynamic: DynamicComponent
    },
    data: {
        foo: 'foo',
        list: ['dummy'],
        name: 'dummy',
        textdata: ''
    },
    oninit: function() {
     this.on("sendDataToCurrentComponent", function() {
       r.fire(this.get('name'), this.get('textdata'))
      }.bind(this));
      
     this.on('addComponent', function(){
       var rndComponent = pickRandomComponent();
        var now = Date.now();
        var componentInstanceName = rndComponent + '-' + now
       this.components[componentInstanceName] = mapper[rndComponent](componentInstanceName)
       this.push('list', componentInstanceName);
      }.bind(this));
    }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/ractive/0.7.3/ractive.min.js"></script>
<script id='template' type='text/ractive'>
    <button on-click="addComponent">Add</button>
   <div>
     <input type="text" value="{{textdata}}" placeholder="Send Text to current component" width="900" on-blur="sendDataToCurrentComponent"/>
    </div>
    {{#each list}}
    <input type='radio' name='{{name}}' value='{{.}}'>{{.}}
    {{/each}}
    <br>
    <dynamic name='{{name}}'/>
       
</script>
<main></main>