patrickkettner / grunt-compile-handlebars

grunt plugin to compile static html from a handlebars plugin
MIT License
115 stars 29 forks source link

Handlebars & 'globals' data not accessible from helper files #49

Closed jenniferlsharps closed 9 years ago

jenniferlsharps commented 9 years ago

I'm trying to access Handlebars.partials inside of a helper, but I can't access Handlebars in my helper (it's undefined).

My workaround right now is to require Handlebars within my helper (as I was trying to follow the source code, and it seems this is how it's been implemented), but this has gotten messy as well since the path to Handlebars instance is at a location not relative to my helper files, but relative to grunt files.

I thought I could make it work by setting some global data in the grunt task which sets the handlebars path, but the global variable was also not available in my helper file unless the helper was called from the root context! To get around this, my grunt task is actually writing a JSON file with my global data into a directory accessible to the helper files. This is not ideal! My code looks something like this (where '../global' is a file containing my global json data written on the fly):

var handlebarsPath = require('../global')['handlebarsPath']; var Handlebars = require(handlebarsPath);

Am I missing something? There surely should be some way for my helpers to access:

  1. the Handlebars instance
  2. 'globals' data, regardless of context
patrickkettner commented 9 years ago

hey @jenniferlsharps!

what does your config look like?

jenniferlsharps commented 9 years ago

Thanks for the response! My config looks like this:

'compile-handlebars': {
    uniqueId: {
            template: inputPath + 'static/**/*.hbs',
            templateData: inputPath + 'static/**/*.json',
            output: outputPath + '**/*.html',
            helpers: inputPath + 'helpers/**/*.js',
            partials: inputPath + 'partials/**/*.hbs'
    }
}

Before, I was also setting:

globals: [
     {translations: translations},
     {handlebarsPath: handlebarsPath}
]

But I've since removed it & written that data to a json file for my workaround

patrickkettner commented 9 years ago

super sorry it took so long to get back to you

from the readme, you can use the handlebars config option to pass a path to your own version of handlebars to be used

handlebars - a string representing the path to an instance of handlebars (if you don't want to use the bundeled version). Note: This cannot be require('handlebars'), as that creates a circular reference. You need to pass the path to the instance you want to use, i.e. handlebars: "./node_modules/handlebars"

im a bit confused on what you are trying to do with your globals - you are saying that translations is undefined in your template when you try to run it?

ghost commented 9 years ago

No worries.

Nope, the problem is not in the template, but in the helpers. 'translations' is undefined in my helper, as is 'Handlebars' itself. I have a 'translate' helper which needs to utilize the 'translations' json in order to perform translations.

I don't have a problem with using a different instance of handlebars to kick off the compilation, I only have a problem with the helpers having access to said instance without the workaround mentioned above.

Thanks! Jenny

patrickkettner commented 9 years ago

@jenny-lynn-sharps what does your helper definition look like?

jenniferlsharps commented 9 years ago

One of them looks like this, which is simply used to render a specific type of partial:

module.exports = function (options) {
    var type = options.hash.params.type;
    var handlebarsPath = require('../global')['handlebarsPath'];
    var Handlebars = require(handlebarsPath);
    var partials = Handlebars.partials;

    if(typeof partials['forms/fields/'+type] !== 'function') {
        partials['forms/fields/'+type] = Handlebars.compile(partials['forms/fields/'+type]);
    }

    return partials['forms/fields/'+type](options.hash.params);
};
patrickkettner commented 9 years ago

That is not a helper. Handlebars helpers are just functions that are passed to registerHelper

function each(context, options) {
  var ret = "";

  for(var i=0, j=context.length; i<j; i++) {
    ret = ret + options.fn(context[i]);
  }

  return ret;
};

module.exports = each;

what is it you are trying to accomplish in that helper?

jennysharps commented 9 years ago

I'm not sure what makes you think this is not a helper--my function is essentially the same as what you just posted. You're just setting yours to module.exports after it's declared as a named function. Please let me know if I'm missing something? I copied the structure here: https://github.com/patrickkettner/grunt-compile-handlebars/blob/master/test/helpers/super_helper.js. The problem is not that my function doesn't work as a helper--it works exactly as expected. The problem is only that my global variables & Handlebars instance are unavailable within the context of the helper.

What this helper is accomplishing is displaying a particular partial. It's a helper for rendering different field types for a form. I just pass in the type & params & pass them to my handlebars partial. It works as expected, but I would merely prefer not to have to include the workarounds I mentioned.

Thanks, Jenny

patrickkettner commented 9 years ago

It is not a helper because you are requiring a common js module inside of it, as well as compiling a template. That is not what a helper is for. A helper modifies strings and then returns them. It should be a straight up function that does not have any external dependencies.

If all it is doing is showing a partial, then you should just use a partial in your configuration

jennysharps commented 9 years ago

I don't see it stated anywhere that a helper can't be used to render a partial, & don't see any reason why a helper registered to Handlebars should have a problem having a dependency on itself--but in any case, fair enough.

I'm using this helper to render my partial because I want to abstract the logic from the template. Rather than having to determine the path of the template I could call, I would have the ability to pass in a parameter and set that within the helper. But I can appreciate your point nonetheless.

Thanks for the responses, Jenny

patrickkettner commented 9 years ago

I don't see it stated anywhere that a helper can't be used to render a partial,

From the handlebars docs

Block helpers make it possible to define custom iterators and other functionality that can invoke the passed block with a new context.

rendering a partial would fall outside of that use case. There isn't an explicit message saying it isn't done, but I am 100% certain that you should not be calling Handlebars.compile (or Handlebars.anything, for that matter) inside of a helper.

don't see any reason why a helper registered to Handlebars should have a problem having a dependency on itself

it is a technical possibility, however the helper has no guarantee of the working directory. You would have to pass in the full path to the module you want to load. That being said, it is outside of normal use case which is just text manipulation

I'm using this helper to render my partial because...

Understood, and thats is a really neat idea, but it is a hack. The only way I can think to accomplish what you are doing is how you are doing it. as a result, im gonna close this as a wontfix/notabug

cheers!

mormubis commented 9 years ago

What the hell? She is completely right. I have same problem here.

For instance, I want to use Handlebars.SafeString and some partials for some reasons and I can't. You should reconsider this issue.

patrickkettner commented 9 years ago

@adelarosab is there a reason you can't do as @jenniferlsharps did and just require handlebars? Or just do the SafeString yourself

also - I am doing this for free - cursing at me doesn't exactly make me want to help.

mormubis commented 9 years ago

I ain't ordering you to do this or cursing. Just saying that the issue isn't completely insane. If you analyze what we asking, you'll see is some reasonable.

PS: I solved this using custom handlebars instance. In this way I can use Handlebars.partials or Handlebars.helpers.

patrickkettner commented 9 years ago

Im not saying its unreasonable, I am saying that it is not what partials are intended for. @kpdecker - am I mistaken in thinking that?

mormubis commented 9 years ago

Sure!, If you search about partials, you'll see they are very limited. Indeed, some people have done some helpers that allow to include partials with steroids.

They are not intended for, but until we have new tools for this purpose...

kpdecker commented 9 years ago

There isn't really any thing that prevents you from accessing partials within a helper as helpers just generate content to be rendered. That being said, it might make more sense for this specific case to use dynamic partials.

{{> (foo) }}

Will execute the helper named foo and it will render partial who's name is returned.

module.exports = function (options) {
  var type = options.hash.params.type;
  return 'forms/fields/'+type;
};
jenniferlsharps commented 9 years ago

Thanks @kpdecker! Hadn't realized this was possible. Definitely a better solution .