Closed callumlocke closed 10 years ago
Yes - definitely. Every fruitmachine module we have in the web app is hooked up to a Hogan template. I've made a worked example web app that includes approximately how we use fruitmachine Hogan in the web app here (also includes our AppCache hacks):- https://github.com/matthew-andrews/web-app/.
The main idea is you plug the template method into fruitmachine modules in via the template
key when defining the module. Eg: https://github.com/matthew-andrews/web-app/blob/master/client/views/article.js#L6
(Using the browserify-hogan transform: https://github.com/matthew-andrews/web-app/blob/master/GruntFile.js#L14)
Ah I'm not sure if we're talking about the same thing - I meant standard Mustache partials like this: {{> mypartial}}
, where one template defers to another one and passes down the current context (spec). It looks like your demo app doesn't do partials in that way, but it seems you've got another approach which I will try and follow, thanks.
Ah right - yes you should be able to do that - it's internal to Hogan. fruitmachine shouldn't interfere with the internals of Hogan.
Not sure if you can see this from OSB but we do this occasionally for some really basic UI elements (eg. progress bars) in the templates we use within fruitmachine modules.
Eg: http://git.ak.ft.com/ftlabs/ft-app/blob/v42/lib/templates/shared/fruit/layout-i.ms#L7
I know fruitmachine doesn't interfere with Hogan internals, but the thing is, {{>foo}}
-style partials need to be rendered in a unique way - when rendering the 'parent' template, you need to pass in a second argument in addition to the context data.
For example...
list.mustache:
<ul>
{{#items}}
{{> item}}
{{/items}}
</ul>
item.mustache:
<li>{{name}}</li>
...then in your JS, after compiling both templates (as listTemplate
and itemTemplate
), you would render a list like this:
var renderedListHTML = listTemplate.render({
name: 'John'
}, {item: itemTemplate});
(Sorry if you knew all that already; I didn't until yesterday.)
If you define a fruitmachine "list" component class and set its template to listTemplate
, then fruitmachine doesn't know to pass in the partials at render time.
The obvious workaround (which took me way too long to think of) is to define your component with a custom template function, which itself calls the Hogan template's render method and passes in the partials, then returns the result... But since fruitmachine recommends Mustache/Hogan, maybe there should be an explicit way to pass in any needed partials when defining a component? If you think that would be good, I'll do a PR for that.
BTW, since you mentioned browserify-hogan... I'm a maintainer on that package, and you might want to know: I just published an update that changes its functionality - now when you require
a template through that transform, you get the whole template object, not just its render
method (so it's more in line with Hogan's official approach). Since fruitmachine accepts either an object or a function as a template, things will probably just continue working the same, but if you manually call your template anywhere, that will no longer work after updating.
I wasn't aware that was how you passed in extra data for partials - cool :-).
The line where we call the template method is here: https://github.com/ftlabs/fruitmachine/blob/master/lib/module/index.js#L497. I quite like that the requirements of the template
option of fruitmachine modules are so basic and ignorant of any templating library specifics (just a function that takes a data object and outputs html) so your suggested workaround seems quite appealing to me. That said I think we would definitely consider any PR that makes passing data into partials easier - as long as the current ignorance of fruitmachine to Hogan is maintained.
As a possible way of making the workaround neater you could write a little transform method:
// pseudo code - not tested!!
module.exports = function(template, partialData) {
return function(data) {
return template.render.call(this, data, partialData);
};
};
Then you just need to do:
var fruitmachine = require('fruitmachine');
var template = require('../../templates/partials/apple.html').render;
var itemTemplate = require('../../templates/partials/item-template.html').render;
var addPartialData = require('./partial-data-transform');
module.exports = fruitmachine.define({
name: 'apple',
template: addPartialData(template, { item: { itemTemplate } })
});
If I understand your notes correctly about the browserify transform should this:
var fruitmachine = require('fruitmachine');
var template = require('../../templates/partials/apple.html');
module.exports = fruitmachine.define({
name: 'apple',
template: template
});
Change to this?
var fruitmachine = require('fruitmachine');
var template = require('../../templates/partials/apple.html').render;
module.exports = fruitmachine.define({
name: 'apple',
template: template
});
That addPartialData
thing is nice. And yes I completely agree about keeping FM ignorant of Hogan's specifics. Better to just put something in the docs about using partials (I will PR something).
re: browserify-transform: yes you've got it – except with fruitmachine you don't need to do that, because it doesn't mind if you pass in the whole object. (FM automatically detects whether it's a callable function or an object with a render
method.)
But for example, in the addPartialData
snippet you just wrote, then you'd need to change that to return template.render.call(...)
, or yeah just do require('apple.html').render
as you suggested.
Oh I just double-checked that and found a bug - require('apple.html').render
was broken, because a Hogan template's render
method uses this
internally to access prototype stuff provided by the Hogan runtime library. (And apparently pulling just a single method out of a require
call like that causes that function to lose its original this
context - this was news to me!).
I've just fixed that bug and republished the transform as v0.1.1. Now either approach works.
Great! Did you have any other ideas about how we might tweak the way template functions are called to make partials easier or were the above approaches acceptable? If so I'll try to find some time over the next few days to improve improve the docs.
I'm happy to keep using the above approaches. The only way I can think of to change FM would be for define
to accept a partials
object as well as a template
object, but I think that would make FM a bit too Hogan-aware.
Closing this issue because an alternative solution was found.
I might be being dumb, but I can't see how I can do this currently... With Hogan, you have to pass in any needed partials into a template's
render
method, and I don't think that's possible with something likemodule.render().inject(el)
since I don't have direct access to the template's own render method. I wondered if a fruitmachine module's ownrender
method might accept a list of partials, but it looks like it doesn't take any arguments).Or should I be using some other fruitmachine functionality to achieve the same thing as partials?