Search code examples
javascriptractivejs

How to dynamically parse a list of keypaths used within a Ractive.js template(s)


Is it possible to parse out a list of keypaths used by a dynamic partial / component?

If I start with a completely empty data object - and dynamically add a partial / component. Is possible to step through the dynamically added partial's html and discover which keypaths are used.

My intent is to then apply this list of keypaths to my data object on the fly. I'm building a drag and drop wysiwyg ui - and want designers to be able to add templates without touching ractive...

Here is some pseudo code which I hope illustrates what I'm trying to achieve.

<script id="foo" type="text/ractive">
   <p>{{blah}}</p>
   <p>{{blahblah}}</p>
</script>

-

var ractive = new Ractive({
  el: '#container',
  template: '{{#items}}{{partial}}{{/items}}',
  data: {
      items : [] // empty to start with
  }
});


ractive.on( 'addpartial', function ( event ) {
  // partial gets added
    // process the partial to find out what keypaths it contains.. put those keypaths into  array

var partialDataArray = [{'blah':''},{'blahblah':''}]

    this.push('items' , { 'partial' : 'foo', partialDataArray }
});

The other option would be to set each 'partial' as a component - but I'd be repeating myself loads (and I'm trying to be all DRY etc)

Cheers, Rob


Solution

  • This code borrows heavily from an example on dynamic components given by Martydpx https://github.com/martypdx - although I am unable to find the post where I found it.

    I've created a setup function that basically parses everything out for me. It means that I can supply a file with a long list of templates (to be used by components)

    <div data-component="first_component">
          <h1>{{h1}}</h1>
          <p>{{p1}}</p>
    </div>
    
    <div data-component="second_component">
          <p>{{p1}}</p>
    </div>
    

    -- and here is the JS. Edit - please see JavaScript regex - get string from within infinite number of curly braces for correct regex.

    var htmlSnippets = [];
    var setup = function() {
      // load in our ractive templates - each one is wrapped in a div with
      // a data attribute of component.
      $.get("assets/snippets/snippets2.htm", function(data) {
        var snippets = $.parseHTML(data);
        // Each over each 'snippet / component template' parsing it out.
        $.each(snippets, function(i, el) {
          if ($(el).attr('data-component')) {
            var componentName = $(el).attr('data-component')
    
            // reg ex to look for curly braces {{ }} - used to get the names of each keypath
            var re = /[^{]+(?=}})/g;
    
            // returns an array containing each keypath within the snippet nb: ['foo' , 'bar']
            var componentData = $(el).html().match(re); 
    
            // this is probably a bit messy... adding a value to each keypath...
            // this returns an array of objects.
            componentData = $.map( componentData, function( value ) {
                return { [value] : 'Lorem ipsum dolor sit amet' };
            });
            // combine that array of objects - so its a single data object
            componentData = componentData.reduce(function(result, currentObject) {
              for(var key in currentObject) {
                  if (currentObject.hasOwnProperty(key)) {
                      result[key] = currentObject[key];
                  }
              }
              return result;
            }, {});
            // and add the component name to this data object
            componentData['componentName'] = componentName;
            // We need to use the data elsewhere - so hold it here...
            htmlSnippets.push(componentData );
    
            // and lastly set our component up using the html snippet as the template
            Ractive.components[componentName] = Ractive.extend({
                template: $(el).html()
            });
          }
        });
    
        Ractive.components.dynamic = Ractive.extend({
            template: '<impl/>',
            components: {
                impl: function(){
                    return this.components[this.get('type')]
                }
            }
        });
    
      });
    }();
    
    
    var ractive = new Ractive({
      el: '#container',
      template: '#template',
      data: {
        widgets: htmlSnippets, 
        pageContent: []
      }
    });