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

support helpers #4

Merged
merged 3 commits into from
Jan 10, 2014
Merged

support helpers #4

merged 3 commits into from
Jan 10, 2014

Conversation

chevett
Copy link
Owner

@chevett chevett commented Jan 8, 2014

How about helpers like this? Will this break anything? Too hacky? It seems to work for my use case.

@kirbysayshi

@kirbysayshi
Copy link
Collaborator

Using the function name itself is a neat idea! But this will run on every template render, which will affect performance.

In addition, the arguments passed to the template function via link are technically internal. It worries me a bit that if I were to change the arguments order, or how templates could render, this would break.

The helpers you're assigning here are also per template instance. While this is neat, it breaks the current expectations of a helper being assigned to the Helpers prototype and thus global within a template. (Perhaps in the future helpers should be able to be added per template instance via an option.)

Have you tried using vash.compileHelper?

// entry.js
require('./helpers/firstName.vash');

var tmpl = require('./template.vash');
var tmpl2 = require('./template.vash');

As long as index.js can somehow know that firstName is a helper, it can call vash.compileHelper instead of vash.compile. module.vash would then be able to just call toClientString, and then the helper would be attached to vash.helpers on the included runtime.

Ideas for how to specify that a template is a helper include:

  • using the naming convention of path/to/template/nameblahblah.helper.vash (or helpers/* or configurable?)
  • Using a hook similar to vash.compileHelper, which looks for vash.helper.[name of helper] = function( (the right way to do this is using esprima like other transforms, but vash uses a regex (gasp!) to prevent having to bundle esprima)

@chevett
Copy link
Owner Author

chevett commented Jan 8, 2014

All valid points. I think the one that bothers me the most is having to know the particulars of your internal code.

So, ./helpers/firstName.vash would contain a javascript function and not a vash template, right? I'm confused about the helper ending in .vash. It appears that helpers can be either a function or another vash template? Is that correct?

Like we are really trying to support two things? Plain old vanilla helper functions and also helpers compiled from vash templates?

@kirbysayshi
Copy link
Collaborator

Well, a helper basically means that this when the function executes is an instance of vash.helpers.constructor (e.g. html within a rendering template). Vash has an added concept of a compiled helper, which allows a helper to be compiled and decompiled as a vash template itself.

A use case is if you wanted a helper that had some "complex" html in it:

<figure id="fig-1">
    <iframe
        style="width: 100%; height: 465px"
        src=""
        allowfullscreen="allowfullscreen"
        frameborder="0">
    </iframe>
    <figcaption>
        Fig. 1: Something about this figure
    </figcaption>
</figure>

If you had this in a helper, you'd have to do manual string concatenation and worry about html injection (or use the manual buffer api):

vash.helpers.imgfigure = function(num, url, caption) {
  return ''
    + '<figure id="fig-' + num + '">'
    + '    <iframe'
    + '        style="width: 100%; height: 465px"'
    + '        src="' + url + '"'
    + '        allowfullscreen="allowfullscreen"'
    + '        frameborder="0">'
    + '    </iframe>'
    + '    <figcaption>'
    + '        Fig. ' + num + ': ' + caption
    + '    </figcaption>'
    + '</figure>'
}

So a compiled helper lets you do this:

vash.helpers.imgfigure = function(num, url, caption) {
  <figure id="fig-@num">
      <iframe
          style="width: 100%; height: 465px"
          src="@url"
          allowfullscreen="allowfullscreen"
          frameborder="0">
      </iframe>
      <figcaption>
          Fig. @num: @caption
      </figcaption>
  </figure>
}

And you can get more advanced. An example is the helper used to generate the table of contents. It actually stores and reads values from the helpers instance to avoid the author needing to manually track footnote and entry indices.

Does that make sense?

@kirbysayshi
Copy link
Collaborator

Also, if you were to call toClientString on that helper, a few extra options get added at the end:

// in a JS console
> vash.helpers.imgfigure.toClientString()
vash.link( function anonymous(num,url,caption) {
var __vbuffer = this.buffer;
var model = this.model;
var html = this;
{
  __vbuffer.push('<figure id=\"fig-');
__vbuffer.push(html.escape(num).toHtmlString());
__vbuffer.push('\">\n      <iframe\n          style=\"width: 100%; height: 465px\"\n          src=\"');
__vbuffer.push(html.escape(url).toHtmlString());
__vbuffer.push('\"\n          allowfullscreen=\"allowfullscreen\"\n          frameborder=\"0\">\n      </iframe>\n      <figcaption>\n          Fig. ');
__vbuffer.push(html.escape(num).toHtmlString());
__vbuffer.push(': ');
__vbuffer.push(html.escape(caption).toHtmlString());
__vbuffer.push('\n      </figcaption>\n  </figure>');

}
}, {"simple":false,"modelName":"model","helpersName":"html","args":["num","url","caption"],"asHelper":"imgfigure"} )

Notice how at the end there are args and asHelper. This tells vash.link to treat this template as a helper and attach it to vash.helpers.imgfigure. Again, I consider those options at the end private implementation details that are necessary to be visible to enable the templates to be "portable" (string vs actual running JS).

@chevett
Copy link
Owner Author

chevett commented Jan 9, 2014

Cool, thanks for all the info. Compiled helpers are starting to look like partials from asp.net. In other words, rather than requiring a vash template with a magic name and treating it as a compiled helper, we could reference the compiled helper from within the template that is using it. So, the entry.js example becomes

entry.js

// require('./helpers/fullName.vash');  get rid of this

var tmpl = require('./template.vash');
var tmpl2 = require('./template.vash');

template.js

Hi @html.partial('./helpers/fullName.vash', model.first, model.last).  Your dumb test worked.

Then we see the html.partial method during template compilation and we also compile and include that helper/partial.

From my perspective, this means I don't have to any random/mysterious requires in my modules, which is important to me. Obviously, I am completely ignoring the LOE or what this would mess up in vash, but I think it might be a good change. What do you think? I'd love to work on this if you think this is doable.

@kirbysayshi
Copy link
Collaborator

Sorry that these are so long... just trying to explain my reasons (and also remember them... it's been a while since I thought them up!)

I believe the reason I didn't make partials official is because it raises large questions about how those templates are resolved in the browser, which currently just assumes a simple template cache. The goal was to give enough out of the box that things could work, but also to enable other things, like a full view engine in either node or the browser, to be possible regardless of transport (ajax, AMD, etc) or module decisions. So it would be easy to make a helper that looks in the template cache and then just errors in the browser or calls require in node... but that would break in browserify because it would be a dynamic require. That's why I leaned towards including the require call: it's an explicit statement for browserify.

Also, there's nothing stopping you from doing var partial = require('partials/template.vash'); partial(model) within a template and having that work, right? So the difference between helpers and partials are just how they're made available. A helper is globally available and is probably best for something like your fullName example. A partial should be provided by a view engine or surrounding machinery, since it's just a template that outputs a string. In this case, the surrounding machinery can be a require statement + vashify. Helpers are also a bit lighter since they don't create their own rendering context.

Vash's compiler, at the moment, also does not have any special hooks (or even the knowledge, really) to note that a call to html.partial is being made and to not escape it.

So, if you wanted to attempt to parse the template for html.partial calls, you'd either have to:
A) compile the module as a template, parse that with esprima, look for html.partial calls, and resolve those (probably easiest)
B) hack a callback system into vash's compiler to check if a particular expression is being called and then do the rest of A.

Or you could avoid compiled helpers and assume helpers would be added globally to the runtime module itself:

var vash = require('vash/build/vash-runtime-all');
vash.helpers.firstName = function() { ... }

@chevett
Copy link
Owner Author

chevett commented Jan 9, 2014

I appreciate the thorough explanations. I want to handle compiled helpers, but I will do so in a separate issue/PR.

This is what I have now:

var tmpl = require('./template.vash');

console.log(tmpl({first:'f', last:'l'}));

and with an uncompiled helper:

var vash = require('vash-runtime');
vash.helpers.fullName = function(m){
  return m.first + ' ' + m.last;
};

var tmpl = require('./template.vash');

console.log(tmpl({first:'f', last:'l'}));

looks good to you?

@kirbysayshi
Copy link
Collaborator

That looks great! And if I ever split the runtime into a separate package, it should be easy to transition.

chevett added a commit that referenced this pull request Jan 10, 2014
@chevett chevett merged commit 88dfba1 into master Jan 10, 2014
@chevett chevett deleted the helpers2 branch January 13, 2014 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants