Attr pass-thru & full context in partial helpers

refs #5162

- allow pagination and navigation partial helpers to have attributes passed through to them
    - e.g. {{navigation header=true}} -> {{#if header}} will now work
    - allows styling navigation to be done differently for different sections of the page
- properly create a data frame, and pass through "this" context
    - means {{navigation header=true}} is the same as {{> navigation header=true navigation=@site.navigation}}
    - our partial helpers, have the same behaviour exactly as if the partial was called directly
- this is additive, and improves behaviour
This commit is contained in:
Hannah Wolfe 2019-03-09 19:35:19 +00:00
parent 82788f2f5c
commit d2b1e0d4b7
8 changed files with 77 additions and 26 deletions

View File

@ -3,6 +3,7 @@
const _ = require('lodash'),
// (Less) dirty requires
proxy = require('../../../../helpers/proxy'),
createFrame = proxy.hbs.handlebars.createFrame,
templates = proxy.templates,
urlService = proxy.urlService,
SafeString = proxy.SafeString,
@ -38,17 +39,18 @@ const subscribeScript = `
// We use the name subscribe_form to match the helper for consistency:
module.exports = function subscribe_form(options) { // eslint-disable-line camelcase
const root = options.data.root,
data = _.merge({}, options.hash, _.pick(root, params), {
// routeKeywords.subscribe: 'subscribe'
action: urlService.utils.urlJoin('/', urlService.utils.getSubdir(), 'subscribe/'),
script: new SafeString(subscribeScript),
hidden: new SafeString(
makeHidden('confirm') +
const root = options.data.root;
const context = _.merge({}, options.hash, _.pick(root, params), {
// routeKeywords.subscribe: 'subscribe'
action: urlService.utils.urlJoin('/', urlService.utils.getSubdir(), 'subscribe/'),
script: new SafeString(subscribeScript),
hidden: new SafeString(
makeHidden('confirm') +
makeHidden('location', root.subscribed_url ? `value=${root.subscribed_url}` : '') +
makeHidden('referrer', root.subscribed_referrer ? `value=${root.subscribed_referrer}` : '')
)
});
return templates.execute('subscribe_form', data, options);
)
});
const data = createFrame(options.data);
return templates.execute('subscribe_form', context, {data});
};

View File

@ -6,16 +6,20 @@ var proxy = require('./proxy'),
string = require('../lib/security/string'),
_ = require('lodash'),
SafeString = proxy.SafeString,
createFrame = proxy.hbs.handlebars.createFrame,
i18n = proxy.i18n,
errors = proxy.errors,
templates = proxy.templates;
module.exports = function navigation(options) {
options = options || {};
options.hash = options.hash || {};
options.data = options.data || {};
var navigationData = options.data.blog.navigation,
currentUrl = options.data.root.relativeUrl,
self = this,
output,
data;
output;
if (!_.isObject(navigationData) || _.isFunction(navigationData)) {
throw new errors.IncorrectUsageError({
@ -71,8 +75,8 @@ module.exports = function navigation(options) {
return out;
});
data = _.merge({}, {navigation: output});
const context = _.merge({}, this, options.hash, {navigation: output});
const data = createFrame(options.data);
return templates.execute('navigation', data, options);
return templates.execute('navigation', context, {data});
};

View File

@ -6,10 +6,15 @@ var proxy = require('./proxy'),
_ = require('lodash'),
errors = proxy.errors,
i18n = proxy.i18n,
createFrame = proxy.hbs.handlebars.createFrame,
templates = proxy.templates,
pagination;
pagination = function (options) {
options = options || {};
options.hash = options.hash || {};
options.data = options.data || {};
if (!_.isObject(this.pagination) || _.isFunction(this.pagination)) {
throw new errors.IncorrectUsageError({
level: 'normal',
@ -36,10 +41,10 @@ pagination = function (options) {
!_.isNumber(this.pagination.total) || !_.isNumber(this.pagination.limit)) {
throw new errors.IncorrectUsageError({message: i18n.t('warnings.helpers.pagination.valuesMustBeNumeric')});
}
const context = _.merge({}, this, options.hash, this.pagination);
const data = createFrame(options.data);
var data = _.merge({}, this.pagination);
return templates.execute('pagination', data, options);
return templates.execute('pagination', context, {data});
};
module.exports = pagination;

View File

@ -7,7 +7,7 @@ var templates = {},
// Execute a template helper
// All template helpers are register as partial view.
templates.execute = function execute(name, context, options) {
templates.execute = function execute(name, context, data) {
var partial = hbs.handlebars.partials[name];
if (partial === undefined) {
@ -21,7 +21,7 @@ templates.execute = function execute(name, context, options) {
hbs.registerPartial(partial);
}
return new hbs.SafeString(partial(context, options));
return new hbs.SafeString(partial(context, data));
};
templates.asset = _.template('<%= source %>?v=<%= version %>');

View File

@ -197,8 +197,7 @@ describe('{{navigation}} helper with custom template', function () {
optionsData = {
data: {
blog: {
navigation: [],
title: 'Chaos is a ladder.'
navigation: [{label: 'Foo', url: '/foo'}]
},
root: {
relativeUrl: ''
@ -208,15 +207,33 @@ describe('{{navigation}} helper with custom template', function () {
});
it('can render one item and @blog title', function () {
var singleItem = {label: 'Foo', url: '/foo'},
testUrl = 'href="' + configUtils.config.get('url') + '/foo"',
var testUrl = 'href="' + configUtils.config.get('url') + '/foo"',
rendered;
optionsData.data.blog.navigation = [singleItem];
// Set @blog.title
optionsData.data.blog.title = 'Chaos is a ladder.';
rendered = helpers.navigation(optionsData);
should.exist(rendered);
rendered.string.should.containEql('Chaos is a ladder');
rendered.string.should.not.containEql('isHeader is set');
rendered.string.should.containEql(testUrl);
rendered.string.should.containEql('Foo');
});
it('can pass attributes through', function () {
var testUrl = 'href="' + configUtils.config.get('url') + '/foo"',
rendered;
// Simulate {{navigation isHeader=true}}
optionsData.hash = {isHeader: true};
rendered = helpers.navigation(optionsData);
should.exist(rendered);
rendered.string.should.not.containEql('Chaos is a ladder');
rendered.string.should.containEql('isHeader is set');
rendered.string.should.containEql(testUrl);
rendered.string.should.containEql('Foo');
});

View File

@ -144,5 +144,23 @@ describe('{{pagination}} helper with custom template', function () {
// strip out carriage returns and compare.
rendered.string.should.match(/Page 1 of 1/);
rendered.string.should.containEql('Chaos is a ladder');
rendered.string.should.not.containEql('isHeader is set');
});
it('can pass attributes through', function () {
var rendered = helpers.pagination.call({
pagination: {page: 1, prev: null, next: null, limit: 15, total: 8, pages: 1},
tag: {slug: 'slug'}
}, {
hash: {isHeader: true},
data: {
blog: {}
}
});
should.exist(rendered);
// strip out carriage returns and compare.
rendered.string.should.match(/Page 1 of 1/);
rendered.string.should.not.containEql('Chaos is a ladder');
rendered.string.should.containEql('isHeader is set');
});
});

View File

@ -1,5 +1,7 @@
{{@blog.title}}
{{#if isHeader}}isHeader is set{{/if}}
{{#foreach navigation}}
<a href="{{url absolute="true"}}">{{label}}</a>
{{/foreach}}

View File

@ -1,2 +1,5 @@
{{@blog.title}}
{{#if isHeader}}isHeader is set{{/if}}
<span class="page-number">Page {{page}} of {{pages}}</span>