22
votes

I'm working on my first Ember.js app and am having some trouble connecting all the dots. It would be really helpful if I could just see all the variables available within a given handlebars template.

There is a related question, but you have to know the variable that is in scope to use it: How do I add console.log() JavaScript logic inside of a Handlebars template?

How can I output all the variables?

7
use a regexp to match the template sections and pluck the definitions.dandavis
can you give an example? I'm working with a demo from github.com/eviltrout/emberreddit.doub1ejack

7 Answers

11
votes

a good option is to debug the value of 'this' in a template using the Handlebars helpers: 1.

{{#each}}
    {{log this}}    
{{/each}}

or, 2. similar to @watson suggested

{{#each}}
    {{debugger}}
{{/each}}

and then drill in to the Local Scope Variables for 'this' in the Dev Tools

enter image description here

or alternatively, 3. you could log things directly from inside your Controller init method, such as:

App.UsersController = Ember.ArrayController.extend({
    init: function() {
        console.log(this);
        console.log(this.getProperties('.'));
    }
});
6
votes

Make sure you try out Firebug - you'll get a different perspective on things, which I found helpful. But don't abandon chrome completely; you will need the Ember Inspector at some point.

I'm using the same debugging helper everyone recommends, and this is how Chrome displays it:

Chrome inspector isn't very helpful

When I expand the same object in firebug, I get the following info, including the variables I was looking for (sources[]) and some other useful properties I hadn't seen in Chrome.

Firefox has more for me to work with

4
votes

I created Barhandles a few years ago. It will use the Handlebars parser to produce the AST, and then extract variable references from it. The extractSchema method will — well — extract a schema. That schema is not based on JSON Schema or Joi or anything. It's a homegrown format that captures most of the things you could possibly extract from Handlebars template.

So, this barhandlers.extractSchema('{{foo.bar}}') produces:

{
  "foo": {
    "_type": "object",
    "_optional": false,
    "bar": {
      "_type": "any",
      "_optional": false
    }
  }
}  

It will take into account that an {{#if expr}} will automatically make nested references optional. It correctly handles scope changes based on {{#with expr}} constructs, and it allows you to add support for your own custom directives as well.

We used it to do validation on the data structures that we passed into the template, and it was working pretty well for that purpose.

2
votes

If you really need to dump the variables in your template, you can explore the template AST and output the content of the relevant nodes (see the compiler sources). This is not an easy task because you have to find your way through trials and errors, and the code is quite low-level and there are not so many comments.

It seems Handlerbars doesn't have a shortcut for what you're asking, so the steps would be:

  1. precompile a template (see the command line source, I think the function is called handlebars.precompile())
  2. explore the AST
1
votes

The sample Ember app you mention defines its EmberObjects right in its app.js. So basically, what's available on the objects are the properties that are defined onto them there. (e.g. subreddit has a title, etc).

If you want a globally available way to dump an object's property schema out to the console, one approach would be to create a "debug" helper that walks the members of the passed-in contexts and writes them out. Something like:

Handlebars.registerHelper('debug', function (emberObject) {
    if (emberObject && emberObject.contexts) {
        var out = '';

        for (var context in emberObject.contexts) {
            for (var prop in context) {
                out += prop + ": " + context[prop] + "\n"
            }
        }

        if (console && console.log) {
            console.log("Debug\n----------------\n" + out);
        }
    }
});

Then call it on whatever you want to inspect:

<div>Some markup</div>{{debug}}<div>Blah</div>

This will use whatever EmberObject is in scope, so pop it inside of an {{#each}} if you want to inspect the list elements, as opposed to the object with that list.

0
votes

The variables available within a template are only constrained by the model you are using to render the template.

You should set a breakpoint in your app where you render the template and see what is in your model at that point, which will should you what you have available to put in your template.

0
votes

You can do this by leveraging Handlebars.parseWithoutProcessing which takes the input template string. If you use TypeScript, that returns a specific type hbs.AST.Program. You can filter for only the moustache statements, and then iterate through these statements to get the variable names.

This method also supports Handlebars helpers, so you can get the key for that, but because of this, this function is a bit more complex as you'd need to check different properties on the moustache statement:

/**
 * Getting the variables from the Handlebars template.
 * Supports helpers too.
 * @param input
 */
const getHandlebarsVariables = (input = '') => {
  const ast = Handlebars.parseWithoutProcessing(input);
  return ast.body
    .filter(({ type }) => type === 'MustacheStatement')
    .map((statement) => statement.params[0]?.original || statement.path?.original);
};

Here's the TypeScript version, which is a bit involved due to the conditional properties, but can help explain the types a bit more:

/**
 * Getting the variables from the Handlebars template.
 * Supports helpers too.
 * @param input
 */
const getHandlebarsVariables = (input: string): string[] => {
  const ast: hbs.AST.Program = Handlebars.parseWithoutProcessing(input);

  return ast.body.filter(({ type }: hbs.AST.Statement) => (
    type === 'MustacheStatement'
  ))
  .map((statement: hbs.AST.Statement) => {
    const moustacheStatement: hbs.AST.MustacheStatement = statement as hbs.AST.MustacheStatement;
    const paramsExpressionList = moustacheStatement.params as hbs.AST.PathExpression[];
    const pathExpression = moustacheStatement.path as hbs.AST.PathExpression;

    return paramsExpressionList[0]?.original || pathExpression.original;
  });
};

I've made a Codepen that illustrates this. Essentially, given the following template:

Hello, {{first_name}}! The lottery prize is {{formatCurrency prize_amount}}! Good luck!

It will use window.prompt to ask the user for their name and the prize amount. The example also implements a helper formatCurrency. You can see it here: https://codepen.io/tinacious/pen/GRqYWJE

enter image description here