Add components dynamically

I am new to DoneJS and any help is appreciated. I am trying to build an App that loads components dynamically in a tab-based widget. Every time the route changes the app should insert the component dynamically in the DOM.
I tried to declare a static property called “tabs” in the AppViewModel that holds an array of components names, like this:

const AppViewModel = Map.extend('AppViewModel',
{
 tabs: []
},
{
  define: {
    message: {
      value: 'Hello World!',
      serialize: false
    }
  }
});
route(':page', { page: 'home' });

I also created a stache helper:

stache.registerHelper("tabsComponent", function(options){

  options.context.constructor.tabs.push(options.context.attr('page'));
  $.each(options.context.constructor.tabs, function(index, el) {
    template += ' <can-import from="system/'+el+'/" can-tag="loading"><'+el+' /></can-import>';
  });
  return stache(template)(this,options.helpers, options.nodeList);
});

And then in the index.stache file:

{{{tabsComponent}}}

And it works, each time the route changes the tabComponents helper is executed adding the current page to the tabs static array, loops the array and renders each component. But the problem is that each time a route changes all components in the static tabs array are rendered again. I want just the last route called to be imported and rendered, because, usually components are forms and a user could be filling a form but clicked on another route and the “state” of the filled form is lost.

Im not sure if this approach is correct or if this is a good practice, but basically i need that on each route change a new component is loaded, but without “losing” the previous one. I pretend to put each component in a tab widget. Any advise is appreciated. THANKS!

Yeah, I wouldn’t do it that way. First, I would use can-component to create components, not helpers: https://canjs.com/docs/can.Component.html. The DoneJS guide has you create components, if you haven’t already checked that out I suggest you do so: https://donejs.com/Guide.html#section=section_Generatecustomelements

As you said, dynamically doing this way causes re-renders. We have a tabs component already you can use if you want: https://github.com/bitovi-components/bit-tabs

If what you’re trying to accomplish is dynamically importing the tab panels, you can do this easily with bit-tabs already:

<bit-tabs>
  <bit-panel title="foo">
    <can-import from="foo/bar" can-tag="loading">
      <foo-bar></foo-bar>
    </can-import>
  </bit-panel>
</bit-tabs>

Hi matthewp,

Thanks for your reply. I think I did not explain it well. The issue is that component names and tag are variables based on the current route. What i did so far:

I define a tabs list attribute:

import List from "can/list/";

const AppViewModel = Map.extend(
{
  define: {
    tabs: {
      value: new List(),
      serialize: false,
      Type: List
    }
    
}

This list is initially empty and each time a route changes a function is fired:

setRoute: function() {

    var self = this;
    var componentName = '';
    var componentParams = {};
    var tagName = 'com';
    $.each(this.route.split('/'), function(index, val) {
      if (val.substr(0,1)==':') {
        var paramName = val.substr(1);
        componentParams[paramName] = self.attr(paramName);
      } else {
        componentName += val+'/';
        tagName += '-'+val;
      }
    });
    
    this.attr('tabs').push({ component:componentName, tag:tagName, bindings:{key: 'value'} });
    
  }

This function builds an object with 3 properties: componentName, tagName, bindings. Following the logic of this function and supposing i have a route like this:

route('books/list/:category')

if a url is changed to: http:/localhost:8080/books/list/5 this function will push this object to tab:

{ component: 'books/list', tag: 'com-books-list', bindings: {category: '5'} }

Then in the stache file im looping this list and rendering a helper passing the object, like this:

 {{#if tabs.length}}
    {{#each tabs}}
                        
         {{importComponent component tag bindings}}
                      
     {{/each}}
  {{/if}}

And finally the helper imports the component based on the parameters received: component, tag an bindings:

stache.registerHelper('importComponent',function(componentName, tagName, bindings) {
  
   var component = componentName();
   var tag = tagName();
   var bindings = bindings();

   return stache('<can-import from="src/'+component+'" can-tag="loading">'+'<'+tag+'/>'+'</can-import>')(this);
  // TODO: include the bindings

});

This way each time a URL changes, a new component is added to the list and rendered by the helper. With this approach just the last “pushed” objetct into the tabs List is rendered again with each URL change.

Am not sure if this a good practice or not, but i dont find another way to accomplish this behavior. Please each suggestion is really appreciated.

THANKS!

Just glancing at what you wrote … it looks very similar to bitballs’ routing:

it uses a pageComponent helper:

That uses a pageComponentConfig getter:

I actually did it after i saw bitball’s routing! It opened my mind! Is exactly the same, just with a little variant.
Im trying not to necessarily define a configuration object, instead make it dynamic defining a fixed nomenclature for component and tags based on the current route. Im not sure yet if this approach is gonna cover all my scenarios or requirements, but so far, it does.

If, in the future, i really need a custom behavior from a component i should create a configuration object for that component. And the setRoute function should first check if there is a configuration defined for that component.

Thanks Justin for share it! Im new to DoneJs, but the concept and ideas behind DoneJS are amazing!! It could not be better!
This is my first project with DoneJs, and definitely not the last one.