Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom helpers could not be accessed directly inside Handlebars template #866

Open
kapooostin opened this issue May 29, 2016 · 28 comments
Open
Labels

Comments

@kapooostin
Copy link

  • assemble 0.12
  • assemble used inside gulpfile.js
  • when trying to use a custom helper I get {$ASYNCID$1$0$} in a console output.

My custom helper works correctly if I prefix its call with helpers..

@assemblebot
Copy link

@kapooostin Thanks for the issue! If you're reporting a bug, please be sure to include:

  • The version of assemble you are using.
  • Your assemblefile.js (This can be in a gist)
  • The commandline output. (Screenshot or gist is fine)
  • What you expected to happen instead.

If your issue is related to one of the following, please open an issue there:

  • grunt-assemble Issues with using assemble in grunt or the grunt-assemble library.
  • handlebars-helpers Issues with using handlebars helpers from the handlebars-helpers library.

@jonschlinkert
Copy link
Member

To be clear, do you mean middleware handlers? e.g. .handle? More details would be great

@kapooostin kapooostin changed the title Custom handlers could not be accessed directly inside Handlebars template Custom helpers could not be accessed directly inside Handlebars template May 29, 2016
@kapooostin
Copy link
Author

No, I meant template helpers (Handlebars helpers to be precise), but mistyped it. I corrected the title.

@jonschlinkert
Copy link
Member

got it, thx. can you add a code example? helper and template, etc...

@kapooostin
Copy link
Author

//gulpfile task
app.helper( 'test', options => options.data.root );
{{ log ( test 'test' ) }}

@kapooostin
Copy link
Author

kapooostin commented May 30, 2016

I rechecked and it seems the problem is in something else. I finished converting my helpers from legacy format. Now they are accessible without helpers. prefix. But somehow helpers do not work inside each instruction:

{{ log (getLayout) }} //prints an array as expected
{{#each (getLayout) as |block| }} //the loop never starts
 ...
{{/each}}

@jonschlinkert
Copy link
Member

thanks for the additional details, @doowb or I will check into this today

@kapooostin
Copy link
Author

kapooostin commented May 30, 2016 via email

@kapooostin
Copy link
Author

Another mystery for me here:

{{log data}} // prints out an object with a 'title' key
{{log (get 'title' data)}} // a helper from assemble/handlebars-helpers, outputs a correct value
{{log data.title}} // returns undefined

@kapooostin kapooostin reopened this May 30, 2016
@doowb
Copy link
Member

doowb commented May 31, 2016

@kapooostin is the code above your complete template? If any of those {{log}} helpers are inside a block helper or partial, the the context may be different and you'll need to use ../ syntax to go back up a level:

{{log ../data.title}}

You can learn more about handlebars paths here.

@kapooostin
Copy link
Author

No, these 3 lines are at the beginning of the template. None of them is inside a loop or partial. Well, actually the whole template is a partial if it matters.

@jonschlinkert
Copy link
Member

the whole template is a partial if it matters.

yes, that might matter

@kapooostin
Copy link
Author

kapooostin commented May 31, 2016

And data to this partial is passed from a helper.

{{> block-section
  data=(getContent block)
}}

@doowb
Copy link
Member

doowb commented Jun 1, 2016

@kapooostin I've been looking into this.

When using async helpers (like the getContent helper), an async id is returned when Handlebars does it's rendering. When the rendering is complete, we call the async helpers allowing them to execute in an async fashion, then replace the async ids with the results.

To ensure that the async ids are resolved correctly, we provide built in partial rendering helpers. Use the built in helpers instead of the {{> }} syntax like this:

{{partial "block-section" (getContent block)}}

Then inside your block-section partial use this instead of data to access the properties on the context:

{{log this}}
{{log (get 'title' this)}}
{{log this.title}}

If this doesn't work, then we have bug.

@kapooostin
Copy link
Author

kapooostin commented Jun 1, 2016 via email

@jonschlinkert
Copy link
Member

does it mean, that helpers are async by default?

yes, since .render is async, we have to ensure that values from all helpers are resolved correctly (@doowb can expand on that if necessary). the following might help...

Could we go the opposite way: render template
synchronously and make helpers synchronous?

We've been discussing this at lot.

When we first refactored assemble, we had both sync and async. but it was confusing and hard to maintain. We decided to go with async only, mainly because middleware handling was async, and we wanted to support consolidate. This meant that helpers would also have to support async rendering. Which is awesome, it works well usually, but there are also lots of edge cases cropping up.

Here is what we're thinking:

  • make all rendering/compiling sync by default
  • add support for sync middleware (not sure how or if this will work yet)
  • publish plugins to support async handling for .render, middleware and helpers, etc

Any feedback would be helpful

@kapooostin
Copy link
Author

Does async rendering support partial blocks?

{{#> myPartial }}
  Failover content
{{/myPartial}}

@kapooostin
Copy link
Author

kapooostin commented Jun 1, 2016

Here is what we're thinking:

make all rendering/compiling sync by default
add support for sync middleware (not sure how or if this will work yet)
publish plugins to support async handling for .render, middleware and helpers, etc
Any feedback would be helpful

I know almost nothing of async rendering so my feedback might be not very helpful. Is it possible to overload default partial syntax {{> }} with async support to make the transition easier?

@jonschlinkert
Copy link
Member

Is it possible to overload default partial syntax {{> }} with async support to make the transition easier?

I'm pretty sure @doowb just mentioned something similar. maybe he has some thoughts on that.

feedback might be not very helpful.

feedback is always helpful! especially since you have a fresh perspective

@kapooostin
Copy link
Author

Then inside your block-section partial use this instead of data to access the properties on the context

It worked, now I have to revamp all the partials to make it work. Maybe I'll try to prepare all the data before rendering to eliminate completely the use of helpers inside partials for data supplying.

@jonschlinkert
Copy link
Member

great, at least that part is working. let us know what you figure out, or if you have ideas for how to do it differently

@kapooostin
Copy link
Author

kapooostin commented Jun 1, 2016

great, at least that part is working. let us know what you figure out, or
if you have ideas for how to do it differently

I still do not know whether partial blocks will work or not.

@doowb
Copy link
Member

doowb commented Jun 1, 2016

Is it possible to overload default partial syntax {{> }} with async support to make the transition easier?

I was looking into this but it's harder than overriding helpers because handlebars users internal functions to handle partials. For now, using assemble's partial helper is a way to ensure that it will work.

I still do not know whether partial blocks will work or not.

I'm working on some documentation around explaining how async helpers work but the gist of it is:

Wrap helpers

Wrap all registered helpers with a function that replaces the original helper so when it's called it:

  • Creates a unique async id
  • Stashes arguments by the async id
  • Returns the async id

This just returns the async id during the handlebars rendering ending up with a string that goes from:

Upper: {{upper name}}

to:

Upper: {$ASYNCID$0$0$}

Resolve Ids

Now that the template has been rendering to a string containing async ids, the ids need to be resolved.

  • Find all async ids in string
  • For each async id
    • Resolve any async ids that were passed in as arguments.
    • Call the original helper passing in the original/resolved plus a callback function.
    • If the helper was registered as an async helper, wait for the results to be returned through the callback, otherwise, use the results returned from the function
    • Replace the async id in the string with the results

Caveats

There are a few things that we came across that drove the decisions on how async helper are used and how their ids are resolved:

  • Subexpressions in handlebars: (and passing return values to other helpers in ejs)
    To let an async helper be used as a subexpression, we have to resolve the async id that ends up being passed into the helper. This is why we wrap sync helpers too since we need to be able to resolve async ids that get passed in.
  • Built-in handlebars helpers: We recently created the assemble-handlebars-helpers library to wrap and re-register the built-in handlebars helpers like {{if}} and {{each}} so that possible async ids are resolved properly (this is why you see an async id when using the built-in {{log}} helper.
  • Subexpression results used as values for hash parameters: This is a case that we just found out about from your example above by doing {{partial "block-section" data=(getContents block)}}. What happens here is that the getContents helper returns an async id during handlebars rendering and assigns it to data. Then other helpers attempt to use a property on data which doesn't exist yet. We're working on a fix for this.
  • Working with the built-in partial syntax {{> }}: This usually works fine when used with named partials and passing in data directly from the context (not through a helper):
    • {{> block-section data=this.section}}
    • {{> (lookup "partial-name" this) data=(getContents section)}} does not work because we don't have a way to override the partial syntax handling and resolve the async id returned from lookup properly.
    • Keeping the above in mind, {#> myPartial }}Failover content{{/myPartial}} should work if none of the partial names or data context are passed from pulling the data from a helper. I haven't personally used that partial syntax, but I'll be testing it out soon to see how it reacts to async helpers.

After all of that, we're discussing ways to make async-helpers play nicely with the partial syntaxes. One way is to use the assemble {{partial}} helper and once the hash issue is fixed, you should be able to use the data inside your partials like normal.

@doowb doowb added the has-docs label Jun 1, 2016
@kapooostin
Copy link
Author

Thank you for a such detailed overview of async helpers. I need some time to absorb it.

@jonschlinkert
Copy link
Member

@doowb can you add that information to the docs?

@doowb
Copy link
Member

doowb commented Jun 30, 2016

Yeah, I'm working on it now.

@grayghostvisuals
Copy link

@doowb Has this info been added to the docs as of yet? Been over 1 year this ticket has been open

@DigitalKrony
Copy link

ok, I think I understand what this is doing, but where do I resolve the asyncID's from? After being rendered, where do they end up living so I can do a .replace()?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants