I'm working with Jade and Express and I would like to use a variable in my include statement. For example:
app.js
app.get('/admin', function (req, res) {
var Admin = require('./routes/admin/app').Admin;
res.render(Admin.view, {
title: 'Admin',
page: 'admin'
});
});
layout.jade
- var templates = page + '/templates/'
include templates
When I do this I get the error EBADF, Bad file descriptor 'templates.jade'
I even tried
include #{templates}
to no avail.
AFAIK JADE does not support dynamic including. What I suggest is to "include" outside the template, i.e.
app.js
app.get('/admin', function (req, res) {
var Admin = require('./routes/admin/app').Admin;
var page = 'admin';
var templates = page + '/templates/';
// render template and store the result in html variable
res.render(templates, function(err, html) {
res.render(Admin.view, {
title: 'Admin',
page: page,
html: html
});
});
});
layout.jade
|!{ html }
this also works:
//controller
var jade = require('jade');
res.render('show', {templateRender: jade.renderFile});
//template
!= templateRender('my/path/'+dynamic+'.jade', options)
This probably will not increase the performance that you would expect from using the 'view cache' setting (it's on by default in NODE_ENV === 'production'). Or even break the app (e.g. if files are not available on the hard drive while deploying new code). Also trying to use this trick in a client-side or isomorphic app will not work because the template can not be compiled.
Found this page googling for the same question, but in a different context, so thought I'd put my solution (read: workaround) here for posterity:
I wanted to surround my include with more context pulled from the variable, e.g. (simplified):
- var templates = page + '/templates/'
- var headid = page + 'head'
- var imgsrc = '/images/' + page
div(id=headid)
h1 #{page}
img(src=imgsrc)
div(id=page)
include templates
Since that doesn't work (Jade doesn't support dynamic includes, as noted by freakish), I hybridized with a mixin:
(Edit– a little more elegant than my previous workaround:)
mixin page1
include page1/templates
mixin page2
include page2/templates
...
- for (var i = 0; i < 3; i++)
- var page = 'page' + i
- var headid = page + 'head'
- var imgsrc = '/images/' + page
div(id=headid)
h1 #{page}
img(src=imgsrc)
div(id=page)
+page
My previous answer:
mixin templates(page)
- var headid = page + 'head'
- var imgsrc = '/images/' + page
div(id=headid)
h1 #{page}
img(src=imgsrc)
+templates('page1')
#page1
include page1/templates/
+templates('page2')
#page2
include page2/templates/
...
It's not elegant, and it won't work if you need to include more than a few things this way, but at least part of the Jade is dynamic.
Why do not use jade inheritance?
Render what you want at middleware level:
res.render('templates/' + template_name + '.jade')
Write common common.jade:
h1 This is a page
.container
block sublevel
h2 Default content
Then write file that extends common.jade:
extends common.jade
block sublevel
h2 Some things are here
It's 2019 and using variables in Pug (previously Jade) mixins has become simple.
When creating your mixin, you can give it parameters as per value(s) you're expecting to pass to the mixin. You can access any nested values using dot notation.
mixinFile.pug:
mixin myMixin(parameter1, parameter2, parameter3)
h2.MyHeading #{parameter1}
p.MyParagraph #{parameter2.myVariable}
.MyBox(id= parameter3.id)
index.pug:
include mixinFile
block content
+MyMixin(variable1, variable2, variable3)
You can read more in the official Pug documentation on Mixins.
Related
I'm making a solitaire game using NodeJs and Express backend and phaser.io frontend. It will offer different layouts of cards to play.
I have the game working "just fine" alpha state, but with a single layout of cards setup in HTML like:
<script src="js/gameboards/data.js"></script>
<script src="js/tableau.js"></script>
where data.js is the file that describes the card layout, and tableau.js is the game logic. I have several different files in /gameboards and for the testing/building, I just change the filename when I want to change the layout.
data.js is not a JSON file, it's a JS object
let gameboard = {
info: {
title: "Standard",
description: "6 columns of 5 cards"
},
[...]
deal: function() {
for (let i = 0; i < this.vars.allstacks; i++) {
etc...
That contains simple object data as well as object methods that define patterns and repetition (like multiple stacks, pyramids, etc) so it can't really be made into a JSON or other straight data file.
What I want to do is present a list of anchors/links to the player of the layout options. They'll click the link to get sent into /game with the layout they chose.
The method I thought of was to have /index send POST-data containing the layoutname. Then, in /game
router.post('/', function(req, res, next) { ... })
with the HTML template and res.render containing
<script src="js/gameboards/<layoutname>.js"></script>
to call the right file.
Is there a better way of doing this than what I listed above? It seems kludgey to me as if there would be a more 'nodey' and elegant way to do it.
To do it in the way you describe you need to enable a templating engine like handlebars or Pug. For pug I do something like this:
In server:
const cdnAction = process.env.S3_CDN_ACTIVE;
app.route('*')
.get( (req, res) => {
const cdnCSSPath = `https://s3.amazonaws.com/${process.env.S3_BUCKET_CDN}/audience.css.gz`;
res.render('home', { cdnActive, cdnPath, cdnCSSPath });
});
In pug file 'home.pug' rendered above:
head
if cdnActive === 'true'
link(href=cdnCSSPath rel='stylesheet' type='text/css')
else
link(href='/style/embed.css' rel='stylesheet' type='text/css')
I am using express-handlebars in my project and have the following problem:
Question
I want to be able to add <script> oder such tags to my overall views head from a partial that is called inside the view.
Example:
The view
{{#layout/master}}
{{#*inline "head-block"}}
<script src="some/source/of/script">
{{/inline}}
...
{{>myPartial}}
{{/layout/master}}
The view is extending another partial (layouts/master) that I use as a layout. It adds its content to that ones head block through the inline partial notation, which works fine
the Partial "myPartial
<script src="another/script/src/bla"></script>
<h1> HELLO </h1>
Now I would like that particular script tag in there to be added to my views head-block. I tried going via #root notation but can only reference context there. Not change anything.
I know I could use jquery or similar to just add the content by referencing the documents head and such. But I wanted to know if this is possible at all via Handlebars.
I do doubt it is in any way. But if you have any ideas or suggestions, please do send them my way! Many thanks!!!
UPDATE
This wont work if you have more than one thing injected into your layout / view. Since this happens when the browser loads the page, it creates some kind of raceconditions where the helpers has to collect the things that have to be injected into the parent file. If its not quick enough, the DOMTree will be built before the helper resolves. So all in all, this solution is NOT what I hoped for. I will research more and try to find a better one...
Here is how I did it. Thanks to Marcel Wasilewski who commented on the post and pointed me to the right thing!
I used the handlebars-extend-block helper. I did not install the package, as it is not compatible with express-handlebars directly (Disclaimer: There is one package that says it is, but it only threw errors for me)
So I just used his helpers that he defines, copied them from the github (I am of course linking to his repo and crediting him!) like so:
var helpers = function() {
// ALL CREDIT FOR THIS CODE GOES TO:
// https://www.npmjs.com/package/handlebars-extend-block
// https://github.com/defunctzombie/handlebars-extend-block
var blocks = Object.create(null);
return {
extend: function (name,context) {
var block = blocks[name];
if (!block) {
block = blocks[name] = [];
}
block.push(context.fn(this));
},
block: function (name) {
var val = (blocks[name] || []).join('\n');
// clear the block
blocks[name] = [];
return val;
}
}
};
module.exports.helpers = helpers;
I then required them into my express handlebars instance like so:
let hbsInstance = exphbs.create({
extname: 'hbs',
helpers: require('../folder/toHelpers/helpersFile').helpers() ,
partialsDir: partialDirs
});
Went into my central layout/master file that`is extended by my view Partial and added this to its <head> section
{{{block 'layout-partial-hook'}}}
(The triple braces are required because the content is HTML. Else handlebars wont recognize that)
Then in the partial itself I added things like so:
{{#extend "layout-partial-hook"}}
<link rel="stylesheet" href="/css/index.css"/>
{{/extend}}
And that did the trick! Thanks!!!
I am building a node application based on express using ejs as template engine.
To support different looks for the site I would like to put files in folders named base holding vanilla stuff and an overlay per style/theme/client. I want the system to lookup files in overlay first, and only if not found use what is in base.
For static content like images and css files this works using the static middleware twice, first for the overlay, then for base.
I want to do the same for templates rendered through ejs. I have found:
Multiple View paths on Node.js + Express
And BananaAcids answer provided in that thread almost works for me as long as I call simple ejs views. If I want to use layouts or includes it breaks down for overlaid views because the base directory is now overlay and layouts that are unchanged from base are no longer found.
A simplified example follows.
File base/layouts/root.ejs:
<!DOCTYPE html>
<html lang="en">
<body>
<!-- Main content of pages using this layout goes here -->
<%- body %>
</body>
</html>
File base/index.ejs:
<% layout('layouts/root') -%>
<p>
A page in base using the root layout
</p>
File overlay/index.ejs:
<% layout('layouts/root') -%>
<p>
Totally different page in the overlay.
</p>
Using BananaAcids approach and setting both paths as view-sources express/ejs now correctly locates overlay/index.ejs as the view to render but as I did not also overlay layouts/root it fails because the resulting file overlay/layouts/root.ejs does not exist.
Is there a way of patching my way further down into ejs so that I can help it locate this file in base/layout/root.ejs instead?
Thank you for reading this and any brain cycles you have expended on it.
Here's what I've used to monkey patch Express (4.x) to add layout support:
/*
Usage:
Set a global/default layout with:
app.set('view layout', 'foo');
Set a layout per-render (overrides global layout) with:
res.render('foo', { layout: 'bar' });
Or disable a layout if a global layout is set with:
res.render('foo', { layout: false });
If no layout is provided using either of the above methods,
then the view will be rendered as-is like normal.
Inside your layout, the variable `body` holds the rendered partial/child view.
Installation:
Call `mpResponse();` before doing `require('express');` in your application.
*/
function mpResponse() {
var expressResponse = require('express/lib/response'),
expressResRender = expressResponse.render;
expressResponse.render = function(view, options, fn) {
options = options || {};
var self = this,
req = this.req,
app = req.app,
layout,
cb;
// support callback function as second arg
if (typeof options === 'function')
fn = options, options = {};
// merge res.locals
options._locals = self.locals;
// default callback to respond
fn = fn || function(err, str) {
if (err) return req.next(err);
self.send(str);
};
if (typeof options.layout === 'string')
layout = options.layout;
else if (options.layout !== false
&& typeof app.get('view layout') === 'string')
layout = app.get('view layout');
if (layout) {
cb = function(err, str) {
if (err) return req.next(err);
options.body = str;
expressResRender.call(self, layout, options, fn);
};
} else
cb = fn;
// render
app.render(view, options, cb);
};
}
I patched EJS to support the multiple views folder feature added in Express v.4.10. There is currently a pending pull request you can find here: https://github.com/mde/ejs/pull/120. If you still need this solution for your project you could include my fork into your package.json as a EJS replacement:
{
...
"dependencies": {
"ejs": "git://github.com/MarcelloDiSimone/ejs.git#feature/multi-views"
}
}
..or you plus one the pull request and hope it'll be accepted soon.
I'm working on some project with Node.js, Express and Jade, where I'd like to seperate layout files. Inside the main file is already separated header, but I don't know how to do this for sublayout where I need to pass data. In this case I need to pass data to widgets for every view on page, but in the route would be too many things to load data into widgets instead of some easy solution which I'm looking for.
I could do this thing on the way which I described above - to load data in view with every request, but this is somehow time & cpu consuming.
Another way I'm thinking of is to create some sublayout for widgets in which I'd load data once and then would be available all the time without calling data from DB in all requests. What's the best way to do that?
I work with mustache but I think you can use a similar strategy that I do.In most of the mustache templates that I use there is a common header and footer section.Along with the scripts and css files.I have created a separate partials file that exports these partials
.For instance my partial file looks like this.
exports.partials = function (isAuthenticated)
{
var menu;
isAuthenticated ?
menu = {
header: '',
footer: ' '
} :
menu = {
header: '',
footer: ''
}
return menu;
};
exports.staticResources = {
bootstrap :'//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.2/css/bootstrap-combined.min.css',
fonts : '//netdna.bootstrapcdn.com/font-awesome/3.0/css/font-awesome.css',
jquery : '//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js'
};
I have another method called generatePartials which as the name suggest generate the partials for my templates
exports.generatePartials = function(isAuthenticated){
var menu = resources.partials(isAuthenticated);
var partials = {
header : menu.header,
footer : menu.footer,
bootstrap : resources.staticResources.bootstrap,
fonts :resources.staticResources.fonts,
jquery :resources.staticResources.jquery,
};
return partials;
};
Now while rendering the template all I have to do is this
app.get('/routeName',function (req, res){
var partials = require('../helpers').generatePartials(req.isAuthenticated());
return res.render('viewName.html', partials);
};
And that's it.
Using node.js I am passing some variables to jade view:
res.render('index', {
locals: {
name: user.name,
hashpassword: JSON.stringify(user.hashPass),
languages: JSON.stringify(langs)}
});
In jade file I have:
body
#heading
h1 nodechat
label !{locals.languages} // working - printing whole json string
#content
- var laangs = !{locals.languages} //not working here!
//SyntaxError: Unexpected token .
- each item in laangs
label= item.EnglishName
The problem is that I cannot pass locals.languages to a variable in jade file. If I assign it to a single html element (like label), it's working, but when I try with var = that doesn't work.
What may be the problem?
See my change below...
body
#heading
h1 nodechat
label !{locals.languages} // working - printing whole json string
#content
//- Do it like this...You're already in JavaScript land after the -
- var laangs = locals.languages
- each item in laangs
label= item.EnglishName
Change !{locals.languages} into locals.languages