Skip to content

Method signatures for template helpers #45

@patrickarlt

Description

@patrickarlt

I've put quite a bit of work into making Acetates existing helper API as simple as possible. Mostly by removing things from what you can do with Nunjucks. Right now you can do this.

// create a link helper that will also add a .active 
// class to the linked page is also teh current page
acetate.helper('link', function(context, url, text){
  // context is the current local variables on the page
  var className = context.url === url ? 'active' : 'inactive';

  // build a relative url to the page we are
  // linking to using the relativeUrl variable
  var relativeUrl = context.relativePath + url;

  // Nunjucks template for the link
  var template = '<a href="{{relativeUrl}}" class="{{className}}">{{text}}</a>';

  // finally render the nunjucks string with the variables
  return acetate.nunjucks.renderString(template, {
    relativeUrl: relativeUrl,
    className: className,
    text: text
  });
});

and use it in your templates like {% link "/", "Home" %}.

You can also do the same thing for blocks. This API is simple but also leaves out some important parts that a few people like have already run into these are.

  • Named arguments. Nunjucks lets you do named args so things like the following things {% link "/", "Home" class="main-link" %} keyword args are passed as an object in the custom tag API
  • Async helpers Nunjucks can wait for async operations like reading external files in custom tags.

The main reason that both of these things do not exist is that they make the method signature for helper functions sort of terrible. Consider our humble {% link %} helper above with the async and keyword args added it would look like this:

acetate.helperAsync('link', function(context, url, text, keywordArgs, callback) {
  // return a string
});

This means that every helper function always gets between 2 and 3 arguments (keywords and page context and maybe the callback fro async helpers) plus anything the user might want to pass in. This has a really bad code smell but I would like to support both those features. Any ideas on what this might look like?

The only thing I can think of is to make the method signature be flexible with a few rules.

  1. Callback (for asyncHelper and asyncBlock is always the last parameter)
  2. options (Nunjucks calls them keyword args) are aways the last parameter (helper and block) or the second to last (asyncHelper and asyncBlock) IF named arguments are detected.
  3. context is always first.
  4. Everything else get stuck in the middle.

This would kind of be a mess but allows for more concise method sigs.


Another option would be to do something like:

acetate.helperAsync('link', function(context, args, options, callback) {
  var url = args[0];
  var text = args[1];

  // return a string
});

This would simple pass all unnamed arguments as an array (args) array and all named arguments as options. I'm not sure if I would want anything to optional here so you could use just args or options but I'm open to it either way.


The third option would be to not support Nunjucks flavor of keyword/named args and just add asyncHelper and asyncBlock with the callback as the last param. If you make some params optional so be it.

acetate.helperAsync('link', function(context, url, text, callback) {
// if you make text optional be prepared because the callback will always be passed as a the last param.
callback('string');
});

If people want to do keyword args on their own they can pass an object literal and handle it themselves.

{% link '/', 'Home', {class:'main-nav'} %}

I'm mostly in favor of taking the third option and adding asyncHelper and asyncBlock because it does not involve any breaking changes and keeps the APIs separate and simple.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions