25

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?

Community
  • 1
  • 1
doub1ejack
  • 10,627
  • 20
  • 66
  • 125

7 Answers7

13

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('.'));
    }
});
Elise Chant
  • 5,048
  • 3
  • 29
  • 36
6

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

doub1ejack
  • 10,627
  • 20
  • 66
  • 125
  • 1
    Firebug / Chrome Inspector show the same data, just in a different format. And, as @doub1ejack mentioned, there's a great (great) extension for the Chrome Inspector. I don't think the comments about Firebug are relevant to the post. Thanks for the help on the debug helper. – Bryan Rayner Mar 05 '14 at 17:06
  • A different format can make a huge difference, particularly when you're in an unfamiliar environment. I found firebug to be more informative (initially) for the reasons illustrated by these screenshots. I fully agree though, that the Ember Chrome extension is fantastic - it just wasn't helping me in the situation from the OP. – doub1ejack Mar 05 '14 at 18:52
  • Good edit. The post is much less biased now :) You've convinced me to try Firebug again, thanks. – Bryan Rayner Mar 05 '14 at 20:31
  • Ember Inspector is now available as a Firefox Add-on: [**Ember Inspector** for Firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) – Mikeumus Dec 22 '14 at 11:01
5

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.

Wilfred Springer
  • 10,869
  • 4
  • 55
  • 69
3

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

Tina
  • 1,186
  • 10
  • 11
2

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
Raffaele
  • 20,627
  • 6
  • 47
  • 86
1

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.

mcw
  • 3,500
  • 1
  • 31
  • 33
  • I like this approach. What is the period for in `{{debug .}}`? I've tried this and so far I haven't seen any useful variables displayed. I seem to see the same 5 strings printed out each time: `view`, `buffer`, `isRenderData`, `keywords` and `insideGroup`. I have added a property to my model and am expecting to at least see that.. – doub1ejack Nov 07 '13 at 21:21
  • 1
    `.` represents the current context object - it's analogous to `this` for your context. You can use `..` to go one level "out" (not up!), and get the context of the wrapper. – mcw Nov 08 '13 at 15:08
  • This looks like though that you're getting the `options.data`, rather than the `context`, in your template - those are all Ember properties. Are you possibly invoking your template without passing your object in? – mcw Nov 08 '13 at 15:10
  • Take a look at `view.js`, line `1183` from Ember: https://github.com/emberjs/ember.js/blob/6a294705bcdd732a06b45574943c3b40831f5a0c/packages/ember-views/lib/views/view.js The `render` function is going to take your template and pass it the `context`, and that data object as the `options.data`. – mcw Nov 08 '13 at 16:22
  • Well.. I don't know. I'm not explicitly passing any objects to templates (I don't think) - I assumed Ember was doing that for me. With this example (https://github.com/eviltrout/emberreddit/blob/master/js/app.js), what changes would you suggest? – doub1ejack Nov 08 '13 at 18:32
  • I modified the code to work with your sample app. Note that now you just call `{{debug}}` without the `.` param, since you're being passed the EmberObject automatically. – mcw Nov 08 '13 at 20:13
0

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.

dezman
  • 18,087
  • 10
  • 53
  • 91
  • This sounds good (would much prefer to be able to use an inspector). I don't know where to put a break point though. I'm exploring a sample project from https://github.com/eviltrout/emberreddit. It doesn't have explicit views (I assume that is where the template is rendered) and I can't put a breakpoint in the templates themselves. Where should I be looking? – doub1ejack Nov 06 '13 at 15:47
  • 1
    @doub1ejack Use the Handlebars debugger helper to set a breakpoint inside the template like ``{{debugger}}``, and then check the Local Scope Variables in your Sources Tab of Chrome Dev Tools. – Elise Chant Jun 23 '14 at 09:58
  • @EliseChant Wow, I didn't know you could do {{debugger}}, that's great to know. – dezman Jun 23 '14 at 18:09