Search code examples
meteorspacebars

Spacebars - Insert inline SVG icons with Template.registerHelper or UI.dynamic


I am making a template helper that will insert inline SVG icons into my templates. I want to pass an argument to add an additional CSS class (which will be used to change the default size, color, etc.).

What I Expect

<button> 
  <svg viewBox="0 0 78 73" class="svg-icon svgWhite" version="1.1" ...>
    <polygon id="icon-star" points="39 ... 60">
    </polygon>
  </svg>
</button>

What I Get

<button> 
  "[object Object]" 
  <svg viewBox="0 0 78 73" class="svg-icon" version="1.1" ...>
    <polygon id="icon-star" points="39 ... 60">
    </polygon>
    <!--[object Object]-->
  </svg>
</button>

My Template

<template name="whatever">
  <button>{{{icon.star svgWhite}}}</button>
</template>

My Template Helper

Template.registerHelper('icon', {
  star:   function (userClass, userWrapper) {
    var wrapper = (userWrapper == undefined) ? "i" : userWrapper;
    var addlClass = (userClass == undefined) ? "" : ", " + userClass;
    var svgWidth = 78;
    var svgHeight = 73;

    var svgCode = '<polygon id="icon-star" points="39 .... 60"></polygon>';

    var icon = '<'+wrapper+'>' +
      '<svg viewBox="0 0 ' + svgWidth + ' ' + svgHeight + '" ' +
      'class="svg-icon' + addlClass + '" ' +
      svgConfig + '>' + svgCode + '</' + wrapper + '>';

    return icon
  }
});

Reference: https://github.com/meteor/meteor/tree/devel/packages/spacebars#helper-arguments


Solution

  • Your template helper registration looks broken, I think you are trying to achieve a simple thing using an overcomplicated way.

    When you start using triple brackets syntax and writing HTML in your javascript, you know you are probably doing it wrong : you should probably use template inclusion to perform what you need.

    First, we define the parent template, it will include a child template that we can configure using attributes (these attributes will act as the data context of the child template).

    <template name="svgButton">
        <button type="button">
            {{> svgIcon class="svg-white"}}
        </button>
    </template>
    

    Then we define the child template, we can use attributes passed from the parent template.

    <template name="svgIcon">
        <svg viewBox="0 0 {{width}} {{height}}" class="svg-icon {{class}}">
            {{! warning, polygon is a self closing tag like img}}
            <polygon id="icon-star" points="..." />
        </svg>
    </template>
    

    We can define helpers that will either take the value of the data context if present, or a default value :

    Template.svgIcon.helpers({
      width:function(){
        return this.width || 78;
      },
      height:function(){
        return this.height || 73;
      }
    });
    

    So we may include the child template using this form for advanced customization :

    {{> svgIcon class="svg-whatever" width="16" height="16"}}
    

    EDIT :

    What happens when I have 20+ icon templates. Do I then have 20+ template helpers with repeatable functions (not DRY)?

    This is the approach I would use :

    First, define all your icon svg content as different templates.

    <template name="svgIconStarContent">
      <polygon id="icon-star" points="..."/>
    </template>
    
    {{! repeat 20 times...
        but this is DRY compliant because each time this is a different icon}}
    

    Then we can use UI.dynamic to include the correct template based on a parameter name.

    https://www.discovermeteor.com/blog/blaze-dynamic-template-includes/

    <template name="svgIcon">
        <svg viewBox="0 0 {{width}} {{height}}" class="svg-icon {{class}}">
            {{> UI.dynamic template=svgIconContent}}
        </svg>
    </template>
    

    Finally, in the parent template, we can pass the name of the template we want to insert dynamically so the content of the icon may vary when invoked with different values.

    {{> svgIcon svgIconContent="svgIconStarContent"}}