hapijs / vision Goto Github PK
View Code? Open in Web Editor NEWTemplates rendering support for hapi.js
License: Other
Templates rendering support for hapi.js
License: Other
This will allow hapi >= 10.2 to know the plugin is available to connections added after it was registered. It is backwards compatible.
I have an app using handlebars as the template engine and I want to register a series of plugins (user registration / auth) that render within the root apps handlebars layout. The plugin will only serve the user form.
Trying to serve a layout from the parent app throws this error:
[error] message: View paths cannot lookup templates outside root path (path includes one or more '../') stack: Error: View paths cannot lookup templates outside root path (path includes one or more '../')
In this case I am trying to pass the path of the root applications layout file via the plugin options.
How can I serve a handlebars template with a form via a hapi plugin but have it render within the root applications layout?
It would be nice if you could specify a global context value that would be merged into the passed in view context. Ideally it would take an object or a function in case you needed to do some logic while setting up your global context.
Currently, the only way to set up a global context is via an extension point and checking the response type for view.
server.views({
context: {
version: '1.5.5',
name: 'Application Name',
}
});
//OR
server.views({
context: function (request) {
return {
version: '1.4.5',
date: Date.now(),
id: request.id
}
}
});
The value of server.views.context
would be merged with whatever context gets passed into reply.view
. Any collision, the local one overwrites the gloabal context
reply.view('index', {
test: 'This is the index view'
});
Would result in a context object of
{
version: '1.5.5',
name: 'Application Name',
test: 'This is the index view'
}
//OR
{
version: '1.4.5',
date: <timewhencontextruns>,
id: 5,
test: 'This is the index view'
}
I think this is much clearer than the suggested approach in the tutorials that looks like this:
// Server extension points
server.ext('onPreResponse', function (request, reply) {
if (request.response.variety === 'view') {
request.response.source.context = Hoek.applyToDefaults(server.settings.app.globalContext, request.response.source.context || {});
reply();
}
else {
reply();
}
});
lib/index.js:
Line 103: hapi/hapi-for-you - Only one variable can be initialized per loop.
/lib/manager.js:
Line 421: hapi/hapi-for-you - Only one variable can be initialized per loop.
Line 453: hapi/hapi-for-you - Only one variable can be initialized per loop.
If you include a helper in the helpers directory and it cannot be loaded (i.e. require()
throws), the error is lost. It would be useful to see some kind of error here, perhaps you just made a simple syntax error that needs fixing.
I have an application where some of my views are in plugins in a different and undetermined folders. I have a build process in gulp which compiles and combines all view into a single file. This file is loaded when the hapi server starts:
var dustViews = require("fs").readFileSync("./lib/views.min.js");
dust.loadSource(dustViews);
console.log('Views loaded to cache');
I have the following view setup for vision:
server.views({
defaultExtension: "dust",
engines: {
dust: require('hapi-dust')
},
isCached: true
//path: '../src/view',
//relativeTo: __dirname,
//compileMode: 'async'
});
Originally I had to specify the path but it should work without it as I already loaded all possible dust files into the cache. Why vision still insist on looking up the view in the folder? (and failing of course because the views collected from the plugins are not there - but they are in the dust cache!
Is it possible to have nested layouts? For example, there would be a "master" layout that contains site-wide HTML (e.g. - global header, etc.) and a section-specific layout that contains HTML shared within a certain section of the site.
An arbitrary example would be a "user settings" layout. The settings layout would contain common elements shared between each settings page, such as a menu for the settings sections. Would it be possible to render a specific settings page, such as "profile settings", into a settings layout that would then render into the master layout?
Adding in a little note about npm installing vision
Hey guys,
I have a plugin which uses handlebars as view engine.
plugin.views({
engines: {
html: require('handlebars')
},
path: __dirname
});
When I make changes to the login.html file I need to restart the server in order to force hapi to pick my changes.
I have globally registered Vision for my server and this is my route:
plugin.route({
method: 'GET',
path: '/login',
handler: (req, reply) => {
reply.view('login', { title: 'Login page via handlebars' });
},
config: {
auth: false
}
});
Is there a way to disable this while developing?
Trying to setup Nunjucks - set watch
to true in the prepare options as outlined in your example but it will not watch/recompile. Has this example been tested?
When overriding relativeTo in the view options, the layout's are relative but the partials are not.
// server config
server.views({
engines: {
html: require('handlebars')
},
relativeTo: 'src/server',
layout: 'default',
layoutPath: 'layouts',
partialsPath: 'partials'
});
// handler
var options = {
relativeTo: 'view/base'
};
return reply.view('index', {}, options);
In the above configuration, the view engine correctly looks in the following location for a template named index.html
.
src/server/view/base
It also correctly looks for a layout file named default
in the following path
src/server/view/base/layouts
However, due to the way partials are loaded up front, the path for partials is relative to the engine path, as in the following:
src/server/partials
When the expected relative path for partials for this view should be
src/server/view/base/partials
Looking through the code it looks like optimization/pre loading was done with the partials which makes this difficult to change.
When an error occurs in a handlebars partial, the error catcher totally drops it: https://github.com/hapijs/vision/blob/master/lib/index.js#L252.
Not sure what the fix here is though, since there's no callback... and I feel like a throw
is a bit too harsh.
Ideas? Thoughts? Recommendations? :-)
Using hapi, vision and handlebars I want to created helpers to do jade style code blocks. In a partial or page I want to define a block of code to be placed in the head of the layout.. not in the {{{content}}} section of the layout.
Example template:
<html>
<head> {{#myBlock}}{{/myBlock}} </head>
<body> {{{content}}} </body>
</html>
and in pages and partials I have this
<div>My body content</div>
{{#extendTo "css"}}
<!--some css style I want in the head -->
<style> div: { color: red; } </style>
{{/extendTo}}
My helper for #extendTo stores the style text and returns empty string. The helper for myBlock takes the stored value and returns it, making it appear in the head.
This works some of the time, but the problem is that the helper blocks are processed in an unknown order. In my example the #myBlock in the template is sometimes processed first. Then the the #extendTo blocks in pages / partials are processed. This means that the style does not make it into the page.
The hapi people told me that the pages / partials are all processed to strings before they are put into the layout and suggested that the priority or order might be in vision.
Is there any way to specify a priority for helper blocks? If a priority could be set I could make sure my extendTo blocks are processed first, guaranteeing that the content is in memory before any of the myBlock helper blocks are processed.
It almost seems like helpers should be processed on partials first, then pages, then layouts. Any way to enforce this?
Thanks.
Hi all bro,
my code is:
var backendPlugin = {
register: function (server, options, next) {
// register view template engine
server.views({
engines: {
html: Handlebars,
},
//relativeTo: './views/backend',
path: 'views/backend/templates',
layoutPath: 'views/backend/layout',
layout: 'default',
partialsPath:'views/backend/partials',
helpersPath: 'views/backend/helpers'
});
// add backend-only route
server.route(Routes.backend);
next();
}
};
backendPlugin.register.attributes = {
name: 'backendPlugin',
version: '1.0.0'
};
var frontendPlugin = {
register: function (server, options, next) {
// register view template engine
server.views({
engines: {
html: Handlebars,
},
//relativeTo: './views/frontend',
path: 'views/frontend/templates',
layoutPath: 'views/frontend/layout',
layout: 'default',
partialsPath:'views/frontend/partials',
helpersPath: 'views/frontend/helpers'
});
// add frontend-only route
server.route(Routes.frontend);
next();
}
};
and in layout of backend & layout of frontend : i insert partial footer
{{!< layout/default}}
{{! The tag above means - insert everything in this file into the {content} of the default.html template }}
{{> navbar}}
{{> content}}
{{> footer}}
but backend render footer is being override by footer of frontend. (html code display)
struct folder is:
projectfolder
--views
---backend
----partials
-----footer.html
---frontend
----partials
-----footer.html
.How extractly partials path to fix issue.
When i visit localhost:3000 template shows up but then if i change template and hit refresh, template shows up without changes, but process hangs for some time (depends how many times i hit refresh). Using same version of jade in express works.
Even if i copy sample from vision/examples/jade/index.js it is same ....
Did i miss something?
I have the following setup:
const Hapi = require('hapi');
const Hoek = require('hoek');
const jade = require('jade');
const vision = require('vision');
const server = new Hapi.Server();
server.connection({
host: 'localhost',
port: 3000
});
server.register(vision, (err) => {
// Hoek.assert(!err, err);
server.views({
engines: {
jade: jade
},
relativeTo: __dirname,
path: 'views',
compileOptions: {
cache: true,
pretty: false,
debug: true,
compileDebug: true
}
});
});
server.route({
method: 'GET',
path: '/sl',
config: {
id: 'root.sl',
handler: function (request, reply) {
reply.view('services');
}
}
});
// Start the server
server.start((err) => {
if (err) {
throw err;
}
console.log('Server running at:', server.info.uri);
});
Vision version: 4.0.1
Hapi version: 11.1.4
Jade version: 1.11.0
Node version: v5.3.0
From the hapi doc:
variety
- a string indicating the type of source
with available values:
'view'
- a view generated with reply.view()
.The context object can be anything and it is not safe to assume we can clone it.
Hello, I pass a property compileOptions
as following, but the ejs template always cache compile result, it's worng way?
server.views({
engines: {
ejs: require('ejs')
},
relativeTo: __dirname,
path: 'Templates',
compileOptions: {
cache: false
}
});
As described in this SO question I made,
Is it possible to create Hapi connections and have each one use Vision and register different view engines?
While trying to use [email protected]
I receive this error when hitting the /documentation
endpoint:
11:14:20 web.1 | TypeError: Cannot read property '_render' of undefined
11:14:20 web.1 | at Object.internals.marshal (...\node_modules\vision\lib\manager.js:583:12)
11:14:20 web.1 | at internals.Response._marshal (...\node_modules\hapi\lib\response.js:473:22)
11:14:20 web.1 | at internals.state (...\node_modules\hapi\lib\transmit.js:124:18)
11:14:20 web.1 | at Items.parallel (...\node_modules\hapi\lib\transmit.js:509:20)
11:14:20 web.1 | at done (...\node_modules\hapi\node_modules\items\lib\index.js:63:25)
11:14:20 web.1 | at each (...\node_modules\hapi\lib\transmit.js:480:20)
11:14:20 web.1 | at Object.exports.parallel (...\node_modules\hapi\node_modules\items\lib\index.js:70:13)
11:14:20 web.1 | at Object.internals.state (...\node_modules\hapi\lib\transmit.js:502:11)
11:14:20 web.1 | at Object.internals.marshal (...\node_modules\hapi\lib\transmit.js:98:15)
11:14:20 web.1 | at Object.exports.send (...\node_modules\hapi\lib\transmit.js:30:15)
Diving in a bit, I see that manager._render
is failing. The manager
object looks like this:
{ statusCode: 200,
data:
{ manager: { _context: null, _engines: [Object], _defaultExtension: 'html' },
template: 'index.html',
context: { hapiSwagger: [Object] },
options: undefined,
compiled: { settings: [Object], template: [Function] } } }
I'm new to vision, so I'm not sure what is causing this issue but figured I'd post here in case it is a bug.
Let me know if you need any further information.
The examples need vision loaded as a plugin since hapi no longer provides vision by default.
npm WARN deprecated [email protected]: Jade has been renamed to pug, please install the latest version of pug instead of jade
npm WARN deprecated [email protected]: Deprecated, use jstransformer
I am having a bit of an issue using vision with handlebars and hapi 9.*. I might be missing something, so below is my entire app.js. My error is this:
Server decoration already defined: views
What am I doing wrong?
var Hapi = require("hapi");
var path = require("path");
var vision = require("vision");
var inert = require("inert");
var jwt = require("jsonwebtoken");
var config = require("config");
var env = process.env.NODE_ENV || "development";
var user = require("./models/user");
var routes = require("./routes");
var validate = function (decodedToken, callback) {
user.findById(decodedToken.accountKey, function(error, result) {
if (!result) {
return callback(error, false, result);
}
return callback(error, true, result)
});
};
var server = new Hapi.Server({
connections: {
routes: {
cors: true
}
}
});
server.register(vision, function (error) {
if (error) {
console.log("Failed to load vision.");
}
server.views({
engines: {
html: require("handlebars")
},
layout: true,
path: __dirname,
layoutPath: path.join(__dirname, "public/templates/core"),
partialsPath: path.join(__dirname, "public/templates/core")
});
});
server.connection({
host: config.get("app.host"),
port: config.get("app.port")
});
server.register({
register: require("hapi-mongo-models"),
options: {
mongodb: {
url: config.get("mongodb.url"),
settings: {}
},
autoIndex: false
}
}, function (error) {
if (error) {
console.log("Failed loading hapi-mongo-models plugin");
}
});
server.register({
register: require("yar"),
options: {
cookieOptions: {
password: config.get("cookies.password"),
isSecure: env === "development" ? false : true,
isHttpOnly: true
}
}
}, function (error) {
if (error) {
console.log("Failed loading yar plugin");
}
});
server.register({
register: require("hapi-auth-jwt")
}, function (error) {
if (error) {
console.log("Failed loading hapi-auth-jwt plugin");
}
server.auth.strategy("token", "jwt", {
key: config.get("auth.privateKey"),
validateFunc: validate
});
server.route(routes);
});
server.app.config = config;
if (!module.parent) {
server.register(require("blipp"), function(error) {
if (error) {
console.log("Failed loading blipp plugin");
}
server.register([
inert,
vision,
{
register: require("hapi-swagger"),
options: {
documentationPath: "/api/documentation"
}
}], function (error) {
if (error) {
console.log("Failed loading hapi-swagger plugin");
}
server.start(function() {
// Add any server.route() config here
console.log("Server running at:", server.info.uri);
});
});
});
}
module.exports = server;
On Hapi 7.5.2, Vision 1.2.2, Handlebars 2.0.0 - I'm receiving the following error:
internalError, message: The partial header could not be found: The partial header could not be found stack: Error: The partial header could not be found
It was working, but what's changed, is that I've moved my configuration file up a level, which is set as follows:
settings: {
views: {
engines: {
hbs: require('handlebars')
},
basePath: Path.join(__dirname, '../web'),
path: './views',
layoutPath: './views/layouts',
helpersPath: './views/helpers',
partialsPath: './views/partials',
layout: true,
isCached: false,
allowAbsolutePaths: true,
allowInsecureAccess: true
}
}
Which is then loaded by a plugin as follows:
plugin.views(config.settings.views);
(The configuration object was previously loaded directory into the plugin.views
method inside index.js for the plugin which was working fine).
I'm looking at vision source now (1.2.2), trying to see why partials may not be loading.
From hapijs/hapi#1953
Related to this hapijs/hapi#2537.
From my testing, internals.handler doesn't get called when reply.view is called from a handler. So the default context object is not passed through.
Getting
Debug: internal, implementation, error
Error: settings.layoutKeyword conflict
...
with
module.exports = function(templ, options) {
return function(ctx, options) {
return require('underscore').template(templ)(ctx || null);
};
};
module.exports = {
engines: {
'html': {
compile: require('./underscore_compiler')
}
},
compileMode: 'sync',
path: __dirname + '/../../views',
partialsPath: __dirname + '/../../views/partials'
};
it breaks when it tries to load the partial
similar code found here
molekilla/rutha@05c7b86
any idea ?
thanks
This causes warnings to be logged to stdout. Could we change this to load only specific files?
Atm, if we have js.map files or tests or something else in that same folder, it will spit out warnings every app start.
Otherwise, rendering fails after onPreResponse
is called. Use response 'prepare' option.
Replaces hapijs/hapi#1882
I previously used Swag(https://github.com/elving/swag) with hapi 7.5.3. I am having a hard time pin pointing the exact line of code that is causing a problem but the helper functions are no longer being registered.
I previously did:
var Handlebars = require('handlebars');
var Swag = require('swag');
Swag.registerHelpers(Handlebars);
server.views({
engines: {
hbs: Handlebars
},
path: './app/views',
layoutPath: './app/views/layouts',
layout: 'defaultLayout',
partialsPath: './app/views'
});
and it worked. Now this seems to be broken with hapi 8. What is the proper way to load in helpers that are from another library
Would be lovely if we could register helpers programmatically.
E.g. server.views.registerHelper('someHelper', function(){});
This would allow for simpler helper module sharing, rather than having to create a helper file for each helper in a bundle.
Perhaps this exists I just couldn't find a simple solution by reverse engineering.
To be consistent with other hapi settings.
Moved from hapijs/hapi#1753
The server.render()
method does not work when used inside a request handler via request.server.render()
when the view manager was created by a plugin. This is breaks because the request.server
does not have access to the plugin realm where the view manager is configured (only has access to a view manager setup directly on the server root outside of any plugin).
The request.render()
method works exactly the same way but it gets its realm from the route the request was bound to instead of the global server root.
Note that this will not work in onRequest
extensions added by the plugin because the route isn't yet set at this point in the request lifecycle and the request.render()
method will produce the same limited results server.render()
can. If you need to call render()
within onRequest
, save a reference to the views manager direction from server.realm.plugins.vision.manager
within the plugin and use that in the extension method.
Prior to v3.0.0
we could modify the views object during the preResponse event. Moving the compilation step earlier in the request lifecycle no longer allows this. Any ideas on getting this work in v3.0.0
?
Example:
template files:
./server/views/desktop/index.ejs
./server/views/mobile/index.ejs
views object:
views: {
engines: {
'ejs': ejs
},
relativeTo: './server/views'
};
onPreResponse:
if ( response.variety === 'view' ) {
var userAgent = request.plugins.scooter;
if ( mobileIdentifier( userAgent ) ) {
request.response.source.options = { path: 'mobile' };
} else {
request.response.source.options = { path: 'desktop' };
}
};
I have a couple of apps that when I deploy to a server, hapi 8.8.0 is installed which has vision 2.x.x as a dependency and installs vision 2.0.1. Vision 2.0.1 has joi 6.x.x as a dependency, but it installs joi 5.1.0.
How is this possible? I have 2 different apps on 2 different computers which deploy to 2 different servers which completely different setups.
Hi there - upgrading to hapi v9.0.0, and have added the vision dependency, as well as what I thought was the correct require statement during plugin registration, but the application is failing with:
server/web/index.js:53
server.views({
^
TypeError: undefined is not a function
Here's a Gist to the complete application setup...
https://gist.github.com/58bits/12e3a8380327062a762b
Is there anything obvious wrong here?
@hueniverse any ideas?
When global context was first suggested in #4, functions that generate context could have a request
argument. Like this :
server.views({
context: function (request) {
return {
version: '1.4.5',
date: Date.now(),
id: request.id
}
}
});
But it is not actually implemented now, and I think that idea is quite essential in many cases of developments.
Update tests to be in line with new testing rules. https://github.com/hapijs/contrib/blob/master/Tests.md
When compiled.template
throws an Error it's handled via Hapi's fail
function within transmit.js
which causes a new Response
to be generated. By the time that this new response is propagated to onPreResponse
listeners there is no indication that an error has occurred via request.response.isBoom
.
Hapi v13.0.0
Vision v4.0.1
Hi!
It looks like vision gives a error on 404 pages and just hangs the server. I will look into it later, but for now I just thought you should know. Maybe it is some easy error? Or is it related to the updates you are making on the hapi 8.0 release?
Debug: hapi, internal, implementation, error
TypeError: Cannot read property 'hasOwnProperty' of undefined
at /Users/Torsph/Development/node/aam/node_modules/hapi/node_modules/vision/lib/index.js:329:20
at /Users/Torsph/Development/node/aam/node_modules/hapi/node_modules/vision/lib/index.js:432:20
at Object.engine.compileFunc (/Users/Torsph/Development/node/aam/node_modules/hapi/node_modules/vision/lib/index.js:162:24)
at /Users/Torsph/Development/node/aam/node_modules/hapi/node_modules/vision/lib/index.js:422:16
at fs.js:291:14
at Object.oncomplete (fs.js:97:15)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.