diff --git a/README.md b/README.md index 7cb5b9e9..e6b56467 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,20 @@ To use partials in handlebars you will need to pass the names and paths in the o } ``` +To use layouts in handlebars you will need to pass the `layout` parameter: +```js +fastify.register(require('point-of-view'), { + engine: { + handlebars: require('handlebars') + }, + layout: './templates/layout.hbs' +}); + +fastify.get('/', (req, reply) => { + reply.view('./templates/index.hbs', { text: 'text' }) +}) +``` + To configure nunjunks environment after initialisation, you can pass callback function to options: ```js options: { diff --git a/index.js b/index.js index 2aa2407a..9cef3bd1 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ const fp = require('fastify-plugin') const readFile = require('fs').readFile +const accessSync = require('fs').accessSync const resolve = require('path').resolve const join = require('path').join const HLRU = require('hashlru') @@ -27,10 +28,22 @@ function fastifyView (fastify, opts, next) { const includeViewExtension = opts.includeViewExtension || false const prod = typeof opts.production === 'boolean' ? opts.production : process.env.NODE_ENV === 'production' const defaultCtx = opts.defaultContext || {} + const layoutFileName = opts.layout + + if (layoutFileName && type !== 'handlebars') { + next(new Error('"layout" option only available for handlebars engine')) + return + } + + if (layoutFileName && !hasAccessToLayoutFile(layoutFileName)) { + next(new Error(`unable to access template "${layoutFileName}"`)) + return + } + const renders = { marko: viewMarko, 'ejs-mate': viewEjsMate, - handlebars: viewHandlebars, + handlebars: withLayout(viewHandlebars), mustache: viewMustache, nunjucks: viewNunjucks, 'art-template': viewArtTemplate, @@ -398,6 +411,40 @@ function fastifyView (fastify, opts, next) { } else { next() } + + function withLayout (render) { + if (layoutFileName) { + return function (page, data, opts) { + const that = this + + render.call({ + getHeader: () => {}, + header: () => {}, + send: (result) => { + if (result instanceof Error) { + throw result + } + + data = Object.assign(data, { body: result }) + + render.call(that, layoutFileName, data, opts) + } + }, page, data, opts) + } + } + + return render + } + + function hasAccessToLayoutFile (fileName) { + try { + accessSync(join(templatesDir, getPage(fileName, 'hbs'))) + + return true + } catch (e) { + return false + } + } } module.exports = fp(fastifyView, { fastify: '^2.x' }) diff --git a/templates/index-for-layout.hbs b/templates/index-for-layout.hbs new file mode 100644 index 00000000..4de36880 --- /dev/null +++ b/templates/index-for-layout.hbs @@ -0,0 +1 @@ +

{{text}}

\ No newline at end of file diff --git a/templates/layout.hbs b/templates/layout.hbs new file mode 100644 index 00000000..d52cf0dd --- /dev/null +++ b/templates/layout.hbs @@ -0,0 +1,7 @@ + + + + + {{{body}}} + + \ No newline at end of file diff --git a/test/test-handlebars.js b/test/test-handlebars.js index 3c999c27..edee1137 100644 --- a/test/test-handlebars.js +++ b/test/test-handlebars.js @@ -181,6 +181,31 @@ test('fastify.view with handlebars engine with callback and html-minifier', t => }) }) +test('fastify.view with handlebars engine with layout option', t => { + t.plan(3) + + const fastify = Fastify() + const handlebars = require('handlebars') + const data = { text: 'it works!' } + + fastify.register(require('../index'), { + engine: { + handlebars + }, + layout: './templates/layout.hbs' + }) + + fastify.ready(err => { + t.error(err) + + fastify.view('./templates/index-for-layout.hbs', data, (err, compiled) => { + t.error(err) + t.strictEqual(handlebars.compile(fs.readFileSync('./templates/index.hbs', 'utf8'))(data), compiled) + fastify.close() + }) + }) +}) + test('reply.view with handlebars engine', t => { t.plan(6) const fastify = Fastify() @@ -519,3 +544,36 @@ test('reply.view with handlebars engine with partials', t => { }) }) }) + +test('reply.view with handlebars engine with layout option', t => { + t.plan(6) + const fastify = Fastify() + const handlebars = require('handlebars') + const data = { text: 'text' } + + fastify.register(require('../index'), { + engine: { + handlebars: handlebars + }, + layout: './templates/layout.hbs' + }) + + fastify.get('/', (req, reply) => { + reply.view('./templates/index-for-layout.hbs', data) + }) + + fastify.listen(0, err => { + t.error(err) + sget({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + }, (err, response, replyBody) => { + t.error(err) + t.strictEqual(response.statusCode, 200) + t.strictEqual(response.headers['content-length'], '' + replyBody.length) + t.strictEqual(response.headers['content-type'], 'text/html; charset=utf-8') + t.strictEqual(handlebars.compile(fs.readFileSync('./templates/index.hbs', 'utf8'))(data), replyBody.toString()) + fastify.close() + }) + }) +}) diff --git a/test/test.js b/test/test.js index 1dec464b..b3994352 100644 --- a/test/test.js +++ b/test/test.js @@ -107,3 +107,33 @@ test('register callback should throw if the engine is not supported', t => { t.is(err.message, '\'notSupported\' not yet supported, PR? :)') }) }) + +test('register callback with handlebars engine should throw if layout file does not exist', t => { + t.plan(2) + const fastify = Fastify() + + fastify.register(require('../index'), { + engine: { + handlebars: require('handlebars') + }, + layout: './templates/does-not-exist.hbs' + }).ready(err => { + t.ok(err instanceof Error) + t.deepEqual('unable to access template "./templates/does-not-exist.hbs"', err.message) + }) +}) + +test('register callback should throw if layout option is provided and engine is not handlebars', t => { + t.plan(2) + const fastify = Fastify() + + fastify.register(require('../index'), { + engine: { + ejs: require('ejs') + }, + layout: './templates/layout.hbs' + }).ready(err => { + t.ok(err instanceof Error) + t.deepEqual('"layout" option only available for handlebars engine', err.message) + }) +})