Closed idpaterson closed 10 years ago
Thanks for the PR and for including tests! Can you provide a little more concrete usage example? For instance, I'm curious what benefit this provides over specifying the layout to extend with a variable.
Handlebars.registerPartial('bar', '...');
el.innerHTML = template({ foo: 'bar' });
{{#extend foo}} {{! extends "bar" }}
...
{{/extend}}
Sure, I suppose the best way to illustrate the difference is by showing the default behavior of express3-handlebars.
First, the environment is established with certain paths for templates and for partials. Partials are found and registered as needed, layouts are found as needed but not registered as partials.
var exphbs = require('express3-handlebars');
// ... set up express app
hbs = exphbs.create({
defaultLayout: 'my_base_layout',
partialsDir: [
'shared/templates/',
'views/partials/'
],
layoutsDir: 'views/layouts/'
});
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');
With that base in place, response handlers simply call res.render
to render templates. The home.handlebars view can use any of the partials that are available, but it does not have direct access to the layout. Instead, it is rendered to a string, mixed in to the {title: 'Home', _data: {...}}
context as body
, then passed to the layout which includes it with {{{body}}}
.
app.get('/', function (req, res) {
res.render('home', {
title: 'Home'
});
});
It is possible to override the layout template at this point as well if necessary.
app.get('/', function (req, res) {
res.render('home', {
title: 'Home',
layout: 'mobile_layout'
});
});
In order to implement {{#extend layout_name}}
without this pull request it would be necessary to make a few changes to disable the default layout functionality:
hbs = exphbs.create({
defaultLayout: false, // render views directly, without a layout
partialsDir: [
'shared/templates/',
'views/partials/',
'views/layouts/' // register layouts as partials
],
layoutsDir: null // avoid loading layouts
});
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');
// Add layout_name to all rendering contexts
app.locals.layout_name = 'my_base_layout';
app.get('/', function (req, res) {
res.render('home', {
title: 'Home'
});
});
That's not at all difficult, but it requires all of our view templates to use Handlebars Layouts syntax. Most templates would simply render the body of the page and not mess with any other blocks in the layout. The minority case would be a template that needs to add additional stylesheets, modify a menu, or similar cases where Handlebars Layouts would shine. Such a template might look like this:
<h1>Alpha</h1>
<p class="funky-lead">Lorem ipsum and such</p>
{{#append "styles"}}
<link rel="stylesheet" type="text/css" href="/styles/funky.css" />
{{/append}}
While most views would use the simple syntax.
<h1>Beta</h1>
<p>Lorem ipsum and such</p>
I hope that helps, let me know if you would prefer some clarification, at least on the last point, in the Readme.
I'll be looking into Express issues this weekend. Had to rewrite some things to support deep inheritance. I'll look into porting your changes over.
Thanks, I hoped to help with the other Express issue that came up this week but I wasn't able to replicate the problem. The solution in this PR is still working very well for us in production, but I have no idea how it will jive with all the other improvements on your radar.
We're using handlebars-layouts heavily, but it's still nice to not have to include the #extend in each template and to just be able to use the layout functionality provided by express-handlebars without any workarounds.
I've added an express test case to the feature/deep-inheritance branch using the hbs
module (see #8). As I mentioned, the deep-inheritance support is a rewrite. One which I'm hoping resolves your issue. Two changes to note:
{{#append [block]}}
, {{#prepend [block]}}
, and {{#replace [block]}}
helpers have been replaced by a single {{#content [block] mode="[append|prepend]"}}
helper.{{#embed [partial]}}
which is a layout-compatible replacement for {{> [partial]}}
.Thanks! I'll check it out when I get a chance.
Because of the rewrite, this pull request is no longer valid. I'm closing it, but please let me know if this is still an issue. Thanks again!
@idpaterson Just curious if you've been able to test the new version yet. Want to make sure you've got what you need!
I wanted to use the Handlebars Layouts helpers, but they did not work with the layout system that I was using, express3-handlebars. Rather than allowing views to specify a layout by name, the layout is specified either in the global express3-handlebars configuration or on a per-render basis. The module compiles the view into an HTML string, then renders the layout, providing the body such that the layout can show it with
{{{body}}}
. Clearly this is not as powerful as Handlebars Layouts.This very simple change allows the
append
,prepend
, andreplace
blocks to be used outside of anextend
block by initializingcontext._blocks = {}
if it does not yet exist._blocks
is still maintained on the context allowing the layout, compiled with the same context or at least with a copy of the_blocks
attribute, to define modifiable blocks.Tests
I added three test cases to hopefully cover the gamut of potential use cases for this.
should properly render programmatically-composed layouts
tests that the approach described above of rendering the view to a string and then passing that to the body. The test uses{{#block "body"}}
rather than{{{body}}}
since it is a more useful pattern.should properly render self-modifying layouts
might be useful in limited circumstances, such as providing top-of-file configuration blocks for a very large and gangly template.should properly render layouts included as partials
exists only to test that including the template as a partial works. There is no reason I can think of to not use anextend
block in this case so I noted that in the template comments.Caveats
This requires either the same context to be used when rendering the layout, or the
_blocks
attribute to be copied.express3-handlebars
does this already by extending the context used for the view to add a property to the rendered view{body: body}
, preserving_blocks
. I do not know whether other layout engines that may also benefit from this change do the same so I added a note to that effect in the readme.